# Docker / Gitea Actions 测试部署 ## 运行方式 工作流 `.gitea/workflows/docker-deploy.yml` 使用现有 `ubuntu-latest` runner 标签,在推送 main 或手动 dispatch 时运行: 1. 验证作业可以访问 Docker daemon。 2. 使用 Gitea 自动签发的短期作业 token 下载精确提交的源码,不将认证信息放入 URL、命令行或日志。 3. 用 uv 和 uv.lock 构建 test 镜像,执行 Ruff 与 pytest。外部数据库测试未配置环境时跳过。 4. 构建不含开发依赖的非 root runtime 镜像,包含 PostgreSQL / Cassandra Python 驱动,允许以后配置存储;不运行数据库服务。 5. 通过独立 Compose 项目启动临时 PostgreSQL / Cassandra,执行全部数据库与注册表测试(包含 6 个真实数据库测试);结束后清理该 CI 项目的容器和数据卷。 6. 用 Compose 部署持久化 PostgreSQL / Cassandra、API、特征 worker 和画像 worker,检查 JSONB 存储、画像投影与宿主机 HTTP 端口。 7. 如提供公网 URL,再探测公网 `/health`。后续可从外部执行匿名和埋点测试。 Docker 镜像直接保存在该 daemon 上,本版不依赖镜像仓库登录。 ## 公网端口 ```text http://CI_SERVER_PUBLIC_IP:18080 ↓ Docker published port 容器 :8000 ↓ /v1/identity/* /v1/events /v1/users/* /v1/ads/* ``` 默认 `0.0.0.0:18080:8000`。Docker 的 published port 映射到 **daemon 所在服务器**,不是任务容器自己的 localhost。 服务器公网 IP、路由、云安全组和防火墙也必须允许 18080/TCP,映射本身不能修改云端安全组。 这是用于合成测试数据的 HTTP 环境,`COOKIE_SECURE=false`;正式使用需要 HTTPS 和 Secure Cookie,已有 Nginx 443 模板可接入。 `/health`、`/docs`、`/openapi.json` 可用于测试;画像查询、Stitch 和历史查询保持服务端凭据鉴权。 CORS 默认允许 localhost:3000、5173、5174;接入控制台域名时设置 `DEPLOY_CORS_ORIGINS`。 ## Runner 要求 runner v0.6.1 的 `container.docker_host` 默认空字符串,会自动查找 daemon 并将 socket 提供给 job。 若显式配置为 `-`,作业不会获得 Docker host,第一步会失败。直接使用宿主机 socket 的配置示例: ```yaml container: docker_host: unix:///var/run/docker.sock ``` 如果 runner 本身在 Docker 中,runner 容器需要挂载宿主机 `/var/run/docker.sock`。 本工作流不额外申请 privileged 或用户自定义 host bind mount;使用 runner 提供的 daemon。 如果使用 Docker-in-Docker,18080 会发布在 DinD daemon 的命名空间中,未必是公网宿主机。 需要切换为宿主机 daemon,或由服务器管理员额外转发 DinD 端口。看到 runner 的 Idle 状态不能证明公网部署已配置好。 Docker socket 具有管理 daemon 的能力,因此部署工作流仅对 main 的 push/dispatch 执行,不接收不可信 PR。 ## 手动运行 / 配置 手动 dispatch 可提供: - `deploy_port`:宿主机公开端口,默认 18080,允许 1024–65535。 - `public_base_url`:例如 `http://SERVER_IP:18080`;用于公网探测和访问地址报告。 也可由仓库管理员设置 `ADCLOUD_DEPLOY_PORT`、`ADCLOUD_PUBLIC_BASE_URL` 两个 Actions 变量;手动输入优先。 执行部署的 bot 只需仓库写入权限;修改 runner 配置和 Actions 变量需要对应管理权限。 在有 Docker 的目标服务器手动部署: ```sh docker build --target runtime --tag adcloud-server:test . DEPLOY_IMAGE=adcloud-server:test bash scripts/compose_stack.sh deploy ``` 当前公网环境使用 PostgreSQL 16 保存身份图谱和 JSONB 权威画像,Cassandra 4.1 保存画像投影,adcloud-events 消费事件规则任务,adcloud-profiles 消费画像 outbox。数据库不发布宿主机端口,只能通过项目 Docker 内网连接。 Cassandra 在此合成测试环境内关闭 TLS / 认证,单节点 RF=1;生产环境需另外配置认证、TLS、备份和多节点副本。 数据库和应用合计内存限制约 4 GiB,服务器应留出至少 4.5 GiB 可用内存(CI 临时数据库会额外占用内存),首次启动 Cassandra 可能需要几分钟。 `compose.yml` 使用健康检查和一次性 CQL 初始化任务控制启动顺序。 临时 CI 使用 `adcloud-ci--` 独立项目;PostgreSQL 密码随机生成。Cassandra 测试同时连接 CI PostgreSQL,验证双存储组合。失败时也会清理该项目。 手动 Compose 操作需通过脚本加载 config 卷中持久化的 PostgreSQL 密码;不要直接输出 `docker compose config`(其中含展开的连接凭据)。 ## 持久化、权限和回滚 - Compose 项目为 `adcloud-test`;PostgreSQL 和 Cassandra 分别使用项目专属命名数据卷。 - `adcloud-server-test-config` 复用原身份密钥卷;首次补充随机 PostgreSQL 密码,重部署复用,配置文件权限 0600,不输出到日志。 - API / worker 使用 UID/GID 10001、只读根文件系统、去除 capabilities、限制内存和进程;均使用 `restart=unless-stopped`。 - named lock 防止并发部署。确认无部署仍在运行后才能清理遗留的 `adcloud-server-test-deploy-lock`。 - 首次切换前先等待数据库就绪;旧 SQLite 容器仅停止,旧数据卷保留,无自动数据迁移。 - 首次 Compose 切换失败时尽可能恢复旧 SQLite 容器;后续 Compose 更新不是原子部署,不保证 schema 迁移可回滚。公网 API 切换有短暂停机。 - 测试部署不自动清除持久化数据卷或旧镜像。原 `deploy_docker.sh` 保留用于独立 SQLite 部署,不与 Compose 同时运行。 ## HTTP 验证 `smoke_api.py` 检查系统/模块路由、匿名 Cookie、埋点、未授权拒绝、可信 Stitch、画像继承和历史事件回溯。 使用合成邮箱与独立匿名身份,会向测试数据库写入少量演示数据。 `--require-projection` 还会验证 PostgreSQL / JSONB 配置,并等待画像状态变为 Cassandra `current`,避免降级到关系型读取掩盖 worker 故障。 验证宿主机端口时,临时探测容器挂载配置卷为只读,读取凭据在进程内发送,不将凭据放到命令参数或日志中。 公网健康检查: ```sh curl --fail http://SERVER_IP:18080/health ``` 外部匿名和事件检查(不需要服务端凭据): ```sh uv run python scripts/smoke_api.py --base-url http://SERVER_IP:18080 --public-only ``` 不要将 SERVICE_TOKEN 放入浏览器或 SDK。需要管理接口测试时,在服务器的受控环境读取部署配置,或配置你自己的受保护测试凭据。 参考:[Docker 端口映射](https://docs.docker.com/engine/network/port-publishing/)、[runner v0.6.1 配置](https://gitea.com/gitea/act_runner/src/tag/v0.6.1/internal/pkg/config/config.example.yaml)、[uv Docker 集成](https://docs.astral.sh/uv/guides/integration/docker/)。 注册表默认强制应用凭据和已发布事件版本;smoke 会创建合成应用、注册 add_cart、重试同一 event_id 并验证数码意向 +1,只输出验证结论。公网 public-only 检查健康、匿名 Cookie 和未注册事件拒绝,不携带管理凭据。