fix(ci): 使用隔离服务运行 PostgreSQL 集成库
ci / verify (pull_request) Successful in 11m30s

将 PostgreSQL 16 改为固定 digest 的 Actions service container,并通过专用 Job 网络向 Go migrator 和集成测试提供数据库。

移除 PR Job 对 Docker CLI 的依赖,继续禁止宿主及内层 Docker API 暴露;同步发布工作流、契约测试和镜像代理恢复说明。
This commit is contained in:
2026-07-21 10:45:52 +08:00
parent 3561efa7da
commit 0aa9b3e88f
5 changed files with 49 additions and 101 deletions
+17 -38
View File
@@ -9,8 +9,22 @@ on:
jobs:
verify:
runs-on: easyai-gateway-ci-unprivileged-v2
services:
postgres:
image: docker.io/library/postgres:16-alpine@sha256:57c72fd2a128e416c7fcc499958864df5301e940bca0a56f58fddf30ffc07777
env:
POSTGRES_USER: easyai_test
POSTGRES_HOST_AUTH_METHOD: trust
POSTGRES_DB: easyai_gateway_test
options: >-
--health-cmd "pg_isready -U easyai_test -d easyai_gateway_test"
--health-interval 2s
--health-timeout 5s
--health-retries 30
env:
TRIVY_DB_REPOSITORY: ghcr.m.daocloud.io/aquasecurity/trivy-db:2
AI_GATEWAY_DATABASE_URL: postgresql://easyai_test@postgres:5432/easyai_gateway_test?sslmode=disable
AI_GATEWAY_TEST_DATABASE_URL: postgresql://easyai_test@postgres:5432/easyai_gateway_test?sslmode=disable
steps:
- name: Checkout without external Actions
env:
@@ -47,7 +61,6 @@ jobs:
go version
node --version
pnpm --version
docker --version
docker-compose version
shellcheck --version
trivy --version
@@ -80,37 +93,9 @@ jobs:
printf 'Go files require gofmt:\n%s\n' "$unformatted" >&2
exit 1
}
- name: Start PostgreSQL 16 integration database
env:
CI_RUN_ID: ${{ github.run_id }}
CI_RUN_NUMBER: ${{ github.run_number }}
run: |
set -eu
container="easyai-gateway-test-pg-${CI_RUN_ID}-${CI_RUN_NUMBER}"
docker run -d --rm --name "$container" \
-e POSTGRES_USER=easyai_test \
-e POSTGRES_PASSWORD=easyai_test_only \
-e POSTGRES_DB=easyai_gateway_test \
postgres:16-alpine
for attempt in $(seq 1 60); do
if docker exec "$container" pg_isready -U easyai_test -d easyai_gateway_test >/dev/null 2>&1; then
break
fi
test "$attempt" -lt 60
sleep 1
done
database_ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$container")
test -n "$database_ip"
printf 'AI_GATEWAY_TEST_DATABASE_URL=postgresql://easyai_test:easyai_test_only@%s:5432/easyai_gateway_test?sslmode=disable\n' "$database_ip" >> "$GITHUB_ENV"
printf 'AI_GATEWAY_TEST_POSTGRES_CONTAINER=%s\n' "$container" >> "$GITHUB_ENV"
docker exec "$container" psql -v ON_ERROR_STOP=1 -U easyai_test -d easyai_gateway_test \
-c 'CREATE TABLE IF NOT EXISTS schema_migrations (version text PRIMARY KEY, applied_at timestamptz NOT NULL DEFAULT now())'
for migration in apps/api/migrations/*.sql; do
docker exec -i "$container" psql -v ON_ERROR_STOP=1 -U easyai_test -d easyai_gateway_test < "$migration"
version=$(basename "$migration" .sql)
docker exec "$container" psql -v ON_ERROR_STOP=1 -U easyai_test -d easyai_gateway_test \
-c "INSERT INTO schema_migrations(version) VALUES ('$version') ON CONFLICT (version) DO NOTHING"
done
- name: Migrate PostgreSQL 16 integration database
working-directory: apps/api
run: go run ./cmd/migrate
- name: Verify Go code
working-directory: apps/api
env:
@@ -142,9 +127,3 @@ jobs:
trivy fs --scanners vuln,secret,misconfig --severity HIGH,CRITICAL \
--ignore-unfixed --exit-code 1 --timeout 15m --skip-dirs .git \
--skip-dirs node_modules .
- name: Stop PostgreSQL integration database
if: always()
run: |
if test -n "${AI_GATEWAY_TEST_POSTGRES_CONTAINER:-}"; then
docker rm -f "$AI_GATEWAY_TEST_POSTGRES_CONTAINER" >/dev/null 2>&1 || true
fi
+17 -38
View File
@@ -7,8 +7,22 @@ on:
jobs:
verify-tag:
runs-on: easyai-gateway-ci-unprivileged-v2
services:
postgres:
image: docker.io/library/postgres:16-alpine@sha256:57c72fd2a128e416c7fcc499958864df5301e940bca0a56f58fddf30ffc07777
env:
POSTGRES_USER: easyai_test
POSTGRES_HOST_AUTH_METHOD: trust
POSTGRES_DB: easyai_gateway_test
options: >-
--health-cmd "pg_isready -U easyai_test -d easyai_gateway_test"
--health-interval 2s
--health-timeout 5s
--health-retries 30
env:
TRIVY_DB_REPOSITORY: ghcr.m.daocloud.io/aquasecurity/trivy-db:2
AI_GATEWAY_DATABASE_URL: postgresql://easyai_test@postgres:5432/easyai_gateway_test?sslmode=disable
AI_GATEWAY_TEST_DATABASE_URL: postgresql://easyai_test@postgres:5432/easyai_gateway_test?sslmode=disable
steps:
- name: Checkout without external Actions
env:
@@ -31,7 +45,6 @@ jobs:
go version
node --version
pnpm --version
docker --version
docker-compose version
shellcheck --version
trivy --version
@@ -65,37 +78,9 @@ jobs:
printf 'Go files require gofmt:\n%s\n' "$unformatted" >&2
exit 1
}
- name: Start PostgreSQL 16 integration database
env:
CI_RUN_ID: ${{ github.run_id }}
CI_RUN_NUMBER: ${{ github.run_number }}
run: |
set -eu
container="easyai-gateway-test-pg-${CI_RUN_ID}-${CI_RUN_NUMBER}"
docker run -d --rm --name "$container" \
-e POSTGRES_USER=easyai_test \
-e POSTGRES_PASSWORD=easyai_test_only \
-e POSTGRES_DB=easyai_gateway_test \
postgres:16-alpine
for attempt in $(seq 1 60); do
if docker exec "$container" pg_isready -U easyai_test -d easyai_gateway_test >/dev/null 2>&1; then
break
fi
test "$attempt" -lt 60
sleep 1
done
database_ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$container")
test -n "$database_ip"
printf 'AI_GATEWAY_TEST_DATABASE_URL=postgresql://easyai_test:easyai_test_only@%s:5432/easyai_gateway_test?sslmode=disable\n' "$database_ip" >> "$GITHUB_ENV"
printf 'AI_GATEWAY_TEST_POSTGRES_CONTAINER=%s\n' "$container" >> "$GITHUB_ENV"
docker exec "$container" psql -v ON_ERROR_STOP=1 -U easyai_test -d easyai_gateway_test \
-c 'CREATE TABLE IF NOT EXISTS schema_migrations (version text PRIMARY KEY, applied_at timestamptz NOT NULL DEFAULT now())'
for migration in apps/api/migrations/*.sql; do
docker exec -i "$container" psql -v ON_ERROR_STOP=1 -U easyai_test -d easyai_gateway_test < "$migration"
version=$(basename "$migration" .sql)
docker exec "$container" psql -v ON_ERROR_STOP=1 -U easyai_test -d easyai_gateway_test \
-c "INSERT INTO schema_migrations(version) VALUES ('$version') ON CONFLICT (version) DO NOTHING"
done
- name: Migrate PostgreSQL 16 integration database
working-directory: apps/api
run: go run ./cmd/migrate
- name: Verify Go code
working-directory: apps/api
env:
@@ -127,9 +112,3 @@ jobs:
trivy fs --scanners vuln,secret,misconfig --severity HIGH,CRITICAL \
--ignore-unfixed --exit-code 1 --timeout 15m --skip-dirs .git \
--skip-dirs node_modules .
- name: Stop PostgreSQL integration database
if: always()
run: |
if test -n "${AI_GATEWAY_TEST_POSTGRES_CONTAINER:-}"; then
docker rm -f "$AI_GATEWAY_TEST_POSTGRES_CONTAINER" >/dev/null 2>&1 || true
fi
+9 -1
View File
@@ -16,7 +16,7 @@
生产 CD 由部署仓的 root-owned dispatcher 执行。它在相同源码 SHA 上同时等待 `ci / verify (push)` 与本次新产生的 `release-ci / verify-tag (push)` 成功,然后只运行部署仓中固定的 BuildKit 构建、Trivy 镜像扫描、digest 发布、备份、迁移、健康检查和回滚命令;不得执行 Tag checkout 中的 `pnpm`、测试、shell 脚本或应用程序。
provision 安装 checksum-pinned 的官方静态 ShellCheck `0.11.0`Docker Compose `5.3.1`并从固定 digest 的 Runner 镜像提取 Docker CLI `29.6.0` 后校验二进制 SHA-256;不会依赖宿主是否预装这些命令。固定 digest 的 Runner 镜像已存在时可离线复用,缺失时才访问镜像仓库。所有工具都在精确 Node Job 镜像内实际执行版本检查
provision 安装 checksum-pinned 的官方静态 ShellCheck `0.11.0`Docker Compose `5.3.1`不会依赖宿主是否预装这些命令。所有下载都在使用前校验固定 SHA-256,并在精确 Node Job 镜像内实际执行版本检查。PostgreSQL 16 集成库作为固定 digest 的 Actions service container 运行,PR Job 不接收宿主或内层 Docker API
## 首次安装或修复 CI Runner
@@ -34,6 +34,14 @@ RUNNER_REGISTRATION_TOKEN='<一次性 Token>' ./scripts/provision-ci-runner.sh
./scripts/provision-ci-runner.sh
```
宿主无法直连 Docker Hub 时,可使用可信 HTTPS 镜像代理;Runner 来源仍必须携带脚本中完全相同的 manifest digest,内层 daemon 拉取的 Job 与 service 镜像也会继续校验工作流固定的 digest:
```bash
CI_RUNNER_IMAGE_SOURCE='docker.m.daocloud.io/gitea/runner:2.0.0-dind-rootless@sha256:5b7b625ff773d0ee761788c47582503ec1b241fa5b81edebad48a57e663f4f3a' \
CI_NESTED_DOCKER_REGISTRY_MIRROR='https://docker.m.daocloud.io' \
./scripts/provision-ci-runner.sh
```
注册 Token 通过交互 stdin 送给短命注册容器,不出现在宿主或容器的命令行参数中;provision 随后立即从环境中清除它,长期 systemd unit 也不包含 Token。不要输出或复制 `/data/.runner` 内容。脚本会停用旧 host Runner,并确保旧 Runner 用户和 v2 遗留用户不属于 `docker`/`sudo` 组。
默认磁盘门禁可用环境变量提高,但生产环境不应降低:
+1 -16
View File
@@ -26,8 +26,6 @@ SHELLCHECK_VERSION=0.11.0
SHELLCHECK_SHA256=8c3be12b05d5c177a04c29e3c78ce89ac86f1595681cab149b65b97c4e227198
COMPOSE_VERSION=5.3.1
COMPOSE_SHA256=f9ebc6ebdb19d769b793c245a736caaeb198c62587f13b25c660c13b4987f959
DOCKER_CLI_VERSION=29.6.0
DOCKER_CLI_SHA256=80b40fa11d797a71ea15f9a31e7004790ddac95f58a8222a40c08f08a639faba
PNPM_VERSION=10.18.1
RUNNER_IMAGE='docker.io/gitea/runner:2.0.0-dind-rootless@sha256:5b7b625ff773d0ee761788c47582503ec1b241fa5b81edebad48a57e663f4f3a'
JOB_IMAGE='docker.io/library/node:24.16.0-bookworm@sha256:40ad9f3064e67d6860b4bc3fe1880b2953934fd6320ada990e45fe0efa6badd7'
@@ -192,22 +190,10 @@ GOBIN="$PREFIX/bin" GOPROXY="${GO_MODULE_PROXY:-https://goproxy.cn,direct}" \
"$PREFIX/toolchains/go/bin/go" install golang.org/x/vuln/cmd/govulncheck@v1.6.0
require_free_space "$MIN_FREE_GIB" "runner image pull"
if ! docker image inspect "$RUNNER_IMAGE_SOURCE" >/dev/null 2>&1; then
docker pull "$RUNNER_IMAGE_SOURCE"
fi
docker pull "$RUNNER_IMAGE_SOURCE"
runner_runtime_image=$(docker image inspect --format '{{.Id}}' "$RUNNER_IMAGE_SOURCE")
printf '%s\n' "$runner_runtime_image" | grep -Eq '^sha256:[0-9a-f]{64}$' || \
fail "pinned runner image did not resolve to an immutable local image ID"
# The job image intentionally contains no Docker client. Extract the static CLI
# from the immutable Runner image, then verify its binary digest before exposing
# it through the read-only CI toolchain mount.
docker_cli_download="$PREFIX/downloads/docker-${DOCKER_CLI_VERSION}-linux-x86_64"
docker run --rm --pull=never --entrypoint /bin/cat "$runner_runtime_image" \
/usr/local/bin/docker > "$docker_cli_download"
printf '%s %s\n' "$DOCKER_CLI_SHA256" "$docker_cli_download" | sha256sum -c -
install -m 0755 "$docker_cli_download" "$PREFIX/bin/docker"
"$PREFIX/bin/docker" --version | grep -Fq "Docker version ${DOCKER_CLI_VERSION},"
chown -R root:root "$PREFIX"
chmod -R go-w "$PREFIX"
@@ -313,7 +299,6 @@ docker exec "$PROBE_CONTAINER" docker run --rm --pull=never \
go version
node --version
pnpm --version
docker --version
docker-compose version
shellcheck --version
trivy --version
+5 -8
View File
@@ -116,8 +116,12 @@ for quality_workflow in "$workflow" "$release_workflow"; do
grep -Fq './tests/ci/pipeline-test.sh' "$quality_workflow"
grep -Fq './tests/ci/semver-test.sh' "$quality_workflow"
grep -Fq 'docker-compose version' "$quality_workflow"
grep -Fq 'docker --version' "$quality_workflow"
grep -Fq 'docker-compose -f docker-compose.yml config --quiet' "$quality_workflow"
grep -Fq 'services:' "$quality_workflow"
grep -Fq 'image: docker.io/library/postgres:16-alpine@sha256:57c72fd2a128e416c7fcc499958864df5301e940bca0a56f58fddf30ffc07777' "$quality_workflow"
grep -Fq 'POSTGRES_HOST_AUTH_METHOD: trust' "$quality_workflow"
grep -Fq 'AI_GATEWAY_TEST_DATABASE_URL: postgresql://easyai_test@postgres:5432/easyai_gateway_test?sslmode=disable' "$quality_workflow"
grep -Fq 'run: go run ./cmd/migrate' "$quality_workflow"
if grep -Fq 'docker compose' "$quality_workflow"; then
echo 'job container must use the read-only standalone Compose binary' >&2
exit 1
@@ -265,14 +269,11 @@ grep -q '^SHELLCHECK_VERSION=0.11.0$' "$provision"
grep -q '^SHELLCHECK_SHA256=8c3be12b05d5c177a04c29e3c78ce89ac86f1595681cab149b65b97c4e227198$' "$provision"
grep -q '^COMPOSE_VERSION=5.3.1$' "$provision"
grep -q '^COMPOSE_SHA256=f9ebc6ebdb19d769b793c245a736caaeb198c62587f13b25c660c13b4987f959$' "$provision"
grep -q '^DOCKER_CLI_VERSION=29.6.0$' "$provision"
grep -q '^DOCKER_CLI_SHA256=80b40fa11d797a71ea15f9a31e7004790ddac95f58a8222a40c08f08a639faba$' "$provision"
grep -Fq 'RUNNER_NAME=${RUNNER_NAME:-easyai-gateway-ci-unprivileged-v2}' "$provision"
grep -Fq "RUNNER_IMAGE='$runner_image'" "$provision"
grep -Fq 'RUNNER_IMAGE_SOURCE=${CI_RUNNER_IMAGE_SOURCE:-$RUNNER_IMAGE}' "$provision"
grep -Fq 'runner_manifest_digest=${RUNNER_IMAGE##*@}' "$provision"
grep -Fq 'CI_RUNNER_IMAGE_SOURCE must use the pinned runner manifest digest' "$provision"
grep -Fq 'if ! docker image inspect "$RUNNER_IMAGE_SOURCE"' "$provision"
grep -Fq 'runner_runtime_image=$(docker image inspect' "$provision"
grep -Fq 'RUNNER_IMAGE_REF="%s"' "$provision"
grep -Fq "JOB_IMAGE='$job_image'" "$provision"
@@ -297,10 +298,6 @@ grep -Fq 'shellcheck-v${SHELLCHECK_VERSION}.linux.x86_64.tar.xz' "$provision"
grep -Fq 'docker-compose-linux-x86_64' "$provision"
grep -Fq 'install -m 0755 "$PREFIX/downloads/shellcheck-v${SHELLCHECK_VERSION}/shellcheck" "$PREFIX/bin/shellcheck"' "$provision"
grep -Fq 'install -m 0755 "$PREFIX/downloads/docker-compose-${COMPOSE_VERSION}-linux-x86_64" "$PREFIX/bin/docker-compose"' "$provision"
grep -Fq 'docker run --rm --pull=never --entrypoint /bin/cat "$runner_runtime_image"' "$provision"
grep -Fq 'printf '\''%s %s\n'\'' "$DOCKER_CLI_SHA256" "$docker_cli_download" | sha256sum -c -' "$provision"
grep -Fq 'install -m 0755 "$docker_cli_download" "$PREFIX/bin/docker"' "$provision"
grep -Fq '"$PREFIX/bin/docker" --version' "$provision"
grep -Fq 'PATH="$PREFIX/toolchains/node/bin:$PATH"' "$provision"
grep -Fq 'gpasswd -d easyai-gateway-ci-v2 docker' "$provision"
grep -Fq 'gpasswd -d easyai-gateway-ci-v2 sudo' "$provision"