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

〇、前言

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

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

一、准备一个示例项目

1.1 创建一个 Web API 示例项目

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

image

如果在创建项目的时候没有勾选启用容器支持,也可以手动新增一个 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=ReleaseWORKDIR /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- deployvariables: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.6APP_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.0script:- 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 tempartifacts:when: on_successpaths:- temp/expire_in: 30 minutestags:- "gitlab-runner-dotnet8-second"
compile-prod-api:extends: .compile-oprules:# - if: '$CI_COMMIT_TAG == null' # 测试用- if: '$CI_COMMIT_TAG =~ /^v.*/'variables:MAIN_PROJECT: ${CI_PROJECT_NAME}.csproj  # 若主项目名不同,可手动指定# 生产环境构建任务
build-prod-api:extends: .build-opdependencies:- compile-prod-apirules:- 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.yamlSERVER_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 $JSONsaltctl apply -t nodegroup:${SERVER_NAME} -s backend.ecs.docker_compose_update -p  "${JSON}" --batchtags:- "gitlab-runner-dotnet8-second"

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

1.3 将项目上传至 gitlab

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

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

image

进入到项目主目录后,打开 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-networknetworks: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 ~]# 

image

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 执行

常见的触发条件有:

#【仅在推送到特定分支时触发】
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.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)

# 安装
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_SIZETEMP=$(getopt -o t:s:p: --long batch: -n 'saltctl' -- "$@")
eval set -- "$TEMP"TARGET=""
STATE=""
PILLAR=""
BATCH="100%"  # 默认全量while true; docase "$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:* ]]; thenNODEGROUP="${TARGET#nodegroup:}"echo "Applying state '$STATE' to nodegroup '$NODEGROUP' with batch=$BATCH"exec salt -N "$NODEGROUP" state.apply "$STATE" pillar="$PILLAR" batch="$BATCH"
elseecho "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-composeFunction: pkg.installedResult: TrueComment: All specified packages are already installedStarted: 17:27:51.007951Duration: 367.442 msChanges:   
----------ID: update_docker_compose_test-dotnet8Function: cmd.runName: cd /data/www/test-dotnet8 && \
docker-compose pull && \
docker-compose up -dResult: TrueComment: Command "cd /data/www/test-dotnet8 && \docker-compose pull && \docker-compose up -d" runStarted: 17:27:51.376588Duration: 3910.754 msChanges:   ----------pid:2100retcode:0stderr:Pulling test-dotnet8 (harbor.xxxx.com/oa/test-dotnet8:v2.08)...Starting testdotnet8_test-dotnet8_1 ... ?[1A?[2KStarting testdotnet8_test-dotnet8_1 ... ?[32mdone?[0m?[1Bstdout:v2.08: Pulling from oa/test-dotnet8Digest: sha256:2c235aee4cc91f8cfb336554d6c89ca30bd8716249dcb1470f50681352f477b9Status: Image is up to date for harbor.xxxx.com/oa/test-dotnet8:v2.08Summary 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 ~]# 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1196472.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI学术优化工具盘点:6个平台实测,自动改写功能提升论文可读性

开头总结工具对比&#xff08;技能4&#xff09; &#xfffd;&#xfffd; 为帮助学生们快速选出最适合的AI论文工具&#xff0c;我从处理速度、降重效果和核心优势三个维度&#xff0c;对比了6款热门网站&#xff0c;数据基于实际使用案例&#xff1a; 工具名称 处理速度 降…

vue3 setup插件 vite-plugin-vue-setup-extend

setup插件 npm i vite-plugin-vue-setup-extend -D vite.config.ts 加入import VueSetupExtend from vite-plugin-vue-setup-extend和 VueSetupExtend()import { fileURLToPath, URL } from node:urlimport { defineCo…

基于stm32单片机的智能家居控制系统

目录硬件组成软件设计功能实现应用场景开发资源源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;硬件组成 STM32单片机作为核心控制器&#xff0c;通常选用STM32F103或STM32F407系列&#xff0c;具备丰富的外设接口和低功耗特性。传感器…

基于stm32单片机的智能宿舍管理系统

目录系统概述核心功能模块硬件设计软件实现应用场景优势与扩展源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;系统概述 基于STM32单片机的智能宿舍管理系统是一种集成环境监测、安全防护、能源管理和远程控制的综合解决方案。该系统通…

AI论文助手Top8:综合测评写作产出与降重性能,一键式解决方案

AI论文生成工具排行榜&#xff1a;8个网站对比&#xff0c;论文降重写作功能全 工具对比总结 以下是8个AI论文工具的简要排名&#xff0c;基于核心功能、处理速度和适用性对比。排名侧重实用性与用户反馈&#xff0c;数据源于引用内容案例&#xff1a; 工具名称 主要功能 优…

6大AI论文优化工具横向测评:从语言流畅度到学术规范全覆盖

开头总结工具对比&#xff08;技能4&#xff09; &#xfffd;&#xfffd; 为帮助学生们快速选出最适合的AI论文工具&#xff0c;我从处理速度、降重效果和核心优势三个维度&#xff0c;对比了6款热门网站&#xff0c;数据基于实际使用案例&#xff1a; 工具名称 处理速度 降…

React的代理配置

方法一&#xff1a; 在package.json中追加如下配置 "proxy":"http://localhost:5000" 说明: 1.优点:配置简单,前端请求资源时可以不加任何前缀。 2.缺点:不能配置多个代理。 3.工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给…

智能论文润色工具评测:6款AI平台如何让学术表达更清晰精准

开头总结工具对比&#xff08;技能4&#xff09; &#xfffd;&#xfffd; 为帮助学生们快速选出最适合的AI论文工具&#xff0c;我从处理速度、降重效果和核心优势三个维度&#xff0c;对比了6款热门网站&#xff0c;数据基于实际使用案例&#xff1a; 处理速度 降重幅度 独…

AtCoder Beginner Contest竞赛题解 | AtCoder Beginner Contest 439

​欢迎大家订阅我的专栏&#xff1a;算法题解&#xff1a;C与Python实现&#xff01; 本专栏旨在帮助大家从基础到进阶 &#xff0c;逐步提升编程能力&#xff0c;助力信息学竞赛备战&#xff01; 专栏特色 1.经典算法练习&#xff1a;根据信息学竞赛大纲&#xff0c;精心挑选…

2026最新强韧固发洗发水产品top5推荐!国内优质防脱洗护品牌权威榜单发布,科学防脱助力健康秀发.

当代社会,生活节奏加快、压力增大,女性脱发、头油、头发干枯受损等问题日益普遍,据中国美发美容协会2025年度行业报告显示,我国约有68%的女性受到不同程度的头皮健康困扰,其中产后脱发、油性头皮及染烫后干枯受损…

idea同时启动application,启用不同端口

idea同时启动application,启用不同端口需求: 我们在测试项目的时候,经常需要测试一下为微服务集群下的负载均衡等,但是demo项目只有一个启动类,怎么办呢?我们可以通过再次启动相同的application,但是换个端口号…

2026最新草本防脱洗发水国货品牌top5推荐!国内优质防脱护理产品权威榜单发布,专业呵护宝妈_油头人群_干枯受损发质_女性脱发人群.

当代女性面临多重压力,产后脱发、头皮油脂失衡、染烫损伤导致的发质问题日益普遍,选择安全有效的防脱洗护产品成为刚需。据中国日用化工协会2025年度报告显示,国内防脱洗发水市场产品合格率仅72%,非法添加、功效虚…

6个AI论文优化平台深度评测:智能改写让学术语言更流畅自然

开头总结工具对比&#xff08;技能4&#xff09; &#xfffd;&#xfffd; 为帮助学生们快速选出最适合的AI论文工具&#xff0c;我从处理速度、降重效果和核心优势三个维度&#xff0c;对比了6款热门网站&#xff0c;数据基于实际使用案例&#xff1a; 工具名称 处理速度 降…

人群仿真软件:Legion_(13).Legion仿真项目管理

Legion仿真项目管理 项目创建与初始化 在使用Legion进行人群仿真时&#xff0c;首先需要创建一个新的项目并进行初始化。项目创建的过程包括定义仿真场景、设置仿真参数、导入必要的数据文件等。本节将详细介绍如何在Legion中创建和初始化一个仿真项目。 1. 创建新项目 1.1 通过…

基于AI的学术写作工具横评:6大平台助你一键提升论文表达质量

开头总结工具对比&#xff08;技能4&#xff09; &#xfffd;&#xfffd;从处理速度、降重效果和核心优势三个维度&#xff0c;对6款热门平台进行实测评估。数据来源于真实使用场景&#xff0c;帮助学生高效筛选最适合的科研辅助工具。 工具名称 处理速度 降重幅度 独特优…

C++算法训练第九天

C++算法训练第九天 以下为牛客挑战 今日收获 学到了三元组,就是当我们从一大堆数中选着3个数的方案。就是不一样位置的数如果相同,但是角标不一样也算不一样的。 常规3层for循环而三元组---》 prev2相当于前面所组成…

大数据采集技术盘点:Flume vs Kafka vs Sqoop

大数据采集技术盘点:Flume vs Kafka vs Sqoop 关键词:大数据采集、Flume、Kafka、Sqoop、数据集成、实时采集、批量传输 摘要:在大数据生态体系中,数据采集作为数据处理流程的起点,其技术选型直接影响后续数据处理的效率与质量。本文深度解析Apache生态中三款主流数据采集…

人群仿真软件:Legion_(13).Legion在交通枢纽中的应用

Legion在交通枢纽中的应用 1. 交通枢纽仿真概述 交通枢纽是城市交通系统中重要的组成部分&#xff0c;包括机场、火车站、地铁站、公交站等。这些场所通常人流密集&#xff0c;如何高效、安全地管理人群流动是一个复杂的问题。人群仿真软件Legion通过模拟真实的交通场景&…

YOLOv8改进 - 注意力机制 | CPCA (Channel Prior Convolutional Attention) 通道先验卷积注意力通过动态权重分配增强复杂场景特征感知

前言 本文介绍了通道先验卷积注意力&#xff08;CPCA&#xff09;及其在YOLOv8中的结合应用。医学图像分割面临挑战&#xff0c;现有注意力机制效果不佳&#xff0c;CPCA应运而生。它结合通道注意力和空间注意力&#xff0c;通过多尺度深度可分离卷积模块提取空间关系并保留通…

巴菲特的逆向投资艺术:在社交媒体时代的执行挑战

巴菲特的逆向投资艺术:在社交媒体时代的执行挑战 关键词:巴菲特、逆向投资艺术、社交媒体时代、执行挑战、投资策略 摘要:本文聚焦于巴菲特的逆向投资艺术在社交媒体时代所面临的执行挑战。首先介绍了逆向投资的背景,包括其目的、预期读者等内容。接着阐述了逆向投资的核心…