210 lines
6.8 KiB
Markdown
210 lines
6.8 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 |
|
||
|
||
Pods run as UID/GID `1000` and set `fsGroup: 1000` so Git processes can create temporary object
|
||
directories under bare repositories. If an existing PVC was previously written by another UID,
|
||
fix ownership once from a maintenance pod:
|
||
|
||
```bash
|
||
chown -R 1000:1000 /data/repos
|
||
chmod -R u+rwX,g+rwX /data/repos
|
||
```
|
||
|
||
## 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
|
||
```
|