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,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 }}
|
||||
Reference in New Issue
Block a user