> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tuneplane.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 用 Helm 部署

> 同一套 Kubernetes 部署，改为 values 驱动——适合多环境或 GitOps 流水线。

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
helm install tuneplane deploy/helm/tuneplane-server \
  --namespace tuneplane --create-namespace \
  --set image.repository=registry.company.com/tuneplane/tuneplane-server \
  --set config.TUNEPLANE_INGEST_URL=http://tuneplane-server.tuneplane.svc.cluster.local \
  --set secrets.existingSecret=tuneplane-server-secrets
```

这个 chart 渲染出的对象和 [kustomize 清单](/zh-Hans/ops/install-kubernetes)一样：
ServiceAccount、ConfigMap、PVC、Role、RoleBinding、Service、Deployment，
以及可选的 Ingress 和 Secret。

跑多套环境、或者想用 values 驱动部署时选 chart；想一眼看清到底会 apply 什么时选清单。

## 一份可以直接改的 values

```yaml values.yaml theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
image:
  repository: registry.company.com/tuneplane/tuneplane-server
  tag: "0.3.0"
imagePullSecrets:
  - name: tuneplane-registry

storage:
  accessMode: ReadWriteMany     # 没有商量余地，见下
  size: 2Ti
  storageClassName: nfs-client

config:
  TUNEPLANE_DEFAULT_FLEET_KIND: kuberay
  TUNEPLANE_STORAGE_ROOT: /tuneplane
  TUNEPLANE_PUBLIC_URL: https://tuneplane.company.com
  TUNEPLANE_INGEST_URL: http://tuneplane-server.tuneplane.svc.cluster.local
  TUNEPLANE_KUBERAY_RAY_VERSION: "2.55.1"
  TUNEPLANE_K8S_SHM_SIZE: 64Gi
  TUNEPLANE_KUBERAY_PRERUNNING_DEADLINE_S: "1800"

secrets:
  existingSecret: tuneplane-server-secrets

ingress:
  enabled: true
  className: nginx
  host: tuneplane.company.com
  tls:
    enabled: true
    secretName: tuneplane-server-tls
```

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
helm upgrade --install tuneplane deploy/helm/tuneplane-server \
  -n tuneplane --create-namespace -f values.yaml
```

## 三个决定成败的值

<ParamField path="storage.accessMode" type="ReadWriteMany" required>
  RWO 的 claim **不会失败**。Kubernetes 会给每个 Pod 各自一个卷，
  于是多节点作业的 checkpoint 分片散落在不同 Pod 上，这次 run 再也续不了——
  而日志里没有任何东西说明原因。chart 默认 RWX；StorageClass 必须真的支持它。
</ParamField>

<ParamField path="config.TUNEPLANE_INGEST_URL" type="url" required>
  训练 Pod 往它 POST 指标。用集群内服务名。
  外部 ingress 地址会先出集群再绕回来；`127.0.0.1` 是 worker 自己的回环。
</ParamField>

<ParamField path="secrets.existingSecret" type="string" required>
  没有 `TUNEPLANE_WEB_JWT_SECRET`，每个进程各用一把自己的密钥签名：
  重启即全员掉线，多副本根本不可用。chart 的安装提示会在两项都没设时给出警告。
</ParamField>

## 密钥

`secrets.create: true` 会从 `secrets.data` 渲染一个 Secret——
测试集群上很方便，生产上不对，因为那些值会落进你的 release 历史。

把 `secrets.existingSecret` 指向由 `kubectl create secret`、Sealed Secrets
或 External Secrets Operator 产生的 Secret：

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
kubectl -n tuneplane create secret generic tuneplane-server-secrets \
  --from-literal=TUNEPLANE_WEB_JWT_SECRET="$(openssl rand -hex 32)" \
  --from-literal=TUNEPLANE_DB_URL='postgresql+psycopg://tuneplane:...@postgres:5432/tuneplane' \
  --from-literal=TUNEPLANE_REDIS_URL='redis://:...@redis:6379/0' \
  --from-literal=TUNEPLANE_S3_SECRET_KEY='...' \
  --from-literal=TUNEPLANE_SECRET_ENC_KEY='...'
```

## chart 替你做的一件事

`TUNEPLANE_K8S_STORAGE_PVC` 是从 chart 实际创建的那个 claim 填进去的，
所以执行器被告知的永远是真实存在的那个 claim。这一对关系手写时很容易错开——
执行器去检查一个并非被挂载的 claim、报告健康，而这个错位只有在某次多节点 run 续不上时才暴露。

只有复用一个不归 chart 所有的 claim 时才需要覆盖它：

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
helm install tuneplane deploy/helm/tuneplane-server --set storage.existingClaim=my-nfs-claim
```

## apply 之前先验

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
helm lint deploy/helm/tuneplane-server
helm template tuneplane deploy/helm/tuneplane-server -f values.yaml | kubectl apply --dry-run=client -f -
```

## 确认成功

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
kubectl -n tuneplane rollout status deploy/tuneplane-server
kubectl -n tuneplane get pvc -o custom-columns=NAME:.metadata.name,MODES:.spec.accessModes
kubectl -n tuneplane port-forward svc/tuneplane-server 8080:80
curl -s localhost:8080/api/cluster/health | jq .storage
```

最后那一条最值得读：它把 `TUNEPLANE_K8S_STORAGE_SHARED` 的声明和 claim 的真实访问模式做核对，
这正是能在作业之前抓到 RWO claim 的那道检查。

<Note>
  这个 chart 的每次改动都会跑 `helm lint` 和 `helm template` 验证。
  对着真实集群安装不是这里的 CI 能做的事——没有哪个 runner 有集群——
  所以在你的集群上第一次安装就是第一次真实测试。先跑上面那条 dry-run。
</Note>
