Spiga

Docker Swarm部署2

2025-08-10 10:55:01

一、安装Harbor

1. Harbor介绍

Harbor是由VMWare在Docker Registry的基础之上进行了二次封装,加进去了很多额外程序,而且提供了一个非常漂亮的web界面。

  • Project Harbor是一个开源的受信任的云本地注册表项目,用于存储、标记和扫描上下文。
  • Harbor扩展了开源Docker发行版,增加了用户通常需要的功能,如安全、身份和管理。
  • Harbor支持高级特性,如用户管理、访问控制、活动监视和实例之间的复制。

2. 功能

  • 多租户内容签名和验证
  • 安全性与漏洞分析
  • 审计日志记录
  • 身份集成和基于角色的访问控制
  • 实例间的镜像复制
  • 可扩展的API和图形UI
  • 国际化(目前为英文和中文)

3. docker compose

Harbor在物理机上部署是非常难的,而为了简化Harbor的应用,Harbor官方直接把Harbor做成了在容器中运行的应用,而且这个容器在Harbor中依赖类似redis、mysql、pgsql等很多存储系统,所以它需要编排很多容器协同起来工作,因此VMWare Harbor在部署和使用时,需要借助于Docker的单机编排工具(Docker compose)来实现。

Compose是一个用于定义和运行多容器Docker应用程序的工具。使用Compose,我们可以使用YAML文件来配置应用程序的服务。然后,只需要一个命令,就可以从配置中创建并启动所有服务。

4. 部署

DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}
mkdir -p $DOCKER_CONFIG/cli-plugins
curl -SL https://github.com/docker/compose/releases/download/v2.39.1/docker-compose-linux-x86_64 -o $DOCKER_CONFIG/cli-plugins/docker-compose

chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose

docker compose version
# 1. 解压harbor安装包
tar xf harbor-offline-installer-v2.13.2.tgz -C /usr/local/
ls /usr/local/
bin  etc  games  harbor  include  lib  lib64  libexec  sbin  share  src

# 2. 复制一个yml文件
cd /usr/local/harbor
cp harbor.yml.tmpl harbor.yml

# 3. 编辑harbor.yml文件
vim harbor.yml

#修改为当前主机的IP或域名
hostname: 192.168.16.135

#注释https功能
# https related config
# https:
  # https port for harbor, default is 443
  # port: 443
  # The path of cert and key files for nginx
  # certificate: /your/certificate/path
  # private_key: /your/private/key/path
  
#可以修改密码,这里使用默认密码
harbor_admin_password: Harbor12345

#数据持久化目录
data_volume: /data

#日志记录
log:
  level: info
  local:
    rotate_count: 50
    rotate_size: 200M
    location: /var/log/harbor
    
# 4. 运行./install.sh文件
./install.sh 

# 5. 查看
# 查看端口
ss -antl
# 查看容器
docker ps

5. 设置开机自启动

# 1. 写一个启动脚本
vim startall.sh
#!/bin/bash

cd /usr/local/harbor
docker compose stop && docker compose start

# 2. 给这个启动脚本设置执行权限
chmod +x startall.sh 
ll startall.sh
-rwxr-xr-x. 1 root root        78 Mar  9 18:13 startall.sh

# 3. 把启动脚本加到系统启动之后最后一个执行的文件
#把rc.local文件设置执行权限
touch /etc/rc.local           # 如果文件不存在,则创建
chmod +x /etc/rc.local       # 确保可执行
vim /etc/rc.local           # 编辑文件
#!/bin/bash
/bin/bash /usr/local/harbor/startall.sh
#以上配置可能不同的系统环境配置方法不一样

# 4. 验证一下
reboot

# 手动启动的方法
/usr/local/harbor/startall.sh

6. 使用

  1. 虚拟机IP访问 192.168.16.135,输入信息登录(用户名:admin 密码:Harbor12345)

  2. 创建项目:test

  3. 查看项目--->镜像仓库--->推送命令

  4. 本机连接Harbor仓库

    • 本机配置daemon.json文件(目前是宿主机,源码在宿主机上)

      vim /etc/docker/daemon.json 
      {
        "insecure-registries":["192.168.16.135"]
      }
      # 重读文件,并重启服务
      systemctl daemon-reload
      systemctl restart docker
      
    • 连接登录

      docker login 192.168.16.135
      Username: admin
      Password: Harbor12345
      WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
      Configure a credential helper to remove this warning. See
      https://docs.docker.com/engine/reference/commandline/login/#credentials-store
      
      Login Succeeded
      
  5. 上传镜像

    # 1. 打包docker镜像
    
    # 2. 设置标签
    docker tag jushuitan/api:v0.1 192.168.16.135/jushuit/api:v0.1
    
    # 3. 上传
    docker push 192.168.16.135/jushuit/api:v0.1
    
    # 4. 去harbor上查看
    
  6. 拉取镜像

    docker pull 192.168.16.135/jushuit/api:v0.1
    
    # 删除镜像
    docker rmi jushuit/api:v0.1
    

注意事项

  • 在客户端上传镜像时一定要记得执行docker login进行用户认证,否则无法直接push
  • 在客户端使用的时候如果不是用的https则必须要在客户端的/etc/docker/daemon.json配置文件中配置insecure-registries参数
  • 数据存放路径应在配置文件中配置到一个容量比较充足的共享存储中
  • Harbor是使用docker compose命令来管理的,如果需要停止Harbor也应用docker compose stop来停止,其他参数请--help

二、安装Git仓库

# Pull image from Docker Hub.
$ docker pull gogs/gogs

# Create local directory for volume.
$ mkdir -p /var/gogs

# Use `docker run` for the first time.
$ docker run --name=gogs --restart always -d -p 10022:22 -p 3000:3000 -v /var/gogs:/data gogs/gogs

# Use `docker start` if you have stopped it.
$ docker start gogs

如果没有mysql可以先安装一个

# 因为Harbor内置了mysql,使用了/path/to/mysql/data目录,我们这里使用/path/to/mysql2/data
docker run --name mysql -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -v /path/to/mysql2/data:/var/lib/mysql --restart unless-stopped -d mysql:latest --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci

# 访问
http://192.168.16.135:10880
注册帐号

三、安装 Jenkins

1. CI/CD

  • CI(Continuous Integration):持续集成,个人到整体的交付。

    指开发人员频繁地(所有开发者,每天多次)将代码更改集成到共享的代码库中,并通过自动化构建和测试来验证这些更改。

    开发人员本地完成单元测试,CI工具自动化完成代码审计和集成测试。

    其主要目的是快速发现和修复错误,确保代码的稳定性和一致性。

  • CD(Continuous Delivery) :持续交付,研发到质量/用户的过程。

    在持续集成的基础上,通过自动化流程将通过自动化测试的代码部署到贴近真实运行环境到类生产环境中,供测试评审。

  • CD(Continuous Deployment):持续部署,通过测试后自动部署到生产

    将测试通过的代码,过自动化流程部署到生产环境

    其主要目的是快速、频繁地将新的代码变更交付到生产环境,确保每次交付都是经过充分验证的。

  • 传统开发模式的问题

    • Bug总是在最后才发现
    • 越是项目后期,问题越难解决
    • 软件交付时机无法保障
    • 程序经常需要变更
    • 无效的等待变多
  • 持续集成的好处

    • 解放了重复性劳动
    • 更快地修复问题
    • 更快地交付成果
    • 减少手工的错误
    • 减少等待时间
    • 自动代码审查

2. 安装

docker run --name jenkins --user root -d --privileged -v jenkins_home:/var/jenkins_home -v /var/run/docker.sock:/var/run/docker.sock -p 8080:8080 -p 50000:50000 --restart=always jenkins/jenkins:lts-jdk17

# 查看初始密码
docker logs jenkins

3. 后台设置

  1. 选择插件来安装,直接X掉,先什么插件都不安装
  2. 配置账户和端口,时区
  3. 修改镜像源:dashboard/manage jenkins,plugins/advanced settings,update site
https://mirrors.huaweicloud.com/jenkins/updates/update-center.json
  1. 安装中文包:继续在 available plugins搜索 chinese 安装时勾选,安装完后自动重启

4. 第一个任务:HolleWorld

  1. 新建任务,选择自由风格,名称 Hello World
  2. 在 Build Steps,增加构建步骤
  3. 选择执行shell,输入echo “Hello World”
  4. 查看 控制台输出

四、Gogs Webhook

  1. 安装 Gogs Trigger 插件

  2. 配置 Jenkins 项目,勾选"Build when a change is pushed to Gogs” 选项

  3. 在Git仓库,仓库设置,管理web钩子,推送地址输入

    http://192.168.16.135:8080/gogs-webhook/?job=test

  4. 如果是本地地址,需要修改gogs,app.ini配置

    # gogs/conf/app.ini
    
    [security]
    INSTALL_LOCK = true
    SECRET_KEY   = e7jXaUG3Xdx2HTA
    LOCAL_NETWORK_ALLOWLIST = 192.168.16.135,localgogs
    
    docker restart gogs
    

    配置完成,可以点击“测试推送”按钮看看是否启动自动构建任务

    ps:Gogs似乎太不安全,不需要身份令牌,也没有其他凭证

五、构建.net应用

  1. jenkins与宿主机共用docker
# 1. 修改 docker.sock 权限
# /var/run/docker.sock 文件是 docker client 和 docker daemon 在本地进行通信的 socket
文件。默认的组为 docker,且 other 用户不具有读写权限
chown root:root docker.sock
chmod o+rw docker.sock

# 2. 强制删除容器
docker stop jenkins & docker rm jenkins

# 3. 重启启动
docker run --name jenkins -u root -d --privileged -v jenkins_home:/var/jenkins_home -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /etc/docker/daemon.json:/etc/docker/daemon.json -p 8080:8080 -p 50000:50000 --restart=always jenkins/jenkins:lts-jdk17

# 测试
docker exec -it jenkins bash
docker info
  1. 流水线任务:自由风格一般用于测试和学习,它存在如下一些问题
  • 构建过程整体流程不可见,无法确认每个流程的时间花费
  • 构建过程出现问题时不好查
  • 构建任务本身无法版本化管理
  1. 构建pipeline script,发布本地应用
pipeline {
    agent any
    stages {
        stage('拉取源代码') {
            steps {
                git 'http://192.168.16.135:3000/171493399/test.git'
            }
        }
        stage('构建镜像') {
            steps {
                sh 'docker build -f "./Test.HttpApi/Dockerfile" -t "test-httpapi" .'
                sh 'docker images --filter "reference=test-httpapi"'
            }
        }
        stage('删除容器') {
            steps {
                sh 'docker rm -f test-httpapi'
            }
        }
        stage('运行容器') {
            steps {
                sh 'docker run -d --name test-httpapi -p 80:8080 test-httpapi'
                sh 'docker ps --filter "name=test-httpapi"'
            }
        }
    }
}
  1. jenkinsfile文件
将上面pipeline脚本保存为jenkinsfile文件,跟源码一起进行版本化管理
如果构建失败,检查一下jenkinsfile文件的编码是否是utf-8
  1. CI/CD标准流程
  • 三个分支,dev分支拉过来,sonarqube 对代码质量检测,完成自动集成测试,合并到预览分支
  • 预览分支的监控触发,拉预览分支代码,生成测试版。
  • 测试通过后,合并到主分支, 拉取,最终版本,自动部署到生产服务器任务

六、发布应用

三个系列的.NET运行时基础映像:

  • 标准映像aspnet:8.0

    • 基础系统 Debian
    • 特点:NET运行时所有的依赖,.NET或ASP.NET 运行时
    • 适用场景:大多数 ASP.NET Core 应用程序的默认选择,提供完整的兼容性和功能
  • 轻量化映像:

    • aspnet:8.0-jammy-chiseled 或 aspnet:8.0-jammy-chiseled-extra
      • 轻量化的Linux发行版,Ubuntu Jammy (22.04) 精简版
      • 特点:Ubuntu 基础,但经过精简,移除了非必要组件
      • 适用场景:需要 Ubuntu 兼容性但希望减小镜像大小的场景
      • 安全特性:默认非 root 用户,减少攻击面
      • extra:在 chiseled 基础上添加了额外组件(如 ICU、SSL 证书等)
    • Alpine
      • 内置MUSL标准库、Busybox、包管理工具
      • 特点:基于轻量级 Alpine Linux,显著减小镜像大小
      • 适用场景:对镜像大小敏感的生产环境,需要最小化攻击面的场景
      • 注意:可能缺少某些库,需要测试应用兼容性
  • 运行时依赖映像:轻量型映像作为基础系统,只安装了.NET运行时的依赖

.NET发布方式

  1. 独立应用:

    • IL裁剪
    • .NET全球化固定模式:强制应用程序在所有地区上表现相同
  2. 依赖运行时的应用

  3. 选择建议

  • 开发环境:使用标准镜像,以获得最佳兼容性
  • 生产环境:优先考虑 alpine 或 chiseled 以减少大小和攻击面
  • 需要特定库:如果应用依赖 Ubuntu 特定库,选择 jammy 变体
  • 安全关键应用:考虑 chiseled 系列,它们默认以非 root 用户运行
docker pull mcr.microsoft.com/dotnet/aspnet:8.0
docker pull mcr.microsoft.com/dotnet/aspnet:8.0-alpine
docker pull mcr.microsoft.com/dotnet/aspnet:8.0-jammy-chiseled
docker pull mcr.microsoft.com/dotnet/aspnet:8.0-jammy-chiseled-extra
// 完整运行时镜像
docker pull mcr.microsoft.com/dotnet/runtime:8.0
// 仅包含运行 .NET 应用程序所需的操作系统依赖项,下面镜像通常用于构建自包含的应用程序
docker pull mcr.microsoft.com/dotnet/runtime-deps:8.0-alpine
docker pull mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy-chiseled
docker pull mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy-chiseled-extra
  1. dockerfile区别
# jammy-chiseled系列

FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./Test.HttpApi.csproj" -c $BUILD_CONFIGURATION -o /app/publish \
	--runtime linux-x64 \ 
	--self-contained true \
    -p:PublishSingleFile=true
	
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy-chiseled AS final
USER app
WORKDIR /app
EXPOSE 8080
COPY --from=publish /app/publish .

ENTRYPOINT ["./Test.HttpApi"]

# alpine环境, 推荐使用alpine

FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./Test.HttpApi.csproj" -c $BUILD_CONFIGURATION -o /app/publish \
	--runtime linux-x64 \ 
	--self-contained true \
    -p:PublishSingleFile=true
	
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy-chiseled AS final
USER app
WORKDIR /app
EXPOSE 8080
COPY --from=publish /app/publish .

ENTRYPOINT ["./Test.HttpApi"]

七、实现弹性伸缩

  1. 必要插件
  • Docker Pipeline
  • Gogs Plugin
  • Docker Plugin
  • Workspace Cleanup
  1. 需要修改 Docker 配置以允许不安全注册表
vim /etc/docker/daemon.json 
{
  "insecure-registries":["192.168.16.135"]
}
# 重读文件,并重启服务
systemctl daemon-reload
systemctl restart docker
  1. 添加Jenkins访问Harbor的凭证
导航到凭证管理/全局凭证,添加新凭证
选择类型为 "Username with password"
填写以下信息:
	* 范围 (Scope): 保持默认 (Global)
	* 用户名 (Username): admin
	* 密码 (Password): Harbor12345
	* ID: 可以留空或指定一个易记的 ID (harbor-creds)
	* 描述: 可选的描述信息 (如 "Harbor credentials")
  1. 配置SSH,Jenkins连接到 Swarm 节点
# 如果只是需要通过链接到swarm管理节点来执行 docker service命令,以下操作可以只在swarm管理节点上配置
# 如果需要jenkins清理旧镜像,所有节点上就都需要配置了
# 建议是都配置,以便不时之需

# 1. 在 Swarm 所有节点上执行
sudo adduser jenkins-agent
密码,其他都直接回传
# 允许新用户执行docker命令
sudo usermod -aG docker jenkins-agent

# 2. 在 Jenkins 宿主机上生成 SSH 密钥对
# 这里的jenkins@docker就是一个标识名称,可以是任意字符串
ssh-keygen -t rsa -b 4096 -C "jenkins@docker"
再直接输入3个回车

# 3. 将公钥复制到 Swarm 所有节点
cat /root/.ssh/id_rsa.pub
复制出所有内容
-----BEGIN OPENSSH PRIVATE KEY-----
下面的echo命令内的内容为密钥内容
-----END OPENSSH PRIVATE KEY-----

# 4. swarm 所有节点上添加公钥
mkdir -p ~/.ssh
echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCkppSFD6KqygeA5PK9zLX3JRkpfXSbbJWL9pUcGDhz/e2NLXh6L/qKrujLFLuvq+ODDQZFpB3ndgV11OKNx2ntZ2PqM+kBrw1Pi1KK9zg7tM22eaXg9EQTLPpx5eGrgMG8cRWiBLUjwxlf4N1wLAVSBdgG3Yid22oQwU5ehQEtlHaJK/xk8U9e3IwNUGPmAaV2IPPuUc6YA8KjSBP5iSWMHkgabUPQI55RC3r0xA2fTANO/ggswN48/EbEth19g+kzpNnbD/P9DGbewOow/iIzswfUIss0BYmDpOVngF0Obge7vuYR8ytxut2f17RojJA6SaajMfYFbgt/WuTgXNY4QxmwpcMAdyLLqg2p4pzHC5ernkHSWJ4KG4b0Aqtr6ztLySh3SWuKtiDWwQJh8oNCg2Q3cLZ0efwObegWJFZgvbDXe5UOlbBDfQZpukLqSM4VBl3Us86IYGQ4Yhq0VWTUrnMolMLrvrqhSj9bEX4nvWAXLRklWh0cPYZ8bgb96FVuVyeQkLYH6Xc2PrkUT6OGn3OOq8ToFy6wyG+MdBUP1Y6vEMUdilPWEVpnbucCdb4ZG/bn8tBcigE0mfpBb3sNyzajMKwyb+GrK1vWaB2Hn9YpU868ZxFolrL9kL7WN6aDoZ4Eh6Z4xu1N/ThQd3A30QUBfvLE0D0rpe1gNfTCZQ== jenkins@192.168.16.129"  | sudo tee ~/.ssh/authorized_keys
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chown -R $USER:$USER ~/.ssh

# 测试
ssh -i /root/.ssh/id_rsa jenkins-agent@192.168.16.129
输入yes 和 密码

# 5. swarm 所有节点上配置只允许公钥登录
vim /etc/ssh/sshd_config
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
PasswordAuthentication no	# 这个设置后ssh客户端就无法用密码登录了,测试阶段可不设置,生产环境强烈要求关闭
ChallengeResponseAuthentication no
# 重启SSH服务
sudo systemctl restart sshd

# 检查认证日志(在连接失败后)
sudo tail -f /var/log/auth.log
  1. 初始化应用
# 基础版
docker service create --name test-httpapi \
  --with-registry-auth \
  --replicas 3 \
  --publish 8011:8080 \
  192.168.16.135/test/test-httpapi:1
  
# 完整版
docker service create --name test-httpapi \
  --with-registry-auth \
  --replicas 3 \
  --publish 8011:8080 \
  # 核心滚动更新参数(必须添加)
  --update-parallelism 1 \
  --update-delay 20s \
  --update-order start-first \
  # 自动回滚参数(必须添加)
  --update-failure-action rollback \
  --update-max-failure-ratio 0.25 \
  --rollback-parallelism 1 \
  --rollback-delay 10s \
  --rollback-monitor 30s \
  # 推荐添加的运维参数
  --limit-memory 512M \
  --restart-condition on-failure \
  --restart-delay 10s \
  --restart-max-attempts 3 \
  --health-cmd "curl -f http://localhost:8080/health || exit 1" \
  --health-interval 10s \
  --health-retries 3 \
  --health-timeout 5s \
  # 原始镜像
  192.168.16.135/test/test-httpapi:1

# 删除服务的指令
docker service rm test-httpapi

# 查看服务配置
docker service inspect --pretty test-httpapi

# 更新服务的指令,集成在pipeline中
docker service update \
  --with-registry-auth \
  --detach=false \
  --image ${imageRef} \
  --container-label-add commit=${env.GIT_COMMIT} \
  --container-label-add build=${env.BUILD_NUMBER} \
  ${env.APP_NAME}
  • 滚动更新核心配置(必须)

    • --update-parallelism 1:每次只更新1个副本

    • --update-delay 20s:更新批次之间等待20秒

    • --update-order start-first:先启动新容器再停止旧容器(零宕机)

  • 自动回滚配置(必须)

    • --update-failure-action rollback:更新失败时自动回滚

    • --update-max-failure-ratio 0.25:1个副本更新失败即触发回滚(3副本场景)

    • --rollback-parallelism 1:回滚时逐个处理副本

    • --rollback-monitor 30s:监控回滚容器健康30秒

  • 健康检查配置(强烈推荐,需要应用程序添加health中间件)

    • --health-cmd "curl -f http://localhost:8080/health || exit 1"
    • --health-interval 10s
    • --health-retries 3
    • --health-timeout 5s
  • 使用start-first策略时,Swarm会等待新容器通过健康检查后才停止旧容器。如果未设置健康检查,Swarm会立即认为新容器已就绪(即使应用未准备好)

  • 资源限制和重启策略

    • --limit-memory 512M:防止内存泄漏影响主机
    • --restart-condition on-failure:非正常退出时自动重启
    • --restart-max-attempts 3:最多重启尝试次数
  1. 修改Jenkins启动命令
# 1. 强制删除容器
docker stop jenkins & docker rm jenkins

# 2. 重启启动
docker run --name jenkins -u root -d --privileged -v jenkins_home:/var/jenkins_home -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /etc/docker/daemon.json:/etc/docker/daemon.json -v ~/.ssh:/var/jenkins_home/.ssh -e "SSH_AUTH_SOCK=/ssh-agent" -p 8080:8080 -p 50000:50000 --restart=always jenkins/jenkins:lts-jdk17
  1. 流水线脚本
pipeline {
    agent any
    
    environment {
        HARBOR_URL = "192.168.16.135"
        PROJECT_NAME = "test"
        APP_NAME = "test-httpapi"
        DOCKER_SWARM = "192.168.16.129"
        GIT_URL = "http://192.168.16.135:3000/171493399/test.git"
	    DOCKERFILE_PATH = "./Test.HttpApi/Dockerfile"
	    IMAGE_TAG = "${HARBOR_URL}/${PROJECT_NAME}/${APP_NAME}:${env.BUILD_NUMBER}"
	    SWARM_NODES = "192.168.16.129,192.168.16.131,192.168.16.132"  // 所有Swarm节点IP,用逗号分隔
        KEEP_COUNT = 5  // 保留的镜像数量
    }
    
    stages {
        stage('拉取代码') {
            steps {
                git url: "${GIT_URL}", branch: "master"
            }
        }
        
        stage('构建并上传镜像') {
            steps {
                script {
                    docker.withRegistry("http://${HARBOR_URL}", "harbor-creds") {
                        // 构建完整镜像标签
                        def customImage = docker.build(env.IMAGE_TAG, "-f ${DOCKERFILE_PATH} .")
						
						// 推送镜像
                        customImage.push()
                    }
                }
            }
        }
        
        stage('滚动更新') {
            steps {
                script {
            		docker.withRegistry("http://${HARBOR_URL}", "harbor-creds") {
                        sh """
                        	ssh -i /var/jenkins_home/.ssh/id_rsa jenkins-agent@${DOCKER_SWARM}  \
                        	docker service update \
                              --with-registry-auth \
                              --detach=false \
                              --image ${IMAGE_TAG} \
                              --container-label-add commit=${env.GIT_COMMIT} \
                              --container-label-add build=${env.BUILD_NUMBER} \
                              ${APP_NAME}
                        """
                    }
                }
            }
        }

		stage('清理旧镜像') {
            steps {
                script {
                    // 1. 清理Jenkins本地镜像
            		// docker images --filter reference="xx" | sort -V | head -n -5 | xargs docker rmi -f
            		// xargs -r 确保无镜像时不会报错
                    sh """
                        docker images --format '{{.Repository}}:{{.Tag}}' \
                            --filter reference="${HARBOR_URL}/${PROJECT_NAME}/${APP_NAME}" \
                        | sort -V | head -n -${KEEP_COUNT} \
                        | xargs -r docker rmi -f || true
                    """
                    
                    // 2. 清理Swarm集群节点镜像
                    def nodes = env.SWARM_NODES.split(',')
                    nodes.each { node ->
                        sh """
                            ssh -i /var/jenkins_home/.ssh/id_rsa jenkins-agent@${node} /bin/sh <<'EOF'
                            # 清理旧镜像
                            docker images --format '{{.Repository}}:{{.Tag}}' \
                                --filter reference="${HARBOR_URL}/${PROJECT_NAME}/${APP_NAME}" \
                            | sort -V | head -n -${KEEP_COUNT} \
                            | xargs -r docker rmi -f || true
                            
                            # 删除残留的<none>镜像
                            docker images -f "dangling=true" -q | xargs -r docker rmi -f || true
                            EOF
                        """

					// 3. Harbor自带回收功能,可以在管理页面上配置自动删除历史镜像
                    }
                }
            }
        }
    }
    
    post {
        always {
            // 清理Jenkins工作空间
        	// Git 拉取的源代码,构建过程中生成的所有文件,编译产物,Docker 构建上下文文件,临时文件、日志文件等
            cleanWs()
        }
    }
}

补充

  • 后续还可以使用sonarqube进行代码质量检测,添加通知钉钉、邮箱等
  • 除了使用ssh连接集群外,还可以使用直接使用Docker Socket,或者把Jenkins的宿主机加入docker swarm集群等多种方式。ssh也有很多配置方式,可自行研究。
  • 应用部署也可以换成docker compose的方式