WebAPI 项目通过 CI/CD 自动化部署到 Linux 服务器(docker-compose)
〇、前言
本文先列举了一个简单的示例项目,然后通过 CI/CD 的方式,将私有镜像库 Harbor 中的镜像,发布到 Linux 中的 Docker 服务。
并且简单介绍了,配置自动发布的过程所涉及的一些概念和配置点,很多设计私有镜像库和私有域名都做了适当处理,仅供参考。如有疑问,欢迎友好沟通。
一、准备一个示例项目
1.1 创建一个 Web API 示例项目
名称例如:Test.WebAPI.Net8.Second,项目配置如下图。主要就是启用容器支持。

如果在创建项目的时候没有勾选启用容器支持,也可以手动新增一个 Dockerfile 文件。
如下,是 Dockerfile 文件的内容:
# 请参阅 https://aka.ms/customizecontainer 以了解如何自定义调试容器,以及 Visual Studio 如何使用此 Dockerfile 生成映像以更快地进行调试。
# 此阶段用于在快速模式(默认为调试配置)下从 VS 运行时
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base # 【建议:改为私有仓库】
USER $APP_UID
WORKDIR /app
#EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:998 #【可改为自定义的端口,也可以使用默认的8080】
# 此阶段用于生成服务项目
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build # 【建议:改为私有仓库】
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["Test.WebAPI.Net8.csproj", "."]
RUN dotnet restore "./Test.WebAPI.Net8.csproj"
COPY . .
WORKDIR /src
RUN dotnet build "./WebApplication1.csproj" -c $BUILD_CONFIGURATION -o /app/build
# 此阶段用于发布要复制到最终阶段的服务项目
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./Test.WebAPI.Net8.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
# 此阶段在生产中使用,或在常规模式下从 VS 运行时使用(在不使用调试配置时为默认值)
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Test.WebAPI.Net8.dll"]
1.2 在项目主目录下添加gitlab-ci.yml文件
文件内容例如:
stages:
- compile
- build
- deploy
variables:
DOCKER_REGISTRY_PREFIX: "harbor.xxxx.com" #【需替换为私有仓库域名】
IMAGE_NAME: ${DOCKER_REGISTRY_PREFIX}/${CI_PROJECT_PATH}
DOCKER_IMAGE_TEST: ${IMAGE_NAME}:${CI_COMMIT_SHA}
DOCKER_IMAGE_PROD: ${IMAGE_NAME}:${CI_COMMIT_TAG}
# DOCKER_IMAGE_PROD: ${IMAGE_NAME}:v1.6
APP_NAME: ${CI_PROJECT_NAME}
# 生产环境编译任务
.compile-op: # 以 . 开头,是隐藏 job 模板,不会直接运行
stage: compile
#【需替换为私有仓库域名】
image: harbor.xxxx.com/cicd/sdk/dotnet:8.0
#image: mcr.microsoft.com/dotnet/sdk:8.0
script:
- mkdir -p temp
# 发布为 Linux 环境的 Release 可执行文件
- dotnet publish Test.WebAPI.Net8.Second.csproj -c Release -o temp --runtime linux-x64 --self-contained false
- pwd
- ls -al temp
artifacts:
when: on_success
paths:
- temp/
expire_in: 30 minutes
tags:
- "gitlab-runner-dotnet8-second"
compile-prod-api:
extends: .compile-op
rules:
# - if: '$CI_COMMIT_TAG == null' # 测试用
- if: '$CI_COMMIT_TAG =~ /^v.*/'
variables:
MAIN_PROJECT: ${CI_PROJECT_NAME}.csproj # 若主项目名不同,可手动指定
# 生产环境构建任务
build-prod-api:
extends: .build-op
dependencies:
- compile-prod-api
rules:
- if: '$CI_COMMIT_TAG =~ /^v.*/'
variables:
DOCKER_IMAGE_NAME: ${DOCKER_IMAGE_PROD}
.build-op:
stage: build
#【需替换为私有仓库域名】
image: harbor.xxxx.com/base/docker:24.0.7-dind # 向下兼容,目标服务器需要大于等于此版本
script:
- ls -al
- echo "Image name:" ${DOCKER_IMAGE_NAME}
- docker build -t ${DOCKER_IMAGE_NAME} -f Dockerfile .
- docker push ${DOCKER_IMAGE_NAME} # 将镜像推送到私有镜像库
tags:
- "gitlab-runner-dotnet8-second"
# 发布
deploy-api-prod:
extends: .deploy-op
# when: manual # 标识为手动执行
rules:
- if: '$CI_COMMIT_TAG =~ /^v.*/'
variables:
DOCKER_COMPOSE_FILE: /data/www/${APP_NAME}/docker-compose.yaml
SERVER_NAME: ${CI_PROJECT_PATH}
PORTS: 8777
.deploy-op:
stage: deploy
#【需替换为私有仓库域名】
image: harbor.xxxx.com/cicd/saltctl-1.0 # 私有仓库中的 saltctl 工具
script:
- |
echo "SERVER_NAME=nodegroup:${SERVER_NAME} TAG={TAG} DOCKER_COMPOSE_FILE=${DOCKER_COMPOSE_FILE} IMAGE_NAME=${IMAGE_NAME} PORTS=${PORTS} MODE=${MODE}"
export JSON="{\"tag\": \"${CI_COMMIT_TAG}\", \"docker_compose_file\": \"${DOCKER_COMPOSE_FILE}\", \"image_name\": \"${IMAGE_NAME}\", \"port\": \"${PORTS}\", \"mode\": \"${MODE}\"}"
echo $JSON
saltctl apply -t nodegroup:${SERVER_NAME} -s backend.ecs.docker_compose_update -p "${JSON}" --batch
tags:
- "gitlab-runner-dotnet8-second"
这样一个简单的示例项目就准备好了。
1.3 将项目上传至 gitlab
在 gitlab 上创建一个新的 blank 项目,名字例如:test-dotnet8。
注意:不要勾选“Initialize repository with a README”(因为已有本地代码)。若勾选了 “Initialize repository with a README”,GitLab 会自动创建一个包含 README.md 文件的初始提交(即远程仓库非空)。此时如果直接尝试推送本地代码,会遇到冲突(因为本地和远程历史不一致)。

进入到项目主目录后,打开 PowerShell,输入 cmd 回车。接着执行下面命令来上传代码。
# 初始化 Git
git init
# 添加 add 所有文件,并提交 commit
git add .
git commit -m 自定义文本:项目初始化提交
# 添加 GitLab 远程仓库地址
# 注意,需替换为实际 GitLab 项目 HTTPS 或 SSH 地址
git remote add origin https://xxxxx.com/oa/test-dotnet8.git
# 推送合并后的代码到 GitLab
# 注意当前分支是否与远程分支相同,一般为 master 或者 main,不用的话需要适时切换
git push origin master
# git branch # 查看当前分支
# git checkout main # 切换到 main 分支
# git pull # 拉取远程分支的内容
# git merge master # 合并指定分支代码到当前分支
# git push # 将本地的项目状态推送到仓库
若在 gitlab 创建项目时,勾选了 “Initialize repository with a README”,则需要增加如下操作:
# 在 push 提交之前,先拉取远程内容(过程中可能需要手动登录 gitlab)
# 分支名字为 main 或者 master,注意核实后执行
git pull origin master --allow-unrelated-histories
# 系统会打开编辑器,提示输入合并提交信息(可直接保存退出,命令:【:wq】)
# 完成后,在进行 push 操作
二、Linux 环境准备(CentOS 7)
2.1 查看当前系统的版本
不清楚当前系统版本的话,可以通过命令lsb_release -a来查看,因为不同版本的系统使用的相关命令可能不同,本文示例均基于 CentOS 7。
# 查看当前系统版本
[root@localhost ~]# cat /etc/os-release
cat: /etc/os-releasecat: No such file or directory
NAME="CentOS Linux"
VERSION="7 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="7"
PRETTY_NAME="CentOS Linux 7 (Core)"
# 查看确认是否安装过 docker
[root@localhost ~]# which docker
/usr/bin/which: no docker in (/usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/usr/local/nginx/sbin:/root/bin)
# 或者直接查询版本
[root@localhost ~]# docker --version
-bash: docker: command not found
[root@localhost ~]#
2.2 安装 Docker 并添加 deploy 用户
# 1. 安装 yum-utils
sudo yum install -y yum-utils
# 2. 添加 Docker 官方仓库(使用 yum-config-manager)
# 使用国内镜像源(阿里云镜像)
sudo curl -o /etc/yum.repos.d/docker-ce.repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# 或者清华大学镜像
# sudo curl -o /etc/yum.repos.d/docker-ce.repo https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/docker-ce.repo
# 3. 安装 Docker
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# 最后验证安装成功
[root@localhost ~]# docker --version
Docker version 26.1.4, build 5650f9b
#【查看系统用户】
cat /etc/passwd
# 创建专用非 root 用户 + 加入 docker 组 + 限制 shell 更便于权限控制
# 添加用户 deploy
sudo adduser deploy
sudo usermod -aG docker deploy # 加入 docker 组
#【确保 Docker 服务正在运行】
sudo systemctl status docker
# 如果没有运行,启动它:
sudo systemctl start docker
sudo systemctl enable docker # 可选:开机自启
# 将当前用户加入 docker 组(避免每次用 sudo)
sudo usermod -aG docker $USER
2.3 安装 docker-compose
# 安装 Docker Compose v2(推荐)
sudo yum install -y docker-compose-plugin
# 测试
docker compose version
# 输出:Docker Compose version v2.27.1
如果仍然需要通过 docker-compose 命令来处理服务,可以创建软连接。
# 先确认 docker compose 的真实插件位置
ls /usr/libexec/docker/cli-plugins/ # RHEL/CentOS/Rocky 常见路径
ls /usr/local/lib/docker/cli-plugins/ # 手动安装常见路径
# 在大多数通过包管理器安装 Docker 的 Linux 系统(如 CentOS、Rocky、Ubuntu)上,Compose V2 插件实际位于:
/usr/libexec/docker/cli-plugins/docker-compose
# 如果已经创建过,需要先删除错误的软链接
sudo rm -f /usr/local/bin/docker-compose
# 创建正确的软链接
sudo ln -s /usr/libexec/docker/cli-plugins/docker-compose /usr/local/bin/docker-compose
# 验证:
[root@localhost ~]# docker-compose version
Docker Compose version v2.27.1
2.4 将 salt 用户加入 docker 组
# 执行以下命令(root 身份)
usermod -aG docker salt
# 查看 salt 用户所属的组
groups salt
# 输出:salt : salt docker
# 若输出扔有问题,执行
[root@www ~]# sudo -i -u salt docker ps
This account is currently not available.
# 此输出,说明 salt 用户的登录 shell 被设置为 /sbin/nologin 或 /usr/sbin/nologin
# 这是出于安全考虑的常见配置 —— Salt 用户通常只用于后台服务,不允许交互式登录。
如果不进行此操作会出现报错:
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock
这说明 salt 用户没有权限访问 Docker 守护进程,需要将 salt 用户加入 docker 组。
三、CI/CD 脚本以及触发
3.1 关于 docker-compose.yaml
用一个 YAML 文件描述整个应用的服务、网络、卷、环境变量等,通过一条命令(如 docker-compose up)一键启动/停止整套服务。
示例:
version: '3.3'
services:
test-dotnet8:
image: harbor.xxxxx.com/oa/test-dotnet8:v1.0 # 自动化脚本就是通过修改这个文件中的 v1.0 → v1.1 来实现滚动更新
ports:
- "8777:998"
volumes:
- /data/www/test-dotnet8/Log:/app/Log # 日志文件持久化
#- /data/www/test-dotnet8/appsettings.json:/app/appsettings.json # 替换配置文件
networks:
- test-network
networks:
test-network:
| 优势 | 说明 |
|---|---|
| 简化部署 | 无需手动敲 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
## 【安装 GitLab Runner】
# 检查是否已安装
which gitlab-runner
## 对于 CentOS / RHEL / Rocky Linux:
# 添加官方仓库
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | sudo bash
# 安装
sudo yum install -y gitlab-runner
## 对于 Ubuntu / Debian:
# 添加官方仓库
# curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash
# 安装
# sudo apt-get install -y gitlab-runner
## 【注册 Runner(连接到的 GitLab 项目)】
sudo gitlab-runner register
# 按提示输入对应的内容:
# Enter the GitLab instance URL (for example, https://gitlab.com/): ## GitLab 地址
# Enter the registration token: ## 项目 → Settings → CI/CD → Runners → Project registration token
# Enter a description for the runner: ## 描述 Runner 用途
# Enter tags for the runner (comma-separated): ## 标签,用于 .gitlab-ci.yml 中指定任务由谁执行
# Enter optional maintenance note for the runner: ## 可选,用于记录维护信息(如负责人、到期时间)
# Enter an executor: instance, shell, virtualbox, docker, docker+machine, kubernetes, custom, ssh, parallels, docker-windows, docker-autoscaler:
## 这是最关键的一步!选择错误会导致部署失败。shell:直接在宿主机执行命令
## 需要在 当前服务器 上直接操作 docker-compose,shell 是最简单、最可靠的方式
# Enter the default Docker image (for example, ruby:3.3):
## 仅当 executor = docker 时才需要填写
## 选择了 shell,这一步不会出现(GitLab Runner 会自动跳过)
# 注册成功的标志:
Runner registered successfully. Feel free to start it, but if it''s running already the config should be automatically reloaded!
Configuration (with the authentication token) was saved in "/etc/gitlab-runner/config.toml"
## 【启动并启用服务】
# 重启
sudo systemctl enable --now gitlab-runner
#验证状态:
sudo systemctl status gitlab-runner
#列出已注册的 Runner:
sudo gitlab-runner list
## 【确保 gitlab-runner 用户能运行 Docker】
# 将 gitlab-runner 用户加入 docker 组
sudo usermod -aG docker gitlab-runner
# 重启 gitlab-runner 服务使组生效
sudo systemctl restart gitlab-runner
# 验证
sudo -u gitlab-runner docker info
在注册 GitLab Runner 时选择 --executor "docker",并且服务器上已安装 Docker,Runner 就会在每次 CI/CD 任务中启动一个临时的 Docker 容器来执行 .gitlab-ci.yml 中定义的命令(比如 dotnet build、docker build 等)。这种方式比 shell 更安全、更干净(每次构建环境隔离),是官方推荐做法。
## 注册 Runner 的实操记录
[root@localhost ~]# sudo gitlab-runner register
Runtime platform arch=amd64 os=linux pid=6402 revision=9ffb4aa0 version=18.8.0
Running in system-mode.
Enter the GitLab instance URL (for example, https://gitlab.com/):
https://<-->.<-->.com/
Enter the registration token:
GR134....S234S
Enter a description for the runner:
[localhost.localdomain]: test-dotnet8-second
Enter tags for the runner (comma-separated):
gitlab-runner-dotnet8-second
Enter optional maintenance note for the runner:
dotnet
WARNING: Support for registration tokens and runner parameters in the 'register' command has been deprecated in GitLab Runner 15.6 and will be replaced with support for authentication tokens. For more information, see https://docs.gitlab.com/ci/runners/new_creation_workflow/
Registering runner... succeeded correlation_id=01KFCRRD7BR00AF88M86JR1HX4 runner=VmoeA351T
Enter an executor: instance, custom, ssh, docker+machine, kubernetes, shell, parallels, virtualbox, docker, docker-windows, docker-autoscaler:
shell
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!
Configuration (with the authentication token) was saved in "/etc/gitlab-runner/config.toml"
[root@localhost ~]#

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 可能需要特定工具(如 kubectl、aws-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 执行
常见的触发条件有:
#【仅在推送到特定分支时触发】
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
#【仅在打 Tag 时触发(如发布版本)】
rules:
- if: '$CI_COMMIT_TAG != null'
rules:
- if: '$CI_COMMIT_TAG =~ /^v.*/' # tag 以字母 v 开头就触发
rules:
- if: '$CI_COMMIT_TAG == null' # 除了提交 tag 之外的操作触发
#【在 Merge Request(MR)中触发】
# 用于运行 PR/MR 中的测试、代码扫描,通常不包含部署到生产
rules:
- if: '$CI_MERGE_REQUEST_ID'
#【手动触发(通过 UI 或 API)】
# 其他来源还包括:"push":代码推送、"schedule":定时任务、"api":API 调用、"trigger":跨项目触发
# 可用于“紧急修复”或“重试部署”
rules:
- if: '$CI_PIPELINE_SOURCE == "web"'
#【排除某些情况(结合 when: never)】
rules:
- if: '$CI_COMMIT_BRANCH == "dev"'
when: never # dev 分支不运行此 job
- when: always # 其他情况都运行
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 命令:
[root@www ~]# cd /data/www/test-dotnet8
[root@www test-dotnet8]# docker-compose up -d --remove-orphans
Creating testdotnet8_test-dotnet8_1 ... done
[root@www test-dotnet8]#
命令执行成功后,再重新触发 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(默认为主机名)。
当然,测试环境可以将两者安装在同一台主机。
| 概念 | 说明 |
|---|---|
| Grains | Minion 的静态元数据(如操作系统、CPU、IP 地址等),用于目标匹配和条件判断。 |
| Pillar | 敏感或动态的配置数据,由 Master 提供,仅对特定 Minion 可见(类似 Ansible 的 Vault + vars)。 |
| State | 声明式配置文件(.sls),定义系统应处于的状态(如“确保 Nginx 已安装并运行”)。 |
| Top File | (top.sls) 定义哪些 Minion 应用哪些 State。 |
| Execution Module | 即时执行的命令模块(如 cmd.run, pkg.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)
# 安装
sudo yum install epel-release -y # 需先启用 EPEL
sudo yum install salt-master salt-minion
# 启动并启用服务
sudo systemctl enable --now salt-master
sudo systemctl enable --now salt-minion
# 配置 Minion 指向本地 Master
sudo vim /etc/salt/minion
# 修改 master 和 id
# 默认情况下,Minion 会尝试连接 localhost,但显式配置更可靠
# master: localhost
# # 或使用 127.0.0.1
# # master: 127.0.0.1
# id: local-salt-minion # 可选:设置唯一的 minion ID(建议显式指定,避免主机名变化导致问题)
# 重启 Minion 服务
systemctl restart salt-minion
# 在 Master 上接受 Minion 的密钥
salt-key -L # 查看待接受密钥
salt-key -a local-salt-minion # 接受该 Minion
# 测试连接
sudo salt 'local-salt-minion' test.ping # 或通配符命令:sudo salt '*' test.ping
# 正确返回:
# local-salt-minion:
# True
# 测试:
[root@www ~]# sudo salt-key -L
Accepted Keys:
Denied Keys:
Unaccepted Keys:
local-salt-minion
Rejected Keys:
[root@www ~]# sudo salt-key -A
The following keys are going to be accepted:
Unaccepted Keys:
local-salt-minion
Proceed? [n/Y] y
Key for minion local-salt-minion accepted.
[root@www ~]# sudo salt '*' test.ping
local-salt-minion:
True
报错处理:[ERROR] Unable to sign_in to master: Invalid master key。
[ERROR] Unable to sign_in to master: Invalid master key
[ERROR] The master key has changed, the salt master could have been subverted...
[CRITICAL] The Salt Master server's public key did not authenticate!
Salt 出于安全考虑,拒绝连接公钥已变更的 Master,防止中间人攻击。因此也不建议频繁重装 Master。
解决方案:清除 Minion 缓存的 Master 公钥并重启。
## 停止 salt-minion 服务
sudo systemctl stop salt-minion
## 删除 Minion 缓存的 Master 公钥
sudo rm -f /etc/salt/pki/minion/minion_master.pub
# 注意:不要删除 minion.pem 和 minion.pub(这是 Minion 自己的密钥),只删 minion_master.pub
## 确保 Master 已启动并生成密钥
sudo systemctl start salt-master
ls /etc/salt/pki/master/master.pub # 确认 master key 存在
## 重新启动 Minion
sudo systemctl start salt-minion
# 此时 Minion 会:
# 从本地 Master(localhost)获取新的公钥
# 自动保存为 /etc/salt/pki/minion/minion_master.pub
# 发起认证请求(生成新的 minion.pub)
## 在 Master 上接受 Minion 密钥
sudo salt-key -L # 查看 Unaccepted Keys
sudo salt-key -a <your-minion-id> # 或 sudo salt-key -A 接受全部
# 若在 /etc/salt/minion 中设置了 id: my-local-minion,这里就用 my-local-minion
## 测试连接
sudo salt '*' test.ping
# 应返回 True。
4.2.2 配置 Nodegroup
# 在 Master 的 /etc/salt/master 中修改:
nodegroups:
oa/test-dotnet8: 'L@local-salt-minion'
# 然后重启 salt-master
systemctl restart salt-master
4.2.3 确保 State 文件存在,没有就创建
如下路径:/srv/salt/backend/ecs/docker_compose_update.sls。文件内容如下:
{% set service_name = pillar.get('service_name', 'app') %}
{% set image_tag = pillar.get('image_tag', 'latest') %}
{% set compose_dir = '/data/www/' + service_name %}
# 1. 确保 docker-compose 已安装(通过 pip 或包管理器)
docker-compose:
pkg.installed:
- name: docker-compose
# 或使用 pip(如果系统包太旧):
# - names:
# - python3-pip
# - reload_modules: true
# 如果用 pip 安装:
# pip.installed:
# - name: docker-compose
# - require:
# - pkg: python3-pip
# 2. 更新 docker-compose 服务
update_docker_compose_{{ service_name }}:
cmd.run:
- name: |
cd {{ compose_dir }} && \
docker-compose pull && \
docker-compose up -d
- require:
- pkg: docker-compose # 现在有这个 ID 了!
# - file: /data/www/test-dotnet8/docker-compose.yml # 可选:确保 compose 文件存在
4.2.4 封装成自己的 saltctl 脚本
创建 saltctl 文件,路径:/usr/local/bin/saltctl。
#!/usr/bin/bash
# 用法: saltctl apply -t nodegroup:NAME -s STATE -p '{"json": "data"}' --batch BATCH_SIZE
TEMP=$(getopt -o t:s:p: --long batch: -n 'saltctl' -- "$@")
eval set -- "$TEMP"
TARGET=""
STATE=""
PILLAR=""
BATCH="100%" # 默认全量
while true; do
case "$1" in
-t) TARGET="$2"; shift 2 ;;
-s) STATE="$2"; shift 2 ;;
-p) PILLAR="$2"; shift 2 ;;
--batch) BATCH="$2"; shift 2 ;;
--) shift; break ;;
*) echo "Invalid option"; exit 1 ;;
esac
done
# 检查是否为 nodegroup
if [[ "$TARGET" == nodegroup:* ]]; then
NODEGROUP="${TARGET#nodegroup:}"
echo "Applying state '$STATE' to nodegroup '$NODEGROUP' with batch=$BATCH"
exec salt -N "$NODEGROUP" state.apply "$STATE" pillar="$PILLAR" batch="$BATCH"
else
echo "Only nodegroup targets supported in this wrapper"
exit 1
fi
通过命令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)中很常见。
解决方式:
# 确认 bash 是否安装,以及实际路径
which bash
# 若系统有 bash,但在 /usr/bin/bash,修改脚本第一行为:#!/usr/bin/bash
# 若无,则手动安装
yum install -y bash
# 另外,也有可能是文件开头有特殊字符
# 查看是否有特殊字符
cat -A /usr/local/bin/saltctl
# 例如:M-oM-;M-?#!/usr/bin/bash$
# 手动删除
sed -i '1s/^\xEF\xBB\xBF//' /usr/local/bin/saltctl
4.2.5 测试使用 saltctl
[root@www ~]# salt 'local-salt-minion' state.apply backend.ecs.docker_compose_update pillar='{"image_tag":"v2.10","service_name":"test-dotnet8"}'
local-salt-minion:
----------
ID: docker-compose
Function: pkg.installed
Result: True
Comment: All specified packages are already installed
Started: 17:27:51.007951
Duration: 367.442 ms
Changes:
----------
ID: update_docker_compose_test-dotnet8
Function: cmd.run
Name: cd /data/www/test-dotnet8 && \
docker-compose pull && \
docker-compose up -d
Result: True
Comment: Command "cd /data/www/test-dotnet8 && \
docker-compose pull && \
docker-compose up -d
" run
Started: 17:27:51.376588
Duration: 3910.754 ms
Changes:
----------
pid:
2100
retcode:
0
stderr:
Pulling test-dotnet8 (harbor.xxxx.com/oa/test-dotnet8:v2.08)...
Starting testdotnet8_test-dotnet8_1 ...
?[1A?[2K
Starting testdotnet8_test-dotnet8_1 ... ?[32mdone?[0m
?[1B
stdout:
v2.08: Pulling from oa/test-dotnet8
Digest: sha256:2c235aee4cc91f8cfb336554d6c89ca30bd8716249dcb1470f50681352f477b9
Status: Image is up to date for harbor.xxxx.com/oa/test-dotnet8:v2.08
Summary for local-salt-minion
------------
Succeeded: 2 (changed=1)
Failed: 0
------------
Total states run: 2
Total run time: 4.278 s
[root@www ~]# docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4f629846266b harbor.xxxx.com/oa/test-dotnet8:v2.08 "dotnet Test.WebAPI.…" 7 hours ago Up 20 seconds 0.0.0.0:8777->998/tcp, :::8777->998/tcp testdotnet8_test-dotnet8_1
[root@www ~]#
本文来自博客园,作者:橙子家,欢迎微信扫码关注博主【橙子家czzj】,有任何疑问欢迎沟通,共同成长!

浙公网安备 33010602011771号