Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d6b6a0b59 | ||
|
|
35b6a37dad | ||
|
|
99c55f1145 | ||
|
|
abe697317c | ||
|
|
96d1de4a95 |
@@ -0,0 +1,11 @@
|
|||||||
|
.git
|
||||||
|
.gitea
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
.DS_Store
|
||||||
|
*.log
|
||||||
|
tests
|
||||||
|
docs
|
||||||
|
web/node_modules
|
||||||
|
web/dist
|
||||||
|
coverage
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
name: Deploy im-workbench (k3s test cluster)
|
name: Deploy olons-server (k3s test cluster)
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -25,13 +25,13 @@ jobs:
|
|||||||
|
|
||||||
- name: 构建并推送镜像
|
- name: 构建并推送镜像
|
||||||
run: |
|
run: |
|
||||||
docker build -t git.118im.com/im_team/im-workbench:${{ steps.vars.outputs.tag }} .
|
docker build -t git.118im.com/im_team/olons-server:${{ steps.vars.outputs.tag }} .
|
||||||
docker push git.118im.com/im_team/im-workbench:${{ steps.vars.outputs.tag }}
|
docker push git.118im.com/im_team/olons-server:${{ steps.vars.outputs.tag }}
|
||||||
|
|
||||||
- name: 部署到k3s测试集群
|
- name: 部署到k3s测试集群
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ~/.kube
|
mkdir -p ~/.kube
|
||||||
echo "${{ secrets.KUBECONFIG }}" > ~/.kube/config
|
echo "${{ secrets.KUBECONFIG }}" > ~/.kube/config
|
||||||
chmod 600 ~/.kube/config
|
chmod 600 ~/.kube/config
|
||||||
kubectl rollout restart deployment/im-workbench -n workbench-system
|
kubectl rollout restart deployment/olons-server -n olons-server-system
|
||||||
kubectl rollout status deployment/im-workbench -n workbench-system --timeout=120s
|
kubectl rollout status deployment/olons-server -n olons-server-system --timeout=120s
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ RUN corepack enable
|
|||||||
COPY web/package.json web/pnpm-lock.yaml web/pnpm-workspace.yaml ./
|
COPY web/package.json web/pnpm-lock.yaml web/pnpm-workspace.yaml ./
|
||||||
RUN pnpm install --frozen-lockfile
|
RUN pnpm install --frozen-lockfile
|
||||||
COPY web/ ./
|
COPY web/ ./
|
||||||
|
ENV VITE_PUBLIC_PATH=/admin/
|
||||||
RUN pnpm run build
|
RUN pnpm run build
|
||||||
|
|
||||||
# ---- Stage 2: build the Go backend ----
|
# ---- Stage 2: build the Go backend ----
|
||||||
@@ -21,6 +22,7 @@ RUN CGO_ENABLED=0 GOOS=linux go build -o /out/im_workbench ./cmd/workbench
|
|||||||
FROM alpine:3.22
|
FROM alpine:3.22
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
RUN apk add --no-cache tzdata
|
||||||
ENV WORKBENCH_CONFIG=/app/configs/workbench.test.json
|
ENV WORKBENCH_CONFIG=/app/configs/workbench.test.json
|
||||||
COPY --from=builder /out/im_workbench /app/im_workbench
|
COPY --from=builder /out/im_workbench /app/im_workbench
|
||||||
COPY configs /app/configs
|
COPY configs /app/configs
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
# Olons Server 测试集群部署 Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** 将 `olons_server` 的 `dev` 分支以独立前后端一体服务部署到现有 k3s 测试集群,并通过 `https://olns-web.118im.com` 验收。
|
||||||
|
|
||||||
|
**Architecture:** 保留仓库现有 Vue 构建产物嵌入 Go 服务的单镜像结构。新增独立 GitOps 应用 `olons-server-system/olons-server`,复用现有 PXC、Redis、Traefik、证书和镜像仓库,通过 Secret 注入运行时配置,不改动现有 `im-workbench`。
|
||||||
|
|
||||||
|
**Tech Stack:** Go 1.26、Vue 3/Vite、Docker、Kubernetes/Kustomize、Argo CD、Traefik、PXC、Redis、Python unittest/PyYAML。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: 固定应用仓库部署契约
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `tests/test_deployment_contract.py`
|
||||||
|
- Create: `.dockerignore`
|
||||||
|
- Modify: `.gitea/workflows/deploy.yml`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the failing test**
|
||||||
|
|
||||||
|
新增测试,断言工作流名称、镜像名、Namespace、Deployment、Docker 前后端一体构建以及 `.dockerignore` 的必要条目。
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run test to verify it fails**
|
||||||
|
|
||||||
|
Run: `python3 -m unittest tests/test_deployment_contract.py -v`
|
||||||
|
|
||||||
|
Expected: FAIL,旧工作流仍引用 `im-workbench` 和 `workbench-system`,且 `.dockerignore` 不存在。
|
||||||
|
|
||||||
|
- [ ] **Step 3: Write minimal implementation**
|
||||||
|
|
||||||
|
将工作流镜像改为 `git.118im.com/im_team/olons-server:<tag>`,滚动目标改为 `olons-server-system/olons-server`;新增仅排除本地和构建垃圾的 `.dockerignore`。
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run test to verify it passes**
|
||||||
|
|
||||||
|
Run: `python3 -m unittest tests/test_deployment_contract.py -v`
|
||||||
|
|
||||||
|
Expected: PASS。
|
||||||
|
|
||||||
|
- [ ] **Step 5: Verify application builds**
|
||||||
|
|
||||||
|
Run: `docker run --rm -v "$PWD":/src -w /src golang:1.26 go test ./...`
|
||||||
|
|
||||||
|
Run: `docker build --platform linux/amd64 -t local/olons-server:test .`
|
||||||
|
|
||||||
|
Expected: 两条命令均退出 0。
|
||||||
|
|
||||||
|
### Task 2: 新增独立 GitOps 应用与公网路由
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `/Users/aa/workspace/imcn/im-deploy-config/tests/test_olons_server.py`
|
||||||
|
- Create: `/Users/aa/workspace/imcn/im-deploy-config/apps/olons-server/base/deployment.yaml`
|
||||||
|
- Create: `/Users/aa/workspace/imcn/im-deploy-config/apps/olons-server/base/service.yaml`
|
||||||
|
- Create: `/Users/aa/workspace/imcn/im-deploy-config/apps/olons-server/base/kustomization.yaml`
|
||||||
|
- Create: `/Users/aa/workspace/imcn/im-deploy-config/apps/olons-server/overlays/test/kustomization.yaml`
|
||||||
|
- Modify: `/Users/aa/workspace/imcn/im-deploy-config/platform/ingress/routes.yaml`
|
||||||
|
- Modify: `/Users/aa/workspace/imcn/im-deploy-config/platform/ingress/certificate.yaml`
|
||||||
|
- Modify: `/Users/aa/workspace/imcn/im-deploy-config/platform/ci/rbac.yaml`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the failing test**
|
||||||
|
|
||||||
|
新增测试,断言一副本、小资源、6061 探针、Secret 注入、无 PVC、测试镜像、专用 Host 路由、根路径重定向、证书域名及 CI RoleBinding。
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run test to verify it fails**
|
||||||
|
|
||||||
|
Run: `python3 -m unittest tests/test_olons_server.py -v`
|
||||||
|
|
||||||
|
Expected: FAIL,目标 Kustomize 目录和专用路由尚不存在。
|
||||||
|
|
||||||
|
- [ ] **Step 3: Write minimal implementation**
|
||||||
|
|
||||||
|
新增 Deployment、Service 与 test overlay;在现有 Traefik IngressRoute 中加入优先级 20 的 `olns-web.118im.com` 路由和根路径重定向中间件;补充证书 DNS 名称及 CI RoleBinding。
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run tests to verify they pass**
|
||||||
|
|
||||||
|
Run: `python3 -m unittest discover -s tests -v`
|
||||||
|
|
||||||
|
Expected: 全部 PASS。
|
||||||
|
|
||||||
|
- [ ] **Step 5: Render and inspect manifests**
|
||||||
|
|
||||||
|
Run: `kubectl kustomize apps/olons-server/overlays/test`
|
||||||
|
|
||||||
|
Expected: 只渲染 `olons-server` Service 与 Deployment,不含 Secret 明文和 PVC。
|
||||||
|
|
||||||
|
### Task 3: 推送、构建镜像与创建运行时 Secret
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- No repository file changes.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Re-fetch both remotes**
|
||||||
|
|
||||||
|
确认远端无新提交后再推送,避免覆盖开发更新。
|
||||||
|
|
||||||
|
- [ ] **Step 2: Commit and push application and GitOps changes**
|
||||||
|
|
||||||
|
分别推送 `olons_server/dev` 与 `im-deploy-config/main`,不强推。
|
||||||
|
|
||||||
|
- [ ] **Step 3: Build and push amd64 image**
|
||||||
|
|
||||||
|
构建并推送 `git.118im.com/im_team/olons-server:test`,记录远端 digest。
|
||||||
|
|
||||||
|
- [ ] **Step 4: Create scoped runtime secrets**
|
||||||
|
|
||||||
|
在 `olons-server-system` 创建 `gitea-registry` 与 `olons-server-secrets`。从现有集群配置安全取得 PXC/Redis 参数,将后台 `security.dataKey` 按当前算法派生为应用 AES/HMAC key,生成独立 API Key 和 Redis 会话前缀;不得输出任何值。
|
||||||
|
|
||||||
|
### Task 4: 同步并完成集群和公网验收
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `/Users/aa/.codex/memories/extensions/ad_hoc/notes/<timestamp>-olons-server-test-deployment.md`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Wait for Argo CD synchronization**
|
||||||
|
|
||||||
|
确认 `olons-server-test`、`platform-ingress`、`platform-ci` 为 `Synced/Healthy`。
|
||||||
|
|
||||||
|
- [ ] **Step 2: Verify Kubernetes runtime**
|
||||||
|
|
||||||
|
确认 Deployment 1/1 Available,Pod Ready、零异常重启,Service/Endpoint 正常,容器使用本次镜像 digest。
|
||||||
|
|
||||||
|
- [ ] **Step 3: Verify middleware isolation**
|
||||||
|
|
||||||
|
确认 PXC、Redis、现有 `im-workbench` 和 `olons-admin` 持续健康且未被重启。
|
||||||
|
|
||||||
|
- [ ] **Step 4: Verify public behavior**
|
||||||
|
|
||||||
|
确认 `/` 跳转 `/admin/`、`/healthz` 200、TLS 有效;在浏览器检查前端无控制台错误,并以真实 API 完成注册、登录和一个会话只读接口。
|
||||||
|
|
||||||
|
- [ ] **Step 5: Run final verification and write memory note**
|
||||||
|
|
||||||
|
重新运行两个仓库测试、源码测试/构建及公网检查;将实际提交、镜像 digest、Argo/Pod/API 结果和未覆盖项写入记忆扩展文件,且不得记录 Secret。
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
# Olons Server 测试集群部署设计
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
将 `olons_server` 的 `dev` 分支作为前后端一体应用部署到现有 k3s 测试集群,通过 `https://olns-web.118im.com` 对外服务。部署复用现有 PXC、Redis、Traefik、证书体系和 Gitea 镜像仓库,不替换或重启现有 `im-workbench`,也不新增数据库、中间件或持久卷。
|
||||||
|
|
||||||
|
## 已确认的源码基线
|
||||||
|
|
||||||
|
- 部署分支:`dev`。
|
||||||
|
- 验证提交:`96d1de4a95e419433d18be6552422450ec85b10a`。
|
||||||
|
- Vue 前端在 Docker 第一阶段构建,产物复制进 Go 运行镜像。
|
||||||
|
- Go 服务监听 `6061`,在 `/admin/` 提供前端,在 `/v1/*` 提供业务 API,并暴露 `/healthz` 与 `/metrics`。
|
||||||
|
- 前端生产构建、Go 全量测试和完整 Docker 构建均已在隔离环境通过。
|
||||||
|
- 应用只允许连接 `olons_admin` 数据库。
|
||||||
|
|
||||||
|
## 方案选择
|
||||||
|
|
||||||
|
采用独立应用部署,不复用现有 `workbench-system/im-workbench` Deployment。
|
||||||
|
|
||||||
|
未采用的方案:
|
||||||
|
|
||||||
|
- 覆盖现有 `im-workbench`:会影响已运行的 `/v1/workbench` 服务,风险不可接受。
|
||||||
|
- 前后端拆分部署:与仓库现有一体化构建和同源 API 设计不一致,增加不必要的镜像、路由和发布复杂度。
|
||||||
|
|
||||||
|
## 应用仓库变更
|
||||||
|
|
||||||
|
在 `olons_server` 中完成以下最小变更:
|
||||||
|
|
||||||
|
1. 新增部署契约测试,先固定镜像名、Namespace、发布目标和一体化构建要求。
|
||||||
|
2. 增加 `.dockerignore`,排除 Git 元数据、本地依赖、构建产物、测试缓存和开发文件。
|
||||||
|
3. 保持现有多阶段 Docker 构建;运行镜像仍只包含 Go 二进制、配置模板和前端产物。
|
||||||
|
4. 将 Gitea Actions 工作流改为构建并推送 `git.118im.com/im_team/olons-server:test`。
|
||||||
|
5. 工作流只滚动更新 `olons-server-system/olons-server`,不得操作现有 `workbench-system/im-workbench`。
|
||||||
|
|
||||||
|
## GitOps 资源
|
||||||
|
|
||||||
|
在 `im-deploy-config` 中新增独立应用:
|
||||||
|
|
||||||
|
- Namespace:`olons-server-system`
|
||||||
|
- Deployment:`olons-server`
|
||||||
|
- Service:`olons-server`
|
||||||
|
- 容器端口:`6061`
|
||||||
|
- 副本数:`1`
|
||||||
|
- 镜像:`git.118im.com/im_team/olons-server:test`
|
||||||
|
- `imagePullPolicy`:`Always`
|
||||||
|
- requests:`50m CPU / 64Mi memory`
|
||||||
|
- limits:`500m CPU / 256Mi memory`
|
||||||
|
- 不创建 PVC
|
||||||
|
|
||||||
|
探针:
|
||||||
|
|
||||||
|
- startup:TCP `6061`
|
||||||
|
- readiness:HTTP `GET /healthz`
|
||||||
|
- liveness:TCP `6061`
|
||||||
|
|
||||||
|
配置通过 Kubernetes Secret 环境变量注入,Git 仓库只保存 Secret 名称和键引用,不保存任何凭据值。
|
||||||
|
|
||||||
|
## 数据库、Redis 与密钥
|
||||||
|
|
||||||
|
- MySQL 复用现有 PXC 中的 `olons_admin` 数据库。
|
||||||
|
- 使用已有最小权限应用账号;如现有账号权限不足,只补充本应用实际需要的表级权限。
|
||||||
|
- Redis 复用现有高可用服务,不修改或重启 Redis。
|
||||||
|
- 数据加密密钥与 HMAC 查找密钥从现有兼容配置安全复制到新 Namespace,保证已有敏感数据可解密和检索。
|
||||||
|
- 为新服务创建独立 API Key。
|
||||||
|
- Redis 会话键使用独立前缀,避免与现有 `im-workbench` 会话冲突。
|
||||||
|
- Secret 值不输出到日志、不写入仓库、不写入部署记录。
|
||||||
|
|
||||||
|
## 公网路由
|
||||||
|
|
||||||
|
在现有 Traefik GitOps 配置中增加高于通配回退规则的专用 Host 路由:
|
||||||
|
|
||||||
|
- Host:`olns-web.118im.com`
|
||||||
|
- `/admin/`、`/v1/`、`/healthz` 以及前端静态资源均转发到 `olons-server-system/olons-server:6061`
|
||||||
|
- `/` 重定向到 `/admin/`
|
||||||
|
|
||||||
|
将 `olns-web.118im.com` 加入现有证书资源。域名当前已通过 Cloudflare 解析到集群入口,但专用路由上线前命中的是默认 Web 应用。
|
||||||
|
|
||||||
|
## 发布顺序
|
||||||
|
|
||||||
|
1. 在两个仓库分别增加失败的部署契约测试。
|
||||||
|
2. 完成应用发布配置和 GitOps 清单,使测试转绿。
|
||||||
|
3. 运行 Go 测试、前端构建、Docker 构建、应用部署契约测试和 GitOps 测试。
|
||||||
|
4. 提交并推送 `olons_server/dev` 与 `im-deploy-config/main`。
|
||||||
|
5. 在 `olons-server-system` 创建 Registry Secret 和应用配置 Secret。
|
||||||
|
6. 构建并推送 amd64 测试镜像。
|
||||||
|
7. 创建或同步 Argo CD Application,等待 Synced/Healthy。
|
||||||
|
8. 验证 Pod、Service、Endpoint、数据库、Redis、TLS、页面与 API。
|
||||||
|
|
||||||
|
## 验收标准
|
||||||
|
|
||||||
|
- Argo CD 应用为 `Synced/Healthy`。
|
||||||
|
- Deployment 为 `1/1 Available`,Pod Ready 且无异常重启。
|
||||||
|
- 实际运行镜像为本次构建的 `olons-server:test` digest。
|
||||||
|
- `/healthz` 在集群内和公网均返回 200。
|
||||||
|
- `https://olns-web.118im.com/` 重定向到 `/admin/`。
|
||||||
|
- 浏览器打开前端无控制台错误,静态资源全部成功加载。
|
||||||
|
- 注册、登录及至少一个需要会话的只读业务接口通过真实请求验证。
|
||||||
|
- PXC 和 Redis 保持健康,现有 `im-workbench`、`olons-admin` 及其他 Argo 应用不受影响。
|
||||||
|
- 仓库中不存在明文 Secret。
|
||||||
|
|
||||||
|
## 回滚
|
||||||
|
|
||||||
|
- 应用回滚到前一镜像 digest 或停止新 Deployment,不触碰共享中间件。
|
||||||
|
- GitOps 路由异常时回退新增的 Host 路由和证书项,恢复原通配回退行为。
|
||||||
|
- 不通过删除数据库记录、清空 Redis 或重启 PXC/Redis实施回滚。
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
|
||||||
|
|
||||||
|
class DeploymentContractTest(unittest.TestCase):
|
||||||
|
def test_workflow_targets_only_olons_server(self):
|
||||||
|
workflow = (ROOT / ".gitea/workflows/deploy.yml").read_text()
|
||||||
|
|
||||||
|
self.assertIn("Deploy olons-server (k3s test cluster)", workflow)
|
||||||
|
self.assertIn("git.118im.com/im_team/olons-server:", workflow)
|
||||||
|
self.assertIn("deployment/olons-server -n olons-server-system", workflow)
|
||||||
|
self.assertNotIn("im-workbench", workflow)
|
||||||
|
self.assertNotIn("workbench-system", workflow)
|
||||||
|
|
||||||
|
def test_dockerfile_builds_frontend_into_backend_image(self):
|
||||||
|
dockerfile = (ROOT / "Dockerfile").read_text()
|
||||||
|
|
||||||
|
self.assertIn("RUN pnpm run build", dockerfile)
|
||||||
|
self.assertIn("VITE_PUBLIC_PATH=/admin/", dockerfile)
|
||||||
|
self.assertIn("COPY --from=web-builder /web/dist ./web/dist", dockerfile)
|
||||||
|
self.assertIn("COPY --from=web-builder /web/dist /app/web/dist", dockerfile)
|
||||||
|
self.assertIn("EXPOSE 6061", dockerfile)
|
||||||
|
|
||||||
|
def test_runtime_image_includes_timezone_database_for_mysql_dsn(self):
|
||||||
|
dockerfile = (ROOT / "Dockerfile").read_text()
|
||||||
|
|
||||||
|
self.assertIn("RUN apk add --no-cache tzdata", dockerfile)
|
||||||
|
|
||||||
|
def test_docker_context_excludes_local_artifacts(self):
|
||||||
|
entries = {
|
||||||
|
line.strip()
|
||||||
|
for line in (ROOT / ".dockerignore").read_text().splitlines()
|
||||||
|
if line.strip() and not line.lstrip().startswith("#")
|
||||||
|
}
|
||||||
|
|
||||||
|
for required in {".git", "web/node_modules", "web/dist", "*.log"}:
|
||||||
|
self.assertIn(required, entries)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user