# fnOS Docker + Jenkins + k3d/K8s 标准发布流程
这套流程把飞牛 NAS 上的 Docker 保留为基础控制面,把业务运行逐步迁到 k3d/K3s:
- Jenkins Controller、Jenkins Agent、本地 Registry 仍由 Docker 管理。
- 后端 API 进入 k3d/K8s,由 Jenkins 构建镜像、推送本地 Registry、滚动更新 Deployment。
- Web 和 Playground 继续走现有 Nginx 静态发布,等后端链路稳定后再决定是否容器化。
## 固定命名
| 对象 | 名称 |
| --- | --- |
| Jenkins Agent | `kt-node-agent` |
| k3d 集群 | `kt-nas` |
| K8s namespace | `kt-prod` |
| 本地 Registry | `k3d-kt-registry.localhost:5000` |
| API Deployment | `kt-template-online-api` |
| API Service | `kt-template-online-api` |
| API K8s 容器名 | `api` |
| API 容器端口 | `48085` |
| API NodePort | `30085` |
| NAS 对外端口 | `48085` |
## 一次性初始化
先确保本机 SSH key 已授权到 NAS 的 root 用户,然后从仓库根目录执行:
```powershell
.\ci\fnos-k8s\run-remote-bootstrap.ps1
```
如果 NAS 上旧的 Docker API 容器已经占用 `48085`,第一次真正切换时再执行:
```powershell
.\ci\fnos-k8s\run-remote-bootstrap.ps1 -Cutover
```
`-Cutover` 会允许脚本停止旧的 `kt-template-online-api` Docker 容器,把 `48085` 交给 k3d loadbalancer 映射到 K8s `NodePort 30085`。
脚本会在 NAS 上完成:
- 创建 `/vol1/docker/kt-k8s/{registry,kubeconfig,secrets,manifests,backups}`。
- 安装缺失的 `k3d` 和 `kubectl`。
- 创建本地 Registry。
- 创建 `kt-nas` 集群。
- 拉取并导入 `rancher/mirrored-pause:3.6`,避免 K3s 节点因 Docker Hub 超时卡在 `ContainerCreating`。
- 导出 host kubeconfig 和 Jenkins Agent kubeconfig。
- 创建 `kt-prod` namespace。
- 将 `kt-node-agent` 接入 k3d Docker 网络。
- 将 kubeconfig 复制到 Agent 内的 `/home/jenkins/agent/kubeconfig/kt-nas.jenkins.yaml`。
- 如果 Agent 内已有 `/home/jenkins/agent/env/kt-template-online-api/.env.production`,同步创建 `kt-template-online-api-env` Secret。
## Jenkins Agent 镜像
Agent 镜像位于:
```text
ci/jenkins-agent/Dockerfile
```
镜像内置:
- Node.js 22
- pnpm 9
- Docker CLI / Buildx / Compose
- kubectl
- Git / OpenSSH
NAS 上重新构建并重启 Agent:
```bash
docker build -t kt-jenkins-agent:node22 -f ci/jenkins-agent/Dockerfile ci/jenkins-agent
docker rm -f kt-node-agent
```
然后按 `ci/jenkins-agent/README.md` 中的 `docker run` 命令重新启动。Agent 启动后再跑一次:
```powershell
.\ci\fnos-k8s\run-remote-bootstrap.ps1
```
这样脚本会把 kubeconfig 重新复制进 Agent,并把 Agent 接到 `k3d-kt-nas` 网络。
## Jenkins 发布参数
后端 Jenkinsfile 的标准参数:
```text
DEPLOY_TARGET=k8s
BUILD_DOCKER_IMAGE=true
PUSH_DOCKER_IMAGE=true
DOCKER_REGISTRY=k3d-kt-registry.localhost:5000
IMAGE_NAME=kt-template-online-api
CONTAINER_ENV_FILE=/home/jenkins/agent/env/kt-template-online-api/.env.production
KUBE_CONFIG_FILE=/home/jenkins/agent/kubeconfig/kt-nas.jenkins.yaml
K8S_MANIFEST_FILE=k8s/prod/api.yaml
K8S_NAMESPACE=kt-prod
K8S_DEPLOYMENT=kt-template-online-api
K8S_CONTAINER=api
K8S_ENV_SECRET=kt-template-online-api-env
```
发布阶段会做四件事:
1. 构建后端 `dist`。
2. 用仓库根目录 `dockerfile` 构建业务镜像。
3. 推送到 NAS 本地 Registry,同时更新 `latest` 标签。
4. 从 Agent 私有 `.env.production` 重建 K8s Secret,并滚动更新 Deployment 镜像。
## 验证
NAS 上验证集群:
```bash
kubectl --kubeconfig /vol1/docker/kt-k8s/kubeconfig/kt-nas.host.yaml get nodes
kubectl --kubeconfig /vol1/docker/kt-k8s/kubeconfig/kt-nas.host.yaml -n kt-prod get pod,svc
```
Agent 内验证:
```bash
docker exec kt-node-agent sh -lc 'kubectl --kubeconfig /home/jenkins/agent/kubeconfig/kt-nas.jenkins.yaml -n kt-prod get pod,svc'
```
API 验证:
```bash
curl -I http://127.0.0.1:48085
```
如果公网入口仍由腾讯云 WireGuard/Caddy 转发到 NAS `10.66.66.2:48085`,切换到 K8s 后公网侧不需要改端口。
## 回滚
查看发布历史:
```bash
kubectl --kubeconfig /vol1/docker/kt-k8s/kubeconfig/kt-nas.host.yaml -n kt-prod rollout history deployment/kt-template-online-api
```
回滚上一个版本:
```bash
kubectl --kubeconfig /vol1/docker/kt-k8s/kubeconfig/kt-nas.host.yaml -n kt-prod rollout undo deployment/kt-template-online-api
kubectl --kubeconfig /vol1/docker/kt-k8s/kubeconfig/kt-nas.host.yaml -n kt-prod rollout status deployment/kt-template-online-api --timeout=180s
```
查看日志:
```bash
kubectl --kubeconfig /vol1/docker/kt-k8s/kubeconfig/kt-nas.host.yaml -n kt-prod logs -l app=kt-template-online-api --tail=200
```
如果需要临时退回旧 Docker 容器,先删除或停止 k3d loadbalancer 对 `48085` 的占用,再按旧 Jenkins Docker 参数重启 `kt-template-online-api` 容器。
## 参考
- k3d:
- k3d Registry:
- kubectl Linux 安装:
- Kubernetes Deployment: