1、Jenkins + docker + ssh 实现 CI/CD 流程

在本地 IDEA 中编写好项目,通过 Git 上传代码到 GitHub;

GitHub 的 webhook 监听到项目的指定分支 /master 有代码更新,通知 Jenkins 开始工作;

Jenkins 接收到指令开始拉取 GitHub 项目分支下的最新代码,然后开始使用 mvn 打包编译;

编译完成后,jenkins 根据项目根目录下的 dockerfile 文件打包项目的镜像文件,并将镜像文件上传到镜像仓库;

Jenkins 上传镜像完成后,通过 ssh 连接到 Linux 服务器,执行 docker 拉取镜像,运行镜像的指令。

2、环境准备

特别声明:本示例中 jenkins 服务器、docker 镜像仓库服务器、开发/测试/生产 服务器 皆是我本地 windows 11环境。本地 windows 11 安装有 docker desktop,运行相应的服务镜像,并通过 frp 建立与阿里云服务器的 TCP 通道,将服务端口暴露到了阿里云服务器。

2.1、Jenkins 服务器准备

1、创建 jenkins.dockerfile 文件,根据官方 jenkins 镜像,打包进去 Docker CE CLI 功能,便于执行 docker 命令。

# 基于 jenkins 官方镜像构建
FROM jenkins/jenkins:2.564-jdk21
# 切换到 root 用户
USER root
# 更新 apt 源,安装 lsb-release,lsb-release 是 Linux 上用来查看发行版标识信息的工具
RUN apt-get update && apt-get install -y lsb-release
# 添加 Docker 官方 GPG 密钥,有了这个密钥,才能从 Docker 官方源下载安装 Docker CE CLI
RUN curl -fsSLo /usr/share/keyrings/docker-archive-keyring.asc \
  https://download.docker.com/linux/debian/gpg
# 添加 Docker 官方源,这个源是 Docker 官方提供的,包含了 Docker CE CLI 的安装包
RUN echo "deb [arch=$(dpkg --print-architecture) \
  signed-by=/usr/share/keyrings/docker-archive-keyring.asc] \
  https://download.docker.com/linux/debian \
  $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list
# 更新 apt 源,安装 Docker CE CLI
RUN apt-get update && apt-get install -y docker-ce-cli
# 切换到 jenkins 用户
USER jenkins
# 安装 Jenkins 插件,blueocean 是 Jenkins 的蓝海项目,docker-workflow 是 Jenkins 的 Docker 工作流插件,json-path-api 是 Jenkins 的 JSON 路径 API 插件
RUN jenkins-plugin-cli --plugins "blueocean docker-workflow json-path-api"

2、创建 jenkins-compose.yaml 文件,编排 docker 容器,将 jenkins 和 docker:dind 服务一起打包运行。其中,docker:dind 为 jenkins 提供 docker 的各项功能。

version: '1.0'

services:
  jenkins-docker:
    # 1. 启动 docker in docker ,给 jenkins 提供 docker 能力
    image: docker:dind
    container_name: jt-docker-dind
    restart: always
    privileged: true # 给 docker 容器最高权限,可以执行 docker 命令,否则在容器中无法启动 docker
    volumes:
      - D:\work\env\docker\jenkins\certs:/certs/client
      - D:\work\env\docker\jenkins\data:/var/jenkins_home
    networks:
      - jt_jenkins_network
    ports:
      - 2376:2376
    environment:
      - DOCKER_TLS_CERTDIR=/certs  #开启 Docker TLS 安全认证,证书放在 /certs
       # ✅ 关键:让证书包含你的公网IP,这样外网才能连接
      - DOCKER_TLS_SAN=IP:[我的公网IP],IP:127.0.0.1,DNS:jenkins-docker
      # ✅ 关键:监听 0.0.0.0 而不是内部IP,允许公网连接
      - DOCKER_OPTS=--host=tcp://0.0.0.0:2376

  jenkins:
  # 构建 jenkins 容器,使用本地 dockerfile 构建的 jenkins 镜像
    build:
      context: .
      dockerfile: jenkins.dockerfile
    container_name: jt-jenkins
    # 在容器启动时,如果容器启动失败,则重启容器
    restart: on-failure
    volumes:
      - D:\work\env\docker\jenkins\data:/var/jenkins_home
      - D:\work\env\docker\jenkins\certs:/certs/client:ro  # ro = 只读
    networks:
      - jt_jenkins_network
    ports:
      - 8080:8080
      - 50000:50000
   # 设置 jenkins 容器的环境变量,使得 jenkins 容器可以访问 docker 容器
    environment:
      # 设置 jenkins 容器可以访问 docker 容器的地址
      - DOCKER_HOST=tcp://jenkins-docker:2376
      # 设置 jenkins 容器可以访问 docker 容器的证书路径
      - DOCKER_CERT_PATH=/certs/client 
      # 设置 jenkins 容器可以访问 docker 容器的 TLS 认证
      - DOCKER_TLS_VERIFY=1
    depends_on:
      - jenkins-docker

networks:
  jt_jenkins_network:
    driver: bridge

3、执行 docker-compose -f D:\work\env\docker\jenkins\jenkins-compose.yaml up -d 启动 jenkins 容器。

4、安装 jenkins 基础配置,参考 jenkins 安装文档。

5、在 frpc 中开启 Jenkins 端口映射,并在阿里云服务器上配置:子域名、nignx 代理,ssl 证书,使 jenkins.jtworld.cn 域名指向本机 jenkins 服务。

6、在 frpc 中开启 jenkins 的 docker:dind 端口映射,并在阿里云服务器上配置 TCP 端口,为 jenkins 提供docker 服务,如 build、push、pull 等。

2.2、docker 镜像仓库服务器准备

1、创建 docker-registry-compose.yaml 文件,其中包括:httpd、registry、registryui 三个容器服务,registry 是镜像仓库,registryui 是 ui 界面,httpd 为镜像仓库配置登录需要的用户名+密码。

services:
  # 自动生成账号密码的初始化容器
  registry-auth:
    image: httpd:2.4.67
    container_name: jt-registry-auth
    restart: on-failure:3
    networks:
      - jt_registry_network
    volumes:
      - D:\work\env\docker\registry\registry\auth:/auth
    # 在这里改你的 用户名 和 密码
    command: >
      sh -c "
      mkdir -p /auth &&
      htpasswd -Bbn [用户名] [密码] > /auth/htpasswd
      "
  # 镜像仓库容器
  registry:
    image: registry:3.1.1
    container_name: jt-registry
    restart: on-failure:3
    networks:
      - jt_registry_network
    volumes:
      - D:\work\env\docker\registry\registry\lib:/var/lib/registry # 镜像数据、仓库元数据
      - D:\work\env\docker\registry\registry\auth:/auth  # 挂载密码文件夹
    ports:
      - 15000:5000
    environment:
      - REGISTRY_STORAGE_DELETE_ENABLED=true  # 允许删除镜像
      # 开启认证 ↓↓↓
      - REGISTRY_AUTH=htpasswd
      - REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm
      - REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd
    depends_on:
      - registry-auth
# 镜像仓库 UI 容器
  registry-ui:
    image: joxit/docker-registry-ui:2.6.0-debian
    container_name: jt-registry-ui
    restart: on-failure:3
    networks:
      - jt_registry_network
    ports:
      - 15001:80
    environment:
      - TZ=Asia/Shanghai
      - SINGLE_REGISTRY=true
      - REGISTRY_TITLE=JT 私有镜像仓库
      - DELETE_IMAGES=true                   # 允许界面删除镜像
      # - REGISTRY_URL=http://localhost:15001    # 指定ip可以访问镜像仓库,不配置则允许所有ip访问
      - NGINX_PROXY_PASS_URL=http://registry:5000
       # 开启 UI 登录 ↓↓↓
      - REGISTRY_SECURED=true
    depends_on:
      - registry

networks:
  jt_registry_network:
    driver: bridge

2、执行 docker compose -f D:\work\env\docker\registry\docker-registry-compose.yaml up -d 启动 docker 镜像仓库容器。

3、在 frpc 中开启端口映射,并在阿里云服务器上配置:子域名、nignx 代理,ssl 证书,使 registry.jtworld.cn 域名指向本机 registry 服务;registryui.jtworld.cn 指向镜像的 ui 页面。

4、验证私有镜像仓库是否安装成功:
执行:docker login --username=[用户名] --password=[密码] registry.jtworld.cn 登录镜像仓库
执行:docker tag [本地的 ImageId] registry.jtworld.cn/[镜像名称]:[镜像版本号] 修改本地镜像名称
执行:docker push registry.jtworld.cn/[镜像名称]:[镜像版本号] 推送镜像到镜像仓库

2.3、Windows11 开发/测试/生产 服务器准备

1、开启 ssh 服务,便于 jenkins 通过 ssh 执行指令。

以管理员身份运行 PowerShell,执行:Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH*' ,执行结果如下:

Name  : OpenSSH.Client~~~~0.0.1.0
State : Installed  # 已安装

Name  : OpenSSH.Server~~~~0.0.1.0
State : NotPresent # 未安装

安装 SSH 客户端,执行:Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0
启动 SSH 客户端,执行:Start-Service sshd
设置开机自启动,执行:Set-Service sshd -StartupType Automatic

2、在 frpc 中开启端口映射,并在阿里云服务器上开放端口号。

3、验证 Windows11 的 SSH 服务是否安装成功。

在阿里云服务器上执行:ssh -p [端口号] [用户@qq.com]@localhost 因为我是通过 frp 进行映射的所以 @ 后面选择本地,如果是有公网 IP 的服务器,直接选择对应 IP。

4、windows11 中 docker desktop 凭证管理

window11 中的 docker desktop 执行 docker login 命令时,登录的凭证默认交由 “windows凭证” 管理,在通过 ssh 执行 docker 登录命令时,无法使用 “window凭证” 中存储的 docker 登录凭证。

修改 C:\Users\25472\.docker\config.json 文件,将 "credsStore": "desktop" 配置删除后,docker 登录的凭证就会直接存储在 config.json 文件中,从而实现远程 ssh 连接 windows11 执行 docker 登录指令可以成功。

{
	"auths": {
		"crpi-sbxf6opn90884xtq.cn-beijing.personal.cr.aliyuncs.com": {},
		"localhost:15000": {
			"auth": "anQ6anQxMjM0NTY="
		},
		"registry.jtworld.cn": {
			"auth": "anQ6anQxMjM0NTY="
		}
	},
	"credsStore": "desktop", // 删除该行
	"currentContext": "desktop-linux",
	"plugins": {
		"-x-cli-hints": {
			"enabled": "true"
		},
		"ai": {
			"hooks": "build,buildx build,run,compose"
		},
		"debug": {
			"hooks": "exec"
		},
		"scout": {
			"hooks": "pull,buildx build"
		}
	},
	"features": {
		"hooks": "true"
	}
}

2.4、GitHub 配置

1、生成 Personal access tokens (classic) 用于访问 GitHub API,执行 git 操作。

登录 GitHub ⇨ 点击用户头像 ⇨ 选择 Developer settings ⇨ 选择 Personal access tokens > Tokens(classic) ⇨ 选择 Generate new token (classic) ⇨ 选择配置如下图:生成的 token 妥善保管

2、jenkins 服务配置 SSH 用于访问 GitHub。

1、以 root 用户身份进入 jenkins 容器中:docker exec -u 0 -it jt-jenkins /bin/bash
2、进入 jenkins 用户:su - jenkins
3、生成 ssh 公钥/私钥 :ssh-keygen -t rsa
4、将 ssh 公钥粘贴到 github 的 SSH and GPG keys 配置中:cat /var/jenkins_home/.ssh/id_rsa.pub
5、配置 known_hosts ,告诉 jenkins 此 github 是可信任的:
vim /var/jenkins_home/.ssh/known_hosts ,配置参考:GitHub 的 SSH 密钥指纹 - GitHub Enterprise Cloud Docs

github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=

3、Jenkins 系统配置

3.1、凭据管理

3.1.1、docker 镜像仓库凭据

1、Jenkins 设置 ⇨ 系统管理 ⇨ 凭据管理 ⇨ Username with password

2、输入 docker 镜像仓库登录的用户名和密码

3.1.2、为 jenkins 提供远程 docker API 服务的凭证

1、Jenkins 设置 ⇨ 系统管理 ⇨ 凭据管理 ⇨ X.509 Client Certificate

2、配置 CA、Cert、Key

根据 2.1jenkins-compose.yaml 的配置 D:\work\env\docker\jenkins\certs:/certs/client 可知,docker:dind 中 ca、cert、key 文件挂载在 D:\work\env\docker\jenkins\certs 目录下,将相应的数据填入对应的配置中。

docker:dind 为 jenkins 提供 docker 能力,比如 build、push、pull 等,使用 docker:dind 提供的能力需要用到该凭证。

3.1.3、GitHub Webhook 凭证

1、Jenkins 设置 ⇨ 系统管理 ⇨ 凭据管理 ⇨ Secret text

2、将 2.4 > 1 中,在 github webhook 生成的 Personal access tokens (classic) 填入,该凭证用于 github 通知 jenkins 有代码更新

3.1.4、GitHub SSH 凭证

1、Jenkins 设置 ⇨ 系统管理 ⇨ 凭据管理 ⇨ SSH Username with private key

2、将 2.4 > 2 中,在 jenkins 中生成的 SSH 私钥添加到凭证,用于拉取 GitHub 仓库代码使用,其中 ssh 私钥文件位置:D:\work\env\docker\jenkins\data\.ssh\id_rsa

3.2、插件管理

下载 Docker plugin 插件

下载 Publish Over SSH 插件

下载 Maven Integration plugin 插件

3.3、系统配置

3.3.1、Jenkins Location 配置

3.3.2、GitHub 配置

其中,API URL,如果使用公共的 GitHub 项目填入 https://api.github.com,如果是自己公司的 GitHub 填自己公司的地址。

3.3.3、Windows 开发服务器 SSH 连接配置

其中,Jump host 是跳板机,生产环境下,公司内网或云服务器为了安全会设置网络隔离,此时 Jenkins 只可以通过跳板机来访问。

3.3.4、Clouds 配置

docker clouds 配置

4、Jenkins 流程配置

4.1、Maven 任务

1、新建任务

2、General

3、源码管理

4、Triggers(默认)

5、Environment(默认)

6、Pre Steps(默认)

7、Build

8、Post Steps

构建成功后执行一些操作

docker 配置,推送镜像

SSH 连接开发服务器,执行命令

docker login --username=[用户名] --password=[密码] registry.jtworld.cn && docker pull rexxxry.xxxorld.cn/myboot:1.0 && docker run -id --name myboot -p 9080:9080 registry.jtworld.cn/myboot:1.0

执行 K8s 命令,在开发服务器的 k8s 的 node 中的 pod 运行 springboot 程序

kubectl create secret docker-registry jt-registry --docker-server=localhost:15000 --docker-username=[用户名] --docker-password=[密码] && kubectl apply -f D:\work\env\docker\K8s\myboot.yaml 

程序配置文件 myboot.yaml 如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myboot
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myboot
  template:
    metadata:
      labels:
        app: myboot
    spec:
      containers:
      - name: myboot
        image: localhost:15000/myboot:1.0        # 你的镜像名
        ports:
        - containerPort: 9080  # 你的项目端口
---
apiVersion: v1
kind: Service
metadata:
  name: myboot
spec:
  selector:
    app: myboot
  ports:
  - port: 80         # Service 端口
    targetPort: 9080 # 容器端口(你的 9080)

配置 ingress 网关对外提供网关端口:kubectl apply -f D:\work\env\docker\K8s\ingress.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: jt-ingress
spec:
  ingressClassName: nginx # 关键,绑定 ingress-nginx 控制器
  rules:
  - host: localhost # 本地域名,随便写
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: myboot
            port:
              number: 80