feat: Alpine ベースの Claude Code リモート実行 Helm チャート初版 (v2.1.259)
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:
@@ -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 }}
|
||||
@@ -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 -}}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user