Files
claude-code/README.md
T
claude c31dd5875c
Helm Chart Release / release-chart (push) Successful in 7s
Build Session Image / build-push (push) Successful in 2m5s
feat: git identity を Web UI から登録・削除できるようにする (v2.1.263-f)
helm --set-file ではなく UI から SSH 秘密鍵 + known_hosts / HTTPS トークンを登録可能に。

- git.guiIdentities.enabled(既定 true)。UI に「認証情報」カード(一覧/追加/削除)
- GET/POST/DELETE /api/identities。登録分はセッション NS の Secret
  (ラベル identity-source=gui / identity-type)として保存。Helm 管理分と同一覧に統合
- manager.py: resolve_identity / list_identities / create_gui_identity / delete_gui_identity。
  build_pod と create_session は Helm 管理 + GUI 登録の両方を解決。
  /api/config の identities を {name,type,source} 配列に変更
- rbac.yaml: guiIdentities.enabled 時のみ manager Role に同 NS secrets の CRUD を付与
- 秘密鍵はリクエストボディで manager へ届くため README に経路 TLS 必須を明記
- helm lint / template 検証済み
2026-09-09 12:47:50 +09:00

40 KiB
Raw Permalink Blame History

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>

目次


アーキテクチャ

                         ┌──────────── リリース 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 化されます。

セッションのライフサイクル

  1. 作成 — UI か POST /api/sessions に repo / ref / identity を渡す。manager は
    • リポジトリ URL の形式と Git ホスト allow-list(git.allowedHosts)を検証
    • 同時セッション数(session.maxSessions)を確認
    • セッション NS に Pod(claude-sess-<10hex>)と Service を作成
  2. ブート(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 を自動起動)
  3. 利用 — /s/<id>/ 経由でブラウザ内ターミナルに接続。claude-verify / claude-push が使える
  4. 破棄 — 次のいずれかで 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 モードの使い方(推奨)

  1. 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"
    
  2. これだけで トークンが定期的に変わっても再デプロイは不要 です。仕組み:

    • セッション Pod は生成のたびに Secret を読むため、常に最新の認証情報で起動します。
    • セッション内の claude は、accessToken が失効していれば refreshToken で 自動更新します(そのセッションの間だけ有効。$HOME/.claude/.credentials.json に書き戻す)。
    • refresher CronJob(anthropic.refresher、既定 30 分ごと)が、Secret 内の refreshToken を使ってクラスタ内で accessToken を更新し、Secret 自体を書き戻します。 これにより手元 PC がオフラインでも、新規セッションは常に有効なトークンで起動できます。
  3. 既存 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

使い方

  1. 管理画面を開く:

    • 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/
  2. auth.mode: token の場合、トークンを取得して UI に貼り付け(または ?token= で開く):

    kubectl -n claude-code get secret claude-code-auth \
      -o jsonpath='{.data.token}' | base64 -d ; echo
    
  3. UI で リポジトリ URL / ブランチ / 認証 identity を指定して「セッション作成」。

  4. 一覧の「開く」でブラウザ内ターミナル(Claude Code)に接続。

  5. セッション内の補助コマンド:

    • claude-verify … session.verifyCommand(例 npm test)を実行
    • claude-push … 検証 → claude/<branch> へ push(保護ブランチ / --force 不可)
  6. 無操作 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 — main push 時に 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 を更新してコミットするだけ (リリースなし)。
  • 手動リリース(臨時) — バージョン末尾にアルファベットを付与(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.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