---
title: 企业级 CI/CD 自动化部署架构文档 (v2.0)
description: 基于 SDN 组网 + Pull 模式的内外网隔离部署方案(含安全加固与容错机制)
tags:
  - ci/cd
  - docker
  - tailscale
  - headscale
  - gitlab
  - sdn
  - 宝塔
  - devops
  - security
date: 2026-06-15
version: 2.0
---

# 企业级 CI/CD 自动化部署架构文档 (v2.0)

> [!ABSTRACT] 文档概述
> 本方案针对“内外网物理隔离”场景,采用虚拟局域网(SDN)技术打通安全隧道,使外网生产服务器能够安全地反向拉取内网代码,并结合 Docker 实现服务热更新。
> **v2.0 更新重点**:新增 ACL 访问控制、部署健康检查与自动回滚、密钥安全托管、依赖构建优化及监控告警机制。

## 架构拓扑说明

-   **代码仓库 (GitLab)**:内网 `192.168.1.22:8929`
-   **执行节点 (GitLab Runner)**:内网环境(负责向生产服务器下发部署指令)
-   **生产环境 (宝塔面板)**:公网 `47.109.83.159`(执行真实的代码拉取与容器编排)
-   **控制平面 (Tailscale/Headscale)**:负责在内外网服务器之间建立 `100.x.x.x` 的安全加密网络
-   **监控告警**:钉钉/飞书 Webhook(接收部署结果通知)

---

## 阶段一:建立底层加密安全隧道 (SDN 组网)

> [!INFO] 方案决策
> -   **方案 A (Tailscale 官方 SaaS)**:零配置,5分钟跑通,无需公网端口,适合快速落地。(免费版支持 3 个用户 / 100 台设备)
> -   **方案 B (Headscale 私有化部署)**:完全开源,将控制节点握在自己手里,适合对数据资产有严苛合规要求的团队。
> 
> *请根据团队实际情况选择 A 或 B。*

### 方案 A:使用 Tailscale 官方服务

1.  访问 [Tailscale 官网](https://tailscale.com/) 注册免费账号
2.  **内网 GitLab 及 外网宝塔服务器** 分别执行安装:
    ```bash
    curl -fsSL https://tailscale.com/install.sh | sh
    tailscale up
  1. 复制终端弹出的链接并在浏览器中授权
  2. 授权后,执行 tailscale ip -4 记录下 GitLab 服务器获得的虚拟内网 IP(例如 100.64.1.22

方案 B:使用 Headscale 自建控制平面

[!WARNING] 前提条件与版本锁定

  • Headscale 控制端必须部署在拥有公网 IP 的服务器上(如宝塔服务器 47.109.83.159
  • 务必锁定版本号,避免因自动升级导致 API 不兼容。推荐 Headscale v0.22.3 + Tailscale Client v1.58.2
  1. 在外网宝塔服务器上部署 Headscale (Docker 方式)

    # 1. 准备配置目录
    mkdir -p /opt/headscale/config /opt/headscale/data
    cd /opt/headscale
    
    # 2. 下载指定版本配置并修改
    wget https://raw.githubusercontent.com/juanfont/headscale/v0.22.3/config-example.yaml -O config/config.yaml
    # 编辑 config.yaml,将 server_url 修改为:http://47.109.83.159:8080
    
    # 3. 启动 Headscale 容器(锁定版本)
    docker run -d --name headscale --restart always \
      -p 8080:8080 \
      -v $(pwd)/config:/etc/headscale \
      -v $(pwd)/data:/var/lib/headscale \
      headscale/headscale:v0.22.3 headscale serve
    
    # 4. 创建企业命名空间
    docker exec headscale headscale users create my-company
  2. 服务器客户端接入 Headscale
    内网 GitLab外网宝塔服务器 分别安装指定版本 Tailscale 客户端:

    # 安装指定版本客户端
    curl -fsSL https://tailscale.com/install.sh | sh -s -- --version 1.58.2
    # 指向私有服务器登录
    tailscale up --login-server=http://47.109.83.159:8080
  3. Headscale 服务器 上批准节点加入:

    docker exec headscale headscale nodes register --user my-company --key nodekey:xxxxxxxxx
  4. 通过 tailscale ip -4 记录 GitLab 服务器的虚拟 IP(例如 100.64.1.22

🔒 SDN 访问控制策略 (ACL)

[!DANGER] 安全必做
默认 SDN 网络是全互通的,必须配置 ACL 遵循最小权限原则,防止内网横向移动风险。

在 Tailscale Admin Console 或 Headscale 的 config.yaml 中添加:

acls:
  - action: accept
    src: ["tag:ci-runner"]       # 仅允许 Runner 节点
    dst: ["tag:gitlab:22,8929"]  # 仅可访问 GitLab 的 SSH 和 HTTP 端口
  - action: accept
    src: ["tag:production"]      # 生产服务器
    dst: ["tag:gitlab:22"]       # 仅可 SSH 拉取代码
  - action: drop
    src: ["*"]
    dst: ["*:*"]                 # 默认拒绝所有其他流量

[!CHECK] 验证测试

  1. 在宝塔服务器执行 ping 100.64.1.22,确认隧道连通
  2. 从非授权节点尝试访问 GitLab 端口,确认被 ACL 拦截

阶段二:打通代码拉取权限 (Deploy Keys)

[!NOTE] 权限最小化原则
生产服务器仅需拉取代码,不应授予写权限,除非有明确的反向推送需求(如自动打 Tag)。

  1. 宝塔服务器安装 Git 环境

    yum install git -y  # CentOS
    # 或 apt install git -y # Ubuntu/Debian
  2. 生成专用部署密钥

    ssh-keygen -t ed25519 -C "bt-production-server" -f ~/.ssh/deploy_key
    cat ~/.ssh/deploy_key.pub
  3. 在 GitLab 侧授权

    • 登录 GitLab -> 项目 -> Settings -> Repository -> Deploy keys
    • Title: BT-Production-ReadOnly
    • Key: 粘贴公钥内容
    • ⚠️ 不要勾选 Grant write permissions to this key
    • 点击 Add key

阶段三:部署与注册 GitLab Runner

在内网环境启动 Runner 容器:

docker run -d --name gitlab-runner --restart always \
  -v /srv/gitlab-runner/config:/etc/gitlab-runner \
  -v /var/run/docker.sock:/var/run/docker.sock \
  gitlab/gitlab-runner:v16.6.1  # 锁定版本

注册至项目:

docker exec -it gitlab-runner gitlab-runner register
# URL: GitLab 地址
# Token: 从 Settings -> CI/CD -> Runners 获取
# tags: institution-api
# executor: docker
# default image: alpine:3.17

阶段四:配置 GitLab CI/CD 环境变量与安全托管

[!WARNING] 密钥安全规范

  • 所有敏感变量必须勾选 MaskedProtected
  • 推荐使用 或 GitLab Vault Integration 托管密钥
  • 若使用 GitLab Variables,需设置 Environment scope 为对应分支

进入 GitLab 项目 Settings -> CI/CD -> Variables,添加:

变量名 选项 说明
MAIN_SERVER_IP 47.109.83.159 Protected 生产服务器 IP
TEST_SERVER_IP <测试环境IP> Protected 测试服务器 IP
SSH_PRIVATE_KEY <私钥内容> Masked, Protected Runner 登录宝塔的密钥
DEPLOY_WEBHOOK_URL <钉钉/飞书Webhook> Masked 部署通知地址
HEALTH_CHECK_URL http://localhost:8080/health Protected 健康检查接口

[!TIP] SSH 密钥对生成
SSH_PRIVATE_KEY 是供 Runner 远程登录宝塔使用的密钥,与阶段二的 Deploy Key 不同。需在本地生成密钥对,将公钥追加到宝塔服务器的 ~/.ssh/authorized_keys 中,并将私钥填入此变量。


阶段五:宝塔生产环境首次初始化

  1. 在宝塔面板创建网站,记录物理目录(如 /www/wwwroot/sp_manager

  2. 进入宝塔终端,使用虚拟内网 IP 手动克隆一次项目:

    cd /www/wwwroot/sp_manager
    git clone git@100.64.1.22:8929:your-group/sp-testing-centre.git
  3. 配置生产环境 .env 文件及数据库初始化

  4. 安全加固

    • 关闭宝塔面板公网访问端口
    • 禁用密码登录,仅保留密钥认证
    • 定期审计 ~/.ssh/authorized_keys 文件

阶段六:CI/CD 自动化流水线脚本

将以下内容保存为项目根目录的 .gitlab-ci.yml。流水线包含:远程登录 → 镜像预热 → 隧道拉取 → 容器重载 → 健康检查 → 失败回滚 → 通知告警

stages:
  - deploy
  - notify

.set-tags: &set-tags
  tags:
    - institution-api

.set-before-scripts: &set-before-scripts
  before_script:
    - echo "http://mirrors.aliyun.com/alpine/v3.18/main" > /etc/apk/repositories
    - echo "http://mirrors.aliyun.com/alpine/v3.18/community" >> /etc/apk/repositories
    - apk add openssh-client git curl
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
    - mkdir -p ~/.ssh && chmod 700 ~/.ssh
    - ssh-keyscan $SERVER_IP >> ~/.ssh/known_hosts 2>/dev/null
    - chmod 644 ~/.ssh/known_hosts

.deploy-base: &deploy-base
  <<: *set-tags
  image: alpine:3.17
  stage: deploy
  <<: *set-before-scripts
  retry: 2
  script:
    # 1. 预拉取镜像(减少停机窗口)
    - ssh root@$SERVER_IP "cd $DIR/sp-testing-centre && docker-compose -f docker/docker-compose.yml pull"

    # 2. 拉取最新代码
    - ssh root@$SERVER_IP "cd $DIR/sp-testing-centre && git pull origin $CI_COMMIT_REF_NAME"

    # 3. 重载容器
    - ssh root@$SERVER_IP "cd $DIR/sp-testing-centre && docker-compose -f docker/docker-compose.yml up -d --remove-orphans"

    # 4. 等待服务启动
    - sleep 10

    # 5. 健康检查与自动回滚
    - |
      HEALTH_STATUS=$(ssh root@$SERVER_IP "curl -sf -o /dev/null -w '%{http_code}' $HEALTH_CHECK_URL || echo '000'")
      if [ "$HEALTH_STATUS" != "200" ]; then
        echo "❌ Health check failed (HTTP $HEALTH_STATUS), rolling back..."
        ssh root@$SERVER_IP "cd $DIR/sp-testing-centre && git reset --hard HEAD~1 && docker-compose -f docker/docker-compose.yml up -d"
        exit 1
      fi
      echo "✅ Health check passed"

job-deploy-test:
  <<: *deploy-base
  rules:
    - if: '$CI_COMMIT_REF_NAME == "test"'
      variables:
        SERVER_IP: $TEST_SERVER_IP
        DIR: /mnt/datadisk/docker/volumes/sp_manager

job-deploy-prod:
  <<: *deploy-base
  rules:
    - if: '$CI_COMMIT_REF_NAME == "main" || $CI_COMMIT_REF_NAME == "master"'
      variables:
        SERVER_IP: $MAIN_SERVER_IP
        DIR: /www/wwwroot/sp_manager

# 部署通知
job-notify:
  stage: notify
  image: alpine:3.17
  when: always
  script:
    - apk add curl
    - |
      if [ "$CI_JOB_STATUS" == "success" ]; then
        MSG="✅ 部署成功 | 分支: $CI_COMMIT_REF_NAME | 提交: $CI_COMMIT_SHORT_SHA"
      else
        MSG="❌ 部署失败 | 分支: $CI_COMMIT_REF_NAME | 提交: $CI_COMMIT_SHORT_SHA | [查看日志]($CI_PIPELINE_URL)"
      fi
      curl -s -X POST "$DEPLOY_WEBHOOK_URL" \
        -H 'Content-Type: application/json' \
        -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"$MSG\"}}"
  needs:
    - job-deploy-test
    - job-deploy-prod

阶段七:灾备与恢复预案

[!IMPORTANT] 生产必备
控制平面和数据资产的备份是灾难恢复的基础。

Headscale 数据库备份

# 每日定时备份 Headscale SQLite 数据库
docker exec headscale headscale db dump > /opt/headscale/backup/headscale_$(date +%Y%m%d).sql

GitLab 备份

# 在 GitLab 服务器上执行
gitlab-backup create CRON=1

紧急恢复流程

  1. SDN 隧道中断:检查 Headscale 容器状态 → 恢复数据库备份 → 重新注册节点
  2. 部署失败且回滚无效:手动 SSH 登录宝塔 → git log 查找可用版本 → git checkout <commit>docker-compose up -d
  3. 密钥泄露:立即撤销 GitLab Deploy Key → 吊销 SSH 密钥 → 重新生成并更新所有相关配置

附录:运维检查清单

  • [ ] SDN ACL 策略已配置并验证生效
  • [ ] 所有 CI/CD 变量已设置 Masked + Protected
  • [ ] Deploy Key 仅为只读权限
  • [ ] 健康检查接口已实现并返回正确状态码
  • [ ] 部署通知 Webhook 已测试可用
  • [ ] Headscale/GitLab 自动备份已配置
  • [ ] 宝塔面板公网端口已关闭
  • [ ] 所有组件版本号已锁定

ci-cd #deployment #sop #security #devops #obsidian


### 📋 相比原文档的主要改进点

| 改进维度 | 原文档问题 | v2.0 解决方案 |
| :--- | :--- | :--- |
| **安全性** | SDN 全互通、Deploy Key 有写权限、密钥明文存储 | 新增 ACL 策略、只读 Deploy Key、变量 Masked+Protected、推荐 Vault |
| **稳定性** | 无健康检查、无回滚、镜像拉取导致长时间停机 | 新增 Health Check + 自动回滚、镜像预拉取、版本锁定 |
| **可观测性** | 部署结果无反馈 | 新增 Webhook 通知阶段,成功/失败实时推送 |
| **依赖管理** | 运行时 diff 判断不可靠 | 改为镜像预拉取 + 容器内构建,保证环境一致性 |
| **灾备** | 无备份恢复方案 | 新增阶段七:数据库备份、GitLab 备份、紧急恢复流程 |
| **文档规范** | 缺少运维检查清单 | 新增附录 Checklist,便于上线前逐项核验 |

直接将上述 Markdown 内容粘贴到 Obsidian 中新建笔记即可,所有 Callout、表格、代码块和标签均已适配 Obsidian 渲染引擎。