Compare commits
16 Commits
dev
..
99f848b34d
| Author | SHA1 | Date | |
|---|---|---|---|
| 99f848b34d | |||
| 0a9c026b1b | |||
| 29aafa5999 | |||
| 0c6c4cfa82 | |||
| e17afac339 | |||
| 9cdfee1596 | |||
| 13b8e047c2 | |||
| f2c3c795e7 | |||
| 46394ffcdf | |||
| 786acfe010 | |||
| 2657a7a339 | |||
| 7a84bb5d16 | |||
| 5da1cba851 | |||
| a2ce6d8502 | |||
| fd4fae318d | |||
| 1acd4be28e |
@@ -23,14 +23,14 @@ jobs:
|
||||
git fetch --no-tags --depth=1 origin "${GITHUB_SHA}"
|
||||
git checkout --detach FETCH_HEAD
|
||||
|
||||
- name: Validate Volcengine registry configuration
|
||||
env:
|
||||
VOLCENGINE_REGISTRY_USERNAME: ${{ secrets.VOLCENGINE_REGISTRY_USERNAME }}
|
||||
VOLCENGINE_REGISTRY_PASSWORD: ${{ secrets.VOLCENGINE_REGISTRY_PASSWORD }}
|
||||
- name: Validate ECR configuration
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -n "${VOLCENGINE_REGISTRY_USERNAME}"
|
||||
test -n "${VOLCENGINE_REGISTRY_PASSWORD}"
|
||||
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 }}"
|
||||
|
||||
- name: Build backend production image
|
||||
run: |
|
||||
@@ -63,16 +63,33 @@ jobs:
|
||||
${IMAGE_NAME}:prod-${GITHUB_SHA} \
|
||||
-c "import app; print(app.app.title)"
|
||||
|
||||
- name: Push backend image to Volcengine registry
|
||||
- name: Push backend image to ECR
|
||||
env:
|
||||
VOLCENGINE_REGISTRY_USERNAME: ${{ secrets.VOLCENGINE_REGISTRY_USERNAME }}
|
||||
VOLCENGINE_REGISTRY_PASSWORD: ${{ secrets.VOLCENGINE_REGISTRY_PASSWORD }}
|
||||
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 }}
|
||||
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
|
||||
|
||||
volcengine_registry="linksy-docker-cn-beijing.cr.volces.com"
|
||||
volcengine_image="${volcengine_registry}/eryao/${IMAGE_NAME}"
|
||||
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
|
||||
|
||||
retry() {
|
||||
for attempt in 1 2 3; do
|
||||
@@ -86,20 +103,31 @@ jobs:
|
||||
done
|
||||
}
|
||||
|
||||
volcengine_login() {
|
||||
printf '%s' "${VOLCENGINE_REGISTRY_PASSWORD}" \
|
||||
| docker login --username "${VOLCENGINE_REGISTRY_USERNAME}" --password-stdin "${volcengine_registry}"
|
||||
ecr_login() {
|
||||
aws ecr get-login-password --region "${AWS_REGION}" \
|
||||
| docker login --username AWS --password-stdin "${ecr_registry}"
|
||||
}
|
||||
|
||||
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"
|
||||
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
|
||||
|
||||
deploy-production:
|
||||
needs: build-backend-image
|
||||
if: github.event_name != 'workflow_dispatch' || github.ref == 'refs/heads/main'
|
||||
runs-on: wsl2-docker-host
|
||||
steps:
|
||||
- name: Validate deploy configuration
|
||||
@@ -107,23 +135,27 @@ jobs:
|
||||
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
|
||||
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
|
||||
VOLCENGINE_REGISTRY_USERNAME: ${{ secrets.VOLCENGINE_REGISTRY_USERNAME }}
|
||||
VOLCENGINE_REGISTRY_PASSWORD: ${{ secrets.VOLCENGINE_REGISTRY_PASSWORD }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_REGION: ${{ secrets.AWS_REGION }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -n "${DEPLOY_SSH_KEY}"
|
||||
test -n "${DEPLOY_HOST}"
|
||||
test -n "${DEPLOY_USER}"
|
||||
test -n "${VOLCENGINE_REGISTRY_USERNAME}"
|
||||
test -n "${VOLCENGINE_REGISTRY_PASSWORD}"
|
||||
test -n "${AWS_ACCESS_KEY_ID}"
|
||||
test -n "${AWS_SECRET_ACCESS_KEY}"
|
||||
test -n "${AWS_REGION}"
|
||||
|
||||
- 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
|
||||
|
||||
@@ -131,34 +163,21 @@ 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}" \
|
||||
"VOLCENGINE_REGISTRY_USERNAME=${remote_registry_username} VOLCENGINE_REGISTRY_PASSWORD=${remote_registry_password} bash -se" <<'REMOTE'
|
||||
"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'
|
||||
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
|
||||
|
||||
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}"
|
||||
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}"
|
||||
|
||||
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
|
||||
|
||||
+37
-36
@@ -13,7 +13,7 @@
|
||||
|
||||
- Docker
|
||||
- Docker Compose v2
|
||||
- Docker CLI registry login support
|
||||
- AWS CLI v2
|
||||
- Node.js 22+ (前端 SSR 服务)
|
||||
- PM2 (前端进程管理)
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
```bash
|
||||
docker --version
|
||||
docker compose version
|
||||
aws --version
|
||||
node --version # 需要 >= 22.12.0
|
||||
pm2 --version
|
||||
```
|
||||
@@ -57,13 +58,23 @@ Internet → Nginx (443)
|
||||
deploy/.env
|
||||
```
|
||||
|
||||
生产镜像固定从火山云镜像仓库拉取:
|
||||
必须包含 AWS ECR 镜像定位变量:
|
||||
|
||||
```text
|
||||
linksy-docker-cn-beijing.cr.volces.com/eryao/eryao-backend:latest
|
||||
AWS_ACCOUNT_ID=<你的 AWS 账号 ID>
|
||||
AWS_REGION=<ECR 所在区域>
|
||||
ECR_REPOSITORY=<ECR 仓库名>
|
||||
```
|
||||
|
||||
如果要手动指定完整镜像地址,可在 `.env` 中设置:
|
||||
如果本目录下的 `.env` 是从项目根目录 `.env` 复制过来的,通常还需要手动追加以上三个变量。
|
||||
|
||||
默认镜像地址会拼接为:
|
||||
|
||||
```text
|
||||
${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPOSITORY}:latest
|
||||
```
|
||||
|
||||
如果要手动指定完整镜像地址,可以在 `.env` 中设置:
|
||||
|
||||
```text
|
||||
ERYAO_BACKEND_IMAGE=<完整镜像地址>
|
||||
@@ -105,7 +116,7 @@ ERYAO_WEB__WORKERS=2
|
||||
ERYAO_WORKER__GROUPS__AGENT__CONCURRENCY=2
|
||||
```
|
||||
|
||||
## 登录火山云镜像仓库
|
||||
## 登录 ECR
|
||||
|
||||
进入部署目录,并把 `.env` 加载到当前 shell:
|
||||
|
||||
@@ -116,14 +127,12 @@ set -a
|
||||
set +a
|
||||
```
|
||||
|
||||
在生产机器上配置好火山云仓库用户名和密码后执行:
|
||||
在生产机器上配置好 AWS 凭据后执行:
|
||||
|
||||
```bash
|
||||
printf '%s' "$VOLCENGINE_REGISTRY_PASSWORD" \
|
||||
| docker login \
|
||||
--username "$VOLCENGINE_REGISTRY_USERNAME" \
|
||||
--password-stdin \
|
||||
linksy-docker-cn-beijing.cr.volces.com
|
||||
aws ecr get-login-password --region "$AWS_REGION" \
|
||||
| docker login --username AWS --password-stdin \
|
||||
"${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com"
|
||||
```
|
||||
|
||||
## 启动服务
|
||||
@@ -171,36 +180,28 @@ docker logs -f eryao-prod-redis
|
||||
|
||||
当前 CI/CD 会在 `main` 分支构建后自动部署到生产机器:
|
||||
|
||||
- 在 Gitea runner 构建后端镜像,并推送到火山云 `eryao` 命名空间,同时保留 commit SHA 和 `latest` 标签。
|
||||
- 推送前清空 ECR 仓库旧镜像,只保留新推送的 `latest`。
|
||||
- 通过 `DEPLOY_SSH_KEY` 登录生产机器。
|
||||
- 在生产机器登录火山云仓库,执行 `docker compose pull`、`docker compose up -d`。
|
||||
- 在生产机器执行 ECR 登录、`docker compose pull`、`docker compose up -d`。
|
||||
- 健康检查通过后清理 7 天前未使用的本地 Docker 镜像。
|
||||
|
||||
Gitea Secrets 需要包含:
|
||||
|
||||
```text
|
||||
VOLCENGINE_REGISTRY_USERNAME
|
||||
VOLCENGINE_REGISTRY_PASSWORD
|
||||
AWS_ACCESS_KEY_ID
|
||||
AWS_SECRET_ACCESS_KEY
|
||||
AWS_REGION
|
||||
AWS_ACCOUNT_ID
|
||||
ECR_REPOSITORY
|
||||
DEPLOY_SSH_KEY
|
||||
DEPLOY_HOST
|
||||
DEPLOY_USER
|
||||
```
|
||||
|
||||
`DEPLOY_SSH_KEY` 是已加入生产机器 `root` 用户 `~/.ssh/authorized_keys` 的部署专用私钥。
|
||||
当前生产机器对应:`DEPLOY_HOST=47.239.107.213`,`DEPLOY_USER=root`。
|
||||
`DEPLOY_SSH_KEY` 是已加入生产机器 `ubuntu` 用户 `~/.ssh/authorized_keys` 的部署专用私钥。
|
||||
当前生产机器对应:`DEPLOY_HOST=18.218.38.213`,`DEPLOY_USER=ubuntu`。
|
||||
|
||||
部署脚本会在每次发布前把后端数据库连接更新为觅爻港区 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` 推送镜像,推送失败时部署停止。
|
||||
CI 的 AWS 用户需要至少允许 ECR 登录、建仓库、推送镜像、列出镜像,以及为了覆盖式清理旧镜像所需的 `ecr:BatchDeleteImage`。如果缺少 `ecr:BatchDeleteImage`,部署仍可继续推送 `latest`,但 ECR 旧镜像不会被清空。
|
||||
|
||||
如需手动更新,在生产机器执行:
|
||||
|
||||
@@ -245,13 +246,13 @@ PUBLIC_API_URL=https://api.meeyao.com npm run build
|
||||
|
||||
```bash
|
||||
# 上传构建产物
|
||||
rsync -avz -e "ssh -i 觅爻港区密钥对.pem" web/dist/ root@47.239.107.213:/root/deploy/web/
|
||||
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/package.json web/package-lock.json 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/
|
||||
|
||||
# 安装生产依赖
|
||||
ssh -i 觅爻港区密钥对.pem root@47.239.107.213 "cd /root/deploy/web && npm install --omit=dev"
|
||||
ssh -i xunmee.pem ubuntu@18.218.38.213 "cd /home/ubuntu/deploy/web && npm install --omit=dev"
|
||||
```
|
||||
|
||||
### PM2 管理
|
||||
@@ -286,13 +287,13 @@ server {
|
||||
|
||||
# 静态资源直接从 client 目录提供
|
||||
location /_astro {
|
||||
root /root/deploy/web/client;
|
||||
root /home/ubuntu/deploy/web/client;
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
location /images {
|
||||
root /root/deploy/web/client;
|
||||
root /home/ubuntu/deploy/web/client;
|
||||
expires 30d;
|
||||
}
|
||||
|
||||
@@ -315,8 +316,8 @@ server {
|
||||
cd web && PUBLIC_API_URL=https://api.meeyao.com npm run build
|
||||
|
||||
# 2. 上传
|
||||
rsync -avz -e "ssh -i 觅爻港区密钥对.pem" dist/ root@47.239.107.213:/root/deploy/web/
|
||||
rsync -avz -e "ssh -i xunmee.pem" dist/ ubuntu@18.218.38.213:/home/ubuntu/deploy/web/
|
||||
|
||||
# 3. 重启服务
|
||||
ssh -i 觅爻港区密钥对.pem root@47.239.107.213 "pm2 restart meeyao-web"
|
||||
ssh -i xunmee.pem ubuntu@18.218.38.213 "pm2 restart meeyao-web"
|
||||
```
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: eryao-prod
|
||||
|
||||
x-backend-common: &backend-common
|
||||
image: ${ERYAO_BACKEND_IMAGE:-linksy-docker-cn-beijing.cr.volces.com/eryao/eryao-backend:latest}
|
||||
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}
|
||||
env_file:
|
||||
- path: ./.env
|
||||
required: true
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name meeyao.com www.meeyao.com;
|
||||
|
||||
client_max_body_size 20m;
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:5775;
|
||||
proxy_http_version 1.1;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 120s;
|
||||
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;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:4322;
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user