从源码到容器:实战搭建全链路DevOps流水线
前言:从“人肉运维”到自动化交付
三年前,我还在用shell脚本做部署,凌晨三点接到报警电话,跑到公司重启服务器。今天,代码提交后自动走完整条流水线,十分钟后新功能就在线上了。这套全链路DevOps方案,是我踩了无数坑、熬了好几个通宵搞出来的,现在分享给你。
一、我的技术栈选型之路
1.1 为什么不用GitHub用GitLab?
刚开始我也考虑过GitHub,毕竟名气大。但后来选了GitLab,原因很实际:
- 私有部署:公司代码放别人服务器上,领导不放心
- 一体化:GitLab CI/CD、容器仓库、Wiki都有了,不用到处搭
- 成本:社区版够用,不用花钱
不过有个坑得注意:GitLab内存吃得厉害,最少给4G,不然卡得你怀疑人生。
1.2 Jenkins vs GitLab CI
GitLab有CI为啥还用Jenkins?我这么配:
- 用GitLab CI做代码质量检查(轻量,触发快)
- 用Jenkins做复杂流水线(插件多,生态成熟)
说白了就是:简单的活让GitLab干,复杂的大活交给Jenkins。
1.3 Harbor:私有的才是放心的
Docker官方仓库很简单,但Harbor多了几个杀手锏:
- 漏洞扫描:推上去的镜像自动扫一遍,有漏洞不给过
- 复制同步:开发、测试、生产三套仓库,一键同步
- 权限控制:谁只能看,谁能下载,谁能上传,分得明明白白
二、实战部署:手把手搭建
2.1 服务器准备
# 电脑选择
1、MacBook Pro 16G + Docker Desktop内存,够用了
2、或者Windows11 16G内存 + WSL + Ubuntu 20.04
# Linux服务器配置(我本地两台4C16G的服务器,当然4c8g也能跑起来)
机器A:Docker + GitLab + Jenkins
机器B:Docker + Harbor + 中间件
!!!别用CentOS!别用CentOS!别用CentOS!重要的事情说三遍。用Ubuntu 20.04,这是最稳的。
2.2 服务器初始化
2.2.1 既然从0开始,肯定是从服务器资源准备开始的,创建2台Ubuntu虚拟机

2.2.2 配置root密码
sudo -i
passwd

2.2.3 配置普通用户sudo时免密
命令行输入: visudo
#在最后配置
dsg ALL=(ALL) NOPASSWD: ALL
保存退出:
按 Ctrl + X
按 Y确认保存
按 Enter确认文件名

2.2.4 验证
sudo whoami

2.2.5 更新软件,开启ssh远程连接
sudo apt update
sudo apt install -y unzip zip telnet vim curl #顺便下载我常用的几个命令
sudo apt install -y openssh-server
ss -lnpt

此时22端口已经开启,看一下本机IP,使用远程工具连一下(这里我使用的远程工具为tabby)


已经成功连接,接下来就可以在本机客户端访问服务器了
2.2.7 设置静态IP
# 查看当前IP
dsg@dsg01:~$ hostname -I

# 查看配置文件
dsg@dsg01:~$ cd /etc/netplan/
dsg@dsg01:/etc/netplan$ ls
01-network-manager-all.yaml
dsg@dsg01:/etc/netplan$ cat 01-network-manager-all.yaml
# Let NetworkManager manage all devices on this system
network:
version: 2
renderer: NetworkManager
dsg@dsg01:/etc/netplan$

# 备份配置文件
dsg@dsg01:/etc/netplan$ sudo cp 01-network-manager-all.yaml 01-network-manager-all.yaml.bak
dsg@dsg01:/etc/netplan$ ls
01-network-manager-all.yaml 01-network-manager-all.yaml.bak
dsg@dsg01:/etc/netplan$
# 修改后的配置文件
dob@dob:netplan$ cat 01-network-manager-all.yaml
network:
version: 2
renderer: networkd
ethernets:
ens18: # 替换为你的网卡名称
dhcp4: no
addresses: [192.168.1.50/24] # IP地址/子网掩码
routes:
- to: default
via: 192.168.1.1 # 网关
nameservers:
addresses: [8.8.8.8, 1.1.1.1] # DNS服务器
dsg@dsg01:/etc/netplan$
#重启网络服务
sudo systemctl start systemd-networkd
sudo netplan apply
2.2.8 替换镜像源
sudo mv /etc/apt/sources.list /etc/apt/sources.list.back
sudo vim /etc/apt/sources.list
deb https://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse
deb-src https://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse
deb https://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse
deb-src https://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse
deb https://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse
deb-src https://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse
# deb https://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse
# deb-src https://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse
deb https://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse
deb-src https://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse
####################################################################
# 默认注释了源码镜像以提高 apt update 速度,如有需要可自行取消注释
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-updates main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-updates main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-backports main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-backports main restricted universe multiverse
deb http://security.ubuntu.com/ubuntu/ focal-security main restricted universe multiverse
# deb-src http://security.ubuntu.com/ubuntu/ focal-security main restricted universe multiverse
# 预发布软件源,不建议启用
# deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-proposed main restricted universe multiverse
# # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-proposed main restricted universe multiverse
sudo apt update && sudo apt upgrade -y
2.2.10 禁用ipv6
# 临时禁用IPv6
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1
sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1
# 永久禁用 IPv6
# 编辑 sysctl.conf 文件,在文件末尾添加以下两行配置:
sudo vim /etc/sysctl.conf
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.
2.2 Docker安装避坑指南
所有项目均部署在/data下,需要授权给普通用户
sudo mkdir /data && sudo chown -R dsg:dsg /data
2.2.1更新系统并安装依赖
sudo apt install apt-transport-https ca-certificates curl software-properties-common -y
2.2.2添加 Docker 官方 GPG 密钥
sudo mkdir -p /usr/share/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
2.2.3添加 Docker APT 仓库
# 添加Docker官方源
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# PS 使用国内镜像(如果上述Docker官方源访问慢)
# 先清理
sudo rm -f /etc/apt/sources.list.d/docker.list
# 使用阿里云镜像
echo "deb [arch=$(dpkg --print-architecture)] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
2.2.4安装 Docker 引擎
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io -y
2.2.5启动并启用 Docker 服务
#默认启动的,如未启动执行如下
sudo systemctl enable docker
sudo systemctl start docker
2.2.6验证安装
sudo docker --version
2.2.7(可选)将用户加入 docker 组
sudo usermod -aG docker $USER

# 重新连接服务器后再次执行,就不需要sudo了
docker ps

2.2.8 配置加速镜像
默认没有/etc/docker/daemon.json ,需要自行配置
#如下为注释版,daemon.json我记得是不能注释的,这里我只是给大家看一下,下面有非注释版的,可以直接复制到服务器里
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": [
"https://ywvvij6u.mirror.aliyuncs.com", // 阿里云Docker镜像加速器
"https://docker.m.daocloud.io", // 百度云Docker镜像加速
"https://mirrors.huaweicloud.com", // 中科大Docker镜像加速
"https://mirror.baidubce.com" // 网易Docker镜像加速
],
// 用于内网或自签证书的私有仓库
"insecure-registries": [
"192.168.1.51", // 自建镜像仓库的话,一般没有证书,需要提前在docker配置
"dsg.harbor.com"
],
"max-concurrent-downloads": 5, // 同时下载的最大镜像层数(推荐3-5)
"max-download-attempts": 3, // 镜像下载失败重试次数
"experimental": true, // 启用Docker实验性功能
"features": {
"buildkit": true // 启用BuildKit构建器(性能更好的构建引擎)
},
"mtu": 1500, // 网络接口MTU值(默认1500,云服务器可适当调小)
"dns": [
"8.8.8.8",
"114.114.114.114"
],
"exec-opts": [
"native.cgroupdriver=systemd" // 使用systemd作为cgroup驱动(K8s要求)
],
"log-driver": "json-file", // 使用json-file日志驱动
"log-opts": {
"max-size": "100m", // 单个日志文件最大100MB
"max-file": "3" // 保留最近3个日志文件
},
"storage-driver": "overlay2", // 推荐使用overlay2存储驱动
"data-root": "/data/docker" // Docker数据存储目录(避免占用系统盘空间)
}
EOF
# 可直接在服务器里执行
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": [
"https://ywvvij6u.mirror.aliyuncs.com",
"https://docker.m.daocloud.io",
"https://mirrors.huaweicloud.com",
"https://mirror.baidubce.com"
],
"insecure-registries": [
"192.168.1.51",
"dsg.harbor.com"
],
"max-concurrent-downloads": 5,
"max-download-attempts": 3,
"experimental": true,
"features": {
"buildkit": true
},
"mtu": 1500,
"dns": [
"8.8.8.8",
"114.114.114.114"
],
"exec-opts": [
"native.cgroupdriver=systemd"
],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
},
"storage-driver": "overlay2",
"data-root": "/data/docker"
}
EOF
# 重启docker
sudo systemctl restart docker
dsg@dsg02:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
dsg@dsg02:~$ docker info| grep Dir
Docker Root Dir: /data/docker
dsg@dsg02:~$
重启前docker数据存储

重启后docker数据存储

2.3 K8s集群搭建
网上有很多一键部署k8s单机版or集群版的教程,当然要是这位大哥说就想手搓一套k8s集群,可以进我主页,我也有写过的k8s集群部署,一主两从,这里不展开讲了。
还有就是之前本来是想写从gitlab + Jenkins + sonarqube + harbor + k8s的cicd全流程,但k8s单机版我看不上。。集群的话太耗资源了,而且考虑到也不是所有人都用得上k8s的,本文也只是想演示cicd全流程部署,重点不在k8s,所以就改成gitlab + Jenkins + sonarqube + harbor + docker实战的cicd全流程部署了,那2台机器演示也是错错有余。
三、核心组件部署:
本项目均采用docker-compose,部署
这里分享两个镜像仓库
一个是官网: https://hub.docker.com ,但需要科学上网
一个是国内渡渡鸟镜像仓库下载地址: https://docker.aityp.com , 该地址非常友好,更新也比较及时,如果你官网打不开,也可以从这里下载对应的镜像
3.1 GitLab部署
3.1.1 在/data目录下创建gitlab目录,并在gitlab目录下创建docker-compose.yaml
dsg@dsg01:~$ cd /data/
dsg@dsg01:/data$ ls
dsg@dsg01:/data$ mkdir gitlab
dsg@dsg01:/data$ cd gitlab
dsg@dsg01:/data/gitlab$ vim docker-compose.yaml
yaml文件如下
services:
gitlab:
image: 'gitlab/gitlab-ce:18.9.3-ce.0'
container_name: gitlab
restart: always
# 资源限制配置 - 新增部分
deploy:
resources:
limits:
memory: 8G # 内存硬限制,容器不能超过此值,如果服务器本身8G,这里就填6G吧
cpus: '2.0' # CPU限制,可以使用2个CPU核心
reservations:
memory: 4G # 内存软限制,系统会尽量保证此内存
cpus: '1.0' # CPU保留,保证至少有1个CPU核心
environment:
TZ: Asia/Shanghai # 设置容器内时间为上海
GITLAB_OMNIBUS_CONFIG: |
external_url 'http://192.168.1.50:8929'
gitlab_rails['gitlab_shell_ssh_port'] = 2224
gitlab_rails['time_zone'] = 'Asia/Shanghai' # 设置 GitLab web页面时区为上海
nginx['client_max_body_size'] = '250m' # 可根据需要调整上传文件大小限制
# 添加 Puma 配置
puma['enable'] = true
puma['worker_timeout'] = 60 # 使用 Puma 的 worker_timeout 配置
puma['worker_processes'] = 2 # 根据服务器资源调整 worker 进程数量
ports:
- '8929:8929'
- '2224:22'
volumes:
- './config:/etc/gitlab'
- './logs:/var/log/gitlab'
- './data:/var/opt/gitlab'
dsg@dsg01:/data/gitlab$ ls
docker-compose.yaml
3.1.2 提前下好gitlab镜像
不下载的话直接根据脚本启动服务也会自动下载的

上面我使用官方下载的镜像,下载速度太慢了,就从渡渡鸟下载


基本上下载速度就是你的网速,下载完后改一下镜像名,渡渡鸟下载的镜像名太长了,我不喜欢,就改成和官网一样了,当然不改也可以
dsg@dsg01:/data/gitlab$ docker tag swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/gitlab/gitlab-ce:18.9.3-ce.0 gitlab/gitlab-ce:18.9.3-ce.0
#删除渡渡鸟的镜像标签
dsg@dsg01:/data/gitlab$ docker rmi swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/gitlab/gitlab-ce:18.9.3-ce.0
Untagged: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/gitlab/gitlab-ce:18.9.3-ce.0
Untagged: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/gitlab/gitlab-ce@sha256:3bf06119e408607062dce1be161adb3580a0afac148bd26e144649a4ab8b9a0f
#查看已下载的gitlab镜像
dsg@dsg01:/data/gitlab$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
gitlab/gitlab-ce 18.9.3-ce.0 1c87bd6ec0d7 2 weeks ago 3.93GB
dsg@dsg01:/data/gitlab$
3.1.3 启动gitlab
dsg@dsg01:/data/gitlab$ ll
总用量 12
drwxrwxr-x 2 dsg dsg 4096 4月 12 12:49 ./
drwxr-xr-x 4 dsg dsg 4096 4月 11 21:30 ../
-rw-rw-r-- 1 dsg dsg 1266 4月 12 12:31 docker-compose.yaml
dsg@dsg01:/data/gitlab$ docker compose up -d
[+] Running 2/2
✔ Network gitlab_default Created 0.1s
✔ Container gitlab Started 0.6s
dsg@dsg01:/data/gitlab$
dsg@dsg01:/data/gitlab$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
gitlab gitlab/gitlab-ce:18.9.3-ce.0 "/assets/init-contai…" gitlab 5 seconds ago Up 5 seconds (health: starting) 80/tcp, 443/tcp, 0.0.0.0:8929->8929/tcp, 0.0.0.0:2224->22/tcp
dsg@dsg01:/data/gitlab$ ls
config data docker-compose.yaml logs
dsg@dsg01:/data/gitlab$
docker compose ps查看服务进程,在status这里能看到(health: starting),说明服务还在启动

gitlab启动很慢,大概需要4到10分钟,如果看到 status为(unhealthy) 就再等等
等看到status为(healthy),就表示服务启动了,超过20分钟还没启动就看看日志吧
这里我启动用了8分钟

3.1.4 查看gitlab初始密码
docker exec -it gitlab cat /etc/gitlab/initial_root_password

3.1.5 用浏览器访问:http://192.168.1.50:8929


3.1.6 gitlab web页面设置中文

自动保存,刷新一下页面就变成中文了

3.1.7 修改gitlab的root密码

我的习惯会在项目目录下写一个readme记录一下项目信息
dsg@dsg01:/data/gitlab$ echo "账密: root / lhw123123" > readme
dsg@dsg01:/data/gitlab$ ll
总用量 28
drwxrwxr-x 5 dsg dsg 4096 4月 12 17:04 ./
drwxr-xr-x 5 dsg dsg 4096 4月 12 15:23 ../
drwxrwxr-x 3 root root 4096 4月 12 12:56 config/
drwxr-xr-x 20 root root 4096 4月 12 13:04 data/
-rw-rw-r-- 1 dsg dsg 1266 4月 12 12:31 docker-compose.yaml
drwxr-xr-x 20 root root 4096 4月 12 13:01 logs/
-rw-rw-r-- 1 dsg dsg 27 4月 12 17:04 readme
dsg@dsg01:/data/gitlab$ cat readme
账密: root / lhw123123
dsg@dsg01:/data/gitlab$
3.1.7 创建令牌
为后面Jenkins拉取代码用,Jenkins能拉取到代码就行,就只给这个read_api和read_repository权限就行了

妥善保管令牌哦,这里我也备份一个,后面会用到
glpat-kj5VJWu358dtA1hOWbAogW86MQp1OjQH.01.0w1j3c28k

3.2 Jenkins部署
镜像我依旧是从渡渡鸟选择的,需要注意的是Jenkins官方从26年3月17开始就停止维护jdk17版本的镜像了,所以新部署的话不要选择jdk17的
swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/jenkins/jenkins:2.557-jdk21

3.2.1 在/data下创建Jenkins目录,准备docker-compose.yaml
dsg@dsg01:~$ cd /data/
dsg@dsg01:/data$ ls
docker gitlab
dsg@dsg01:/data$ mkdir jenkins
dsg@dsg01:/data$ cd jenkins/
dsg@dsg01:/data/jenkins$ vim docker-compose.yaml
services:
jenkins:
image: jenkins/jenkins:2.557-jdk21
container_name: jenkins
ports:
- 8080:8080
- 50000:50000
volumes:
- ./data/:/var/jenkins_home/
- /var/run/docker.sock:/var/run/docker.sock
- /usr/bin/docker:/usr/bin/docker
- /etc/docker/daemon.json:/etc/docker/daemon.json
- /usr/lib64/libaio.so.1:/usr/lib/x86_64-linux-gnu/libaio.so.1
environment:
- TZ=Asia/Shanghai # 设置时区为中国上海
- JAVA_OPTS=-Duser.timezone=Asia/Shanghai # 设置 Java 的时区
user: "1000:998"
3.2.2 设置Jenkins使用docker权限
既然是docker部署,后续可能要涉及到Jenkins要有执行docker命令的权限,所以上面的yaml文件里把宿主机上的 Docker 套接字权限挂载到Jenkins里,并使用与宿主机相同的 docker 组
grep docker /etc/group
docker:x:998:dsg
dsg@dsg01:/data/jenkins$
user: "1000:998" # 1000 是 jenkins 用户,998 是 docker 组
3.2.3 照例提前下好镜像,并改名
dsg@dsg01:/data/jenkins$ docker pull swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/jenkins/jenkins:2.557-jdk21
2.557-jdk21: Pulling from ddn-k8s/docker.io/jenkins/jenkins
f2a851576be3: Pull complete
263a8cb529be: Pull complete
7b1fa8cce42f: Pull complete
ba4279b40293: Pull complete
a4eb64a3877b: Pull complete
2178c34a4196: Pull complete
7ef25b9fda4a: Pull complete
a85a8b14b120: Pull complete
c110610d56c5: Pull complete
e04d921a82d3: Pull complete
31faed2f8c1e: Pull complete
1f528fa80cb4: Pull complete
Digest: sha256:54eff9992111c45e5436c589c08387a44f597701a61359fdd4043b193531d764
Status: Downloaded newer image for swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/jenkins/jenkins:2.557-jdk21
swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/jenkins/jenkins:2.557-jdk21
dsg@dsg01:/data/jenkins$ docker tag swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/jenkins/jenkins:2.557-jdk21 jenkins/jenkins:2.557-jdk21
dsg@dsg01:/data/jenkins$ docker rmi swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/jenkins/jenkins:2.557-jdk21
dockerUntagged: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/jenkins/jenkins:2.557-jdk21
Untagged: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/jenkins/jenkins@sha256:54eff9992111c45e5436c589c08387a44f597701a61359fdd4043b193531d764
dsg@dsg01:/data/jenkins$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
jenkins/jenkins 2.557-jdk21 b3a33014db78 11 days ago 482MB
gitlab/gitlab-ce 18.9.3-ce.0 1c87bd6ec0d7 2 weeks ago 3.93GB
dsg@dsg01:/data/jenkins$

3.2.4 启动镜像
dsg@dsg01:/data/jenkins$ mkdir data #先本地创建好data目录
dsg@dsg01:/data/jenkins$ docker compose up -d

3.2.5 启动后进入容器内部,执行docker ps
没问题就说明Jenkins能使用宿主机上的docker命令了
dsg@dsg01:/data/jenkins$ docker exec -it jenkins bash
jenkins@08bb61d004c9:/$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
08bb61d004c9 jenkins/jenkins:2.557-jdk21 "/usr/bin/tini -- /u…" About a minute ago Up About a minute 0.0.0.0:8080->8080/tcp, 0.0.0.0:50000->50000/tcp jenkins
42eb648c4147 gitlab/gitlab-ce:18.9.3-ce.0 "/assets/init-contai…" 4 hours ago Up 4 hours (healthy) 80/tcp, 443/tcp, 0.0.0.0:8929->8929/tcp, 0.0.0.0:2224->22/tcp gitlab
jenkins@08bb61d004c9:/$
3.2.6 然后页面访问:http://192.168.1.50:8080

由于我们已经把/var/jenkins_home/挂载到了本地Jenkins目录下的data目录,所以这个密码在这个data目录里,查看密码
dsg@dsg01:/data/jenkins$ ls
data docker-compose.yaml
dsg@dsg01:/data/jenkins$ cat data/secrets/initialAdminPassword
706b518a972c4fbc9ad63dd2698fdbf2
dsg@dsg01:/data/jenkins$

这里不要点【安装推荐的插件】,Jenkins默认下载源很慢的,直接选择后面的【选择插件来安装】

进来后默认,点安装就行

开始安装了,即使失败了也不用管,等后面进入主页后更换下载源了,都可以重新下载的

3.2.7 创建管理员用户,点击保存并完成

访问地址,默认就行

3.2.8 开始使用Jenkins

进入到Jenkins

3.2.9 设置国内下载源,下载插件

打开设置后提示
Building on the built-in node can be a security issue. You should set up distributed builds. See the documentation.
其实就是Jenkins 容器本身,默认作为构建执行的机器。直接运行构建任务,存在安全隐患(比如构建过程可能污染容器环境、权限管理风险等)。官方建议配置 分布式构建(即添加额外的“代理节点”来执行构建,让主节点只负责调度)。简单说部署主从节点,主节点监督就行。但咱只是学习一下,就无所谓了,忽略就行

选择插件管理


左边全选,然后右边点击update就可以更新插件了

有的插件更新后需要重启Jenkins服务,有的只需要重新打开就可以生效

如果下载报错,或者更新较慢,可选择在这里替换下载源,我下载的这一版本Jenkins用下来到没有什么下载报错的问题,所以如果你用的Jenkins版本和我一样,应该是不需要换源的
清华大学:https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json
华为开源镜像站:https://mirrors.huaweicloud.com/jenkins/updates/update-center.json
腾讯云:https://mirrors.cloud.tencent.com/jenkins/updates/update-center.json
中国科学技术大学:https://mirrors.ustc.edu.cn/jenkins/updates/update-center.json
北京理工大学:https://mirror.bit.edu.cn/jenkins/updates/update-center.json

或者修改配置文件url
dsg@dsg01:/data/jenkins$ pwd
/data/jenkins
dsg@dsg01:/data/jenkins$ ls
data docker-compose.yaml
dsg@dsg01:/data/jenkins$ cat data/hudson.model.UpdateCenter.xml
<?xml version='1.1' encoding='UTF-8'?>
<sites>
<site>
<id>default</id>
<url>https://updates.jenkins.io/update-center.json</url>
</site>
</sites>dsg@dsg01:/data/jenkins$

改完后重启一下Jenkins
docker compose restart
3.2.10 安装插件
然后我们下载几个需要用到的插件:Git Parameter 、 Publish Over SSH,选择安装并重启



3.2.11 给Jenkins配置maven、jdk、git
给Jenkins配置编译环境
maven官网下载:https://maven.apache.org/download.cgi

jdk官网下载: https://www.oracle.com/cn/java/technologies/downloads/

git官网下载 : https://www.kernel.org/pub/software/scm/git/

也可以直接在服务器上执行
mkdir /data/apphub/
cd /data/apphub/
wget https://dlcdn.apache.org/maven/maven-3/3.9.14/binaries/apache-maven-3.9.14-bin.tar.gz
wget https://www.kernel.org/pub/software/scm/git/git-2.53.0.tar.gz #这一条是后续补充的,解压后和其它一同放在Jenkins目录下就行
jdk17我这边也上传到了服务器上,如果你从官网下载慢的话呀,可以从我这下载 jdk-17.0.18_linux-x64_bin.tar

解压maven和jdk
dsg@dsg01:/data/apphub$ tar xf apache-maven-3.9.14-bin.tar.gz
dsg@dsg01:/data/apphub$ tar xf jdk-17.0.18_linux-x64_bin.tar.gz
dsg@dsg01:/data/apphub$ ls
apache-maven-3.9.14 apache-maven-3.9.14-bin.tar.gz jdk-17.0.18 jdk-17.0.18_linux-x64_bin.tar.gz
dsg@dsg01:/data/apphub$
3.2.11.1 修改maven的配置文件,主要也是修改maven的下载地址和jdk
dsg@dsg01:/data/apphub$ vim apache-maven-3.9.14/conf/settings.xml
修改处1:设置阿里云下载地址
<mirror>
<id>aliyun</id>
<mirrorOf>central</mirrorOf>
<name>Aliyun Maven</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>

修改处2: 设置maven默认的jdk版本
<profile>
<id>jdk-17</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>17</jdk>
</activation>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.release>17</maven.compiler.release>
</properties>
</profile>
<activeProfiles>
<activeProfile>jdk-17</activeProfile>
</activeProfiles>

3.2.12 将修改好后的jdk17和maven放在Jenkins容器里
在Jenkins的docker-compose里我把Jenkins的/var/jenkins_home/挂载到./data/了,所以我把maven和jdk移动到/data/jenkins/data/,其实就是放到容器里的/var/jenkins_home/目录
dsg@dsg01:/data/apphub$ ls
apache-maven-3.9.14 apache-maven-3.9.14-bin.tar.gz jdk-17.0.18 jdk-17.0.18_linux-x64_bin.tar.gz
dsg@dsg01:/data/apphub$ mv apache-maven-3.9.14 jdk-17.0.18 /data/jenkins/data/
dsg@dsg01:/data/apphub$ cd /data/jenkins/
dsg@dsg01:/data/jenkins$ ls data/{*jdk*,*maven*}
data/apache-maven-3.9.14:
bin boot conf lib LICENSE NOTICE README.txt
data/jdk-17.0.18:
bin conf include jmods legal lib LICENSE man README release
dsg@dsg01:/data/jenkins$ grep jenkins_home docker-compose.yaml
- ./data/:/var/jenkins_home/
dsg@dsg01:/data/jenkins$
进入Jenkins容器后,可以查到jdk和maven
dsg@dsg01:/data/jenkins$ docker exec -it jenkins bash
jenkins@08bb61d004c9:/$ ls /var/jenkins_home/jdk-17.0.18/
LICENSE README bin conf include jmods legal lib man release
jenkins@08bb61d004c9:/$ ls /var/jenkins_home/apache-maven-3.9.14/
LICENSE NOTICE README.txt bin boot conf lib
jenkins@08bb61d004c9:/$
3.2.13 Jenkins页面配置jdk,git,maven的环境变量
页面打开 http://172.28.30.50:8080/manage/configureTools/ 直接进入:系统管理/全局工具配置界面

配置jdk
如果地址不对,会直接提示目录不存在如下

配置git

配置maven

点击保存
四、应用容器化:真实项目配置
4.1 gitlab创建项目,上传源码
4.1.1 为了方便演示CICD的全流程,我就用ai随便写个web贪吃蛇小游戏
项目源码: https://gitee.com/lhwjc/snake/tree/red_snake/,需要的自取

4.1.2 该项目所用技术栈如下
Spring Boot 3.2.0 - Web框架
Java 17 - 运行环境
Maven 3.9.14 - 构建工具
HTML5/Canvas - 前端游戏渲染
JavaScript (ES6) - 游戏逻辑
4.1.3 接下来就让我们在gitlab新建一个snake项目,把项目源码推到gitlab上

创建空白项目


4.1.4 这里我就用Windows的WSL创建的Ubuntu里存放项目源码
# 1. 进入项目目录的父目录
cd project
# 2. 从 GitLab 克隆空仓库
git clone http://192.168.1.50:8929/root/snake.git
# 3. 进入克隆下来的仓库目录
cd snake

4.1.5 将snake项目源码复制到仓库目录中
cp -r /mnt/d/app/Workfile/Project/opencode/dsg/* .

4.1.6 创建 .gitignore 文件排除不需要提交的文件
cat > .gitignore << EOF
# Maven
target/
*.jar
*.war
*.ear
*.zip
*.tar.gz
# IDE
.idea/
*.iml
.classpath
.project
.settings/
.vscode/
# 系统文件
.DS_Store
Thumbs.db
EOF
4.1.7 添加文件到 Git
git add .
4.1.8 配置用户信息(如果之前没配过)
git config --global user.email "dsg@163.com"
git config --global user.name "dsg"
4.1.9 提交代码
git commit -m "初始化项目:snake游戏"
4.1.10 推送到远程仓库
git push origin main

4.1.11 登陆gitlab查看项目源码

4.2 Jenkins实现基础CI操作
4.2.1 下载gitlab插件


4.2.2 配置全局凭证及gitlab仓库
下载好之后,在系统管理/系统配置里能看到gitlab的相关配置项,添加全局凭据



测试通过,点击保存

4.2.3 Jenkins创建流水线项目

一般就是选【流水线】或者【构建一个自由风格的软件项目】,网上选择后者的项目比较多,我就选择流水线的吧


4.2.4 编写Jenkins pipeline脚本
然后直接滑到最下面编写Jenkins pipeline脚本

脚本如下
pipeline {
agent any
parameters {
gitParameter(
name: 'BRANCH',
type: 'PT_BRANCH',
description: '选择要构建的分支',
defaultValue: 'main', // 建议与常用分支保持一致
branchFilter: 'origin/(.*)', // 过滤远程分支
quickFilterEnabled: true,
selectedValue: 'DEFAULT',
sortMode: 'ASCENDING'
)
}
stages {
stage('清理工作空间') {
steps {
cleanWs()
}
}
stage('拉取代码') {
steps {
checkout([
$class: 'GitSCM',
branches: [[name: "${params.BRANCH}"]], // 动态使用参数值
userRemoteConfigs: [[
credentialsId: '395fd755-a549-467a-9ea9-6a4dcdda8041',
url: 'http://192.168.1.50:8929/root/snake.git'
]],
extensions: [[$class: 'CloneOption', depth: 1, shallow: true]] // 浅克隆提高速度
])
}
}
stage('查看代码版本') {
steps {
sh '''
git log -1 --oneline
ls -la
'''
}
}
}
}
4.2.5 开始第一次构建
应用并保存,点击立即构建

点左下角的这个任务序号

4.2.6 点击查看控制台日志


4.2.7 最后出现Finished: SUCCESS,就说明本次构建成功了

点击这个左上角Jenkins老头,也能够看到我们的这个snake工程是构建成功的

4.2.8 在服务器上查看拉取下来的代码位置
前面我们通过docker-compose.yaml将容器里的/var/jenkins_home/workspace/ 挂载到宿主机当前目录下的data目录,所以即可以直接在本机查看拉取下来的代码,也可以进入容器查看

这里需要注意一下,因为我们是选了指定分支构建的,初次构建的话需应用脚本,再次构建就可以发现这里有个:Build with Parameters,后面就可以指定分支去构建代码了,默认是main分支。

4.2.9 通过Jenkins打包
下载 Maven Integration plugin插件,然后重启Jenkins

前面我们已经通过Jenkins成功的将gitlab代码拉到本地了,接下来实现Jenkins将代码构建成Java应用,继续完善Jenkins流水线,增加构建步骤
pipeline {
agent any
parameters {
gitParameter(
name: 'BRANCH',
type: 'PT_BRANCH',
description: '选择要构建的分支',
defaultValue: 'main',
branchFilter: 'origin/(.*)',
quickFilterEnabled: true,
selectedValue: 'DEFAULT',
sortMode: 'ASCENDING'
)
}
tools {
maven "maven3.9.14"
jdk "jdk17"
}
stages {
stage('清理工作空间') {
steps {
cleanWs()
}
}
stage('拉取代码') {
steps {
checkout([
$class: 'GitSCM',
branches: [[name: "${params.BRANCH}"]],
userRemoteConfigs: [[
credentialsId: 'gitlab_token',
url: 'http://192.168.1.50:8929/root/snake.git'
]],
extensions: [[$class: 'CloneOption', depth: 1, shallow: true]]
])
}
}
stage('Maven构建') {
steps {
script {
sh """
echo "🔍 Maven 版本:"
mvn --version
echo "🚀 开始构建..."
mvn clean package -DskipTests
pwd && ls -la
"""
// 检查JAR文件是否存在
def jarExists = sh(
script: 'find ./target/ -name "*.jar" | grep -q .',
returnStatus: true
) == 0
if (!jarExists) {
error "构建失败:未找到 snake 模块的 JAR 文件,请检查编译日志!"
} else {
echo "JAR 文件生成成功:"
sh 'find ./target/ -name "*.jar" -exec ls -lh {} +'
}
}
}
}
}
}
再次构建成功,并生成jar包

到这里我们的CI工作就完成了.
4.3 Jenkins实现基础CD操作
4.3.1 通过Jenkins部署应用
本次我们准备了2台服务器,服务器A装了gitlab+Jenkins,服务器B则安装Harbor和使用docker部署应用,这里有3个方案实现我们通过Jenkins启动容器的需求
1、Jenkins将jar包构建成docker镜像,然后传输给服务器B,服务器B启动容器
2、Jenkins直接将构建好的jar包传输给服务器B,在服务器B本地构建成镜像,然后启动容器
3、Jenkins将jar包构建成docker镜像,推送到Harbor镜像仓库,然后服务器B从镜像仓库下载镜像并启动容器
在没有Harbor的情况下,由于生成的镜像体积肯定要比原jar大的,所以一般都会直接传输jar到服务器B,然后在服务器B镜像构建镜像和启动服务的操作。下面我们先演示方案2的操作步骤
4.3.3 设置服务器A和服务器B互相免密(2服务器都需要操作)
生成服务器密钥,执行ssh-keygen,然后直接回车就行

生成后会在家目录下生成一个.ssh目录
dsg@dsg01:~$ ll ~/.ssh/
总用量 16
drwx------ 2 dsg dsg 4096 4月 18 21:46 ./
drwxr-xr-x 15 dsg dsg 4096 4月 18 21:46 ../
-rw------- 1 dsg dsg 2590 4月 18 21:46 id_rsa #私钥
-rw-r--r-- 1 dsg dsg 563 4月 18 21:46 id_rsa.pub #公钥
将公钥传给本机和另外一台服务器
dsg@dsg01:~$ ssh-copy-id dsg@192.168.1.50 #回车后输入服务器密码
dsg@dsg01:~$ ssh-copy-id dsg@192.168.1.51 #回车后输入服务器密码
当出现Now try logging into the machine, with: "ssh 'dsg@192.168.1.51'" ,就说明传输成功了
接下来测试免密连接对方服务器 ssh dsg@192.168.1.51,成功实现免密登陆服务器
另一台服务器也需要进行如上操作哈
4.3.4 Jenkins配置服务器信息
在系统管理/系统设置里找到Publish over SSH,没有这个菜单的需要在插件里安装并重启后再次打开这个菜单,就会显示了。点击新增

填写说明,
SSH Server
Name: 别名
Hostname: 服务器IP
Username: 服务器登陆用户
Remote Directory: 远程的目录
高级
Passphrase / Password: 使用密码连接,不建议
Path to key: 使用私钥文件连接,需要把服务器B的私钥文件Jenkins里,如/var/jenkins_home/1.51id_rsa,正常最好是这样填写,安全等级最高
Key: 直接输入私钥,不建议将私钥直接暴露在服务器上,我这么写图方便。。。

要将下面完整的私钥信息复制到上述key里,或者复制到Jenkins服务器里,cat ~/.ssh/id_rsa

测试连接成功

记得另一台服务器也配置好哈
4.3.5 通过Jenkins传输应用到服务器B
继续编写pipeline,增加传输步骤
pipeline {
agent any
parameters {
gitParameter(
name: 'BRANCH',
type: 'PT_BRANCH',
description: '选择要构建的分支',
defaultValue: 'main',
branchFilter: 'origin/(.*)',
quickFilterEnabled: true,
selectedValue: 'DEFAULT',
sortMode: 'ASCENDING'
)
}
tools {
maven "maven3.9.14"
jdk "jdk17"
}
stages {
stage('清理工作空间') {
steps {
cleanWs()
}
}
stage('拉取代码') {
steps {
checkout([
$class: 'GitSCM',
branches: [[name: "${params.BRANCH}"]],
userRemoteConfigs: [[
credentialsId: 'gitlab_token',
url: 'http://192.168.1.50:8929/root/snake.git'
]],
extensions: [[$class: 'CloneOption', depth: 1, shallow: true]]
])
}
}
stage('Maven构建') {
steps {
script {
sh """
echo "🔍 Maven 版本:"
mvn --version
echo "🚀 开始构建..."
mvn clean package -DskipTests
"""
}
}
}
// ✅ 新增:部署到目标服务器(使用 sshPublisher)
stage('部署到目标服务器') {
steps {
script {
// 获取 JAR 文件名
def jarFile = sh(
script: 'find target/ -name "*.jar" | head -n 1',
returnStdout: true
).trim()
if (jarFile.isEmpty()) {
error "未找到 JAR 文件,无法部署!"
}
echo "准备上传: ${jarFile}"
// 使用 sshPublisher 插件
sshPublisher(publishers: [
sshPublisherDesc(
configName: 'ssh51', // 对应在系统配置中的 SSH Server 名称
transfers: [
sshTransfer(
sourceFiles: jarFile, // 相对路径,相对于工作空间
removePrefix: 'target/', // 去掉路径前缀
remoteDirectory: '/apphub/', // 远程目录
execCommand: '', // 部署后可以执行的命令
usePromotionTimestamp: false,
flatten: false
)
],
usePromotionTimestamp: false,
useWorkspaceInPromotion: false,
verbose: true
)
])
echo "✅ 部署成功!JAR 已上传至 192.168.1.51:/data/"
}
}
}
}
}
再次构建成功

登陆服务器B,查看上传的jar包,确认上传成功,这里/data是在sshPublisher设置的默认远程目录,在这个基础上,将jar包放到apphub下,这个目录没有的话系统会自动创建

4.3.6 服务器B编写脚本实现构建镜像并启动容器
下面我将脚本统一放在/data/snake目录下,先将jar包从/data/snake移动到此目录下

Dockerfile脚本如下
FROM eclipse-temurin:17-alpine
WORKDIR /app
COPY *jar /app/snake.jar
CMD ["java", "-jar", "snake.jar"]
docker-compose.yaml脚本如下
services:
snake:
image: snake:latest
container_name: snake_game
ports:
- "8989:8989"
restart: always
编写shell脚本,完整自动构建镜像和启动容器
脚本名deploy_snake.sh
#!/bin/bash
set -e # 遇到错误立即退出
# 构建镜像
echo "🔨 开始构建镜像..."
docker build -t snake:latest .
if [ $? -eq 0 ]; then
echo "✅ 镜像构建成功"
else
echo "❌ 镜像构建失败!"
exit 1
fi
# 使用 docker-compose 重新部署(自动处理容器更新)
echo "🔄 重新部署 Snake 游戏..."
docker compose down # 先停止并删除旧容器
docker compose up -d # 启动新容器
if [ $? -eq 0 ]; then
echo "✅ 容器启动成功!"
echo "🎮 Snake 游戏已运行在 http://localhost:8989"
# 显示容器状态
echo ""
echo "📋 容器状态:"
docker ps --filter name=snake_game
else
echo "❌ 容器启动失败!"
exit 1
fi
给deploy_snake.sh 可执行权限,并执行脚本
chmod +x deploy_snake.sh
./sh deploy_snake.sh

贪吃蛇游戏打开,说明脚本没问题

4.3.7 Jenkins实现远程构建镜像并启动容器
到这里有同学就会发现,我基本上是每写一个步骤就会构建一次,原因很简单,如果一次性写完从拉取代码到启动的所有任务,万一中间出错,就得再改再来,所以我们就一步一步的写,稳步进行。
在服务器B需要做的是,写个Dockerfile,将jar包构建成镜像,然后我们用docker compose启动容器。为什么不用docker run,选用docker compose呢。很简单,使用docker compose启动容器,一次写好,终身受用。如果使用docker run的方式的话,要么要重新部署的话,就得先删除就容器,然后再docker run命令启动,就得找个小本本记住docker run命令
接下来继续完善pipeline,实现远程部署。
pipeline {
agent any
parameters {
gitParameter(
name: 'BRANCH',
type: 'PT_BRANCH',
description: '选择要构建的分支',
defaultValue: 'main',
branchFilter: 'origin/(.*)',
quickFilterEnabled: true,
selectedValue: 'DEFAULT',
sortMode: 'ASCENDING'
)
}
tools {
maven "maven3.9.14"
jdk "jdk17"
}
stages {
stage('清理工作空间') {
steps {
cleanWs()
}
}
stage('拉取代码') {
steps {
checkout([
$class: 'GitSCM',
branches: [[name: "${params.BRANCH}"]],
userRemoteConfigs: [[
credentialsId: 'gitlab_token',
url: 'http://192.168.1.50:8929/root/snake.git'
]],
extensions: [[$class: 'CloneOption', depth: 1, shallow: true]]
])
}
}
stage('Maven构建') {
steps {
script {
sh """
echo "🔍 Maven 版本:"
mvn --version
echo "🚀 开始构建..."
mvn clean package -DskipTests
"""
}
}
}
// ✅ 修改:部署到目标服务器
stage('部署到目标服务器') {
steps {
script {
// 获取 JAR 文件名
def jarFile = sh(
script: 'find target/ -name "*.jar" | head -n 1',
returnStdout: true
).trim()
if (jarFile.isEmpty()) {
error "未找到 JAR 文件,无法部署!"
}
echo "准备上传: ${jarFile}"
// 使用 sshPublisher 插件上传到 /apphub 目录
sshPublisher(publishers: [
sshPublisherDesc(
configName: 'ssh51',
transfers: [
sshTransfer(
sourceFiles: jarFile,
removePrefix: 'target/',
remoteDirectory: '/apphub/', // 先上传到 /apphub 目录
execCommand: '',
usePromotionTimestamp: false,
flatten: false
)
],
usePromotionTimestamp: false,
useWorkspaceInPromotion: false,
verbose: true
)
])
// 执行远程服务器上的部署命令
sshPublisher(publishers: [
sshPublisherDesc(
configName: 'ssh51',
transfers: [
sshTransfer(
sourceFiles: 'deploy_snake.sh',
removePrefix: '',
remoteDirectory: '/apphub/',
execCommand: '''
# 进入目标目录
cd /data/snake/
# 删除旧的 JAR 文件
echo "🗑️ 删除旧的 JAR 文件..."
rm -f /data/snake/snake*.jar
# 移动新的 JAR 文件到 /data/snake 目录
echo "🚚 移动新的 JAR 文件..."
mv /apphub/*.jar /data/snake/
# 赋予执行权限
chmod +x /data/snake/deploy_snake.sh
# 执行部署脚本
echo "🚀 执行部署脚本..."
cd /data/snake/
./deploy_snake.sh
echo "✅ 部署完成!"
''',
usePromotionTimestamp: false,
flatten: false
)
],
usePromotionTimestamp: false,
useWorkspaceInPromotion: false,
verbose: true
)
])
echo "✅ 部署成功!JAR 已上传并执行部署脚本"
}
}
}
}
}
继续构建

构建成功后再次访问snake页面游戏

至此,我们已经完整了基础的cicd操作。简直太棒了
4.4 版本更新,从源码到容器,一键部署
目前项目分支只有一个main分支,下面我把贪吃蛇及整体色彩变成了红色,并推到red_snake分支上,然后从Jenkins上重新构建snake工程,看下构建成功后的贪吃蛇颜色有没变化
我没有用idea等开发工具,就直接用命令演示吧
dsg004@DSG004:snake$ git checkout -b red_snake #从main分支新建red_snake
Switched to a new branch 'red_snake'
dsg004@DSG004:snake$ cp -rf /mnt/d/app/Workfile/Project/opencode/dsg/* . #将新代码复制过来
dsg004@DSG004:snake$ git add . #暂存文件
dsg004@DSG004:snake$ git commit -m "将贪吃蛇颜色由绿色改为红色" #提交修改说明
[red_snake 951bb38] 将贪吃蛇颜色由绿色改为红色
1 file changed, 5 insertions(+), 5 deletions(-)
dsg004@DSG004:snake$ git push origin red_snake #推送到远端仓库
Username for 'http://192.168.1.50:8929': root
Password for 'http://root@192.168.1.50:8929':
Enumerating objects: 13, done.
Counting objects: 100% (13/13), done.
Delta compression using up to 16 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (7/7), 632 bytes | 632.00 KiB/s, done.
Total 7 (delta 2), reused 0 (delta 0), pack-reused 0
remote:
remote: To create a merge request for red_snake, visit:
remote: http://192.168.1.50:8929/root/snake/-/merge_requests/new?merge_request%5Bsource_branch%5D=red_snake
remote:
To http://192.168.1.50:8929/root/snake.git
* [new branch] red_snake -> red_snake
dsg004@DSG004:snake$
然后在gitlab上就能查到新建的分支了

在Jenkins上选择’red_snake’分支,点击 Build

构建成功

访问更新后的snake游戏,发现贪吃蛇及整体色彩变成了红色.

4.5 Harbor仓库部署
上面我们已经完成了基础的CICD流程,但是有个问题,就是版本管理怎么做呢,怎么回退呢
有朋友就可能已经发现了,在每次脚本构建的镜像版本和docker-compose.yam里的镜像一直是latest,那历史的镜像版本就丢失了,而且存在了一堆没有标签的镜像,这个其实可以通脚本来控制tag标签,但本次我们不做他讲,直接引出Harbor仓库,使用Harbor仓库管理镜像,让Jenkins构建镜像并推送Harbor仓库,服务器B从Harbor仓库拉取最新的镜像,然后部署服务。

清理无用的镜像
docker image prune

4.5.1 Harbor镜像下载
官网:https://github.com/goharbor/harbor/releases下载地址,打不开连接的也可以从我这个地址下载 harbor-offline-installer-v2.15.0

4.5.2 生成SSL证书(可选但推荐)
mkdir -p /data/certs
cd /data/certs
openssl req -newkey rsa:4096 -nodes -sha256 -keyout ca.key -x509 -days 3650 -out ca.crt -subj "/CN=dsg.harbor.com"
此处域名要和/etc/docker/daemon.json配置的域名一样


将域名添加到/etc/hosts里
sudo tee -a /etc/hosts <<< '192.168.1.51 dsg.harbor.com'
4.5.3 修改harbor.yml配置文件
将harbor-offline-installer-v2.15.0.tgz上传到/data/apphub目录下,并解压,将解压后harbor目录移动到/data下
dsg@dsg02:/data/certs$ cd /data/harbor/
dsg@dsg02:/data/harbor$ ls
harbor-offline-installer-v2.15.0.tgz

cp harbor.yml.tmpl harbor.yml
vim harbor.yml
修改以下关键配置项:
hostname: dsg.harbor.com
https:
port: 443
certificate: /data/certs/ca.crt
private_key: /data/certs/ca.key
harbor_admin_password: Harbor12345
database:
password: root123
data_volume: /data/harbor
4.5.4 安装Harbor
sudo ./install.sh

4.5.5 访问Harbor
浏览器访问:https://192.168.1.51
登录账号: admin
密码: Harbor12345
顺便把登陆信息写到readme里,备忘一下
tee -a readme << EOF
浏览器访问:https://192.168.1.51
登录账号: admin
密码: Harbor12345
EOF


新建一个snake项目


点进去后查看推送命令

这里我们使用的是docker部署,只需要看docker命令就行
Docker 推送命令
docker tag SOURCE_IMAGE[:TAG] dsg.harbor.com/snake/REPOSITORY[:TAG]
推送镜像到当前项目
docker push dsg.harbor.com/snake/REPOSITORY[:TAG]
4.5.6 登陆镜像,并推送镜像到Harbor仓库
#登陆镜像仓库
docker login --username=admin dsg.harbor.com

#修改snake镜像名称,我习惯以为年月日时分作为tag
docker tag snake:latest dsg.harbor.com/snake/snake:202604191451
#推送镜像到Harbor仓库
docker push dsg.harbor.com/snake/snake:202604191451

登陆Harbor查看推送后的镜像

点进去后能看到这个tag的镜像,我们只推送了一次,所以这里也只有一个镜像
顺便把jdk也推送到镜像仓库去,这样Jenkins就可以从本地仓库拉取镜像了
docker tag eclipse-temurin:17-alpine dsg.harbor.com/snake/eclipse-temurin:17-alpine
docker dsg.harbor.com/snake/eclipse-temurin:17-alpine
4.5.7 在Jenkins配置Harbor的凭证

4.6 Jenkins构建镜像并推送镜像到Harbor仓库
4.6.1 思路分析
这里需要考虑个事情,如果Jenkins去构建镜像的话,肯定是需要Jenkins去通过Dockerfile构建的,要么在pipeline里写好,然后是放在gitlab的项目源码里,Jenkins拉取到代码的同时也就把Dockerfile拉取下来了,等maven打包完直接去构建镜像。
顺便我们将docker-compose.yaml也放到gitlab上,一并随源码拉取到本地,然后Jenkins将docker-compose.yaml传输到服务器B指定目录,去部署,这样的话,也不怕本地的yaml文件被丢失或者篡改
第三点,既然有了Harbor,我们将不再使用latest标签了,让Jenkins生成一个YYYYmmddHHMM的全局变量,再配合构建时的任务号作为tag如:202604191618.7,同时修改docker-compose.yaml里的tag为一个变量,这样的话每次部署都会是最新的版本,且不会让旧的容器失去tag
这一段内容涉及的较多,不像是视频教程可以沟通,希望大家细细理解一下。有朋友也会注意到我每次写的pipeline或多或少都和上一个阶段的不一样,这是因为有时需要调试代码,有时需要放在下一个阶段展示,所以都会有所修改,但每个阶段的 pipeline都是可用的。
将修改后的Dockerfile和docker-compose.yaml放到gitlab
Dockerfile,修改基础镜像来源为Harbor仓库
FROM dsg.harbor.com/snake/eclipse-temurin:17-alpine
WORKDIR /app
COPY *jar /app/snake.jar
CMD ["java", "-jar", "snake.jar"]
docker-compose.yaml,将latest改成env_tag,后面Jenkins自动把env_tag改为最新构建的镜像tag
services:
snake:
image: snake:env_tag
container_name: snake_game
ports:
- "8989:8989"
restart: always
这里main和red_snake都需要推送的哈,我只放了一个分支的截图

4.6.3 给Jenkins工程增加阶段视图
肯定也有朋友会关心这个snake工程怎么空空的,别的项目都是有任务视图的

别急哈,让我们下载这个插件Pipeline Stage View,然后重启Jenkins

重启后的 Jenkins,现在是不是就好看很多了

4.6.3 继续完善pipeline,实现Jenkins自动构建并推送镜像仓库
pipeline {
agent any
parameters {
gitParameter(
name: 'BRANCH',
type: 'PT_BRANCH',
description: '选择要构建的分支',
defaultValue: 'main',
branchFilter: 'origin/(.*)',
quickFilterEnabled: true,
selectedValue: 'DEFAULT',
sortMode: 'ASCENDING'
)
}
tools {
maven "maven3.9.14"
jdk "jdk17"
}
stages {
stage('清理工作空间') {
steps {
cleanWs()
}
}
stage('拉取代码') {
steps {
checkout([
$class: 'GitSCM',
branches: [[name: "${params.BRANCH}"]],
userRemoteConfigs: [[
credentialsId: 'gitlab_token',
url: 'http://192.168.1.50:8929/root/snake.git'
]],
extensions: [[$class: 'CloneOption', depth: 1, shallow: true]]
])
}
}
stage('Maven构建') {
steps {
script {
sh """
echo "🔍 Maven 版本:"
mvn --version
echo "🚀 开始构建..."
mvn clean package -DskipTests
"""
}
}
}
// ✅ 新增:镜像构建并推送至Harbor仓库
stage('镜像构建并推送至Harbor仓库') {
steps {
script {
// 获取当前时间戳(年月日时分)
def timestamp = sh(
script: 'date +%Y%m%d%H%M',
returnStdout: true
).trim()
// 获取构建号
def buildNumber = env.BUILD_NUMBER
// 构建镜像标签
def imageTag = "${timestamp}.${buildNumber}"
def fullImageName = "dsg.harbor.com/snake/snake:${imageTag}"
// 使用凭证登录 Harbor
withCredentials([usernamePassword(credentialsId: 'harbor_info', usernameVariable: 'HARBOR_USER', passwordVariable: 'HARBOR_PASS')]) {
sh """
docker login --username=\${HARBOR_USER} --password=\${HARBOR_PASS} dsg.harbor.com
"""
}
echo "准备构建镜像: ${fullImageName}"
// 将 Dockerfile 移动到 target 目录,构建镜像
sh """
cp Dockerfile target/
cd target
docker build -t ${fullImageName} .
"""
// 推送镜像
sh """
docker push ${fullImageName}
"""
// 登出 Harbor
sh """
docker logout dsg.harbor.com
"""
// 修改 docker-compose.yaml 中的 env_tag
sh """
sed -i "s|image: snake:env_tag|image: ${fullImageName}|g" docker-compose.yaml
"""
// 设置环境变量供后续步骤使用
env.IMAGE_TAG = imageTag
env.FULL_IMAGE_NAME = fullImageName
echo "✅ 镜像构建并推送成功: ${fullImageName}"
echo "✅ docker-compose.yaml 已更新 env_tag 为: ${imageTag}"
}
}
}
// ✅ 修改:部署到目标服务器
stage('部署到目标服务器') {
steps {
script {
// 上传并更新 docker-compose.yaml 文件
sshPublisher(publishers: [
sshPublisherDesc(
configName: 'ssh51',
transfers: [
sshTransfer(
sourceFiles: 'docker-compose.yaml',
removePrefix: '',
remoteDirectory: '/tmp/',
execCommand: """#!/bin/bash
# 备份原文件
mv /data/snake/docker-compose.yaml /data/tmp/docker-compose.yaml.backup.\$(date +%Y%m%d_%H%M%S)
# 移动新文件到目标目录
mv /data/tmp/docker-compose.yaml /data/snake/docker-compose.yaml
# 进入目标目录
cd /data/snake/
# 停止现有容器
echo "🛑 停止现有容器..."
docker compose down -v
# 启动新容器
echo "🚀 启动新容器..."
docker compose up -d
echo "✅ 部署完成!"
""",
usePromotionTimestamp: false,
flatten: false
)
],
usePromotionTimestamp: false,
useWorkspaceInPromotion: false,
verbose: true
)
])
echo "✅ 部署成功!docker-compose.yaml 已更新并重新启动容器"
}
}
}
}
}
重新构建成功

docker-compose.yaml中的tag也修改为时间戳+构建的任务号

4.7 服务器上的历史镜像版本处理
随着我们构建次数的逐渐增多,服务器上的历史版本也会越来越多,其实这些镜像版本已经都在Harbor了,所以宿主机不用存这么多镜像版本的 ,这时我们可以再增加一个步骤,根据镜像标签的时间戳清理历史镜像,只保留最近3个镜像版本(202604191434.5和202604191523.6非构建出来的镜像,只是为了演示从202604191624.8复制出来的,这里不要被误导了)

增加清理历史镜步骤
pipeline {
agent any
parameters {
gitParameter(
name: 'BRANCH',
type: 'PT_BRANCH',
description: '选择要构建的分支',
defaultValue: 'main',
branchFilter: 'origin/(.*)',
quickFilterEnabled: true,
selectedValue: 'DEFAULT',
sortMode: 'ASCENDING'
)
}
tools {
maven "maven3.9.14"
jdk "jdk17"
}
stages {
stage('清理工作空间') {
steps {
cleanWs()
}
}
stage('拉取代码') {
steps {
checkout([
$class: 'GitSCM',
branches: [[name: "${params.BRANCH}"]],
userRemoteConfigs: [[
credentialsId: 'gitlab_token',
url: 'http://192.168.1.50:8929/root/snake.git'
]],
extensions: [[$class: 'CloneOption', depth: 1, shallow: true]]
])
}
}
stage('Maven构建') {
steps {
script {
sh """
echo "🔍 Maven 版本:"
mvn --version
echo "🚀 开始构建..."
mvn clean package -DskipTests
"""
}
}
}
// ✅ 新增:镜像构建并推送至Harbor仓库
stage('镜像构建并推送至Harbor仓库') {
steps {
script {
// 获取当前时间戳(年月日时分)
def timestamp = sh(
script: 'date +%Y%m%d%H%M',
returnStdout: true
).trim()
// 获取构建号
def buildNumber = env.BUILD_NUMBER
// 构建镜像标签
def imageTag = "${timestamp}.${buildNumber}"
def fullImageName = "dsg.harbor.com/snake/snake:${imageTag}"
// 使用凭证登录 Harbor
withCredentials([usernamePassword(credentialsId: 'harbor_info', usernameVariable: 'HARBOR_USER', passwordVariable: 'HARBOR_PASS')]) {
sh """
docker login --username=\${HARBOR_USER} --password=\${HARBOR_PASS} dsg.harbor.com
"""
}
echo "准备构建镜像: ${fullImageName}"
// 将 Dockerfile 移动到 target 目录,构建镜像
sh """
cp Dockerfile target/
cd target
docker build -t ${fullImageName} .
"""
// 推送镜像
sh """
docker push ${fullImageName}
"""
// 登出 Harbor
sh """
docker logout dsg.harbor.com
"""
// 修改 docker-compose.yaml 中的 env_tag
sh """
sed -i "s|image: snake:env_tag|image: ${fullImageName}|g" docker-compose.yaml
"""
// 设置环境变量供后续步骤使用
env.IMAGE_TAG = imageTag
env.FULL_IMAGE_NAME = fullImageName
echo "✅ 镜像构建并推送成功: ${fullImageName}"
echo "✅ docker-compose.yaml 已更新 env_tag 为: ${imageTag}"
}
}
}
// ✅ 修改:部署到目标服务器
stage('部署到目标服务器') {
steps {
script {
// 上传并更新 docker-compose.yaml 文件
sshPublisher(publishers: [
sshPublisherDesc(
configName: 'ssh51',
transfers: [
sshTransfer(
sourceFiles: 'docker-compose.yaml',
removePrefix: '',
remoteDirectory: '/tmp/',
execCommand: """#!/bin/bash
# 备份原文件
mv /data/snake/docker-compose.yaml /data/tmp/docker-compose.yaml.backup.\$(date +%Y%m%d_%H%M%S)
# 移动新文件到目标目录
mv /data/tmp/docker-compose.yaml /data/snake/docker-compose.yaml
# 进入目标目录
cd /data/snake/
# 停止现有容器
echo "🛑 停止现有容器..."
docker compose down -v
# 启动新容器
echo "🚀 启动新容器..."
docker compose up -d
echo "✅ 部署完成!"
""",
usePromotionTimestamp: false,
flatten: false
)
],
usePromotionTimestamp: false,
useWorkspaceInPromotion: false,
verbose: true
)
])
echo "✅ 部署成功!docker-compose.yaml 已更新并重新启动容器"
}
}
}
// ✅ 新增:清理历史镜像标签
stage('清理历史镜像标签') {
steps {
script {
// 在远程服务器上清理历史镜像
sshPublisher(publishers: [
sshPublisherDesc(
configName: 'ssh51',
transfers: [
sshTransfer(
sourceFiles: '',
removePrefix: '',
remoteDirectory: '',
execCommand: """#!/bin/bash
echo "🔍 清理旧镜像..."
# 获取所有 dsg.harbor.com/snake/snake 镜像标签,按时间戳排序,保留最新的3个,删除其余的
docker images --filter reference='dsg.harbor.com/snake/snake' --format "{{.Tag}}" | \\
awk -F '.' '{print \$1}' | sort -nr | tail -n +4 | \\
while read -r timestamp_part; do
if [ -n "\$timestamp_part" ]; then
# 获取对应的完整镜像标签并删除
docker images --filter reference='dsg.harbor.com/snake/snake' --format "{{.Repository}}:{{.Tag}}" | \\
grep "\$timestamp_part." | xargs -r docker rmi -f
fi
done
echo "✅ 历史镜像清理完成!"
""",
usePromotionTimestamp: false,
flatten: false
)
],
usePromotionTimestamp: false,
useWorkspaceInPromotion: false,
verbose: true
)
])
}
}
}
}
}

构建成功后,本地只有最新的3个镜像版本

4.7.1 将pipeline也放到gitlab上

修改snake的流水线配置,让pipeline的来源更改为gitlab

五 结语
那坚持看到这里的朋友,恭喜你掌握了从源码到容器:构建全链路DevOps流水线,你真棒啊。
这个文章到这里基本上就结束了,开头不历时2个周末。无奈周末也在加班,断断续续,但也终于写完了。
本来还可以在Jenkins中增加SonarQube代码质量检测,这一步放在maven之后进行的操作, 本文省略了,有感兴趣的同学可以研究一下

如果你对DevOps感兴趣的话,这篇文章反复看几遍,你一定会有所收获的。如果还是不懂,建议转小破站,忘记是哪个博主的视频了,人家讲的是真的好,跟着视频一步步来,小白也能完美掌握。如果正在学习的你有不懂的问题也可以私。
后面有时间的话我打算再去写一篇关于Jenkins+Flyway一键执行脚本

配合生产业务大概像下面这样,通过Jenkins一键部署,效率简直不要太好

再然后,还想写一遍ELK+filebeat,部署生产级的日志监控平台,让多项目管理,日志问题排查不再话下
再然后。。。有缘的话我们再会
&spm=1001.2101.3001.5002&articleId=160048875&d=1&t=3&u=134f2e79d1ad480aa65f7d3e165c3a5d)
826

被折叠的 条评论
为什么被折叠?



