claude-code
Alpine ベースの軽量コンテナで Claude Code をリモート実行する Helm チャート。
ブラウザ(PC / スマートフォン)からアクセスし、セッションごとに独立した使い捨て Pod の中で Git リポジトリ(GitHub / Gitea など互換 Git 全般)をチェックアウト → 編集 → 検証 → プッシュします。 一定時間アイドルなセッションは Pod ごと自動破棄され、リソースを占有し続けません。 Raspberry Pi などの ARM ベアメタル Kubernetes を主な対象にしています。
設計目標 — Claude Code は本来ローカルで動かすものだが、これをクラスタにデプロイすることで、 Git 管理された複数プロジェクトを PC からもスマホからも横断的に、安全に触れるようにする。
- Gitea リポジトリ:
ssh://git@192.168.9.65/helmchart/claude-code - Helm リポジトリ:
https://git.cafepieters.com/api/packages/helmchart/helm - セッションイメージ:
git.cafepieters.com/helmchart/claude-code/session:<appVersion>
目次
- アーキテクチャ
- セッションのライフサイクル
- セキュリティモデル
- Claude 認証(API 課金なし / サブスクリプション)
- 管理画面へのアクセス(LoadBalancer / Ingress)
- インストール
- 使い方
- values リファレンス
- セッションイメージ
- 運用
- トラブルシューティング
- リリースフロー
- バージョン履歴
アーキテクチャ
┌──────────── リリース NS ────────────┐
ブラウザ (PC/スマホ) │ Deployment: claude-code │
│ HTTPS/WSS │ ┌──────────┐ ┌───────────────┐ │
▼ │ │ gateway │──▶│ manager │ │
Ingress / LB ──────────▶│ │ (nginx) │ │ (python stdlib)│ │
│ │ 認証 │ │ - Web UI │ │
│ │ WS proxy │ │ - Session API │ │
│ └────┬─────┘ │ - 回収ループ │ │
│ │ └───────┬────────┘ │
│ │ ttyd(WS) │ K8s API │
└───────┼─────────────────┼───────────┘
▼ ▼ (Role: pods/services)
┌───────── セッション NS (restricted PSA) ──────────┐
│ Pod claude-sess-ab12cd34ef Pod claude-sess-… │
│ ┌───────────────────────┐ │
│ │ alpine + node + git │ git clone │
│ │ + ttyd + claude-code │ → claude (ttyd) │
│ │ 非root / roFS / netpol│ → claude-verify/push │
│ └───────────────────────┘ アイドルで自壊 │
└────────────────────────────────────────────────┘
| コンポーネント | イメージ | 役割 |
|---|---|---|
| gateway | nginxinc/nginx-unprivileged:*-alpine |
単一入口。auth.mode に応じた認証、/s/<id>/ をセッション Pod の ttyd へ WebSocket リバースプロキシ、リクエスト毎にアクティビティ時刻を更新 |
| manager | python:3.x-alpine(標準ライブラリのみ・追加 pip 不要) |
モバイル対応 Web UI、セッション作成/一覧/破棄 API、アイドル/寿命の回収ループ。in-cluster ServiceAccount で Kubernetes API を直接操作 |
| session | git.cafepieters.com/helmchart/claude-code/session(既定)または任意の node:alpine 系 |
セッションごとに作られる 使い捨て Pod。git clone → claude を ttyd でブラウザに提供 |
| reaper | manager と同じイメージ(--reap-once) |
manager 障害時の保険となる孤児掃除 CronJob |
manager / gateway / session-entrypoint の実体は files/ にあり、.Files.Get で ConfigMap 化されます。
セッションのライフサイクル
- 作成 — UI か
POST /api/sessionsにrepo/ref/identityを渡す。manager は- リポジトリ URL の形式と Git ホスト allow-list(
git.allowedHosts)を検証 - 同時セッション数(
session.maxSessions)を確認 - セッション NS に Pod(
claude-sess-<10hex>)と Service を作成
- リポジトリ URL の形式と Git ホスト allow-list(
- ブート(
files/session-entrypoint.sh, 非 root)- マウントされた SSH 鍵 / HTTPS トークンで git 認証を構成(
known_hosts必須、TOFU 無効) - リポジトリを
session.workspaceDirにclone(--filter=blob:none) claudeが無ければ$HOMEに非 root で npm インストールttydを起動(session.autoStartClaude: trueならclaudeを自動起動)
- マウントされた SSH 鍵 / HTTPS トークンで git 認証を構成(
- 利用 —
/s/<id>/経由でブラウザ内ターミナルに接続。claude-verify/claude-pushが使える - 破棄 — 次のいずれかで Pod ごと消滅(Service も回収)
- アイドル:
ttydへの接続がsession.idleTimeoutSeconds秒間ゼロ → Pod 内ウォッチドッグが自壊 - アイドル(保険): manager の回収ループが
last-activityアノテーションを見て削除 - 最大寿命:
session.maxLifetimeSeconds(Pod のactiveDeadlineSeconds)到達で強制終了 - 手動: UI の「破棄」/
DELETE /api/sessions/<id> - 孤児掃除: 予備 CronJob(
session.reaper.cronjob)
- アイドル:
セキュリティモデル
| 層 | 対策 |
|---|---|
| 入口 | すべて gateway 経由。auth.mode= token(既定)/ basic / forward(外部 SSO へ委譲)/ none。WebSocket は Cookie トークンで認証(ブラウザは WS に Authorization ヘッダを付けられないため) |
| 制御プレーン RBAC | manager はセッション NS の pods / services のみ操作する Role(ClusterRole 不使用)。他 NS・他リソースには触れない |
| セッション Pod | automountServiceAccountToken: false(API サーバー到達不可)、runAsNonRoot / readOnlyRootFilesystem / capabilities.drop: [ALL] / seccompProfile: RuntimeDefault。書き込み可能なのは emptyDir(/workspace, $HOME, /tmp)のみ |
| ネットワーク | NetworkPolicy でセッションの ingress は gateway からのみ。egress は DNS + HTTPS(443, git/Anthropic) + SSH(22)。networkPolicy.blockedEgressCIDRs(既定でクラウドメタデータ IP 169.254.169.254/32)を egress の except に指定。自クラスタの Pod/Service CIDR を追加すると内部 SSRF も遮断できる |
| Git 到達先 | git.allowedHosts の allow-list。既定は allow-list 外を拒否(allowArbitraryRepos: true で解除) |
| 認証情報 | ANTHROPIC_API_KEY / SSH 鍵 / トークンは Kubernetes Secret。values.yaml に平文で書かず --set / --set-file / existingSecret で注入。git HTTPS トークンはセッション内で credential.helper store の 0600 ファイルに限定し、プロセス環境に残さない |
| PodSecurity | セッション NS に pod-security.kubernetes.io/enforce: restricted を付与(sessionsNamespace.create: true 時) |
| ブラスト半径 | ResourceQuota(Pod / Service 数)+ LimitRange(コンテナ CPU/メモリ上限)+ session.maxSessions |
| 自動 push | 既定 無効。有効時もデフォルト/保護ブランチ(autoPush.protectedBranches)への push と --force は不可。branchPrefix 配下の作業ブランチにのみ push |
注意 —
values.yamlに API キーや秘密鍵を書いてコミットしないでください。helm get values/ CI ログ / Git 履歴に残ります。existingSecretか CI からの--setを使ってください。
Claude 認証(API 課金なし / サブスクリプション)
このチャートは Claude の API(従量課金)を使わずに、ローカルの Claude Code と同じ
サブスクリプション認証(Pro / Max プラン) で動かせます。セッション内の claude の
挙動・機能はローカル実行と同一です(同じ CLI・同じログイン方式)。
anthropic.authMode は 3 つ:
| モード | 注入するもの | トークン更新 | 課金 |
|---|---|---|---|
credentials(既定・推奨) |
~/.claude/.credentials.json 全体(refreshToken 込み) |
セッション内 claude が自己更新 + クラスタ内 refresher CronJob が Secret を自動更新 |
サブスクリプション枠(API 課金なし) |
oauth |
CLAUDE_CODE_OAUTH_TOKEN(claude setup-token の単発トークン) |
自動更新なし(refreshToken を含まない)。失効したら手動再投入 |
サブスクリプション枠 |
apiKey |
ANTHROPIC_API_KEY(sk-ant-...) |
— | API 従量課金 |
credentials モードの使い方(推奨)
-
claude にログイン済みの手元 PC の認証情報ファイルをそのまま渡します (このファイルには
accessTokenと、それを更新するためのrefreshTokenが入っています):# 場所: Linux/macOS = ~/.claude/.credentials.json # Windows = %USERPROFILE%\.claude\.credentials.json # (macOS でキーチェーン保存の場合は Keychain の "Claude Code" 項目を書き出す) helm upgrade --install claude-code cafepieters/claude-code -n claude-code --create-namespace \ -f examples/loadbalancer-oauth.values.yaml \ --set-file anthropic.credentialsJson="$HOME/.claude/.credentials.json" -
これだけで トークンが定期的に変わっても再デプロイは不要 です。仕組み:
- セッション Pod は生成のたびに Secret を読むため、常に最新の認証情報で起動します。
- セッション内の
claudeは、accessTokenが失効していればrefreshTokenで 自動更新します(そのセッションの間だけ有効。$HOME/.claude/.credentials.jsonに書き戻す)。 - refresher CronJob(
anthropic.refresher、既定 30 分ごと)が、Secret 内のrefreshTokenを使ってクラスタ内でaccessTokenを更新し、Secret 自体を書き戻します。 これにより手元 PC がオフラインでも、新規セッションは常に有効なトークンで起動できます。
-
既存 Secret を使う場合:
kubectl -n claude-code-sessions create secret generic claude-creds \ --from-file=.credentials.json="$HOME/.claude/.credentials.json" # values: anthropic.existingSecret=claude-creds # (refresher に patch させるため RBAC はこの Secret 名に限定して付与される)
注意 / 制約
- 利用枠はローカルの Claude Code と共有です。同時セッションを増やすとプランの レート上限に早く達します。
session.maxLifetimeSeconds(既定 12h)がaccessTokenの寿命より長いと、長時間 セッションが自分でトークンを更新し、refreshTokenのローテーションで refresher 側の 値が一時的に古くなることがあります。refresher は短い周期(既定 30 分)で追従します。- refresher は Claude Code CLI の OAuth クライアント値(
anthropic.refresher.tokenUrl/clientId)に依存します。これらは非公式で、仕様変更により動かなくなる可能性があります。 その場合でもセッション内claudeの自己更新は独立して機能します(新規セッションの 起動時トークンが古い場合のみ影響)。不要ならanthropic.refresher.enabled: false。 - refresher の権限は セッション NS の認証情報 Secret 1 個への
get/patchのみ (resourceNamesで限定した Role)。 refreshToken自体が失効・失効させられた場合は、手元 PC でclaudeに再ログインして--set-file anthropic.credentialsJson=...で再投入します。- Secret の中身は refresher が書き換えます。
helm upgradeで--set-fileを付けない限り Helm は Secret の値を上書きしません(helm.sh/resource-policy: keep+ 3-way merge)。
管理画面へのアクセス(LoadBalancer / Ingress)
管理画面(gateway)への到達経路は 2 つあり、併用できます。既定は LoadBalancer です。
A. LoadBalancer でローカルネットワークの IP を使う(既定・Ingress 不要)
service.type: LoadBalancer(既定)にすると、クラスタの LB 実装
(kube-vip / MetalLB / Cilium LB-IPAM など)がローカルネットワークの IP を払い出します。
その IP をブラウザで開くだけでアクセスできます。ドメインも Ingress も TLS 終端も不要です。
ingress:
enabled: false # Ingress は張らない
service:
type: LoadBalancer
port: 80
annotations:
kube-vip.io/loadbalancerIPs: "192.168.9.50" # 払い出す VIP(空きアドレス)
kube-vip.io/vipHost: "cpi11"
loadBalancerSourceRanges: # 接続元をローカルセグメントに限定(第一の防御線)
- 192.168.9.0/24
externalTrafficPolicy: Local # 実クライアント IP を保持
kubectl -n claude-code get svc claude-code # EXTERNAL-IP を確認
# → http://<EXTERNAL-IP>/ をブラウザで開く
| キー | 説明 |
|---|---|
service.type |
LoadBalancer(既定)/ ClusterIP / NodePort |
service.port |
待受ポート(既定 80) |
service.annotations |
LB 実装へのヒント(kube-vip の VIP 指定、MetalLB のプール指定など) |
service.loadBalancerIP |
払い出す IP を直接指定(annotations 指定を推奨) |
service.loadBalancerSourceRanges |
接続を許可する CIDR。Ingress を張らない構成では必ず設定して接続元を絞る |
service.loadBalancerClass |
複数 LB 実装がある場合の指定 |
service.externalTrafficPolicy |
Local で実クライアント IP を保持 |
service.nodePort |
type: NodePort 時の固定ポート |
セキュリティ — LoadBalancer は L3/L4 でネットワークに口を開けます。
loadBalancerSourceRangesで接続元を絞り、auth.modeはtoken(既定)以上にしてください。VIP セグメントは ルーターの DHCP 範囲と重複させないこと。
B. Ingress でドメインアクセス(従来どおり維持)
ingress.enabled: true で Ingress を作成します。A と同時に有効化しても構いません。
WebSocket 用に proxy-read-timeout / proxy-send-timeout を既定 3600 に設定します
(ingress.annotations で上書き可)。TLS は ingress.tls で設定します。
ingress:
enabled: true
className: nginx
hosts:
- host: claude-code.example.com
paths: [{ path: /, pathType: Prefix }]
tls:
- secretName: claude-code-tls
hosts: [claude-code.example.com]
インストール
前提
- Kubernetes 1.25+(PodSecurity admission)、NetworkPolicy 対応 CNI(Calico / Cilium など)
- 管理画面の公開方法に応じて どちらか:
- LoadBalancer 実装(kube-vip / MetalLB / Cilium LB-IPAM など) … 既定
- Ingress コントローラー …
ingress.enabled: trueの場合 - どちらも無い場合は
service.type: ClusterIP+kubectl port-forwardで利用可
- Claude の認証情報 …
~/.claude/.credentials.json(既定credentialsモード・推奨) またはclaude setup-tokenの OAuth トークン、ANTHROPIC_API_KEY
Helm リポジトリから
helm repo add cafepieters https://git.cafepieters.com/api/packages/helmchart/helm
helm repo update
helm upgrade --install claude-code cafepieters/claude-code \
-n claude-code --create-namespace \
-f my.values.yaml \
--set anthropic.apiKey="$ANTHROPIC_API_KEY"
ローカル LB の IP + サブスクリプション認証(API 不要・推奨構成)
# 手元の PC は claude にログイン済みであること(~/.claude/.credentials.json が存在)
helm upgrade --install claude-code cafepieters/claude-code \
-n claude-code --create-namespace \
-f examples/loadbalancer-oauth.values.yaml \
--set-file anthropic.credentialsJson="$HOME/.claude/.credentials.json" \
--set-file git.identities.gitea-ssh.sshPrivateKey=P:/Claude/.ssh/id_claude \
--set-file git.identities.gitea-ssh.knownHosts=./known_hosts.gitea
以降トークンが変わっても再デプロイは不要です(refresher CronJob が Secret を追従更新)。
examples/loadbalancer-oauth.values.yaml は Ingress 無効・service.type: LoadBalancer +
kube-vip の VIP 指定 + loadBalancerSourceRanges によるローカルセグメント限定の構成です。
ローカルチャートから(GitHub 公開リポジトリだけ触る最小構成)
helm upgrade --install claude-code ./claude-code \
-n claude-code --create-namespace \
-f examples/minimal.values.yaml \
--set anthropic.apiKey="$ANTHROPIC_API_KEY"
Gitea(SSH) + GitHub(HTTPS) 両対応
helm upgrade --install claude-code ./claude-code \
-n claude-code --create-namespace \
-f examples/gitea-and-github.values.yaml \
--set anthropic.apiKey="$ANTHROPIC_API_KEY" \
--set-file git.identities.gitea-ssh.sshPrivateKey=P:/Claude/.ssh/id_claude \
--set-file git.identities.gitea-ssh.knownHosts=./known_hosts.gitea \
--set git.identities.github-https.token="$GITHUB_PAT"
known_hosts は事前に取得します:
ssh-keyscan 192.168.9.65 > known_hosts.gitea
使い方
-
管理画面を開く:
- LoadBalancer(既定):
kubectl -n claude-code get svc claude-codeのEXTERNAL-IPを ブラウザで開く(http://<IP>/) - Ingress: 設定したドメイン(
https://claude-code.example.com/) - どちらも無効:
kubectl -n claude-code port-forward svc/claude-code 8080:80→http://localhost:8080/
- LoadBalancer(既定):
-
auth.mode: tokenの場合、トークンを取得して UI に貼り付け(または?token=で開く):kubectl -n claude-code get secret claude-code-auth \ -o jsonpath='{.data.token}' | base64 -d ; echo -
UI で リポジトリ URL / ブランチ / 認証 identity を指定して「セッション作成」。
-
一覧の「開く」でブラウザ内ターミナル(Claude Code)に接続。
-
セッション内の補助コマンド:
claude-verify…session.verifyCommand(例npm test)を実行claude-push… 検証 →claude/<branch>へ push(保護ブランチ /--force不可)
-
無操作
session.idleTimeoutSeconds秒でセッション(Pod)は自動的に消えます。
values リファレンス
image
| キー | 既定 | 説明 |
|---|---|---|
image.gateway.* |
nginxinc/nginx-unprivileged:1.29.1-alpine |
受付 nginx(非 root 版) |
image.manager.* |
library/python:3.13-alpine |
制御プレーン(標準ライブラリのみ) |
image.session.* |
library/node:22-alpine |
セッション。既定はブート時に apk/npm を実行するため初回起動が遅い。セッションイメージ参照 |
imagePullSecrets |
[] |
プライベートレジストリ用 |
gateway / manager
| キー | 既定 | 説明 |
|---|---|---|
gateway.replicaCount |
1 |
制御プレーン Deployment のレプリカ数 |
gateway.port |
8080 |
gateway コンテナ待受ポート(Service / Ingress バックエンド) |
manager.logLevel |
info |
debug / info / warning / error |
gateway.resources / manager.resources |
控えめ | それぞれの resources |
sessionsNamespace
| キー | 既定 | 説明 |
|---|---|---|
sessionsNamespace.name |
claude-code-sessions |
セッション Pod を隔離する NS(空 = リリース NS) |
sessionsNamespace.create |
true |
この NS を作成し PodSecurity ラベルを付与するか。既存 NS を使うなら false にして手動でラベル付与 |
sessionsNamespace.podSecurityLevel |
restricted |
付与する PodSecurity enforce レベル |
sessionsNamespace.resourceQuota.enabled |
true |
セッション NS の ResourceQuota(既定は Pod/Service 数の抑制のみ。compute 集計上限は hard で明示) |
sessionsNamespace.resourceQuota.hard |
{} |
指定するとそのまま spec.hard |
sessionsNamespace.limitRange.* |
max 4CPU/4Gi | セッション NS のコンテナ既定値・上限(LimitRange) |
auth — アクセス認証
| キー | 既定 | 説明 |
|---|---|---|
auth.mode |
token |
token / basic / forward / none |
auth.token.value |
"" |
空なら install 時に自動生成(lookup で再 install 間も安定)。運用では existingSecret 推奨 |
auth.token.existingSecret / existingSecretKey |
"" / token |
既存 Secret からトークンを読む |
auth.basic.users |
[] |
htpasswd -nbB user pass の出力を 1 行以上 |
auth.basic.existingSecret / existingSecretKey |
"" / htpasswd |
既存 htpasswd Secret |
auth.forward.url |
"" |
auth_request の転送先(例: oauth2-proxy の /oauth2/auth) |
auth.forward.signInUrl |
"" |
未認証時のサインイン URL(任意) |
token モードでは UI のログインフォームで一度トークンを入力すると、HttpOnly Cookie に保存され
以降の /s/<id>/(WebSocket 含む)に自動的に付与されます。
anthropic — Claude Code 認証情報
詳細は Claude 認証(API 課金なし / サブスクリプション) を参照。
| キー | 既定 | 説明 |
|---|---|---|
anthropic.authMode |
credentials |
credentials(~/.claude/.credentials.json・推奨)/ oauth(CLAUDE_CODE_OAUTH_TOKEN)/ apiKey(ANTHROPIC_API_KEY) |
anthropic.credentialsJson |
"" |
--set-file anthropic.credentialsJson=$HOME/.claude/.credentials.json で注入 |
anthropic.apiKey / anthropic.oauthToken |
"" |
--set / --set-file で注入 |
anthropic.existingSecret |
"" |
既存 Secret を使う(キー名は existingSecretKeyCredentials(既定 .credentials.json)/ ...ApiKey / ...OauthToken) |
anthropic.refresher.enabled |
true |
credentials モードで、Secret 内 refreshToken を使いトークンを定期更新する CronJob を作成 |
anthropic.refresher.schedule |
*/30 * * * * |
refresher CronJob のスケジュール |
anthropic.refresher.refreshIfExpiresInSeconds |
3600 |
accessToken の残り寿命がこの秒数未満なら更新 |
anthropic.refresher.tokenUrl / clientId |
Claude Code CLI の公開値 | OAuth トークンエンドポイントとクライアント ID(非公式・仕様変更で要更新) |
anthropic.extraEnv |
{} |
全セッションへ渡す追加 env(例 ANTHROPIC_BASE_URL、Bedrock/Vertex 切替など) |
Secret は セッション NS に作成されます。credentials モードはファイルとしてマウントし
セッション起動時に $HOME/.claude/.credentials.json へコピー(書き込み可能)、
apiKey / oauth モードは envFrom で環境変数として注入します。
git — 接続先と認証アイデンティティ
| キー | 既定 | 説明 |
|---|---|---|
git.allowedHosts |
GitHub/GitLab/Gitea | clone を許可するホスト allow-list |
git.allowArbitraryRepos |
false |
allow-list を無効化し任意 URL を許可(非推奨) |
git.defaultAuthorName / Email |
Claude / claude@cafepieters.com |
コミット author 既定 |
git.guiIdentities.enabled |
true |
Web UI から identity を登録・削除できるようにする(下記) |
git.identities.<name>.type |
— | ssh / https-token(Helm 管理・UI では読み取り専用) |
…ssh.sshPrivateKey / knownHosts |
— | PEM 秘密鍵 / ssh-keyscan 出力(knownHosts 必須) |
…https-token.username / token |
x-access-token / — |
HTTPS ベーシック(GitHub PAT は username 任意) |
…existingSecret ほか |
— | 既存 Secret 参照(キー名は existingSecretKey* で調整) |
GitHub でも Gitea でも GitLab でも、SSH か HTTPS トークンで認証できる Git ホストなら同じ仕組みで扱えます。 セッション作成時に identity 名を選ぶと、その Secret だけがそのセッション Pod にマウントされます。
GUI からの identity 登録(git.guiIdentities.enabled: true)
Web UI の 「認証情報(identity)」カード から、SSH 秘密鍵 + known_hosts、または
HTTPS トークンを登録・削除できます。helm を触らずにセッションで使う鍵を追加できます。
- 登録内容はセッション NS の Secret(ラベル
claude-code.cafepieters.com/identity-source: gui) として保存され、git.identities(Helm 管理・identity-source: helm)と同じ一覧に並びます。 Helm 管理のものは UI から削除できません。 - セキュリティ: 秘密鍵はブラウザ → gateway → manager をリクエストボディで通ります。
経路の TLS(Ingress の
tls/ LB の TLS 終端)を必ず有効にしてください。 有効化すると manager にセッション NS のsecretsへのget/list/create/update/delete権限が付きます(同 NS には Claude 認証情報 Secret も存在)。不要ならenabled: false。 known_hostsは登録時必須です(ssh-keyscan <host>の出力を貼り付け。ホスト鍵 TOFU 無効化)。
URL の形式(git.identities で選んだ鍵で認証されます):
| 形式 | 例 | identity |
|---|---|---|
| SSH(SCP 記法) | git@192.168.9.65:websites/e-stay-tours.git |
ssh 型 |
| SSH(URL 記法) | ssh://git@192.168.9.65/websites/e-stay-tours.git |
ssh 型 |
| HTTPS | https://git.cafepieters.com/websites/e-stay-tours.git |
https-token 型(プライベート時) |
SSH のユーザー名は 常に
gitです(Gitea・GitHub 共通)。自分の Gitea アカウント名 (claude@…など)ではありません。認証は identity の鍵で行われるため、その鍵を持つ アカウントが対象リポジトリの参照権限を持っている必要があります。
session — 使い捨てセッション
| キー | 既定 | 説明 |
|---|---|---|
session.autoStartClaude |
true |
ブート後に claude を自動起動(false はログインシェル) |
session.workspaceDir |
/workspace |
clone 先 |
session.ttydPort |
7681 |
ttyd 待受(Pod 内のみ) |
session.extraApkPackages |
[] |
追加 apk(事前ビルドイメージなど root 権限がある場合のみ有効) |
session.claudeCodeVersion |
"" |
インストールする @anthropic-ai/claude-code バージョン(空 = チャート appVersion) |
session.resources |
100m/256Mi 〜 2CPU/2Gi/4Gi(eph) | セッション Pod の resources |
session.idleTimeoutSeconds |
1800 |
無通信での破棄しきい値 |
session.maxLifetimeSeconds |
43200 |
Pod の activeDeadlineSeconds |
session.reaper.intervalSeconds |
60 |
manager 回収ループ間隔 |
session.reaper.cronjob.enabled / schedule |
true / */15 * * * * |
予備の孤児掃除 CronJob |
session.maxSessions |
10 |
同時セッション数上限(manager が enforce) |
session.verifyCommand |
"" |
claude-verify / claude-push が実行するコマンド |
session.autoPush.enabled |
false |
claude-push を許可するか |
session.autoPush.branchPrefix |
claude/ |
push 先ブランチのプレフィックス |
session.autoPush.protectedBranches |
[main, master, develop, release] |
push 禁止ブランチ |
session.podLabels / podAnnotations / nodeSelector / tolerations / priorityClassName |
— | セッション Pod へのメタデータ / スケジューリング |
networkPolicy
| キー | 既定 | 説明 |
|---|---|---|
networkPolicy.enabled |
true |
NetworkPolicy を作成するか(CNI が対応必須) |
networkPolicy.dnsNamespaceSelector |
kube-system |
DNS を許可する NS セレクタ |
networkPolicy.allowInternetHttps |
true |
セッションの 443/tcp 外向きを許可(git HTTPS + Anthropic API) |
networkPolicy.allowSshGit |
true |
セッションの 22/tcp 外向きを許可(SSH git) |
networkPolicy.blockedEgressCIDRs |
[169.254.169.254/32] |
egress except。自クラスタの Pod/Service CIDR を足すと内部 SSRF を遮断 |
networkPolicy.sessionEgressExtra |
[] |
追加許可(社内ミラー / プロキシ)。{cidr, ports[]} |
完全にインターネットを遮断したい場合は allowInternetHttps: false にして egress プロキシ
(例: 許可リスト付き Squid)を sessionEgressExtra で通してください。
ingress / service
→ 管理画面へのアクセス(LoadBalancer / Ingress) を参照。
service.type の既定は LoadBalancer、ingress.enabled の既定は false(両者は併用可)。
セッションイメージ
既定の image.session は CI(session-image.yaml)がビルドする 事前ビルドイメージ
git.cafepieters.com/helmchart/claude-code/session:<appVersion> です。
git / openssh-client / ttyd / bash / node / @anthropic-ai/claude-code を同梱し、
非 root(uid 10001)で動作するため、restricted PodSecurity / readOnlyRootFilesystem 下でも
確実に起動し、コールドスタートも数秒です。
session-image.yaml は files/Dockerfile.session 等の変更 push、または
image-update-and-release.yaml による Chart.yaml 更新で発火し、linux/amd64,linux/arm64 の
2 アーキテクチャでビルドして Gitea Container Registry へ push します。手動ビルドは:
docker buildx build --platform linux/amd64,linux/arm64 \
--build-arg CLAUDE_CODE_VERSION=2.1.259 \
-f files/Dockerfile.session \
-t git.cafepieters.com/helmchart/claude-code/session:2.1.259 --push .
事前ビルドを使わない場合
image.session に素の node:22-alpine などを指定することもできます。この場合ブート
スクリプトが $HOME に非 root で @anthropic-ai/claude-code を npm インストールします
(初回数十秒)。ただし ttyd は非 root では apk add できない ため、ttyd を含まない
イメージでは起動に失敗します。ttyd 入りの独自イメージを用意するか、事前ビルドイメージを
使ってください。
運用
- セッション一覧:
kubectl -n <sessionsNamespace> get pods -l app.kubernetes.io/component=session - セッションのログ:
kubectl -n <sessionsNamespace> logs claude-sess-<id> - 強制破棄: UI から、または
kubectl -n <sessionsNamespace> delete pod,svc claude-sess-<id> - 回収の確認: manager のログに
reaping session <id> (idle|max-lifetime|phase=...) - トークンのローテーション:
auth.token.existingSecretの値を更新し、Deployment を再起動 - アップグレード:
helm upgrade。files/*の変更は checksum アノテーションで自動ローリング
トラブルシューティング
| 症状 | 原因と対処 |
|---|---|
セッションを「開く」と 502 / 一覧で Failed になる |
セッション Pod のブートが失敗している。UI の 「ログ」ボタン(または kubectl logs -n <sessionsNamespace> claude-sess-<id>)で死因を確認。終了 Pod は session.reaper.terminatedRetentionSeconds(既定 900s)残る |
clone が ~60〜130 秒後にタイムアウトして Failed |
セッション Pod から Git ホストへの egress が届いていない。多くは NetworkPolicy(Cilium 等) が ipBlock egress を通していない。--set networkPolicy.enabled=false で切り分け。恒久対応は networkPolicy.sessionEgressExtra に対象 CIDR を追加、または Cilium 環境なら egress ルールの調整 |
clone が could not read Username / Authentication failed |
プライベートリポジトリに認証情報が無い。HTTPS URL には https-token identity(Gitea/GitHub の PAT)を、SSH URL(git@host:... / ssh://git@host/...)には ssh identity を選ぶ。URL のスキームと identity の型を一致させる |
clone が repository ... not found / does not appear to be a git repository |
パス誤り、またはその identity のアカウントに対象リポジトリの参照権限が無い |
UI が 502(/s/... のログが gateway に出ない) |
前段(LB / Ingress)が実体の無いセッションに返している。まず一覧で該当セッションが Running か、上記でブート失敗していないか確認 |
セッション作成が 許可されていない Git ホスト |
git.allowedHosts に対象ホストを追加(IP と FQDN は別物) |
clone が Host key verification failed |
identity の knownHosts が対象ホストの鍵と不一致。ssh-keyscan <host> で取り直す |
| WebSocket がすぐ切れる | Ingress の proxy-read-timeout を延長。auth.mode: token で Cookie 未設定なら一度 UI からログイン |
| セッションが破棄されない | manager の RBAC / ログを確認。予備 CronJob(session.reaper.cronjob)が有効か |
apk エラーがログに出る |
既定イメージ + readOnlyRootFilesystem の制約。事前ビルドイメージを使う |
| manager が全リクエストを 401 | auth.mode: token でトークンが空。auth.token.value か existingSecret を設定 |
リリースフロー
Chart.yaml の version / appVersion は Claude Code CLI(@anthropic-ai/claude-code npm 最新版)と同一。
helm-release.yaml—mainpush 時に Gitea Package Registry へチャートを publish(全リポジトリ共通)。image-update-and-release.yaml— 毎週月曜 3:00 JST。- npm の Claude Code 最新版に追従。主アプリ更新時のみ
Chart.yaml更新 → セッションイメージ multi-arch ビルド/push → Git タグv<ver>→ Gitea リリース → チャート publish。 nginx/pythonの Alpine タグのみ更新の場合はvalues.yamlを更新してコミットするだけ (リリースなし)。
- npm の Claude Code 最新版に追従。主アプリ更新時のみ
- 手動リリース(臨時) — バージョン末尾にアルファベットを付与(
2.1.259→2.1.259-a)。 対応するCHANGELOG-<version>.mdを追加する。
必要な Gitea Secrets: GITEA_TOKEN / REGISTRY_USER / REGISTRY_TOKEN。
ワークフロー一覧
| ファイル | トリガー | 動作 |
|---|---|---|
helm-release.yaml |
main への push / 手動 |
helm lint。Chart.yaml の version が未リリース(Gitea リリース・Package Registry 双方に無い)なら タグ + Gitea リリース + チャート publish。リリース済みなら lint のみ |
image-update-and-release.yaml |
毎週月曜 3:00 JST / 手動 | npm の @anthropic-ai/claude-code 最新版と base image(nginx / python)の最新 Alpine タグを確認。Claude Code の更新があれば values.yaml / Chart.yaml / README 履歴を更新してコミット → タグ + リリース + publish。base image のみの更新は values.yaml を更新してコミットのみ(リリースなし) |
session-image.yaml |
files/Dockerfile.session ほかの変更 / 手動 |
セッションイメージ(.../claude-code/session)を linux/amd64,linux/arm64 でビルドし、Chart.yaml の appVersion タグ + latest で Gitea Container Registry へ push |
バージョン履歴
| バージョン | Claude Code | 変更内容 |
|---|---|---|
| 2.1.302 | 2.1.302 | 自動更新 |
| 2.1.296 | 2.1.296 | 自動更新 |
| 2.1.289 | 2.1.289 | 自動更新 |
| 2.1.283 | 2.1.283 | 自動更新 |
| 2.1.278 | 2.1.278 | 自動更新 |
| 2.1.270 | 2.1.270 | 自動更新 |
| 2.1.263-f | 2.1.263 | Web UI から git identity(SSH 秘密鍵 + known_hosts / HTTPS トークン)を登録・削除できる「認証情報」カードを追加(git.guiIdentities.enabled、既定 true)。GET/POST/DELETE /api/identities。GUI 登録分はセッション NS の Secret(identity-source: gui)として保存し、Helm 管理分と同じ一覧に統合。manager RBAC に同 NS の secrets CRUD を条件付き付与 |
| 2.1.263-e | 2.1.263 | リポジトリ URL バリデーションを緩和(ssh://user@… / user@host:path / host:path も受理。ホスト allowlist は従来どおり適用)。エラーメッセージに受理形式と「SSH ユーザーは常に git」の注意を明記。UI の入力欄にヒントを追加 |
| 2.1.263-d | 2.1.263 | 失敗したセッションの調査性を改善。session.reaper.terminatedRetentionSeconds(既定 900)で終了 Pod を即削除せず保持。UI に 「ログ」ボタン(GET /api/sessions/<id>/log)を追加し、Failed の理由(exitCode / reason)を一覧に表示。セッションブートスクリプトが URL スキームと identity 型の不一致を警告し、git clone の出力をそのままログに出すよう変更 |
| 2.1.263-c | 2.1.263 | 修正: gateway の nginx が起動時 pcre2_compile() failed: missing closing parenthesis でクラッシュループ。location ~ の正規表現に {10} が含まれ、nginx がブロック区切りの {} と解釈していた。正規表現全体をダブルクォートで囲んで解消 |
| 2.1.263-b | 2.1.263 | anthropic.authMode: credentials(既定)を追加。~/.claude/.credentials.json 全体を注入し、セッション内 claude の自己更新に加え、クラスタ内 refresher CronJob(refreshToken で Secret を定期更新)でトークン失効に自動追従。→ トークンが変わっても再デプロイ不要。refresher の RBAC は認証情報 Secret 1 個の get/patch に限定 |
| 2.1.263-a | 2.1.263 | Service を LoadBalancer 対応(kube-vip 等でローカル LB の IP から管理画面へアクセス、loadBalancerSourceRanges で接続元制限、既定を LoadBalancer に変更)。Ingress は従来どおり併用可。API を使わないサブスクリプション認証(claude setup-token → authMode: oauth)の手順を README に追記 |
| 2.1.263 | 2.1.263 | 自動更新 |
| 2.1.259 | 2.1.259 | 初版。gateway + manager + 使い捨て session Pod + reaper、GitHub/Gitea 対応、アイドル自動破棄、NetworkPolicy / RBAC 最小化 / restricted PodSecurity |