Jenkins 实战3:Jenkins + habor + docker 自动化部署

我们今天来实现一个基于 docker 的自动化部署。

  1. 首先,程序员将本地代码,git push 到远程 GitLab 服务器。
  2. 然后,Jenkins git pull 代码到 Jenkins 服务器,使用 maven 帮我们打成 jar 包,并用 docker build 成镜像。
  3. 紧接着 Jenkins 帮我们把镜像 push 到远程 harbor 镜像仓库。
  4. 最后,测试服务器从 harbor 上 docker pull 拉取镜像,docker run 运行成容器。

一、前置准备

这里需要用到四台服务器,一台安装 GitLab,一台安装 Jenkins,一台安装 harbor,还有一台安装有 docker 的测试服务器。当然,harbor 跟 docker 放在同一台也行,就看每个人手头现有的服务器资源是怎样的。

服务器信息如下:

|---------------|--------------------|----------|--------------------------------------|
| 服务器名 | IP | 配置 | 安装的软件 |
| harbor100 | 192.168.40.100 | 4C4G | harbor |
| gitlab99 | 192.168.40.99 | 8C8G | gitlab |
| jenkins98 | 192.168.40.98 | 4C4G | jenkins\jdk\maven\git\docker |
| test97 | 192.168.40.97 | 2C2G | docker |

二、环境准备

1、给 Jenkins 服务器安装 docker

1)配置安装 docker 需要的 repo 源

bash 复制代码
# 安装 yum-utils 工具以便能使用 yum-config-manager 命令
yum install -y yum-utils
# 添加 repo 源
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# 清理 yum 缓存并重新生成缓存
yum clean all && yum makecache

执行完会在 /etc/yum.repos.d/ 目录下,新增一个 docker-ce.repo 配置文件。

2)安装 docker

bash 复制代码
yum install -y docker-ce-24.0.6
# 启动 docker 并设置为 开机自启动
systemctl enable docker && systemctl start docker

大家也可以安装自己想要的 docker 版本,不一定需要跟我一样。

3)配置镜像加速

bash 复制代码
# 编辑
vim /etc/docker/daemon.json

# 修改 harborIP 为你自己的 harborIP
{
 "registry-mirrors":["https://docker.lmirror.top","https://docker.m.daocloud.io", "https://hub.uuuadc.top","https://docker.anyhub.us.kg","https://dockerhub.jobcher.com","https://dockerhub.icu","https://docker.ckyl.me","https://docker.awsl9527.cn","https://docker.laoex.link"],
"insecure-registries":["192.168.40.100"]
} 

若 Harbor 使用自签名证书或 HTTP 协议,需在 /etc/docker/daemon.json 中配置:

"insecure-registries":["192.168.40.100"]

4)重启 docker

bash 复制代码
systemctl daemon-reload && systemctl restart docker

5)查看 docker 状态

bash 复制代码
# running 就是成功的
systemctl status docker

2、给测试服务器安装 docker

跟 1 一模一样的方式,给测试服务器安装 docker,略。

3、在 harbor 上新建项目

4、Jenkins 服务器推送镜像到 harbor

1)创建一个目录用于构建镜像

bash 复制代码
[root@jenkins98 ~]# mkdir -p /root/test
[root@jenkins98 ~]# ls
test

2)将项目 jar 包与 Dockerfile 文件存放在该目录下

bash 复制代码
[root@jenkins98 test]# ls
app.jar  Dockerfile

Dockerfile 文件内容如下:

bash 复制代码
FROM openjdk:17
EXPOSE 8088

WORKDIR /root
ADD app.jar app.jar

ENTRYPOINT ["java","-jar","app.jar"]

FROM:从一个基础镜像开始

EXPOSE:定义容器暴露的服务端口

WORKDIR:WORKDIR 指令用于为后续的 RUN、CMD、ENTRYPOINT、COPY 和 ADD 等指令设置工作目录。启动容器后,默认的登录目录也是 WORKDIR 指定的目录(除非被 CMD 或 ENTRYPOINT 覆盖)。

ADD:将当前目录下的 app.jar 文件,添加到容器工作目录下的 app.jar。也就是容器运行之后,/root 目录下会有一个 app.jar 文件。

ENTRYPOINT:定义容器启动时执行的命令。也就是容器启动后执行

bash 复制代码
java -jar app.jar

ENTRYPOINT 用来定义容器核心启动命令(入口点),它不会被 docker run 参数所覆盖,固定容器必须执行的核心程序。

3)构建镜像

bash 复制代码
docker build -t 192.168.40.100/jenkins-study/my-app:1.0 .

-t:tag 的意思,后面接构建后的镜像TAG。在这里,镜像TAG 为

Harbor IP\] / \[项目名称\] / \[镜像名称\]:\[版本号

.:指定Dockerfile文件位置

查看镜像是否构建成功:

bash 复制代码
[root@jenkins98 test]# docker images 
REPOSITORY                            TAG       IMAGE ID       CREATED          SIZE
192.168.40.100/jenkins-study/my-app   1.0       e1c57de530dd   13 seconds ago   489MB

4)测试镜像是否可用

bash 复制代码
docker run -d -p 8088:8088 --name jenkins-study 192.168.40.100/jenkins-study/my-app:1.0

-d:后台启动

-p:定义宿主机端口与容器端口的映射关系

--name:定义容器的名称

浏览器访问 Jenkins 服务器IP:8088

证明镜像构建没问题

5)推送镜像到 Harbor 服务器

  • 登录 Harbor
bash 复制代码
docker login 192.168.40.100 -u admin -p Harbor12345

-u:Harbor username

-p:Harbor 密码

bash 复制代码
[root@jenkins98 test]# docker login 192.168.40.100 -u admin -p Harbor12345
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded
  • 推送镜像到 Harbor
bash 复制代码
docker push 192.168.40.100/jenkins-study/my-app:1.0
bash 复制代码
[root@jenkins98 test]# docker push 192.168.40.100/jenkins-study/my-app:1.0
The push refers to repository [192.168.40.100/jenkins-study/my-app]
31559b2e79e9: Pushed 
5f70bf18a086: Pushed 
dc9fa3d8b576: Pushed 
27ee19dc88f2: Pushed 
c8dd97366670: Pushed 
1.0: digest: sha256:ff23bd7a7fc56dcf319c40415f7f662a3096e01148aea0835eae640c95911e9b size: 1372
  • Harbor 上查看镜像

5、测试服务器推送镜像到 harbor

跟 4 一模一样的方式,验证从测试服务器推送镜像到 harbor,略。

三、Jenkins CICD

1、将 Dockerfile 拷贝到项目根路径下,并提交到远程 GitLab 仓库

2、新建 Jenkins Pipeline 流水线任务

Dashboard > 新建 Item

3、配置流水线框架

Dashboard > first-pipeline >Configuration

选择 Pipeline script 脚本方式

可以选择的流水线定义有两种:

Pipeline script:自己在下面编辑框内编写流水线脚本。

Pipeline script from SCM:从版本管理系统(如:Git)中拉取已经写好的 Jenkinsfile,实现流水线配置的可版本化。

在右侧下拉选项中,选择 Hello World,以此为基础,编写流水线脚本。

在流水线语法 Tab 页,选择插件或者命令,可以帮助我们以配置的方式,来生成流水线脚本。

4、拉取代码

示例步骤选择 git:Git,填入代码仓库地址,点击添加凭证

填入 gitlab 相关信息,点击添加

下拉选择凭证,点击生成流水线脚本

复制生成的代码,粘贴到流水线编写框中

5、构建 jar 包

构建 jar 包需要使用 maven,而 maven 是一种工具(tools),它的配置在

Dashboard > Manage Jenkins > Tools > Maven 安装里

Maven 我们在 实战1 已经配置过了,所以流水线中我们可以直接拿它的名字 maven386 来使用。

  • 添加 "构建 jar 包" 阶段:
bash 复制代码
pipeline {
    agent any
    tools {
        maven 'maven386' // 引用全局配置的 Maven 名称(需提前在"全局工具配置"中设置)
    }
    stages {
        stage('拉取代码') {
            steps {
                git credentialsId: 'gitlab', url: 'http://192.168.40.99/study/jenkins-study.git'
                echo '拉取成功'
            }
        }
        stage('构建 jar 包') {
            steps {
                sh 'mvn clean package -DskipTests=true'
                echo '构建 jar 包成功'
            }
        }
    }
}

保存【配置】并【立即构建】,可以在控制台输出中查看构建日志

  • 制品

构建成功后,默认会在 /root/.jenkins/workspace/jenkins-study/target 目录下,帮我们生成一个 jar 包,说明构建 jar 包成功。

  • 移动 jar 包并重命名

为了方便我们后续 docker build,我们移动并重命名一下 jar 包,让它跟 Dockerfile 在一个路径下

bash 复制代码
pipeline {
    agent any
    tools {
        maven 'maven386' // 引用全局配置的 Maven 名称(需提前在"全局工具配置"中设置)
    }
    environment {
    	HARBOR_PROJECT = "jenkins-study"  // Harbor 中的项目名
    }
    stages {
        stage('拉取代码') {
            steps {
                git credentialsId: 'gitlab', url: 'http://192.168.40.99/study/jenkins-study.git'
                echo '拉取成功'
            }
        }
        stage('构建 jar 包') {
            steps {
                sh """
                      mvn clean package -DskipTests=true
                      cp ./target/${HARBOR_PROJECT}*.jar ./app.jar
                """
                echo '构建 jar 包成功'
            }
        }
    }
}

environment:定义环境变量,environment 后续的模块就可以使用 ${KEY} 来获取 VALUE

""" xxxxx """:定义多行 Shell 命令,不同行 Shell 命令之间具有上下文关系

cp ./target/${HARBOR_PROJECT}*.jar ./app.jar:将 target 目录下的 jar 包拷贝到当前目录并重命名为 app.jar

保存【配置】并【立即构建】后,app.jar 跟 Dockerfile 在同一个路径下了:

6、构建镜像

bash 复制代码
pipeline {
    agent any
    tools {
        maven 'maven386' // 引用全局配置的 Maven 名称(需提前在"全局工具配置"中设置)
    }
    environment {
    	HARBOR_DOMAIN = "192.168.40.100"  // Harbor 域名/IP
        HARBOR_PROJECT = "jenkins-study"  // Harbor 中的项目名
	    IMAGE_NAME = "my-app"  // 镜像名称
        IMAGE_TAG = "${BUILD_NUMBER}"  // 镜像标签(用 Jenkins 构建号,确保唯一)
        FULL_IMAGE = "${HARBOR_DOMAIN}/${HARBOR_PROJECT}/${IMAGE_NAME}:${IMAGE_TAG}"  // 完整镜像地址
    }
    stages {
        stage('拉取代码') {
            steps {
                git credentialsId: 'gitlab', url: 'http://192.168.40.99/study/jenkins-study.git'
                echo '拉取成功'
            }
        }
        stage('构建 jar 包') {
            steps {
                sh """
                      mvn clean package -DskipTests=true
                      cp ./target/${HARBOR_PROJECT}*.jar ./app.jar
                """
                echo '构建 jar 包成功'
            }
        }
        stage('构建 docker 镜像') {
            steps {
                 // 假设项目根目录有 Dockerfile,执行构建
                sh """
                      echo "开始构建镜像:${FULL_IMAGE}"
                      docker build -t ${FULL_IMAGE} .
                """                
                echo '构建 docker 镜像成功'
            }
        }

    }
}

保存【配置】并【立即构建】后,在 jenkins98 服务器查看镜像:

bash 复制代码
[root@jenkins98 ~]# docker images
REPOSITORY                            TAG       IMAGE ID       CREATED          SIZE
192.168.40.100/jenkins-study/my-app   3         b07ccde534f1   47 seconds ago   489MB

7、推送镜像到 Harbor

要推送镜像到 Harbor,就要登录 Harbor,就需要 用户名、密码;用户名密码在 Jenkins 里面叫 凭据。

打开流水线语法 Tab 页,http://192.168.40.98:8080/job/jenkins-study/pipeline-syntax/ 示例步骤选择 withCredentials: Bind credentials to variables,新增

新增一个用户名密码分开的绑定

定义用户名变量、密码变量、添加 Harbor 凭据

填入 Harbor 相关信息后点击添加

选择新建的Harbor 凭据,生成流水线脚本

拷贝流水线脚本到我们的流水线代码中

Groovy 复制代码
pipeline {
    agent any
    tools {
        maven 'maven386' // 引用全局配置的 Maven 名称(需提前在"全局工具配置"中设置)
    }
    environment {
    	HARBOR_DOMAIN = "192.168.40.100"  // Harbor 域名/IP
        HARBOR_PROJECT = "jenkins-study"  // Harbor 中的项目名
	    IMAGE_NAME = "my-app"  // 镜像名称
        IMAGE_TAG = "${BUILD_NUMBER}"  // 镜像标签(用 Jenkins 构建号,确保唯一)
        FULL_IMAGE = "${HARBOR_DOMAIN}/${HARBOR_PROJECT}/${IMAGE_NAME}:${IMAGE_TAG}"  // 完整镜像地址
        CONTAINER_NAME = "jenkins-study"  // 部署的容器名
    }
    stages {
        stage('拉取代码') {
            steps {
                git credentialsId: 'gitlab', url: 'http://192.168.40.99/study/jenkins-study.git'
                echo '拉取成功'
            }
        }
        stage('构建 jar 包') {
            steps {
                sh """
                      mvn clean package -DskipTests=true
                      cp ./target/${HARBOR_PROJECT}*.jar ./app.jar
                """
                echo '构建 jar 包成功'
            }
        }
        stage('构建 docker 镜像') {
            steps {
                 // 假设项目根目录有 Dockerfile,执行构建
                sh """
                      echo "开始构建镜像:${FULL_IMAGE}"
                      docker build -t ${FULL_IMAGE} .
                """                
                echo '构建 docker 镜像成功'
            }
        }
        stage('推送镜像到 Harbor') {
            steps {
                script {
                    // 使用 Harbor 凭证登录并推送
                    withCredentials([usernamePassword(
                        credentialsId: 'harbor-creds',
                        passwordVariable: 'HARBOR_PWD',
                        usernameVariable: 'HARBOR_USER'
                    )]) {
                        sh """
                            # 登录 Harbor(若为 HTTP,需配置 Docker  insecure-registries)
                            docker login ${HARBOR_DOMAIN} -u ${HARBOR_USER} -p ${HARBOR_PWD}
                            
                            # 推送镜像
                            docker push ${FULL_IMAGE}
                            
                            # 登出(可选)
                            docker logout ${HARBOR_DOMAIN}
                        """
                    }
                }
            }
        }


    }
}

Jenkins 流水线语法 script 脚本的作用:

  • 在 Jenkins 流水线(Pipeline)中,script 块是一个非常重要的语法元素,主要作用是在声明式流水线(Declarative Pipeline)中嵌入脚本式流水线(Scripted Pipeline)的语法,从而增强流水线的灵活性和可编程性。
  • 声明式流水线(以 pipeline { ... } 为标志)语法简洁、结构固定,适合定义标准化的流程(如 agentstagespost 等固定结构),但对复杂逻辑(如循环、条件判断嵌套、动态生成步骤等)支持有限。
  • script 块允许在声明式流水线中插入脚本式语法(类似 Groovy 脚本),实现复杂的流程控制。
  • script 块可以在声明式流水线的 stepspostenvironment 等部分中使用,用于在固定结构中插入自定义逻辑。
  • script 块是声明式和脚本式流水线的 "桥梁",但应避免过度使用(否则会失去声明式流水线的简洁性)。
  • 脚本式语法更灵活,但也更易出错(需自己处理异常、流程控制等),建议优先使用声明式的内置语法(如 when 条件),仅在必要时用 script 块扩展。
  • 上述代码其实也可以不使用 script 块,这里只是提供一种补充,让大家知道有这种语法功能。

保存【配置】并【立即构建】后,在 Harbor 服务器查看镜像:

8、部署到 Docker 服务器

我们要想在测试服务器运行 docker 命令,需要安装 SSH Publisher 插件。

1)安装 SSH Publisher 插件

Dashboard > Manage Jenkins > 插件管理 > Available plugins

安装完,返回首页

2)配置测试服务器信息

Dashboard > Manage Jenkins > System,新增测试服务器

  • 填上测试服务器信息并保存
  • Test Configuration 测试连接

3)生成流水线脚本

在流水线语法 Tab 页

拷贝流水线脚本到我们的流水线代码中

Groovy 复制代码
pipeline {
    agent any
    tools {
        maven 'maven386' // 引用全局配置的 Maven 名称(需提前在"全局工具配置"中设置)
    }
    environment {
    	HARBOR_DOMAIN = "192.168.40.100"  // Harbor 域名/IP
        HARBOR_PROJECT = "jenkins-study"  // Harbor 中的项目名
	    IMAGE_NAME = "my-app"  // 镜像名称
        IMAGE_TAG = "${BUILD_NUMBER}"  // 镜像标签(用 Jenkins 构建号,确保唯一)
        FULL_IMAGE = "${HARBOR_DOMAIN}/${HARBOR_PROJECT}/${IMAGE_NAME}:${IMAGE_TAG}"  // 完整镜像地址
        CONTAINER_NAME = "jenkins-study"  // 部署的容器名
    }
    stages {
        stage('拉取代码') {
            steps {
                git credentialsId: 'gitlab', url: 'http://192.168.40.99/study/jenkins-study.git'
                echo '拉取成功'
            }
        }
        stage('构建 jar 包') {
            steps {
                sh """
                      mvn clean package -DskipTests=true
                      cp ./target/${HARBOR_PROJECT}*.jar ./app.jar
                """
                echo '构建 jar 包成功'
            }
        }
        stage('构建 docker 镜像') {
            steps {
                 // 假设项目根目录有 Dockerfile,执行构建
                sh """
                      echo "开始构建镜像:${FULL_IMAGE}"
                      docker build -t ${FULL_IMAGE} .
                """                
                echo '构建 docker 镜像成功'
            }
        }
        stage('推送镜像到 Harbor') {
            steps {
                script {
                    // 使用 Harbor 凭证登录并推送
                    withCredentials([usernamePassword(
                        credentialsId: 'harbor-creds',
                        passwordVariable: 'HARBOR_PWD',
                        usernameVariable: 'HARBOR_USER'
                    )]) {
                        sh """
                            # 登录 Harbor(若为 HTTP,需配置 Docker  insecure-registries)
                            docker login ${HARBOR_DOMAIN} -u ${HARBOR_USER} -p ${HARBOR_PWD}
                            
                            # 推送镜像
                            docker push ${FULL_IMAGE}
                            
                            # 登出(可选)
                            docker logout ${HARBOR_DOMAIN}
                        """
                    }
                }
            }
        }
        stage('部署到 Docker 服务器') {
            steps {
                script {
                    withCredentials([
                        usernamePassword(
                            credentialsId: 'harbor-creds',
                            passwordVariable: 'HARBOR_PWD',
                            usernameVariable: 'HARBOR_USER'
                        )
                    ]) {
                        // 通过 SSH 连接远程服务器,执行部署命令
                        sshPublisher(
                            publishers: [
                                sshPublisherDesc(
                                    configName: "test97",  // 对应 Publish Over SSH 配置的服务器名
                                    transfers: [
                                        sshTransfer(
                                            execCommand: """
                                                # 远程服务器登录 Harbor
                                                docker login ${HARBOR_DOMAIN} -u ${HARBOR_USER} -p ${HARBOR_PWD}
                                                
                                                # 停止并删除旧容器(若存在)
                                                if [ \$(docker ps -a -q -f name=${CONTAINER_NAME}) ]; then
                                                    docker stop ${CONTAINER_NAME} && docker rm ${CONTAINER_NAME}
                                                fi
                                                
                                                # 拉取最新镜像
                                                docker pull ${FULL_IMAGE}
                                                
                                                # 启动新容器(根据需求调整端口、环境变量等)
                                                docker run -d --name ${CONTAINER_NAME} -p 8088:8088 ${FULL_IMAGE}
                                                
                                                # 登出 Harbor
                                                docker logout ${HARBOR_DOMAIN}
                                            """,
                                            execTimeout: 120000  // 超时时间 2 分钟
                                        )
                                    ]
                                )
                            ]
                        )
                    }
                }
            }
        }

    }
}

docker ps -a -q -f name=${CONTAINER_NAME}) :

  • **docker ps:**显示容器信息
  • -a:all,显示所有容器信息,包括正在运行的和已停止的
  • -q:quiet,只显示容器ID
  • **-f name=${CONTAINER_NAME}):**filter 过滤容器名称=jenkins-study 的容器

整条命令的意思,是过滤出容器名称=jenkins-study 的容器,只返回容器ID
""" if [ \$() ] """ 中 \ 的作用:

  • \$ 中的 \转义字符 ,作用是保留 $ 符号本身,防止其被外层的解析器提前解析
  • 在多行字符串内部,如果直接写 $(...),外层的解析器(比如 Python 解释器,或生成 shell 脚本的程序)可能会把 $ 当作变量或命令替换的标记,尝试提前解析 $(...) 的内容,导致不符合预期的结果。
  • 加上 \ 转义后,\$ 会被外层解析器当作普通的 $ 符号处理,最终生成的字符串中会保留 $(...) 结构。当这段字符串被作为 shell 脚本执行时,shell 会正常解析 $(...) 作为命令替换(即执行 docker ps ... 并获取结果)。

保存【配置】并【立即构建】后,在 test97 服务器查看:

bash 复制代码
[root@test97 ~]# docker images 
REPOSITORY                            TAG       IMAGE ID       CREATED        SIZE
192.168.40.100/jenkins-study/my-app   5         3e575c84958d   16 hours ago   489MB

[root@test97 ~]# docker ps
CONTAINER ID   IMAGE                                    COMMAND               CREATED          STATUS          PORTS                                       NAMES
8afe1f6f176b   192.168.40.100/jenkins-study/my-app:5   "java -jar app.jar"   17 seconds ago   Up 16 seconds   0.0.0.0:8088->8088/tcp, :::8088->8088/tcp   jenkins-study

TAG 都是 5,证明容器正常运行。

题外话:

大家如果容器起不来,可以使用 docker inspect 容器ID 命令来查看容器日志

9、修改业务代码测试

修改业务代码,提交到远程 GitLab,启动流水线构建,在浏览器查看

测试服务器 test97 部署的是最新的代码了

四、总结

大家如果感觉博主写得还可以的话,请点赞加关注吧~~~~

博主写了好几个小时,中间网络有问题,我点击了好多次保存草稿,但是服务器上没有给我保存,造成后面有好几节的内容丢失,博主只能重写。

5555555555555555555555555555555555555555555555555555555555555

相关推荐
vortex57 小时前
Linux 用户管理详解:从古老Unix到现代集成
linux·运维·unix
玩转测试开发8 小时前
xshell设置跳板机登录内网服务器
运维·服务器·数据库
Java 码农8 小时前
linux shell 数组
linux·运维·服务器
大梦谁先觉i9 小时前
Linux 磁盘空间“消失”之谜:文件已删,空间却不释放?
linux·运维·服务器
序属秋秋秋9 小时前
《Linux系统编程之开发工具》【编译器 + 自动化构建器】
linux·运维·服务器·c语言·c++·自动化·编译器
塔能物联运维9 小时前
物联网运维中基于自适应射频环境监测的动态频谱优化技术
运维·物联网
小涂10 小时前
在Linux(deepin-community-25)下安装MongoDB
linux·运维·mongodb
艾莉丝努力练剑10 小时前
【Linux基础开发工具 (一)】详解Linux软件生态与包管理器:从yum / apt原理到镜像源实战
linux·运维·服务器·ubuntu·centos·1024程序员节
月巴月巴白勺合鸟月半10 小时前
生成私钥公钥
运维·服务器