×

WebAPI 项目通过 CI/CD 自动化部署到 Linux 服务器(docker-compose)

独孤求败 独孤求败 发表于2026-02-11 10:09:24 浏览16 评论0

抢沙发发表评论

〇、前言

本文先列举了一个简单的示例项目,然后通过 CI/CD 的方式,将私有镜像库 Harbor 中的镜像,发布到 Linux 中的 Docker 服务。

并且简单介绍了,配置自动发布的过程所涉及的一些概念和配置点,很多设计私有镜像库和私有域名都做了适当处理,仅供参考。如有疑问,欢迎友好沟通。


一、准备一个示例项目

1.1 创建一个 Web API 示例项目

名称例如:Test.WebAPI.Net8.Second,项目配置如下图。主要就是启用容器支持。

图片.png

如果在创建项目的时候没有勾选启用容器支持,也可以手动新增一个 Dockerfile 文件。

如下,是 Dockerfile 文件的内容:

图片.png

1.2 在项目主目录下添加gitlab-ci.yml文件

文件内容例如:

图片.png

图片.png

这样一个简单的示例项目就准备好了。


1.3 将项目上传至 gitlab

在 gitlab 上创建一个新的 blank 项目,名字例如:test-dotnet8。

注意:不要勾选“Initialize repository with a README”(因为已有本地代码)。若勾选了 “Initialize repository with a README”,GitLab 会自动创建一个包含 README.md 文件的初始提交(即远程仓库非空)。此时如果直接尝试推送本地代码,会遇到冲突(因为本地和远程历史不一致)。

图片.png

进入到项目主目录后,打开 PowerShell,输入 cmd 回车。接着执行下面命令来上传代码。

图片.png

二、Linux 环境准备(CentOS 7)

2.1 查看当前系统的版本

不清楚当前系统版本的话,可以通过命令lsb_release -a来查看,因为不同版本的系统使用的相关命令可能不同,本文示例均基于 CentOS 7。

图片.png

2.2 安装 Docker 并添加 deploy 用户

图片.png

2.3 安装 docker-compose

图片.png

这说明 salt 用户没有权限访问 Docker 守护进程,需要将 salt 用户加入 docker 组。


三、CI/CD 脚本以及触发

3.1 关于 docker-compose.yaml

用一个 YAML 文件描述整个应用的服务、网络、卷、环境变量等,通过一条命令(如 docker-compose up)一键启动/停止整套服务。

示例:

图片.png

优势说明
简化部署无需手动敲 docker run ... 多条复杂命令
环境一致性开发、测试、生产使用同一份配置
服务编排自动处理依赖顺序(如先启 DB 再启 Web)
版本控制友好YAML 文件可纳入 Git 管理
一键启停docker-compose up -d 启动全部,down 停止并清理
常用命令作用
docker-compose up创建并启动所有服务(前台运行)
docker-compose up -d后台启动(守护模式)
docker-compose down停止并删除容器、网络(默认不删卷)
docker-compose pull拉取所有服务的新镜像
docker-compose logs -f web查看 web 服务实时日志

注意:现代 Docker 已内置 Compose 插件,也可用 docker compose(空格)代替 docker-compose(连字符)。若当前环境仍为 docker-compose 命令,则可以创建软链接,见本文 2.3。


3.2 配置 Runner

图片.png

图片.png

在注册 GitLab Runner 时选择 --executor "docker",并且服务器上已安装 Docker,Runner 就会在每次 CI/CD 任务中启动一个临时的 Docker 容器来执行 .gitlab-ci.yml 中定义的命令(比如 dotnet build、docker build 等)。这种方式比 shell 更安全、更干净(每次构建环境隔离),是官方推荐做法。

图片.png

图片.png

3.3 CI 和 CD 推荐使用不同的 Runner

在技术层面,两个步骤是可以共用同一个 Runner。Runner 本质是任务执行器,只要它具备运行 CI 和 CD 所需的工具、权限和网络访问能力,一个 Runner 完全可以同时执行 CI 和 CD 的 job。大多数 CI/CD 系统(如 GitLab CI、GitHub Actions)并不强制区分 CI Runner 和 CD Runner。

CI(持续集成)和 CD(持续部署/交付)是否需要使用两个不同的 Runner,不是强制要求,但在很多实际场景中推荐分开。以下是几个简单的原因:

原因说明
安全隔离CD 阶段通常涉及生产环境密钥、部署权限。若 CI Runner 被污染(如运行了恶意 PR),可能危及生产系统。
权限最小化CI Runner 只需编译/测试权限;CD Runner 需要访问生产 API、K8s 集群等。遵循“最小权限原则”。
网络隔离生产部署机器可能位于内网或 DMZ 区,而 CI 构建可在公网或开发网络完成。
环境差异CI 可能在干净容器中运行;CD 可能需要特定工具(如 kubectlaws-cli、Ansible)。
审计与追踪分开后更容易监控“谁部署了什么”,符合合规要求(如 ISO 27001、SOC2)。

常见的场景和建议使用的 Runner 个数:

场景是否需要多个 Runner说明
单一语言项目(如纯 .net)1 个即可只需一个能运行 .net 的环境
多平台构建(Windows + Linux + macOS)≥3 个每个平台通常需要独立的 Runner
多语言项目(前端 + 后端 + 移动端)≥2~3 个如 Node.js、.net、Swift 环境隔离
安全隔离(如生产部署 vs 测试)≥2 个避免敏感操作在通用 Runner 上执行
并行任务加速多个相同类型 Runner提高并发能力,但类型可能相同

最佳实践:即使初期共用,也要在架构上预留分离能力(如通过 tags 控制),随着项目成熟逐步隔离。


3.4 触发 Pipeline 执行

常见的触发条件有:

图片.png

3.5 如果修改了 docker-compose.yml 中的 service 名称

若在 docker-compose.yml 中 修改了 service 名称(例如从 web 改为 api),Docker Compose 会将其视为 一个全新的服务,而旧的服务会被认为是“孤儿”(orphaned)。如果不正确处理,会导致:容器残留(旧服务还在运行)、网络/卷冲突、资源未释放、部署失败(如你之前遇到的 “has active endpoints” 错误)。

推荐使用: --remove-orphans 方式,这是最简单、最安全的方式。

它可以:启动新 service(new_service);自动停止并删除不再出现在 compose 文件中的旧 service(old_service);保留数据卷(除非你额外加 --volumes)。

如下示例,先进入项目主目录,再使用 docker-compose 命令:

图片.png

命令执行成功后,再重新触发 cd 过程就会顺利执行。


四、SaltStack

4.1 简介

SaltStack(通常简称为 Salt)是一个开源的、基于 Python 的自动化运维工具,用于配置管理、远程执行、监控和编排。它以高性能、可扩展性和灵活性著称,特别适合管理大规模基础设施。

  • SaltStack 的核心特点

高速通信:使用 ZeroMQ(或 TCP)作为消息总线,实现极低延迟的命令分发。

并行执行:支持数千台主机同时执行命令。

灵活架构:Master/Minion 模式(主从模式)、Masterless 模式(无主模式,适用于边缘或临时环境)。

声明式与命令式结合:使用 YAML 编写状态(State)文件进行声明式配置、支持即时命令执行(如 salt '*' cmd.run 'uptime')。

丰富的模块系统:内置数百个执行模块(execution modules)和状态模块(state modules),涵盖文件、服务、包管理、用户、网络等。

事件驱动架构:支持 Reactor 系统,可根据事件自动触发动作(如自动响应故障、自动扩容等)。

安全可靠:基于 AES 加密通信、Minion 需要向 Master 请求认证(公钥交换)、支持细粒度权限控制(通过 external_auth 和 Pillar)。

  • SaltStack 架构主要由 Master 和 Minion 组成

Master:控制中心,负责下发指令、存储配置(States、Pillar)、管理 Minion 列表。通常部署在一台或多台高可用服务器上。
Minion:安装在被管理节点上的代理程序。接收 Master 指令,执行任务并返回结果。每个 Minion 有唯一 ID(默认为主机名)。

当然,测试环境可以将两者安装在同一台主机。

概念说明
GrainsMinion 的静态元数据(如操作系统、CPU、IP 地址等),用于目标匹配和条件判断。
Pillar敏感或动态的配置数据,由 Master 提供,仅对特定 Minion 可见(类似 Ansible 的 Vault + vars)。
State声明式配置文件(.sls),定义系统应处于的状态(如“确保 Nginx 已安装并运行”)。
Top File(top.sls) 定义哪些 Minion 应用哪些 State。
Execution Module即时执行的命令模块(如 cmd.runpkg.install)。
Reactor响应事件(如 Minion 上线、服务崩溃)自动触发动作。
  • 基本工作流程(Master/Minion 模式)

1)安装 Salt Master 和 Minion。
2)Minion 启动后向 Master 发送公钥请求。
3)Master 接受 Minion 的密钥(salt-key -A)。
4)用户在 Master 上编写 State 文件或直接执行命令。
5)Master 将任务推送给指定 Minion。
6)Minion 执行任务并返回结果(JSON 格式)。
7)结果汇总显示在 Master 终端或日志中。


4.2 将 SaltStack 的 salt 命令,封装成 saltctl

saltctl 目标用法:saltctl apply -t nodegroup:SERVERNAME -s STATE -p '{"json": "data"}' --batch BATCH_SIZE

4.2.1 安装 master 和 minion,并配置连接(基于 CentOS)

图片.png

报错处理:[ERROR] Unable to sign_in to master: Invalid master key

图片.png

Salt 出于安全考虑,拒绝连接公钥已变更的 Master,防止中间人攻击。因此也不建议频繁重装 Master。

 解决方案:清除 Minion 缓存的 Master 公钥并重启。

图片.png

4.2.2 配置 Nodegroup

图片.png

4.2.3 确保 State 文件存在,没有就创建

如下路径:/srv/salt/backend/ecs/docker_compose_update.sls。文件内容如下:

图片.png

4.2.4 封装成自己的 saltctl 脚本

创建 saltctl 文件,路径:/usr/local/bin/saltctl

图片.png

通过命令chmod +x /usr/local/bin/saltctl赋予 saltctl 执行权限。

报错处理:/usr/local/bin/saltctl: line 1: #!/bin/bash: No such file or directory

根本原因:系统中没有 /bin/bash,或者 bash 安装在其他位置(如 /usr/bin/bash)。这在某些精简 Linux 发行版(如 Alpine、部分容器镜像、最小化 CentOS/Debian)中很常见。

解决方式:

图片.png

4.2.5 测试使用 saltctl

图片.png

图片.png

群贤毕至

访客