Kubernetes 部署 Jenkins + Kubernetes 的 CI CD
Jenkins 与 Kubernetes 的 CI 与 CD & Git + Maven + Docker+Kubectl
参考:
http://www.mydlq.club/article/47/
https://plugins.jenkins.io/kubernetes/
- Jenkins 与 Kubernetes 的 CI 与 CD & Git + Maven + Docker+Kubectl
- 一、Kubernetes 部署 Jenkins
- 二、Jenkins安装相关插件
- 三、配置相关凭据
- 四、Jenkins 配置 Kubernetes 插件
- 五、创建相关文件
- 六、如何写流水线脚本和使用插件
- 七、在 Jenkins 创建模板任务
- 八、创建 Pipeline 脚本
一、Kubernetes 部署 Jenkins
1.1. 使用StorageClass+NFS创建pv
具体创建StorageClass+NFS方法详见下面链接或自行百度
https://www.cnblogs.com/yg0070/p/16440498.html
创建pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
annotations:
volume.beta.kubernetes.io/storage-provisioner: master-nfs-storage # 与storageClass中metadata.name保持一致
finalizers:
- kubernetes.io/pvc-protection
name: jenkins
namespace: jenkins
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi #存储空间大小
storageClassName: master-nfs-storage # 与storageClass中的provisioner一致
1.2. 创建ServiceAccount & ClusterRoleBinding
Kubernetes 集群一般情况下都默认开启了 RBAC 权限,所以需要创建一个角色和服务账户,设置角色拥有一定权限,然后将角色与 ServiceAccount 绑定,最后将 ServiceAccount 与 Jenkins 绑定,这样来赋予 Jenkins 一定的权限,使其能够执行一些需要权限才能进行的操作。这里为了方便,将 cluster-admin 绑定到 ServiceAccount 来保证 Jenkins 拥有足够的权限。
apiVersion: v1
kind: ServiceAccount
metadata:
name: jenkins-admin #ServiceAccount名
namespace: jenkins #指定namespace,一定要修改成你自己的namespace
labels:
name: jenkins
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: jenkins-admin
labels:
name: jenkins
subjects:
- kind: ServiceAccount
name: jenkins-admin
namespace: jenkins
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
1.3. 创建Service & Deployment
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: jenkins
name: jenkins
namespace: jenkins
spec:
replicas: 1
selector:
matchLabels:
app: jenkins
template:
metadata:
labels:
app: jenkins
spec:
serviceAccountName: jenkins-admin
containers:
- name: jenkins
image: 'jenkins/jenkins:lts'
securityContext:
privileged: true #拥有特权
runAsUser: 0 #设置以ROOT用户运行容器
ports:
- containerPort: 8080
name: http
protocol: TCP
- containerPort: 50000
name: jnlp
protocol: TCP
resources:
limits:
cpu: '2'
memory: 2Gi
requests:
cpu: '1'
memory: 1Gi
env:
- name: LIMITS_MEMORY
valueFrom:
resourceFieldRef:
divisor: 1Mi
resource: limits.memory
- name: JAVA_OPTS #设置变量,指定时区和 jenkins slave 执行者设置
value: >-
-Xmx$(LIMITS_MEMORY)m -XshowSettings:vm
-Dhudson.slaves.NodeProvisioner.initialDelay=0
-Dhudson.slaves.NodeProvisioner.MARGIN=50
-Dhudson.slaves.NodeProvisioner.MARGIN0=0.85
-Duser.timezone=Asia/Shanghai
-Dorg.apache.commons.jelly.tags.fmt.timeZone=Asia/Shanghai
- name: JENKINS_OPTS #设置路径前缀加上 Jenkins
value: '--prefix=/jenkins'
- name: JENKINS_JAVA_OPTIONS
value: '-Dorg.apache.commons.jelly.tags.fmt.timeZone=Asia/Shanghai'
- name: JAVA_ARGS
value: >-
-Xmx$(LIMITS_MEMORY)m -XshowSettings:vm
-Dhudson.slaves.NodeProvisioner.initialDelay=0
-Dhudson.slaves.NodeProvisioner.MARGIN=50
-Dhudson.slaves.NodeProvisioner.MARGIN0=0.85
-Duser.timezone=Asia/Shanghai
-Dorg.apache.commons.jelly.tags.fmt.timeZone=Asia/Shanghai
- name: JENKINS_JAVA_OPTIONS
value: >-
-Xmx$(LIMITS_MEMORY)m -XshowSettings:vm
-Dhudson.slaves.NodeProvisioner.initialDelay=0
-Dhudson.slaves.NodeProvisioner.MARGIN=50
-Dhudson.slaves.NodeProvisioner.MARGIN0=0.85
-Duser.timezone=Asia/Shanghai
volumeMounts:
- mountPath: /var/jenkins_home #设置要挂在的目录
name: data
volumes:
- name: data
persistentVolumeClaim:
claimName: jenkins #设置PVC
---
apiVersion: v1
kind: Service
metadata:
labels:
app: jenkins
name: jenkins
namespace: jenkins
spec:
ports:
- name: http
nodePort: 32001 #NodePort方式暴露 Jenkins 端口
port: 8080 #服务端口
protocol: TCP
targetPort: 8080
- name: jnlp
nodePort: 32002
port: 50000 #代理端口
protocol: TCP
targetPort: 50000
selector:
app: jenkins
type: NodePort
参数说明:
- JAVA_OPTS: JVM 参数设置
- JENKINS_OPTS: Jenkins 参数设置
- 其它参数: 默认情况下,Jenkins 生成代理是保守的。例如,如果队列中有两个构建,它不会立即生成两个执行器。它将生成一个执行器,并等待某个时间释放第一个执行器,然后再决定生成第二个执行器。Jenkins 确保它生成的每个执行器都得到了最大限度的利用。如果你想覆盖这个行为,并生成一个执行器为每个构建队列立即不等待,所以在 Jenkins 启动时候添加这些参数:
-Dhudson.slaves.NodeProvisioner.initialDelay=0
-Dhudson.slaves.NodeProvisioner.MARGIN=50
-Dhudson.slaves.NodeProvisioner.MARGIN0=0.85
1.4.获取 Jenkins 生成的 Token
在安装 Jenkins 时候,它默认生成一段随机字符串在控制台日志中,用于安装时验证。这里需要获取它输出在控制台中的日志信息,来获取 Token 字符串。
查看jenkins pod 启动日志
- -n:指定应用启动的 namespace
$ kubectl log $(kubectl get pods -n mydlqcloud | awk '{print $1}' | grep jenkins) -n mydlqcloud
在日志中可以看到,默认给的token为:
*************************************************************
Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:
96b199612s12sd15s55dfs52dff55db8d
This may also be found at: /var/jenkins_home/secrets/initialAdminPassword
*************************************************************
5.启动 Jenkins 进行初始化
输入 Kubernetes 集群地址和上面设置的 Nodeport
方式的端口号 32001
,访问Jenins UI 界面进行初始化。输入从日志中获取的token进入初始化页面。
安装插件
安装插件,选择 推荐安装
方式进行安装即可,后续再安装需要的插件。
设置用户名、密码
在这里输入一个用户名、密码,方便后续登录,如果不设置可能下次登录需要使用之前日志中默认的 Token 串来登录。
配置 Jenkins 地址
配置 Jenkins URL 地址,来告知 Jenkins 自己的 URL,在发送邮件、触发钩子等可能用到。
进入 Jenkins 界面
到此 Jenkins 初始化就配置完成,成功进入 Jenkins 界面。
二、Jenkins安装相关插件
Jenkins 中可以打开 系统管理->插件管理->可选插件 来安装下面的一些插件:
- Git: Jenkins 安装中默认安装 Git 插件,所以不需要单独安装。利用 git 工具可以将 github、gitlab 等等的地址下载源码。
- Docker: Jenkins 安装中默认安装 Docker 插件,所以不需要单独安装。利用 Docker 插件可以设置 Docker 环境,运行 Docker 命令,配置远程 Docker 仓库凭据等。
- Kubernetes: Kubernetes 插件的目的是能够使用 Kubernetes 集群动态配置 Jenkins 代理(使用Kubernetes调度机制来优化负载),运行单个构建,等构建完成后删除该代理。这里我们需要用到这个插件来启动 Jenkins Slave 代理镜像,让代理执行 Jenkins 要执行的 Job。
- Kubernetes Cli: Kubernetes Cli 插件作用是在执行 Jenkins Job 时候提供 kubectl 与 Kubernetes 集群交互环境。可以在 Pipeline 或自由式项目中允许执行 kubectl 相关命令。它的主要作用是提供 kubectl 运行环境,当然也可以提供 helm 运行环境。
- Config File Provider: Config File Provider 插件作用就是提供在 Jenkins 中存储 properties、xml、json、settings.xml 等信息,可以在执行 Pipeline 过程中可以写入存储的配置。例如,存入一个 Maven 全局 Settings.xml 文件,在执行 Pipeline Job 时候引入该 Settings.xml ,这样 Maven 编译用的就是该全局的 Settings.xml。
- Pipeline Utility Steps: 这是一个操作文件的插件,例如读写 json、yaml、pom.xml、Properties 等等。在这里主要用这个插件读取 pom.xml 文件的参数设置,获取变量,方便构建 Docker 镜像。
- Git Parameter: 能够与 Git 插件结合使用,动态获取 Git 项目中分支信息,在 Jenkins Job 构建前提供分支选项,来让项目执行人员选择拉取对应分支的代码。
- DingTalk:钉钉插件,能够推送钉钉机器人消息。
三、配置相关凭据
选择 凭据->系统->全局凭据->添加凭据 来新增 Git、Docker Hub、Kubernetes 等认证凭据。
3.1、添加 Git 认证凭据
配置的参数值:
- 类型:Username with password
- 范围:全局
- 用户名(Git 用户名): 略
- 密码(Git 密码):略
- ID:global-git-credential
- 描述:全局 Git 凭据
3.2、添加 Kubernetes Token 凭据
配置的参数值:
- 类型:Secret text
- 范围:全局
- Secret(K8S Token 串):略
- ID:global-kubernetes-credential
- 描述:全局的 K8S Token
3.3、添加 Docker 仓库认证凭据
配置的参数值:
- 类型:Username with password
- 范围:全局
- 用户名(Docker 仓库用户名):略
- 密码(Docker 仓库密码):略
- ID:docker-hub-credential
- 描述:Docker 仓库认证凭据
四、Jenkins 配置 Kubernetes 插件
进入 系统管理->节点管理->云 中,点击 新增一个云 选项,来新建一个与 Kubernetes 的连接,然后按照下面各个配置项进行配置。
4.1、Kubernetes Plugin 基本配置
4.1.1、配置连接 Kubernetes 参数
配置 Kubernetes API 地址,然后再选择 Kubernetes Token 凭据。
注意: 如果你的 Jenkins 也是安装在 Kubernetes 环境中,那么可以直接使用 Kubernetes 集群内的 Kubernetes API 地址,如果 Jnekins 是在安装在正常物理机或者虚拟机环境中,那么使用集群外的 Kubernetes API 地址,两个地址如下:
- 集群内地址:https://kubernetes.default.svc.cluster.local
- 集群外地址:https://{Kubernetes 集群 IP}:6443
然后点击连接测试,查看是否能成功连通 Kubernetes,如果返回结果 Successful 则代表连接成功,否则失败。
4.1.2、配置 Jenkins 地址
注意: 这里的 Jenkins 地址是供 Slave 节点连接 Jenkins Master 节点用的,所以这里需要配置 Jenkins Master 的 URL 地址。这里和上面一样,也是考虑 Jenkins 是部署在 Kubernetes 集群内还是集群外,两个地址如下:
- 集群内地址:https://{Jenkins Pod 名称}.{Jenkins Pod 所在 Namespace}/
- 集群外地址:https://{Kubernetes 集群 IP}:{Jenkins NodePort 端口}/
如果 Jnekins 中配置了 /jenkins 前缀,则 URL 后面加上 /jenkins,否则不加,这个地址根据自己的 Jnekins 实际情况来判断。
4.2、Kubernetes 插件配置
详细配置见 https://plugins.jenkins.io/kubernetes/
4.2.1、配置 Pod 名称和标签列表
配置 Pod 模板的名称和标签列表名,Pod 模板名可用于子模板继承,标签列表可用于 Jenkins Job 中指定,使用此 Pod 模板来执行任务。
4.2.2、配置 Maven
4.2.2.1、配置Maven镜像
- 名称:maven
- Docker 镜像:registry.cn-shanghai.aliyuncs.com/mydlq/maven:3.6.0-jdk8-alpine
- 其它参数:默认值即可
Maven 镜像可以从官方 Docker Hub 下载,地址:https://hub.docker.com/_/maven
4.2.2.2、创建 Maven 存储使用的 PV、PVC
Maven需要挂载存储,将中央仓库下载的 Jar 存储到共享目录
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: maven
namespace: jenkins
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: master-nfs-storage
4.2.2.3、配置 Maven 挂载
在卷选项中,选择添加卷,选择 Persistent Volume Claim
按如下添加配置:
- 申明值(PVC 名称):maven
- 挂在路径(容器内的目录):/root/.m2
4.2.3、配置 Docker In Docker
4.2.3.1、配置 Docker In Docker 镜像
- 名称:docker
- Docker 镜像:registry.cn-shanghai.aliyuncs.com/mydlq/docker:18.06.3-dind
- 其它参数:默认值即可
Docker-IN-Docker 镜像可以从官方 Docker Hub 下载,地址:https://hub.docker.com/_/docker
4.2.3.2、配置 Docker 挂载
Kubernetes 中 Pod 的容器是启动在各个节点上,每个节点就是一台宿主机,里面进行了很多 Docker 配置,所以我们这里将宿主机的 Docker 配置挂载进入 Docker 镜像。选择添加卷,选择 Host Path Volume
按如下添加配置:
① 路径 /usr/bin/docker:
- 主机路径(宿主机目录):/usr/bin/docker
- 挂载路径(容器内的目录):/usr/bin/docker
② 路径 /var/run/docker.sock:
- 主机路径(宿主机目录):/var/run/docker.sock
- 挂载路径(容器内的目录):/var/run/docker.sock
③ 路径 /etc/docker:
- 主机路径(宿主机目录):/etc/docker
- 挂载路径(容器内的目录):/etc/docker
4.2.4、配置 Kubectl 镜像
- 名称:kubectl
- Docker 镜像:registry.cn-shanghai.aliyuncs.com/mydlq/kubectl:1.15.3
- 其它参数:默认值即可
Kubectl 镜像可以从官方 Docker Hub 下载,地址:https://hub.docker.com/r/bitnami/kubectl
4.2.5、配置 Pod 的原始 yaml
此项可省略,保存后jenkins会自动生成,这里记录生成后的yaml
apiVersion: "v1"
kind: "Pod"
metadata:
annotations:
buildUrl: "http://jenkins.jenkins:8080/jenkins/job/new-test/19/"
runUrl: "job/new-test/19/"
labels:
jenkins: "slave"
jenkins/label: "jnlp"
name: "jnlp-agent"
spec:
containers:
- env:
- name: "JENKINS_TUNNEL"
value: "jenkins.jenkins:50000"
- name: "JENKINS_AGENT_WORKDIR"
value: "/home/jenkins/agent"
- name: "JENKINS_URL"
value: "http://jenkins.jenkins:8080/jenkins/"
image: "jenkins/inbound-agent:4.3-4"
name: "jnlp"
resources:
limits: {}
requests:
memory: "256Mi"
cpu: "100m"
volumeMounts:
- mountPath: "/home/jenkins/agent"
name: "workspace-volume"
readOnly: false
nodeSelector:
kubernetes.io/os: "linux"
restartPolicy: "Never"
volumes:
- emptyDir:
medium: ""
name: "workspace-volume"
完整的配置如下
五、创建相关文件
之前安装了 Config File Provider 插件,该插件功能就是可以在 Jenkins 上存储一些配置文件,例如,我们经常使用到的 yaml、properties、Dockerfile、Maven 的 Settings.xml 等文件,都可以存储到 Jenkins 该插件中,也可以将文件存储到项目中从项目中读取文件。
打开 系统管理->Managed files ,在其中新增几个文件:
- Maven 配置文件: Maven 的 Settings.xml 配置文件。
- Dockerfile 文件: Dockerfile 脚本。
- Kubernetes 部署文件: 将应用部署到 kubernetes 的 Deployment 文件。
5.1、新增 Maven 配置文件
选择 Add a new Config—>Global Maven settings.xml 来新增一个 Maven 全局 Settings.xml 文件:
- ID: global-maven-settings
- Name: MavenGlobalSettings
- Comment: 全局 Maven Settings.xml 文件
- Content: 内容如下↓:
为了加快 jar 包的下载速度,这里将仓库地址指向 aliyun Maven 仓库地址。
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<pluginGroups>
</pluginGroups>
<proxies>
</proxies>
<servers>
</servers>
<mirrors>
<!--Aliyun Maven-->
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
<profiles>
</profiles>
</settings>
5.2、新增 Dockerfile 文件
选择 Add a new Config—>Custom file 来新增一个 Dockerfile 文件:
java服务dockerfile
- ID: global-dockerfile-file
- Name: Dockerfile
- Comment: 全局 Dockerfile 文件
- Content: 内容如下↓:
FROM java:8
VOLUME /tmp
ADD target/*.jar app.jar
RUN sh -c 'touch /app.jar'
ENV JVM_OPTS="-Xss256k -Duser.timezone=Asia/Shanghai -Djava.security.egd=file:/dev/./urandom"
ENV JAVA_OPTS=""
ENV APP_OPTS=""
ENTRYPOINT [ "sh", "-c", "java $JVM_OPTS $JAVA_OPTS -jar /app.jar $APP_OPTS" ]
vue服务dockerfile
- ID: global-dockerfile-file
- Name: Dockerfile
- Comment: 全局 Dockerfile 文件
- Content: 内容如下↓:
FROM node:lts-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install -g cnpm --registry=https://registry.npm.taobao.org
RUN cnpm install
COPY . ./
RUN npm run build:prod
# production stage
FROM nginx:stable-alpine as production-stage
COPY nginx/nginx.conf /etc/nginx/nginx.conf
COPY --from=build-stage /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
5.3、新增 Kubernetes 部署文件
选择 Add a new Config—>Custom file 来新增一个 Kubernetes 部署文件:
- ID: global-kubernetes-deployment
- Name: deployment.yaml
- Comment: 全局 Kubernetes 部署文件
- Content: 内容如下↓:
apiVersion: v1
kind: Service
metadata:
name: #APP_NAME
labels:
app: #APP_NAME
spec:
type: NodePort
ports:
- name: server #服务端口
port: 8080
targetPort: 8080
- name: management #监控及监控检查的端口
port: 8081
targetPort: 8081
selector:
app: #APP_NAME
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: #APP_NAME
labels:
app: #APP_NAME
spec:
replicas: #APP_REPLICAS
selector:
matchLabels:
app: #APP_NAME
strategy:
type: Recreate #设置更新策略为删除策略,如需要灰度发布则去掉使用默认的
template:
metadata:
labels:
app: #APP_NAME
spec:
containers:
- name: #APP_NAME
image: #APP_IMAGE_NAME
imagePullPolicy: Always
ports:
- containerPort: 8080 #服务端口
name: server
- containerPort: 8081 #监控及监控检查的端口
name: management
env:
- name: "update_uuid"
value: "#APP_UUID" #生成的随机值,放置执行kubectl apply时能够执行
- name: SPRING_PROFILES_ACTIVE
value: #SPRING_PROFILES_ACTIVE
- name: REDIS_HOST
value: #REDIS_HOST
- name: REDIS_PORT
value: "#REDIS_PORT"
- name: REDIS_PW
value: #REDIS_PW
- name: DB_URL
value: #DB_URL
- name: DB_NAME
value: #DB_NAME
- name: DB_PASSWORD
value: #DB_PASSWORD
resources:
limits:
cpu: 2000m
memory: 1024Mi
requests:
cpu: 1000m
memory: 512Mi
imagePullSecrets:
- name: #{K8S_PULLIMAGES_SECRET}
为了模板能够动态替换某些值,上面模板中设置了几个可替换的参数,用 #变量名称 来标记,后面我们在执行 Pipeline 时候将里面的 #xxx变量 标记替换掉,上面配置的变量有:
- #APP_NAME: 应用名称。
- #APP_PORT:应用端口
- #APP_REPLICAS: 应用副本数。
- #APP_IMAGE_NAME: 镜像名称。
- #APP_UUID: 生成的随机值,因为后续 Kubectl 在执行命令时候,如果部署对象中的值没有一点变化的话,将不会执行 kubectl apply 命令,所以这里设置了一个随机值,以确保每次部署文件都不一致。
- #SPRING_PROFILES_ACTIVE:项目参数 - 指定环境
- #REDIS_HOST:项目参数-redis地址
- #REDIS_PORT:项目参数-redis端口
- #REDIS_PW:项目参数-redis密码
- #DB_URL:项目参数-数据库地址及端口
- #DB_NAME:项目参数-数据库名
- #DB_USERNAME:项目参数-数据库用户名
- #DB_PASSWORD:项目参数-数据库密码
- #REDIS_HOST:项目参数-redis地址
- #K8S_PULLIMAGES_SECRET:k8s中维护的拉取镜像的secret凭证
并且还有一点就是要注意,设置更新策略为 Recreate(删除再创建) 策略,否则后面的健康检查阶段将不能正常检查更新后的项目。
Kubernetes 默认为 RollingUpdate 策略,该策略为应用启动时,先将新实例启动,再删除旧的实例,就是因为这样,在后面健康检查阶段,健康检查 URL 地址还是未更新前的旧实例的 URL 地址,会导致健康检查不准确,所以必须改为 Recreate 策略,先删除旧实例,再创建新实例。
六、如何写流水线脚本和使用插件
具体配置参考官网文档:
https://plugins.jenkins.io/kubernetes/
https://www.jenkins.io/doc/pipeline/steps/ssh-steps/
https://plugins.jenkins.io/ssh-steps/#plugin-content-withcredentials
6.1、脚本中设置全局超时时间
设置任务超时时间,如果在规定时间内任务没有完成,则进行失败操作,格式如下:
timeout(time: 60, unit: 'SECONDS') {
// 脚本
}
6.2、脚本中使用 Git 插件
Git 插件方法使用格式,及其部分参数:
- changelog: 是否检测变化日志
- url: Git 项目地址
- branch: Git 分支
- credentialsId: Jenkins 存的 Git 凭据 ID 值
git changelog: true,
url: "http://gitlab.xxxx/xxx.git"
branch: "master",
credentialsId: "xxxx-xxxx-xxxx-xxxx",
6.3、脚本中使用 Kubernetes 插件
Kubernetes 插件中存在 PodTemplate 方法,在执行脚本时候,会自动在 Kubernetes 中创建 Pod Template 配置的 Slave Pod,在其中执行 podTemplate 代码块中的脚本。
def label = "jnlp-agent"
podTemplate(label: label,cloud: 'kubernetes' ){
node (label) {
print "在 Slave Pod 中执行任务"
}
}
podTemplate 方法参数简介:
- cloud: 之前 Kuberntes 插件配置的 Cloud 名称
- label: 之前 Kuberntes 插件配置的 Cloud 中 Pod Template 里面的 Label 标签名称。
6.4、脚本中使用 Docker 镜像
在之前配置了 Kubernetes 插件的 Pod Template 配置中,配置了几个容器,每个容器中都有特定的功能的环境,例如:
- Maven 容器中能够执行 mvn 命令。
- Kuberctl 容器能够执行 kubectl 命令。
- Docker In Docker 容器中能够执行 Docker 命令。
既然每个容器都能提供特定的环境,那么再执行执行 Pipleline 脚本时候,就可以在不同的镜像中使用不同的环境的命令:
- Maven 镜像
container('maven') {
sh "mvn install
}
- Docker In Docker 镜像
container('docker') {
sh "docker build -t xxxxx:1.0.0 .
}
- Kubectl 镜像
container('kubectl') {
sh "kubectl apply -f xxxx.yaml"
}
6.5、脚本中引入 Jenkins 中预先存储的文件
在之前的 系统设置->File Manager 中,存储了很多文件,例如:
- Docker 的镜像构建脚本文件 Dockerfile。
- Maven 的全局设置文件 Settings.xml
- Kubernetes 的部署文件 deployment.yaml
在使用 Pipleline 脚本时候,我们需要将这些文件文本提取出来,创建在执行任务的流程中,创建这些文件可以使用 Config File Provider 插件提供的 configFileProvider 方法,如下所示:
- 创建 settings.xml 文件
configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
sh "cat settings.xml"
}
- 创建 Dockerfile 文件
configFileProvider([configFile(fileId: "global-dockerfile-file", targetLocation: "Dockerfile")]){
sh "cat Dockerfile"
}
- 创建 Dockerfile 文件
configFileProvider([configFile(fileId: "global-kubernetes-deployment", targetLocation: "deployment.yaml")]){
sh "cat deployment.yaml"
}
6.6、脚本创建文件
在使用 Groovy 写 Pipleline 脚本时候,经常要将变量的文本生成文件,方便在执行流水线过程中操作文本文件使用,如何将文件转换为文件,可以使用 Pipeline Utility Steps 插件的 writeFile 方法,如下:
writeFile encoding: 'UTF-8', file: './test.txt', text: "写入文件的文本内容"
6.7、脚本中使用 Http Rrequest 插件
脚本中可以使用 HttpRequest 来对某一地址进行请求,这里简单使用 Get 请求地址,复杂的可以查看 Jenkins 插件的官网查看使用示例。
下面是使用 Http Request 的 Get 请求示例:
result = httpRequest "http:www.baidu.com"
if ("${result.status}" == "200") {
print "Http 请求成功"
}
6.8、脚本中使用 Kubernetes Cli 插件
在之前说过,在 kubectl 镜像中能够使用 kubectl 命令,不过由于执行 Kubectl 命令一般需要在镜像的 $HOME/.kube/ 目录中存在连接 Kubernetes API 的 config 文件,使其 kubectl 命令有明确请求 kubernetes API 的地址和用户权限,不过将 config 文件挂入镜像内部是一件比较繁琐的事情。
好在 Jenkins 提供的 Kubectl Cli 插件,只要在其中配置连接 Kubernetes 的 Token 凭据,就能够在 Kubectl Cli 提供的 withKubeConfig 方法,拥有类似存在 config 一样的功能,在 kubectl 镜像中的 withKubeConfig 方法块内执行 kubectl 就可以操作配置的 Kubectl Cli 的凭据的 K8S 集群。
container('kubectl') {
withKubeConfig([credentialsId: "Kubernetes Token 凭据 ID",serverUrl: "https://kubernetes.default.svc.cluster.local"]) {
sh "kubectl get nodes"
}
}
6.9、脚本中操作字符串替换值
在使用 Groovy 语法写 Pipleline 脚本时候,我们经常要替换先前设置好的一些文本的值,这里我们简单示例一下,如何替换字符串。
// 测试的字符串
sourceStr = "这是要替换的值:#value1,这是要替换的值:#value2"
// 替换#value1与#value2连个值
afterStr = sourceStr.replaceAll("#value1","AAA").replaceAll("#value2","BBB")
// 输出替换后的字符串
print "${afterStr}"
6.10、脚本中读取 pom.xml 参数
在执行 Java 项目的流水线时,我们经常要动态获取项目中的属性,很多属性都配置在项目的 pom.xml 中,还好 Pipeline Utility Steps 插件提供能够读取 pom.xml 的方法,示例如下:
stage('读取pom.xml参数阶段'){
// 读取 Pom.xml 参数
pom = readMavenPom file: './pom.xml'
// 输出读取的参数
print "${pom.artifactId}"
print = "${pom.version}"
}
6.11、脚本中使用 Docker 插件构建与推送镜像
在流水线脚本中,我们一般不直接使用 Docker 命令,而是使用 Docker 插件提供的 docker.withRegistry("") 方法来构建与推送镜像,并且还能在方法中配置登录凭据信息,来让仓库验证权限,这点是非常方便的。使用示例如下:
docker.withRegistry("http://xxxx Docker 仓库地址", "Docker 仓库凭据 ID") {
// 构建 Docker 镜像
def customImage = docker.build("${dockerImageName}")
// 推送 Docker 镜像
customImage.push()
}
七、在 Jenkins 创建模板任务
创建一个 Pipeline Job 来充当各个 Jenkins Job 的模板,方便后续创建 Job 时,直接复制模板项目,然后修改配置就能使用。所以这里我们创建一个模板 Pipeline Job,在 Job 配置中需要添加一些参数和环境变量,方便我们动态替换一些值。
7.1、创建 Pipeline 任务
- 任务名称: my-template
- 任务类型: 流水线项目
7.2、配置项目构建基本参数
配置同一时间一个 Job 只能构建一个,不允许多个并发构建。另外需要设置项目构建后,包的保留时间,以防止包过多且大占用大量空间(一个包很肯能占 10MB~200MB 大小)导致储不足。
7.3、配置变量
在 Job 配置的 参数化构建过程 中,添加下面参数:
7.3.1、配置GIT变量
7.3.1.1、Git 项目地址变量
- 变量名称:GIT_PROJECT_URL
- 类型:String
- 描述:项目 Git 地址
- 默认值:"https://xxxxxxxxxxxx"
7.3.1.2、Git 分支变量
- 变量名称:GIT_BRANCH
- 类型:Git Parameter
- 描述:选择 Git 分支
- 默认值:master
7.3.1.3、Git 凭据变量
- 变量名称:GIT_CREADENTIAL
- 类型:Credentials
- 描述:Git 凭据
- 默认值:global-git-credential
7.3.2、配置 Maven 变量
7.3.2.1、Maven 构建命令变量
- 变量名称:MAVEN_BUILD_OPTION
- 类型:Choices
- 描述:要执行的执行 Maven 命令选择
- 可选值:['package', 'install', 'deploy']
- 默认值:install
7.3.3、配置 Docker 变量
7.3.3.1、Docker 项目地址变量
- 变量名称:DOCKER_HUB_URL
- 类型:String
- 描述:Docker 仓库地址
- 默认值(默认 Docker 仓库地址):"---------------"
7.3.3.2、Docker 仓库项目组变量
- 变量名称:DOCKER_HUB_GROUP
- 类型:String
- 描述:Docker 仓库项目组名
- 默认值:""
7.3.3.3、Docker 仓库认证凭据变量
- 变量名称:DOCKER_HUB_CREADENTIAL
- 类型:Credentials
- 描述:Docker 仓库认证凭据
- 默认值:docker-hub-credential
7.3.3.4、Docker Dockerfile 文件 ID 变量
- 变量名称:DOCKER_DOCKERFILE_ID
- 类型:String
- 描述:存于 Jenkins "Managed files" 的 Dockerfile 文件的 ID
- 默认值:"global-dockerfile-file"
7.3.4、配置 Kubernetes 变量
7.3.4.1、Kubernetes 认证凭据变量
- 变量名称:KUBERNETES_CREADENTIAL
- 类型:Credentials
- 描述:Kubernetes 认证 Token
- 默认值:global-kubernetes-credential
7.3.4.2、Kubernetes Namespace 变量
- 变量名称:KUBERNETES_NAMESPACE
- 类型:String
- 描述:Kubernetes 命名空间 Namespace
- 默认值:""
7.3.4.3、Kubernetes 应用实例副本数
- 变量名称:KUBERNETES_APP_REPLICAS
- 类型:String
- 描述:应用实例副本数
- 默认值:1
7.3.4.4、Kubernetes 应用部署 yaml 文件ID
- 变量名称:KUBERNETES_DEPLOYMENT_ID
- 类型:String
- 描述:存于 Jenkins "Managed files" 的 K8S 部署文件的 ID
- 默认值:"global-kubernetes-deployment"
7.3.5、配置 HTTP 变量
7.3.5.1、HTTP 健康检查端口
- 变量名称:HTTP_REQUEST_PORT
- 类型:String
- 描述:Http Request 端口(健康检测端口)
- 默认值:8081
7.3.5.2、HTTP 健康检查地址
- 变量名称:HTTP_REQUEST_URL
- 类型:String
- 描述:Http Request 项目中的相对路径(健康检测路径)
- 默认值:/actuator/health
7.3.5.3、HTTP 健康检查次数
- 变量名称:HTTP_REQUEST_NUMBER
- 类型:Choices
- 描述:Http Request 请求次数
- 可选值:['10', '5', '10', '15', '20', '25', '30']
- 默认值:10
7.3.5.4、HTTP 健康检查时间间隔
- 变量名称:HTTP_REQUEST_INTERVAL
- 类型:Choices
- 描述:Http Request 时间间隔
- 可选值:['10', '5', '15', '20', '25', '30']
- 默认值:10
7.3.6、配置项目参数
全部采用String类型创建
7.3.6.1、服务端口号
- 变量名称:PROJECT_ENV_PORT
- 类型:String
- 描述:项目访问端口
- 默认值:8080
7.3.6.2、服务环境
- 变量名称:PROJECT_ENV_SPRING_PROFILES_ACTIVE
- 类型:String
- 描述:项目参数 - 环境参数
- 默认值:dev
7.3.6.3、Redis地址
- 变量名称:PROJECT_ENV_REDIS_HOST
- 类型:String
- 描述:项目参数-redis地址
- 默认值:
7.3.6.4、Redis端口
- 变量名称:PROJECT_ENV_REDIS_PORT
- 类型:String
- 描述:项目参数-redis端口
- 默认值:
7.3.6.5、Redis密码
- 变量名称:PROJECT_ENV_REDIS_PW
- 类型:String
- 描述:项目参数-redis密码
- 默认值:
7.3.6.6、数据库地址及端口
- 变量名称:PROJECT_ENV_DB_URL
- 类型:String
- 描述:项目参数-数据库地址及端口
- 默认值:
7.3.6.7、数据库名
- 变量名称:PROJECT_ENV_DB_NAME
- 类型:String
- 描述:项目参数-数据库名
- 默认值:
7.3.6.8、数据库密码
- 变量名称:PROJECT_ENV_DB_PASSWORD
- 类型:String
- 描述:项目参数-数据库密码
- 默认值:
7.3.6.9、k8s中维护的拉取镜像的secret凭证
- 变量名称:K8S_PULLIMAGES_SECRET
- 类型:String
- 描述:k8s中维护的拉取镜像的secret凭证
- 默认值:
八、创建 Pipeline 脚本
接下将使用 Groovy 语法创建一个为 SpringBoot 项目准备的 CI/CD 的脚本式的流水线脚本。其中,脚本中包含多个阶段,分别为 Git 拉取镜像,Maven 编译 Java 项目,Docker 构建与推送镜像,Kubectl 部署应用到 Kubernetes 中,最后使用 Http 请求进行健康检查,成功的话发送钉钉消息,下面是各个阶段脚本及其介绍。
8.1、脚本中使用 Kubernetes 插件及设置超时时间
使用 Kubernetes 插件执行任务,并设置超时时间为 10 分钟,脚本如下:
// 设置超时时间 600 SECONDS,方法块内的方法执行超时,任务就标记为失败
timeout(time: 600, unit: 'SECONDS') {
def label = "jnlp-agent"
podTemplate(label: label,cloud: 'kubernetes' ){
node (label) {
print "在 Slave Pod 中执行任务"
}
}
}
8.2、脚本中 Git 拉取项目阶段
接下来接着往整体的脚本中添加 Git 模块,其中需要引用上面配置的变量,将变量填入脚本中的方法,如下:
timeout(time: 600, unit: 'SECONDS') {
def label = "jnlp-agent"
podTemplate(label: label,cloud: 'kubernetes' ){
node (label) {
stage('Git阶段'){
git changelog: true,
url: "${params.GIT_PROJECT_URL}",
branch: "${params.GIT_BRANCH}",
credentialsId: "${params.GIT_CREADENTIAL}"
}
stage('get_commit_msg') {
script {
MAX_MSG_LEN = 100
def changeString = ""
echo "Gathering SCM changes"
// 获取提交的说明
def changeLogSets = currentBuild.changeSets
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
def entry = entries[j]
truncated_msg = entry.msg.take(MAX_MSG_LEN)
changeString += "--${truncated_msg} [${entry.author}]\n"
}
}
if (!changeString) {
changeString = " - 无"
}
env.GIT_COMMIT_MSG = changeString
}
}
}
}
}
变量介绍:
- GIT_BRANCH: Git 项目分支变量。
- GIT_PROJECT_URL: Git 项目 URL 变量。
- GIT_CREADENTIAL: Git 凭据 ID 变量。
8.3、脚本中 Maven 编译项目阶段
timeout(time: 600, unit: 'SECONDS') {
def label = "jnlp-agent"
podTemplate(label: label,cloud: 'kubernetes' ){
node (label) {
stage('Git阶段'){
git changelog: true,
url: "${params.GIT_PROJECT_URL}",
branch: "${params.GIT_BRANCH}",
credentialsId: "${params.GIT_CREADENTIAL}"
}
stage('get_commit_msg') {
script {
MAX_MSG_LEN = 100
def changeString = ""
echo "Gathering SCM changes"
// 获取提交的说明
def changeLogSets = currentBuild.changeSets
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
def entry = entries[j]
truncated_msg = entry.msg.take(MAX_MSG_LEN)
changeString += "--${truncated_msg} [${entry.author}]\n"
}
}
if (!changeString) {
changeString = " - 无"
}
env.GIT_COMMIT_MSG = changeString
}
}
stage('Maven阶段'){
container('maven') {
// 创建 Maven 需要的 Settings.xml 文件
configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
// 执行 Maven 命令构建项目,并且设置 Maven 配置为刚刚创建的 Settings.xml 文件
sh "mvn -T 1C clean ${params.MAVEN_BUILD_OPTION} -Dmaven.test.skip=true --settings settings.xml"
}
}
}
}
}
}
变量介绍:
- MAVEN_BUILD_OPTION: Maven 执行的构建命令,package、install 或 deploy。
- global-maven-settings: 全局 Maven 的 Settings.xml 文件的 ID 值,这里是使用 configFileProvider 插件来创建该文件。
8.4、脚本中读取 pom.xml 参数阶段
这里使用 Pipeline Utility Steps
的 readMavenPom
方法读取项目的 pom.xml
文件,并设置 appName
与 appVersion
两个全局参数。
timeout(time: 600, unit: 'SECONDS') {
def label = "jnlp-agent"
podTemplate(label: label,cloud: 'kubernetes' ){
node (label) {
stage('Git阶段'){
git changelog: true,
url: "${params.GIT_PROJECT_URL}",
branch: "${params.GIT_BRANCH}",
credentialsId: "${params.GIT_CREADENTIAL}"
}
stage('获取git提交信息') {
script {
MAX_MSG_LEN = 100
def changeString = ""
echo "Gathering SCM changes"
// 获取提交的说明
def changeLogSets = currentBuild.changeSets
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
def entry = entries[j]
truncated_msg = entry.msg.take(MAX_MSG_LEN)
changeString += "--${truncated_msg} [${entry.author}]\n"
}
}
if (!changeString) {
changeString = " - 无"
}
env.GIT_COMMIT_MSG = changeString
}
}
stage('Maven阶段'){
container('maven') {
// 创建 Maven 需要的 Settings.xml 文件
configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
// 执行 Maven 命令构建项目,并且设置 Maven 配置为刚刚创建的 Settings.xml 文件
sh "mvn -T 1C clean ${params.MAVEN_BUILD_OPTION} -Dmaven.test.skip=true --settings settings.xml"
}
}
}
stage('读取pom.xml参数阶段'){
// 读取 Pom.xml 参数
pom = readMavenPom file: './pom.xml'
// 设置 appName 和 appVersion 两个全局参数
appName = "${pom.artifactId}"
appVersion = "${pom.version}"
}
}
}
}
变量介绍:
- pom.artifactId: 从 pom.xml 文件中读取的 artifactId 参数值。
- pom.version: 从 pom.xml 文件中读取的 version 参数值。
8.5、脚本中 Docker 镜像构建与推送模块
timeout(time: 600, unit: 'SECONDS') {
def label = "jnlp-agent"
podTemplate(label: label,cloud: 'kubernetes' ){
node (label) {
stage('Git阶段'){
git changelog: true,
url: "${params.GIT_PROJECT_URL}",
branch: "${params.GIT_BRANCH}",
credentialsId: "${params.GIT_CREADENTIAL}"
}
stage('获取git提交信息') {
script {
MAX_MSG_LEN = 100
def changeString = ""
echo "Gathering SCM changes"
// 获取提交的说明
def changeLogSets = currentBuild.changeSets
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
def entry = entries[j]
truncated_msg = entry.msg.take(MAX_MSG_LEN)
changeString += "--${truncated_msg} [${entry.author}]\n"
}
}
if (!changeString) {
changeString = " - 无"
}
env.GIT_COMMIT_MSG = changeString
}
}
stage('Maven阶段'){
print "开始构建maven"
container('maven') {
// 创建 Maven 需要的 Settings.xml 文件
configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
// 执行 Maven 命令构建项目,并且设置 Maven 配置为刚刚创建的 Settings.xml 文件
sh "mvn -T 1C clean ${params.MAVEN_BUILD_OPTION} -Dmaven.test.skip=true --settings settings.xml"
}
}
}
stage('读取pom.xml参数阶段'){
// 读取 Pom.xml 参数
pom = readMavenPom file: './pom.xml'
// 设置 appName 和 appVersion 两个全局参数
appName = "${pom.artifactId}"
appVersion = "${pom.version}"
}
stage('Docker阶段'){
print "开始构建docker镜像"
container('docker') {
// 创建 Dockerfile 文件,但只能在方法块内使用
configFileProvider([configFile(fileId: "${params.DOCKER_DOCKERFILE_ID}", targetLocation: "Dockerfile")]){
// 设置 Docker 镜像名称
dockerImageName = "${params.DOCKER_HUB_URL}/${params.DOCKER_HUB_GROUP}/${appName}:${appVersion}"
// 判断 DOCKER_HUB_GROUP 是否为空,有些仓库是不设置仓库组的
if ("${params.DOCKER_HUB_GROUP}" == '') {
dockerImageName = "${params.DOCKER_HUB_URL}/${appName}:${appVersion}"
}
// 提供 Docker 环境,使用 Docker 工具来进行 Docker 镜像构建与推送
docker.withRegistry("http://${params.DOCKER_HUB_URL}", "${params.DOCKER_HUB_CREADENTIAL}") {
def customImage = docker.build("${dockerImageName}")
print "docker镜像构建完成"
customImage.push()
print "docker镜像push完成"
sh "docker rmi ${dockerImageName}"
print "镜像已删除"
}
}
}
}
}
}
}
变量介绍:
- DOCKER_DOCKERFILE_ID: Dockerfile 文件的 ID。
- DOCKER_HUB_URL: Docker 仓库 URL 地址。
- DOCKER_HUB_GROUP: Docker 仓库项目组名。
- DOCKER_HUB_CREADENTIAL: Docker 仓库认证凭据。
- appName: 从 pom.xml 中读取的应用名称。
- appVersion: 从 pom.xml 中读取的应用版本号。
8.6、Kubernetes 模块
timeout(time: 600, unit: 'SECONDS') {
def label = "jnlp-agent"
podTemplate(label: label,cloud: 'kubernetes' ){
node (label) {
stage('Git阶段'){
git changelog: true,
url: "${params.GIT_PROJECT_URL}",
branch: "${params.GIT_BRANCH}",
credentialsId: "${params.GIT_CREADENTIAL}"
}
stage('获取git提交信息') {
script {
MAX_MSG_LEN = 100
def changeString = ""
echo "Gathering SCM changes"
// 获取提交的说明
def changeLogSets = currentBuild.changeSets
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
def entry = entries[j]
truncated_msg = entry.msg.take(MAX_MSG_LEN)
changeString += "--${truncated_msg} [${entry.author}]\n"
}
}
if (!changeString) {
changeString = " - 无"
}
env.GIT_COMMIT_MSG = changeString
}
}
stage('Maven阶段'){
print "开始构建maven"
container('maven') {
// 创建 Maven 需要的 Settings.xml 文件
configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
// 执行 Maven 命令构建项目,并且设置 Maven 配置为刚刚创建的 Settings.xml 文件
sh "mvn -T 1C clean ${params.MAVEN_BUILD_OPTION} -Dmaven.test.skip=true --settings settings.xml"
}
}
}
stage('读取pom.xml参数阶段'){
// 读取 Pom.xml 参数
pom = readMavenPom file: './pom.xml'
// 设置 appName 和 appVersion 两个全局参数
appName = "${pom.artifactId}"
appVersion = "${pom.version}"
}
stage('Docker阶段'){
print "开始构建docker镜像"
container('docker') {
// 创建 Dockerfile 文件,但只能在方法块内使用
configFileProvider([configFile(fileId: "${params.DOCKER_DOCKERFILE_ID}", targetLocation: "Dockerfile")]){
// 设置 Docker 镜像名称
dockerImageName = "${params.DOCKER_HUB_URL}/${params.DOCKER_HUB_GROUP}/${appName}:${appVersion}"
// 判断 DOCKER_HUB_GROUP 是否为空,有些仓库是不设置仓库组的
if ("${params.DOCKER_HUB_GROUP}" == '') {
dockerImageName = "${params.DOCKER_HUB_URL}/${appName}:${appVersion}"
}
// 提供 Docker 环境,使用 Docker 工具来进行 Docker 镜像构建与推送
docker.withRegistry("http://${params.DOCKER_HUB_URL}", "${params.DOCKER_HUB_CREADENTIAL}") {
def customImage = docker.build("${dockerImageName}")
print "docker镜像构建完成"
customImage.push()
print "docker镜像push完成"
sh "docker rmi ${dockerImageName}"
print "镜像已删除"
}
}
}
}
stage('Kubernetes 阶段'){
container('kubectl') {
// 使用 Kubectl Cli 插件的方法,提供 Kubernetes 环境,在其方法块内部能够执行 kubectl 命令
withKubeConfig([credentialsId: "${params.KUBERNETES_CREADENTIAL}",serverUrl: "https://kubernetes.default.svc.cluster.local"]) {
// 使用 configFile 插件,创建 Kubernetes 部署文件 deployment.yaml
configFileProvider([configFile(fileId: "${params.KUBERNETES_DEPLOYMENT_ID}", targetLocation: "deployment.yaml")]){
// 读取 Kubernetes 部署文件
deploy = readFile encoding: "UTF-8", file: "deployment.yaml"
// 替换部署文件中的变量,并将替换后的文本赋予 deployfile 变量
deployfile = deploy.replaceAll("#APP_NAME","${appName}")
.replaceAll("#APP_PORT","${params.PROJECT_ENV_PORT}")
.replaceAll("#SPRING_PROFILES_ACTIVE","${params.PROJECT_ENV_SPRING_PROFILES_ACTIVE}")
.replaceAll("#REDIS_HOST","${params.PROJECT_ENV_REDIS_HOST}")
.replaceAll("#REDIS_PORT","${params.PROJECT_ENV_REDIS_PORT}")
.replaceAll("#REDIS_PW","${params.PROJECT_ENV_REDIS_PW}")
.replaceAll("#DB_URL","${params.PROJECT_ENV_DB_URL}")
.replaceAll("#DB_NAME","${params.PROJECT_ENV_DB_NAME}")
.replaceAll("#DB_PASSWORD","${params.PROJECT_ENV_DB_PASSWORD}")
.replaceAll("#APP_REPLICAS","${params.KUBERNETES_APP_REPLICAS}")
.replaceAll("#APP_IMAGE_NAME","${dockerImageName}")
.replaceAll("#APP_UUID",(new Random().nextInt(100000)).toString())
.replaceAll("#K8S_PULLIMAGES_SECRET","${params.K8S_PULLIMAGES_SECRET}")
// 生成新的 Kubernetes 部署文件,内容为 deployfile 变量中的文本,文件名称为 "deploy.yaml"
writeFile encoding: 'UTF-8', file: './deploy.yaml', text: "${deployfile}"
// 输出新创建的部署 yaml 文件内容
sh "cat deploy.yaml"
// 执行 Kuberctl 命令进行部署操作
sh "kubectl apply -n ${params.KUBERNETES_NAMESPACE} -f deploy.yaml"
}
}
}
}
}
}
}
变量介绍:
- KUBERNETES_CREADENTIAL: Kubernetes API 认证凭据。
- KUBERNETES_DEPLOYMENT_ID: Kubernetes 部署文件的 ID。
- appName: 从 pom.xml 中读取的应用名称。
- PROJECT_ENV_PORT: 项目参数 - 访问端口。
- PROJECT_ENV_SPRING_PROFILES_ACTIVE: 项目参数 - 环境。
- PROJECT_ENV_REDIS_HOST: 项目参数 - redis地址。
- PROJECT_ENV_REDIS_PORT: 项目参数 - redis端口。
- PROJECT_ENV_REDIS_PW: 项目参数 - redis密码。
- PROJECT_ENV_DB_URL: 项目参数 - 数据库地址。
- PROJECT_ENV_DB_NAME: 项目参数 - 数据库用户名。
- PROJECT_ENV_DB_PASSWORD: 项目参数 - 数据库密码。
- KUBERNETES_APP_REPLICAS: Kubernetes 部署应用的副本数。
- dockerImageName: Docker 镜像名称。
- K8S_PULLIMAGES_SECRET: k8s中维护的拉取镜像的secret凭证。
- KUBERNETES_NAMESPACE: Kubernetes 部署应用的 Namespace。
8.7、HTTP 健康检查模块
timeout(time: 600, unit: 'SECONDS') {
def label = "jnlp-agent"
podTemplate(label: label,cloud: 'kubernetes' ){
node (label) {
stage('Git阶段'){
git changelog: true,
url: "${params.GIT_PROJECT_URL}",
branch: "${params.GIT_BRANCH}",
credentialsId: "${params.GIT_CREADENTIAL}"
}
stage('获取git提交信息') {
script {
MAX_MSG_LEN = 100
def changeString = ""
echo "Gathering SCM changes"
// 获取提交的说明
def changeLogSets = currentBuild.changeSets
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
def entry = entries[j]
truncated_msg = entry.msg.take(MAX_MSG_LEN)
changeString += "--${truncated_msg} [${entry.author}]\n"
}
}
if (!changeString) {
changeString = " - 无"
}
env.GIT_COMMIT_MSG = changeString
}
}
stage('Maven阶段'){
print "开始构建maven"
container('maven') {
// 创建 Maven 需要的 Settings.xml 文件
configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
// 执行 Maven 命令构建项目,并且设置 Maven 配置为刚刚创建的 Settings.xml 文件
sh "mvn -T 1C clean ${params.MAVEN_BUILD_OPTION} -Dmaven.test.skip=true --settings settings.xml"
}
}
}
stage('读取pom.xml参数阶段'){
// 读取 Pom.xml 参数
pom = readMavenPom file: './pom.xml'
// 设置 appName 和 appVersion 两个全局参数
appName = "${pom.artifactId}"
appVersion = "${pom.version}"
}
stage('Docker阶段'){
print "开始构建docker镜像"
container('docker') {
// 创建 Dockerfile 文件,但只能在方法块内使用
configFileProvider([configFile(fileId: "${params.DOCKER_DOCKERFILE_ID}", targetLocation: "Dockerfile")]){
// 设置 Docker 镜像名称
dockerImageName = "${params.DOCKER_HUB_URL}/${params.DOCKER_HUB_GROUP}/${appName}:${appVersion}"
// 判断 DOCKER_HUB_GROUP 是否为空,有些仓库是不设置仓库组的
if ("${params.DOCKER_HUB_GROUP}" == '') {
dockerImageName = "${params.DOCKER_HUB_URL}/${appName}:${appVersion}"
}
// 提供 Docker 环境,使用 Docker 工具来进行 Docker 镜像构建与推送
docker.withRegistry("http://${params.DOCKER_HUB_URL}", "${params.DOCKER_HUB_CREADENTIAL}") {
def customImage = docker.build("${dockerImageName}")
print "docker镜像构建完成"
customImage.push()
print "docker镜像push完成"
sh "docker rmi ${dockerImageName}"
print "镜像已删除"
}
}
}
}
stage('Kubernetes 阶段'){
container('kubectl') {
// 使用 Kubectl Cli 插件的方法,提供 Kubernetes 环境,在其方法块内部能够执行 kubectl 命令
withKubeConfig([credentialsId: "${params.KUBERNETES_CREADENTIAL}",serverUrl: "https://kubernetes.default.svc.cluster.local"]) {
// 使用 configFile 插件,创建 Kubernetes 部署文件 deployment.yaml
configFileProvider([configFile(fileId: "${params.KUBERNETES_DEPLOYMENT_ID}", targetLocation: "deployment.yaml")]){
// 读取 Kubernetes 部署文件
deploy = readFile encoding: "UTF-8", file: "deployment.yaml"
// 替换部署文件中的变量,并将替换后的文本赋予 deployfile 变量
deployfile = deploy.replaceAll("#APP_NAME","${appName}")
.replaceAll("#APP_PORT","${params.PROJECT_ENV_PORT}")
.replaceAll("#SPRING_PROFILES_ACTIVE","${params.PROJECT_ENV_SPRING_PROFILES_ACTIVE}")
.replaceAll("#REDIS_HOST","${params.PROJECT_ENV_REDIS_HOST}")
.replaceAll("#REDIS_PORT","${params.PROJECT_ENV_REDIS_PORT}")
.replaceAll("#REDIS_PW","${params.PROJECT_ENV_REDIS_PW}")
.replaceAll("#DB_URL","${params.PROJECT_ENV_DB_URL}")
.replaceAll("#DB_NAME","${params.PROJECT_ENV_DB_NAME}")
.replaceAll("#DB_PASSWORD","${params.PROJECT_ENV_DB_PASSWORD}")
.replaceAll("#APP_REPLICAS","${params.KUBERNETES_APP_REPLICAS}")
.replaceAll("#APP_IMAGE_NAME","${dockerImageName}")
.replaceAll("#APP_UUID",(new Random().nextInt(100000)).toString())
.replaceAll("#K8S_PULLIMAGES_SECRET","${params.K8S_PULLIMAGES_SECRET}")
// 生成新的 Kubernetes 部署文件,内容为 deployfile 变量中的文本,文件名称为 "deploy.yaml"
writeFile encoding: 'UTF-8', file: './deploy.yaml', text: "${deployfile}"
// 输出新创建的部署 yaml 文件内容
sh "cat deploy.yaml"
// 执行 Kuberctl 命令进行部署操作
sh "kubectl apply -n ${params.KUBERNETES_NAMESPACE} -f deploy.yaml"
}
}
}
}
stage('健康检查阶段'){
// 设置检测延迟时间 10s,10s 后再开始检测
sleep 10
// 健康检查地址
httpRequestUrl = "http://${appName}.${params.KUBERNETES_NAMESPACE}:${params.HTTP_REQUEST_PORT}${params.HTTP_REQUEST_URL}"
// 循环使用 httpRequest 请求,检测服务是否启动
for(n = 1; n <= "${params.HTTP_REQUEST_NUMBER}".toInteger(); n++){
try{
// 输出请求信息和请求次数
print "访问服务:${appName} \n" +
"访问地址:${httpRequestUrl} \n" +
"访问次数:${n}"
// 如果非第一次检测,就睡眠一段时间,等待再次执行 httpRequest 请求
if(n > 1){
sleep "${params.HTTP_REQUEST_INTERVAL}".toInteger()
}
// 使用 HttpRequest 插件的 httpRequest 方法检测对应地址
result = httpRequest "${httpRequestUrl}"
// 判断是否返回 200
if ("${result.status}" == "200") {
print "Http 请求成功,流水线结束"
break
}
}catch(Exception e){
print "监控检测失败,将在 ${params.HTTP_REQUEST_INTERVAL} 秒后将再次检测。"
// 判断检测次数是否为最后一次检测,如果是最后一次检测,并且还失败了,就对整个 Jenkins 任务标记为失败
if (n == "${params.HTTP_REQUEST_NUMBER}".toInteger()) {
currentBuild.result = "FAILURE"
}
}
}
}
}
}
}
变量介绍:
- HTTP_REQUEST_PORT: HTTP 健康检查端口。
- HTTP_REQUEST_URL: HTTP 健康检查 URL 地址。
- HTTP_REQUEST_NUMBER: HTTP 健康检查次数。
- HTTP_REQUEST_INTERVAL: HTTP 健康检查间隔。
- KUBERNETES_NAMESPACE: Kubernetes 的 Namespace。
- appName: 从 pom.xml 中读取的应用名称。
8.8、发送钉钉通知
8.8.1、创建钉钉机器人
在需要推送钉钉消息的群中创建钉钉机器人,设置机器人名字及安全设置(jenkins中会用到)
8.8.2、获取webhook,并复制webhook
8.8.3、配置Jenkins
选择 系统管理—>系统配置—>钉钉 来配置钉钉机器人:
点击测试,可以看是否连通,发送钉钉机器人信息成功,则配置成功
8.8.4、脚本中调用dingtalk
timeout(time: 600, unit: 'SECONDS') {
def label = "jnlp-agent"
podTemplate(label: label,cloud: 'kubernetes' ){
node (label) {
stage('Git阶段'){
git changelog: true,
url: "${params.GIT_PROJECT_URL}",
branch: "${params.GIT_BRANCH}",
credentialsId: "${params.GIT_CREADENTIAL}"
}
stage('获取git提交信息') {
script {
MAX_MSG_LEN = 100
def changeString = ""
echo "Gathering SCM changes"
// 获取提交的说明
def changeLogSets = currentBuild.changeSets
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
def entry = entries[j]
truncated_msg = entry.msg.take(MAX_MSG_LEN)
changeString += "--${truncated_msg} [${entry.author}]\n"
}
}
if (!changeString) {
changeString = " - 无"
}
env.GIT_COMMIT_MSG = changeString
}
}
stage('Maven阶段'){
print "开始构建maven"
container('maven') {
// 创建 Maven 需要的 Settings.xml 文件
configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
// 执行 Maven 命令构建项目,并且设置 Maven 配置为刚刚创建的 Settings.xml 文件
sh "mvn -T 1C clean ${params.MAVEN_BUILD_OPTION} -Dmaven.test.skip=true --settings settings.xml"
}
}
}
stage('读取pom.xml参数阶段'){
// 读取 Pom.xml 参数
pom = readMavenPom file: './pom.xml'
// 设置 appName 和 appVersion 两个全局参数
appName = "${pom.artifactId}"
appVersion = "${pom.version}"
}
stage('Docker阶段'){
print "开始构建docker镜像"
container('docker') {
// 创建 Dockerfile 文件,但只能在方法块内使用
configFileProvider([configFile(fileId: "${params.DOCKER_DOCKERFILE_ID}", targetLocation: "Dockerfile")]){
// 设置 Docker 镜像名称
dockerImageName = "${params.DOCKER_HUB_URL}/${params.DOCKER_HUB_GROUP}/${appName}:${appVersion}"
// 判断 DOCKER_HUB_GROUP 是否为空,有些仓库是不设置仓库组的
if ("${params.DOCKER_HUB_GROUP}" == '') {
dockerImageName = "${params.DOCKER_HUB_URL}/${appName}:${appVersion}"
}
// 提供 Docker 环境,使用 Docker 工具来进行 Docker 镜像构建与推送
docker.withRegistry("http://${params.DOCKER_HUB_URL}", "${params.DOCKER_HUB_CREADENTIAL}") {
def customImage = docker.build("${dockerImageName}")
print "docker镜像构建完成"
customImage.push()
print "docker镜像push完成"
sh "docker rmi ${dockerImageName}"
print "镜像已删除"
}
}
}
}
stage('Kubernetes 阶段'){
container('kubectl') {
// 使用 Kubectl Cli 插件的方法,提供 Kubernetes 环境,在其方法块内部能够执行 kubectl 命令
withKubeConfig([credentialsId: "${params.KUBERNETES_CREADENTIAL}",serverUrl: "https://kubernetes.default.svc.cluster.local"]) {
// 使用 configFile 插件,创建 Kubernetes 部署文件 deployment.yaml
configFileProvider([configFile(fileId: "${params.KUBERNETES_DEPLOYMENT_ID}", targetLocation: "deployment.yaml")]){
// 读取 Kubernetes 部署文件
deploy = readFile encoding: "UTF-8", file: "deployment.yaml"
// 替换部署文件中的变量,并将替换后的文本赋予 deployfile 变量
deployfile = deploy.replaceAll("#APP_NAME","${appName}")
.replaceAll("#APP_PORT","${params.PROJECT_ENV_PORT}")
.replaceAll("#SPRING_PROFILES_ACTIVE","${params.PROJECT_ENV_SPRING_PROFILES_ACTIVE}")
.replaceAll("#REDIS_HOST","${params.PROJECT_ENV_REDIS_HOST}")
.replaceAll("#REDIS_PORT","${params.PROJECT_ENV_REDIS_PORT}")
.replaceAll("#REDIS_PW","${params.PROJECT_ENV_REDIS_PW}")
.replaceAll("#DB_URL","${params.PROJECT_ENV_DB_URL}")
.replaceAll("#DB_NAME","${params.PROJECT_ENV_DB_NAME}")
.replaceAll("#DB_PASSWORD","${params.PROJECT_ENV_DB_PASSWORD}")
.replaceAll("#APP_REPLICAS","${params.KUBERNETES_APP_REPLICAS}")
.replaceAll("#APP_IMAGE_NAME","${dockerImageName}")
.replaceAll("#APP_UUID",(new Random().nextInt(100000)).toString())
.replaceAll("#K8S_PULLIMAGES_SECRET","${params.K8S_PULLIMAGES_SECRET}")
// 生成新的 Kubernetes 部署文件,内容为 deployfile 变量中的文本,文件名称为 "deploy.yaml"
writeFile encoding: 'UTF-8', file: './deploy.yaml', text: "${deployfile}"
// 输出新创建的部署 yaml 文件内容
sh "cat deploy.yaml"
// 执行 Kuberctl 命令进行部署操作
sh "kubectl apply -n ${params.KUBERNETES_NAMESPACE} -f deploy.yaml"
}
}
}
}
stage('健康检查阶段'){
// 设置检测延迟时间 10s,10s 后再开始检测
sleep 10
// 健康检查地址
httpRequestUrl = "http://${appName}.${params.KUBERNETES_NAMESPACE}:${params.HTTP_REQUEST_PORT}${params.HTTP_REQUEST_URL}"
// 循环使用 httpRequest 请求,检测服务是否启动
for(n = 1; n <= "${params.HTTP_REQUEST_NUMBER}".toInteger(); n++){
try{
// 输出请求信息和请求次数
print "访问服务:${appName} \n" +
"访问地址:${httpRequestUrl} \n" +
"访问次数:${n}"
// 如果非第一次检测,就睡眠一段时间,等待再次执行 httpRequest 请求
if(n > 1){
sleep "${params.HTTP_REQUEST_INTERVAL}".toInteger()
}
// 使用 HttpRequest 插件的 httpRequest 方法检测对应地址
result = httpRequest "${httpRequestUrl}"
// 判断是否返回 200
if ("${result.status}" == "200") {
print "Http 请求成功,流水线结束"
// 发送钉钉通知
dingtalk (
robot: "jenkins-ding-msg", //jenkins中设置的钉钉参数中的id
type:'ACTION_CARD',
atAll: false,
title: "${appName}更新成功",
messageUrl: 'xxxx',
text: [
"### [${appName}](${env.JOB_URL}) ",
'---',
"- 任务:[${currentBuild.displayName}](${env.BUILD_URL})",
'- 状态:<font color=#00CD00 >更新成功</font>',
"- 更新内容:${env.GIT_COMMIT_MSG}",
"- 持续时间:${currentBuild.durationString}".split("and counting")[0],
]
)
break
}
}catch(Exception e){
print "监控检测失败,将在 ${params.HTTP_REQUEST_INTERVAL} 秒后将再次检测。"
// 判断检测次数是否为最后一次检测,如果是最后一次检测,并且还失败了,就对整个 Jenkins 任务标记为失败
if (n == "${params.HTTP_REQUEST_NUMBER}".toInteger()) {
currentBuild.result = "FAILURE"
dingtalk (
robot: "jenkins-ding-msg", //jenkins中设置的钉钉参数中的id
type:'ACTION_CARD',
atAll: false,
title: "${appName}更新失败",
messageUrl: 'xxxx',
text: [
"### [${appName}](${env.JOB_URL}) ",
'---',
"- 任务:[${currentBuild.displayName}](${env.BUILD_URL})",
'- 状态:<font color=#EE0000 >更新失败</font>',
"- 持续时间:${currentBuild.durationString}".split("and counting")[0],
]
)
}
}
}
}
}
}
}
9、完整脚本
9.1、使用在jenkins中配置的jnlp-agent
def label = "jnlp-agent"
timeout(time: 900, unit: 'SECONDS') {
podTemplate(label: label, cloud: 'kubernetes'){
node (label) {
stage('Git阶段'){
git changelog: true,
url: "${params.GIT_PROJECT_URL}",
branch: "${params.GIT_BRANCH}",
credentialsId: "${params.GIT_CREADENTIAL}"
}
stage('获取git提交信息') {
script {
MAX_MSG_LEN = 100
def changeString = ""
echo "Gathering SCM changes"
def changeLogSets = currentBuild.changeSets
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
def entry = entries[j]
truncated_msg = entry.msg.take(MAX_MSG_LEN)
changeString += "--${truncated_msg} [${entry.author}]\n"
}
}
if (!changeString) {
changeString = " - 无"
}
env.GIT_COMMIT_MSG = changeString
}
}
stage('Maven阶段'){
print "开始构建maven"
container('maven') {
// 创建 Maven 需要的 Settings.xml 文件
configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
// 执行 Maven 命令构建项目,并且设置 Maven 配置为刚刚创建的 Settings.xml 文件
sh "mvn -T 1C clean ${params.MAVEN_BUILD_OPTION} -Dmaven.test.skip=true --settings settings.xml"
}
}
print "maven构建完成"
}
stage('读取pom.xml参数阶段'){
// 读取 Pom.xml 参数
pom = readMavenPom file: './pom.xml'
// 设置 appName 和 appVersion 两个全局参数
appName = "${pom.artifactId}"
appVersion = "${pom.version}"
}
stage('Docker阶段'){
print "开始构建docker镜像"
container('docker') {
// 创建 Dockerfile 文件,但只能在方法块内使用
configFileProvider([configFile(fileId: "${params.DOCKER_DOCKERFILE_ID}", targetLocation: "Dockerfile")]){
// 设置 Docker 镜像名称
dockerImageName = "${params.DOCKER_HUB_URL}/${params.DOCKER_HUB_GROUP}/${appName}:${appVersion}"
// 判断 DOCKER_HUB_GROUP 是否为空,有些仓库是不设置仓库组的
if ("${params.DOCKER_HUB_GROUP}" == '') {
dockerImageName = "${params.DOCKER_HUB_URL}/${appName}:${appVersion}"
}
// 提供 Docker 环境,使用 Docker 工具来进行 Docker 镜像构建与推送
docker.withRegistry("https://${params.DOCKER_HUB_URL}", "${params.DOCKER_HUB_CREADENTIAL}") {
def customImage = docker.build("${dockerImageName}")
print "docker镜像构建完成"
customImage.push()
print "docker镜像push完成"
sh "docker rmi ${dockerImageName}"
print "镜像已删除"
}
}
}
}
stage('Kubernetes 阶段'){
print "重启k8s中的服务"
container('kubectl') {
withKubeConfig([credentialsId: "${params.KUBERNETES_CREADENTIAL}",serverUrl: "https://kubernetes.default.svc.cluster.local"]) {
// 读取 Kubernetes 部署文件
deploy = readFile encoding: "UTF-8", file: "./deployment.yaml"
deployfile = deploy.replaceAll("#APP_NAME","${appName}")
.replaceAll("#APP_PORT","${params.PROJECT_ENV_PORT}")
.replaceAll("#SPRING_PROFILES_ACTIVE","${params.PROJECT_ENV_SPRING_PROFILES_ACTIVE}")
.replaceAll("#REDIS_HOST","${params.PROJECT_ENV_REDIS_HOST}")
.replaceAll("#REDIS_PORT","${params.PROJECT_ENV_REDIS_PORT}")
.replaceAll("#REDIS_PW","${params.PROJECT_ENV_REDIS_PW}")
.replaceAll("#DB_URL","${params.PROJECT_ENV_DB_URL}")
.replaceAll("#DB_NAME","${params.PROJECT_ENV_DB_NAME}")
.replaceAll("#DB_PASSWORD","${params.PROJECT_ENV_DB_PASSWORD}")
.replaceAll("#APP_REPLICAS","${params.KUBERNETES_APP_REPLICAS}")
.replaceAll("#APP_IMAGE_NAME","${dockerImageName}")
.replaceAll("#APP_UUID",(new Random().nextInt(100000)).toString())
.replaceAll("#K8S_PULLIMAGES_SECRET","${params.K8S_PULLIMAGES_SECRET}")
// 生成新的 Kubernetes 部署文件,内容为 deployfile 变量中的文本,文件名称为 "deploy.yaml"
writeFile encoding: 'UTF-8', file: './deploy.yaml', text: "${deployfile}"
// 输出新创建的部署 yaml 文件内容
sh "cat deploy.yaml"
// 执行 Kuberctl 命令进行部署操作
sh "kubectl apply -n ${params.KUBERNETES_NAMESPACE} -f deploy.yaml"
print "重启完了"
}
}
}
stage('健康检查阶段'){
// 设置检测延迟时间 10s,10s 后再开始检测
sleep 10
// 健康检查地址
httpRequestUrl = "http://${appName}.${params.KUBERNETES_NAMESPACE}:${params.HTTP_REQUEST_PORT}${params.HTTP_REQUEST_URL}"
// 循环使用 httpRequest 请求,检测服务是否启动
for(n = 1; n <= "${params.HTTP_REQUEST_NUMBER}".toInteger(); n++){
try{
// 输出请求信息和请求次数
print "访问服务:${appName} \n" +
"访问地址:${httpRequestUrl} \n" +
"访问次数:${n}"
// 如果非第一次检测,就睡眠一段时间,等待再次执行 httpRequest 请求
if(n > 1){
sleep "${params.HTTP_REQUEST_INTERVAL}".toInteger()
}
// 使用 HttpRequest 插件的 httpRequest 方法检测对应地址
result = httpRequest "${httpRequestUrl}"
// 判断是否返回 200
if ("${result.status}" == "200") {
print "Http 请求成功,流水线结束"
dingtalk (
robot: "shawanyi-test",
type:'ACTION_CARD',
atAll: false,
title: "${appName}更新成功",
messageUrl: 'xxxx',
text: [
"### [${appName}](${env.JOB_URL}) ",
'---',
"- 任务:[${currentBuild.displayName}](${env.BUILD_URL})",
'- 状态:<font color=#00CD00 >更新成功</font>',
"- 更新内容:${env.GIT_COMMIT_MSG}",
"- 持续时间:${currentBuild.durationString}".split("and counting")[0],
]
)
break
}
}catch(Exception e){
print "监控检测失败,将在 ${params.HTTP_REQUEST_INTERVAL} 秒后将再次检测。"
// 判断检测次数是否为最后一次检测,如果是最后一次检测,并且还失败了,就对整个 Jenkins 任务标记为失败
if (n == "${params.HTTP_REQUEST_NUMBER}".toInteger()) {
currentBuild.result = "FAILURE"
dingtalk (
robot: "shawanyi-test",
type:'ACTION_CARD',
atAll: false,
title: "${appName}更新失败",
messageUrl: 'xxxx',
text: [
"### [${appName}](${env.JOB_URL}) ",
'---',
"- 任务:[${currentBuild.displayName}](${env.BUILD_URL})",
'- 状态:<font color=#EE0000 >更新失败</font>',
"- 持续时间:${currentBuild.durationString}".split("and counting")[0],
]
)
}
}
}
}
}
}
}
9.2、直接在groovy脚本中指定镜像参数
// 使用默认的jnlp
def label = "jnlp"
timeout(time: 900, unit: 'SECONDS') {
podTemplate(label: label, cloud: 'kubernetes',
// 手动指定需要用的镜像
containers: [
containerTemplate(name: 'maven', image: 'maven:3.3.9-jdk-8-alpine', ttyEnabled: true, command: 'cat'),
containerTemplate(name: 'docker', image: 'docker:stable', ttyEnabled: true, command: 'cat'),
containerTemplate(name: 'kubectl', image: 'lachlanevenson/k8s-kubectl:v1.18.20', ttyEnabled: true, command: 'cat')
],
// 挂载卷
volumes: [
persistentVolumeClaim(claimName: 'maven', mountPath: '/root/.m2'),
hostPathVolume(hostPath: '/usr/bin/docker', mountPath: '/usr/bin/docker'),
hostPathVolume(hostPath: '/var/run/docker.sock', mountPath: '/var/run/docker.sock'),
hostPathVolume(hostPath: '/etc/docker', mountPath: '/etc/docker'),
],
){
node (label) {
stage('Git阶段'){
git changelog: true,
url: "${params.GIT_PROJECT_URL}",
branch: "${params.GIT_BRANCH}",
credentialsId: "${params.GIT_CREADENTIAL}"
}
stage('获取git提交信息') {
script {
MAX_MSG_LEN = 100
def changeString = ""
echo "Gathering SCM changes"
def changeLogSets = currentBuild.changeSets
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
def entry = entries[j]
truncated_msg = entry.msg.take(MAX_MSG_LEN)
changeString += "--${truncated_msg} [${entry.author}]\n"
}
}
if (!changeString) {
changeString = " - 无"
}
env.GIT_COMMIT_MSG = changeString
}
}
stage('Maven阶段'){
print "开始构建maven"
container('maven') {
// 创建 Maven 需要的 Settings.xml 文件
configFileProvider([configFile(fileId: "global-maven-settings", targetLocation: "settings.xml")]){
// 执行 Maven 命令构建项目,并且设置 Maven 配置为刚刚创建的 Settings.xml 文件
sh "mvn -T 1C clean ${params.MAVEN_BUILD_OPTION} -Dmaven.test.skip=true --settings settings.xml"
}
}
print "maven构建完成"
}
stage('读取pom.xml参数阶段'){
// 读取 Pom.xml 参数
pom = readMavenPom file: './pom.xml'
// 设置 appName 和 appVersion 两个全局参数
appName = "${pom.artifactId}"
appVersion = "${pom.version}"
}
stage('Docker阶段'){
print "开始构建docker镜像"
container('docker') {
// 创建 Dockerfile 文件,但只能在方法块内使用
configFileProvider([configFile(fileId: "${params.DOCKER_DOCKERFILE_ID}", targetLocation: "Dockerfile")]){
// 设置 Docker 镜像名称
dockerImageName = "${params.DOCKER_HUB_URL}/${params.DOCKER_HUB_GROUP}/${appName}:${appVersion}"
// 判断 DOCKER_HUB_GROUP 是否为空,有些仓库是不设置仓库组的
if ("${params.DOCKER_HUB_GROUP}" == '') {
dockerImageName = "${params.DOCKER_HUB_URL}/${appName}:${appVersion}"
}
// 提供 Docker 环境,使用 Docker 工具来进行 Docker 镜像构建与推送
docker.withRegistry("https://${params.DOCKER_HUB_URL}", "${params.DOCKER_HUB_CREADENTIAL}") {
def customImage = docker.build("${dockerImageName}")
print "docker镜像构建完成"
customImage.push()
print "docker镜像push完成"
sh "docker rmi ${dockerImageName}"
print "镜像已删除"
}
}
}
}
stage('Kubernetes 阶段'){
print "重启k8s中的服务"
container('kubectl') {
withKubeConfig([credentialsId: "${params.KUBERNETES_CREADENTIAL}",serverUrl: "https://kubernetes.default.svc.cluster.local"]) {
// 读取 Kubernetes 部署文件
deploy = readFile encoding: "UTF-8", file: "./deployment.yaml"
deployfile = deploy.replaceAll("#APP_NAME","${appName}")
.replaceAll("#APP_PORT","${params.PROJECT_ENV_PORT}")
.replaceAll("#SPRING_PROFILES_ACTIVE","${params.PROJECT_ENV_SPRING_PROFILES_ACTIVE}")
.replaceAll("#REDIS_HOST","${params.PROJECT_ENV_REDIS_HOST}")
.replaceAll("#REDIS_PORT","${params.PROJECT_ENV_REDIS_PORT}")
.replaceAll("#REDIS_PW","${params.PROJECT_ENV_REDIS_PW}")
.replaceAll("#DB_URL","${params.PROJECT_ENV_DB_URL}")
.replaceAll("#DB_NAME","${params.PROJECT_ENV_DB_NAME}")
.replaceAll("#DB_PASSWORD","${params.PROJECT_ENV_DB_PASSWORD}")
.replaceAll("#APP_REPLICAS","${params.KUBERNETES_APP_REPLICAS}")
.replaceAll("#APP_IMAGE_NAME","${dockerImageName}")
.replaceAll("#APP_UUID",(new Random().nextInt(100000)).toString())
.replaceAll("#K8S_PULLIMAGES_SECRET","${params.K8S_PULLIMAGES_SECRET}")
// 生成新的 Kubernetes 部署文件,内容为 deployfile 变量中的文本,文件名称为 "deploy.yaml"
writeFile encoding: 'UTF-8', file: './deploy.yaml', text: "${deployfile}"
// 输出新创建的部署 yaml 文件内容
sh "cat deploy.yaml"
// 执行 Kuberctl 命令进行部署操作
sh "kubectl apply -n ${params.KUBERNETES_NAMESPACE} -f deploy.yaml"
print "重启完了"
}
}
}
stage('健康检查阶段'){
// 设置检测延迟时间 10s,10s 后再开始检测
sleep 10
// 健康检查地址
httpRequestUrl = "http://${appName}.${params.KUBERNETES_NAMESPACE}:${params.HTTP_REQUEST_PORT}${params.HTTP_REQUEST_URL}"
// 循环使用 httpRequest 请求,检测服务是否启动
for(n = 1; n <= "${params.HTTP_REQUEST_NUMBER}".toInteger(); n++){
try{
// 输出请求信息和请求次数
print "访问服务:${appName} \n" +
"访问地址:${httpRequestUrl} \n" +
"访问次数:${n}"
// 如果非第一次检测,就睡眠一段时间,等待再次执行 httpRequest 请求
if(n > 1){
sleep "${params.HTTP_REQUEST_INTERVAL}".toInteger()
}
// 使用 HttpRequest 插件的 httpRequest 方法检测对应地址
result = httpRequest "${httpRequestUrl}"
// 判断是否返回 200
if ("${result.status}" == "200") {
print "Http 请求成功,流水线结束"
dingtalk (
robot: "shawanyi-test",
type:'ACTION_CARD',
atAll: false,
title: "${appName}更新成功",
messageUrl: 'xxxx',
text: [
"### [${appName}](${env.JOB_URL}) ",
'---',
"- 任务:[${currentBuild.displayName}](${env.BUILD_URL})",
'- 状态:<font color=#00CD00 >更新成功</font>',
"- 更新内容:${env.GIT_COMMIT_MSG}",
"- 持续时间:${currentBuild.durationString}".split("and counting")[0],
]
)
break
}
}catch(Exception e){
print "监控检测失败,将在 ${params.HTTP_REQUEST_INTERVAL} 秒后将再次检测。"
// 判断检测次数是否为最后一次检测,如果是最后一次检测,并且还失败了,就对整个 Jenkins 任务标记为失败
if (n == "${params.HTTP_REQUEST_NUMBER}".toInteger()) {
currentBuild.result = "FAILURE"
dingtalk (
robot: "shawanyi-test",
type:'ACTION_CARD',
atAll: false,
title: "${appName}更新失败",
messageUrl: 'xxxx',
text: [
"### [${appName}](${env.JOB_URL}) ",
'---',
"- 任务:[${currentBuild.displayName}](${env.BUILD_URL})",
'- 状态:<font color=#EE0000 >更新失败</font>',
"- 持续时间:${currentBuild.durationString}".split("and counting")[0],
]
)
}
}
}
}
}
}
}