基于 Jenkins、Gitlab、Harbor、Helm 和 Kubernetes 的完整 CICD案例
一. 练习项目
本次示例项目是一个完整的基于 Spring Boot、Spring Security、JWT、React 和 Ant Design 构建的一个开源的投票应用,项目地址:https://github.com/callicoder/spring-security-react-ant-design-polls-app。
我们将会在该项目的基础上添加部分代码,并实践 CI/CD 流程。
1. 服务端
本次测试就以部署服务端为例子,首先需要更改的是服务端配置,我们需要将数据库链接的配置更改成环境变量的形式,写死了的话就没办法进行定制了,修改服务端文件src/main/resources/application.properties
,将下面的数据库配置部分修改成如下形式:
spring.datasource.url= jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:polling_app}?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false
spring.datasource.username= ${DB_USER:root}
spring.datasource.password= ${DB_PASSWORD:root}
当环境变量中有上面的数据配置的时候,就会优先使用环境变量中的值,没有的时候就会用默认的值进行数据库配置。由于我们要将项目部署到 Kubernetes 集群中去,所以我们需要将服务端进行容器化,所以我们在项目根目录下面添加一个Dockerfile
文件进行镜像构建:
FROM adoptopenjdk/maven-openjdk11 as BUILD
COPY src /usr/app/src
COPY pom.xml /usr/app
RUN mvn -f /usr/app/pom.xml clean package -Dmaven.test.skip=true
#FROM openjdk:8-jdk-alpine
FROM openjdk:11.0.10-jdk
MAINTAINER cnych <icnych@gmail.com>
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
ENV TZ=Asia/Shanghai
RUN mkdir /app
WORKDIR /app
COPY --from=BUILD /usr/app/target/polls-0.0.1-SNAPSHOT.jar /app/polls.jar
EXPOSE 8080
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar","/app/polls.jar"]
前面课程中我们就讲解过 Docker 的多阶段构建,这里我们定义了两个阶段,第一个阶段利用maven:3.6-alpine
这个基础镜像将我们的项目进行打包,然后将该阶段打包生成的jar
包文件复制到第二阶段进行最后的镜像打包,这样就可以很好的完成我们的 Docker 镜像的构建工作。
二. 配置jenkins
现在项目准备好了,接下来我们可以开始 Jenkins 的配置,还记得前面在 Pipeline 结合 Kubernetes 的课程中我们使用了一个kubernetes
的 Jenkins 插件,但是之前使用的方式有一些不妥的地方,我们 Jenkins Pipeline 构建任务绑定到了一个固定的 Slave Pod 上面,这样就需要我们的 Slave Pod 中必须包含一系列构建所需要的依赖,比如 docker、maven、node、java 等等,这样就难免需要我们自己定义一个很庞大的 Slave 镜像。
现在我们可以直接在 Pipeline 中去自定义 Slave Pod 中所需要用到的容器模板,这样我们需要什么镜像只需要在 Slave Pod Template 中声明即可,完全不需要去定义一个庞大的 Slave 镜像了。
1.删除pod template
首先去掉 Jenkins 中 kubernetes 插件中的 Pod Template 的定义,Jenkins -> 系统管理 -> 系统设置 -> 云 -> Kubernetes区域,删除下方的Kubernetes Pod Template
-> 保存
2.新建流水线任务
这里我们新建一个名为polling-app-server类型为流水线的任务
2.1 trigger
然后在这里需要勾选触发远程构建
的触发器,其中令牌我们可以随便写一个字符串,然后记住下面的 URL,将 JENKINS_URL 替换成 Jenkins 的地址,我们这里的地址就是:
http://192.168.2.34:30002/job/polling-app-server/build?token=server321
2.2 pipeline scm
然后再下面流水线区域选择Pipeline script from SCM
,意思就是从代码仓库中通过Jenkinsfile
文件获取Pipeline script
脚本定义,然后选择 SCM 来源为Git
,在出现的列表中配置上仓库地址 http://192.168.2.35:88/root/polling-app-server.git
由于我们是在一个 Slave Pod 中去进行构建,所以如果使用 SSH 的方式去访问 Gitlab 代码仓库的话就需要频繁的去更新 SSH-KEY,所以我们这里采用直接使用用户名和密码的形式来方式:
2.3 gitlab auth
在Credentials
区域点击添加
按钮添加我们访问 Gitlab 的用户名和密码:
2.4 gitlab branch config
然后需要我们配置用于构建的分支,如果所有的分支我们都想要进行构建的话,只需要将Branch Specifier
区域留空即可,一般情况下不同的环境对应的分支才需要构建,比如 master、develop、test 等,我们这里就只配置 master 和 develop 两个分支用户构建(实际我只配了master)
3. gitlab 配置webhook
然后前往 Gitlab 中配置项目polling-app-server
Webhook,settings -> Integrations,填写上面得到的 trigger 地址:http://192.168.2.34:30002/job/polling-app-server/build?token=server321
保存后,可以直接点击Test
-> Push Event
测试是否可以正常访问 Webhook 地址,这里需要注意的是我们需要配置下 Jenkins 的安全配置,否则会报错403 No valid crumb was included in the request
解决方法:https://www.jianshu.com/p/00fcfa4a53b5
如果测试出现了Hook executed successfully: HTTP 201
则证明 Webhook 配置成功了
4.创建测试jenkinsfile
配置成功后我们只需要往 Gitlab 仓库推送代码就会触发 Pipeline 构建了。接下来我们直接在服务端代码仓库根目录下面添加Jenkinsfile
文件,用于描述流水线构建流程。首先定义最简单的流程,要注意这里和前面课程的不同之处,这里我们使用podTemplate
来定义不同阶段使用的的容器,有哪些阶段呢?
Clone 代码 -> 代码静态分析 -> 单元测试 -> Maven 打包 -> Docker 镜像构建/推送 -> Helm 更新服务。
Clone 代码在默认的 Slave 容器中即可;静态分析和单元测试我们这里直接忽略,有需要这个阶段的同学自己添加上即可;Maven 打包肯定就需要 Maven 的容器了;Docker 镜像构建/推送是不是就需要 Docker 环境了呀;最后的 Helm 更新服务是不是就需要一个有 Helm 的容器环境了,所以我们这里就可以很简单的定义podTemplate
了,如下定义:(添加一个kubectl
工具用于测试)
def label = "slave-${UUID.randomUUID().toString()}"
podTemplate(label: label, serviceAccount: 'jenkins2', containers: [
containerTemplate(name: 'maven', image: 'adoptopenjdk/maven-openjdk11', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'docker', image: 'docker', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'kubectl', image: 'cnych/kubectl', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'helm', image: 'cnych/helm', command: 'cat', ttyEnabled: true)
], volumes: [
hostPathVolume(mountPath: '/root/.m2', hostPath: '/var/run/m2'),
hostPathVolume(mountPath: '/home/jenkins/.kube', hostPath: '/root/.kube'),
hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')
]) {
node(label) {
def myRepo = checkout scm
def gitCommit = myRepo.GIT_COMMIT
def gitBranch = myRepo.GIT_BRANCH
stage('单元测试') {
echo "测试阶段"
}
stage('代码编译打包') {
container('maven') {
echo "打码编译打包阶段"
}
}
stage('构建 Docker 镜像') {
container('docker') {
echo "构建 Docker 镜像阶段"
}
}
stage('运行 Kubectl') {
container('kubectl') {
echo "查看 K8S 集群 Pod 列表"
sh "kubectl get pods"
}
}
stage('运行 Helm') {
container('helm') {
echo "查看 Helm Release 列表"
sh "helm list"
}
}
}
}
注意:
1)kubectl get pods阶段,podTemplate中需要添加serviceAcount: 'jenkins2',否则会报权限错误,这个jenkins2在部署jenkins时已指定
2)helm list时会报权限错误,这个是因为之前创建jenkins这个sa时,没有添加list secret的权限。在clusterRole为jenkins2里添加上即可
kubectl edit clusterRole jenkins2
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list #加上list权限
上面这段groovy
脚本比较简单,我们需要注意的是volumes
区域的定义,
将容器中的/root/.m2
目录挂载到宿主机上是为了给Maven
构建添加缓存的,不然每次构建的时候都需要去重新下载依赖,这样就非常慢了;
挂载.kube
目录是为了能够让kubectl
和helm
两个工具可以读取到 Kubernetes 集群的连接信息,不然我们是没办法访问到集群的;
最后挂载/var/run/docker.sock
文件是为了能够让我们的docker
这个容器获取到Docker Daemon
的信息的,因为docker
这个镜像里面只有客户端的二进制文件,我们需要使用宿主机的Docker Daemon
来构建镜像,当然我们也需要在运行 Slave Pod 的节点上拥有访问集群的文件,然后在每个Stage
阶段使用特定需要的容器来进行任务的描述即可,所以这几个volumes
都是非常重要的
volumes: [
hostPathVolume(mountPath: '/root/.m2', hostPath: '/var/run/m2'),
hostPathVolume(mountPath: '/home/jenkins/.kube', hostPath: '/root/.kube'),
hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')
]
另外一个值得注意的就是label
标签的定义,我们这里使用 UUID 生成一个随机的字符串,这样可以让 Slave Pod 每次的名称都不一样,而且这样就不会被固定在一个 Pod 上面了,以后有多个构建任务的时候就不会存在等待的情况了,这和我们之前的课程中讲到的固定在一个 label 标签上有所不同。
正常情况就可以看到我们的流水线任务polling-app-server
已经被触发构建了,然后回到我们的 Kubernetes 集群中可以看到多了一个 slave 开头的 Pod,里面有5个容器,就是我们上面 podTemplate 中定义的4个容器,加上一个默认的 jenkins slave 容器,同样的,构建任务完成后,这个 Pod 也会被自动销毁掉:
5.完善jenkinsfile
第一个阶段:单元测试,我们可以在这个阶段是运行一些单元测试或者静态代码分析的脚本,我们这里直接忽略。
第二个阶段:代码编译打包,我们可以看到我们是在一个maven
的容器中来执行的,所以我们只需要在该容器中获取到代码,然后在代码目录下面执行 maven 打包命令即可,如下所示:
stage('代码编译打包') {
try{
container('maven') {
echo "打码编译打包阶段"
sh "mvn clean package -Dmaven.test.skip=true"
}
} catch(exc){
println "构建失败 - ${currentBuild.fullDisplayName}"
throw(exc)
}
}
这里下载maven插件组件会很慢,可以修改下插件源为国内,需要更改default.json
default.json的具体路径是:{jenkins的工作路径}/updates/default.json
sed -i 's#updates.jenkins.io/download/plugins#mirrors.tuna.tsinghua.edu.cn/jenkins/plugins#g' default.json
sed -i 's#www.google.com#www.baidu.com#g' default.json
解决没有合法证书而不能查看Https网址的问题
需要将~/.jenkins/hudson.model.UpdateCenter.xml中的https改为http
第三个阶段:构建 Docker 镜像,要构建 Docker 镜像,就需要提供镜像的名称和 tag,要推送到 Harbor 仓库,就需要提供登录的用户名和密码,所以我们这里使用到了withCredentials
方法,在里面可以提供一个credentialsId
为harbor
的认证信息,如下
stage('构建 Docker 镜像') {
withCredentials([[$class: 'UsernamePasswordMultiBinding',
credentialsId: 'harbor',
usernameVariable: 'harborUser',
passwordVariable: 'harborPassword']]) {
container('docker') {
echo "构建 Docker 镜像阶段"
sh """
docker login ${dockerRegistryUrl} -u ${harborUser} -p ${harborPassword}
docker build -t ${image}:${imageTag} .
docker push ${image}:${imageTag}
"""
}
}
}
其中 image, imageTag, dockerRegistryUrl 我们可以在上面node(label){}中定义成全局变量:
..
node(label) {
def myRepo = checkout scm
def gitCommit = myRepo.GIT_COMMIT
def gitBranch = myRepo.GIT_BRANCH
def dockerRegistryUrl = "192.168.2.35"
def imageEndpoint = "course/polling-app-server"
def image = "${dockerRegistryUrl}/${imageEndpoint}"
def imageTag = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
...
harbor 的用户名和密码信息则需要通过凭据
来进行添加,进入 jenkins 首页 -> 左侧菜单凭据
-> 添加凭据
,选择用户名和密码类型的,其中 ID 一定要和上面的credentialsId
的值保持一致: 这个之前已经做过了。
第四个阶段:运行 kubectl 工具,其实在我们当前使用的流水线中是用不到 kubectl 工具的,那么为什么我们这里要使用呢?这还不是因为我们暂时还没有去写应用的 Helm Chart 包吗?所以我们先去用原始的 YAML 文件来编写应用部署的资源清单文件,这也是我们写出 Chart 包前提,因为只有知道了应用如何部署才可能知道 Chart 包如何编写,所以我们先编写应用部署资源清单
首先当然就是 Deployment 控制器了,如下所示:(k8s.yaml)
需要事先创建命令空间course
apiVersion: apps/v1
kind: Deployment
metadata:
name: polling-server
namespace: course
spec:
replicas: 1
selector:
matchLabels:
app: polling-server
template:
metadata:
labels:
app: polling-server
spec:
restartPolicy: Always
imagePullSecrets:
- name: myreg
containers:
- image: <IMAGE>:<IMAGE_TAG>
name: polling-server
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
name: api
env:
- name: DB_HOST
value: mysql
- name: DB_PORT
value: "3306"
- name: DB_NAME
value: polling_app
- name: DB_USER
value: polling
- name: DB_PASSWORD
value: polling321
---
kind: Service
apiVersion: v1
metadata:
name: polling-server
namespace: course
spec:
selector:
app: polling-server
type: ClusterIP
ports:
- name: api-port
port: 8080
targetPort: api
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
namespace: course
spec:
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
restartPolicy: Always
containers:
- name: mysql
image: mysql:5.7
imagePullPolicy: IfNotPresent
ports:
- containerPort: 3306
name: dbport
env:
- name: MYSQL_ROOT_PASSWORD
value: rootPassW0rd
- name: MYSQL_DATABASE
value: polling_app
- name: MYSQL_USER
value: polling
- name: MYSQL_PASSWORD
value: polling321
volumeMounts:
- name: db
mountPath: /var/lib/mysql
volumes:
- name: db
hostPath:
path: /var/lib/mysql
---
kind: Service
apiVersion: v1
metadata:
name: mysql
namespace: course
spec:
selector:
app: mysql
type: ClusterIP
ports:
- name: dbport
port: 3306
targetPort: dbport
可以看到我们上面的 YAML 文件中添加使用的镜像是用标签代替的:<IMAGE>:<IMAGE_TAG>
,这是因为我们的镜像地址是动态的,下依赖我们在上一个阶段打包出来的镜像地址的,所以我们这里用标签代替,然后将标签替换成真正的值即可,
另外为了保证应用的稳定性,我们还在应用中添加了健康检查,所以需要在代码中创建了一个健康检查的 Controller:(src/main/java/com/example/polls/controller/StatusController.java)
package com.example.polls.controller;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/_status/healthz")
public class StatusController {
@GetMapping
public String healthCheck() {
return "UP";
}
}
最后就是环境变量了,还记得前面我们更改了资源文件中数据库的配置吗?(src/main/resources/application.properties)因为要尽量通用,我们在部署应用的时候很有可能已经有一个外部的数据库服务了,所以这个时候通过环境变量传入进来即可。另外由于我们这里使用的是私有镜像仓库,所以需要在集群中提前创建一个harbor对应的 Secret 对象:
kubectl create secret docker-registry myreg --docker-server=192.168.2.35 --docker-username=admin --docker-password=Harbor12345 --docker-email=1816635208@qq.com --namespace course
在代码根目录下面创建一个 manifests 的目录,用来存放上面的资源清单文件,正常来说是不是我们只需要在镜像构建成功后,将上面的 k8s.yaml 文件中的镜像标签替换掉就 OK,所以这一步的动作如下:
stage('运行 Kubectl') {
container('kubectl') {
echo "查看 K8S 集群 Pod 列表"
sh "kubectl get pods"
sh """
sed -i "s#<IMAGE>#${image}#" manifests/k8s.yaml
sed -i "s/<IMAGE_TAG>/${imageTag}/" manifests/k8s.yaml
kubectl apply -f k8s.yaml
"""
}
}
可能是版本或者系统原因,在我的环境中sed命令最后的/也需要加上,否则报错。这里因为image中有"/", 使用"#”来代替"/"
第五阶段:运行 Helm 工具,就是直接使用 Helm 来部署应用了,现在有了上面的基本的资源对象了,要创建 Chart 模板就相对容易了,Chart 模板仓库地址:https://github.com/cnych/polling-helm,我们可以根据values.yaml
文件来进行自定义安装,模板中我们定义了可以指定使用外部数据库服务或者内部独立的数据库服务,具体的我们可以去看模板中的定义。首先我们可以先使用这个模板在集群中来测试下。首先在集群中 Clone 上面的 Chart 模板:
git clone https://github.com/cnych/polling-helm.git
然后我们使用内部的数据库服务,新建一个 custom.yaml 文件来覆盖 values.yaml 文件中的值:
persistence:
enabled: true
persistentVolumeClaim:
database:
storageClass: "database"
database:
type: internal
internal:
database: "polling"
# 数据库用户
username: "polling"
# 数据库用户密码
password: "polling321"
可以看到我们这里使用了一个名为database
的 StorgeClass 对象,所以还得创建先创建这个资源对象:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: database
provisioner: fuseim.pri/ifs
然后我们就可以在 Chart 根目录下面安装应用,执行下面的命令:
helm upgrade --install polling -f custom.yaml . --namespace course
再执行之前最好把原先k8s.yaml生成的资源删掉
安装完成后,正常情况如下
6.使用helm实现全部部署
这样我们就完成了使用 Helm Chart 安装应用的过程,但是现在我们使用的包还是直接使用的 git 仓库中的,平常我们正常安装的时候都是使用的 Chart 仓库中的包,所以我们需要将该 Chart 包上传到一个仓库中去。
比较幸运的是我们的 Harbor 也是支持 Helm Chart 包的。我们可以选择手动通过 Harbor 的 Dashboard 将 Chart 包进行上传,也可以通过使用Helm Push
插件:
官网下载helm-push_0.9.0_linux_amd64.tar.gz,其它版本太新了不太会用
https://github.com/chartmuseum/helm-push/tags
https://github.com/chartmuseum/helm-push/releases/download/v0.9.0/helm-push_0.9.0_linux_amd64.tar.gz
helm env 查看plugin路径,我这里是HELM_PLUGINS="/root/.local/share/helm/plugins"
在此路径下新建helm-push文件夹,并将安装包拷贝到该文件夹下解压即可
mkdir /root/.local/share/helm/plugins/helm-push
cp helm-push_0.9.0_linux_amd64.tar.gz /root/.local/share/helm/plugins/helm-push
tar zxvf /root/.local/share/helm/plugins/helm-push/helm-push_0.9.0_linux_amd64.tar.gz
首先将 Harbor 提供的仓库添加到 helm repo 中,由于是私有仓库,所以在添加的时候我们需要添加用户名和密码:
helm repo add course http://192.168.2.35/chartrepo/course --username=admin --password=Harbor12345
其中chartrepo是固定写法
将之前下载的polling-helm包上传到Harbor仓库的course项目中
helm push polling-helm course
到这里 Helm 相关的工作就准备好了。那么我们如何在 Jenkins Pipeline 中去使用 Helm 呢?我们可以回顾下,我们平时的一个 CI/CD 的流程:开发代码 -> 提交代码 -> 触发镜像构建 -> 修改镜像tag -> 推送到镜像仓库中去 -> 然后更改 YAML 文件镜像版本 -> 使用 kubectl 工具更新应用。
现在我们是不是直接使用 Helm 了,就不需要去手动更改 YAML 文件了,也不需要使用 kubectl 工具来更新应用了,而是只需要去覆盖下 helm 中的镜像版本,直接 upgrade 是不是就可以达到应用更新的结果了。我们可以去查看下 chart 包的 values.yaml 文件中关于 api 服务的定义:
api:
image:
repository: cnych/polling-api
tag: 0.0.7
pullPolicy: IfNotPresent
我们是不是只需要将上面关于 api 服务使用的镜像用我们这里 Jenkins 构建后的替换掉就可以了,这样我们更改上面的最后运行 Helm
的阶段如下:
stage('运行 Helm') {
container('helm') {
echo "更新 polling 应用"
sh """
helm upgrade --install polling polling --set persistence.persistentVolumeClaim.database.storageClass=database --set database.type=internal --set database.internal.database=polling --set database.internal.username=polling --set database.internal.password=polling321 --set api.image.repository=${image} --set api.image.tag=${imageTag} --set imagePullSecrets[0].name=myreg --namespace course
"""
}
}
当然我们可以将需要更改的值都放入一个 YAML 之中来进行修改,我们这里通过--set
来覆盖对应的值,这样整个 API 服务的完整 Jenkinsfile 文件如下所示:
def label = "slave-${UUID.randomUUID().toString()}"
def helmLint(String chartDir) {
println "校验 chart 模板"
sh "helm lint ${chartDir}"
}
//def helmInit() {
// println "初始化 helm client"
// sh "helm init --client-only --stable-repo-url https://mirror.azure.cn/kubernetes/charts/"
//}
def helmRepo(Map args) {
println "添加 course repo"
sh "helm repo add --username ${args.username} --password ${args.password} course http://192.168.2.35/chartrepo/course"
println "更新 repo"
sh "helm repo update"
println "获取 Chart 包"
sh """
helm fetch course/polling
tar -xzvf polling-0.1.0.tgz
"""
}
def helmDeploy(Map args) {
//helmInit()
helmRepo(args)
if (args.dry_run) {
println "Debug 应用"
sh "helm upgrade --dry-run --debug --install ${args.name} ${args.chartDir} --set persistence.persistentVolumeClaim.database.storageClass=database --set api.image.repository=${args.image} --set api.image.tag=${args.tag} --set imagePullSecrets[0].name=myreg --namespace=${args.namespace}"
} else {
println "部署应用"
sh "helm upgrade --install ${args.name} ${args.chartDir} --set persistence.persistentVolumeClaim.database.storageClass=database --set api.image.repository=${args.image} --set api.image.tag=${args.tag} --set imagePullSecrets[0].name=myreg --namespace=${args.namespace}"
echo "应用 ${args.name} 部署成功. 可以使用 helm status ${args.name} 查看应用状态"
}
}
podTemplate(label: label, serviceAccount: 'jenkins2', containers: [
containerTemplate(name: 'maven', image: 'adoptopenjdk/maven-openjdk11', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'docker', image: 'docker', command: 'cat', ttyEnabled: true),
//containerTemplate(name: 'kubectl', image: 'cnych/kubectl', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'helm', image: 'cnych/helm', command: 'cat', ttyEnabled: true)
], volumes: [
hostPathVolume(mountPath: '/root/.m2', hostPath: '/var/run/m2'),
hostPathVolume(mountPath: '/home/jenkins/.kube', hostPath: '/root/.kube'),
hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')
]) {
node(label) {
def myRepo = checkout scm
def gitCommit = myRepo.GIT_COMMIT
def gitBranch = myRepo.GIT_BRANCH
def dockerRegistryUrl = "192.168.2.35"
def imageEndpoint = "course/polling-app-server"
def image = "${dockerRegistryUrl}/${imageEndpoint}"
def imageTag = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
stage('单元测试') {
echo "测试阶段"
}
stage('代码编译打包') {
try{
container('maven') {
echo "打码编译打包阶段"
sh "mvn clean package -Dmaven.test.skip=true"
}
} catch(exc){
println "构建失败 - ${currentBuild.fullDisplayName}"
throw(exc)
}
}
stage('构建 Docker 镜像') {
withCredentials([[$class: 'UsernamePasswordMultiBinding',
credentialsId: 'harbor',
usernameVariable: 'harborUser',
passwordVariable: 'harborPassword']]) {
container('docker') {
echo "构建 Docker 镜像阶段"
sh """
docker login ${dockerRegistryUrl} -u ${harborUser} -p ${harborPassword}
docker build -t ${image}:${imageTag} .
docker push ${image}:${imageTag}
"""
}
}
}
//stage('运行 Kubectl') {
// container('kubectl') {
// echo "查看 K8S 集群 Pod 列表"
// sh "kubectl get pods"
// sh """
// sed -i "s#<IMAGE>#${image}#" manifests/k8s.yaml
// sed -i "s/<IMAGE_TAG>/${imageTag}/" manifests/k8s.yaml
// kubectl apply -f manifests/k8s.yaml
// """
// }
//}
stage('运行 Helm') {
withCredentials([[$class: 'UsernamePasswordMultiBinding',
credentialsId: 'harbor',
usernameVariable: 'harborUser',
passwordVariable: 'harborPassword']]) {
container('helm') {
//echo "查看 Helm Release 列表"
//sh "helm list"
echo "开始helm部署"
helmDeploy(
dry_run : false,
name : "polling",
chartDir : "polling",
namespace : "course",
tag : "${imageTag}",
image : "${image}",
username : "${harborUser}",
password : "${harborPassword}"
)
echo "helm 部署成功"
}
}
}
}
}