duoqi-api/docs/ci-deployment-guide.md
Wang Zhuoxuan 5b1f0848ac
Some checks failed
CI/CD Pipeline / Code Quality (push) Failing after 17s
CI/CD Pipeline / Unit Tests (push) Has been skipped
CI/CD Pipeline / Build & Deploy Test (push) Has been skipped
CI/CD Pipeline / Build & Deploy Production (push) Has been skipped
feat: 添加管理员修改自己密码的接口
新增 PUT /v1/admin/change-password 端点,允许已登录管理员
(admin / super_admin)修改自己的密码。需验证旧密码,
且新旧密码不能相同。错误由全局 errorHandler 统一处理。
2026-04-23 12:32:31 +08:00

37 KiB
Raw Blame History

Duoqi API 部署与持续集成方案

基于 Gitea + 阿里云轻量应用服务器 (Alibaba Cloud Linux 3) 的私有化部署方案

目录


架构概览

为什么选择 Gitea 而不是 GitLab

对比项 Gitea GitLab CE
最低内存 ~200MB ~4GB
安装复杂度 单二进制文件 需要多组件
CI/CD Gitea Actions兼容 GitHub Actions 语法) GitLab CI/CD
容器镜像仓库 内置(可选) 内置
适合场景 小团队、资源有限 大团队、功能全面
2C/2G 可行性 完全可行 内存不足

单服务器资源分配2C/2G

┌─────────────────────────────────────────────────────────────────────┐
│                     阿里云轻量应用服务器 (2C/2G)                      │
│                                                                     │
│  ┌───────────────────────────────────────────────────────────────┐  │
│  │  Nginx 反向代理 (:80/:443 ← 唯一对外入口)                      │  │
│  │  ┌──────────────┐ ┌──────────────┐ ┌────────────────────────┐│  │
│  │  │ api.duoqi.me│ │test-api.     │ │ git.duoqi.me          ││  │
│  │  │ → :3000 prod │ │duoqi.me     │ │ → :3200 Gitea (IP限制) ││  │
│  │  │              │ │ → :3001 test │ │                        ││  │
│  │  └──────────────┘ └──────────────┘ └────────────────────────┘│  │
│  └───────────────────────────────────────────────────────────────┘  │
│         │                  │                  │                      │
│         ▼                  ▼                  ▼                      │
│  ┌─────────────┐  ┌──────────────┐  ┌──────────────────────────┐   │
│  │ duoqi-api   │  │ duoqi-api    │  │ Gitea + Act Runner       │   │
│  │ (prod)      │  │ (test)       │  │ 代码托管 + CI/CD         │   │
│  │ 127.0.0.1   │  │ 127.0.0.1    │  │ 127.0.0.1:3200          │   │
│  │ :3000       │  │ :3001        │  │ ~200MB                  │   │
│  │ ~300MB      │  │ ~200MB       │  └──────────────────────────┘   │
│  └─────────────┘  └──────────────┘                                   │
│                                                                     │
│  ┌───────────────────────────────────────────────────────────────┐  │
│  │              阿里云 RDS MySQL (外置,仅内网可达)                 │  │
│  │  ┌──────────────┐  ┌──────────────┐                            │  │
│  │  │  duoqi_prod  │  │  duoqi_test  │                            │  │
│  │  └──────────────┘  └──────────────┘                            │  │
│  └───────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────┘

内存预算:

组件 预估占用 说明
OS + 系统 ~200MB Alibaba Cloud Linux 3
Docker ~150MB Docker Daemon
Gitea ~200MB 含 Act Runner
Nginx ~50MB 反向代理
duoqi-api (prod) ~300MB 生产容器
duoqi-api (test) ~200MB 测试容器(按需启停)
合计 ~1.1GB 剩余 ~900MB 缓冲

Phase 1: 单服务器方案(当前)

资源规划

资源 配置 用途
轻量应用服务器 2C/2G/40GB SSD Gitea + Docker + Nginx
阿里云 RDS MySQL 1C/1G 基础版 duoqi_prod + duoqi_test
域名 + SSL api.duoqi.me 生产环境入口

服务器初始化

系统选择 Alibaba Cloud Linux 3(阿里云官方优化内核,内存占用更低,与 RDS 兼容性更好)

# SSH 登录
ssh root@your-server-ip

# 1. 更新系统
dnf update -y

# 2. 安装基础工具
dnf install -y curl git nginx certbot python3-certbot-nginx

# 3. 安装 DockerAlibaba Cloud Linux 内置 Docker 源)
dnf install -y docker docker-compose-plugin
systemctl enable --now docker

# 4. 配置 Docker 镜像加速(国内网络必须)
tee /etc/docker/daemon.json << 'EOF'
{
  "registry-mirrors": [
    "https://docker.1ms.run",
    "https://docker.xuanyuan.me"
  ]
}
EOF
systemctl restart docker

# 5. 配置防火墙firewalld
#    所有服务通过 Nginx 反向代理,只需开放 22/80/443
systemctl enable --now firewalld
firewall-cmd --permanent --add-service=ssh
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload

# 6. 创建项目目录
mkdir -p /opt/duoqi-api
mkdir -p /opt/gitea
mkdir -p /opt/backups
mkdir -p /opt/runner-image

# 7. 验证
cat /etc/os-release      # 确认 Alibaba Cloud Linux
docker --version
nginx -v

Gitea 安装与配置

方式一:二进制安装(推荐,更省内存)

Gitea 是单个 Go 二进制文件,直接运行在宿主机上比 Docker 方式省 ~50MB 内存,且 systemd 管理更可靠。 在 2C/2G 服务器上,省下的每一 MB 都有意义。

# 下载 Gitea
wget -O /usr/local/bin/gitea https://dl.gitea.io/gitea/1.22/gitea-1.22-linux-amd64
chmod +x /usr/local/bin/gitea

# 创建用户和目录
groupadd --system git
useradd --system --gid git --shell /bin/bash -m git
mkdir -p /var/lib/gitea/{custom,data,log}
chown -R git:git /var/lib/gitea
mkdir -p /etc/gitea
chown git:git /etc/gitea

# 创建 systemd 服务
cat > /etc/systemd/system/gitea.service << 'EOF'
[Unit]
Description=Gitea
After=network.target

[Service]
User=git
WorkingDirectory=/var/lib/gitea
ExecStart=/usr/local/bin/gitea web -c /etc/gitea/app.ini --port 3200 --http-addr 127.0.0.1
Restart=always
Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea

[Install]
WantedBy=multi-user.target
EOF

# 启动
systemctl enable gitea
systemctl start gitea

# 等待 Gitea 生成默认配置文件
sleep 3
systemctl stop gitea

配置反向代理适配(关键步骤):

Gitea 需要知道自己对外是 https://git.duoqi.me,否则 Git clone URL 和 Web UI 链接会指向 127.0.0.1:3200

# 编辑 Gitea 配置文件
cat >> /etc/gitea/app.ini << 'EOF'

[server]
DOMAIN           = git.duoqi.me
ROOT_URL         = https://git.duoqi.me/
HTTP_ADDR        = 127.0.0.1
HTTP_PORT        = 3200
SSH_DOMAIN       = git.duoqi.me
SSH_PORT         = 22
SSH_LISTEN_PORT  = 22
OFFLINE_MODE     = true          ; 禁用 Gravatar 等外部服务,加快页面加载

[security]
INSTALL_LOCK      = true         ; 已完成安装,禁止再次访问安装页面

[service]
REGISTER_EMAIL_CONFIRM = false   ; 按需开启
DISABLE_REGISTRATION   = true    ; 禁止公开注册,仅管理员创建账号

[other]
SHOW_FOOTER_VERSION = false      ; 隐藏版本号,减少信息泄露
EOF

# 修正权限
chown git:git /etc/gitea/app.ini
chmod 640 /etc/gitea/app.ini

# 启动
systemctl start gitea

方式二Docker 部署(备选)

cat > /opt/gitea/docker-compose.yml << 'EOF'
version: '3.8'

services:
  gitea:
    image: gitea/gitea:latest
    container_name: gitea
    restart: unless-stopped
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - GITEA__server__DOMAIN=git.duoqi.me
      - GITEA__server__ROOT_URL=https://git.duoqi.me/
      - GITEA__server__HTTP_ADDR=127.0.0.1
      - GITEA__server__HTTP_PORT=3200
      - GITEA__server__SSH_DOMAIN=git.duoqi.me
    volumes:
      - ./data:/data
      - /etc/localtime:/etc/localtime:ro
    network_mode: host    ; 需要绑定 127.0.0.1,使用 host 网络
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
EOF

cd /opt/gitea && docker compose up -d

注意: Docker 方式会额外占用 ~50MB 内存(容器运行时开销),仅当需要快速试用或测试时使用。

Gitea 初始化配置

1. 浏览器访问 https://git.duoqi.me通过 Nginx 反向代理)
   首次启动时也可临时访问 http://127.0.0.1:3200
2. 首次配置(安装页面):
   - 数据库SQLite3默认
   - 服务器域名git.duoqi.me
   - Gitea HTTP 监听端口3200
   - SSH 服务端口22
   - 管理员账号:创建 admin 用户
3. 创建仓库duoqi-api
4. 推送代码HTTP
   git remote add gitea https://git.duoqi.me/admin/duoqi-api.git
   git push gitea main
   git checkout -b develop
   git push gitea develop

   或使用 SSH
   git remote add gitea git@git.duoqi.me:admin/duoqi-api.git
   git push gitea main
   git checkout -b develop
   git push gitea develop

安装 Act RunnerCI/CD 执行器)

国内网络关键配置Gitea Act Runner 使用 Docker 执行器,需要解决以下问题:

  1. Docker Hub 镜像拉取 → 通过 Docker 镜像加速解决(已在服务器初始化配置)
  2. GitHub Actions 拉取 → 通过 github_mirror 配置从 gitea.com 镜像解决
  3. 容器访问 Gitea API → 通过 network: host 让容器共享宿主机网络
  4. Job 容器执行 docker 命令 → 挂载宿主机 Docker socket镜像内置 Docker CLI
1. 构建 Runner 自定义镜像

oven/bun 不包含 git 和 docker CLI需要构建自定义镜像

  • gitactions/checkout 需要,否则会回退到 REST APIGitHub 与 Gitea URL 格式不兼容)
  • docker CLICI 中执行 docker build 等命令需要,通过 socket 连接宿主机 Docker daemon
  • 固定版本:使用 Bun 1.3 而非 latest确保 CI 环境可复现
# 创建 Dockerfile
cat > /opt/runner-image/Dockerfile << 'EOF'
FROM oven/bun:1.3
RUN sed -i 's|deb.debian.org|mirrors.aliyun.com|g' /etc/apt/sources.list.d/debian.sources \
    && apt-get update \
    && apt-get install -y git docker.io \
    && rm -rf /var/lib/apt/lists/*
EOF

# 构建镜像(使用阿里云 Debian 镜像源加速)
docker build -t duoqi-runner:bun-git /opt/runner-image

# 验证镜像包含所需工具
docker run --rm duoqi-runner:bun-git bun --version
docker run --rm duoqi-runner:bun-git git --version
docker run --rm duoqi-runner:bun-git docker --version
2. 安装并注册 Runner
# 下载 Act Runner
wget -O /usr/local/bin/act_runner https://gitea.com/gitea/act_runner/releases/latest/download/act_runner-linux-amd64
chmod +x /usr/local/bin/act_runner

# 在 Gitea Web UI 中生成 Token
# Settings → Actions → Runners → Create new Runner → 复制 Token

# 创建专用工作目录register 和 daemon 必须在同一目录)
mkdir -p /opt/act-runner

# 注册 Runner使用自定义镜像
cd /opt/act-runner && act_runner register \
  --instance http://localhost:3200 \
  --token YOUR_RUNNER_TOKEN \
  --name duoqi-runner \
  --labels ubuntu-latest:docker://duoqi-runner:bun-git
3. 生成并配置 Runner 配置文件
# 生成默认配置
cd /opt/act-runner && act_runner generate-config > config.yaml

编辑 /opt/act-runner/config.yaml,修改以下关键配置:

runner:
  # 移除默认的测试环境变量
  envs: {}
  # 从 gitea.com 镜像拉取 GitHub Actions解决国内无法访问 GitHub 的问题)
  github_mirror: 'https://gitea.com'
  labels:
    - "ubuntu-latest:docker://duoqi-runner:bun-git"

container:
  # 容器使用宿主机网络(解决容器无法访问 127.0.0.1:3200 Gitea 的问题)
  network: "host"
  # 挂载宿主机目录到 job 容器CI deploy 步骤需要读取 docker-compose.yml
  options: "-v /opt/duoqi-api:/opt/duoqi-api"
  # 允许挂载的 volume 白名单
  valid_volumes:
    - /opt/duoqi-api
  # 不强制每次拉取镜像(国内网络下减少失败风险)
  force_pull: false
4. 创建 systemd 服务并启动
cat > /etc/systemd/system/act-runner.service << 'EOF'
[Unit]
Description=Gitea Act Runner
After=docker.service

[Service]
WorkingDirectory=/opt/act-runner
ExecStart=/usr/local/bin/act_runner daemon --config /opt/act-runner/config.yaml
Restart=always
Environment=HOME=/root

[Install]
WantedBy=multi-user.target
EOF

# 启动
systemctl daemon-reload
systemctl enable act-runner
systemctl start act-runner

# 验证:确认 active (running),且 Gitea Web UI 中 Runner 显示在线
systemctl status act-runner

注意act_runner 是宿主机原生运行的二进制,不是容器。它本身已有完整的文件系统和网络访问权限。

  • 不需要 BindPaths 挂载 Docker socket宿主机进程可直接访问
  • 不需要 BindPaths 挂载 /opt/duoqi-api(同上)
  • job 容器需要访问宿主机目录,通过 config.yaml 中的 container.optionsvalid_volumes 配置

环境隔离策略

RDS 数据库隔离

-- 连接到 RDS
mysql -h your-rds-endpoint -u root -p

-- 创建生产库
CREATE DATABASE duoqi_prod CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 创建测试库
CREATE DATABASE duoqi_test CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 创建用户并授权
CREATE USER 'duoqi_prod'@'%' IDENTIFIED BY 'prod-password';
GRANT ALL PRIVILEGES ON duoqi_prod.* TO 'duoqi_prod'@'%';

CREATE USER 'duoqi_test'@'%' IDENTIFIED BY 'test-password';
GRANT ALL PRIVILEGES ON duoqi_test.* TO 'duoqi_test'@'%';

FLUSH PRIVILEGES;

环境配置文件

密钥生成(在配置环境变量之前):

# 生成 JWT_SECRET用于签名和验证用户 JWT
openssl rand -base64 32

# 生成 ADMIN_TOKEN管理后台认证令牌
openssl rand -base64 32

# 或使用 Node.js 生成:
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"

安全提示

  • JWT_SECRET 必须至少 32 字符(代码中有强制校验)
  • 生产环境和测试环境必须使用不同的密钥
  • 密钥生成后应妥善保管,不要提交到 Git 仓库
  • 可在任意服务器生成,关键是通过安全渠道传输到目标环境

生产环境 /opt/duoqi-api/.env.prod

DATABASE_URL=mysql://duoqi_prod:prod-password@your-rds-endpoint:3306/duoqi_prod
JWT_SECRET=prod-super-secret-jwt-key  # 替换为生成的密钥
JWT_EXPIRES_IN=1h
JWT_REFRESH_EXPIRES_IN=30d
ADMIN_TOKEN=prod-admin-token  # 替换为生成的密钥
PORT=3000
NODE_ENV=production
LOG_LEVEL=warn
# ... 其他生产环境变量

测试环境 /opt/duoqi-api/.env.test

DATABASE_URL=mysql://duoqi_test:test-password@your-rds-endpoint:3306/duoqi_test
JWT_SECRET=test-secret-key  # 与生产环境不同
JWT_EXPIRES_IN=1h
JWT_REFRESH_EXPIRES_IN=30d
ADMIN_TOKEN=test-admin-token  # 与生产环境不同
PORT=3001
NODE_ENV=test
LOG_LEVEL=debug
# ... 其他测试环境变量可使用测试用的华为、OSS配置

Docker Compose 环境隔离

服务器 compose 文件 /opt/duoqi-api/docker-compose.yml

代码库中对应文件为 docker-compose.prod.yml,部署时手动重命名为 docker-compose.yml

# 使用 host 网络模式,避免 Docker bridge 子网与阿里云 VPC 内网 IP 段冲突

services:
  # ===== 生产环境 =====
  api-prod:
    build:
      context: .
      dockerfile: Dockerfile
    image: duoqi-api:prod
    container_name: duoqi-api-prod
    restart: unless-stopped
    network_mode: host
    env_file: .env.prod
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    deploy:
      resources:
        limits:
          memory: 400M

  # ===== 测试环境Docker profiles 按需启停) =====
  api-test:
    build:
      context: .
      dockerfile: Dockerfile
    image: duoqi-api:test
    container_name: duoqi-api-test
    restart: "no"
    network_mode: host
    env_file: .env.test
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3001/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    logging:
      driver: "json-file"
      options:
        max-size: "5m"
        max-file: "2"
    deploy:
      resources:
        limits:
          memory: 300M
    profiles:
      - test

启停命令:

# 只启动生产环境(默认)
docker compose up -d

# 启动生产 + 测试环境
docker compose --profile test up -d

# 停止测试环境(释放内存)
docker compose --profile test stop api-test

# 重新构建并启动
docker compose up -d --build api-prod
docker compose --profile test up -d --build api-test

CI/CD 流程

双分支工作流

develop 分支(开发测试)              main 分支(生产发布)
    │                                    │
    ▼                                    ▼
┌───────────┐                      ┌───────────┐
│  quality  │ ← 类型检查            │  quality  │ ← 类型检查
└─────┬─────┘                      └─────┬─────┘
      ▼                                  ▼
┌───────────┐                      ┌───────────┐
│   test    │ ← 单元测试            │   test    │ ← 单元测试
└─────┬─────┘                      └─────┬─────┘
      ▼                                  ▼
┌────────────┐                     ┌────────────┐
│ build-test │ ← 构建测试镜像       │ build-prod │ ← 构建生产镜像
└─────┬──────┘                     └─────┬──────┘
      ▼                                  ▼
┌─────────────┐                    ┌─────────────┐
│ deploy-test │ ← 自动部署到测试    │ deploy-prod │ ← 手动确认后部署生产
└─────────────┘                    └─────────────┘

日常开发流程

# 1. 在 develop 上开发
git checkout develop
# ... 编写代码 ...
git add .
git commit -m "feat: 新功能描述"
git push origin develop
# → 自动触发quality → test → build-test → deploy-test

# 2. 在测试环境验证http://test-api.duoqi.me
#    如果发现问题,继续在 develop 上修改并 push

# 3. 测试通过,合并到 main 发布
git checkout main
git merge develop
git push origin main
# → 自动触发quality → test → build-prod
# → 在 Gitea Web UI 手动确认 deploy-prod

触发规则

事件 quality test build deploy
push 到 develop build-test deploy-test自动
push 到 main build-prod deploy-prod手动确认

注意develop 分支需要在 Gitea 仓库中手动创建:

git checkout -b develop
git push origin develop

关键设计说明

设计决策 原因
双分支隔离develop + main develop 是试验场main 是稳定版本,互不干扰
不同分支构建不同镜像 develop 只构建 test 镜像main 只构建 prod 镜像,节省 2C/2G 服务器资源
本地构建镜像,无外部仓库 单服务器不需要镜像中转,节省资源
测试环境使用 Docker profiles 按需启停,节省内存
生产部署手动确认 防止误操作,确保人工验证后才上线
使用 Gitea Actions 兼容 GitHub Actions 语法,学习成本低
Runner 使用 network: host 容器共享宿主机网络,解决容器无法访问 Gitea 的问题
API 容器使用 network_mode: host 避免 Docker bridge 默认子网172.x.0.0/16与阿里云 VPC 内网 IP 段冲突
Runner 使用 github_mirror 从 gitea.com 镜像拉取 Actions解决国内无法访问 GitHub 的问题
自定义 Runner 镜像bun + git + docker CLI 避免 checkout REST API 不兼容,支持 CI 中执行 docker 命令
Runner config 的 options + valid_volumes 让 job 容器能挂载宿主机的 /opt/duoqi-api 目录,读取 docker-compose.yml
CI health check 使用 bun -e "fetch(...)" runner 镜像未安装 curl用 bun 内置 fetch API 做 HTTP 健康检查
固定 Bun 版本1.3 确保 CI 环境可复现,避免 latest 版本变化导致意外失败

部署操作

手动部署(首次或紧急)

# SSH 登录服务器
ssh root@your-server-ip
cd /opt/duoqi-api

# 从 Gitea 拉取最新代码
git -C /opt/duoqi-api/repo pull  # 如果用 git clone 方式

# 或使用部署脚本
bash scripts/deploy.sh prod

使用部署脚本

# 部署到生产环境
bash scripts/deploy.sh prod

# 部署到测试环境
bash scripts/deploy.sh test

# 停止测试环境
bash scripts/deploy.sh test-stop

# 查看状态
bash scripts/deploy.sh status

# 回滚
bash scripts/deploy.sh rollback prod

Nginx 配置

所有服务通过 Nginx 统一入口,后端端口仅绑定 localhost防火墙只开 22/80/443。

# 生产环境 API
cat > /etc/nginx/conf.d/duoqi-api.conf << 'EOF'
server {
    listen 80;
    server_name api.duoqi.me;

    location / {
        proxy_pass http://localhost:3000;
        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;
    }
}
EOF

# 测试环境 API仅限内部访问
cat > /etc/nginx/conf.d/duoqi-api-test.conf << 'EOF'
server {
    listen 80;
    server_name test-api.duoqi.me;

    # 限制访问 IP开发团队办公网络
    # allow 123.56.78.90;
    # deny all;

    location / {
        proxy_pass http://localhost:3001;
        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;
    }
}
EOF

# Gitea 代码仓库(限制访问 IP仅团队可访问
cat > /etc/nginx/conf.d/gitea.conf << 'EOF'
server {
    listen 80;
    server_name git.duoqi.me;

    # 限制访问 IP开发团队办公网络
    # allow 123.56.78.90;
    # deny all;

    location / {
        proxy_pass http://127.0.0.1:3200;
        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;

        # Gitea 需要较大的请求体git push
        client_max_body_size 100M;
    }
}
EOF

# 验证并生效
nginx -t
systemctl enable --now nginx
systemctl reload nginx

配置 HTTPS

# 为三个域名统一申请证书
certbot --nginx -d api.duoqi.me -d test-api.duoqi.me -d git.duoqi.me

# 自动续期验证
certbot renew --dry-run

Note

在子域名还没有配置解析的前提下,会影响证书的签发。后期如果新增子域名,并且需要同一张证书时,可以通过--expand参数进行申请。

# 方案1先申请已就绪的子域名
sudo certbot --nginx -d example.com -d www.example.com

# 后续子域名就绪后,再扩容证书
sudo certbot --nginx --expand \
  -d example.com -d www.example.com -d api.example.com -d blog.example.com
常见错误
# ❌ 错误:只写新增域名,会导致原有域名被移除!
sudo certbot --nginx --expand -d api.example.com -d blog.example.com

# ✅ 正确:列出所有域名(原有 + 新增)
sudo certbot --nginx --expand \
  -d example.com -d www.example.com -d api.example.com -d blog.example.com

Phase 2: 多服务器扩展(未来)

架构演进

Phase 1 (当前)                     Phase 2 (扩展后)
┌──────────────┐                   ┌─────────────────────────────────┐
│ 单台服务器    │                   │       负载均衡 (SLB/Nginx)       │
│              │                   │   api.duoqi.me → 443/80        │
│ Gitea        │                   └──────────┬──────────────────────┘
│ duoqi-api    │        ──────▶               │
│ duoqi-api    │                   ┌──────────┴──────────┐
│ (prod+test)  │                   │                     │
│ Nginx        │              ┌────┴────┐          ┌─────┴────┐
└──────────────┘              │ API-1   │          │ API-2    │
                              │ (prod)  │          │ (prod)   │
┌──────────────┐              └─────────┘          └──────────┘
│ RDS MySQL    │         ┌─────────────┐    ┌──────────────┐
│ duoqi_prod   │         │ CI/CD 服务器 │    │  测试服务器   │
│ duoqi_test   │         │ Gitea       │    │ duoqi-api    │
└──────────────┘         │ Harbor 镜像  │    │ (test)       │
                         └─────────────┘    └──────────────┘

扩展触发条件

当出现以下情况时,考虑从 Phase 1 升级到 Phase 2

指标 Phase 1 上限 Phase 2 起步
日活用户 ~1,000 >1,000
内存使用率 持续 >80%
CPU 使用率 持续 >70%
需要独立测试服务器
需要多实例高可用

私有镜像仓库

多服务器场景下需要镜像仓库统一分发:

方案一Gitea 内置 Container Registry推荐

# 在 Gitea 的 app.ini 中启用
[packages]
ENABLED = true
STORAGE_TYPE = local

# 推送镜像
docker build -t your-server-ip:3200/admin/duoqi-api:latest .
docker push your-server-ip:3200/admin/duoqi-api:latest

方案二Harbor大规模场景

# docker-compose.harbor.yml
# 需要独立服务器或至少 4GB 内存
services:
  harbor:
    image: goharbor/harbor:latest
    # ... Harbor 企业级镜像仓库

负载均衡与水平扩展

# Phase 2: 多实例 docker-compose.yml
# API 服务器上的配置

services:
  api:
    image: your-registry/duoqi-api:latest
    network_mode: host
    deploy:
      replicas: 2       # 运行 2 个实例
      resources:
        limits:
          cpus: '1'
          memory: 512M
    env_file: .env.prod
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 15s
      timeout: 5s
      retries: 3

阿里云 SLB 配置:

1. 创建负载均衡实例
2. 添加后端服务器API-1, API-2
3. 配置健康检查路径:/health
4. 配置 SSL 证书
5. 域名 DNS 解析到 SLB 公网 IP

Phase 2 CI/CD 流程

推送代码 → Gitea Actions → 构建 + 推送到私有镜像仓库
                                    │
                    ┌───────────────┼───────────────┐
                    ▼               ▼               ▼
              部署到测试服务器   部署到 API-1    部署到 API-2
              (自动)            (手动/灰度)      (手动/灰度)

运维管理

日常维护命令

# 查看所有容器状态
docker compose ps

# 查看生产日志
docker compose logs -f api-prod

# 查看测试日志
docker compose --profile test logs -f api-test

# 重启生产环境
docker compose restart api-prod

# 进入容器调试
docker compose exec api-prod sh

# 查看资源使用
docker stats --no-stream

数据库管理

# 测试库重置(清空测试数据)
mysql -h your-rds-endpoint -u duoqi_test -p -e "DROP DATABASE duoqi_test; CREATE DATABASE duoqi_test CHARACTER SET utf8mb4;"

# 执行迁移(从本地或 CI 中执行,容器内无 drizzle-kit
# 详见 docs/database-migration-guide.md
DATABASE_URL=mysql://duoqi_prod:password@rm-xxxxx:3306/duoqi_prod bun run db:migrate

# 导入种子数据到测试库(首次部署时从本地执行)
DATABASE_URL=mysql://duoqi_test:password@rm-xxxxx:3306/duoqi_test bun run db:seed

# 备份生产库
mysqldump -h your-rds-endpoint -u duoqi_prod -p duoqi_prod > /opt/backups/duoqi_prod_$(date +%Y%m%d).sql

备份策略

# 创建备份脚本
cat > /opt/backups/backup.sh << 'SCRIPT'
#!/bin/bash
set -e

BACKUP_DIR="/opt/backups"
DATE=$(date +%Y%m%d_%H%M%S)

# 数据库备份
mysqldump -h your-rds-endpoint -u duoqi_prod -pyour-password duoqi_prod \
  | gzip > "$BACKUP_DIR/db_prod_$DATE.sql.gz"

# Gitea 仓库备份
sudo -u git gitea dump -c /etc/gitea/app.ini
# 备份文件生成在当前目录gitea-dump-*.zip

# 保留最近 7 天的备份
find $BACKUP_DIR -name "*.gz" -mtime +7 -delete

echo "[$DATE] Backup completed"
SCRIPT

chmod +x /opt/backups/backup.sh

# 定时任务(每天凌晨 2 点)
echo "0 2 * * * /opt/backups/backup.sh >> /opt/backups/backup.log 2>&1" | crontab -

故障排查

常见问题

1. 内存不足

# 查看内存使用
free -h

# 停止测试环境释放内存
docker compose --profile test stop api-test

# 清理 Docker 缓存
docker system prune -f

# 查看 Docker 磁盘占用
docker system df

2. 容器启动失败

# 查看退出日志
docker compose logs api-prod

# 检查环境变量
docker compose config

# 检查端口冲突
netstat -tlnp | grep -E '3000|3001'

3. Gitea Runner 不可用

# 检查 Runner 状态
systemctl status act-runner

# 重启 Runner
systemctl restart act-runner

# 查看日志
journalctl -u act-runner -f

4. CI Action 拉取失败(国内网络)

# 检查 github_mirror 配置
grep github_mirror /opt/act-runner/config.yaml
# 应输出github_mirror: 'https://gitea.com'

# 清除缓存的 action修复损坏的缓存
rm -rf /root/.cache/act/

# 重启 Runner
systemctl restart act-runner

5. CI checkout 失败ECONNREFUSED 或 404

# 检查容器网络配置
grep -A2 "network:" /opt/act-runner/config.yaml
# 应包含network: "host"

# 确认 Gitea API 可达
curl http://localhost:3200/api/v1/repos/search?q=duoqi-api

6. 数据库连接失败

# 从服务器测试 RDS 连通性
mysql -h your-rds-endpoint -u duoqi_prod -p -e "SELECT 1;"

# 检查 RDS 白名单是否包含服务器 IP
# 阿里云 RDS 控制台 → 数据安全性 → 白名单设置

7. Docker bridge 网络与 VPC 内网冲突

症状:服务器无法连接 RDS或 VPC 内其他服务ping 显示源 IP 为 172.x.0.1Docker 网桥 IP而非宿主机内网 IP。

# 查看路由表,检查是否有 Docker 网桥路由劫持了 VPC 流量
route -n
# 如果看到类似以下行,说明 Docker bridge 子网与 RDS IP 段冲突:
# 172.23.0.0   0.0.0.0   255.255.0.0   U   0   0   0 br-xxxxxx

# 查看哪个 Docker 网络创建了冲突子网
docker network ls
docker network inspect <network-name>

# 解决方案 1使用 network_mode: host推荐已在本方案中采用
# 在 docker-compose.yml 中为服务添加 network_mode: host
# 这样不会创建 Docker bridge 网络,从根本上避免 IP 段冲突

# 解决方案 2指定不冲突的子网
# docker network create --subnet=192.168.200.0/24 my-network

8. CI health check 失败

# 检查 health 路径是否正确health 路由注册时无 /v1 前缀)
# 正确:/health    错误:/v1/health

# 检查容器是否在运行
docker ps -a --filter name=duoqi-api-prod

# 检查端口是否在监听host 网络模式下直接看宿主机端口)
ss -tlnp | grep 3000

# 查看容器日志
docker logs duoqi-api-prod --tail 50

# 注意CI health check 使用 bun -e "fetch(...)" 而非 curl
# 如果 bun 的 promise 处理有问题,使用 top-level await
# bun -e "try{const r=await fetch('http://localhost:3000/health');process.exit(r.ok?0:1)}catch{process.exit(1)}"

回滚操作

# 查看本地镜像历史
docker images | grep duoqi-api

# 回滚到上一个版本
cd /opt/duoqi-api
docker compose down api-prod
# 修改 docker-compose.yml 使用指定版本镜像
docker compose up -d api-prod

附录

A. 完整目录结构(服务器端)

/opt/
├── gitea/                        # Gitea 代码托管
│   ├── docker-compose.yml
│   └── data/                     # Gitea 数据(仓库、配置)
├── act-runner/                   # Act Runner CI/CD 执行器
│   ├── config.yaml               # Runner 配置(网络、镜像源等)
│   └── .runner                   # 注册数据
├── runner-image/                 # Runner 自定义镜像
│   └── Dockerfile                # 基于 oven/bun + git
├── duoqi-api/                    # 应用部署
│   ├── docker-compose.yml        # 包含 prod + test 配置
│   ├── .env.prod                 # 生产环境变量
│   ├── .env.test                 # 测试环境变量
│   ├── Dockerfile                # 镜像构建
│   └── repo/                     # Git 仓库(用于构建)
├── backups/                      # 备份目录
│   ├── backup.sh
│   └── *.sql.gz
└── scripts/                      # 运维脚本
    └── deploy.sh

B. Gitea Actions 与 GitHub Actions 语法对照

特性 GitHub Actions Gitea Actions
文件路径 .github/workflows/ .gitea/workflows/
语法 YAML 相同(完全兼容)
触发条件 on: push 相同
Runner GitHub 托管 自托管 Act Runner
镜像 runs-on: ubuntu-latest 相同,或自定义标签

C. 安全建议

项目 建议
SSH 禁用密码登录,仅密钥认证
防火墙 firewalld 仅开放 22/80/443后端端口全部绑定 localhost
测试环境 通过 Nginx 限制访问 IP
数据库 生产用户和测试用户严格分离权限
JWT 生产与测试使用不同密钥
SSL 生产环境必须启用 HTTPS
内核更新 Alibaba Cloud Linux 安全补丁自动推送,及时更新

D. Ubuntu 与 Alibaba Cloud Linux 命令速查

操作 Ubuntu (apt) Alibaba Cloud Linux (dnf)
更新系统 apt update && apt upgrade -y dnf update -y
安装软件 apt install -y <pkg> dnf install -y <pkg>
安装 Docker curl -fsSL https://get.docker.com | sh dnf install -y docker
防火墙 ufw allow 80/tcp firewall-cmd --permanent --add-port=80/tcp && firewall-cmd --reload
创建用户 adduser --system --group git groupadd --system git && useradd --system --gid git git
Nginx 配置 /etc/nginx/sites-available/ /etc/nginx/conf.d/

文档版本: v5.3.0 (host 网络模式 + Act Runner 挂载配置 + 故障排查更新) 最后更新: 2026-04-18 维护者: Duoqi Team