duoqi-api/docs/ci-deployment-guide.md
Wang Zhuoxuan 567613aa6b docs: 更新 CI 部署文档 v5.1.0 → v5.2.0
主要更新:
- Runner 镜像:添加 Docker CLI,固定 Bun 版本为 1.3
- systemd 服务:添加 Docker socket 挂载配置
- CI 流程:移除覆盖率相关描述(只运行单元测试)
- 关键设计说明:更新 Runner 镜像和 Docker socket 挂载说明
2026-04-17 16:33:35 +08:00

1119 lines
35 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Duoqi API 部署与持续集成方案
> 基于 Gitea + 阿里云轻量应用服务器 (Alibaba Cloud Linux 3) 的私有化部署方案
## 目录
- [架构概览](#架构概览)
- [Phase 1: 单服务器方案(当前)](#phase-1-单服务器方案当前)
- [资源规划](#资源规划)
- [服务器初始化](#服务器初始化)
- [Gitea 安装与配置](#gitea-安装与配置)
- [环境隔离策略](#环境隔离策略)
- [CI/CD 流程](#cicd-流程)
- [部署操作](#部署操作)
- [Nginx 配置](#nginx-配置)
- [Phase 2: 多服务器扩展(未来)](#phase-2-多服务器扩展未来)
- [架构演进](#架构演进)
- [私有镜像仓库](#私有镜像仓库)
- [负载均衡与水平扩展](#负载均衡与水平扩展)
- [运维管理](#运维管理)
- [故障排查](#故障排查)
- [附录](#附录)
---
## 架构概览
### 为什么选择 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 兼容性更好)
```bash
# 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 都有意义。
```bash
# 下载 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`。
```bash
# 编辑 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 部署(备选)
```bash
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需要构建自定义镜像
> - **git**`actions/checkout` 需要,否则会回退到 REST APIGitHub 与 Gitea URL 格式不兼容)
> - **docker CLI**CI 中执行 `docker build` 等命令需要,通过 socket 连接宿主机 Docker daemon
> - **固定版本**:使用 Bun 1.3 而非 latest确保 CI 环境可复现
```bash
# 创建 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
```bash
# 下载 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 配置文件
```bash
# 生成默认配置
cd /opt/act-runner && act_runner generate-config > config.yaml
```
编辑 `/opt/act-runner/config.yaml`,修改以下关键配置:
```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"
# 不强制每次拉取镜像(国内网络下减少失败风险)
force_pull: false
```
##### 4. 创建 systemd 服务并启动
```bash
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
# 关键:挂载 Docker socket让 job 容器能访问宿主机的 Docker daemon
BindPaths=/var/run/docker.sock:/var/run/docker.sock
[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
```
### 环境隔离策略
#### RDS 数据库隔离
```sql
-- 连接到 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;
```
#### 环境配置文件
**密钥生成**(在配置环境变量之前):
```bash
# 生成 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`
```env
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`
```env
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`
```yaml
version: '3.8'
services:
# ===== 生产环境 =====
api-prod:
build:
context: /opt/gitea/data/git/repositories/admin/duoqi-api.git
dockerfile: Dockerfile
image: duoqi-api:prod
container_name: duoqi-api-prod
restart: unless-stopped
env_file: .env.prod
ports:
- "3000:3000"
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
# ===== 测试环境(按需启停) =====
api-test:
build:
context: /opt/gitea/data/git/repositories/admin/duoqi-api.git
dockerfile: Dockerfile
image: duoqi-api:test
container_name: duoqi-api-test
restart: "no" # 不自动重启,手动控制
env_file: .env.test
ports:
- "3001:3001"
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 # 使用 profiles 按需启停
```
**启停命令:**
```bash
# 只启动生产环境(默认)
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 │ ← 手动确认后部署生产
└─────────────┘ └─────────────┘
```
#### 日常开发流程
```bash
# 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 仓库中手动创建:
> ```bash
> 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 的问题 |
| Runner 使用 `github_mirror` | 从 gitea.com 镜像拉取 Actions解决国内无法访问 GitHub 的问题 |
| 自定义 Runner 镜像bun + git + docker CLI | 避免 checkout REST API 不兼容,支持 CI 中执行 docker 命令 |
| Runner 挂载 Docker socket | Job 容器通过 socket 访问宿主机 Docker daemon执行构建操作 |
| 固定 Bun 版本1.3 | 确保 CI 环境可复现,避免 latest 版本变化导致意外失败 |
### 部署操作
#### 手动部署(首次或紧急)
```bash
# 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
# 部署到生产环境
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。
```bash
# 生产环境 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
```bash
# 为三个域名统一申请证书
certbot --nginx -d api.duoqi.me -d test-api.duoqi.me -d git.duoqi.me
# 自动续期验证
certbot renew --dry-run
```
> [!NOTE]
>
> 在子域名还没有配置解析的前提下,会影响证书的签发。后期如果新增子域名,并且需要同一张证书时,可以通过`--expand`参数进行申请。
```bash
# 方案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
```
##### 常见错误
```bash
# ❌ 错误:只写新增域名,会导致原有域名被移除!
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大规模场景**
```yaml
# docker-compose.harbor.yml
# 需要独立服务器或至少 4GB 内存
services:
harbor:
image: goharbor/harbor:latest
# ... Harbor 企业级镜像仓库
```
### 负载均衡与水平扩展
```yaml
# Phase 2: 多实例 docker-compose.yml
# API 服务器上的配置
version: '3.8'
services:
api:
image: your-registry/duoqi-api:latest
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
(自动) (手动/灰度) (手动/灰度)
```
---
## 运维管理
### 日常维护命令
```bash
# 查看所有容器状态
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
```
### 数据库管理
```bash
# 测试库重置(清空测试数据)
mysql -h your-rds-endpoint -u duoqi_test -p -e "DROP DATABASE duoqi_test; CREATE DATABASE duoqi_test CHARACTER SET utf8mb4;"
# 执行迁移
docker compose exec api-prod npx drizzle-kit migrate
# 导入种子数据到测试库
docker compose --profile test exec api-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
```
### 备份策略
```bash
# 创建备份脚本
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. 内存不足
```bash
# 查看内存使用
free -h
# 停止测试环境释放内存
docker compose --profile test stop api-test
# 清理 Docker 缓存
docker system prune -f
# 查看 Docker 磁盘占用
docker system df
```
#### 2. 容器启动失败
```bash
# 查看退出日志
docker compose logs api-prod
# 检查环境变量
docker compose config
# 检查端口冲突
netstat -tlnp | grep -E '3000|3001'
```
#### 3. Gitea Runner 不可用
```bash
# 检查 Runner 状态
systemctl status act-runner
# 重启 Runner
systemctl restart act-runner
# 查看日志
journalctl -u act-runner -f
```
#### 4. CI Action 拉取失败(国内网络)
```bash
# 检查 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
```bash
# 检查容器网络配置
grep -A2 "network:" /opt/act-runner/config.yaml
# 应包含network: "host"
# 确认 Gitea API 可达
curl http://localhost:3200/api/v1/repos/search?q=duoqi-api
```
#### 6. 数据库连接失败
```bash
# 从服务器测试 RDS 连通性
mysql -h your-rds-endpoint -u duoqi_prod -p -e "SELECT 1;"
# 检查 RDS 白名单是否包含服务器 IP
# 阿里云 RDS 控制台 → 数据安全性 → 白名单设置
```
### 回滚操作
```bash
# 查看本地镜像历史
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.2.0 (双分支工作流 + 国内网络适配 + Docker 执行器完善)
**最后更新**: 2026-04-17
**维护者**: Duoqi Team