feat(devops): configure RKE2 test deployment
giteabot / giteabot (push) Canceled after 0s
giteabot backport / giteabot (push) Canceled after 0s
release-nightly-snapcraft / build-and-publish (push) Canceled after 0s
release-nightly / nightly-binary (push) Canceled after 0s
release-nightly / nightly-container (push) Canceled after 0s

Assisted-by: Codex:GPT-5
This commit is contained in:
2026-08-17 03:04:24 +08:00
parent c3fdb0ff38
commit 3a3bf9468a
10 changed files with 882 additions and 6 deletions
+3 -1
View File
@@ -1,2 +1,4 @@
# Copy this file to .env and replace the placeholder with the SMTP password. # Copy this file to .env and replace all placeholders.
GITEA__mailer__PASSWD=replace-with-tencent-ses-smtp-password GITEA__mailer__PASSWD=replace-with-tencent-ses-smtp-password
GITA_DB_PASSWORD=replace-with-random-database-password
MARIADB_ROOT_PASSWORD=replace-with-random-root-password
+570
View File
@@ -0,0 +1,570 @@
# Gita Actions 与 RKE2 测试环境 DevOps 方案
> 文档日期:2026-08-17
>
> 目标:代码通过合并请求进入 `master` 分支后,自动完成测试、镜像构建、镜像推送和 RKE2 测试环境发布。
## 1. 结论
推荐采用以下流水线:
```text
功能分支 -> 合并请求检查 -> 合并到 master -> Gita Actions
-> 构建并推送不可变镜像 -> Helm 原子升级 RKE2
-> 健康检查/冒烟测试 -> 成功或自动回滚
```
触发器应使用 `push``master`,同时通过分支保护禁止直接推送 `master`。这样“`master` 收到 push”在管理流程上就等同于“合并请求已合并”。
第一阶段采用 Gita Actions 直接执行 Helm 发布;后续若引入 Argo CD,可保留构建阶段,仅将部署阶段替换为更新环境仓库,由 Argo CD 拉取并同步。
### 1.1 2026-08-16 至 2026-08-17 实施结果
| 项目 | 已实施配置 |
| --- | --- |
| 触发规则 | `.gitea/workflows/deploy-test.yaml` 只在 `master` push(由合并产生)或人工调度时执行 |
| 目标仓库提交 | `AItrackwalker/AItrackwalker``develop` 分支;`d4b27d8` 增加流水线,`a924528` 修正失败镜像保留,`d9b95d7` 改为从 Gita 内部检出,`be11e4e` 启用完整构建缓存清理,`fef3e00` 增加发布后验证失败回滚和回滚完成前镜像保护,`4af2d6d` 精确保留当前及上一部署版本,`64705fb` 在推送后释放 Runner 本地镜像并在部署前清空构建缓存,`a69fc7b` 修复前端在 RKE2 中错误使用 Docker DNS`c64f3cf` 修复滚动发布后旧页面动态模块失效的问题 |
| 分支保护 | `master` 禁止直接 push、禁止 force push、至少 1 人批准;拒绝评审、分支落后或批准后代码变化都会阻止合并 |
| Runner | Gita Runner `v0.6.1`,仓库级注册,名称 `rke2-test-runner`,标签 `trackwalker-build``rke2-test-deploy`Runner 官方镜像固定到 SHA-256 digest,定制任务镜像使用日期版本标签且基础镜像固定 digest |
| Runner 隔离 | Rootless Docker-in-Docker,不挂载宿主机 Docker Socket;经批准启用 `privileged`,限制 4 CPU、6 GiB、并发 1 |
| Registry | 7 个业务镜像推送到 `git.roncarve.com/aitrackwalker/*`,使用 `master-<12位SHA>` 不可变标签;测试节点将该域名解析到 Docker 网关 `172.19.0.1`,避免同机镜像拉取绕行公网 |
| 镜像保留 | 部署前读取当前集群镜像标签;成功后逐个镜像包精确保留新标签和上一部署标签,其余版本删除;镜像推送后移除 Runner 本地标签,部署前及任务结束时清理构建缓存和悬空镜像 |
| Kubernetes 凭据 | ServiceAccount `gita-deployer`,仅授权 `trackwalker` 命名空间;已确认不可读取 Node;同机 Runner 通过 Docker 网关 `172.19.0.1:6443` 连接,并使用 `tls-server-name: 62.234.49.39` 校验证书 |
| 拉取凭据 | `trackwalker/gita-registry` Secret,并挂载到该命名空间默认 ServiceAccount;使用独立 `read:package` 令牌 |
| Gita 凭据 | 专用账号 `trackwalker-ci`Actions 写令牌为 `write:package,read:repository`,与集群只读拉取令牌分离;仓库 Secrets 为 `REGISTRY_USERNAME``REGISTRY_PASSWORD``RKE2_TEST_KUBECONFIG_B64` |
| 验证记录 | `#1` 发现外部 checkout 超时;`#2` 验证内部检出并发现任务镜像缺少 Helm;`#3` 验证定制工具镜像并发现 Registry DNS 指向错误;`#4` 完成 7 镜像构建与推送并发现同机公网 RKE2 API 不可达;`#5` 完成 Helm revision 5 和全部 rollout,发现网关上游端口错误及发布后失败镜像不应提前删除;`#6` 完成首轮 7 镜像冷构建和 Helm revision 6`#7` 完成前端 Kubernetes DNS 修复后的 7 镜像构建、Helm revision 7、全部 rollout、HTTPS 与认证配置接口验证;`#8` 完成旧动态模块恢复修复、Helm revision 8、全部 rollout、缓存策略和模块加载验证,状态为成功 |
### 1.2 最终验收快照
2026-08-17 已在当前测试机完成实际 Runner 构建部署验收:
| 验收项 | 结果 |
| --- | --- |
| Actions | Run `#8`,提交 `c64f3cf8f35b``workflow_dispatch`,状态成功 |
| Helm | `trackwalker` revision 8 为 `deployed`revision 7 为 `superseded` |
| 工作负载 | 8 个 Deployment、5 个 StatefulSet 全部 Ready7 个业务 Deployment 使用 `master-c64f3cf8f35b` |
| HTTPS | `https://aitrackwalker.roncarve.com/` 返回 HTTP 200 |
| 认证接口 | `GET /api/v1/auth/config` 返回 HTTP 200,认证模式为 `LOCAL`,要求登录;前端日志不再出现 `backend could not be resolved` |
| 动态模块 | 已失效的 `CommandCenterView-B7Qoj-nd.js` 返回 HTTP 404 而不是首页 HTML;当前模块返回 HTTP 200;入口页与 Service Worker 禁止缓存,带哈希资源使用 immutable 缓存,前端监听 `vite:preloadError` 并单次自动刷新 |
| 镜像版本 | 7 个业务镜像包均且仅保留 `master-c64f3cf8f35b``master-a69fc7bdbb79` |
| 回滚镜像 | revision 7 使用的 `master-a69fc7bdbb79` 完整保留,可作为上一版本回滚目标 |
| 权限边界 | `gita-deployer` 可以更新 `trackwalker` Deployment,但不能读取 Node |
| Runner 清理 | Build Cache 为 0,本地仅保留固定任务镜像及其基础镜像;未挂载宿主 Docker Socket |
| 磁盘 | `/dev/vda1` 为 80 GiBRun `#8` 构建期间最低观测剩余约 7.4 GiB,镜像与构建缓存清理完成后剩余约 12 GiB、使用率 86% |
| 临时凭据 | `codex-*` 临时访问令牌数量为 0 |
| 最新流水线 | `develop` 已推送 `c64f3cf`;镜像推送后删除 Runner 本地标签,进入部署前执行完整 BuildKit 缓存清理,并使用容器系统 DNS 解析后端服务 |
实现文件:
- Gita 服务与 Runner`contrib/roncarve/compose.production.yaml`
- Caddy Registry 网络别名:`contrib/roncarve/gateway/docker-compose.yml`
- 测试环境 HTTPS 到 Traefik 的网关:`contrib/roncarve/gateway/nginx.conf`
- Runner 参数:`contrib/roncarve/runner/config.yaml`
- RKE2 最小权限:`contrib/roncarve/rke2/trackwalker-ci-rbac.yaml`
- 目标仓库工作流:`.gitea/workflows/deploy-test.yaml`
- 目标仓库测试环境值:`.gitea/deploy/test-values.yaml`
- 目标仓库镜像清理:`.gitea/scripts/cleanup-container-versions.sh`
工作流不依赖 GitHub 上的 `actions/checkout`。源码检出使用 Gita 自动生成的短期任务令牌和内部服务地址,避免外部 GitHub 网络波动阻断发布;令牌不会输出到日志或写入仓库。
### 1.3 前端动态模块发布兼容
前端采用带内容哈希的动态模块。滚动发布后,已打开的旧页面可能继续请求已被新镜像替换的旧文件名。当前实现同时使用以下保护:
- Nginx 对 `/assets/` 仅返回真实静态文件,缺失文件返回 HTTP 404,不回退到 `index.html`
- `index.html`、SPA 导航响应和 `service-worker.js` 使用 `no-cache, no-store, must-revalidate`;带哈希的 `/assets/` 使用一年 `immutable` 缓存。
- 前端监听 Vite 的 `vite:preloadError`,动态模块失效时按会话最多自动刷新一次,避免无限刷新。
- Service Worker 仅缓存成功响应,只有导航请求可使用 HTML 离线回退;JS、CSS 等非导航资源缺失时返回网络错误。
该机制从提交 `c64f3cf` 起生效。发布前已经打开且仍在执行旧 JavaScript 的页面需要首次手动强制刷新一次,后续发布由新代码自动恢复。
## 2. 当前测试环境现状
本节汇总 2026-08-16 初始检查与 2026-08-17 最终验收结果,未在文档中记录任何密码、令牌或私钥。
| 项目 | 当前状态 |
| --- | --- |
| 测试机 | `62.234.49.39` / `VM-0-3-tencentos` |
| 操作系统/架构 | TencentOS Server 4 / x86_64 |
| 节点资源 | 8 CPU、15 GiB 内存、无 Swap |
| 磁盘 | `/dev/vda1` 和 XFS 已在线扩容到 80 GiBRun `#8` 完整构建发布及镜像清理后剩余约 12 GiB、使用率 86% |
| RKE2 | `v1.36.1+rke2r1`,服务为 active |
| 集群拓扑 | 单节点;角色为 control-plane、etcd,节点 Ready |
| 容器运行时 | containerd `2.2.3-k3s1` |
| 网络 | CanalPod CIDR `10.42.0.0/16`Service CIDR `10.43.0.0/16` |
| Ingress | Traefik 3.6.16,默认 IngressClass |
| 测试域名 | `aitrackwalker.roncarve.com`;外部 TLS 由 Caddy 终止,再转发到 Traefik NodePort `30080` |
| 存储 | local-path;所有 PV 为单节点本地 RWO,回收策略为 Delete |
| 应用命名空间 | `trackwalker` |
| 应用工作负载 | 8 个 Deployment、5 个 StatefulSet,检查时全部 Ready |
| 数据服务 | PostgreSQL、Redis、Kafka、MinIO、Prometheus |
| Helm | Release `trackwalker` revision 8 已部署,revision 7 可用于上一版本回滚 |
| 部署脚本 | `/opt/trackwalker/build-context/deploy/scripts/deploy.sh` |
| Helm Chart | `/opt/trackwalker/build-context/deploy/charts/trackwalker` |
| 当前应用版本 | `0.1.0-dev` |
| 当前镜像地址 | 7 个业务 Deployment 已切换到 `git.roncarve.com/aitrackwalker/*:master-c64f3cf8f35b`;第三方 `media-gateway` 仍使用既有外部镜像地址 |
| RKE2 镜像配置 | embedded registry 已开启;没有 `/etc/rancher/rke2/registries.yaml``/etc/hosts``git.roncarve.com` 映射到本机 Caddy 所在 Docker 网关 `172.19.0.1`TLS 仍校验正式域名证书 |
| Kubernetes 拉取凭据 | 已创建 `trackwalker/gita-registry`,并配置为默认 ServiceAccount 的 imagePullSecret |
| etcd 备份 | 本地约每 12 小时生成一次快照,当前可见最近 5 份 |
测试节点同时运行 Gita、MariaDB、Caddy、Web 站点等 Docker 容器。第一阶段按公司确认在该节点构建,通过 4 CPU、6 GiB、并发 1、独立 Rootless Docker 数据目录和构建后清理限制资源占用;后续仍建议将 Runner 迁移到独立构建机。
## 3. 必须先处理的问题
### 3.1 构建 Runner 位置
长期推荐新增一台独立构建机运行 Gita Runner。第一阶段已按确认部署在当前测试机:
- 安装 Docker/BuildKit、Git、kubectl 和 Helm。
- Runner 仅注册到指定组织或仓库,标签使用 `trackwalker-build`
- 限制并发为 1,设置 CPU、内存和磁盘清理策略。
- 构建机只需要访问 Gita、镜像仓库和 RKE2 API `62.234.49.39:6443`
如果第一阶段只能使用当前测试机,应至少:
- 将 Runner 容器限制为不超过 4 CPU、6 GiB 内存。
- Actions 并发限制为 1。
- Runner Docker 数据保存在 `/opt/gita/runner/docker`,与 RKE2 containerd 数据隔离。
- 定期清理构建缓存,但不得清理 RKE2/containerd 正在使用的镜像。
- Rootless Docker-in-Docker 需要 `privileged: true`;该权限已经明确批准,且未挂载 `/var/run/docker.sock`
### 3.2 镜像仓库
将应用镜像从占位地址 `registry.example.com` 迁移到 Gita OCI 镜像仓库,例如:
```text
git.roncarve.com/aitrackwalker/frontend
git.roncarve.com/aitrackwalker/backend
git.roncarve.com/aitrackwalker/uav-access-service
git.roncarve.com/aitrackwalker/vision-inference
git.roncarve.com/aitrackwalker/pointcloud-analysis
git.roncarve.com/aitrackwalker/artifact-installer
git.roncarve.com/aitrackwalker/edge-agent
```
当前 Registry 与 RKE2 位于同一测试节点。节点 `/etc/hosts` 必须包含:
```text
172.19.0.1 git.roncarve.com
```
`172.19.0.1``gateway_gateway` Docker 网络的网关,访问 443 端口会进入 Caddy,并继续使用 `git.roncarve.com` 的正式 TLS 证书。若以后重建 Docker 网络并改变网段,应同步更新该映射;可用 `docker network inspect gateway_gateway``curl https://git.roncarve.com/v2/` 验证,未登录时返回 HTTP 401 即表示 Registry 路由正常。
每次构建使用提交 SHA 作为不可变标签,例如:
```text
git.roncarve.com/aitrackwalker/backend:master-a1b2c3d4e5f6
```
不要仅依赖 `latest``master` 可变标签。可额外推送 `test-latest` 供人工查看,但 Helm 发布必须使用 SHA 标签或镜像 digest。
为 RKE2 创建专用只读镜像账号,并在 `trackwalker` 命名空间创建 `docker-registry` 类型 Secret。Helm Chart 应显式引用该 imagePullSecret;不要把镜像密码写入 values 文件或工作流。
### 3.3 分支保护
在目标仓库对 `master` 设置:
- 禁止普通成员直接 push。
- 只允许通过合并请求进入 `master`
- 至少 1 人批准。
- 必须通过单元测试、静态检查和镜像构建预检。
- 合并前必须解决冲突和未处理讨论。
- 禁止 force push 和删除分支。
工作流本身无法可靠区分“合并产生的 push”和“直接 push”。强制合并请求流程应由分支保护保证。
## 4. Gita 与 Runner 配置
### 4.1 启用 Actions
在 Gita Docker 环境变量中加入:
```yaml
GITEA__actions__ENABLED: "true"
```
已重新创建 Gita 容器并完成 Runner 仓库级注册。注册令牌只保存在服务器只读挂载文件中,不写入业务仓库。
### 4.2 Runner 标签与职责
当前一台 Runner 同时声明两个标签:
| Runner 标签 | 职责 | 权限 |
| --- | --- | --- |
| `trackwalker-build` | 测试、构建、推送镜像 | Docker 和镜像仓库写权限;无 Kubernetes 管理权限 |
| `rke2-test-deploy` | Helm 发布、验证和回滚 | 仅允许操作 `trackwalker` 命名空间 |
当前工作流使用 `trackwalker-build` 运行单一串行任务,部署阶段使用命名空间最小权限 kubeconfig;`rke2-test-deploy` 标签为后续拆分构建和部署任务预留。
### 4.3 Gita Secrets
已在 `AItrackwalker/AItrackwalker` 仓库级配置以下 Secrets
| Secret | 用途 |
| --- | --- |
| `REGISTRY_USERNAME` | 镜像仓库机器人账号 |
| `REGISTRY_PASSWORD` | 镜像仓库机器人密码或令牌 |
| `RKE2_TEST_KUBECONFIG_B64` | 仅限 `trackwalker` 命名空间的 kubeconfigBase64 编码 |
未保存 root SSH 私钥、RKE2 管理员 kubeconfig `/etc/rancher/rke2/rke2.yaml` 或平台管理员密码。
## 5. RKE2 最小权限
创建 CI 专用 ServiceAccount,并只授予测试命名空间发布所需权限。以下为基础示例,后续应根据 Helm 实际管理的资源进一步收紧:
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: gita-deployer
namespace: trackwalker
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: gita-deployer
namespace: trackwalker
rules:
- apiGroups: [""]
resources: ["configmaps", "secrets", "services", "serviceaccounts", "pods", "pods/log", "persistentvolumeclaims"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "replicasets"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: ["batch"]
resources: ["jobs"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: gita-deployer
namespace: trackwalker
subjects:
- kind: ServiceAccount
name: gita-deployer
namespace: trackwalker
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: gita-deployer
```
Helm 需要管理 Release Secret,因此 Role 中包含 Secret 写权限。应用业务 Secret 应继续使用 SOPS 或其他密钥管理方式,不由普通构建步骤读取。
## 6. 流水线设计
### 6.1 合并请求阶段
对所有指向 `master` 的合并请求执行:
1. 格式化、Lint 和单元测试。
2. 前后端构建检查。
3. Helm `lint``template`
4. Kubernetes 清单校验。
5. 镜像构建预检,但不推送测试环境标签、不部署。
6. 可选生成 SBOM 和漏洞扫描报告。
### 6.2 合并后阶段
`master` 收到 push 后执行:
1. 再次检出合并后的完整代码。
2. 执行必要的快速回归测试。
3. 构建受影响的应用镜像。
4. 推送 `master-<12位SHA>` 标签、记录 digest,并立即删除 Runner 本地标签。
5. 所有镜像推送完成后清空 BuildKit 构建缓存,避免部署阶段磁盘空间不足。
6. 生成一次性 CI values 文件,不修改服务器上的源码目录。
7. 执行 `helm upgrade --install --atomic --wait --timeout 20m`
8. 检查 Deployment/StatefulSet rollout。
9.`https://aitrackwalker.roncarve.com` 执行冒烟测试。
10. rollout 或冒烟测试失败时回滚到升级前 Helm revision;只有回滚成功后才删除失败标签。
11. 成功后精确保留当前和上一部署标签,并保存构建日志、镜像 digest、Helm revision 和发布结果。
`--atomic` 会在 Helm 升级失败时回滚本次 Release,但数据库结构变更仍需遵守向前/向后兼容原则,不能依赖 Helm 自动还原数据。
## 7. 工作流示例
目标仓库已创建 `.gitea/workflows/deploy-test.yaml`。实际版本按 Runner 并发 1 串行构建 7 个镜像,使用 `.gitea/deploy/test-values.yaml` 覆盖业务镜像仓库,并在部署成功后调用 `.gitea/scripts/cleanup-container-versions.sh` 执行精确保留策略。
以下内容用于说明流水线结构;运维时应以目标仓库中的实际文件为准:
```yaml
name: deploy-test-on-master
on:
push:
branches:
- master
workflow_dispatch:
concurrency:
group: trackwalker-test
cancel-in-progress: false
env:
REGISTRY: git.roncarve.com
IMAGE_PREFIX: git.roncarve.com/aitrackwalker
NAMESPACE: trackwalker
RELEASE_NAME: trackwalker
jobs:
test:
runs-on: trackwalker-build
steps:
- name: Checkout merged source from Gita
env:
GITA_JOB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git init .
git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
git -c http.extraHeader="Authorization: token ${GITA_JOB_TOKEN}" \
fetch --depth=1 origin "${GITHUB_SHA}"
git checkout --detach FETCH_HEAD
- name: Run project checks
run: ./scripts/ci-test.sh
- name: Validate Helm chart
run: |
helm lint deploy/charts/trackwalker
helm template trackwalker deploy/charts/trackwalker \
--namespace trackwalker \
--values deploy/charts/trackwalker/values.yaml \
--values deploy/charts/trackwalker/values-single.yaml \
>/dev/null
build:
runs-on: trackwalker-build
needs: test
strategy:
fail-fast: true
matrix:
include:
- image: frontend
context: frontend
- image: backend
context: platform/backend
- image: uav-access-service
context: uav-access-service
- image: vision-inference
context: ai-services/vision-inference
- image: pointcloud-analysis
context: ai-services/pointcloud-analysis
- image: artifact-installer
context: ai-services/artifact-installer
- image: edge-agent
context: edge-agent
steps:
- name: Checkout merged source from Gita
env:
GITA_JOB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git init .
git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
git -c http.extraHeader="Authorization: token ${GITA_JOB_TOKEN}" \
fetch --depth=1 origin "${GITHUB_SHA}"
git checkout --detach FETCH_HEAD
- name: Login to registry
run: |
printf '%s' '${{ secrets.REGISTRY_PASSWORD }}' | \
docker login "${REGISTRY}" \
--username '${{ secrets.REGISTRY_USERNAME }}' \
--password-stdin
- name: Build and push image
shell: bash
env:
COMMIT_SHA: ${{ gitea.sha }}
run: |
set -Eeuo pipefail
short_sha="${COMMIT_SHA:0:12}"
image="${IMAGE_PREFIX}/${{ matrix.image }}:master-${short_sha}"
docker build --pull --tag "${image}" "${{ matrix.context }}"
docker push "${image}"
deploy:
runs-on: rke2-test-deploy
needs: build
environment: test
steps:
- name: Checkout merged source from Gita
env:
GITA_JOB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git init .
git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
git -c http.extraHeader="Authorization: token ${GITA_JOB_TOKEN}" \
fetch --depth=1 origin "${GITHUB_SHA}"
git checkout --detach FETCH_HEAD
- name: Configure Kubernetes access
shell: bash
run: |
set -Eeuo pipefail
install -d -m 0700 "${RUNNER_TEMP}/kube"
printf '%s' '${{ secrets.RKE2_TEST_KUBECONFIG_B64 }}' | \
base64 -d >"${RUNNER_TEMP}/kube/config"
chmod 0600 "${RUNNER_TEMP}/kube/config"
echo "KUBECONFIG=${RUNNER_TEMP}/kube/config" >>"${GITHUB_ENV}"
- name: Deploy with Helm
shell: bash
env:
COMMIT_SHA: ${{ gitea.sha }}
run: |
set -Eeuo pipefail
short_sha="${COMMIT_SHA:0:12}"
tag="master-${short_sha}"
helm upgrade --install "${RELEASE_NAME}" deploy/charts/trackwalker \
--namespace "${NAMESPACE}" \
--values deploy/charts/trackwalker/values.yaml \
--values deploy/charts/trackwalker/values-single.yaml \
--set global.imageRegistry="${REGISTRY}" \
--set images.frontend.tag="${tag}" \
--set images.backend.tag="${tag}" \
--set images.uavAccess.tag="${tag}" \
--set images.visionCpu.tag="${tag}" \
--set images.pointcloudCpu.tag="${tag}" \
--set images.artifactInstaller.tag="${tag}" \
--set images.edgeAgent.tag="${tag}" \
--atomic \
--wait \
--timeout 20m
- name: Verify rollout
shell: bash
run: |
set -Eeuo pipefail
for deployment in \
frontend backend uav-access-service vision-inference \
pointcloud-analysis artifact-installer edge-agent; do
kubectl --namespace "${NAMESPACE}" rollout status \
"deployment/${deployment}" --timeout=180s
done
- name: Smoke test
run: |
curl --fail --show-error --silent \
--retry 5 --retry-delay 5 \
https://aitrackwalker.roncarve.com/ >/dev/null
```
示例中的 `./scripts/ci-test.sh` 需要由 TrackWalker 项目统一封装各语言测试命令。实际镜像构建路径应以目标仓库结构为准。
## 8. 复用现有部署资产
现有 `deploy.sh` 已包含以下能力:
- 部署锁,避免并行发布。
- 配置和版本校验。
- SOPS 解密入口。
- Helm `--atomic --wait --timeout 20m`
- 发布后验证。
- `--resume-from application` 从应用阶段继续。
第一阶段可以在 CI 中复用该脚本:
```bash
deploy/scripts/deploy.sh \
--profile single \
--inventory deploy/inventories/single/hosts.yml \
--config deploy/config/site.yml \
--secrets "${RUNNER_TEMP}/secrets.sops.yaml" \
--bundle-root . \
--allow-development-bundle \
--resume-from application
```
但长期建议增加专门的 `scripts/ci-deploy-test.sh`,只执行应用升级和验证,避免每次提交都进入操作系统、RKE2 安装和离线包校验流程。
## 9. 回滚策略
正常回滚:
```bash
helm --namespace trackwalker history trackwalker
helm --namespace trackwalker rollback trackwalker <revision> --wait --timeout 20m
```
紧急镜像回滚:
```bash
kubectl --namespace trackwalker rollout undo deployment/backend
kubectl --namespace trackwalker rollout status deployment/backend --timeout=180s
```
要求:
- 每个业务镜像仅保留最近 2 个成功构建版本:正在使用版本和上一次使用版本。
- 清理操作只在 Helm 部署、rollout 和冒烟测试全部成功后执行;发布后验证失败时先回滚,只有回滚成功才删除失败标签,回滚失败则保留镜像供人工恢复。
- 每次发布记录 Git SHA、镜像 digest、Helm revision、执行人和时间。
- 数据库迁移采用 expand/contract,两次发布完成破坏性变更。
- 发布前检查 etcd 快照状态;业务数据还需 PostgreSQL、MinIO 等独立备份,不能只依赖 etcd。
## 10. 当前环境风险和整改优先级
### P0:自动部署前完成
1. 已完成系统盘分区和 XFS 在线扩容,容量为 80 GiB。
2. 已完成业务镜像仓库覆盖;第三方基础组件仍按原配置使用 `registry.example.com`
3. 已创建 Gita 镜像仓库机器人账号和 RKE2 imagePullSecret。
4. 已启用 Gita Actions 并注册仓库级受控 Runner。
5. 已开启 `master` 分支保护,禁止直接 push 和 force push。
6. 已创建命名空间级 RBAC,并验证凭据不能读取 Node。
7. 待独立处理 `/opt/trackwalker/deployment-secrets.yaml` 的明文审计和 SOPS 迁移;本次流水线未复制该文件,也未扩大 Secret 读取范围。
### P1:首轮流水线完成后
1. 已由 Caddy 为 `aitrackwalker.roncarve.com` 提供 TLS;后续增加证书到期监控。
2. 增加镜像 SBOM、漏洞扫描和 digest 锁定。
3. 将 Helm、kubectl 和 Runner 镜像版本固定,禁止使用 `latest`
4. Actions 日志和构建缓存策略已启用;继续观察磁盘水位并设置容量告警。
5. 对 PostgreSQL、MinIO 和 PVC 建立独立备份与恢复演练。
6. 为 npm、Maven 和 PyPI 建立依赖代理缓存;冷构建时 `pointcloud-analysis` 需要下载约 448 MiB 的 Open3D wheel,是当前最明显的外部网络耗时点。
### P2:后续演进
1. 增加 RKE2 worker 节点,将业务工作负载迁出 control-plane。
2. 将 Gita、构建 Runner 与 RKE2 节点分离。
3. 引入 Argo CD,改为 GitOps 部署。
4. 引入 Harbor 作为统一镜像仓库,并配置签名、扫描和保留策略。
5. 引入 Prometheus Alertmanager、Loki/Grafana 和发布事件关联。
## 11. 验收标准
- 合并请求未合并时不会部署测试环境。
- 合并进入 `master` 后 1 分钟内创建流水线。
- 每次部署使用唯一 SHA 标签或 digest。
- 任一步测试或镜像构建失败时不执行部署。
- Helm 升级失败时 Release 自动回滚。
- 成功后所有 Deployment 和 StatefulSet Ready,冒烟测试通过。
- Runner 日志中不出现密码、Token、kubeconfig 内容。
- `master` 不能被普通成员直接 push。
- 可以根据 Git SHA 查到对应镜像和 Helm revision。
- 可以在 10 分钟内回滚到上一个可用版本。
## 12. 推荐实施顺序
1. 已扩容测试机磁盘。
2. 已启用 Gita Actions。
3. 已部署并注册构建 Runner。
4. 已配置 Gita OCI 镜像仓库和机器人账号。
5. 已配置 RKE2 imagePullSecret 与 CI RBAC。
6. 已修改 Helm Chart,支持业务镜像单独指定私有仓库。
7. 已增加 `master` 合并后构建部署工作流并提交到 `develop`
8. 待项目团队补充合并请求阶段的语言级测试与 Lint 工作流。
9. 已完成首次完整冷构建部署验证;待安排维护窗口执行一次失败回滚和一次人工回滚演练。
10. 稳定运行后再评估 Argo CD 和 Harbor。
## 13. 参考资料
- [Gitea Actions](https://docs.gitea.com/usage/actions/overview)
- [Gitea Runner](https://docs.gitea.com/usage/actions/act-runner)
- [RKE2 私有镜像仓库配置](https://docs.rke2.io/install/private_registry)
- [Kubernetes RBAC](https://kubernetes.io/docs/reference/access-authn-authz/rbac/)
- [Helm Upgrade](https://helm.sh/docs/helm/helm_upgrade/)
+10 -2
View File
@@ -1,6 +1,6 @@
# Gita 本地 Docker # Gita 本地 Docker
复制 `.env.example``.env`,并填写腾讯云邮件推送 SMTP 密码: 复制 `.env.example``.env`。本地模式填写腾讯云邮件推送 SMTP 密码;生产模式还需填写 Gita 数据库密码和 MariaDB root 密码:
```powershell ```powershell
Copy-Item contrib/roncarve/.env.example contrib/roncarve/.env Copy-Item contrib/roncarve/.env.example contrib/roncarve/.env
@@ -24,4 +24,12 @@ docker compose -f contrib/roncarve/compose.local.yaml up -d --build
docker compose -f contrib/roncarve/compose.local.yaml down docker compose -f contrib/roncarve/compose.local.yaml down
``` ```
生产环境使用 `compose.production.yaml`,通过外部 `gateway_gateway` 网络接入现有 Caddy,数据目录为 `/opt/gita/data` 生产环境使用 `compose.production.yaml`,通过外部 `gateway_gateway` 网络接入现有 Caddy。Gita 数据目录为 `/opt/gita/data`MariaDB 数据目录为 `/opt/gita/mariadb`Runner 数据目录为 `/opt/gita/runner`
`gateway/docker-compose.yml` 记录 Caddy/Nginx 网关容器结构,`gateway/nginx.conf` 将测试环境 HTTPS 请求转发到 RKE2 Traefik NodePort `30080`。同机 RKE2 拉取 Gita Registry 时,测试节点 `/etc/hosts` 使用 `172.19.0.1 git.roncarve.com`,避免镜像流量绕行公网。
Gita Actions、镜像保留策略和 RKE2 测试环境发布配置见 [DEVOPS-RKE2-TEST.md](DEVOPS-RKE2-TEST.md)。
Runner 任务镜像由 `runner/job-image.Dockerfile` 构建,基础镜像固定 digest,只增加 Helm 和 kubectl 两个部署工具。构建上下文中的二进制文件应分别来自固定版本的 Helm 镜像和当前 RKE2 安装目录。
2026-08-17 已在测试机完成 7 镜像构建和 Helm revision 8 发布,全部工作负载 Ready,HTTPS 冒烟测试、认证配置接口和动态模块加载均验证通过。旧动态模块现在返回 404 而不是首页 HTML,入口页禁止缓存,当前前端可在后续发布出现过期模块时单次自动刷新。镜像仓库按服务仅保留当前与上一构建两个标签;Runner 在每个镜像推送后释放本地标签,并在部署前清空 BuildKit 缓存。
+60 -3
View File
@@ -1,4 +1,29 @@
services: services:
mariadb:
image: mariadb:11.4
container_name: gita-mariadb
hostname: gita-mariadb
restart: unless-stopped
env_file:
- .env
environment:
MARIADB_DATABASE: gita
MARIADB_USER: gita
MARIADB_PASSWORD: ${GITA_DB_PASSWORD}
MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD}
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_bin
volumes:
- /opt/gita/mariadb:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 12
networks:
gateway:
aliases:
- mariadb
gita: gita:
build: build:
context: ../.. context: ../..
@@ -9,10 +34,13 @@ services:
NPM_REGISTRY: https://registry.npmmirror.com NPM_REGISTRY: https://registry.npmmirror.com
image: roncarve/gita:1.27.2-production image: roncarve/gita:1.27.2-production
container_name: gitea container_name: gitea
hostname: git.roncarve.com hostname: gitea
restart: unless-stopped restart: unless-stopped
env_file: env_file:
- .env - .env
depends_on:
mariadb:
condition: service_healthy
ports: ports:
- "2222:22" - "2222:22"
environment: environment:
@@ -21,8 +49,13 @@ services:
GITEA_WORK_DIR: /data/gita GITEA_WORK_DIR: /data/gita
GITEA_CUSTOM: /data/gita GITEA_CUSTOM: /data/gita
GITEA____APP_NAME: Gita 代码协作平台 GITEA____APP_NAME: Gita 代码协作平台
GITEA__database__DB_TYPE: sqlite3 GITEA__database__DB_TYPE: mysql
GITEA__database__PATH: /data/gita/gita.db GITEA__database__HOST: mariadb:3306
GITEA__database__NAME: gita
GITEA__database__USER: gita
GITEA__database__PASSWD: ${GITA_DB_PASSWORD}
GITEA__database__CHARSET: utf8mb4
GITEA__actions__ENABLED: "true"
GITEA__server__PROTOCOL: http GITEA__server__PROTOCOL: http
GITEA__server__HTTP_ADDR: 0.0.0.0 GITEA__server__HTTP_ADDR: 0.0.0.0
GITEA__server__HTTP_PORT: "3000" GITEA__server__HTTP_PORT: "3000"
@@ -56,6 +89,30 @@ services:
aliases: aliases:
- gitea - gitea
runner:
image: docker.io/gitea/act_runner@sha256:6b8f7c4297c0a5c4c181e4737665d4af69288cdc380e2887105a05a2b78930df
container_name: gita-runner
hostname: gita-rke2-test-runner
restart: unless-stopped
privileged: true
cpus: 4
mem_limit: 6g
depends_on:
- gita
environment:
CONFIG_FILE: /config.yaml
GITEA_INSTANCE_URL: http://gitea:3000/
GITEA_RUNNER_REGISTRATION_TOKEN_FILE: /run/secrets/runner-registration-token
GITEA_RUNNER_NAME: rke2-test-runner
DOCKER_HOST: unix:///var/run/user/1000/docker.sock
volumes:
- /opt/gita/runner/config.yaml:/config.yaml:ro
- /opt/gita/runner/registration-token:/run/secrets/runner-registration-token:ro
- /opt/gita/runner/data:/data
- /opt/gita/runner/docker:/home/rootless/.local/share/docker
networks:
- gateway
networks: networks:
gateway: gateway:
external: true external: true
+59
View File
@@ -0,0 +1,59 @@
{
email admin@roncarve.com
}
rancher.aitrackwalker.roncarve.com {
encode zstd gzip
reverse_proxy https://host.docker.internal:30443 {
transport http {
tls_insecure_skip_verify
}
}
}
aitrackwalker.roncarve.com {
encode zstd gzip
reverse_proxy nginx:8080
}
http://62.234.49.39 {
encode zstd gzip
reverse_proxy nginx:8080 {
header_up Host aitrackwalker.roncarve.com
}
}
http://62.234.49.39:8080 {
encode zstd gzip
reverse_proxy nginx:8080 {
header_up Host aitrackwalker.roncarve.com
}
}
www.roncarve.com {
encode zstd gzip
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
}
reverse_proxy roncarve-web:80
}
roncarve.com {
encode zstd gzip
handle /WW_verify_kMPlH8gCU5u7a4LQ.txt {
root * /srv/wecom-verify
file_server
}
handle {
redir https://www.roncarve.com{uri} permanent
}
}
git.roncarve.com {
encode zstd gzip
reverse_proxy gitea:3000
}
@@ -0,0 +1,48 @@
services:
caddy:
image: caddy:2.10-alpine
container_name: trackwalker-caddy
restart: unless-stopped
ports:
- "80:80"
- "8080:8080"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./wecom-verify:/srv/wecom-verify:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- nginx
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
gateway:
aliases:
- git.roncarve.com
nginx:
image: nginx:1.27-alpine
container_name: trackwalker-nginx
restart: unless-stopped
expose:
- "8080"
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
networks:
gateway:
aliases:
- nginx
app:
volumes:
caddy_data:
caddy_config:
networks:
gateway:
app:
name: trackwalker-app
+39
View File
@@ -0,0 +1,39 @@
events {}
http {
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream trackwalker_app {
server host.docker.internal:30080;
}
server {
listen 8080;
server_name _;
client_max_body_size 0;
location = /gateway-health {
access_log off;
default_type text/plain;
return 200 "gateway ok\n";
}
location / {
proxy_pass http://trackwalker_app;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_request_buffering off;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
}
@@ -0,0 +1,53 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: gita-deployer
namespace: trackwalker
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: gita-deployer
namespace: trackwalker
rules:
- apiGroups: [""]
resources: ["configmaps", "secrets", "services", "serviceaccounts"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "replicasets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["batch"]
resources: ["jobs"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: gita-deployer
namespace: trackwalker
subjects:
- kind: ServiceAccount
name: gita-deployer
namespace: trackwalker
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: gita-deployer
---
apiVersion: v1
kind: Secret
metadata:
name: gita-deployer-token
namespace: trackwalker
annotations:
kubernetes.io/service-account.name: gita-deployer
type: kubernetes.io/service-account-token
+36
View File
@@ -0,0 +1,36 @@
log:
level: info
runner:
file: /data/.runner
capacity: 1
timeout: 2h
shutdown_timeout: 5m
insecure: false
fetch_timeout: 5s
fetch_interval: 2s
fetch_interval_max: 5s
labels:
- "trackwalker-build:docker://roncarve/trackwalker-runner-tools:2026.08.17"
- "rke2-test-deploy:docker://roncarve/trackwalker-runner-tools:2026.08.17"
cache:
enabled: false
container:
network: bridge
privileged: false
options: "--cpus=3 --memory=4g --pids-limit=1024"
valid_volumes: []
docker_host: ""
force_pull: false
force_rebuild: false
require_docker: true
docker_timeout: 30s
bind_workdir: false
host:
workdir_parent: /data/host-workspace
metrics:
enabled: false
@@ -0,0 +1,4 @@
FROM docker.gitea.com/runner-images:ubuntu-24.04@sha256:e77e2b1ebba51adb1c59d8eb185bc54e397b7e22442756aa7ea0e7b841fd2906
COPY --chmod=0755 helm /usr/local/bin/helm
COPY --chmod=0755 kubectl /usr/local/bin/kubectl