PVC name is now immutable — hardcoded in all 4 deployment templates instead of being a configurable Helm value. Removed pvcName from values.yaml and --set pvcName from deploy.sh. This ensures the PVC can never be renamed or deleted by Helm operations, only manually via kubectl.
201 lines
6.5 KiB
Markdown
201 lines
6.5 KiB
Markdown
# Deploy Helm Chart
|
||
|
||
Monolithic Helm chart for all backend services.
|
||
|
||
## Services
|
||
|
||
| Service | Port(s) | Replicas | HPA | Purpose |
|
||
|----------------------|-------------------------|----------|----------|---------------------------------------------|
|
||
| `app` | 3000 (HTTP) | 2 | 2–10 | Main API server |
|
||
| `gitserver` | 8021 (HTTP), 2222 (SSH) | 1 | 1–5 | Git HTTP + SSH server |
|
||
| `email_worker` | 8084 (HTTP) | 1 | disabled | Email queue consumer (single instance only) |
|
||
| `git_hook` | 8083 (HTTP) | 1 | 1–5 | Git hook worker pool |
|
||
| `metrics_aggregator` | 9090 (HTTP) | 1 | 1–5 | Prometheus scrape + Loki push |
|
||
| `static_server` | 8081 (HTTP) | 1 | 1–5 | Static file server (avatars, blobs, media) |
|
||
|
||
## Prerequisites
|
||
|
||
The following resources must exist in the cluster **before** installing the Helm chart. They are not managed by Helm —
|
||
install, upgrade, and uninstall of the chart will not touch them.
|
||
|
||
### 1. Namespace
|
||
|
||
```bash
|
||
kubectl create namespace app
|
||
```
|
||
|
||
### 2. PVC (aliyun-nfs-app, 200Ti, ReadWriteMany)
|
||
|
||
```bash
|
||
kubectl apply -f - <<'EOF'
|
||
apiVersion: v1
|
||
kind: PersistentVolumeClaim
|
||
metadata:
|
||
name: shared-data
|
||
namespace: app
|
||
spec:
|
||
accessModes:
|
||
- ReadWriteMany
|
||
resources:
|
||
requests:
|
||
storage: 200Ti
|
||
storageClassName: aliyun-nfs-app
|
||
EOF
|
||
```
|
||
|
||
> The chart references this PVC by hardcoded name `shared-data`. This name is immutable — it cannot be changed via Helm
|
||
> values.
|
||
|
||
### 3. ConfigMap
|
||
|
||
```bash
|
||
kubectl apply -f - <<'EOF'
|
||
apiVersion: v1
|
||
kind: ConfigMap
|
||
metadata:
|
||
name: app-env
|
||
namespace: app
|
||
data:
|
||
APP_REPOS_ROOT: "/data/repos"
|
||
APP_AVATAR_PATH: "/data/avatars"
|
||
STORAGE_PATH: "/data/files"
|
||
STATIC_ROOT: "/data"
|
||
APP_LOG_LEVEL: "info"
|
||
APP_COOKIE_SECURE: "false"
|
||
APP_DOMAIN_URL: "https://your-domain.com"
|
||
APP_DATABASE_URL: "postgres://user:pass@postgres:5432/app"
|
||
APP_REDIS_URL: "redis://redis:6379"
|
||
APP_AI_BASIC_URL: "https://api.openai.com/v1"
|
||
APP_AI_API_KEY: "sk-..."
|
||
APP_SMTP_PASSWORD: "..."
|
||
APP_SESSION_SECRET: "min-32-byte-random-string..."
|
||
APP_SSH_SERVER_PRIVATE_KEY: "<hex-encoded-private-key>"
|
||
EOF
|
||
```
|
||
|
||
| Variable | Default / Example | Required |
|
||
|------------------------------|-----------------------------|-----------|
|
||
| `APP_REPOS_ROOT` | `/data/repos` | Yes |
|
||
| `APP_AVATAR_PATH` | `/data/avatars` | Yes |
|
||
| `STORAGE_PATH` | `/data/files` | Yes |
|
||
| `STATIC_ROOT` | `/data` | Yes |
|
||
| `APP_LOG_LEVEL` | `info` | No |
|
||
| `APP_COOKIE_SECURE` | `false` | No |
|
||
| `APP_DOMAIN_URL` | `https://your-domain.com` | Yes |
|
||
| `APP_DATABASE_URL` | `postgres://...` | **Yes** |
|
||
| `APP_REDIS_URL` | `redis://...` | **Yes** |
|
||
| `APP_AI_BASIC_URL` | `https://api.openai.com/v1` | **Yes** |
|
||
| `APP_AI_API_KEY` | `sk-...` | **Yes** |
|
||
| `APP_SMTP_PASSWORD` | `...` | **Yes** |
|
||
| `APP_SESSION_SECRET` | min 32 bytes | **Yes** |
|
||
| `APP_SSH_SERVER_PRIVATE_KEY` | hex-encoded PEM | **Yes** |
|
||
| `APP_SSH_PORT` | `2222` | Yes (k8s) |
|
||
|
||
> **SSH host key**: `APP_SSH_SERVER_PRIVATE_KEY` must be the hex-encoded Ed25519 private key PEM bytes.
|
||
> ```bash
|
||
> ssh-keygen -t ed25519 -f /tmp/ssh_host_key -N ""
|
||
> hexdump -v -e '/1 "%02x"' < /tmp/ssh_host_key
|
||
> ```
|
||
>
|
||
> **Session secret**: generate 48 random bytes:
|
||
> ```bash
|
||
> openssl rand -base64 48
|
||
> ```
|
||
>
|
||
> Override the ConfigMap name with `--set configMapName=your-cm-name`.
|
||
|
||
### 4. Verify prerequisites
|
||
|
||
```bash
|
||
kubectl get namespace app
|
||
kubectl get pvc -n app shared-data
|
||
kubectl get configmap -n app app-env
|
||
```
|
||
|
||
## Quick Start
|
||
|
||
```bash
|
||
helm template deploy ./deploy --namespace app --set imageRegistry=ghcr.io/your-org
|
||
helm lint ./deploy
|
||
|
||
# Install
|
||
helm upgrade --install deploy ./deploy \
|
||
--namespace app \
|
||
--set imageRegistry=ghcr.io/your-org \
|
||
--set imageTag=v0.2.9
|
||
```
|
||
|
||
## Storage
|
||
|
||
All services share a single PVC (`shared-data`) via `subPath` mounts:
|
||
|
||
| SubPath | Mount | Used By |
|
||
|-----------|-----------------|--------------------------|
|
||
| `repos` | `/data/repos` | app, gitserver, git-hook |
|
||
| `avatars` | `/data/avatars` | app |
|
||
| `files` | `/data/files` | app |
|
||
| `static` | `/data` | static-server |
|
||
|
||
## Autoscaling
|
||
|
||
All services except `email_worker` have HPA enabled by default. The email worker is fixed at 1 replica and must not be
|
||
scaled.
|
||
|
||
To adjust HPA bounds per service:
|
||
|
||
```bash
|
||
--set services.app.autoscaling.maxReplicas=20
|
||
--set services.app.autoscaling.targetCPUUtilization=70
|
||
```
|
||
|
||
To disable HPA for a service:
|
||
|
||
```bash
|
||
--set services.git_hook.autoscaling.enabled=false
|
||
```
|
||
|
||
## Ingress
|
||
|
||
```bash
|
||
helm upgrade --install deploy ./deploy \
|
||
--namespace app \
|
||
--set ingress.enabled=true \
|
||
--set ingress.className=nginx \
|
||
--set ingress.hosts[0].host=your-domain.com
|
||
```
|
||
|
||
## Dependencies
|
||
|
||
All services require these to be reachable from the cluster:
|
||
|
||
- PostgreSQL (via `APP_DATABASE_URL`)
|
||
- Redis (via `APP_REDIS_URL`)
|
||
- Git binary (included in all Docker images)
|
||
- OpenAI-compatible API (via `APP_AI_BASIC_URL` + `APP_AI_API_KEY`)
|
||
- Qdrant vector DB (via `APP_QDRANT_URL`)
|
||
- SMTP server (via `APP_SMTP_*`)
|
||
- Embedding model (via `APP_EMBED_MODEL_*`)
|
||
|
||
Optional dependencies with graceful degradation:
|
||
|
||
| Dependency | Variable | Fallback |
|
||
|----------------|-------------------------------|------------------|
|
||
| NATS JetStream | `NATS_URL` + `NATS_TOKEN` | Redis queue |
|
||
| Loki | `LOKI_URL` | Logs discarded |
|
||
| OTEL Collector | `OTEL_EXPORTER_OTLP_ENDPOINT` | Tracing disabled |
|
||
|
||
## Production Example
|
||
|
||
```bash
|
||
helm upgrade --install deploy ./deploy \
|
||
--namespace app \
|
||
--set imageRegistry=ghcr.io/your-org \
|
||
--set imageTag=v0.2.9 \
|
||
--set services.app.replicas=3 \
|
||
--set services.app.autoscaling.maxReplicas=20 \
|
||
--set ingress.enabled=true \
|
||
--set ingress.className=nginx \
|
||
--set ingress.hosts[0].host=your-domain.com \
|
||
--set configMapName=app-env
|
||
```
|