diff --git a/.gitea/workflows/build-production-docker.yml b/.gitea/workflows/build-production-docker.yml index 808c05e..ee44050 100644 --- a/.gitea/workflows/build-production-docker.yml +++ b/.gitea/workflows/build-production-docker.yml @@ -23,14 +23,14 @@ jobs: git fetch --no-tags --depth=1 origin "${GITHUB_SHA}" git checkout --detach FETCH_HEAD - - name: Validate ECR configuration + - name: Validate Volcengine registry configuration + env: + VOLCENGINE_REGISTRY_USERNAME: ${{ secrets.VOLCENGINE_REGISTRY_USERNAME }} + VOLCENGINE_REGISTRY_PASSWORD: ${{ secrets.VOLCENGINE_REGISTRY_PASSWORD }} run: | set -euo pipefail - test -n "${{ secrets.AWS_ACCESS_KEY_ID }}" - test -n "${{ secrets.AWS_SECRET_ACCESS_KEY }}" - test -n "${{ secrets.AWS_REGION }}" - test -n "${{ secrets.AWS_ACCOUNT_ID }}" - test -n "${{ secrets.ECR_REPOSITORY }}" + test -n "${VOLCENGINE_REGISTRY_USERNAME}" + test -n "${VOLCENGINE_REGISTRY_PASSWORD}" - name: Build backend production image run: | @@ -63,35 +63,16 @@ jobs: ${IMAGE_NAME}:prod-${GITHUB_SHA} \ -c "import app; print(app.app.title)" - - name: Push backend image to ECR + - name: Push backend image to Volcengine registry env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }} - AWS_REGION: ${{ secrets.AWS_REGION }} - AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }} - ECR_REPOSITORY: ${{ secrets.ECR_REPOSITORY }} + VOLCENGINE_REGISTRY_USERNAME: ${{ secrets.VOLCENGINE_REGISTRY_USERNAME }} + VOLCENGINE_REGISTRY_PASSWORD: ${{ secrets.VOLCENGINE_REGISTRY_PASSWORD }} run: | set -euo pipefail export HTTP_PROXY= HTTPS_PROXY= ALL_PROXY= http_proxy= https_proxy= all_proxy= - caller_account_id="$(aws sts get-caller-identity --query Account --output text)" - if [ "${caller_account_id}" != "${AWS_ACCOUNT_ID}" ]; then - echo "AWS_ACCOUNT_ID does not match caller identity" >&2 - exit 1 - fi - - ecr_registry="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com" - ecr_image="${ecr_registry}/${ECR_REPOSITORY}" - - aws ecr describe-repositories \ - --region "${AWS_REGION}" \ - --repository-names "${ECR_REPOSITORY}" >/dev/null 2>&1 \ - || aws ecr create-repository \ - --region "${AWS_REGION}" \ - --repository-name "${ECR_REPOSITORY}" \ - --image-scanning-configuration scanOnPush=true \ - --encryption-configuration encryptionType=AES256 >/dev/null + volcengine_registry="linksy-docker-cn-beijing.cr.volces.com" + volcengine_image="${volcengine_registry}/eryao/${IMAGE_NAME}" retry() { for attempt in 1 2 3; do @@ -105,28 +86,16 @@ jobs: done } - ecr_login() { - aws ecr get-login-password --region "${AWS_REGION}" \ - | docker login --username AWS --password-stdin "${ecr_registry}" + volcengine_login() { + printf '%s' "${VOLCENGINE_REGISTRY_PASSWORD}" \ + | docker login --username "${VOLCENGINE_REGISTRY_USERNAME}" --password-stdin "${volcengine_registry}" } - retry ecr_login - docker tag "${IMAGE_NAME}:prod-${GITHUB_SHA}" "${ecr_image}:latest" - retry docker push "${ecr_image}:latest" - - untagged_image_ids="$(aws ecr list-images \ - --region "${AWS_REGION}" \ - --repository-name "${ECR_REPOSITORY}" \ - --filter tagStatus=UNTAGGED \ - --query 'imageIds[*]' \ - --output json)" - if [ "${untagged_image_ids}" != "[]" ]; then - aws ecr batch-delete-image \ - --region "${AWS_REGION}" \ - --repository-name "${ECR_REPOSITORY}" \ - --image-ids "${untagged_image_ids}" >/dev/null \ - || echo "Warning: ECR image cleanup failed; ensure the CI AWS user has ecr:BatchDeleteImage" >&2 - fi + retry volcengine_login + docker tag "${IMAGE_NAME}:prod-${GITHUB_SHA}" "${volcengine_image}:${GITHUB_SHA}" + docker tag "${IMAGE_NAME}:prod-${GITHUB_SHA}" "${volcengine_image}:latest" + retry docker push "${volcengine_image}:${GITHUB_SHA}" + retry docker push "${volcengine_image}:latest" deploy-production: needs: build-backend-image @@ -137,27 +106,23 @@ jobs: DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} DEPLOY_USER: ${{ secrets.DEPLOY_USER }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_REGION: ${{ secrets.AWS_REGION }} + VOLCENGINE_REGISTRY_USERNAME: ${{ secrets.VOLCENGINE_REGISTRY_USERNAME }} + VOLCENGINE_REGISTRY_PASSWORD: ${{ secrets.VOLCENGINE_REGISTRY_PASSWORD }} run: | set -euo pipefail test -n "${DEPLOY_SSH_KEY}" test -n "${DEPLOY_HOST}" test -n "${DEPLOY_USER}" - test -n "${AWS_ACCESS_KEY_ID}" - test -n "${AWS_SECRET_ACCESS_KEY}" - test -n "${AWS_REGION}" + test -n "${VOLCENGINE_REGISTRY_USERNAME}" + test -n "${VOLCENGINE_REGISTRY_PASSWORD}" - name: Deploy production server env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }} - AWS_REGION: ${{ secrets.AWS_REGION }} DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} DEPLOY_USER: ${{ secrets.DEPLOY_USER }} DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} + VOLCENGINE_REGISTRY_USERNAME: ${{ secrets.VOLCENGINE_REGISTRY_USERNAME }} + VOLCENGINE_REGISTRY_PASSWORD: ${{ secrets.VOLCENGINE_REGISTRY_PASSWORD }} run: | set -euo pipefail @@ -165,21 +130,34 @@ jobs: printf '%s\n' "${DEPLOY_SSH_KEY}" > ~/.ssh/eryao_deploy_key chmod 600 ~/.ssh/eryao_deploy_key ssh-keyscan -H "${DEPLOY_HOST}" >> ~/.ssh/known_hosts + remote_registry_username="$(printf '%q' "${VOLCENGINE_REGISTRY_USERNAME}")" + remote_registry_password="$(printf '%q' "${VOLCENGINE_REGISTRY_PASSWORD}")" ssh -i ~/.ssh/eryao_deploy_key \ -o IdentitiesOnly=yes \ "${DEPLOY_USER}@${DEPLOY_HOST}" \ - "AWS_ACCESS_KEY_ID='${AWS_ACCESS_KEY_ID}' AWS_SECRET_ACCESS_KEY='${AWS_SECRET_ACCESS_KEY}' AWS_DEFAULT_REGION='${AWS_REGION}' AWS_REGION='${AWS_REGION}' bash -se" <<'REMOTE' + "VOLCENGINE_REGISTRY_USERNAME=${remote_registry_username} VOLCENGINE_REGISTRY_PASSWORD=${remote_registry_password} bash -se" <<'REMOTE' set -euo pipefail cd ~/deploy + test -f ./.env + for required_key in ERYAO_DATABASE__HOST ERYAO_DATABASE__PORT ERYAO_DATABASE__NAME ERYAO_DATABASE__USER ERYAO_DATABASE__PASSWORD; do + grep -q "^${required_key}=" ./.env + done + sed -i \ + -e 's#^ERYAO_DATABASE__HOST=.*#ERYAO_DATABASE__HOST=pgm-j6ckocm243gd2997.pg.cnhk.rds.aliyuncs.com#' \ + -e 's#^ERYAO_DATABASE__PORT=.*#ERYAO_DATABASE__PORT=5432#' \ + -e 's#^ERYAO_DATABASE__NAME=.*#ERYAO_DATABASE__NAME=supabase_db#' \ + -e 's#^ERYAO_DATABASE__USER=.*#ERYAO_DATABASE__USER=postgres#' \ + ./.env + set -a . ./.env set +a - ecr_registry="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com" - aws ecr get-login-password --region "${AWS_REGION}" \ - | sudo docker login --username AWS --password-stdin "${ecr_registry}" + volcengine_registry="linksy-docker-cn-beijing.cr.volces.com" + printf '%s' "${VOLCENGINE_REGISTRY_PASSWORD}" \ + | sudo docker login --username "${VOLCENGINE_REGISTRY_USERNAME}" --password-stdin "${volcengine_registry}" sudo docker compose --env-file ./.env -f docker-compose.prod.yml --profile workers pull sudo docker compose --env-file ./.env -f docker-compose.prod.yml --profile workers up -d --remove-orphans diff --git a/deploy/README.md b/deploy/README.md index f590cf9..a35f516 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -13,7 +13,7 @@ - Docker - Docker Compose v2 -- AWS CLI v2 +- Docker CLI registry login support - Node.js 22+ (前端 SSR 服务) - PM2 (前端进程管理) @@ -22,7 +22,6 @@ ```bash docker --version docker compose version -aws --version node --version # 需要 >= 22.12.0 pm2 --version ``` @@ -58,23 +57,13 @@ Internet → Nginx (443) deploy/.env ``` -必须包含 AWS ECR 镜像定位变量: +生产镜像固定从火山云镜像仓库拉取: ```text -AWS_ACCOUNT_ID=<你的 AWS 账号 ID> -AWS_REGION= -ECR_REPOSITORY= +linksy-docker-cn-beijing.cr.volces.com/eryao/eryao-backend:latest ``` -如果本目录下的 `.env` 是从项目根目录 `.env` 复制过来的,通常还需要手动追加以上三个变量。 - -默认镜像地址会拼接为: - -```text -${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPOSITORY}:latest -``` - -如果要手动指定完整镜像地址,可以在 `.env` 中设置: +如果要手动指定完整镜像地址,可在 `.env` 中设置: ```text ERYAO_BACKEND_IMAGE=<完整镜像地址> @@ -116,7 +105,7 @@ ERYAO_WEB__WORKERS=2 ERYAO_WORKER__GROUPS__AGENT__CONCURRENCY=2 ``` -## 登录 ECR +## 登录火山云镜像仓库 进入部署目录,并把 `.env` 加载到当前 shell: @@ -127,12 +116,14 @@ set -a set +a ``` -在生产机器上配置好 AWS 凭据后执行: +在生产机器上配置好火山云仓库用户名和密码后执行: ```bash -aws ecr get-login-password --region "$AWS_REGION" \ - | docker login --username AWS --password-stdin \ - "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com" +printf '%s' "$VOLCENGINE_REGISTRY_PASSWORD" \ + | docker login \ + --username "$VOLCENGINE_REGISTRY_USERNAME" \ + --password-stdin \ + linksy-docker-cn-beijing.cr.volces.com ``` ## 启动服务 @@ -180,28 +171,36 @@ docker logs -f eryao-prod-redis 当前 CI/CD 会在 `main` 分支构建后自动部署到生产机器: -- 推送前清空 ECR 仓库旧镜像,只保留新推送的 `latest`。 +- 在 Gitea runner 构建后端镜像,并推送到火山云 `eryao` 命名空间,同时保留 commit SHA 和 `latest` 标签。 - 通过 `DEPLOY_SSH_KEY` 登录生产机器。 -- 在生产机器执行 ECR 登录、`docker compose pull`、`docker compose up -d`。 +- 在生产机器登录火山云仓库,执行 `docker compose pull`、`docker compose up -d`。 - 健康检查通过后清理 7 天前未使用的本地 Docker 镜像。 Gitea Secrets 需要包含: ```text -AWS_ACCESS_KEY_ID -AWS_SECRET_ACCESS_KEY -AWS_REGION -AWS_ACCOUNT_ID -ECR_REPOSITORY +VOLCENGINE_REGISTRY_USERNAME +VOLCENGINE_REGISTRY_PASSWORD DEPLOY_SSH_KEY DEPLOY_HOST DEPLOY_USER ``` -`DEPLOY_SSH_KEY` 是已加入生产机器 `ubuntu` 用户 `~/.ssh/authorized_keys` 的部署专用私钥。 -当前生产机器对应:`DEPLOY_HOST=18.218.38.213`,`DEPLOY_USER=ubuntu`。 +`DEPLOY_SSH_KEY` 是已加入生产机器 `root` 用户 `~/.ssh/authorized_keys` 的部署专用私钥。 +当前生产机器对应:`DEPLOY_HOST=47.239.107.213`,`DEPLOY_USER=root`。 -CI 的 AWS 用户需要至少允许 ECR 登录、建仓库、推送镜像、列出镜像,以及为了覆盖式清理旧镜像所需的 `ecr:BatchDeleteImage`。如果缺少 `ecr:BatchDeleteImage`,部署仍可继续推送 `latest`,但 ECR 旧镜像不会被清空。 +部署脚本会在每次发布前把后端数据库连接更新为觅爻港区 RDS: + +```text +ERYAO_DATABASE__HOST=pgm-j6ckocm243gd2997.pg.cnhk.rds.aliyuncs.com +ERYAO_DATABASE__PORT=5432 +ERYAO_DATABASE__NAME=supabase_db +ERYAO_DATABASE__USER=postgres +``` + +`ERYAO_DATABASE__PASSWORD` 保留生产服务器现有值,不写入仓库或 CI 日志。 + +`VOLCENGINE_REGISTRY_USERNAME` 当前为 `洵觅科技2025@2101878614`。`VOLCENGINE_REGISTRY_PASSWORD` 必须配置为火山云仓库访问密码;CI 会先登录 registry,再向 `eryao/eryao-backend` 推送镜像,推送失败时部署停止。 如需手动更新,在生产机器执行: @@ -246,13 +245,13 @@ PUBLIC_API_URL=https://api.meeyao.com npm run build ```bash # 上传构建产物 -rsync -avz -e "ssh -i xunmee.pem" web/dist/ ubuntu@18.218.38.213:/home/ubuntu/deploy/web/ +rsync -avz -e "ssh -i 觅爻港区密钥对.pem" web/dist/ root@47.239.107.213:/root/deploy/web/ # 上传依赖配置 -rsync -avz -e "ssh -i xunmee.pem" web/package.json web/package-lock.json ubuntu@18.218.38.213:/home/ubuntu/deploy/web/ +rsync -avz -e "ssh -i 觅爻港区密钥对.pem" web/package.json web/package-lock.json root@47.239.107.213:/root/deploy/web/ # 安装生产依赖 -ssh -i xunmee.pem ubuntu@18.218.38.213 "cd /home/ubuntu/deploy/web && npm install --omit=dev" +ssh -i 觅爻港区密钥对.pem root@47.239.107.213 "cd /root/deploy/web && npm install --omit=dev" ``` ### PM2 管理 @@ -287,13 +286,13 @@ server { # 静态资源直接从 client 目录提供 location /_astro { - root /home/ubuntu/deploy/web/client; + root /root/deploy/web/client; expires 30d; add_header Cache-Control "public, immutable"; } location /images { - root /home/ubuntu/deploy/web/client; + root /root/deploy/web/client; expires 30d; } @@ -316,8 +315,8 @@ server { cd web && PUBLIC_API_URL=https://api.meeyao.com npm run build # 2. 上传 -rsync -avz -e "ssh -i xunmee.pem" dist/ ubuntu@18.218.38.213:/home/ubuntu/deploy/web/ +rsync -avz -e "ssh -i 觅爻港区密钥对.pem" dist/ root@47.239.107.213:/root/deploy/web/ # 3. 重启服务 -ssh -i xunmee.pem ubuntu@18.218.38.213 "pm2 restart meeyao-web" +ssh -i 觅爻港区密钥对.pem root@47.239.107.213 "pm2 restart meeyao-web" ``` diff --git a/deploy/docker-compose.prod.yml b/deploy/docker-compose.prod.yml index e87d152..b45f43f 100644 --- a/deploy/docker-compose.prod.yml +++ b/deploy/docker-compose.prod.yml @@ -1,7 +1,7 @@ name: eryao-prod x-backend-common: &backend-common - image: ${ERYAO_BACKEND_IMAGE:-${AWS_ACCOUNT_ID:?AWS_ACCOUNT_ID is required}.dkr.ecr.${AWS_REGION:?AWS_REGION is required}.amazonaws.com/${ECR_REPOSITORY:?ECR_REPOSITORY is required}:latest} + image: ${ERYAO_BACKEND_IMAGE:-linksy-docker-cn-beijing.cr.volces.com/eryao/eryao-backend:latest} env_file: - path: ./.env required: true