Files
claude-code/README.md
T
claude cd7092be17
Helm Chart Release / release-chart (push) Successful in 7s
Build Session Image / build-push (push) Successful in 2m3s
feat: Service を LoadBalancer 対応にしローカル LB の IP で管理画面へアクセス可能に (v2.1.263-a)
- templates/service.yaml: service.type 既定を LoadBalancer に。annotations
  (kube-vip の VIP 指定等) / loadBalancerIP / loadBalancerClass /
  loadBalancerSourceRanges / externalTrafficPolicy / nodePort / labels を追加。
  Ingress 機能は変更なし(LoadBalancer と併用可)
- values.yaml / values.schema.json: service ブロックを拡張
- templates/NOTES.txt: LoadBalancer の EXTERNAL-IP 案内を追加
- examples/loadbalancer-oauth.values.yaml: Ingress 無効 + LB + source range 制限 +
  サブスクリプション認証 (authMode: oauth) の構成例を追加
- README: 「Claude 認証(API 課金なし / サブスクリプション)」「管理画面への
  アクセス(LoadBalancer / Ingress)」セクションを追加。claude setup-token →
  anthropic.authMode: oauth(CLAUDE_CODE_OAUTH_TOKEN 注入)でローカル実行と同一挙動
- helm lint / template 検証済み
2026-09-08 14:01:34 +09:00

498 lines
31 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 課金なし / サブスクリプション)](#claude-認証api-課金なし--サブスクリプション)
- [管理画面へのアクセス(LoadBalancer / Ingress)](#管理画面へのアクセスloadbalancer--ingress)
- [インストール](#インストール)
- [使い方](#使い方)
- [values リファレンス](#values-リファレンス)
- [image](#image)
- [gateway / manager](#gateway--manager)
- [sessionsNamespace](#sessionsnamespace)
- [auth — アクセス認証](#auth--アクセス認証)
- [anthropic — Claude Code 認証情報](#anthropic--claude-code-認証情報)
- [git — 接続先と認証アイデンティティ](#git--接続先と認証アイデンティティ)
- [session — 使い捨てセッション](#session--使い捨てセッション)
- [networkPolicy](#networkpolicy)
- [service / ingress](#ingress--service)
- [セッションイメージ](#セッションイメージ)
- [運用](#運用)
- [トラブルシューティング](#トラブルシューティング)
- [リリースフロー](#リリースフロー)
- [バージョン履歴](#バージョン履歴)
---
## アーキテクチャ
```
┌──────────── リリース 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・同じログイン方式)。
1. 手元の PC(Claude にログイン済み)で長期トークンを発行:
```bash
claude setup-token
```
ブラウザで承認すると `CLAUDE_CODE_OAUTH_TOKEN` として使える長期トークンが表示されます。
2. それをチャートに渡す(`authMode: oauth`):
```bash
helm upgrade --install claude-code cafepieters/claude-code -n claude-code --create-namespace \
-f examples/loadbalancer-oauth.values.yaml \
--set anthropic.oauthToken="<発行されたトークン>"
```
Secret にしておく場合:
```bash
kubectl -n claude-code-sessions create secret generic claude-oauth \
--from-literal=CLAUDE_CODE_OAUTH_TOKEN="<トークン>"
# values: anthropic.existingSecret=claude-oauth
```
| 方式 | values | 認証情報 | 課金 |
|---|---|---|---|
| **サブスクリプション(推奨)** | `anthropic.authMode: oauth` | `anthropic.oauthToken`(`claude setup-token`) | Claude プランの利用枠を消費(API 課金なし) |
| API キー | `anthropic.authMode: apiKey` | `anthropic.apiKey`(`sk-ant-...`) | API 従量課金 |
- トークンは **セッション NS の Secret** に保存され、セッション Pod へ環境変数
`CLAUDE_CODE_OAUTH_TOKEN` として注入されます(`values.yaml` に平文で書かない)。
- 利用枠はローカルの Claude Code と共有です。複数セッションを同時に走らせると
プランのレート上限に早く達する点に注意してください。
- トークンには有効期限があります。失効したら `claude setup-token` で再発行し
`helm upgrade --set anthropic.oauthToken=...`(または Secret 更新 + Pod 再起動)します。
## 管理画面へのアクセス(LoadBalancer / Ingress)
管理画面(gateway)への到達経路は 2 つあり、**併用できます**。既定は LoadBalancer です。
### A. LoadBalancer でローカルネットワークの IP を使う(既定・Ingress 不要)
`service.type: LoadBalancer`(既定)にすると、クラスタの LB 実装
(**kube-vip** / MetalLB / Cilium LB-IPAM など)がローカルネットワークの IP を払い出します。
その IP をブラウザで開くだけでアクセスできます。ドメインも Ingress も TLS 終端も不要です。
```yaml
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 を保持
```
```bash
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` で設定します。
```yaml
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 setup-token` の OAuth トークン(推奨)または `ANTHROPIC_API_KEY`
### Helm リポジトリから
```bash
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 不要・推奨構成)
```bash
claude setup-token # 手元の PC で長期 OAuth トークンを発行
helm upgrade --install claude-code cafepieters/claude-code \
-n claude-code --create-namespace \
-f examples/loadbalancer-oauth.values.yaml \
--set anthropic.oauthToken="<発行されたトークン>" \
--set-file git.identities.gitea-ssh.sshPrivateKey=P:/Claude/.ssh/id_claude \
--set-file git.identities.gitea-ssh.knownHosts=./known_hosts.gitea
```
`examples/loadbalancer-oauth.values.yaml` は Ingress 無効・`service.type: LoadBalancer` +
kube-vip の VIP 指定 + `loadBalancerSourceRanges` によるローカルセグメント限定の構成です。
### ローカルチャートから(GitHub 公開リポジトリだけ触る最小構成)
```bash
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) 両対応
```bash
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` は事前に取得します:
```bash
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=` で開く):
```bash
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 認証情報
| キー | 既定 | 説明 |
|---|---|---|
| `anthropic.authMode` | `apiKey` | `apiKey`(`ANTHROPIC_API_KEY`)/ `oauth`(`CLAUDE_CODE_OAUTH_TOKEN`) |
| `anthropic.apiKey` / `anthropic.oauthToken` | `""` | `--set` / `--set-file` で注入 |
| `anthropic.existingSecret` | `""` | 既存 Secret を使う(キー名は `existingSecretKeyApiKey` / `existingSecretKeyOauthToken`) |
| `anthropic.extraEnv` | `{}` | 全セッションへ渡す追加 env(例 `ANTHROPIC_BASE_URL`、Bedrock/Vertex 切替など) |
Secret は **セッション NS** に作成され、セッション Pod へ `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.identities.<name>.type` | — | `ssh` / `https-token` |
| `…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 にマウントされます。
### 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)](#管理画面へのアクセス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 します。手動ビルドは:
```bash
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 アノテーションで自動ローリング
## トラブルシューティング
| 症状 | 原因と対処 |
|---|---|
| UI が 502 / セッションを開くと 502 | セッション Pod がまだ Ready でない(初回は `npm i` に時間)。数十秒待つ。恒久対策は事前ビルドイメージ |
| セッション作成が `許可されていない Git ホスト` | `git.allowedHosts` に対象ホストを追加(IP と FQDN は別物) |
| clone が `Host key verification failed` | identity の `knownHosts` が対象ホストの鍵と不一致。`ssh-keyscan <host>` で取り直す |
| clone が `terminated ... could not read Username` | HTTPS の場合 `git.identities.<name>` 未指定 or token 無効。SSH の場合は identity の type/秘密鍵を確認 |
| 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-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 |