Skip to content

Repository files navigation

try-k3s-on-cf-workers

Cloudflare Workers Containers 上に k3s (Kubernetes) の single-node クラスタを立てて、実際に Pod をデプロイして遊べるようにする実験プロジェクト。

  • クラスタ全部が 1 個の Cloudflare Container の中に閉じている(Firecracker microVM)
  • ブラウザ / curl / 手元の kubectl の 3 経路で操作できる
  • 公開エンドポイントは /services/<name>/ の Ingress ベース、内部管理系は /manage/ 配下に分離
  • 認証は Cloudflare Access(Service Token)で前段ガード
  • 追加の VM / K8s コントロールプレーンサービスに一切課金しない(Workers Paid のみ)

背景メモ:


できること

デプロイ後に手に入るもの:

URL 用途
https://<worker>.workers.dev/health 生存確認
https://<worker>.workers.dev/services/<name>/* クラスタに kubectl apply した Ingress ルールに沿って backend Service にルーティング
https://<worker>.workers.dev/manage/dashboard/ Kubernetes Dashboard
https://<worker>.workers.dev/manage/apiserver/* Kubernetes API を丸ごと reverse-proxy(kubectl で叩ける)
https://<worker>.workers.dev/manage/kubectl?args=... ブラウザで直接 kubectl を実行
https://<worker>.workers.dev/manage/status k3s サマリ (pods, nodes)

新しいサービスを公開したいとき: kubectl apply -f my-ingress.yaml(テンプレは docs/examples/)を叩くだけで /services/<name>/* から届くようになる。status.py などのアプリコードを触る必要は無い

アーキテクチャ

Cloudflare Worker  ──►  Container (Firecracker microVM, standard-2 = 1 vCPU / 6 GiB / 12 GB)
                          │
                          ├─ Python status.py (PID 1, port 8080)
                          │  ├─ /health, /manage/*, /services/*
                          │  ├─ /manage/apiserver → k3s apiserver reverse-proxy (HTTP + WebSocket)
                          │  ├─ /manage/dashboard → Kubernetes Dashboard reverse-proxy
                          │  └─ /services/*       → Traefik ClusterIP:80 (verbatim)
                          │
                          ├─ svcproxy (Go)
                          │  Services + EndpointSlices を polling → 各 ClusterIP を lo に追加
                          │  → TCP splice で pod endpoint に転送 (kube-proxy の代替)
                          │
                          └─ k3s server (all-in-one)
                             ├─ control plane: kube-apiserver + controller-manager + scheduler + SQLite
                             ├─ node: kubelet + containerd
                             └─ pods (containerd 上の nested container):
                                ├─ coredns
                                ├─ metrics-server
                                ├─ local-path-provisioner
                                ├─ kubernetes-dashboard (skip-login)
                                ├─ traefik (Ingress Controller)
                                └─ ... (kubectl apply したもの)

kube-proxy入っていないのがポイント。CF Container の kernel は netfilter モジュールが軒並み built-in に無く、内蔵の kube-proxy がどのモードでも動かない。かわりに container/svcproxy/ の Go 実装が Services を watch して userspace で TCP をリレーしている。

前提条件

  • Cloudflare Workers Paid プラン(Containers が Paid 限定機能)
  • Node.js 22 以上、npm、Docker(wrangler deploy がローカルビルドで使う)
  • macOS or Linux
  • (任意)Cloudflare Access — 誰にも見られたくない場合

セットアップ

# 1. clone してあなたの手元へ
git clone <this-repo> my-k3s
cd my-k3s

# 2. 依存インストール
npm install

# 3. Cloudflare にログイン
npx wrangler login

# 4. wrangler.jsonc の name を変える (worker のサブドメイン名になる)
#    "name": "my-k3s"   などに書き換え

# 5. deploy (初回は Docker ビルドで数分)
npx wrangler deploy

# 6. 動作確認 (deploy 完了時に表示された URL に対して)
curl https://my-k3s.<your-subdomain>.workers.dev/health
# {"ok":true}

初回は k3s + system pod の起動で 1-2 分かかる。/manage/status を叩けば apiserver / pod の状況が見える。

Cloudflare Access で保護する(任意だけど推奨)

生の URL を晒すと /manage/kubectl?args=... から誰でもクラスタを触れてしまう。CF Access で Service Token を要求する構成に:

  1. CF ダッシュボード → Zero Trust → Access → Applications で Self-hosted Application を追加
    • Application domain: my-k3s.<your-subdomain>.workers.dev
    • Policy: Service Auth タイプで一つ作り、下で発行する Service Token に紐付ける
  2. Zero Trust → Access → Service Auth → Service Tokens で Create Service Token
    • 発行された Client IDClient Secret をメモ
  3. .env.example.env にコピーして値を書き込む(または mise.toml / シェル profile へ):
    CF_ACCESS_CLIENT_ID=...
    CF_ACCESS_CLIENT_SECRET=...
    
  4. 以後、curl は -H "CF-Access-Client-Id: $CF_ACCESS_CLIENT_ID" -H "CF-Access-Client-Secret: $CF_ACCESS_CLIENT_SECRET" を毎回付ける。

使い方

ブラウザ

  • /manage/dashboard/ → Kubernetes Dashboard(skip-login で入れる)
  • /manage/status → pod / node 一覧の JSON
  • /services/<name>/ → 自分でデプロイしたアプリ

curl から kubectl サブコマンドを実行

curl -H "CF-Access-Client-Id: $CF_ACCESS_CLIENT_ID" \
     -H "CF-Access-Client-Secret: $CF_ACCESS_CLIENT_SECRET" \
     'https://my-k3s.<subdomain>.workers.dev/manage/kubectl?args=get+pods+-A'

手元の kubectl を使う

bin/access-proxy.py を立ち上げて、そこに向けて kubectl を叩く。

# 環境変数を設定 (.env をシェルで export するか、mise 経由で)
export K3S_CF_UPSTREAM=my-k3s.<subdomain>.workers.dev
export CF_ACCESS_CLIENT_ID=...      # CF Access を使う場合だけ
export CF_ACCESS_CLIENT_SECRET=...

# 別ターミナルで proxy を起動
python3 bin/access-proxy.py
# → https://127.0.0.1:8087 で listen

# トークンを発行 (cluster-admin。8h 有効)
TOKEN=$(curl -s -H "CF-Access-Client-Id: $CF_ACCESS_CLIENT_ID" \
             -H "CF-Access-Client-Secret: $CF_ACCESS_CLIENT_SECRET" \
             "https://$K3S_CF_UPSTREAM/manage/kubectl?args=create+token+kubectl-user+-n+kube-system+--duration=8h" \
        | python3 -c 'import json,sys; print(json.load(sys.stdin)["stdout"].strip())')

# kubectl から叩く
kubectl --server=https://127.0.0.1:8087/manage/apiserver \
        --token="$TOKEN" \
        --insecure-skip-tls-verify=true \
        get nodes

kubectl exec / logs -f / port-forward などの WebSocket 系操作も通る。

新しいサービスをデプロイする

# 例: nginx
kubectl create deployment nginx --image=nginx --port=80
kubectl expose deployment nginx --port=80

# public に出す (StripPrefix Middleware + Ingress)
kubectl apply -f docs/examples/ingress-nginx.yaml

# ブラウザで https://my-k3s.<subdomain>.workers.dev/services/nginx/

docs/examples/README.md に汎用テンプレあり。

料金の目安

Workers Paid ベース料金 $5/月 に加えて、Container の稼働時間分:

構成 月額目安(Workers Paid 込み)
standard-2 24/7 (現在のデフォルト、onActivityExpired no-op で常時起動) ≈ $50
standard-2 1 日 2 時間だけ (onActivityExpired オーバーライドを外して sleep 復活) ≈ $10
standard-1 24/7 (4 GiB 版) ≈ $38
basic 24/7 (1 GiB。dashboard/voicevox は無理) ≈ $12

Container 停止中は課金されない。「試すだけ」なら sleep 復活 (src/index.tsonActivityExpired オーバーライドを削除) がお得。

詳しくは https://developers.cloudflare.com/containers/pricing/

クリーンアップ

課金を止めたいときは Cloudflare のダッシュボードから:

  • Workers & Pages → 該当 Worker を Delete
  • Zero Trust → Access → Applications / Service Tokens を掃除

または CLI で:

npx wrangler delete

トラブルシューティング

  • wrangler deployNo changes to be made: image は変わってないという表示だが、container/manifests/*.yaml を編集した時は build cache に引っかかることがある。container/DockerfileRUN echo "cache-buster: ..." の値を bump して再 deploy。
  • /manage/* が 302 で *.cloudflareaccess.com に飛ぶ: CF Access ヘッダを付け忘れている。.env を export しているか確認。
  • kubectl401: create token して発行した SA トークンは 1〜8h で失効する。再発行する。
  • Pod が CrashLoopBackOff: 大概はメモリ不足。wrangler.jsoncinstance_typestandard-3(8 GiB)や standard-4(12 GiB)に上げる。
  • /services/<name>/ が 404: Ingress ルールが無い、または backend service 名が間違っている。kubectl get ingress -Akubectl get svc -A を突き合わせる。

ライセンス

MIT。詳細は LICENSE

Releases

Packages

Contributors

Languages