二十二 DevOps实战
(一) 自动化构建Java应用
1.创建Java测试用例
示例项目可以从https://gitee.com/dukuan/spring-boot-project.git找到该项目(也可以使用公司的Java项目也是一样的)。
接下来将该项目导入到自己的GitLab中。首先找到之前创建的Kubernetes组,然后点击New Project:
![](https://img.haomeiwen.com/i20896689/2e25e730e86f923d.png)
选择Import Project:
![](https://img.haomeiwen.com/i20896689/42e48762bfdf3d0f.png)
点击Repo by URL:
![](https://img.haomeiwen.com/i20896689/5400c4d46efce995.png)
在Git repository URL输入示例地址,然后点击Create Project即可:
![](https://img.haomeiwen.com/i20896689/fc98eac30e7bbc25.png)
导入后,如下所示:
![](https://img.haomeiwen.com/i20896689/244969590eacfd62.png)
2.定义Jenkinsfile
接下来再GitLab的源代码中添加Jenkinsfile。首先点击代码首页的“+”号,然后点击New file:
![](https://img.haomeiwen.com/i20896689/77ca7e0e9ae479ec.png)
在窗口中,添加如下内容:
pipeline {
agent {
kubernetes {
cloud 'kubernetes-study'
slaveConnectTimeout 1200
workspaceVolume hostPathWorkspaceVolume(hostPath: "/opt/workspace", readOnly: false)
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- args: [\'$(JENKINS_SECRET)\', \'$(JENKINS_NAME)\']
image: 'registry.cn-beijing.aliyuncs.com/citools/jnlp:alpine'
name: jnlp
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: "/etc/localtime"
name: "localtime"
readOnly: false
- command:
- "cat"
env:
- name: "LANGUAGE"
value: "en_US:en"
- name: "LC_ALL"
value: "en_US.UTF-8"
- name: "LANG"
value: "en_US.UTF-8"
image: "registry.cn-beijing.aliyuncs.com/citools/maven:3.5.3"
imagePullPolicy: "IfNotPresent"
name: "build"
tty: true
volumeMounts:
- mountPath: "/etc/localtime"
name: "localtime"
- mountPath: "/root/.m2/"
name: "cachedir"
readOnly: false
- command:
- "cat"
env:
- name: "LANGUAGE"
value: "en_US:en"
- name: "LC_ALL"
value: "en_US.UTF-8"
- name: "LANG"
value: "en_US.UTF-8"
image: "registry.cn-beijing.aliyuncs.com/citools/kubectl:self-1.17"
imagePullPolicy: "IfNotPresent"
name: "kubectl"
tty: true
volumeMounts:
- mountPath: "/etc/localtime"
name: "localtime"
readOnly: false
- command:
- "cat"
env:
- name: "LANGUAGE"
value: "en_US:en"
- name: "LC_ALL"
value: "en_US.UTF-8"
- name: "LANG"
value: "en_US.UTF-8"
image: "registry.cn-beijing.aliyuncs.com/citools/docker:19.03.9-git"
imagePullPolicy: "IfNotPresent"
name: "docker"
tty: true
volumeMounts:
- mountPath: "/etc/localtime"
name: "localtime"
readOnly: false
- mountPath: "/var/run/docker.sock"
name: "dockersock"
readOnly: false
restartPolicy: "Never"
nodeSelector:
build: "true"
securityContext: {}
volumes:
- hostPath:
path: "/var/run/docker.sock"
name: "dockersock"
- hostPath:
path: "/usr/share/zoneinfo/Asia/Shanghai"
name: "localtime"
- name: "cachedir"
hostPath:
path: "/opt/m2"
'''
}
}
stages {
stage('Pulling Code') {
parallel {
stage('Pulling Code by Jenkins') {
when {
expression {
env.gitlabBranch == null
}
}
steps {
git(changelog: true, poll: true, url: 'git@CHANGE_HERE_FOR_YOUR_GITLAB_URL:root/spring-boot-project.git', branch: "${BRANCH}", credentialsId: 'gitlab-key')
script {
COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()
TAG = BUILD_TAG + '-' + COMMIT_ID
println "Current branch is ${BRANCH}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"
}
}
}
stage('Pulling Code by trigger') {
when {
expression {
env.gitlabBranch != null
}
}
steps {
git(url: 'git@CHANGE_HERE_FOR_YOUR_GITLAB_URL:root/spring-boot-project.git', branch: env.gitlabBranch, changelog: true, poll: true, credentialsId: 'gitlab-key')
script {
COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()
TAG = BUILD_TAG + '-' + COMMIT_ID
println "Current branch is ${env.gitlabBranch}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"
}
}
}
}
}
stage('Building') {
steps {
container(name: 'build') {
sh """
curl repo.maven.apache.org
mvn clean install -DskipTests
ls target/*
"""
}
}
}
stage('Docker build for creating image') {
environment {
HARBOR_USER = credentials('HARBOR_ACCOUNT')
}
steps {
container(name: 'docker') {
sh """
echo ${HARBOR_USER_USR} ${HARBOR_USER_PSW} ${TAG}
docker build -t ${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} .
docker login -u ${HARBOR_USER_USR} -p ${HARBOR_USER_PSW} ${HARBOR_ADDRESS}
docker push ${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG}
"""
}
}
}
stage('Deploying to K8s') {
environment {
MY_KUBECONFIG = credentials('study-k8s-kubeconfig')
}
steps {
container(name: 'kubectl'){
sh """
/usr/local/bin/kubectl --kubeconfig $MY_KUBECONFIG set image deploy -l app=${IMAGE_NAME} ${IMAGE_NAME}=${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} -n $NAMESPACE
"""
}
}
}
}
environment {
COMMIT_ID = ""
HARBOR_ADDRESS = "CHANGE_HERE_FOR_YOUR_HARBOR_URL"
REGISTRY_DIR = "kubernetes"
IMAGE_NAME = "spring-boot-project"
NAMESPACE = "kubernetes"
TAG = ""
}
parameters {
gitParameter(branch: '', branchFilter: 'origin/(.*)', defaultValue: '', description: 'Branch for build and deploy', name: 'BRANCH', quickFilterEnabled: false, selectedValue: 'NONE', sortMode: 'NONE', tagFilter: '*', type: 'PT_BRANCH')
}
}
3.Jenkinsfile详解
首先是顶层的Agent,定义的是Kubernetes的Pod作为Jenkins的Slave:
agent {
# 定义使用Kubernetes作为agent
kubernetes {
# 选择的云为之前配置的名字
cloud 'kubernetes-study'
slaveConnectTimeout 1200
# 将workspace改成hostPath,因为该Slave会固定节点创建,如果有存储可用,可以改成PVC的模式
workspaceVolume hostPathWorkspaceVolume(hostPath: "/opt/workspace", readOnly: false)
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
# jnlp容器,和Jenkins主节点通信
- args: [\'$(JENKINS_SECRET)\', \'$(JENKINS_NAME)\']
image: 'registry.cn-beijing.aliyuncs.com/citools/jnlp:alpine'
name: jnlp
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: "/etc/localtime"
name: "localtime"
readOnly: false
# build容器,包含执行构建的命令,比如Java的需要mvn构建,就可以用一个maven的镜像
- command:
- "cat"
env:
- name: "LANGUAGE"
value: "en_US:en"
- name: "LC_ALL"
value: "en_US.UTF-8"
- name: "LANG"
value: "en_US.UTF-8"
# 使用Maven镜像,包含mvn工具。NodeJS可以用node的镜像
image: "registry.cn-beijing.aliyuncs.com/citools/maven:3.5.3"
imagePullPolicy: "IfNotPresent"
# 容器的名字,流水线的stage可以直接使用该名字
name: "build"
tty: true
volumeMounts:
- mountPath: "/etc/localtime"
name: "localtime"
# Pod单独创建了一个缓存的volume,将其挂载到了maven插件的缓存目录,默认是/root/.m2
- mountPath: "/root/.m2/"
name: "cachedir"
readOnly: false
# 发版容器,因为最终是发版至Kubernetes的,所以需要有一个kubectl命令
- command:
- "cat"
env:
- name: "LANGUAGE"
value: "en_US:en"
- name: "LC_ALL"
value: "en_US.UTF-8"
- name: "LANG"
value: "en_US.UTF-8"
# 镜像的版本可以替换为其它的版本,也可以不进行替换,因为只执行set命令,所以版本是兼容的
image: "registry.cn-beijing.aliyuncs.com/citools/kubectl:self-1.17"
imagePullPolicy: "IfNotPresent"
name: "kubectl"
tty: true
volumeMounts:
- mountPath: "/etc/localtime"
name: "localtime"
readOnly: false
# 用于生成镜像的容器,需要包含docker命令
- command:
- "cat"
env:
- name: "LANGUAGE"
value: "en_US:en"
- name: "LC_ALL"
value: "en_US.UTF-8"
- name: "LANG"
value: "en_US.UTF-8"
image: "registry.cn-beijing.aliyuncs.com/citools/docker:19.03.9-git"
imagePullPolicy: "IfNotPresent"
name: "docker"
tty: true
volumeMounts:
- mountPath: "/etc/localtime"
name: "localtime"
readOnly: false
# 由于容器没有启动docker服务,所以将宿主机的docker经常挂载至容器即可
- mountPath: "/var/run/docker.sock"
name: "dockersock"
readOnly: false
restartPolicy: "Never"
# 固定节点部署
nodeSelector:
build: "true"
securityContext: {}
volumes:
# Docker socket volume
- hostPath:
path: "/var/run/docker.sock"
name: "dockersock"
- hostPath:
path: "/usr/share/zoneinfo/Asia/Shanghai"
name: "localtime"
# 缓存目录
- name: "cachedir"
hostPath:
path: "/opt/m2"
'''
}
之后看一下Jenkinsfile最后的环境变量和parameters的配置:
# 定义一些全局的环境变量
environment {
COMMIT_ID = ""
HARBOR_ADDRESS = "CHANGE_HERE_FOR_YOUR_HARBOR_URL" # Harbor地址
REGISTRY_DIR = "kubernetes" # Harbor的项目目录
IMAGE_NAME = "spring-boot-project" # 镜像的名称
NAMESPACE = "kubernetes" # 该应用在Kubernetes中的命名空间
TAG = "" # 镜像的Tag,在此用BUILD_TAG+COMMIT_ID组成
}
parameters {
# 之前讲过一些choice、input类型的参数,本次使用的是GitParameter插件
# 该字段会在Jenkins页面生成一个选择分支的选项
gitParameter(branch: '', branchFilter: 'origin/(.*)', defaultValue: '', description: 'Branch for build and deploy', name: 'BRANCH', quickFilterEnabled: false, selectedValue: 'NONE', sortMode: 'NONE', tagFilter: '*', type: 'PT_BRANCH')
}
接下来是拉代码的stage,这个stage是一个并行的stage,因为考虑了该流水线是手动触发还是触发:
stage('Pulling Code') {
parallel {
stage('Pulling Code by Jenkins') {
when {
expression {
# 假如env.gitlabBranch为空,则该流水线为手动触发,那么就会执行该stage,如果不为空则会执行同级的另外一个stage
env.gitlabBranch == null
}
}
steps {
# 这里使用的是git插件拉取代码,BRANCH变量取自于前面介绍的parameters配置
# git@xxxxxx:root/spring-boot-project.git代码地址
# credentialsId: 'gitlab-key',之前创建的拉取代码的key
git(changelog: true, poll: true, url: 'git@xxxxxx:root/spring-boot-project.git', branch: "${BRANCH}", credentialsId: 'gitlab-key')
script {
# 定义一些变量用于生成镜像的Tag
# 获取最近一次提交的Commit ID
COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()
# 赋值给TAG变量,后面的docker build可以取到该TAG的值
TAG = BUILD_TAG + '-' + COMMIT_ID
println "Current branch is ${BRANCH}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"
}
}
}
stage('Pulling Code by trigger') {
when {
expression {
# 如果env.gitlabBranch不为空,说明该流水线是通过webhook触发,则此时执行该stage,上述的stage不再执行。此时BRANCH变量为空
env.gitlabBranch != null
}
}
steps {
# 以下配置和上述一致,只是此时branch: env.gitlabBranch取的值为env.gitlabBranch
git(url: 'git@xxxxxxxxxxx:root/spring-boot-project.git', branch: env.gitlabBranch, changelog: true, poll: true, credentialsId: 'gitlab-key')
script {
COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()
TAG = BUILD_TAG + '-' + COMMIT_ID
println "Current branch is ${env.gitlabBranch}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"
}
}
}
}
}
代码拉下来后,就可以执行构建命令,由于本次实验是Java示例,所以需要使用mvn命令进行构建:
stage('Building') {
steps {
# 使用Pod模板里面的build容器进行构建
container(name: 'build') {
sh """
# 编译命令,需要根据自己项目的实际情况进行修改,可能会不一致
mvn clean install -DskipTests
# 构建完成后,一般情况下会在target目录下生成jar包
ls target/*
"""
}
}
}
生成编译产物后,需要根据该产物生成对应的镜像,此时可以使用Pod模板的docker容器:
stage('Docker build for creating image') {
# 首先取出来HARBOR的账号密码
environment {
HARBOR_USER = credentials('HARBOR_ACCOUNT')
}
steps {
# 指定使用docker容器
container(name: 'docker') {
sh """
# 执行build命令,Dockerfile会在下一小节创建,也是放在代码仓库,和Jenkinsfile同级
docker build -t ${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} .
# 登录Harbor,HARBOR_USER_USR和HARBOR_USER_PSW由上述environment生成
docker login -u ${HARBOR_USER_USR} -p ${HARBOR_USER_PSW} ${HARBOR_ADDRESS}
# 将镜像推送至镜像仓库
docker push ${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG}
"""
}
}
}
最后一步就是将该镜像发版至Kubernetes集群中,此时使用的是包含kubectl命令的容器:
stage('Deploying to K8s') {
# 获取连接Kubernetes集群证书
environment {
MY_KUBECONFIG = credentials('study-k8s-kubeconfig')
}
steps {
# 指定使用kubectl容器
container(name: 'kubectl'){
sh """
# 直接set更改Deployment的镜像即可
/usr/local/bin/kubectl --kubeconfig $MY_KUBECONFIG set image deploy -l app=${IMAGE_NAME} ${IMAGE_NAME}=${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} -n $NAMESPACE
"""
}
}
}
}
注意:本次发版的命令为/usr/local/bin/kubectl --kubeconfig {IMAGE_NAME}
{HARBOR_ADDRESS}/
{IMAGE_NAME}:
NAMESPACE。由于本示例的Deployment名称、Label、容器名称均和项目名称一致,所以就统一了IMAGE_NAME,如果示例不是按照此规范命名,需要进行更改。
4.定义Dockerfile
在执行流水线过程时,需要将代码的编译产物做成镜像。Dockerfile主要写的是如何生成公司业务的镜像。而本次示例是Java项目,只需要把Jar包放在有Jre环境的镜像中,然后启动该Jar包即可:
# 基础镜像可以按需修改,可以更改为公司自有镜像
FROM registry.cn-beijing.aliyuncs.com/dotbalo/jre:8u211-data
# jar包名称改成实际的名称,本示例为spring-cloud-eureka-0.0.1-SNAPSHOT.jar
COPY target/spring-cloud-eureka-0.0.1-SNAPSHOT.jar ./
# 启动Jar包
CMD java -jar spring-cloud-eureka-0.0.1-SNAPSHOT.jar
5.定义Kubernetes资源
本示例在GitLab创建的Group为kubernetes,可以将其认为是一个项目,同一个项目可以部署至Kubernetes集群中同一个Namespace中,本示例为kubernetes命名空间。由于使用的是私有仓库,因此也需要先配置拉取私有仓库镜像的密钥:
# kubectl create ns kubernetes
namespace/kubernetes created
# kubectl create secret docker-registry harborkey --docker-server=CHANGE_HERE_FOR_YOUR_HARBOR_ADDRESS --docker-username=admin --docker-password=Harbor12345 --docker-email=mail@kubeasy.com -n kubernetes
secret/harborkey created
配置该应用的Deployment(部分内容),需要注意文件的加粗部分:
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: spring-boot-project # Deployment标签,和流水线的set -l一致
name: spring-boot-project # Deployment名称
namespace: kubernetes
spec:
replicas: 1
selector:
matchLabels:
app: spring-boot-project # Pod的标签
template:
metadata:
creationTimestamp: null
labels:
app: spring-boot-project # Pod的标签
spec:
...
- name: LANG
value: C.UTF-8
image: nginx # 此处使用的nginx作为原始的镜像,通过Jenkins构建并发版后,变成Java应用的镜像
imagePullPolicy: IfNotPresent
lifecycle: {}
livenessProbe:
failureThreshold: 2
initialDelaySeconds: 30
periodSeconds: 10
successThreshold: 1
tcpSocket:
port: 8761 # 端口号和健康检查按照实际情况进行修改
timeoutSeconds: 2
name: spring-boot-project # 容器的名称,需要和流水线set命令的容器名称一致
ports:
- containerPort: 8761 # 端口号按照实际情况进行修改
name: web
protocol: TCP
readinessProbe:
failureThreshold: 2
initialDelaySeconds: 30
periodSeconds: 10
successThreshold: 1
tcpSocket:
port: 8761 # 端口号和健康检查按照实际情况进行修改
timeoutSeconds: 2
resources: # 资源请求按照实际情况修改
limits:
cpu: 994m
memory: 1170Mi
requests:
cpu: 10m
memory: 55Mi
dnsPolicy: ClusterFirst
imagePullSecrets:
- name: harborkey # Harbor仓库密钥,需要和上述创建的Secret一致
restartPolicy: Always
securityContext: {}
serviceAccountName: default
定义该应用的Service和Ingress:
---
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: spring-boot-project
name: spring-boot-project
namespace: kubernetes
spec:
ports: # 端口按照实际情况进行修改
- name: web
port: 8761
protocol: TCP
targetPort: 8761
selector:
app: spring-boot-project
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
creationTimestamp: null
name: spring-boot-project
namespace: kubernetes
spec:
rules:
- host: spring-boot-project.test.com
http:
paths:
- backend:
service:
name: spring-boot-project
port:
number: 8761
path: /
pathType: ImplementationSpecific
完整文件:
---
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: spring-boot-project
name: spring-boot-project
namespace: kubernetes
spec:
ports:
- name: web
port: 8761
protocol: TCP
targetPort: 8761
selector:
app: spring-boot-project
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
creationTimestamp: null
name: spring-boot-project
namespace: kubernetes
spec:
rules:
- host: spring-boot-project.test.com
http:
paths:
- backend:
service:
name: spring-boot-project
port:
number: 8761
path: /
pathType: ImplementationSpecific
status:
loadBalancer: {}
---
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: spring-boot-project
name: spring-boot-project
namespace: kubernetes
spec:
replicas: 1
selector:
matchLabels:
app: spring-boot-project
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: spring-boot-project
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- spring-boot-project
topologyKey: kubernetes.io/hostname
weight: 100
containers:
- env:
- name: TZ
value: Asia/Shanghai
- name: LANG
value: C.UTF-8
image: nginx
imagePullPolicy: IfNotPresent
lifecycle: {}
livenessProbe:
failureThreshold: 2
initialDelaySeconds: 30
periodSeconds: 10
successThreshold: 1
tcpSocket:
port: 8761
timeoutSeconds: 2
name: spring-boot-project
ports:
- containerPort: 8761
name: web
protocol: TCP
readinessProbe:
failureThreshold: 2
initialDelaySeconds: 30
periodSeconds: 10
successThreshold: 1
tcpSocket:
port: 8761
timeoutSeconds: 2
resources:
limits:
cpu: 994m
memory: 1170Mi
requests:
cpu: 10m
memory: 55Mi
dnsPolicy: ClusterFirst
imagePullSecrets:
- name: harborkey
restartPolicy: Always
securityContext: {}
serviceAccountName: default
创建该资源:
# kubectl create -f spring-boot-project.yaml
service/spring-boot-project created
ingress.networking.k8s.io/spring-boot-project created
deployment.apps/spring-boot-project created
提示:由于在Harbor安装小节已经配置了kubernetes的项目目录,在此不再重复创建,如果需要将镜像推送至其它目录,按照同样的步骤创建即可。
6.创建Jenkins任务(Job)
单击首页的创建任务(Job)选项(或者是New Item)并配置任务信息,如图所示:
![](https://img.haomeiwen.com/i20896689/bd58aeacabe0f963.png)
输入的Job的名称(一般和GitLab的仓库名字一致,便于区分),类型为Pipeline,最后点击OK即可:
![](https://img.haomeiwen.com/i20896689/3252dd1cf30431dc.png)
在新页面,点击Pipeline,输入Git仓库地址、选择之前配置GitLab Key、分支为master,点击Save即可:
![](https://img.haomeiwen.com/i20896689/dcea044a27f5e5cc.png)
创建完成后,点击Build Now(由于Jenkins参数由Jenkinsfile生成,所以第一次执行流水线会失败):
![](https://img.haomeiwen.com/i20896689/59b3a43c1a717e88.png)
第一次构建结束后,可以看到Build Now变成了Build with Parameters。点击Build with Parameters后,可以读取到Git仓库的分支,之后可以选择分支进行手动构建(后面的章节会介绍自动触发):
![](https://img.haomeiwen.com/i20896689/cad800340908167b.png)
选择分支,之后点击Build,然后点击Build History的进度条即可看到构建日志:
![](https://img.haomeiwen.com/i20896689/61651f2c4b109486.png)
构建日志上部分为创建Pod的日志,可以看到Pod为Agent指定的Pod:
![](https://img.haomeiwen.com/i20896689/422c17ee1ed2d719.png)
也可以点击Blue Ocean更加直观的流程:
![](https://img.haomeiwen.com/i20896689/c7e1ff9b21968ceb.png)
如果是Java应用,可以看到BUILD SUCCESS说明mvn编译已经通过:
![](https://img.haomeiwen.com/i20896689/9bd27a29b966db68.png)
编译结束后,可以看到制作镜像的日志:
![](https://img.haomeiwen.com/i20896689/574020d3c04ee173.png)
之后镜像被推送至Harbor:
![](https://img.haomeiwen.com/i20896689/de6357dd4d19ccb9.png)
最后为发版至Kubernetes:
![](https://img.haomeiwen.com/i20896689/4650489a5924ea7b.png)
Finished:SUCCESS说明该流水线正常结束。接下来可以查看Deployment的镜像:
# kubectl get deploy -n kubernetes spring-boot-project -oyaml | grep "image:"
image: xxxxxxxxxxxxx.com/kubernetes/spring-boot-project:jenkins-spring-boot-project-4-959387c
# kubectl get po -n kubernetes
NAME READY STATUS RESTARTS AGE
spring-boot-project-869589667d-2zqjm 1/1 Running 0 6m53s
如果配置了域名,可以通过域名访问(测试域名需要配置hosts):
![](https://img.haomeiwen.com/i20896689/a56622882b5ad82a.png)
(二) 自动化构建Vue/H5前端应用
本节介绍自动化构建Vue/H5应用,其构建方式和自动化构建Java基本相同,重点是更改Deployment、Jenkinsfile和Dockerfile即可。
前端应用测试项目地址:https://gitee.com/dukuan/vue-project-new.git,可以参考Java小节的方式,导入前端项目到GitLab中,当然也可以使用公司自己的项目。
1.定义Jenkinsfile
完整文件:
pipeline {
agent {
kubernetes {
cloud 'kubernetes-study'
slaveConnectTimeout 1200
workspaceVolume hostPathWorkspaceVolume(hostPath: "/opt/workspace", readOnly: false)
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- args: [\'$(JENKINS_SECRET)\', \'$(JENKINS_NAME)\']
image: 'registry.cn-beijing.aliyuncs.com/citools/jnlp:alpine'
name: jnlp
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: "/etc/localtime"
name: "localtime"
readOnly: false
- command:
- "cat"
env:
- name: "LANGUAGE"
value: "en_US:en"
- name: "LC_ALL"
value: "en_US.UTF-8"
- name: "LANG"
value: "en_US.UTF-8"
image: "registry.cn-beijing.aliyuncs.com/citools/node:lts"
imagePullPolicy: "IfNotPresent"
name: "build"
tty: true
volumeMounts:
- mountPath: "/etc/localtime"
name: "localtime"
- mountPath: "/root/.m2/"
name: "cachedir"
readOnly: false
- command:
- "cat"
env:
- name: "LANGUAGE"
value: "en_US:en"
- name: "LC_ALL"
value: "en_US.UTF-8"
- name: "LANG"
value: "en_US.UTF-8"
image: "registry.cn-beijing.aliyuncs.com/citools/kubectl:self-1.17"
imagePullPolicy: "IfNotPresent"
name: "kubectl"
tty: true
volumeMounts:
- mountPath: "/etc/localtime"
name: "localtime"
readOnly: false
- command:
- "cat"
env:
- name: "LANGUAGE"
value: "en_US:en"
- name: "LC_ALL"
value: "en_US.UTF-8"
- name: "LANG"
value: "en_US.UTF-8"
image: "registry.cn-beijing.aliyuncs.com/citools/docker:19.03.9-git"
imagePullPolicy: "IfNotPresent"
name: "docker"
tty: true
volumeMounts:
- mountPath: "/etc/localtime"
name: "localtime"
readOnly: false
- mountPath: "/var/run/docker.sock"
name: "dockersock"
readOnly: false
restartPolicy: "Never"
nodeSelector:
build: "true"
securityContext: {}
volumes:
- hostPath:
path: "/var/run/docker.sock"
name: "dockersock"
- hostPath:
path: "/usr/share/zoneinfo/Asia/Shanghai"
name: "localtime"
- name: "cachedir"
hostPath:
path: "/opt/m2"
'''
}
}
stages {
stage('Pulling Code') {
parallel {
stage('Pulling Code by Jenkins') {
when {
expression {
env.gitlabBranch == null
}
}
steps {
git(changelog: true, poll: true, url: 'git@10.103.236.251:kubernetes/vue-project-new-new.git', branch: "${BRANCH}", credentialsId: 'gitlab-key')
script {
COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()
TAG = BUILD_TAG + '-' + COMMIT_ID
println "Current branch is ${BRANCH}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"
}
}
}
stage('Pulling Code by trigger') {
when {
expression {
env.gitlabBranch != null
}
}
steps {
git(url: 'git@10.103.236.251:kubernetes/vue-project-new.git', branch: env.gitlabBranch, changelog: true, poll: true, credentialsId: 'gitlab-key')
script {
COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()
TAG = BUILD_TAG + '-' + COMMIT_ID
println "Current branch is ${env.gitlabBranch}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"
}
}
}
}
}
stage('Building') {
steps {
container(name: 'build') {
sh """
npm install --registry=https://registry.npm.taobao.org
npm run build
"""
}
}
}
stage('Docker build for creating image') {
environment {
HARBOR_USER = credentials('HARBOR_ACCOUNT')
}
steps {
container(name: 'docker') {
sh """
echo ${HARBOR_USER_USR} ${HARBOR_USER_PSW} ${TAG}
docker build -t ${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} .
docker login -u ${HARBOR_USER_USR} -p ${HARBOR_USER_PSW} ${HARBOR_ADDRESS}
docker push ${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG}
"""
}
}
}
stage('Deploying to K8s') {
environment {
MY_KUBECONFIG = credentials('study-k8s-kubeconfig')
}
steps {
container(name: 'kubectl'){
sh """
/usr/local/bin/kubectl --kubeconfig $MY_KUBECONFIG set image deploy -l app=${IMAGE_NAME} ${IMAGE_NAME}=${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} -n $NAMESPACE
"""
}
}
}
}
environment {
COMMIT_ID = ""
HARBOR_ADDRESS = "10.103.236.204"
REGISTRY_DIR = "kubernetes"
IMAGE_NAME = "vue-project-new"
NAMESPACE = "kubernetes"
TAG = ""
}
parameters {
gitParameter(branch: '', branchFilter: 'origin/(.*)', defaultValue: '', description: 'Branch for build and deploy', name: 'BRANCH', quickFilterEnabled: false, selectedValue: 'NONE', sortMode: 'NONE', tagFilter: '*', type: 'PT_BRANCH')
}
}
Jenkinsfile和Java项目并无太大区别,需要更改的位置如下:
pipeline {
agent {
kubernetes {
... # 此处代码有省略
# 构建容器改为具有NodeJS环境的镜像,版本需要和公司项目一致
image: "registry.cn-beijing.aliyuncs.com/citools/node:lts"
imagePullPolicy: "IfNotPresent"
name: "build"
tty: true
volumeMounts:
- mountPath: "/etc/localtime"
name: "localtime"
- mountPath: "/root/.m2/" # NodeJS的缓存目录为node_modules,此处可以不配置,因为workspace采用的是hostPath,该目录会被缓存到创建Pod节点的/opt/目录
name: "cachedir"
readOnly: false
... # 此处代码有省略
# 此处的地址需要改为自己的Git地址
git(changelog: true, poll: true, url: 'git@xxxxxxxxxx:kubernetes/vue-project-new.git', branch: "${BRANCH}", credentialsId: 'gitlab-key')
...
}
stage('Pulling Code by trigger') {
when {
expression {
env.gitlabBranch != null
}
}
steps {
# 此处的地址需要改为自己的Git地址
git(url: 'git@xxxxxxxx:kubernetes/vue-project-new.git', branch: env.gitlabBranch, changelog: true, poll: true, credentialsId: 'gitlab-key')
...
stage('Building') {
steps {
container(name: 'build') {
sh """
# 此处为NodeJS/Vue项目的构建命令,根据实际情况进行修改
npm install --registry=https://registry.npm.taobao.org
npm run build
"""
}
}
}
...
REGISTRY_DIR = "kubernetes"
IMAGE_NAME = "vue-project-new" # 名字为vue-project-new
NAMESPACE = "kubernetes"
TAG = ""
...
2.定义Dockerfile
前端应用构建后一般会在dist文件下产生html文件,只需要拷贝到nginx的根目录下即可:
FROM registry.cn-beijing.aliyuncs.com/dotbalo/nginx:1.15.12
COPY dist/* /usr/share/nginx/html/
3.定义Kubernetes资源
对于Kubernetes的资源也是类似的,只需要更改资源名称和端口号即可(加粗部分):
---
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: vue-project-new
name: vue-project-new
namespace: kubernetes
spec:
ports:
- name: web
port: 80
protocol: TCP
targetPort: 80
selector:
app: vue-project-new
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
creationTimestamp: null
name: vue-project-new
namespace: kubernetes
spec:
rules:
- host: vue-project-new.test.com
http:
paths:
- backend:
service:
name: vue-project-new
port:
number: 80
path: /
pathType: ImplementationSpecific
---
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: vue-project-new
name: vue-project-new
namespace: kubernetes
spec:
replicas: 1
selector:
matchLabels:
app: vue-project-new
template:
metadata:
creationTimestamp: null
labels:
app: vue-project-new
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- vue-project-new
topologyKey: kubernetes.io/hostname
weight: 100
...
lifecycle: {}
livenessProbe:
failureThreshold: 2
initialDelaySeconds: 30
periodSeconds: 10
successThreshold: 1
tcpSocket:
port: 80
timeoutSeconds: 2
name: vue-project-new
ports:
- containerPort: 80
name: web
protocol: TCP
readinessProbe:
failureThreshold: 2
initialDelaySeconds: 30
periodSeconds: 10
successThreshold: 1
tcpSocket:
port: 80
timeoutSeconds: 2
...
完整文件:
---
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: vue-project-new
name: vue-project-new
namespace: kubernetes
spec:
ports:
- name: web
port: 80
protocol: TCP
targetPort: 80
selector:
app: vue-project-new
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
creationTimestamp: null
name: vue-project-new
namespace: kubernetes
spec:
rules:
- host: vue-project-new.test.com
http:
paths:
- backend:
service:
name: vue-project-new
port:
number: 80
path: /
pathType: ImplementationSpecific
---
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: vue-project-new
name: vue-project-new
namespace: kubernetes
spec:
replicas: 1
selector:
matchLabels:
app: vue-project-new
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: vue-project-new
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- vue-project-new
topologyKey: kubernetes.io/hostname
weight: 100
containers:
- env:
- name: TZ
value: Asia/Shanghai
- name: LANG
value: C.UTF-8
image: nginx
imagePullPolicy: IfNotPresent
lifecycle: {}
livenessProbe:
failureThreshold: 2
initialDelaySeconds: 30
periodSeconds: 10
successThreshold: 1
tcpSocket:
port: 80
timeoutSeconds: 2
name: vue-project-new
ports:
- containerPort: 80
name: web
protocol: TCP
readinessProbe:
failureThreshold: 2
initialDelaySeconds: 30
periodSeconds: 10
successThreshold: 1
tcpSocket:
port: 80
timeoutSeconds: 2
resources:
limits:
cpu: 994m
memory: 1170Mi
requests:
cpu: 10m
memory: 55Mi
dnsPolicy: ClusterFirst
imagePullSecrets:
- name: harborkey
restartPolicy: Always
securityContext: {}
serviceAccountName: default
创建该资源:
# kubectl create -f vue-project-new.yaml
service/vue-project-new created
ingress.networking.k8s.io/vue-project-new created
deployment.apps/vue-project-new created
4.创建Jenkins Job
创建Jenkins Job和之前并无太大区别。首先创建Pipeline类型的Job,名称为vue-project-new:
![](https://img.haomeiwen.com/i20896689/dfbc39de55187621.png)
同样在Pipeline栏,写上地址、Key和分支:
![](https://img.haomeiwen.com/i20896689/eea817fe5adc5f75.png)
同样的,第一次构建也会失败,第二次可以选择分支构建:
![](https://img.haomeiwen.com/i20896689/2fdf38768589e670.png)
当出现SUCCESS时,表示构建结束:
![](https://img.haomeiwen.com/i20896689/f97343cb018614ae.png)
之后可以在浏览器访问该域名:
![](https://img.haomeiwen.com/i20896689/239e1511f2dae0d1.png)
(三) 自动化构建Golang项目
上述演示了Java和前端应用的自动化,接下来演示一下对于Golang的自动化构建,本次示例的代码地址:https://gitee.com/dukuan/go-project.git。
1.定义Jenkinsfile
本次示例的Jenkinsfile和之前的也无太大区别,需要的更改的位置是构建容器的镜像、缓存目录、Git地址和项目名称:
pipeline {
agent {
kubernetes {
...
# 构建镜像改为golang,需要根据实际情况更改版本
image: "registry.cn-beijing.aliyuncs.com/citools/golang:1.15"
...
# 缓存目录为/go/pkg/,执行go build时下载的依赖包会缓存在该目录
- mountPath: "/go/pkg/"
name: "cachedir"
readOnly: false
... # 缓存目录保存的位置
- name: "cachedir"
hostPath:
path: "/opt/gopkg"
'''
}
}
...
# 构建命令
stage('Building') {
steps {
container(name: 'build') {
sh """
export GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
go build
"""
}
}
}
...
完整文件:
pipeline {
agent {
kubernetes {
cloud 'kubernetes-study'
slaveConnectTimeout 1200
workspaceVolume hostPathWorkspaceVolume(hostPath: "/opt/workspace", readOnly: false)
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- args: [\'$(JENKINS_SECRET)\', \'$(JENKINS_NAME)\']
image: 'registry.cn-beijing.aliyuncs.com/citools/jnlp:alpine'
name: jnlp
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: "/etc/localtime"
name: "localtime"
readOnly: false
- command:
- "cat"
env:
- name: "LANGUAGE"
value: "en_US:en"
- name: "LC_ALL"
value: "en_US.UTF-8"
- name: "LANG"
value: "en_US.UTF-8"
image: "registry.cn-beijing.aliyuncs.com/citools/golang:1.15"
imagePullPolicy: "IfNotPresent"
name: "build"
tty: true
volumeMounts:
- mountPath: "/etc/localtime"
name: "localtime"
- mountPath: "/go/pkg/"
name: "cachedir"
readOnly: false
- command:
- "cat"
env:
- name: "LANGUAGE"
value: "en_US:en"
- name: "LC_ALL"
value: "en_US.UTF-8"
- name: "LANG"
value: "en_US.UTF-8"
image: "registry.cn-beijing.aliyuncs.com/citools/kubectl:self-1.17"
imagePullPolicy: "IfNotPresent"
name: "kubectl"
tty: true
volumeMounts:
- mountPath: "/etc/localtime"
name: "localtime"
readOnly: false
- command:
- "cat"
env:
- name: "LANGUAGE"
value: "en_US:en"
- name: "LC_ALL"
value: "en_US.UTF-8"
- name: "LANG"
value: "en_US.UTF-8"
image: "registry.cn-beijing.aliyuncs.com/citools/docker:19.03.9-git"
imagePullPolicy: "IfNotPresent"
name: "docker"
tty: true
volumeMounts:
- mountPath: "/etc/localtime"
name: "localtime"
readOnly: false
- mountPath: "/var/run/docker.sock"
name: "dockersock"
readOnly: false
restartPolicy: "Never"
nodeSelector:
build: "true"
securityContext: {}
volumes:
- hostPath:
path: "/var/run/docker.sock"
name: "dockersock"
- hostPath:
path: "/usr/share/zoneinfo/Asia/Shanghai"
name: "localtime"
- name: "cachedir"
hostPath:
path: "/opt/gopkg"
'''
}
}
stages {
stage('Pulling Code') {
parallel {
stage('Pulling Code by Jenkins') {
when {
expression {
env.gitlabBranch == null
}
}
steps {
git(changelog: true, poll: true, url: 'git@10.103.236.251:kubernetes/go-project.git', branch: "${BRANCH}", credentialsId: 'gitlab-key')
script {
COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()
TAG = BUILD_TAG + '-' + COMMIT_ID
println "Current branch is ${BRANCH}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"
}
}
}
stage('Pulling Code by trigger') {
when {
expression {
env.gitlabBranch != null
}
}
steps {
git(url: 'git@10.103.236.251:kubernetes/go-project.git', branch: env.gitlabBranch, changelog: true, poll: true, credentialsId: 'gitlab-key')
script {
COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()
TAG = BUILD_TAG + '-' + COMMIT_ID
println "Current branch is ${env.gitlabBranch}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"
}
}
}
}
}
stage('Building') {
steps {
container(name: 'build') {
sh """
export GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
go build
"""
}
}
}
stage('Docker build for creating image') {
environment {
HARBOR_USER = credentials('HARBOR_ACCOUNT')
}
steps {
container(name: 'docker') {
sh """
pwd
ls
echo ${HARBOR_USER_USR} ${HARBOR_USER_PSW} ${TAG}
docker build -t ${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} .
docker login -u ${HARBOR_USER_USR} -p ${HARBOR_USER_PSW} ${HARBOR_ADDRESS}
docker push ${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG}
"""
}
}
}
stage('Deploying to K8s') {
environment {
MY_KUBECONFIG = credentials('study-k8s-kubeconfig')
}
steps {
container(name: 'kubectl'){
sh """
/usr/local/bin/kubectl --kubeconfig $MY_KUBECONFIG set image deploy -l app=${IMAGE_NAME} ${IMAGE_NAME}=${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} -n $NAMESPACE
"""
}
}
}
}
environment {
COMMIT_ID = ""
HARBOR_ADDRESS = "10.103.236.203"
REGISTRY_DIR = "kubernetes"
IMAGE_NAME = "go-project"
NAMESPACE = "kubernetes"
TAG = ""
}
parameters {
gitParameter(branch: '', branchFilter: 'origin/(.*)', defaultValue: '', description: 'Branch for build and deploy', name: 'BRANCH', quickFilterEnabled: false, selectedValue: 'NONE', sortMode: 'NONE', tagFilter: '*', type: 'PT_BRANCH')
}
}
2.定义Dockerfile
和之前不一样的地方是,Golang编译后生成的是一个二进制文件,可以直接执行,所以底层镜像设置为alpine或者其它的小镜像即可:
FROM registry.cn-beijing.aliyuncs.com/dotbalo/alpine-glibc:alpine-3.9
COPY conf/ ./conf # 如果定义了单独的配置文件,可能需要拷贝到镜像中
COPY ./go-project ./ # 包名按照实际情况进行修改
ENTRYPOINT [ "./go-project"] # 启动该应用
完整文件:
FROM registry.cn-beijing.aliyuncs.com/dotbalo/alpine-glibc:alpine-3.9
# 如果定义了单独的配置文件,可能需要拷贝到镜像中
COPY conf/ ./conf
# 包名按照实际情况进行修改
COPY ./go-project ./
ENTRYPOINT [ "./go-project"] # 启动该应用
3.定义Kubernetes资源
---
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: go-project
name: go-project
namespace: kubernetes
spec:
ports:
- name: web
port: 8080
protocol: TCP
targetPort: 8080
selector:
app: go-project
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
creationTimestamp: null
name: go-project
namespace: kubernetes
spec:
rules:
- host: go-project.test.com
http:
paths:
- backend:
service:
name: go-project
port:
number: 8080
path: /
pathType: ImplementationSpecific
---
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: go-project
name: go-project
namespace: kubernetes
spec:
replicas: 1
selector:
matchLabels:
app: go-project
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: go-project
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- go-project
topologyKey: kubernetes.io/hostname
weight: 100
containers:
- env:
- name: TZ
value: Asia/Shanghai
- name: LANG
value: C.UTF-8
image: nginx
imagePullPolicy: IfNotPresent
lifecycle: {}
livenessProbe:
failureThreshold: 2
initialDelaySeconds: 30
periodSeconds: 10
successThreshold: 1
tcpSocket:
port: 8080
timeoutSeconds: 2
name: go-project
ports:
- containerPort: 8080
name: web
protocol: TCP
readinessProbe:
failureThreshold: 2
initialDelaySeconds: 30
periodSeconds: 10
successThreshold: 1
tcpSocket:
port: 8080
timeoutSeconds: 2
resources:
limits:
cpu: 994m
memory: 1170Mi
requests:
cpu: 10m
memory: 55Mi
dnsPolicy: ClusterFirst
imagePullSecrets:
- name: harborkey
restartPolicy: Always
securityContext: {}
serviceAccountName: default
4.创建Jenkins Job
直接创建即可:
# kubectl create -f go-project.yaml
service/go-project created
ingress.networking.k8s.io/go-project created
deployment.apps/go-project created
创建Jenkins Job和上述小节也并无区别,在此不再重复演示。创建完成后,可以自行构建,看到如下信息表示创建成功:
![](https://img.haomeiwen.com/i20896689/e139fe62833096c2.png)
之后即可访问该应用(如果是后端项目可以无需创建Ingress暴露服务):
![](https://img.haomeiwen.com/i20896689/0bc46aa0897f12ce.png)
之后在创建Pod的节点,可以看到缓存目录:
[root@k8s-node01 gopkg]# cd /opt/gopkg/
[root@k8s-node01 gopkg]# ls
mod sumdb
(四) 自动触发构建
之前的构建都是采用手动选择分支进行构建的,实际使用时,项目可能有很多,如果都是手动触发可能比较消耗人力。所以推荐可以按需配置自动触发,即提交代码后自动触发Jenkins进行构建任务。
本次用Java项目进行演示。首先找到Java项目的Job,点击Configure:
![](https://img.haomeiwen.com/i20896689/a6437174881e7a2b.png)
之后选择Build Triggers,勾选Build when a change…,记录webhook URL:
![](https://img.haomeiwen.com/i20896689/19a5ab44ad3bc8ad.png)
之后点击Advanced:
![](https://img.haomeiwen.com/i20896689/db7bdef1cf75f87e.png)
选择Allow all branches,如果不想任何分支都可以触发该流水线,可以选择Filter进行条件匹配。之后点击Generate生成Secret token,最后点击Save即可。
接下来配置GitLab,首先点击Menu--->Admin:
![](https://img.haomeiwen.com/i20896689/1c379f1943df677d.png)
选择Settings--->Network,之后允许访问外部的请求:
![](https://img.haomeiwen.com/i20896689/f0e33c1f499dc066.png)
保存后,找到Java项目,点击Settings--->WebHooks:
![](https://img.haomeiwen.com/i20896689/ae752c2ed879c8c4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
在新页面输入Webhook地址和token:
![](https://img.haomeiwen.com/i20896689/f62b824c78717237.png)
确认无误后,点击Add webhook:
![](https://img.haomeiwen.com/i20896689/ee04b59be4560d3a.png)
之后下方会添加一个新的Project Hooks,可以点击Test进行Push测试:
![](https://img.haomeiwen.com/i20896689/7976f2e7242b5b56.png)
点击后,即可在Jenkins页面看到任务被触发:
![](https://img.haomeiwen.com/i20896689/db32465e9c4e55ce.png)
也可以通过Blue Ocean看到是自动触发的stage被执行:
![](https://img.haomeiwen.com/i20896689/2438f491f174118f.png)
以上就是通过GitLab的事件触发Jenkins任务,在实际使用时,此功能非常常用,一般会用于开发、测试等环境,省去了手动构建的过程。而在UAT和生产环境,一般不需要再次构建,而是选择其它环境产生的镜像进行发版,接下来看一下如何进行不构建进行发版。
(五) 一次构建多次部署
创建一个新的Job,名字为go-project-uat,类型Pipeline:
![](https://img.haomeiwen.com/i20896689/871ff8027b066d3f.png)
点击页面的This Project is parameterized(参数化构建):
![](https://img.haomeiwen.com/i20896689/06fd6b7a10a675d7.png)
选择参数类型为Image Tag Parameter(需要安装Image Tag插件),之后定义Name为变量的名称,Iamge Name为Harbor的目录和镜像名称:
![](https://img.haomeiwen.com/i20896689/a6206cb8f85515c6.png)
点击Advance,输入仓库的地址,注意如果配置了证书,需要配置https:
![](https://img.haomeiwen.com/i20896689/ff131eef28e2f525.png)
之后可以先点击Save,然后测试能否获取到进行Tag。保存后点击Build with Parameters:
![](https://img.haomeiwen.com/i20896689/095276df7a3acb7d.png)
之后点击Configure,添加Pipeline脚本:
pipeline {
agent {
kubernetes {
cloud 'kubernetes-study'
slaveConnectTimeout 1200
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
# 只需要配置jnlp和kubectl镜像即可
- args: [\'$(JENKINS_SECRET)\', \'$(JENKINS_NAME)\']
image: 'registry.cn-beijing.aliyuncs.com/citools/jnlp:alpine'
name: jnlp
imagePullPolicy: IfNotPresent
- command:
- "cat"
env:
- name: "LANGUAGE"
value: "en_US:en"
- name: "LC_ALL"
value: "en_US.UTF-8"
- name: "LANG"
value: "en_US.UTF-8"
image: "registry.cn-beijing.aliyuncs.com/citools/kubectl:self-1.17"
imagePullPolicy: "IfNotPresent"
name: "kubectl"
tty: true
restartPolicy: "Never"
'''
}
}
stages {
stage('Deploy') {
environment {
MY_KUBECONFIG = credentials('study-k8s-kubeconfig')
}
steps {
container(name: 'kubectl'){
sh """
echo ${IMAGE_TAG} # 该变量即为前台选择的镜像
kubectl --kubeconfig=${MY_KUBECONFIG} set image deployment -l app=${IMAGE_NAME} ${IMAGE_NAME}=${HARBOR_ADDRESS}/${IMAGE_TAG} -n ${NAMESPACE}
kubectl --kubeconfig=${MY_KUBECONFIG} get po -l app=${IMAGE_NAME} -n ${NAMESPACE} -w
"""
}
}
}
}
environment {
HARBOR_ADDRESS = "HARBOR_ADDRESS"
NAMESPACE = "kubernetes"
IMAGE_NAME = "go-project"
TAG = ""
}
}
保存后,选择一个镜像,点击Build:
![](https://img.haomeiwen.com/i20896689/48d589b017eee109.png)
即可看到是直接将镜像版本更新至Kubernetes,并无构建过程,可以省下很多时间。该流水线也可以选择之前的版本进行回滚操作。