说明:本文为脱敏版技术方案。项目名、仓库地址、端口、目录、域名、内网地址、主机名和密钥路径均使用通用占位符,落地时请替换为自己的实际环境。
1. 方案摘要
本方案面向小型私有化部署场景,目标是在飞牛 NAS 上把 Docker、Jenkins、前端静态发布和后端 API 容器发布整合成一套可持续迭代的标准流程。
- 飞牛 NAS 作为内网计算与数据承载节点。
- Docker 承载 Jenkins Controller、Jenkins Agent、本地 Registry、Nginx、数据库、对象存储等基础容器。
- 后端 API 迁入 NAS 本机的 k3d/K3s 集群,由 Jenkins 构建镜像并滚动发布。
- 前端 Web 与 Playground 暂时继续走 Nginx 静态目录发布。
- 公网入口由云服务器中转节点承接,家庭 NAS 不直接暴露 80/443。
这是一套“先稳定、再演进”的方案:先把后端 API 标准化接入 K8s,让发布、回滚、密钥注入、运行状态观测具备统一入口;后续再逐步把前端、Ingress、监控和备份纳入同一套平台能力。
2. 建设目标
2.1 业务目标
- 形成后端 API 从代码提交到 NAS K8s 上线的标准流水线。
- 保持现有公网访问方式基本不变,降低切换风险。
- 保留前端现有 Nginx 静态发布模式,避免一次性迁移范围过大。
- 让发布失败可回滚、运行状态可排查、生产环境变量不进入 Git。
- 让未来新增服务可以复用同一套命名、目录、端口、镜像和 Jenkins 流水线规范。
2.2 技术目标
- Jenkins 只负责 CI/CD 编排,不直接保存真实生产配置。
- Jenkins Agent 具备 Node.js、pnpm、Docker CLI、kubectl 等构建发布能力。
- Docker 本地 Registry 固定在 NAS,减少发布时对公网镜像仓库的依赖。
- K8s 运行态使用 Deployment + Service,默认支持滚动更新和
rollout undo。 - 生产环境变量从 Agent 私有文件生成 K8s Secret,做到 Git 与真实配置隔离。
- 所有一次性初始化动作脚本化,减少手工步骤漂移。
3. 总体架构
整体链路可以拆成四层:公网入口层、NAS Docker 控制层、NAS K8s 运行层和应用层。
开发者 / Git push -> Git 仓库 [GIT_REPO] -> Jenkins Controller -> Jenkins Agent -> pnpm install / lint / test / build -> docker build 业务镜像 -> NAS 本地 Registry -> kubectl apply / set image / rollout status -> k3d/K3s 集群 -> 后端 API Pod -> NAS API 端口 前端 Web / Playground -> Jenkins 构建静态产物 -> Nginx 静态目录 -> NAS 前端端口 公网访问 -> 云服务器中转节点 -> VPN 或内网转发链路 -> NAS 服务端口
3.1 分层职责
| 层级 | 承载对象 | 职责 |
|---|---|---|
| 公网入口层 | 云服务器中转节点、反向代理、VPN 隧道 | 公网 TLS、域名入口、转发到 NAS |
| NAS Docker 控制层 | Jenkins、Agent、Registry、Nginx、数据库、对象存储 | 构建、发布、基础服务和静态资源 |
| NAS K8s 运行层 | k3d/K3s | 后端 API 工作负载、滚动更新、回滚 |
| 应用层 | API、Web、Playground | 业务接口、前台展示、可视化编辑器 |
3.2 为什么采用 k3d/K3s
- NAS 已经以 Docker 为主要运行环境,k3d 可以直接复用 Docker,不需要额外虚拟机。
- K3s 轻量,适合家庭 NAS 或小型私有化部署。
- k3d 可以把集群 API、NodePort、Registry 标准化在 Docker 网络内。
- 对现有 Docker 服务影响较小,可以先迁移后端 API,后续再扩展其他服务。
4. 命名与资源规范
公开方案中使用通用命名。实际落地时建议固定命名,避免 Jenkins 参数、K8s manifest、Nginx 配置和运维脚本之间出现漂移。
| 对象 | 示例命名 |
|---|---|
| Jenkins Agent 节点 | app-node-agent |
| Jenkins Agent 镜像 | app-jenkins-agent:node22 |
| k3d 集群 | app-nas |
| K8s namespace | app-prod |
| 本地 Registry | [REGISTRY_HOST]:[REGISTRY_PORT] |
| API 镜像 | [REGISTRY_HOST]:[REGISTRY_PORT]/backend-api:<tag> |
| API Deployment | backend-api |
| API env Secret | backend-api-env |
5. 端口与目录规范
5.1 端口规范
| 服务 | NAS 端口 | 容器/K8s 端口 | 说明 |
|---|---|---|---|
| API | [API_HOST_PORT] | [API_CONTAINER_PORT] | 由 k3d loadbalancer 映射到 NodePort |
| API NodePort | [API_NODE_PORT] | [API_CONTAINER_PORT] | K8s Service NodePort |
| Web | [WEB_HOST_PORT] | [WEB_CONTAINER_PORT] | Nginx 静态服务 |
| Playground | [PLAYGROUND_HOST_PORT] | [PLAYGROUND_CONTAINER_PORT] | Nginx 静态服务 |
5.2 目录规范
| 路径 | 用途 |
|---|---|
[DATA_ROOT]/app-k8s | K8s 标准化根目录 |
[DATA_ROOT]/app-k8s/registry | 本地 Registry 数据 |
[DATA_ROOT]/app-k8s/kubeconfig | host 与 Agent kubeconfig |
[AGENT_WORKDIR]/env/backend-api/.env.production | Agent 内 API 生产环境变量 |
[AGENT_WORKDIR]/kubeconfig/app-nas.jenkins.yaml | Agent 内 kubeconfig |
6. Jenkins 流水线设计
6.1 发布目标
| DEPLOY_TARGET | 行为 |
|---|---|
k8s | 标准模式,构建镜像、推送本地 Registry、滚动更新 K8s |
docker | 兼容旧模式,构建镜像后用 docker run 替换旧容器 |
none | 只做 CI,不构建或发布镜像 |
标准生产发布参数: DEPLOY_TARGET=k8s BUILD_DOCKER_IMAGE=true PUSH_DOCKER_IMAGE=true DOCKER_REGISTRY=[REGISTRY_HOST]:[REGISTRY_PORT] IMAGE_NAME=backend-api CONTAINER_ENV_FILE=[AGENT_WORKDIR]/env/backend-api/.env.production KUBE_CONFIG_FILE=[AGENT_WORKDIR]/kubeconfig/app-nas.jenkins.yaml K8S_MANIFEST_FILE=k8s/prod/api.yaml K8S_NAMESPACE=app-prod K8S_DEPLOYMENT=backend-api K8S_CONTAINER=api K8S_ENV_SECRET=backend-api-env
6.2 流水线阶段
Checkout -> Prepare -> Install -> Lint -> Test -> Build -> Docker Build -> Docker Push -> K8s Deploy -> Archive Artifacts
6.3 镜像标签策略
默认镜像标签建议使用分支名加 Jenkins 构建号,便于排查和回滚:
[REGISTRY_HOST]:[REGISTRY_PORT]/backend-api:main-28 [REGISTRY_HOST]:[REGISTRY_PORT]/backend-api:latest
- 分支名需要经过 Docker tag 兼容化处理。
- PR 构建不发布到 K8s。
- main、master、release/* 默认允许发布。
- latest 只作为 manifest 默认镜像和人工排查辅助,真实发布以构建 tag 为准。
7. K8s 运行态设计
7.1 Deployment
replicas: 1 strategy: RollingUpdate maxSurge: 1 maxUnavailable: 0 revisionHistoryLimit: 5
单机 NAS 场景默认使用单副本。未来如果 API 无状态化、数据库连接和上传存储都稳定后,可以扩展到两个或更多副本。
7.2 Service
type: NodePort port: [API_CONTAINER_PORT] targetPort: [API_CONTAINER_PORT] nodePort: [API_NODE_PORT]
k3d 创建集群时将 NAS 宿主机端口映射到 NodePort。这样外部仍然访问原 API 端口,不需要修改云服务器侧转发规则。
7.3 探针与资源限制
当前可以先使用 TCP 探针,保证容器端口已监听。后续建议新增 /health 之类的 HTTP 健康检查接口,再把探针改为 HTTP GET。
readinessProbe: tcpSocket [API_CONTAINER_PORT] livenessProbe: tcpSocket [API_CONTAINER_PORT] requests: cpu: 100m memory: 256Mi limits: cpu: 1000m memory: 768Mi
8. 配置与密钥设计
仓库只提交 .env.example。真实生产配置放在 Jenkins Agent 私有路径中,并通过流水线生成 K8s Secret。
[AGENT_WORKDIR]/env/backend-api/.env.production kubectl -n app-prod create secret generic backend-api-env \ --from-env-file=[AGENT_WORKDIR]/env/backend-api/.env.production \ --dry-run=client -o yaml | kubectl apply -f -
这种方式可以让环境变量变更不进入 Git,同时 Secret 与 Deployment 更新在同一条流水线里完成。需要注意的是,Kubernetes 原生 Secret 默认只是 base64 编码,不等于加密。单机可信环境短期可接受,长期建议接入 SOPS、SealedSecret 或外部 Secret 管理。
9. 初始化与切换方案
9.1 安全初始化
.\ci\fnos-k8s\run-remote-bootstrap.ps1
- 安装或确认 k3d。
- 安装或确认 kubectl。
- 创建标准数据目录。
- 创建本地 Registry。
- 如果 API 端口已被旧容器占用,则停止在集群创建前。
9.2 正式切换
.\ci\fnos-k8s\run-remote-bootstrap.ps1 -Cutover
- 停止旧后端 API Docker 容器。
- 创建 k3d 集群,并映射 NAS API 端口到 K8s NodePort。
- 创建生产 namespace。
- 复制 kubeconfig 到 Jenkins Agent。
- 从 Agent env 文件创建 K8s Secret。
- 由 Jenkins 或手动 kubectl 部署 API。
9.3 网络问题处理
NAS 拉取 GitHub、Docker Hub、Kubernetes 官方二进制可能较慢。建议把基础二进制和关键镜像提前缓存,业务镜像优先推送到 NAS 本地 Registry。
- k3d 和 kubectl 可以由本机下载后通过 scp 上传到 NAS。
- K3s sandbox 镜像建议在 bootstrap 中主动拉取并导入集群。
- 业务镜像发布时推送本地 Registry,避免每次发布都依赖公网镜像仓库。
10. 日常发布 SOP
- 开发者提交代码到发布分支。
- Jenkins 多分支流水线拉取最新 Jenkinsfile。
- Jenkins 执行 install、lint、test、build。
- Jenkins 构建业务镜像并推送 NAS 本地 Registry。
- Jenkins 从 Agent 私有 env 文件重建 K8s Secret。
- Jenkins 更新 Deployment 镜像并等待 rollout 成功。
- 运维人员验证 API、前端入口和核心业务接口。
常用验证: kubectl --kubeconfig [HOST_KUBECONFIG] -n app-prod get deploy,pod,svc -o wide curl -sS -i http://127.0.0.1:[API_HOST_PORT]/ Agent 内验证: docker exec app-node-agent sh -lc \ 'kubectl --kubeconfig [AGENT_KUBECONFIG] -n app-prod get deploy,pod,svc'
11. 回滚方案
11.1 K8s 标准回滚
kubectl --kubeconfig [HOST_KUBECONFIG] \ -n app-prod rollout history deployment/backend-api kubectl --kubeconfig [HOST_KUBECONFIG] \ -n app-prod rollout undo deployment/backend-api kubectl --kubeconfig [HOST_KUBECONFIG] \ -n app-prod rollout status deployment/backend-api --timeout=180s
11.2 指定镜像回滚
kubectl --kubeconfig [HOST_KUBECONFIG] \ -n app-prod set image deployment/backend-api \ api=[REGISTRY_HOST]:[REGISTRY_PORT]/backend-api:[TAG]
11.3 退回旧 Docker 模式
仅在 K8s 集群不可用且短期无法恢复时使用。退回 Docker 模式需要先释放 NAS API 端口,再用旧 Docker 参数启动后端容器。该方式会绕开 K8s 的 Deployment 回滚能力,只建议作为应急方案。
12. 运维与排障
查看服务状态:
kubectl --kubeconfig [HOST_KUBECONFIG] -n app-prod get all
docker ps --format '{{.Names}} {{.Status}} {{.Ports}}'
查看 API 日志:
kubectl --kubeconfig [HOST_KUBECONFIG] \
-n app-prod logs -l app=backend-api --tail=200
查看事件:
kubectl --kubeconfig [HOST_KUBECONFIG] \
-n app-prod get events --sort-by=.lastTimestamp
| 问题 | 现象 | 处理 |
|---|---|---|
| Jenkins 参数缓存旧值 | Registry 参数为空导致发布失败 | Jenkinsfile 运行时 fallback 到本地 Registry |
| Agent 缺少 kubectl | 部署阶段找不到 kubectl | 重建 Agent 镜像或把 kubectl 注入当前容器 |
| sandbox 镜像拉取失败 | Pod 卡在 ContainerCreating | 提前拉取并导入 K3s sandbox 镜像 |
| Secret 缺失 | Pod 启动失败或 env 不完整 | 确认 Agent env 文件存在后重跑 Jenkins |
| 默认镜像不可拉 | ImagePullBackOff | 确保 latest 或目标 tag 已推送到本地 Registry |
13. 安全边界
- 家庭 NAS 不直接暴露 80/443。
- 公网入口由云服务器中转节点承接,NAS 通过内网隧道或转发链路提供服务。
- Jenkins Agent 通过 Docker socket 控制 NAS Docker,仅放在可信内网和可信 Jenkins 任务中使用。
- 真实生产 env 不进入 Git。
- kubeconfig 只放在 NAS 和 Agent 私有路径。
| 风险 | 说明 | 当前策略 | 后续优化 |
|---|---|---|---|
| Docker socket 挂载 | Agent 拥有 NAS Docker 控制权 | 仅用于可信内网构建发布 | 拆出专用构建节点或 rootless buildkit |
| K8s Secret 非加密 | 原生 Secret 默认不是强加密 | 单机可信环境短期接受 | 引入 SOPS 或 SealedSecret |
| Registry 无鉴权 | 本地 Registry 主要供 NAS 与 k3d 使用 | 限制在本机和 Docker 网络 | 增加 registry auth 或网络隔离 |
| 单机集群 | NAS 故障会影响服务 | 通过备份和冷恢复降低风险 | 建立异地备份或备用节点 |
14. 备份与恢复建议
| 对象 | 路径或来源 |
|---|---|
| Registry 数据 | [DATA_ROOT]/app-k8s/registry |
| kubeconfig | [DATA_ROOT]/app-k8s/kubeconfig |
| 生产 env | [AGENT_WORKDIR]/env |
| Jenkins 配置 | Jenkins Controller volume |
| 数据库数据 | 数据库 volume |
| 对象存储数据 | 对象存储 volume |
- 恢复 Docker、Jenkins、Agent、Registry。
- 恢复标准 K8s 数据目录。
- 执行 bootstrap 脚本重新创建或修复 k3d 集群。
- 用本地 Registry 中的镜像恢复 API Deployment。
- 验证 API、Web、Playground 三类入口。
15. 后续演进路线
15.1 巩固后端 K8s 发布
- 给后端新增 HTTP 健康检查接口。
- 将 K8s 探针从 TCP 改为 HTTP。
- 给 Jenkins 增加发布后 smoke test。
- 定期清理本地 Registry 旧镜像。
- 给 bootstrap 增加更清晰的日志和幂等检查。
15.2 前端、Ingress 与观测
- Web 和 Playground 继续保留 Nginx 静态部署,发布产物纳入版本化 release 目录。
- 评估是否将 Nginx 也迁入 K8s。
- 引入 Traefik 或 Nginx Ingress,统一管理 API、Web、Playground。
- 增加基础监控:Pod 状态、重启次数、磁盘、内存、Registry 空间。
- 用 SOPS 或 SealedSecret 管理 K8s Secret。
16. 验收标准
| 验收项 | 标准 |
|---|---|
| Jenkins 构建 | 发布分支可完成 lint/test/build/docker/k8s deploy |
| K8s 运行 | 后端 API Deployment 为 1/1 |
| 端口访问 | NAS API 端口可访问到后端 API |
| 回滚能力 | rollout history 可看到发布记录,rollout undo 可执行 |
| 配置隔离 | 真实生产 env 不进入 Git |
| Agent 能力 | Agent 内可执行 node、pnpm、docker、kubectl |
| 本地 Registry | 可 push/pull API 镜像 |
| 文档完整性 | SOP、技术方案、bootstrap 脚本均在仓库内 |
17. 核心命令速查
查看集群: kubectl --kubeconfig [HOST_KUBECONFIG] get nodes 查看 API: kubectl --kubeconfig [HOST_KUBECONFIG] -n app-prod get deploy,pod,svc -o wide 查看日志: kubectl --kubeconfig [HOST_KUBECONFIG] -n app-prod logs -l app=backend-api --tail=200 手动部署指定镜像: kubectl --kubeconfig [HOST_KUBECONFIG] -n app-prod \ set image deployment/backend-api \ api=[REGISTRY_HOST]:[REGISTRY_PORT]/backend-api:[TAG] 回滚: kubectl --kubeconfig [HOST_KUBECONFIG] -n app-prod \ rollout undo deployment/backend-api
这套方案适合以 NAS 为核心的小型私有化部署。它不是为了把所有组件一次性“云原生化”,而是先把最容易产生发布风险的后端 API 纳入 K8s 标准发布链路,再逐步扩展到前端、Ingress、监控、备份和密钥治理。这样既保留了 Docker 在 NAS 上的简单直接,也获得了 K8s 在发布、回滚和运行态管理上的稳定收益。