Miroを自宅にも導入しようと思い立ち、自宅オンプレクラスタを持っているのでその上にデプロイしてみたので、その話をしてみる。
Excalidrawとは
OSSで手書き風スタイルのバーチャルホワイトボード。
https://github.com/excalidraw/excalidraw
セルフホストできる Miro オルタナティブを探してた時にたまたま見たサイトで1位になっていたので使ってみた。
AFFiNE でも良かったんだけど、そこまで多機能じゃなくてもいいかなと思った。
Namespace のマニフェストを用意する
Excalidraw用に専用の Namespace を作るマニフェスト。名前やラベルはそのまま Excalidraw にした。
apiVersion: v1
kind: Namespace
metadata:
name: excalidraw
labels:
app.kubernetes.io/name: excalidraw
Deployment のマニフェストを用意する
Pod は何台必要なのかやどのイメージを利用するかを記載した Deployment マニフェストを用意した。
apiVersion: apps/v1
kind: Deployment
metadata:
name: excalidraw
namespace: excalidraw
labels:
app.kubernetes.io/name: excalidraw
spec:
replicas: 1
revisionHistoryLimit: 3
selector:
matchLabels:
app.kubernetes.io/name: excalidraw
template:
metadata:
labels:
app.kubernetes.io/name: excalidraw
spec:
automountServiceAccountToken: false
containers:
- name: excalidraw
image: excalidraw/excalidraw:latest@sha256:f7ee194addd607bf831d2af0f0a34463dd4225e426cf35199ef0b12a803398e9
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 5
periodSeconds: 15
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 3
periodSeconds: 10
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
memory: 128Mi
このマニフェストでは個人利用なので Pod は1台で、Docker Hub の excalidraw/excalidraw:latest をインデックスダイジェストをつけて利用している。
また Excalidraw は静的ファイルのみの SPA なので PVC は使用していない。 同じ理由で Kubernetes API も使用しないのでfalseにしてる。
クラスタ内通信を行うために Service マニフェストを用意する
excalidraw Namespace 内にある対象 Pod への接続先を記載した Service マニフェストを用意した。
apiVersion: v1
kind: Service
metadata:
name: excalidraw
namespace: excalidraw
labels:
app.kubernetes.io/name: excalidraw
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: excalidraw
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
Service はクラスタ内部で 80番ポートへの接続を受け付け、対象 Pod の http ポート(この場合はコンテナの80番)へ転送する。
厳密には以下のような流れになる。
クライアントPod → Service:80 → 対象Pod:80 → Excalidrawコンテナ
HTTPリクエストした際の振る舞いを設定するために HTTPRoute マニフェストを用意する
Gatewayリスナーからバックエンドのネットワークエンドポイントに対するHTTPリクエストのルーティングの振る舞いを記載した HTTPRoute マニフェストを用意した。
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: excalidraw
namespace: excalidraw
spec:
parentRefs:
- name: shared-gateway
namespace: gateway
sectionName: https
hostnames:
- excalidraw.shooonng.com
rules:
- backendRefs:
- name: excalidraw
port: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: excalidraw-redirect
namespace: excalidraw
spec:
parentRefs:
- name: shared-gateway
namespace: gateway
sectionName: http
hostnames:
- excalidraw.shooonng.com
rules:
- filters:
- type: RequestRedirect
requestRedirect:
scheme: https
statusCode: 301
上段は https://excalidraw.shooonng.com へのリクエストを excalidraw Namespace の excalidraw Service の80番ポートへ転送する。
下段は http://excalidraw.shooonng.com とHTTPリスナーでリクエストが来た場合にHTTPSへリダイレクトする設定。
次のような通信構成になっている。
ブラウザ → shared-gateway の HTTPS リスナー → HTTPRoute → excalidraw Service:80 → Pod:80
構成を管理するために Kustomize マニフェストを用意する
複数のKubernetes マニフェストをまとめ、共通設定を適用して最終的なリソースを生成するために記載した Kustomization マニフェストを用意した。
kind: Kustomization
namespace: excalidraw
resources:
- namespace.yaml
- deployment.yaml
- service.yaml
- httproute.yaml
excalidraw Namespace に以下のリソースを生成している。
- Namespace
- Deployment
- Service
- HTTPRoute
マニフェストをデプロイする
GitOpsのためにArgoCDを用意してあるので、GitHubにpushすればそのままデプロイされる。
Podが立ち上がって、Curlが通ればデプロイできている。
# kubectl get pods -n excalidraw
NAME READY STATUS RESTARTS AGE
excalidraw-5bc7659688-9txtc 1/1 Running 0 10h
# curl -I https://excalidraw.shooonng.com
HTTP/1.1 200 OK
server: envoy
date: Sat, 17 Oct 2026 15:07:45 GMT
content-type: text/html
content-length: 6843
last-modified: Wed, 06 May 2026 21:19:55 GMT
etag: "69fbb07b-1abb"
accept-ranges: bytes
x-envoy-upstream-service-time: 0