feat: Alpine ベースの Claude Code リモート実行 Helm チャート初版 (v2.1.259)
Helm Chart Release / release-chart (push) Successful in 12s
Build Session Image / build-push (push) Successful in 2m15s

gateway(nginx) + manager(python 標準ライブラリ) + セッションごとの使い捨て Pod +
reaper CronJob の構成。ブラウザ(PC/スマホ)からアクセスし、セッション単位で
独立した Alpine コンテナに Git リポジトリ(GitHub/Gitea など)をチェックアウトして
編集・検証・プッシュする。無操作が続いたセッションは Pod ごと自動破棄。

- セキュリティ: セッション NS 分離 / restricted PodSecurity / RBAC はセッション NS の
  pods,services のみの Role / セッション Pod は SA トークン非マウント・非 root・
  readOnlyRootFilesystem・drop ALL caps・seccomp / NetworkPolicy で egress を
  DNS+HTTPS+SSH に限定 / Git ホスト allow-list / ResourceQuota+LimitRange
- ライフサイクル: idleTimeout(Pod 内ウォッチドッグ + manager 回収) / maxLifetime
  (activeDeadlineSeconds) / maxSessions / 予備の孤児掃除 CronJob
- 認証: token(既定) / basic / forward(外部SSO) / none。WebSocket は Cookie トークン
- Claude 認証: ANTHROPIC_API_KEY または CLAUDE_CODE_OAUTH_TOKEN
- CI: helm-release.yaml(未リリース版のみ tag+release+publish) /
  image-update-and-release.yaml(週次 npm 追従) / session-image.yaml(multi-arch build)
- files/ 実体を .Files.Get で ConfigMap 化。helm lint / template 検証済み
This commit is contained in:
2026-09-03 08:10:12 +09:00
parent 457a81a17f
commit 1bd0fec23a
39 changed files with 3745 additions and 1 deletions
+57
View File
@@ -0,0 +1,57 @@
claude-code ({{ .Chart.Version }}) をデプロイしました。
制御プレーン : {{ include "claude-code.fullname" . }} (namespace: {{ .Release.Namespace }})
セッション NS : {{ include "claude-code.sessionsNamespace" . }}
セッション像 : {{ .Values.image.session.registry }}/{{ .Values.image.session.repository }}:{{ .Values.image.session.tag }}
認証モード : {{ .Values.auth.mode }}
アイドル破棄 : {{ .Values.session.idleTimeoutSeconds }}s / 最大寿命 {{ .Values.session.maxLifetimeSeconds }}s / 同時 {{ .Values.session.maxSessions }}
アクセス方法:
{{- if .Values.ingress.enabled }}
{{- range .Values.ingress.hosts }}
https://{{ .host }}/
{{- end }}
{{- else }}
kubectl -n {{ .Release.Namespace }} port-forward svc/{{ include "claude-code.fullname" . }} 8080:{{ .Values.service.port }}
→ http://localhost:8080/
{{- end }}
{{- if eq .Values.auth.mode "token" }}
アクセストークンの取得:
kubectl -n {{ .Release.Namespace }} get secret {{ include "claude-code.authSecretName" . }} \
-o jsonpath='{.data.{{ .Values.auth.token.existingSecretKey | default "token" }}}' | base64 -d ; echo
UI の「アクセストークン」欄に貼り付けるか、`?token=<TOKEN>` を付けて開いてください。
{{- else if eq .Values.auth.mode "basic" }}
Basic 認証のユーザーでログインしてください。
{{- else if eq .Values.auth.mode "forward" }}
外部認証プロキシ({{ .Values.auth.forward.url }})経由でログインしてください。
{{- else }}
⚠ auth.mode=none — 認証なしで公開されています。信頼済みネットワーク限定で使用してください。
{{- end }}
{{- if and (not .Values.anthropic.existingSecret) (not .Values.anthropic.apiKey) (not .Values.anthropic.oauthToken) }}
⚠ Claude Code の認証情報が未設定です(anthropic.apiKey / oauthToken / existingSecret のいずれも空)。
セッション内の `claude` は認証に失敗します。`--set anthropic.apiKey=...` で再適用してください。
{{- end }}
{{- if not .Values.git.identities }}
⚠ git.identities が未設定です。公開リポジトリの clone のみ可能です。
GitHub / Gitea へ push するには values に SSH 鍵か HTTPS トークンを登録してください(README 参照)。
{{- end }}
使い方:
1. UI で「リポジトリ URL」「ブランチ」「認証 identity」を指定してセッション作成
2. 一覧の「開く」でブラウザ内ターミナル(Claude Code)に接続
3. セッション内で `claude-verify` / `claude-push` が利用可能
4. 無操作 {{ .Values.session.idleTimeoutSeconds }}s でセッション(Pod)は自動破棄されます
許可された Git ホスト: {{ join ", " .Values.git.allowedHosts }}
{{- if .Values.git.allowArbitraryRepos }}
(allowArbitraryRepos=true のため任意ホストを許可)
{{- end }}
+112
View File
@@ -0,0 +1,112 @@
{{/*
==========================================================================
claude-code — 共通ヘルパー
==========================================================================
*/}}
{{- define "claude-code.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- define "claude-code.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := include "claude-code.name" . -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- define "claude-code.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/* 制御プレーン共通ラベル */}}
{{- define "claude-code.labels" -}}
helm.sh/chart: {{ include "claude-code.chart" . }}
{{ include "claude-code.selectorLabels" . }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/part-of: claude-code
{{- with .Values.commonLabels }}
{{ toYaml . }}
{{- end }}
{{- end -}}
{{- define "claude-code.selectorLabels" -}}
app.kubernetes.io/name: {{ include "claude-code.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}}
{{/* セッション Pod を作成するネームスペース */}}
{{- define "claude-code.sessionsNamespace" -}}
{{- default .Release.Namespace .Values.sessionsNamespace.name -}}
{{- end -}}
{{/* 制御プレーン ServiceAccount 名 */}}
{{- define "claude-code.managerServiceAccountName" -}}
{{- printf "%s-manager" (include "claude-code.fullname" .) -}}
{{- end -}}
{{/* セッション ServiceAccount 名 */}}
{{- define "claude-code.sessionServiceAccountName" -}}
{{- printf "%s-session" (include "claude-code.fullname" .) -}}
{{- end -}}
{{/* 認証トークン Secret 名 */}}
{{- define "claude-code.authSecretName" -}}
{{- if .Values.auth.token.existingSecret -}}
{{- .Values.auth.token.existingSecret -}}
{{- else if .Values.auth.basic.existingSecret -}}
{{- .Values.auth.basic.existingSecret -}}
{{- else -}}
{{- printf "%s-auth" (include "claude-code.fullname" .) -}}
{{- end -}}
{{- end -}}
{{/* Anthropic 認証情報 Secret 名(セッション NS 側) */}}
{{- define "claude-code.anthropicSecretName" -}}
{{- if .Values.anthropic.existingSecret -}}
{{- .Values.anthropic.existingSecret -}}
{{- else -}}
{{- printf "%s-anthropic" (include "claude-code.fullname" .) -}}
{{- end -}}
{{- end -}}
{{/* Git アイデンティティ Secret 名(アイデンティティ名を与える) */}}
{{- define "claude-code.gitIdentitySecretName" -}}
{{- $ctx := .ctx -}}
{{- $name := .name -}}
{{- $id := index $ctx.Values.git.identities $name -}}
{{- if and $id $id.existingSecret -}}
{{- $id.existingSecret -}}
{{- else -}}
{{- printf "%s-git-%s" (include "claude-code.fullname" $ctx) $name -}}
{{- end -}}
{{- end -}}
{{/* 自動生成トークン(未指定時)。リリース間で安定させるため lookup を優先。 */}}
{{- define "claude-code.resolvedToken" -}}
{{- if .Values.auth.token.value -}}
{{- .Values.auth.token.value -}}
{{- else -}}
{{- $sec := (lookup "v1" "Secret" .Release.Namespace (printf "%s-auth" (include "claude-code.fullname" .))) -}}
{{- if and $sec $sec.data (index $sec.data "token") -}}
{{- index $sec.data "token" | b64dec -}}
{{- else -}}
{{- randAlphaNum 40 -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/* 制御プレーン Pod セキュリティコンテキスト */}}
{{- define "claude-code.podSecurityContext" -}}
{{- toYaml .Values.podSecurityContext -}}
{{- end -}}
{{- define "claude-code.containerSecurityContext" -}}
{{- toYaml .Values.containerSecurityContext -}}
{{- end -}}
+12
View File
@@ -0,0 +1,12 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "claude-code.fullname" . }}-gateway
namespace: {{ .Release.Namespace }}
labels:
{{- include "claude-code.labels" . | nindent 4 }}
data:
gateway.conf.template: |
{{ .Files.Get "files/gateway.conf.template" | indent 4 }}
entrypoint.sh: |
{{ .Files.Get "files/gateway-entrypoint.sh" | indent 4 }}
+10
View File
@@ -0,0 +1,10 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "claude-code.fullname" . }}-manager
namespace: {{ .Release.Namespace }}
labels:
{{- include "claude-code.labels" . | nindent 4 }}
data:
manager.py: |
{{ .Files.Get "files/manager.py" | indent 4 }}
+10
View File
@@ -0,0 +1,10 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "claude-code.fullname" . }}-session
namespace: {{ include "claude-code.sessionsNamespace" . }}
labels:
{{- include "claude-code.labels" . | nindent 4 }}
data:
entrypoint.sh: |
{{ .Files.Get "files/session-entrypoint.sh" | indent 4 }}
+71
View File
@@ -0,0 +1,71 @@
{{- if .Values.session.reaper.cronjob.enabled }}
apiVersion: batch/v1
kind: CronJob
metadata:
name: {{ include "claude-code.fullname" . }}-reaper
namespace: {{ .Release.Namespace }}
labels:
{{- include "claude-code.labels" . | nindent 4 }}
app.kubernetes.io/component: reaper
spec:
schedule: {{ .Values.session.reaper.cronjob.schedule | quote }}
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 1
failedJobsHistoryLimit: 3
startingDeadlineSeconds: 120
jobTemplate:
spec:
activeDeadlineSeconds: 300
backoffLimit: 1
template:
metadata:
labels:
{{- include "claude-code.selectorLabels" . | nindent 12 }}
app.kubernetes.io/component: reaper
spec:
restartPolicy: Never
serviceAccountName: {{ include "claude-code.managerServiceAccountName" . }}
automountServiceAccountToken: true
securityContext:
{{- include "claude-code.podSecurityContext" . | nindent 12 }}
containers:
- name: reaper
image: "{{ .Values.image.manager.registry }}/{{ .Values.image.manager.repository }}:{{ .Values.image.manager.tag }}"
imagePullPolicy: {{ .Values.image.manager.pullPolicy }}
command: ["python3", "-u", "/app/manager.py", "--reap-once"]
env:
- name: PYTHONDONTWRITEBYTECODE
value: "1"
- name: LOG_LEVEL
value: {{ .Values.manager.logLevel | quote }}
- name: SESSIONS_NAMESPACE
value: {{ include "claude-code.sessionsNamespace" . | quote }}
- name: FULLNAME
value: {{ include "claude-code.fullname" . | quote }}
- name: IDLE_TIMEOUT_SECONDS
value: {{ .Values.session.idleTimeoutSeconds | quote }}
- name: MAX_LIFETIME_SECONDS
value: {{ .Values.session.maxLifetimeSeconds | quote }}
- name: TTYD_PORT
value: {{ .Values.session.ttydPort | quote }}
securityContext:
{{- include "claude-code.containerSecurityContext" . | nindent 16 }}
resources:
requests: { cpu: 20m, memory: 32Mi }
limits: { cpu: 200m, memory: 128Mi }
volumeMounts:
- name: manager-app
mountPath: /app
readOnly: true
- name: tmp
mountPath: /tmp
volumes:
- name: manager-app
configMap:
name: {{ include "claude-code.fullname" . }}-manager
defaultMode: 0555
- name: tmp
emptyDir:
medium: Memory
sizeLimit: 8Mi
{{- end }}
+255
View File
@@ -0,0 +1,255 @@
{{- $sessNs := include "claude-code.sessionsNamespace" . -}}
{{- $ccVersion := .Values.session.claudeCodeVersion | default .Chart.AppVersion -}}
{{- /* --- git identities を manager が解釈できる JSON へ変換 --- */ -}}
{{- $idents := dict -}}
{{- range $name, $id := .Values.git.identities -}}
{{- $entry := dict "type" $id.type "secret" (include "claude-code.gitIdentitySecretName" (dict "ctx" $ "name" $name)) -}}
{{- if eq $id.type "ssh" -}}
{{- $_ := set $entry "keyPrivateKey" ($id.existingSecretKeyPrivateKey | default "id") -}}
{{- $_ := set $entry "keyKnownHosts" ($id.existingSecretKeyKnownHosts | default "known_hosts") -}}
{{- else if eq $id.type "https-token" -}}
{{- $_ := set $entry "keyToken" ($id.existingSecretKeyToken | default "token") -}}
{{- $_ := set $entry "username" ($id.username | default "x-access-token") -}}
{{- end -}}
{{- $_ := set $idents $name $entry -}}
{{- end -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "claude-code.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "claude-code.labels" . | nindent 4 }}
app.kubernetes.io/component: control-plane
spec:
replicas: {{ .Values.gateway.replicaCount }}
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
selector:
matchLabels:
{{- include "claude-code.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: control-plane
template:
metadata:
labels:
{{- include "claude-code.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: control-plane
annotations:
checksum/manager: {{ .Files.Get "files/manager.py" | sha256sum }}
checksum/gateway: {{ print (.Files.Get "files/gateway.conf.template") (.Files.Get "files/gateway-entrypoint.sh") | sha256sum }}
checksum/session: {{ .Files.Get "files/session-entrypoint.sh" | sha256sum }}
{{- with .Values.commonAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
serviceAccountName: {{ include "claude-code.managerServiceAccountName" . }}
automountServiceAccountToken: true
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
securityContext:
{{- include "claude-code.podSecurityContext" . | nindent 8 }}
containers:
# ---- gateway: 受付・認証・WebSocket プロキシ ----
- name: gateway
image: "{{ .Values.image.gateway.registry }}/{{ .Values.image.gateway.repository }}:{{ .Values.image.gateway.tag }}"
imagePullPolicy: {{ .Values.image.gateway.pullPolicy }}
command: ["/bin/sh", "/etc/gateway/entrypoint.sh"]
env:
- name: GATEWAY_PORT
value: {{ .Values.gateway.port | quote }}
- name: MANAGER_PORT
value: "8081"
- name: TTYD_PORT
value: {{ .Values.session.ttydPort | quote }}
- name: SESSIONS_NS
value: {{ $sessNs | quote }}
- name: AUTH_MODE
value: {{ .Values.auth.mode | quote }}
{{- if eq .Values.auth.mode "forward" }}
- name: AUTH_FORWARD_URL
value: {{ required "auth.mode=forward には auth.forward.url が必須です" .Values.auth.forward.url | quote }}
{{- end }}
- name: NGINX_ERROR_LEVEL
value: {{ eq .Values.manager.logLevel "debug" | ternary "info" "warn" | quote }}
ports:
- name: http
containerPort: {{ .Values.gateway.port }}
securityContext:
{{- include "claude-code.containerSecurityContext" . | nindent 12 }}
readinessProbe:
httpGet: { path: /healthz, port: http }
periodSeconds: 10
livenessProbe:
httpGet: { path: /healthz, port: http }
initialDelaySeconds: 10
periodSeconds: 20
resources:
{{- toYaml .Values.gateway.resources | nindent 12 }}
volumeMounts:
- name: gateway-config
mountPath: /etc/gateway
readOnly: true
- name: nginx-tmp
mountPath: /tmp/nginx
{{- if eq .Values.auth.mode "basic" }}
- name: gateway-secret
mountPath: /etc/gateway-secret
readOnly: true
{{- end }}
# ---- manager: ライフサイクル API / UI / 回収ループ ----
- name: manager
image: "{{ .Values.image.manager.registry }}/{{ .Values.image.manager.repository }}:{{ .Values.image.manager.tag }}"
imagePullPolicy: {{ .Values.image.manager.pullPolicy }}
command: ["python3", "-u", "/app/manager.py"]
env:
- name: LISTEN_PORT
value: "8081"
- name: PYTHONDONTWRITEBYTECODE
value: "1"
- name: LOG_LEVEL
value: {{ .Values.manager.logLevel | quote }}
- name: FULLNAME
value: {{ include "claude-code.fullname" . | quote }}
- name: SESSIONS_NAMESPACE
value: {{ $sessNs | quote }}
- name: SESSION_SERVICE_ACCOUNT
value: {{ include "claude-code.sessionServiceAccountName" . | quote }}
- name: SESSION_IMAGE
value: "{{ .Values.image.session.registry }}/{{ .Values.image.session.repository }}:{{ .Values.image.session.tag }}"
- name: SESSION_IMAGE_PULL_POLICY
value: {{ .Values.image.session.pullPolicy | quote }}
{{- with .Values.imagePullSecrets }}
- name: SESSION_IMAGE_PULL_SECRETS_JSON
value: {{ toJson . | quote }}
{{- end }}
- name: SESSION_BOOTSTRAP_CONFIGMAP
value: {{ include "claude-code.fullname" . }}-session
- name: ANTHROPIC_SECRET
value: {{ include "claude-code.anthropicSecretName" . | quote }}
- name: GIT_IDENTITIES_JSON
value: {{ $idents | toJson | quote }}
- name: GIT_ALLOWED_HOSTS
value: {{ join "," .Values.git.allowedHosts | quote }}
- name: GIT_ALLOW_ARBITRARY
value: {{ .Values.git.allowArbitraryRepos | quote }}
- name: GIT_DEFAULT_AUTHOR_NAME
value: {{ .Values.git.defaultAuthorName | quote }}
- name: GIT_DEFAULT_AUTHOR_EMAIL
value: {{ .Values.git.defaultAuthorEmail | quote }}
- name: IDLE_TIMEOUT_SECONDS
value: {{ .Values.session.idleTimeoutSeconds | quote }}
- name: MAX_LIFETIME_SECONDS
value: {{ .Values.session.maxLifetimeSeconds | quote }}
- name: REAP_INTERVAL_SECONDS
value: {{ .Values.session.reaper.intervalSeconds | quote }}
- name: MAX_SESSIONS
value: {{ .Values.session.maxSessions | quote }}
- name: TTYD_PORT
value: {{ .Values.session.ttydPort | quote }}
- name: SESSION_RESOURCES_JSON
value: {{ toJson .Values.session.resources | quote }}
- name: WORKSPACE_DIR
value: {{ .Values.session.workspaceDir | quote }}
- name: AUTOSTART_CLAUDE
value: {{ .Values.session.autoStartClaude | quote }}
- name: VERIFY_COMMAND
value: {{ .Values.session.verifyCommand | quote }}
- name: AUTOPUSH_JSON
value: {{ toJson .Values.session.autoPush | quote }}
- name: EXTRA_APK_PACKAGES
value: {{ join " " .Values.session.extraApkPackages | quote }}
- name: CLAUDE_CODE_VERSION
value: {{ $ccVersion | quote }}
{{- with .Values.anthropic.extraEnv }}
- name: ANTHROPIC_EXTRA_ENV_JSON
value: {{ toJson . | quote }}
{{- end }}
{{- with .Values.session.podLabels }}
- name: SESSION_POD_LABELS_JSON
value: {{ toJson . | quote }}
{{- end }}
{{- with .Values.session.podAnnotations }}
- name: SESSION_POD_ANNOTATIONS_JSON
value: {{ toJson . | quote }}
{{- end }}
{{- with .Values.session.nodeSelector }}
- name: SESSION_NODE_SELECTOR_JSON
value: {{ toJson . | quote }}
{{- end }}
{{- with .Values.session.tolerations }}
- name: SESSION_TOLERATIONS_JSON
value: {{ toJson . | quote }}
{{- end }}
{{- with .Values.session.priorityClassName }}
- name: SESSION_PRIORITY_CLASS
value: {{ . | quote }}
{{- end }}
- name: AUTH_MODE
value: {{ .Values.auth.mode | quote }}
{{- if eq .Values.auth.mode "token" }}
- name: AUTH_TOKEN_FILE
value: /etc/manager-secret/token
{{- end }}
ports:
- name: manager
containerPort: 8081
securityContext:
{{- include "claude-code.containerSecurityContext" . | nindent 12 }}
readinessProbe:
httpGet: { path: /healthz, port: manager }
periodSeconds: 10
livenessProbe:
httpGet: { path: /healthz, port: manager }
initialDelaySeconds: 10
periodSeconds: 20
resources:
{{- toYaml .Values.manager.resources | nindent 12 }}
volumeMounts:
- name: manager-app
mountPath: /app
readOnly: true
- name: manager-tmp
mountPath: /tmp
{{- if eq .Values.auth.mode "token" }}
- name: manager-secret
mountPath: /etc/manager-secret
readOnly: true
{{- end }}
volumes:
- name: gateway-config
configMap:
name: {{ include "claude-code.fullname" . }}-gateway
defaultMode: 0555
- name: nginx-tmp
emptyDir:
medium: Memory
sizeLimit: 16Mi
- name: manager-app
configMap:
name: {{ include "claude-code.fullname" . }}-manager
defaultMode: 0555
- name: manager-tmp
emptyDir:
medium: Memory
sizeLimit: 16Mi
{{- if eq .Values.auth.mode "token" }}
- name: manager-secret
secret:
secretName: {{ include "claude-code.authSecretName" . }}
items:
- key: {{ .Values.auth.token.existingSecretKey | default "token" }}
path: token
{{- end }}
{{- if eq .Values.auth.mode "basic" }}
- name: gateway-secret
secret:
secretName: {{ include "claude-code.authSecretName" . }}
items:
- key: {{ .Values.auth.basic.existingSecretKey | default "htpasswd" }}
path: htpasswd
{{- end }}
+40
View File
@@ -0,0 +1,40 @@
{{- if .Values.ingress.enabled }}
{{- $fullName := include "claude-code.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ $fullName }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "claude-code.labels" . | nindent 4 }}
annotations:
{{- /* WebSocket を長時間維持できるよう read/send タイムアウトを既定で延長(明示指定があれば優先) */}}
{{- $defaults := dict
"nginx.ingress.kubernetes.io/proxy-read-timeout" "3600"
"nginx.ingress.kubernetes.io/proxy-send-timeout" "3600" }}
{{- toYaml (merge (deepCopy .Values.ingress.annotations) $defaults) | nindent 4 }}
spec:
{{- with .Values.ingress.className }}
ingressClassName: {{ . }}
{{- end }}
{{- with .Values.ingress.tls }}
tls:
{{- toYaml . | nindent 4 }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType | default "Prefix" }}
backend:
service:
name: {{ $fullName }}
port:
number: {{ $svcPort }}
{{- end }}
{{- end }}
{{- end }}
+19
View File
@@ -0,0 +1,19 @@
{{- if .Values.sessionsNamespace.limitRange.enabled }}
{{- $lr := .Values.sessionsNamespace.limitRange -}}
apiVersion: v1
kind: LimitRange
metadata:
name: {{ include "claude-code.fullname" . }}-sessions
namespace: {{ include "claude-code.sessionsNamespace" . }}
labels:
{{- include "claude-code.labels" . | nindent 4 }}
spec:
limits:
- type: Container
max:
{{- toYaml $lr.max | nindent 8 }}
default:
{{- toYaml $lr.default | nindent 8 }}
defaultRequest:
{{- toYaml $lr.defaultRequest | nindent 8 }}
{{- end }}
+19
View File
@@ -0,0 +1,19 @@
{{- if and .Values.sessionsNamespace.create (ne (include "claude-code.sessionsNamespace" .) .Release.Namespace) }}
apiVersion: v1
kind: Namespace
metadata:
name: {{ include "claude-code.sessionsNamespace" . }}
labels:
{{- include "claude-code.labels" . | nindent 4 }}
{{- with .Values.sessionsNamespace.podSecurityLevel }}
pod-security.kubernetes.io/enforce: {{ . }}
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/audit: {{ . }}
pod-security.kubernetes.io/warn: {{ . }}
{{- end }}
annotations:
"helm.sh/resource-policy": keep
{{- with .Values.commonAnnotations }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
+51
View File
@@ -0,0 +1,51 @@
{{- if .Values.networkPolicy.enabled }}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "claude-code.fullname" . }}-manager
namespace: {{ .Release.Namespace }}
labels:
{{- include "claude-code.labels" . | nindent 4 }}
spec:
podSelector:
matchLabels:
{{- include "claude-code.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: control-plane
policyTypes:
- Ingress
- Egress
ingress:
# ブラウザ / Ingress コントローラーからの受付ポート
- ports:
- protocol: TCP
port: {{ .Values.gateway.port }}
egress:
# DNS
- to:
- namespaceSelector:
matchLabels:
{{- toYaml .Values.networkPolicy.dnsNamespaceSelector | nindent 14 }}
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
# kube-apiserver(セッション Pod/Service の CRUD)
- ports:
- protocol: TCP
port: 443
- protocol: TCP
port: 6443
# セッション Pod の ttyd へのリバースプロキシ
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: {{ include "claude-code.sessionsNamespace" . }}
podSelector:
matchLabels:
app.kubernetes.io/name: claude-code
app.kubernetes.io/component: session
ports:
- protocol: TCP
port: {{ .Values.session.ttydPort }}
{{- end }}
+75
View File
@@ -0,0 +1,75 @@
{{- if .Values.networkPolicy.enabled }}
{{- $np := .Values.networkPolicy -}}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "claude-code.fullname" . }}-session
namespace: {{ include "claude-code.sessionsNamespace" . }}
labels:
{{- include "claude-code.labels" . | nindent 4 }}
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: claude-code
app.kubernetes.io/component: session
policyTypes:
- Ingress
- Egress
ingress:
# 制御プレーン(gateway)からの ttyd 接続のみ許可
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: {{ .Release.Namespace }}
podSelector:
matchLabels:
{{- include "claude-code.selectorLabels" . | nindent 14 }}
app.kubernetes.io/component: control-plane
ports:
- protocol: TCP
port: {{ .Values.session.ttydPort }}
egress:
# DNS
- to:
- namespaceSelector:
matchLabels:
{{- toYaml $np.dnsNamespaceSelector | nindent 14 }}
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
{{- if $np.allowInternetHttps }}
# HTTPS(git over HTTPS + api.anthropic.com)。内部レンジは except で遮断。
- to:
- ipBlock:
cidr: 0.0.0.0/0
{{- with $np.blockedEgressCIDRs }}
except:
{{- toYaml . | nindent 14 }}
{{- end }}
ports:
- protocol: TCP
port: 443
{{- end }}
{{- if $np.allowSshGit }}
# SSH git(port 22)
- to:
- ipBlock:
cidr: 0.0.0.0/0
{{- with $np.blockedEgressCIDRs }}
except:
{{- toYaml . | nindent 14 }}
{{- end }}
ports:
- protocol: TCP
port: 22
{{- end }}
{{- range $np.sessionEgressExtra }}
- to:
- ipBlock:
cidr: {{ .cidr }}
ports:
{{- toYaml .ports | nindent 8 }}
{{- end }}
{{- end }}
+15
View File
@@ -0,0 +1,15 @@
{{- if .Values.podDisruptionBudget.enabled }}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ include "claude-code.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "claude-code.labels" . | nindent 4 }}
spec:
minAvailable: {{ .Values.podDisruptionBudget.minAvailable }}
selector:
matchLabels:
{{- include "claude-code.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: control-plane
{{- end }}
+40
View File
@@ -0,0 +1,40 @@
{{- $sessNs := include "claude-code.sessionsNamespace" . -}}
---
# 制御プレーンがセッション NS 内で必要とする最小権限。
# ClusterRole は使わず、セッション NS に閉じた Role のみ。
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "claude-code.fullname" . }}-manager
namespace: {{ $sessNs }}
labels:
{{- include "claude-code.labels" . | nindent 4 }}
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch", "create", "delete", "deletecollection"]
- apiGroups: [""]
resources: ["pods/log", "pods/status"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["services"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["events"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "claude-code.fullname" . }}-manager
namespace: {{ $sessNs }}
labels:
{{- include "claude-code.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "claude-code.fullname" . }}-manager
subjects:
- kind: ServiceAccount
name: {{ include "claude-code.managerServiceAccountName" . }}
namespace: {{ .Release.Namespace }}
+22
View File
@@ -0,0 +1,22 @@
{{- if .Values.sessionsNamespace.resourceQuota.enabled }}
{{- $rq := .Values.sessionsNamespace.resourceQuota -}}
{{- $max := int .Values.session.maxSessions -}}
apiVersion: v1
kind: ResourceQuota
metadata:
name: {{ include "claude-code.fullname" . }}-sessions
namespace: {{ include "claude-code.sessionsNamespace" . }}
labels:
{{- include "claude-code.labels" . | nindent 4 }}
spec:
hard:
{{- if $rq.hard }}
{{- /* 明示指定された上限をそのまま使う */}}
{{- toYaml $rq.hard | nindent 4 }}
{{- else }}
{{- /* 既定は同時セッション数の抑制のみ。compute 集計上限は hard で明示指定する。 */}}
pods: {{ add $max 2 | quote }}
count/services: {{ add $max 2 | quote }}
count/pods: {{ add $max 2 | quote }}
{{- end }}
{{- end }}
+18
View File
@@ -0,0 +1,18 @@
{{- if not .Values.anthropic.existingSecret }}
# Claude Code 認証情報。セッション NS に作成し、セッション Pod へ env として注入する。
# 値が空のままインストールするとセッション内の claude が認証に失敗する(NOTES 参照)。
apiVersion: v1
kind: Secret
metadata:
name: {{ include "claude-code.fullname" . }}-anthropic
namespace: {{ include "claude-code.sessionsNamespace" . }}
labels:
{{- include "claude-code.labels" . | nindent 4 }}
type: Opaque
stringData:
{{- if eq .Values.anthropic.authMode "oauth" }}
CLAUDE_CODE_OAUTH_TOKEN: {{ .Values.anthropic.oauthToken | quote }}
{{- else }}
ANTHROPIC_API_KEY: {{ .Values.anthropic.apiKey | quote }}
{{- end }}
{{- end }}
+32
View File
@@ -0,0 +1,32 @@
{{- if eq .Values.auth.mode "token" }}
{{- if not .Values.auth.token.existingSecret }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "claude-code.fullname" . }}-auth
namespace: {{ .Release.Namespace }}
labels:
{{- include "claude-code.labels" . | nindent 4 }}
annotations:
"helm.sh/resource-policy": keep
type: Opaque
stringData:
token: {{ include "claude-code.resolvedToken" . | quote }}
{{- end }}
{{- else if eq .Values.auth.mode "basic" }}
{{- if not .Values.auth.basic.existingSecret }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "claude-code.fullname" . }}-auth
namespace: {{ .Release.Namespace }}
labels:
{{- include "claude-code.labels" . | nindent 4 }}
type: Opaque
stringData:
htpasswd: |
{{- range .Values.auth.basic.users }}
{{ . }}
{{- end }}
{{- end }}
{{- end }}
+30
View File
@@ -0,0 +1,30 @@
{{- $ctx := . -}}
{{- $sessNs := include "claude-code.sessionsNamespace" . -}}
{{- range $name, $id := .Values.git.identities }}
{{- if not $id.existingSecret }}
---
# Git アイデンティティ "{{ $name }}"(type: {{ $id.type }})
apiVersion: v1
kind: Secret
metadata:
name: {{ include "claude-code.fullname" $ctx }}-git-{{ $name }}
namespace: {{ $sessNs }}
labels:
{{- include "claude-code.labels" $ctx | nindent 4 }}
claude-code.cafepieters.com/git-identity: {{ $name | quote }}
type: Opaque
stringData:
{{- if eq $id.type "ssh" }}
{{- if not $id.sshPrivateKey }}{{ fail (printf "git.identities.%s: type=ssh には sshPrivateKey か existingSecret が必要です" $name) }}{{- end }}
{{- if not $id.knownHosts }}{{ fail (printf "git.identities.%s: type=ssh には knownHosts が必須です(ホスト鍵の TOFU を無効化するため)" $name) }}{{- end }}
id: {{ $id.sshPrivateKey | quote }}
known_hosts: {{ $id.knownHosts | quote }}
{{- else if eq $id.type "https-token" }}
{{- if not $id.token }}{{ fail (printf "git.identities.%s: type=https-token には token か existingSecret が必要です" $name) }}{{- end }}
username: {{ $id.username | default "x-access-token" | quote }}
token: {{ $id.token | quote }}
{{- else }}
{{ fail (printf "git.identities.%s: type は ssh か https-token を指定してください" $name) }}
{{- end }}
{{- end }}
{{- end }}
+18
View File
@@ -0,0 +1,18 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "claude-code.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "claude-code.labels" . | nindent 4 }}
app.kubernetes.io/component: control-plane
spec:
type: {{ .Values.service.type }}
ports:
- name: http
port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
selector:
{{- include "claude-code.selectorLabels" . | nindent 4 }}
app.kubernetes.io/component: control-plane
+22
View File
@@ -0,0 +1,22 @@
---
# 制御プレーン(manager / reaper)用 ServiceAccount。
# セッション NS 内で Pod / Service を作成・削除する権限のみを持つ(rbac.yaml)。
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "claude-code.managerServiceAccountName" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "claude-code.labels" . | nindent 4 }}
automountServiceAccountToken: true
---
# セッション Pod 用 ServiceAccount。
# いかなる RBAC も紐づけず、トークンも配布しない(API サーバーへ到達不能)。
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "claude-code.sessionServiceAccountName" . }}
namespace: {{ include "claude-code.sessionsNamespace" . }}
labels:
{{- include "claude-code.labels" . | nindent 4 }}
automountServiceAccountToken: false