# CI/CD 部署文档 本文档描述基于 Drone CI + Docker + Harbor 私有镜像仓库(内网)的 CI/CD 流水线配置。其他项目可参考本文档搭建自己的 CI/CD 流程。 ## 一、整体架构 ``` 代码推送 (master) → Drone CI 触发 → 构建 Docker 镜像 → 推送内网 Harbor → SSH 部署到服务器 ``` | 组件 | 地址/说明 | |------|----------| | CI 平台 | Drone CI (`192.168.195.25:13080`) | | 镜像仓库 | Harbor (`192.168.195.25:19900`,HTTP) | | 部署方式 | Docker Compose | | 部署服务器 | `192.168.195.15`(可按项目分配不同服务器) | | 触发条件 | `master` 分支 push 事件 | ## 二、快速开始(新项目接入指南) ### 2.1 在 Harbor 创建项目 1. 访问 Harbor 管理界面:`http://192.168.195.25:19900` 2. 创建一个新项目,名称使用项目标识(如 `my-project`) 3. 设置为私有项目(推送需要认证) ### 2.2 在 Drone CI 激活仓库 1. 访问 Drone CI:`http://192.168.195.25:13080` 2. 使用 Gitea/Gogs 账号登录(Drone 与代码仓库联动) 3. 在仓库列表中找到你的项目,点击 **Activate** 4. 进入仓库设置 → **Secrets**,添加以下密钥: | Secret 名称 | 说明 | 示例值 | |-------------|------|--------| | `harbor_username` | Harbor 仓库用户名 | `admin` | | `harbor_password` | Harbor 仓库密码 | `Harbor12345` | | `ssh_username` | 部署服务器 SSH 用户名 | `root` | | `ssh_password` | 部署服务器 SSH 密码 | `your-password` | ### 2.3 编写 `.drone.yml` 在项目根目录创建 `.drone.yml`,参考下方模板。 ### 2.4 编写 Dockerfile 为每个需要构建的服务编写 Dockerfile。 ### 2.5 编写 `docker-compose.yml` 在部署服务器上创建 docker-compose 配置。 ### 2.6 推送代码触发流水线 推送代码到 `master` 分支,Drone 会自动触发构建和部署。 ## 三、`.drone.yml` 配置模板 ### 3.1 单服务项目模板 适用于只有一个服务需要构建和部署的项目: ```yaml --- kind: pipeline type: docker name: my-project # ← 改为你的项目名 trigger: branch: - master # ← 触发分支,可改为 main event: - push steps: # ==================== 构建并推送镜像 ==================== - name: build image: plugins/docker settings: registry: 192.168.195.25:19900 repo: 192.168.195.25:19900/my-project/app # ← 改为 Harbor项目名/镜像名 dockerfile: Dockerfile # ← Dockerfile 路径 context: . # ← 构建上下文目录 tags: - latest - ${DRONE_COMMIT_SHA:0:8} # commit SHA 前8位,用于回滚 username: from_secret: harbor_username password: from_secret: harbor_password insecure: true # Harbor 使用 HTTP 时必须 # ==================== 部署到服务器 ==================== - name: deploy image: appleboy/drone-ssh settings: host: 192.168.195.15 # ← 改为你的部署服务器 IP username: from_secret: ssh_username password: from_secret: ssh_password port: 22 script: - cd /disk/docker-compose/my-project # ← 改为服务器上的部署目录 - docker compose pull - docker compose up -d depends_on: - build ``` ### 3.2 多服务项目模板 适用于有多个服务(如 API + Admin)需要分别构建的项目: ```yaml --- kind: pipeline type: docker name: my-project trigger: branch: - master event: - push steps: # ==================== 构建服务 A ==================== - name: build-service-a image: plugins/docker settings: registry: 192.168.195.25:19900 repo: 192.168.195.25:19900/my-project/service-a dockerfile: src/ServiceA/Dockerfile context: src tags: - latest - ${DRONE_COMMIT_SHA:0:8} username: from_secret: harbor_username password: from_secret: harbor_password insecure: true # ==================== 构建服务 B ==================== - name: build-service-b image: plugins/docker settings: registry: 192.168.195.25:19900 repo: 192.168.195.25:19900/my-project/service-b dockerfile: src/ServiceB/Dockerfile context: src tags: - latest - ${DRONE_COMMIT_SHA:0:8} username: from_secret: harbor_username password: from_secret: harbor_password insecure: true # ==================== 部署到服务器 ==================== - name: deploy image: appleboy/drone-ssh settings: host: 192.168.195.15 username: from_secret: ssh_username password: from_secret: ssh_password port: 22 script: - cd /disk/docker-compose/my-project - docker compose pull - docker compose up -d depends_on: - build-service-a # 等待所有构建完成 - build-service-b ``` > 多个 build 步骤默认并行执行,deploy 通过 `depends_on` 等待全部完成后执行。 ## 四、MiAssessment 项目实际配置 本项目(学业邑规划)的实际配置如下,供参考: ### 4.1 构建的镜像 | 镜像 | 说明 | Dockerfile | 构建上下文 | |------|------|-----------|-----------| | `mi-assessment/api` | 小程序 API(.NET 10) | `server/MiAssessment/src/MiAssessment.Api/Dockerfile` | `server/MiAssessment` | | `mi-assessment/admin` | 后台管理 API + 前端(.NET 10,内含 admin-web 构建产物) | `server/MiAssessment/src/MiAssessment.Admin/Dockerfile` | `server/MiAssessment` | 每个镜像打两个标签:`latest` 和 commit SHA 前 8 位(用于回滚)。 ### 4.2 流水线步骤 `.drone.yml` 定义了 3 个步骤: 1. `build-api` — 构建小程序 API 镜像 2. `build-admin` — 构建后台管理 API 镜像(并行) 3. `deploy` — SSH 部署(等待前两步完成) ### 4.3 完整 `.drone.yml` ```yaml --- kind: pipeline type: docker name: mi-assessment trigger: branch: - master event: - push steps: - name: build-api image: plugins/docker settings: registry: 192.168.195.25:19900 repo: 192.168.195.25:19900/mi-assessment/api dockerfile: server/MiAssessment/src/MiAssessment.Api/Dockerfile context: server/MiAssessment tags: - latest - ${DRONE_COMMIT_SHA:0:8} username: from_secret: harbor_username password: from_secret: harbor_password insecure: true - name: build-admin image: plugins/docker settings: registry: 192.168.195.25:19900 repo: 192.168.195.25:19900/mi-assessment/admin dockerfile: server/MiAssessment/src/MiAssessment.Admin/Dockerfile context: server/MiAssessment tags: - latest - ${DRONE_COMMIT_SHA:0:8} username: from_secret: harbor_username password: from_secret: harbor_password insecure: true - name: deploy image: appleboy/drone-ssh settings: host: 192.168.195.15 username: from_secret: ssh_username password: from_secret: ssh_password port: 22 script: - cd /disk/docker-compose/mi-assessment - docker compose pull - docker compose up -d depends_on: - build-api - build-admin ``` ## 五、基础镜像管理 ### 5.1 为什么需要内网基础镜像 内网环境无法直接拉取 Docker Hub / MCR 的镜像,需要提前将基础镜像推送到内网 Harbor 的 `library` 项目中。 ### 5.2 推送基础镜像 在能访问外网的机器上执行: ```bash # 一键推送(如果项目提供了脚本) bash scripts/push-base-images.sh # 手动推送单个镜像 docker pull mcr.microsoft.com/dotnet/aspnet:10.0-preview docker tag mcr.microsoft.com/dotnet/aspnet:10.0-preview 192.168.195.25:19900/library/dotnet/aspnet:10.0-preview docker push 192.168.195.25:19900/library/dotnet/aspnet:10.0-preview docker tag mcr.microsoft.com/dotnet/sdk:8.0 192.168.195.25:19900/library/dotnet/sdk:8.0 docker pull mcr.microsoft.com/dotnet/sdk:8.0 mcr.microsoft.com/dotnet/sdk:8.0 192.168.195.25:19900/library/dotnet/sdk:8.0 docker push 192.168.195.25:19900/library/dotnet/sdk:8.0 mcr.microsoft.com/dotnet/aspnet:8.0.12 ``` ### 5.3 常用基础镜像 | 基础镜像 | 内网地址 | 用途 | |---------|---------|------| | `dotnet/aspnet:10.0-preview` | `192.168.195.25:19900/library/dotnet/aspnet:10.0-preview` | .NET 运行时 | | `dotnet/sdk:10.0-preview` | `192.168.195.25:19900/library/dotnet/sdk:10.0-preview` | .NET 构建 | | `node:20-alpine` | `192.168.195.25:19900/library/node:20-alpine` | Node.js 前端构建 | | `python:3.12-slim` | `192.168.195.25:19900/library/python:3.12-slim` | Python 项目 | | `golang:1.22-alpine` | `192.168.195.25:19900/library/golang:1.22-alpine` | Go 项目 | | `nginx:alpine` | `192.168.195.25:19900/library/nginx:alpine` | 静态文件服务 | ### 5.4 Dockerfile 中使用内网镜像 ```dockerfile # 使用内网 Harbor 的基础镜像 FROM 192.168.195.25:19900/library/node:20-alpine AS build WORKDIR /app COPY . . RUN npm install && npm run build FROM 192.168.195.25:19900/library/nginx:alpine COPY --from=build /app/dist /usr/share/nginx/html ``` ## 六、部署服务器配置 ### 6.1 安装 Docker 和 Docker Compose ```bash # CentOS / RHEL yum install -y docker-ce docker-compose-plugin # Ubuntu / Debian apt-get install -y docker-ce docker-compose-plugin # 启动 Docker systemctl enable docker systemctl start docker ``` ### 6.2 配置信任内网 Harbor(HTTP) 由于 Harbor 使用 HTTP 而非 HTTPS,需要配置 Docker 信任该仓库: ```bash # 编辑 /etc/docker/daemon.json { "insecure-registries": ["192.168.195.25:19900"] } # 重启 Docker systemctl restart docker ``` ### 6.3 登录 Harbor ```bash docker login 192.168.195.25:19900 # 输入用户名和密码 ``` ### 6.4 创建部署目录 ```bash mkdir -p /disk/docker-compose/my-project cd /disk/docker-compose/my-project ``` ### 6.5 编写 `docker-compose.yml` 示例(单服务): ```yaml version: "3.8" services: app: image: 192.168.195.25:19900/my-project/app:latest container_name: my-project-app ports: - "8080:8080" environment: - ASPNETCORE_ENVIRONMENT=Production restart: unless-stopped ``` 示例(多服务,参考 MiAssessment): ```yaml version: "3.8" services: api: image: 192.168.195.25:19900/mi-assessment/api:latest container_name: mi-assessment-api ports: - "5000:8080" environment: - ASPNETCORE_ENVIRONMENT=Production volumes: - ./appsettings.api.json:/app/appsettings.Production.json restart: unless-stopped admin: image: 192.168.195.25:19900/mi-assessment/admin:latest container_name: mi-assessment-admin ports: - "5001:8080" environment: - ASPNETCORE_ENVIRONMENT=Production volumes: - ./appsettings.admin.json:/app/appsettings.Production.json restart: unless-stopped ``` ## 七、Drone CI 配置详解 ### 7.1 关键字段说明 | 字段 | 说明 | |------|------| | `kind: pipeline` | 流水线类型 | | `type: docker` | 使用 Docker 执行器 | | `trigger.branch` | 触发分支 | | `trigger.event` | 触发事件(push / pull_request / tag) | | `settings.registry` | Harbor 仓库地址 | | `settings.repo` | 镜像完整路径(含 registry) | | `settings.dockerfile` | Dockerfile 相对路径 | | `settings.context` | Docker 构建上下文目录 | | `settings.tags` | 镜像标签列表 | | `settings.insecure` | 允许 HTTP 推送(Harbor 非 HTTPS 时必须) | | `from_secret` | 引用 Drone Secrets 中的密钥 | | `depends_on` | 步骤依赖,等待指定步骤完成后执行 | ### 7.2 常用 Drone 变量 | 变量 | 说明 | 示例值 | |------|------|--------| | `${DRONE_COMMIT_SHA}` | 完整 commit SHA | `a1b2c3d4e5f6...` | | `${DRONE_COMMIT_SHA:0:8}` | commit SHA 前 8 位 | `a1b2c3d4` | | `${DRONE_BRANCH}` | 当前分支名 | `master` | | `${DRONE_TAG}` | Git 标签(tag 事件时) | `v1.0.0` | | `${DRONE_BUILD_NUMBER}` | 构建编号 | `42` | | `${DRONE_REPO_NAME}` | 仓库名 | `mi-assessment` | ### 7.3 高级配置示例 #### 按分支部署到不同环境 ```yaml --- kind: pipeline type: docker name: deploy-staging trigger: branch: - develop event: - push steps: - name: build image: plugins/docker settings: registry: 192.168.195.25:19900 repo: 192.168.195.25:19900/my-project/app tags: - staging - ${DRONE_COMMIT_SHA:0:8} # ... 其他配置同上 - name: deploy-staging image: appleboy/drone-ssh settings: host: 192.168.195.20 # 测试服务器 username: from_secret: ssh_username password: from_secret: ssh_password port: 22 script: - cd /disk/docker-compose/my-project-staging - docker compose pull - docker compose up -d depends_on: - build --- kind: pipeline type: docker name: deploy-production trigger: branch: - master event: - push steps: - name: build image: plugins/docker settings: registry: 192.168.195.25:19900 repo: 192.168.195.25:19900/my-project/app tags: - latest - ${DRONE_COMMIT_SHA:0:8} # ... 其他配置同上 - name: deploy-production image: appleboy/drone-ssh settings: host: 192.168.195.15 # 生产服务器 # ... 其他配置同上 ``` #### 添加构建通知(钉钉/企业微信) ```yaml - name: notify image: plugins/webhook settings: urls: https://oapi.dingtalk.com/robot/send?access_token=xxx content_type: application/json template: | { "msgtype": "text", "text": { "content": "✅ {{repo.name}} 部署成功\n分支: {{build.branch}}\n提交: {{build.commit}}" } } depends_on: - deploy when: status: - success ``` ## 八、新项目接入清单 按以下清单逐项完成,即可为新项目接入 CI/CD: | # | 步骤 | 操作位置 | 说明 | |---|------|---------|------| | 1 | 创建 Harbor 项目 | Harbor Web UI | 项目名与代码仓库名一致 | | 2 | 推送基础镜像 | 外网机器 | 将 Dockerfile 中用到的基础镜像推送到 `library` | | 3 | 编写 Dockerfile | 代码仓库 | 使用内网基础镜像地址 | | 4 | 编写 `.drone.yml` | 代码仓库根目录 | 参考上方模板 | | 5 | 激活 Drone 仓库 | Drone Web UI | 点击 Activate | | 6 | 配置 Drone Secrets | Drone Web UI | 添加 harbor / ssh 凭证 | | 7 | 创建部署目录 | 部署服务器 | `/disk/docker-compose/{项目名}` | | 8 | 编写 `docker-compose.yml` | 部署服务器 | 配置镜像、端口、环境变量 | | 9 | 配置 `insecure-registries` | 部署服务器 | Docker daemon 信任 Harbor | | 10 | 推送代码触发 | 代码仓库 | push 到 master 分支 | ## 九、常见问题 ### Q: 构建时拉取基础镜像失败? 基础镜像需要提前推送到内网 Harbor。Drone Runner 的 Docker daemon 也需要配置 `insecure-registries`。`.drone.yml` 中的 `insecure: true` 只处理推送,Dockerfile 中 `FROM` 拉取需要 runner 层面配置。 ### Q: Harbor 认证失败(unauthorized)? 检查 Drone Secrets 中的 `harbor_username` / `harbor_password` 是否正确且未过期。 ### Q: 部署后服务没更新? 1. 检查 `docker compose pull` 是否拉到了新镜像 2. 确认服务器 Docker 已配置 `insecure-registries` 3. 检查容器状态:`docker compose ps` / `docker compose logs` ### Q: 如何回滚到指定版本? 使用 commit SHA 标签: ```bash cd /disk/docker-compose/my-project # 修改 docker-compose.yml 中的镜像标签 # 将 :latest 改为 :a1b2c3d4(对应 commit SHA 前8位) # 然后重新部署 docker compose up -d ``` ### Q: 如何手动触发构建? 在 Drone 界面找到对应仓库,点击 **New Build**,选择分支即可。 ### Q: 如何只部署不重新构建? 直接在服务器上操作: ```bash cd /disk/docker-compose/my-project docker compose pull docker compose up -d ``` ### Q: 构建步骤超时? 在 `.drone.yml` 的 step 中添加超时配置(Drone 默认 60 分钟): ```yaml - name: build image: plugins/docker settings: # ... # Drone 2.x 不直接支持 step 级别 timeout, # 可在仓库设置中调整全局超时时间 ``` ### Q: 如何查看构建日志? 访问 Drone CI 界面 `http://192.168.195.25:13080`,找到对应仓库和构建编号,点击查看每个步骤的日志。