diff --git a/CHANGELOG-2.1.263-a.md b/CHANGELOG-2.1.263-a.md new file mode 100644 index 0000000..2928fb4 --- /dev/null +++ b/CHANGELOG-2.1.263-a.md @@ -0,0 +1,34 @@ +# CHANGELOG 2.1.263-a — 手動リリース + +v2.1.263(自動更新)の上に適用する手動リリース。 + +## 追加 / 変更 + +- **Service の LoadBalancer 対応(既定を `LoadBalancer` に変更)** + - `service.annotations` … LB 実装へのヒント。kube-vip の VIP 指定 + (`kube-vip.io/loadbalancerIPs` / `kube-vip.io/vipHost`)、MetalLB のプール指定など。 + - `service.loadBalancerIP` / `service.loadBalancerClass` + - `service.loadBalancerSourceRanges` … 接続元 CIDR を制限。Ingress を張らない構成での + 第一の防御線。 + - `service.externalTrafficPolicy` … `Local` で実クライアント IP を保持。 + - `service.nodePort` … `type: NodePort` 時の固定ポート。 + - `service.labels` + - Ingress 機能(`ingress.*`)は変更なし。LoadBalancer と併用できる。 +- `templates/NOTES.txt` に LoadBalancer の EXTERNAL-IP 確認手順を追加。 +- `examples/loadbalancer-oauth.values.yaml` を追加 + (Ingress 無効 + `service.type: LoadBalancer` + kube-vip VIP + `loadBalancerSourceRanges` + によるローカルセグメント限定 + `anthropic.authMode: oauth`)。 + +## ドキュメント + +- README に **「Claude 認証(API 課金なし / サブスクリプション)」** セクションを追加。 + `claude setup-token` で発行した長期 OAuth トークンを `anthropic.oauthToken` + (`authMode: oauth`)に渡すと、API 従量課金を使わずローカルの Claude Code と同一の + 挙動で動作する。トークンはセッション NS の Secret に保存し、セッション Pod へ + `CLAUDE_CODE_OAUTH_TOKEN` として注入。 +- README に **「管理画面へのアクセス(LoadBalancer / Ingress)」** セクションを追加。 + +## バージョン + +`version` = `2.1.263-a` / `appVersion` = `"2.1.263"`(主アプリ = Claude Code CLI は据え置き、 +チャートのみ手動リリース)。 diff --git a/CLAUDE.md b/CLAUDE.md index c8340d4..d68ccfc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -57,6 +57,30 @@ Git リポジトリ(GitHub / Gitea など互換 Git 全般)をチェック ## 改修履歴 +### Service の LoadBalancer 対応 + サブスクリプション認証手順の明文化(2026-09-08, v2.1.263-a) + +**背景**: Ingress を張らずに、ローカルネットワークの LB(kube-vip 等)から払い出した IP で +管理画面へアクセスしたい、という運用要望。あわせて「Claude の API 課金を使わず、ローカルの +Claude Code と同様にサブスクリプションで使いたい」という確認があった。 + +**修正内容**: +- `templates/service.yaml` を書き換え、`service.type: LoadBalancer` を既定化。 + `service.annotations`(kube-vip の `kube-vip.io/loadbalancerIPs` / `vipHost` 等)、 + `loadBalancerIP` / `loadBalancerClass` / `loadBalancerSourceRanges` / + `externalTrafficPolicy` / `nodePort` / `labels` を追加。Ingress 機能は変更なし(併用可)。 +- `values.yaml` の `service:` ブロック拡張、`values.schema.json` に対応プロパティ追加。 +- `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` を + セッション Pod に注入)で、ローカル実行の Claude Code と同一挙動。 + +**対象ファイル**: `templates/service.yaml`, `values.yaml`, `values.schema.json`, +`templates/NOTES.txt`, `examples/loadbalancer-oauth.values.yaml`, `README.md`, `Chart.yaml`, +`CHANGELOG-2.1.263-a.md` + ### 初版(2026-09-03, v2.1.259) チャート新規作成。gateway + manager + 使い捨て session Pod + reaper CronJob の構成で、 diff --git a/Chart.yaml b/Chart.yaml index e8311e3..6d00808 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -6,7 +6,7 @@ description: >- 使い捨てコンテナで Git リポジトリ(GitHub / Gitea など)をチェックアウト・編集・ 検証・プッシュする。一定時間アイドルなセッションは自動破棄される。 type: application -version: 2.1.263 +version: 2.1.263-a appVersion: "2.1.263" keywords: - claude-code diff --git a/README.md b/README.md index 5ea1794..11f694c 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ Raspberry Pi などの ARM ベアメタル Kubernetes を主な対象にして - [アーキテクチャ](#アーキテクチャ) - [セッションのライフサイクル](#セッションのライフサイクル) - [セキュリティモデル](#セキュリティモデル) +- [Claude 認証(API 課金なし / サブスクリプション)](#claude-認証api-課金なし--サブスクリプション) +- [管理画面へのアクセス(LoadBalancer / Ingress)](#管理画面へのアクセスloadbalancer--ingress) - [インストール](#インストール) - [使い方](#使い方) - [values リファレンス](#values-リファレンス) @@ -32,7 +34,7 @@ Raspberry Pi などの ARM ベアメタル Kubernetes を主な対象にして - [git — 接続先と認証アイデンティティ](#git--接続先と認証アイデンティティ) - [session — 使い捨てセッション](#session--使い捨てセッション) - [networkPolicy](#networkpolicy) - - [ingress / service](#ingress--service) + - [service / ingress](#ingress--service) - [セッションイメージ](#セッションイメージ) - [運用](#運用) - [トラブルシューティング](#トラブルシューティング) @@ -111,13 +113,120 @@ manager / gateway / session-entrypoint の実体は `files/` にあり、`.Files > **注意** — `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:/// をブラウザで開く +``` + +| キー | 説明 | +|---|---| +| `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 など) -- Ingress コントローラー(外部公開する場合。WebSocket を通すため read/send timeout を延長) -- `ANTHROPIC_API_KEY`(または `claude setup-token` で発行した OAuth トークン) +- 管理画面の公開方法に応じて **どちらか**: + - 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 リポジトリから @@ -130,6 +239,22 @@ helm upgrade --install claude-code cafepieters/claude-code \ --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 @@ -159,11 +284,11 @@ ssh-keyscan 192.168.9.65 > known_hosts.gitea ## 使い方 -1. アクセス(Ingress 無効ならポートフォワード): - - ```bash - kubectl -n claude-code port-forward svc/claude-code 8080:80 - ``` +1. 管理画面を開く: + - **LoadBalancer(既定)**: `kubectl -n claude-code get svc claude-code` の `EXTERNAL-IP` を + ブラウザで開く(`http:///`) + - **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=` で開く): @@ -288,9 +413,8 @@ GitHub でも Gitea でも GitLab でも、SSH か HTTPS トークンで認証 ### ingress / service -`ingress.enabled: true` で Ingress を作成。WebSocket 用に `proxy-read-timeout` / -`proxy-send-timeout` を既定 `3600` に設定します(`ingress.annotations` で上書き可)。 -`service.type` は `ClusterIP` 既定。 +→ [管理画面へのアクセス(LoadBalancer / Ingress)](#管理画面へのアクセスloadbalancer--ingress) を参照。 +`service.type` の既定は `LoadBalancer`、`ingress.enabled` の既定は `false`(両者は併用可)。 ## セッションイメージ @@ -368,5 +492,6 @@ docker buildx build --platform linux/amd64,linux/arm64 \ | バージョン | 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 | diff --git a/examples/loadbalancer-oauth.values.yaml b/examples/loadbalancer-oauth.values.yaml new file mode 100644 index 0000000..0459f38 --- /dev/null +++ b/examples/loadbalancer-oauth.values.yaml @@ -0,0 +1,63 @@ +# ローカルネットワーク限定の LoadBalancer で公開し、Claude のサブスクリプション +# (API 課金なし)で動かす構成。Ingress は張らない。 +# +# 前提: +# - クラスタに LB 実装(kube-vip / MetalLB / Cilium LB-IPAM 等)がある +# - ローカルで `claude setup-token` を実行して長期 OAuth トークンを発行済み +# +# 実行例: +# helm upgrade --install claude-code cafepieters/claude-code \ +# -n claude-code --create-namespace \ +# -f examples/loadbalancer-oauth.values.yaml \ +# --set anthropic.oauthToken="$(cat ~/.claude/oauth-token.txt)" \ +# --set-file git.identities.gitea-ssh.sshPrivateKey=P:/Claude/.ssh/id_claude \ +# --set-file git.identities.gitea-ssh.knownHosts=./known_hosts.gitea + +# ---- Claude 認証: API を使わずサブスクリプション(Pro / Max)で動かす ---- +anthropic: + authMode: oauth # ANTHROPIC_API_KEY ではなく CLAUDE_CODE_OAUTH_TOKEN を使う + # oauthToken は --set / --set-file / existingSecret で注入する(平文で書かない) + +# ---- 管理画面はローカル LB の IP でアクセス(Ingress なし)---- +ingress: + enabled: false + +service: + type: LoadBalancer + port: 80 + annotations: + # kube-vip で払い出す VIP を固定(この 192.168.9.x は空いているものを指定) + kube-vip.io/loadbalancerIPs: "192.168.9.50" + kube-vip.io/vipHost: "cpi11" + # 接続元をローカルセグメントに限定(Ingress を張らない構成での第一の防御線) + loadBalancerSourceRanges: + - 192.168.9.0/24 + externalTrafficPolicy: Local # 実クライアント IP を保持(LB 実装が対応する場合) + +# ---- ブラウザアクセスの認証: トークン(自動生成)---- +auth: + mode: token + # token.value を空にすると install 時に自動生成される。 + # 取得: kubectl -n claude-code get secret claude-code-auth -o jsonpath='{.data.token}' | base64 -d + +# ---- Git 接続先 ---- +git: + allowedHosts: + - github.com + - 192.168.9.65 + - git.cafepieters.com + identities: + gitea-ssh: + type: ssh + # sshPrivateKey / knownHosts は --set-file で注入 + # github-https: + # type: https-token + # username: "x-access-token" + +session: + autoStartClaude: true + idleTimeoutSeconds: 1800 + maxSessions: 5 + # verifyCommand: "npm test" + # autoPush: + # enabled: true diff --git a/templates/NOTES.txt b/templates/NOTES.txt index 8b0cdc8..1b3ebe2 100644 --- a/templates/NOTES.txt +++ b/templates/NOTES.txt @@ -11,7 +11,15 @@ claude-code ({{ .Chart.Version }}) をデプロイしました。 {{- range .Values.ingress.hosts }} https://{{ .host }}/ {{- end }} -{{- else }} +{{- end }} +{{- if eq .Values.service.type "LoadBalancer" }} + ロードバランサーの IP を確認してブラウザで開いてください: + kubectl -n {{ .Release.Namespace }} get svc {{ include "claude-code.fullname" . }} -w + → http://:{{ .Values.service.port }}/ + {{- with .Values.service.loadBalancerSourceRanges }} + (接続元は {{ join ", " . }} に制限されています) + {{- end }} +{{- else if not .Values.ingress.enabled }} kubectl -n {{ .Release.Namespace }} port-forward svc/{{ include "claude-code.fullname" . }} 8080:{{ .Values.service.port }} → http://localhost:8080/ {{- end }} diff --git a/templates/service.yaml b/templates/service.yaml index 6cc8683..e6e8f8e 100644 --- a/templates/service.yaml +++ b/templates/service.yaml @@ -6,13 +6,36 @@ metadata: labels: {{- include "claude-code.labels" . | nindent 4 }} app.kubernetes.io/component: control-plane + {{- with .Values.service.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} spec: type: {{ .Values.service.type }} + {{- if and (eq .Values.service.type "LoadBalancer") .Values.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.service.loadBalancerIP | quote }} + {{- end }} + {{- with .Values.service.loadBalancerClass }} + loadBalancerClass: {{ . | quote }} + {{- end }} + {{- if and (eq .Values.service.type "LoadBalancer") .Values.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: + {{- toYaml .Values.service.loadBalancerSourceRanges | nindent 4 }} + {{- end }} + {{- with .Values.service.externalTrafficPolicy }} + externalTrafficPolicy: {{ . }} + {{- end }} ports: - name: http port: {{ .Values.service.port }} targetPort: http protocol: TCP + {{- if and (eq .Values.service.type "NodePort") .Values.service.nodePort }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} selector: {{- include "claude-code.selectorLabels" . | nindent 4 }} app.kubernetes.io/component: control-plane diff --git a/values.schema.json b/values.schema.json index d06445c..f22e637 100644 --- a/values.schema.json +++ b/values.schema.json @@ -236,7 +236,14 @@ "additionalProperties": false, "properties": { "type": { "type": "string", "enum": ["ClusterIP", "NodePort", "LoadBalancer"] }, - "port": { "type": "integer", "minimum": 1, "maximum": 65535 } + "port": { "type": "integer", "minimum": 1, "maximum": 65535 }, + "nodePort": { "type": ["integer", "null"], "minimum": 1, "maximum": 65535 }, + "annotations": { "type": "object" }, + "loadBalancerIP": { "type": "string" }, + "loadBalancerClass": { "type": "string" }, + "loadBalancerSourceRanges": { "type": "array", "items": { "type": "string" } }, + "externalTrafficPolicy": { "type": "string", "enum": ["", "Cluster", "Local"] }, + "labels": { "type": "object" } } }, diff --git a/values.yaml b/values.yaml index bfc41f0..65e1a6a 100644 --- a/values.yaml +++ b/values.yaml @@ -308,8 +308,32 @@ ingress: # - claude-code.example.com service: - type: ClusterIP + # LoadBalancer : ローカルネットワークの LB(kube-vip / MetalLB / Cilium LB-IPAM 等)から + # IP を払い出し、その IP で管理画面へアクセスする(Ingress 不要・既定) + # ClusterIP : Service IP のみ。port-forward か Ingress 経由でアクセス + # NodePort : 各ノードの高位ポートで公開 + type: LoadBalancer port: 80 + # NodePort を固定したい場合のみ(type: NodePort) + nodePort: null + # Service に付与するアノテーション。kube-vip で VIP を指定する例: + # kube-vip.io/loadbalancerIPs: "192.168.9.50" + # kube-vip.io/vipHost: "cpi11" + # MetalLB でプールを指定する例: + # metallb.universe.tf/address-pool: "lan" + annotations: {} + # 払い出す IP を直接指定(LB 実装が対応していれば)。annotations 指定を推奨。 + loadBalancerIP: "" + loadBalancerClass: "" + # 接続元をローカルネットワークに限定する(LB 実装が対応していれば)。 + # Ingress を張らない構成では、ここで許可レンジを絞ることが第一の防御線になる。 + loadBalancerSourceRanges: [] + # - 192.168.9.0/24 + # - 10.0.0.0/8 + # Local にすると実クライアント IP を保持する(kube-proxy/LB 実装依存) + externalTrafficPolicy: "" + # Service 自体への追加ラベル + labels: {} # -------------------------------------------------------------------------- # 制御プレーンの Pod / コンテナセキュリティ