Kubernetes 部署清单,Argo CD 从这里同步
  • JavaScript 100%
Find a file
AkagiYui 9105f0db3f CI 接管发布:不可变镜像 tag + EasyTier 中继
- apps/ 改成 kustomize,镜像 tag 统一在 kustomization.yaml 的 images 段,
  CI 用 hack/set-image-tag.mjs 改一行就是一次发布
- 去掉 imagePullPolicy: Always——tag 不可变之后 Argo CD 自己能发现更新
- apps/easytier.yaml:EasyTier 公共共享节点,klipper 直接绑宿主机 11010
- player-api 补 INVOICE_PROVIDER / NATS_URL / EASYTIER_PEERS,admin-api 补 NATS_URL
- runner:任务容器连边车 dind,并把 Forgejo 的公网名指到控制 IP(hairpin)
2026-09-03 05:34:37 +08:00
apps CI 接管发布:不可变镜像 tag + EasyTier 中继 2026-09-03 05:34:37 +08:00
argocd 业务服务:两个 API、官网、管理站点的部署清单 2026-09-03 04:15:25 +08:00
bootstrap 基础设施部署清单 2026-09-03 03:44:55 +08:00
hack CI 接管发布:不可变镜像 tag + EasyTier 中继 2026-09-03 05:34:37 +08:00
infra CI 接管发布:不可变镜像 tag + EasyTier 中继 2026-09-03 05:34:37 +08:00
vendor 基础设施部署清单 2026-09-03 03:44:55 +08:00
.gitignore 基础设施部署清单 2026-09-03 03:44:55 +08:00
README.md 文档:补上业务服务部署、镜像构建与三个环境坑 2026-09-03 05:01:56 +08:00

部署

测试环境的 Kubernetes 与基础设施。清单都在这个目录下, 可以直接 kubectl apply / helm -f,也可以后续交给 Argo CD 托管。

集群本身怎么装的见 ../../staging-env.md

现状

单节点 k3s v1.36.4,机器 96 核 / 123 GiB / 900 GB 可用。

组件 版本 位置 入口
Traefik 3.7.8 kube-system k3s 自带
cert-manager chart jetstack cert-manager
CloudNativePG operator chart 0.29.0 cnpg-system
PostgreSQL 18.1 CNPG Cluster3 实例 dainiwan pg-rw / pg-ro
Valkey 9.0 StatefulSet dainiwan valkey:6379
NATS + JetStream chart nats dainiwan nats:4222
Argo CD chart argo-cd argocd argocd.staging…
Forgejo 15.0.7 chart 17.1.5OCI forgejo forgejo.staging…
Forgejo Runner 9.0.1 Deployment + dind forgejo
GlitchTip 6.0.3 普通清单 glitchtip glitchtip.staging…

GitOps

集群由 Argo CD 从 Forgejo 上的这个仓库同步,不要再手工 apply

https://forgejo.staging.dnw.434512.xyz/dainiwan/deploy

改配置的正确姿势:改这个仓库 → push → Argo CD 自动跟随(实测约 100 秒内生效)。

结构

argocd/root.yaml 是 app-of-apps 的根,手工 apply 一次即可; 之后它管着 argocd/apps/ 下的所有 Application。加一个组件 = 往 argocd/apps/ 丢一个 yaml 再 push。

kubectl apply -f argocd/root.yaml   # 只需要做一次
kubectl -n argocd get application    # 之后看这里

Helm 类组件用的是多源 Applicationchart 从上游拉values 从本仓库取 $values/infra/<组件>/values.yaml),这样 values 有版本历史chart 不用 vendor。

两个不归 Argo CD 管的东西

Argo CD 自己。 让它管自己,升级时会「同步到一半把自己重启」,救起来很麻烦。 它由 deploy/argocd/values.yaml + helm 手工维护。

所有 Secret。 口令、令牌、证书都只在集群里,仓库里一个都没有。 新建环境时按下面「装机顺序」里的 kubectl create secret 重新生成。 以后要进 GitOps 的话用 Sealed Secrets 或 External Secrets别直接提交。

helm 的历史记录还在

cert-manager / cnpg / nats / forgejo 这四个当初是 helm 装的,现在归 Argo CD 管了, 但 helm list -A 里还能看到旧的 release 记录。它们已经不是事实来源。

留着是为了保底Argo CD 出问题时还能 helm upgrade --install 直接救。 代价是有人可能误以为该用 helm 改配置,然后和 Argo CD 的 selfHeal 打架。 想彻底清干净:

for r in cert-manager:cert-manager cnpg:cnpg-system nats:dainiwan forgejo:forgejo; do
  kubectl -n ${r#*:} delete secret -l "owner=helm,name=${r%%:*}"
done

装机顺序

依赖关系决定顺序,跳步会失败。

export KUBECONFIG=/etc/rancher/k3s/k3s.yaml   # 服务器上;/etc/profile.d/k3s.sh 已经写好

# 0. 命名空间
kubectl apply -f deploy/bootstrap/namespaces.yaml

# 1. helm 仓库
helm repo add jetstack https://charts.jetstack.io
helm repo add cnpg     https://cloudnative-pg.github.io/charts
helm repo add nats     https://nats-io.github.io/k8s/helm/charts
helm repo add argo     https://argoproj.github.io/argo-helm
helm repo update

# 2. cert-manager
helm upgrade --install cert-manager jetstack/cert-manager -n cert-manager --create-namespace \
  -f deploy/infra/cert-manager/values.yaml --wait
kubectl apply -f deploy/infra/cert-manager/clusterissuer.yaml

# 3. CNPG operatorchart 要从本机传见下面「GitHub 下载」)
helm upgrade --install cnpg /root/charts/cloudnative-pg-0.29.0.tgz -n cnpg-system --create-namespace \
  -f deploy/infra/cnpg/values.yaml --wait

# 4. 数据库口令(随机生成,只落在 Secret 里)
kubectl -n dainiwan create secret generic pg-app-credentials \
  --from-literal=username=dainiwan \
  --from-literal=password="$(openssl rand -base64 24 | tr -d /=+ | cut -c1-28)"
for app in forgejo glitchtip; do
  kubectl -n dainiwan create secret generic ${app}-db --type=kubernetes.io/basic-auth \
    --from-literal=username=$app \
    --from-literal=password="$(openssl rand -base64 24 | tr -d /=+ | cut -c1-28)"
done

# 5. Postgres 集群与附加库
kubectl apply -f deploy/infra/postgres/cluster.yaml
kubectl apply -f deploy/infra/postgres/databases.yaml
kubectl -n dainiwan wait --for=condition=Ready cluster/pg --timeout=10m

# 6. Valkey 与 NATS
kubectl apply -f deploy/infra/valkey/valkey.yaml
helm upgrade --install nats nats/nats -n dainiwan -f deploy/infra/nats/values.yaml --wait

# 7. Argo CD
helm upgrade --install argocd argo/argo-cd -n argocd --create-namespace \
  -f deploy/argocd/values.yaml --wait

# 8. Forgejochart 是 OCI 制品,不在传统 helm 仓库里)
kubectl -n forgejo create secret generic forgejo-admin \
  --from-literal=username=dnwadmin \
  --from-literal=password="$(openssl rand -base64 24 | tr -d /=+ | cut -c1-24)" \
  --from-literal=email=support@joelclub.com
kubectl -n dainiwan get secret forgejo-db -o yaml \
  | sed 's/namespace: dainiwan/namespace: forgejo/' | kubectl apply -f -
helm upgrade --install forgejo oci://code.forgejo.org/forgejo-helm/forgejo --version 17.1.5 \
  -n forgejo -f deploy/infra/forgejo/values.yaml

# 9. Forgejo Runner
POD=$(kubectl -n forgejo get pod -l app.kubernetes.io/name=forgejo -o name | head -1)
TOKEN=$(kubectl -n forgejo exec $POD -c forgejo -- forgejo forgejo-cli actions generate-runner-token | tail -1 | tr -d '\r\n')
kubectl -n forgejo create secret generic forgejo-runner-token --from-literal=token="$TOKEN"
kubectl apply -f deploy/infra/forgejo/runner.yaml

# 10. GlitchTip
PW=$(kubectl -n dainiwan get secret glitchtip-db -o jsonpath='{.data.password}' | base64 -d)
kubectl -n glitchtip create secret generic glitchtip-secrets \
  --from-literal=SECRET_KEY="$(openssl rand -base64 48 | tr -d /=+ | cut -c1-50)" \
  --from-literal=DATABASE_URL="postgres://glitchtip:${PW}@pg-rw.dainiwan.svc.cluster.local:5432/glitchtip" \
  --from-literal=REDIS_URL="redis://valkey.dainiwan.svc.cluster.local:6379/3"
kubectl apply -f deploy/infra/glitchtip/env.yaml -f deploy/infra/glitchtip/glitchtip.yaml

装的过程中踩到的坑

helm 找不到集群。 k3s 的 kubectl 会自动读 /etc/rancher/k3s/k3s.yamlhelm 不会。 已经写了 /etc/profile.d/k3s.sh 导出 KUBECONFIG,非交互式 ssh 里要自己 export。

GitHub release 资产下不动。 CNPG 的 chart 托管在 GitHub releases 上, 服务器上连拉三次都是 unexpected EOF(普通 GitHub 页面是通的,大文件不行)。 绕法是本机下好再传:

curl -sLO https://github.com/cloudnative-pg/charts/releases/download/cloudnative-pg-v0.29.0/cloudnative-pg-0.29.0.tgz
scp cloudnative-pg-0.29.0.tgz root@110.42.57.249:/root/charts/

镜像和普通 chart 仓库都没问题——只有 GitHub 的 release 资产要这么绕。

CNPG 的角色不能用独立的 DatabaseRole CR。 operator 只给 Cluster spec 里引用到的 Secret 授权,独立 CR 引用的 Secret 会因为 RBAC 读不到而一直 secrets "forgejo-db" is forbidden。角色要写在 Cluster 的 managed.roles 里。

Forgejo 的 chart 不在 helm 仓库里。 code.forgejo.org/api/packages/forgejo-helm/helm 的 index 返回 200 但里面是空的,helm search 找不到任何 chart。 它是 OCI 制品:oci://code.forgejo.org/forgejo-helm/forgejo

Forgejo 的 additionalConfigFromEnvs 必须放在 gitea: 段下面。 放顶层不报错也不生效表现是数据库口令为空、init 容器反复 password authentication failed

GlitchTip 的 Helm chart 拉不到。 官方 chart 在 GitLab 包仓库,匿名访问 401。 改成普通清单部署,顺便复用集群里的 Postgres 和 Valkey少维护一套库。

访问方式

*.staging.dnw.434512.xyz 已经配了泛解析,指向 114.66.55.190。 三个后台都是 Let's Encrypt 正式证书cert-manager HTTP-01 自动签发续期)。

export KUBECONFIG=~/.kube/dainiwan-staging.yaml   # 开发机上
kubectl get pods -A
服务 地址 账号
Forgejo https://forgejo.staging.dnw.434512.xyz dnwadmin
Argo CD https://argocd.staging.dnw.434512.xyz admin
GlitchTip https://glitchtip.staging.dnw.434512.xyz 首次进去创建

口令都在集群的 Secret 里,不写进仓库:

kubectl -n forgejo get secret forgejo-admin -o jsonpath='{.data.password}' | base64 -d
kubectl -n argocd  get secret argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d
kubectl -n dainiwan get secret pg-app-credentials -o jsonpath='{.data.password}' | base64 -d

Argo CD 的初始口令用完记得删掉那个 Secret。

集群内连接串

业务服务部署进来之后用这些地址:

DATABASE_URL=postgres://dainiwan:<pw>@pg-rw.dainiwan.svc.cluster.local:5432/dainiwan
VALKEY_ADDR=valkey.dainiwan.svc.cluster.local:6379
NATS_URL=nats://nats.dainiwan.svc.cluster.local:4222

pg-rw 永远指向当前主库,主从切换时不用改配置;只读查询可以走 pg-ro

业务服务

四个应用都在 apps/ 下,由 Argo CD 的 business Application 管。

服务 镜像 入口
admin-api dainiwan/admin-api:dev admin.staging…/api
player-api dainiwan/player-api:dev api.staging…
website dainiwan/website:dev www.staging…
admin-web dainiwan/admin-web:dev admin.staging…

管理站点的 /api 由 Traefik 直接转给 admin-api和静态站点同源 前端不需要跨域配置nginx 里也不用再代理一层。

构建镜像

镜像目前在服务器上手工构建Forgejo Actions 的流水线还没写):

ssh root@110.42.57.249
cd /root/src/backend && git pull      # 或 /root/src/frontend
REG=forgejo.staging.dnw.434512.xyz/dainiwan
docker build -f services/player-api/Dockerfile -t $REG/player-api:dev . && docker push $REG/player-api:dev

官网要在构建期连 API预渲染活动详情页多两个参数

cd /root/src/frontend
docker build --add-host api.staging.dnw.434512.xyz:110.42.57.249 \
  --build-arg NUXT_PUBLIC_API_BASE=https://api.staging.dnw.434512.xyz/api/v1 \
  -f app-website/Dockerfile -t $REG/website:dev .

推完镜像要 kubectl -n dainiwan rollout restart deploy/<名字> tag 是可变的 :devArgo CD 看不出变化。清单里已经设了 imagePullPolicy: Always,不然节点会一直复用本地旧镜像。 正式发版应该用不可变 taggit sha那时 Argo CD 能自己发现更新。

构建环境的三个坑

Go 模块代理。 proxy.golang.org 在这台机器上不通Dockerfile 里默认 GOPROXY=https://goproxy.cn,direct,海外构建用 --build-arg 覆盖。

基础镜像。 Docker 的 registry-mirrors 只对 docker.io 生效,而且不支持 带路径+鉴权的 Harbor 代理项目。做法是先从代理项目拉下来再打上规范 tag

M=registry-mirror.rainyun.cn/proxy-docker.io/library
docker pull $M/golang:1.27-alpine && docker tag $M/golang:1.27-alpine golang:1.27-alpine
docker pull registry-mirror.rainyun.cn/proxy-gcr.io/distroless/static-debian12:nonroot \
  && docker tag registry-mirror.rainyun.cn/proxy-gcr.io/distroless/static-debian12:nonroot \
     gcr.io/distroless/static-debian12:nonroot

前端镜像不能用 Alpine。 pnpm 12 会按 devEngines.runtime 自己下一个 Node 那是 glibc 构建的,在 musl 上跑不起来(缺 libatomic.so.1)。用 bookworm-slim。 另外 Node 26 起不再自带 corepack装 pnpm 要用 npm i -g pnpm@<版本>

数据库迁移

apps/migrate-job.yaml 是 Argo CD 的 PreSync 钩子,每次同步先跑迁移再滚服务。 注意钩子只在真的发生同步时执行——首次引导或应用已经 Synced 时不会触发, 那种情况下手工跑一次:

sed '/argocd.argoproj.io\/hook/d; s/name: db-migrate/name: db-migrate-once/' \
  apps/migrate-job.yaml | kubectl apply -f -

演示数据用同一个 Job 换成 command: ["/seed"]。seed 自带「非 development 不灌」 的保护,测试环境要临时把 APP_ENV 覆盖成 development

宿主机访问自建域名

这台机器到不了自己的业务 IP 114.66.55.190(雨云的 hairpin 限制), 所以 /etc/hosts 里把三个自建域名指到了控制 IP 110.42.57.249。 docker build 里同理,用 --add-host

还没做

  • 单机模拟多节点,用于故障转移演练。现在三个 Postgres 实例都在同一台机器上, 能演练「主库 Pod 挂掉自动切主」,演练不了节点级故障。 Postgres 的 affinity.enablePodAntiAffinity 现在是关的,多节点之后要打开。
  • 业务服务本身admin-api / player-api / 官网 / 管理站点)还没打镜像、没进集群。
  • Forgejo Actions 流水线:镜像还是手工构建推送的,没有 CI。
  • 不可变镜像 tag:现在是 :dev,改成 git sha 之后 Argo CD 才能自动发现新版本。
  • 备份CNPG 支持 PITR 到对象存储,还没配。
  • Secret 管理:现在全靠手工 kubectl create secret,没有版本、没法审计。 上生产前要换成 Sealed Secrets 或 External Secrets。