从源码到容器:构建全链路DevOps流水线(GitLab/Jenkins/Harbor/Docker)

从源码到容器:实战搭建全链路DevOps流水线

前言:从“人肉运维”到自动化交付

三年前,我还在用shell脚本做部署,凌晨三点接到报警电话,跑到公司重启服务器。今天,代码提交后自动走完整条流水线,十分钟后新功能就在线上了。这套全链路DevOps方案,是我踩了无数坑、熬了好几个通宵搞出来的,现在分享给你。

一、我的技术栈选型之路

1.1 为什么不用GitHub用GitLab?

刚开始我也考虑过GitHub,毕竟名气大。但后来选了GitLab,原因很实际:

  1. 私有部署:公司代码放别人服务器上,领导不放心
  2. 一体化:GitLab CI/CD、容器仓库、Wiki都有了,不用到处搭
  3. 成本:社区版够用,不用花钱

不过有个坑得注意: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 412 12:49 ./
drwxr-xr-x 4 dsg dsg 4096 411 21:30 ../
-rw-rw-r-- 1 dsg dsg 1266 412 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 412 17:04 ./
drwxr-xr-x  5 dsg  dsg  4096 412 15:23 ../
drwxrwxr-x  3 root root 4096 412 12:56 config/
drwxr-xr-x 20 root root 4096 412 13:04 data/
-rw-rw-r--  1 dsg  dsg  1266 412 12:31 docker-compose.yaml
drwxr-xr-x 20 root root 4096 412 13:01 logs/
-rw-rw-r--  1 dsg  dsg    27 412 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 418 21:46 ./
drwxr-xr-x 15 dsg dsg 4096 418 21:46 ../
-rw-------  1 dsg dsg 2590 418 21:46 id_rsa    #私钥
-rw-r--r--  1 dsg dsg  563 418 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都是可用的。

将修改后的Dockerfiledocker-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,部署生产级的日志监控平台,让多项目管理,日志问题排查不再话下
再然后。。。有缘的话我们再会

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值