如果在创建项目的时候没有勾选启用容器支持也可以手动新增一个 Dockerfile 文件。如下是 Dockerfile 文件的内容# 请参阅 https://aka.ms/customizecontainer 以了解如何自定义调试容器以及 Visual Studio 如何使用此 Dockerfile 生成映像以更快地进行调试。# 此阶段用于在快速模式(默认为调试配置)下从 VS 运行时FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base # 【建议改为私有仓库】USER $APP_UIDWORKDIR /app#EXPOSE 8080ENV ASPNETCORE_URLShttp://:998 #【可改为自定义的端口也可以使用默认的8080】# 此阶段用于生成服务项目FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build # 【建议改为私有仓库】ARG BUILD_CONFIGURATIONReleaseWORKDIR /srcCOPY [Test.WebAPI.Net8.csproj, .]RUN dotnet restore ./Test.WebAPI.Net8.csprojCOPY . .WORKDIR /srcRUN dotnet build ./WebApplication1.csproj -c $BUILD_CONFIGURATION -o /app/build# 此阶段用于发布要复制到最终阶段的服务项目FROM build AS publishARG BUILD_CONFIGURATIONReleaseRUN dotnet publish ./Test.WebAPI.Net8.csproj -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHostfalse# 此阶段在生产中使用或在常规模式下从 VS 运行时使用(在不使用调试配置时为默认值)FROM base AS finalWORKDIR /appCOPY --frompublish /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-secondcompile-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_NAMEnodegroup:${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 回车。接着执行下面命令来上传代码。# 初始化 Gitgit init# 添加 add 所有文件并提交 commitgit 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 72.1 查看当前系统的版本不清楚当前系统版本的话可以通过命令lsb_release -a来查看因为不同版本的系统使用的相关命令可能不同本文示例均基于 CentOS 7。# 查看当前系统版本[rootlocalhost ~]# cat /etc/os-releasecat: /etc/os-releasecat: No such file or directoryNAMECentOS LinuxVERSION7 (Core)IDcentosID_LIKErhel fedoraVERSION_ID7PRETTY_NAMECentOS Linux 7 (Core)# 查看确认是否安装过 docker[rootlocalhost ~]# 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)# 或者直接查询版本[rootlocalhost ~]# docker --version-bash: docker: command not found[rootlocalhost ~]#2.2 安装 Docker 并添加 deploy 用户# 1. 安装 yum-utilssudo 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. 安装 Dockersudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin# 最后验证安装成功[rootlocalhost ~]# docker --versionDocker version 26.1.4, build 5650f9b#【查看系统用户】cat /etc/passwd# 创建专用非 root 用户 加入 docker 组 限制 shell 更便于权限控制# 添加用户 deploysudo adduser deploysudo usermod -aG docker deploy # 加入 docker 组#【确保 Docker 服务正在运行】sudo systemctl status docker# 如果没有运行启动它sudo systemctl start dockersudo systemctl enable docker # 可选开机自启# 将当前用户加入 docker 组避免每次用 sudosudo usermod -aG docker $USER2.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# 验证[rootlocalhost ~]# docker-compose versionDocker Compose version v2.27.12.4 将 salt 用户加入 docker 组# 执行以下命令root 身份usermod -aG docker salt# 查看 salt 用户所属的组groups salt# 输出salt : salt docker# 若输出扔有问题执行[rootwww ~]# sudo -i -u salt docker psThis 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.3services:test-dotnet8:image: harbor.xxxxx.com/oa/test-dotnet8:v1.0 # 自动化脚本就是通过修改这个文件中的 v1.0 → v1.1 来实现滚动更新ports:- 8777:998volumes:- /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, dockermachine, kubernetes, custom, ssh, parallels, docker-windows, docker-autoscaler:## 这是最关键的一步选择错误会导致部署失败。shell直接在宿主机执行命令## 需要在 当前服务器 上直接操作 docker-composeshell 是最简单、最可靠的方式# 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 its 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#列出已注册的 Runnersudo 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并且服务器上已安装 DockerRunner 就会在每次 CI/CD 任务中启动一个临时的 Docker 容器来执行 .gitlab-ci.yml 中定义的命令比如 dotnet build、docker build 等。这种方式比 shell 更安全、更干净每次构建环境隔离是官方推荐做法。## 注册 Runner 的实操记录[rootlocalhost ~]# sudo gitlab-runner registerRuntime platform archamd64 oslinux pid6402 revision9ffb4aa0 version18.8.0Running in system-mode.Enter the GitLab instance URL (for example, https://gitlab.com/):https://--.--.com/Enter the registration token:GR134....S234SEnter a description for the runner:[localhost.localdomain]: test-dotnet8-secondEnter tags for the runner (comma-separated):gitlab-runner-dotnet8-secondEnter optional maintenance note for the runner:dotnetWARNING: 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_id01KFCRRD7BR00AF88M86JR1HX4 runnerVmoeA351TEnter an executor: instance, custom, ssh, dockermachine, kubernetes, shell, parallels, virtualbox, docker, docker-windows, docker-autoscaler:shellRunner registered successfully. Feel free to start it, but if its running already the config should be automatically reloaded!Configuration (with the authentication token) was saved in /etc/gitlab-runner/config.toml[rootlocalhost ~]#image3.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多语言项目前端 后端 移动端 ≥23 个 如 Node.js、.net、Swift 环境隔离安全隔离如生产部署 vs 测试 ≥2 个 避免敏感操作在通用 Runner 上执行并行任务加速 多个相同类型 Runner 提高并发能力但类型可能相同最佳实践即使初期共用也要在架构上预留分离能力如通过 tags 控制随着项目成熟逐步隔离。3.4 触发 Pipeline 执行常见的触发条件有#【仅在推送到特定分支时触发】rules:- if: $CI_COMMIT_BRANCH main#【仅在打 Tag 时触发如发布版本】rules:- if: $CI_COMMIT_TAG ! nullrules:- if: $CI_COMMIT_TAG ~ /^v.*/ # tag 以字母 v 开头就触发rules:- if: $CI_COMMIT_TAG null # 除了提交 tag 之外的操作触发#【在 Merge RequestMR中触发】# 用于运行 PR/MR 中的测试、代码扫描通常不包含部署到生产rules:- if: $CI_MERGE_REQUEST_ID#【手动触发通过 UI 或 API】# 其他来源还包括push代码推送、schedule定时任务、apiAPI 调用、trigger跨项目触发# 可用于“紧急修复”或“重试部署”rules:- if: $CI_PIPELINE_SOURCE web#【排除某些情况结合 when: never】rules:- if: $CI_COMMIT_BRANCH devwhen: never # dev 分支不运行此 job- when: always # 其他情况都运行3.5 如果修改了 docker-compose.yml 中的 service 名称若在 docker-compose.yml 中 修改了 service 名称例如从 web 改为 apiDocker Compose 会将其视为 一个全新的服务而旧的服务会被认为是“孤儿”orphaned。如果不正确处理会导致容器残留旧服务还在运行、网络/卷冲突、资源未释放、部署失败如你之前遇到的 “has active endpoints” 错误。推荐使用 --remove-orphans 方式这是最简单、最安全的方式。它可以启动新 servicenew_service自动停止并删除不再出现在 compose 文件中的旧 serviceold_service保留数据卷除非你额外加 --volumes。如下示例先进入项目主目录再使用 docker-compose 命令[rootwww ~]# cd /data/www/test-dotnet8[rootwww test-dotnet8]# docker-compose up -d --remove-orphansCreating testdotnet8_test-dotnet8_1 ... done[rootwww test-dotnet8]#命令执行成功后再重新触发 cd 过程就会顺利执行。回到顶部四、SaltStack4.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。2Minion 启动后向 Master 发送公钥请求。3Master 接受 Minion 的密钥salt-key -A。4用户在 Master 上编写 State 文件或直接执行命令。5Master 将任务推送给指定 Minion。6Minion 执行任务并返回结果JSON 格式。7结果汇总显示在 Master 终端或日志中。4.2 将 SaltStack 的 salt 命令封装成 saltctlsaltctl 目标用法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 # 需先启用 EPELsudo yum install salt-master salt-minion# 启动并启用服务sudo systemctl enable --now salt-mastersudo systemctl enable --now salt-minion# 配置 Minion 指向本地 Mastersudo 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# 测试[rootwww ~]# sudo salt-key -LAccepted Keys:Denied Keys:Unaccepted Keys:local-salt-minionRejected Keys:[rootwww ~]# sudo salt-key -AThe following keys are going to be accepted:Unaccepted Keys:local-salt-minionProceed? [n/Y] yKey for minion local-salt-minion accepted.[rootwww ~]# sudo salt * test.pinglocal-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 servers 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-masterls /etc/salt/pki/master/master.pub # 确认 master key 存在## 重新启动 Minionsudo systemctl start salt-minion# 此时 Minion 会# 从本地 Masterlocalhost获取新的公钥# 自动保存为 /etc/salt/pki/minion/minion_master.pub# 发起认证请求生成新的 minion.pub## 在 Master 上接受 Minion 密钥sudo salt-key -L # 查看 Unaccepted Keyssudo salt-key -a # 或 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: Llocal-salt-minion# 然后重启 salt-mastersystemctl restart salt-master4.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 -- $TEMPTARGETSTATEPILLARBATCH100% # 默认全量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 ;;esacdone# 检查是否为 nodegroupif [[ $TARGET nodegroup:* ]]; thenNODEGROUP${TARGET#nodegroup:}echo Applying state $STATE to nodegroup $NODEGROUP with batch$BATCHexec salt -N $NODEGROUP state.apply $STATE pillar$PILLAR batch$BATCHelseecho Only nodegroup targets supported in this wrapperexit 1fi通过命令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/saltctl4.2.5 测试使用 saltctl[rootwww ~]# 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 (changed1)Failed: 0------------Total states run: 2Total run time: 4.278 s[rootwww ~]# docker container lsCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES4f629846266b 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[rootwww ~]#够杖衫儋
记忆系统优化:从记录到智能检索
如果在创建项目的时候没有勾选启用容器支持也可以手动新增一个 Dockerfile 文件。如下是 Dockerfile 文件的内容# 请参阅 https://aka.ms/customizecontainer 以了解如何自定义调试容器以及 Visual Studio 如何使用此 Dockerfile 生成映像以更快地进行调试。# 此阶段用于在快速模式(默认为调试配置)下从 VS 运行时FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base # 【建议改为私有仓库】USER $APP_UIDWORKDIR /app#EXPOSE 8080ENV ASPNETCORE_URLShttp://:998 #【可改为自定义的端口也可以使用默认的8080】# 此阶段用于生成服务项目FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build # 【建议改为私有仓库】ARG BUILD_CONFIGURATIONReleaseWORKDIR /srcCOPY [Test.WebAPI.Net8.csproj, .]RUN dotnet restore ./Test.WebAPI.Net8.csprojCOPY . .WORKDIR /srcRUN dotnet build ./WebApplication1.csproj -c $BUILD_CONFIGURATION -o /app/build# 此阶段用于发布要复制到最终阶段的服务项目FROM build AS publishARG BUILD_CONFIGURATIONReleaseRUN dotnet publish ./Test.WebAPI.Net8.csproj -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHostfalse# 此阶段在生产中使用或在常规模式下从 VS 运行时使用(在不使用调试配置时为默认值)FROM base AS finalWORKDIR /appCOPY --frompublish /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-secondcompile-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_NAMEnodegroup:${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 回车。接着执行下面命令来上传代码。# 初始化 Gitgit init# 添加 add 所有文件并提交 commitgit 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 72.1 查看当前系统的版本不清楚当前系统版本的话可以通过命令lsb_release -a来查看因为不同版本的系统使用的相关命令可能不同本文示例均基于 CentOS 7。# 查看当前系统版本[rootlocalhost ~]# cat /etc/os-releasecat: /etc/os-releasecat: No such file or directoryNAMECentOS LinuxVERSION7 (Core)IDcentosID_LIKErhel fedoraVERSION_ID7PRETTY_NAMECentOS Linux 7 (Core)# 查看确认是否安装过 docker[rootlocalhost ~]# 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)# 或者直接查询版本[rootlocalhost ~]# docker --version-bash: docker: command not found[rootlocalhost ~]#2.2 安装 Docker 并添加 deploy 用户# 1. 安装 yum-utilssudo 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. 安装 Dockersudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin# 最后验证安装成功[rootlocalhost ~]# docker --versionDocker version 26.1.4, build 5650f9b#【查看系统用户】cat /etc/passwd# 创建专用非 root 用户 加入 docker 组 限制 shell 更便于权限控制# 添加用户 deploysudo adduser deploysudo usermod -aG docker deploy # 加入 docker 组#【确保 Docker 服务正在运行】sudo systemctl status docker# 如果没有运行启动它sudo systemctl start dockersudo systemctl enable docker # 可选开机自启# 将当前用户加入 docker 组避免每次用 sudosudo usermod -aG docker $USER2.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# 验证[rootlocalhost ~]# docker-compose versionDocker Compose version v2.27.12.4 将 salt 用户加入 docker 组# 执行以下命令root 身份usermod -aG docker salt# 查看 salt 用户所属的组groups salt# 输出salt : salt docker# 若输出扔有问题执行[rootwww ~]# sudo -i -u salt docker psThis 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.3services:test-dotnet8:image: harbor.xxxxx.com/oa/test-dotnet8:v1.0 # 自动化脚本就是通过修改这个文件中的 v1.0 → v1.1 来实现滚动更新ports:- 8777:998volumes:- /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, dockermachine, kubernetes, custom, ssh, parallels, docker-windows, docker-autoscaler:## 这是最关键的一步选择错误会导致部署失败。shell直接在宿主机执行命令## 需要在 当前服务器 上直接操作 docker-composeshell 是最简单、最可靠的方式# 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 its 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#列出已注册的 Runnersudo 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并且服务器上已安装 DockerRunner 就会在每次 CI/CD 任务中启动一个临时的 Docker 容器来执行 .gitlab-ci.yml 中定义的命令比如 dotnet build、docker build 等。这种方式比 shell 更安全、更干净每次构建环境隔离是官方推荐做法。## 注册 Runner 的实操记录[rootlocalhost ~]# sudo gitlab-runner registerRuntime platform archamd64 oslinux pid6402 revision9ffb4aa0 version18.8.0Running in system-mode.Enter the GitLab instance URL (for example, https://gitlab.com/):https://--.--.com/Enter the registration token:GR134....S234SEnter a description for the runner:[localhost.localdomain]: test-dotnet8-secondEnter tags for the runner (comma-separated):gitlab-runner-dotnet8-secondEnter optional maintenance note for the runner:dotnetWARNING: 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_id01KFCRRD7BR00AF88M86JR1HX4 runnerVmoeA351TEnter an executor: instance, custom, ssh, dockermachine, kubernetes, shell, parallels, virtualbox, docker, docker-windows, docker-autoscaler:shellRunner registered successfully. Feel free to start it, but if its running already the config should be automatically reloaded!Configuration (with the authentication token) was saved in /etc/gitlab-runner/config.toml[rootlocalhost ~]#image3.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多语言项目前端 后端 移动端 ≥23 个 如 Node.js、.net、Swift 环境隔离安全隔离如生产部署 vs 测试 ≥2 个 避免敏感操作在通用 Runner 上执行并行任务加速 多个相同类型 Runner 提高并发能力但类型可能相同最佳实践即使初期共用也要在架构上预留分离能力如通过 tags 控制随着项目成熟逐步隔离。3.4 触发 Pipeline 执行常见的触发条件有#【仅在推送到特定分支时触发】rules:- if: $CI_COMMIT_BRANCH main#【仅在打 Tag 时触发如发布版本】rules:- if: $CI_COMMIT_TAG ! nullrules:- if: $CI_COMMIT_TAG ~ /^v.*/ # tag 以字母 v 开头就触发rules:- if: $CI_COMMIT_TAG null # 除了提交 tag 之外的操作触发#【在 Merge RequestMR中触发】# 用于运行 PR/MR 中的测试、代码扫描通常不包含部署到生产rules:- if: $CI_MERGE_REQUEST_ID#【手动触发通过 UI 或 API】# 其他来源还包括push代码推送、schedule定时任务、apiAPI 调用、trigger跨项目触发# 可用于“紧急修复”或“重试部署”rules:- if: $CI_PIPELINE_SOURCE web#【排除某些情况结合 when: never】rules:- if: $CI_COMMIT_BRANCH devwhen: never # dev 分支不运行此 job- when: always # 其他情况都运行3.5 如果修改了 docker-compose.yml 中的 service 名称若在 docker-compose.yml 中 修改了 service 名称例如从 web 改为 apiDocker Compose 会将其视为 一个全新的服务而旧的服务会被认为是“孤儿”orphaned。如果不正确处理会导致容器残留旧服务还在运行、网络/卷冲突、资源未释放、部署失败如你之前遇到的 “has active endpoints” 错误。推荐使用 --remove-orphans 方式这是最简单、最安全的方式。它可以启动新 servicenew_service自动停止并删除不再出现在 compose 文件中的旧 serviceold_service保留数据卷除非你额外加 --volumes。如下示例先进入项目主目录再使用 docker-compose 命令[rootwww ~]# cd /data/www/test-dotnet8[rootwww test-dotnet8]# docker-compose up -d --remove-orphansCreating testdotnet8_test-dotnet8_1 ... done[rootwww test-dotnet8]#命令执行成功后再重新触发 cd 过程就会顺利执行。回到顶部四、SaltStack4.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。2Minion 启动后向 Master 发送公钥请求。3Master 接受 Minion 的密钥salt-key -A。4用户在 Master 上编写 State 文件或直接执行命令。5Master 将任务推送给指定 Minion。6Minion 执行任务并返回结果JSON 格式。7结果汇总显示在 Master 终端或日志中。4.2 将 SaltStack 的 salt 命令封装成 saltctlsaltctl 目标用法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 # 需先启用 EPELsudo yum install salt-master salt-minion# 启动并启用服务sudo systemctl enable --now salt-mastersudo systemctl enable --now salt-minion# 配置 Minion 指向本地 Mastersudo 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# 测试[rootwww ~]# sudo salt-key -LAccepted Keys:Denied Keys:Unaccepted Keys:local-salt-minionRejected Keys:[rootwww ~]# sudo salt-key -AThe following keys are going to be accepted:Unaccepted Keys:local-salt-minionProceed? [n/Y] yKey for minion local-salt-minion accepted.[rootwww ~]# sudo salt * test.pinglocal-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 servers 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-masterls /etc/salt/pki/master/master.pub # 确认 master key 存在## 重新启动 Minionsudo systemctl start salt-minion# 此时 Minion 会# 从本地 Masterlocalhost获取新的公钥# 自动保存为 /etc/salt/pki/minion/minion_master.pub# 发起认证请求生成新的 minion.pub## 在 Master 上接受 Minion 密钥sudo salt-key -L # 查看 Unaccepted Keyssudo salt-key -a # 或 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: Llocal-salt-minion# 然后重启 salt-mastersystemctl restart salt-master4.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 -- $TEMPTARGETSTATEPILLARBATCH100% # 默认全量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 ;;esacdone# 检查是否为 nodegroupif [[ $TARGET nodegroup:* ]]; thenNODEGROUP${TARGET#nodegroup:}echo Applying state $STATE to nodegroup $NODEGROUP with batch$BATCHexec salt -N $NODEGROUP state.apply $STATE pillar$PILLAR batch$BATCHelseecho Only nodegroup targets supported in this wrapperexit 1fi通过命令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/saltctl4.2.5 测试使用 saltctl[rootwww ~]# 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 (changed1)Failed: 0------------Total states run: 2Total run time: 4.278 s[rootwww ~]# docker container lsCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES4f629846266b 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[rootwww ~]#够杖衫儋