一、动态生成Slave

1.1、简介

之前我们都是在物理机或者虚拟机上部署jenkins,但是这种部署方式会有一些难点,如下:

  • 主 Master 发生单点故障时,整个流程都不可用了
  • 每个 Slave 的配置环境不一样,来完成不同语言的编译打包等操作,但是这些差异化的配置导致管理起来非常不方便,维护起来也是比较费劲
  • 资源分配不均衡,有的 Slave 要运行的 job 出现排队等待,而有的 Slave 处于空闲状态
  • 资源有浪费,每台 Slave 可能是物理机或者虚拟机,当 Slave 处于空闲状态时,也不会完全释放掉资源。

正因为上面的这些种种痛点,我们渴望一种更高效更可靠的方式来完成这个 CI/CD 流程,而 Docker 虚拟化容器技术能很好的解决这个痛点,又特别是在 Kubernetes 集群环境下面能够更好来解决上面的问题,下图是基于 Kubernetes 搭建 Jenkins 集群的简单示意图:
7.1、Jenkins - 图1
从图上可以看到 Jenkins Master 和 Jenkins Slave 以 Pod 形式运行在 Kubernetes 集群的 Node 上,Master 运行在其中一个节点,并且将其配置数据存储到一个 Volume 上去,Slave 运行在各个节点上,并且它不是一直处于运行状态,它会按照需求动态的创建并自动删除。

这种方式的工作流程大致为:当 Jenkins Master 接受到 Build 请求时,会根据配置的 Label 动态创建一个运行在 Pod 中的 Jenkins Slave 并注册到 Master 上,当运行完 Job 后,这个 Slave 会被注销并且这个 Pod 也会自动删除,恢复到最初状态。

这种方式部署给我们带来如下好处:

  • 服务高可用,当 Jenkins Master 出现故障时,Kubernetes 会自动创建一个新的 Jenkins Master 容器,并且将 Volume 分配给新创建的容器,保证数据不丢失,从而达到集群服务高可用。
  • 动态伸缩,合理使用资源,每次运行 Job 时,会自动创建一个 Jenkins Slave,Job 完成后,Slave 自动注销并删除容器,资源自动释放,而且 Kubernetes 会根据每个资源的使用情况,动态分配 Slave 到空闲的节点上创建,降低出现因某节点资源利用率高,还排队等待在该节点的情况。
  • 扩展性好,当 Kubernetes 集群的资源严重不足而导致 Job 排队等待时,可以很容易的添加一个 Kubernetes Node 到集群中,从而实现扩展。

1.2、部署

1、创建PV、PVC,为Jenkins提供数据持久化:

  1. ---
  2. apiVersion: v1
  3. kind: PersistentVolume
  4. metadata:
  5. name: jenkins-pv
  6. spec:
  7. capacity:
  8. storage: 5Gi
  9. accessModes:
  10. - ReadWriteMany
  11. persistentVolumeReclaimPolicy: Delete
  12. nfs:
  13. server: 172.16.1.128
  14. path: /data/k8s/jenkins
  15. ---
  16. apiVersion: v1
  17. kind: PersistentVolumeClaim
  18. metadata:
  19. name: jenkins-pvc
  20. namespace: devops
  21. spec:
  22. accessModes:
  23. - ReadWriteMany
  24. resources:
  25. requests:
  26. storage: 5Gi

2、创建角色授权

  1. apiVersion: v1
  2. kind: ServiceAccount
  3. metadata:
  4. name: jenkins-sa
  5. namespace: devops
  6. ---
  7. apiVersion: rbac.authorization.k8s.io/v1beta1
  8. kind: ClusterRole
  9. metadata:
  10. name: jenkins-cr
  11. rules:
  12. - apiGroups: ["extensions", "apps"]
  13. resources: ["deployments"]
  14. verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
  15. - apiGroups: [""]
  16. resources: ["services"]
  17. verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
  18. - apiGroups: [""]
  19. resources: ["pods"]
  20. verbs: ["create","delete","get","list","patch","update","watch"]
  21. - apiGroups: [""]
  22. resources: ["pods/exec"]
  23. verbs: ["create","delete","get","list","patch","update","watch"]
  24. - apiGroups: [""]
  25. resources: ["pods/log"]
  26. verbs: ["get","list","watch"]
  27. - apiGroups: [""]
  28. resources: ["secrets"]
  29. verbs: ["get"]
  30. ---
  31. apiVersion: rbac.authorization.k8s.io/v1beta1
  32. kind: ClusterRoleBinding
  33. metadata:
  34. name: jenkins-crd
  35. roleRef:
  36. kind: ClusterRole
  37. name: jenkins-cr
  38. apiGroup: rbac.authorization.k8s.io
  39. subjects:
  40. - kind: ServiceAccount
  41. name: jenkins-sa
  42. namespace: devops

1、在Kubernetes中部署Jenkins,新建Deployment,jenkins-deploy.yaml

  1. ---
  2. apiVersion: apps/v1
  3. kind: Deployment
  4. metadata:
  5. name: jenkins
  6. namespace: devops
  7. spec:
  8. selector:
  9. matchLabels:
  10. app: jenkins
  11. replicas: 1
  12. template:
  13. metadata:
  14. labels:
  15. app: jenkins
  16. spec:
  17. terminationGracePeriodSeconds: 10
  18. serviceAccount: jenkins-sa
  19. containers:
  20. - name: jenkins
  21. image: jenkins/jenkins:lts
  22. imagePullPolicy: IfNotPresent
  23. env:
  24. - name: JAVA_OPTS
  25. value: -XshowSettings:vm -Dhudson.slaves.NodeProvisioner.initialDelay=0 -Dhudson.slaves.NodeProvisioner.MARGIN=50 -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85 -Duser.timezone=Asia/Shanghai
  26. ports:
  27. - containerPort: 8080
  28. name: web
  29. protocol: TCP
  30. - containerPort: 50000
  31. name: agent
  32. protocol: TCP
  33. resources:
  34. limits:
  35. cpu: 1000m
  36. memory: 1Gi
  37. requests:
  38. cpu: 500m
  39. memory: 512Mi
  40. livenessProbe:
  41. httpGet:
  42. path: /login
  43. port: 8080
  44. initialDelaySeconds: 60
  45. timeoutSeconds: 5
  46. failureThreshold: 12
  47. readinessProbe:
  48. httpGet:
  49. path: /login
  50. port: 8080
  51. initialDelaySeconds: 60
  52. timeoutSeconds: 5
  53. failureThreshold: 12
  54. volumeMounts:
  55. - name: jenkinshome
  56. mountPath: /var/jenkins_home
  57. securityContext:
  58. fsGroup: 1000
  59. volumes:
  60. - name: jenkinshome
  61. persistentVolumeClaim:
  62. claimName: jenkins-pvc
  63. ---
  64. apiVersion: v1
  65. kind: Service
  66. metadata:
  67. name: jenkins
  68. namespace: devops
  69. labels:
  70. app: jenkins
  71. spec:
  72. selector:
  73. app: jenkins
  74. type: NodePort
  75. ports:
  76. - name: web
  77. port: 8080
  78. targetPort: web
  79. nodePort: 30002
  80. - name: agent
  81. port: 50000
  82. targetPort: agent

5、创建上面的资源清单

  1. # kubectl apply -f jenkins-rbac.yaml
  2. # kubectl apply -f jenkins-pvc.yaml
  3. # kubectl apply -f jenkins-deploy.yaml

启动如果报如下错误(因为我们容器里是以jenkins用户启动,而我们NFS服务器上是root启动,所以没有权限):

  1. [root@master manifests]# kubectl logs jenkins-688c6cd5fd-lj6zg -n devops
  2. touch: cannot touch '/var/jenkins_home/copy_reference_file.log': Permission denied
  3. Can not write to /var/jenkins_home/copy_reference_file.log. Wrong volume permissions?

然后给我们NFS服务器上的目录授权即可:

  1. # chown -R 1000 /data/k8s/jenkins/jenkins

然后登录网站,因为我们Service是采用NodePort类型,其端口为30002,我们直接在浏览器用这个端口访问:
image.png
密码可以通过如下命令获得:

  1. # cat /data/k8s/jenkins/secrets/initialAdminPassword
  2. 12b503a274354e09a465b4f76664db70

然后安装插件到安装完成。

1.3、配置

1、安装插件kubernetes
image.png

2、填写Kubernetes和Jenkins的配置信息
配置管理->系统配置->新增cloud。
image.png
image.png
image.png
按照图中红色框中填写,其中Kubernetes命名空间填写我们Jenkins所在的命名空间。
备注:
如果连接测试失败,很可能是权限问题,我们就需要把ServiceAccount的凭证jenkins-sa添加进来。

3、配置Pod模板
image.png
另外需要挂载两个主机目录:

  1. /var/run/docker.sock:该文件是用于 Pod 中的容器能够共享宿主机的 Docker;
  2. /root/.kube:这个目录挂载到容器的/root/.kube目录下面这是为了让我们能够在 Pod 的容器中能够使用 kubectl 工具来访问我们的 Kubernetes 集群,方便我们后面在 Slave Pod 部署 Kubernetes 应用;

image.png
避免一些权限不足,需要配置ServiceAccount
image.png

1.4、测试

1、创建一个项目
image.png

2、在标签位置填写我们前面模板中定义的Label
image.png

3、直接在构建处执行shell进行测试
image.png

然后点击构建,在终端可以看到整个过程:

  1. [root@master manifests]# kubectl get pod -n devops -w
  2. NAME READY STATUS RESTARTS AGE
  3. jenkins-6595ddd5d-m5fvd 1/1 Running 0 144m
  4. jenkins-slave-kkc2b 0/1 Pending 0 0s
  5. jenkins-slave-kkc2b 0/1 Pending 0 0s
  6. jenkins-slave-kkc2b 0/1 ContainerCreating 0 0s
  7. jenkins-slave-kkc2b 1/1 Running 0 3s
  8. jenkins-slave-kkc2b 1/1 Terminating 0 31s
  9. jenkins-slave-kkc2b 1/1 Terminating 0 31s

也可以在jenkins里看日志如下:
image.png

二、Pipeline

2.1、简介

Pipeline,简单来说,就是一套运行在 Jenkins 上的工作流框架,将原来独立运行于单个或者多个节点的任务连接起来,实现单个任务难以完成的复杂流程编排可视化的工作。
Jenkins Pipeline 有几个核心概念:

  • Node:节点,一个 Node 就是一个 Jenkins 节点,Master 或者 Agent,是执行 Step 的具体运行环境,比如我们之前动态运行的 Jenkins Slave 就是一个 Node 节点
  • Stage:阶段,一个 Pipeline 可以划分为若干个 Stage,每个 Stage 代表一组操作,比如:Build、Test、Deploy,Stage 是一个逻辑分组的概念,可以跨多个 Node
  • Step:步骤,Step 是最基本的操作单元,可以是打印一句话,也可以是构建一个 Docker 镜像,由各类 Jenkins 插件提供,比如命令:sh ‘make’,就相当于我们平时 shell 终端中执行 make 命令一样。

Pipeline的使用:

  • Pipeline 脚本是由 Groovy 语言实现的
  • Pipeline 支持两种语法:Declarative(声明式)和 Scripted Pipeline(脚本式)语法
  • Pipeline 也有两种创建方法:可以直接在 Jenkins 的 Web UI 界面中输入脚本;也可以通过创建一个 Jenkinsfile 脚本文件放入项目源码库中
  • 一般我们都推荐在 Jenkins 中直接从源代码控制(SCMD)中直接载入 Jenkinsfile Pipeline 这种方法

2.2、创建

2.2.1、简单的Pipeline

直接 在Jenkins的WEB UI上输入脚本。
image.png
image.png
脚本内容:

  1. node {
  2. stage('Clone') {
  3. echo "1.Clone Stage"
  4. }
  5. stage('Test') {
  6. echo "2.Test Stage"
  7. }
  8. stage('Build') {
  9. echo "3.Build Stage"
  10. }
  11. stage('Deploy') {
  12. echo "4. Deploy Stage"
  13. }
  14. }

然后保存—> 点击构建—> 观察日志
image.png
输出符合我们脚本内容。

2.2.2、在slave中运行Pipeline

上面对Jenkins的Pipeline做了简单的测试,但是其并未在我们的Slave中运行,如果要在Slave中运行,其就要使用我们前面添加的Label,如下:

  1. node('joker-jnlp') {
  2. stage('Clone') {
  3. echo "1.Clone Stage"
  4. }
  5. stage('Test') {
  6. echo "2.Test Stage"
  7. }
  8. stage('Build') {
  9. echo "3.Build Stage"
  10. }
  11. stage('Deploy') {
  12. echo "4. Deploy Stage"
  13. }
  14. }

然后我们保存并点击构建,观察Pod的变化:

  1. [root@master ~]# kubectl get pod -n devops -w
  2. NAME READY STATUS RESTARTS AGE
  3. jenkins-6595ddd5d-m5fvd 1/1 Running 0 2d23h
  4. jenkins-slave-vq8wf 0/1 Pending 0 0s
  5. jenkins-slave-vq8wf 0/1 Pending 0 0s
  6. jenkins-slave-vq8wf 0/1 ContainerCreating 0 0s
  7. jenkins-slave-vq8wf 1/1 Running 0 2s
  8. jenkins-slave-vq8wf 1/1 Terminating 0 27s
  9. jenkins-slave-vq8wf 1/1 Terminating 0 27s

我们可以看到其依据我们定义的模板动态生成了jenkins-slave的Pod,我们在Jenkins的日志中查看:
image.png
可以看到两个的名字是一样的。

2.2.3、部署完整应用

部署应用的流程如下:

  • 编写代码
  • 测试
  • 编写 Dockerfile
  • 构建打包 Docker 镜像
  • 推送 Docker 镜像到仓库
  • 编写 Kubernetes YAML 文件
  • 更改 YAML 文件中 Docker 镜像 TAG
  • 利用 kubectl 工具部署应用

所以基本的Pipeline脚本框架应该如下:

  1. node('joker-jnlp') {
  2. stage('Clone') {
  3. echo "1.Clone Stage"
  4. }
  5. stage('Test') {
  6. echo "2.Test Stage"
  7. }
  8. stage('Build') {
  9. echo "3.Build Docker Image Stage"
  10. }
  11. stage('Push') {
  12. echo "4.Push Docker Image Stage"
  13. }
  14. stage('YAML') {
  15. echo "5. Change YAML File Stage"
  16. }
  17. stage('Deploy') {
  18. echo "6. Deploy Stage"
  19. }
  20. }

第一步:克隆代码

  1. stage('Clone') {
  2. echo "1.Clone Stage"
  3. git url: "https://github.com/baidjay/jenkins-demo.git"
  4. script {
  5. build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
  6. }
  7. echo "${build_tag}"
  8. }

我们这里采用和git commit的记录为镜像的 tag,这里有一个好处就是镜像的 tag 可以和 git 提交记录对应起来,也方便日后对应查看。但是由于这个 tag 不只是我们这一个 stage 需要使用,下一个推送镜像是不是也需要,所以这里我们把这个 tag 编写成一个公共的参数,把它放在 Clone 这个 stage 中。

第二步:测试

  1. stage('Test') {
  2. echo "2.Test Stage"
  3. }

测试可以是单测,也可以是工具,我们这里就简单存在这个步骤。

第三步:构建镜像

  1. stage('Build') {
  2. echo "3.Build Docker Image Stage"
  3. sh "docker build -t registry.cn-hangzhou.aliyuncs.com/ik9s/jenkins-demo:${build_tag} ."
  4. }

这一步我们就使用到上面定义的build_tag变量。

第四步:推送镜像

  1. stage('Push') {
  2. echo "4.Push Docker Image Stage"
  3. sh "docker login --username=www.565361785@qq.com registry.cn-hangzhou.aliyuncs.com -p xxxxx"
  4. sh "docker push registry.cn-hangzhou.aliyuncs.com/ik9s/jenkins-demo:${build_tag}"
  5. }

配置Jenkins,隐藏用户名密码信息:
image.png
其中ID:AliRegistry 是我们后面要用的值。
这样我们上面的脚本就可以定义如下:

  1. stage('Push') {
  2. echo "4.Push Docker Image Stage"
  3. withCredentials([usernamePassword(credentialsId: 'AliRegistry', passwordVariable: 'AliRegistryPassword', usernameVariable: 'AliRegistryUser')]) {
  4. sh "docker login -u ${AliRegistryUser} registry.cn-hangzhou.aliyuncs.com -p ${AliRegistryPassword}"
  5. sh "docker push registry.cn-hangzhou.aliyuncs.com/ik9s/jenkins-demo:${build_tag}"
  6. }
  7. }

注意我们这里在 stage 中使用了一个新的函数withCredentials,其中有一个 credentialsId 值就是我们刚刚创建的 ID 值,而对应的用户名变量就是 ID 值加上 User,密码变量就是 ID 值加上 Password,然后我们就可以在脚本中直接使用这里两个变量值来直接替换掉之前的登录 docker hub 的用户名和密码,现在是不是就很安全了,我只是传递进去了两个变量而已,别人并不知道我的真正用户名和密码,只有我们自己的 Jenkins 平台上添加的才知道。

第五步:更改YAML文件

  1. stage('YAML') {
  2. echo "5. Change YAML File Stage"
  3. sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s.yaml"
  4. sh "sed -i 's/<BRANCH_NAME>/${env.BRANCH_NAME}/' k8s.yaml"
  5. }

其YAML文件为(YAML文件放在项目根目录):

  1. apiVersion: extensions/v1beta1
  2. kind: Deployment
  3. metadata:
  4. name: jenkins-demo
  5. spec:
  6. template:
  7. metadata:
  8. labels:
  9. app: jenkins-demo
  10. spec:
  11. containers:
  12. - image: registry.cn-hangzhou.aliyuncs.com/ik9s/jenkins-demo:<BUILD_TAG>
  13. imagePullPolicy: IfNotPresent
  14. name: jenkins-demo
  15. env:
  16. - name: branch
  17. value: <BRANCH_NAME>

第六步:部署
部署阶段我们增加人工干预,可能需要将该版本先发布到测试环境、QA 环境、或者预览环境之类的,总之直接就发布到线上环境去还是挺少见的,所以我们需要增加人工确认的环节,一般都是在 CD 的环节才需要人工干预,比如我们这里的最后两步,我们就可以在前面加上确认,比如:
我们将YAML这一步改为:

  1. stage('YAML') {
  2. echo "5. Change YAML File Stage"
  3. def userInput = input(
  4. id: 'userInput',
  5. message: 'Choose a deploy environment',
  6. parameters: [
  7. [
  8. $class: 'ChoiceParameterDefinition',
  9. choices: "Dev\nQA\nProd",
  10. name: 'Env'
  11. ]
  12. ]
  13. )
  14. echo "This is a deploy step to ${userInput.Env}"
  15. sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s.yaml"
  16. sh "sed -i 's/<BRANCH_NAME>/${env.BRANCH_NAME}/' k8s.yaml"
  17. sh "sed -i 's#cnych/jenkins-demo#registry.cn-hangzhou.aliyuncs.com/ik9s/jenkins-demo#' k8s.yaml"
  18. }

然后再部署阶段:

  1. stage('Deploy') {
  2. echo "6. Deploy Stage"
  3. if (userInput.Env == "Dev") {
  4. // deploy dev stuff
  5. } else if (userInput.Env == "QA"){
  6. // deploy qa stuff
  7. } else {
  8. // deploy prod stuff
  9. }
  10. sh "kubectl apply -f k8s.yaml"
  11. }

由于这一步也属于部署的范畴,所以我们可以将最后两步都合并成一步,我们最终的Pipeline脚本如下:

  1. node('joker-jnlp') {
  2. stage('Clone') {
  3. echo "1.Clone Stage"
  4. git url: "https://github.com/baidjay/jenkins-demo.git"
  5. script {
  6. build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
  7. }
  8. echo "${build_tag}"
  9. }
  10. stage('Test') {
  11. echo "2.Test Stage"
  12. }
  13. stage('Build') {
  14. echo "3.Build Docker Image Stage"
  15. sh "docker build -t registry.cn-hangzhou.aliyuncs.com/ik9s/jenkins-demo:${build_tag} ."
  16. }
  17. stage('Push') {
  18. echo "4.Push Docker Image Stage"
  19. withCredentials([usernamePassword(credentialsId: 'AliRegistry', passwordVariable: 'AliRegistryPassword', usernameVariable: 'AliRegistryUser')]) {
  20. sh "docker login -u ${AliRegistryUser} registry.cn-hangzhou.aliyuncs.com -p ${AliRegistryPassword}"
  21. sh "docker push registry.cn-hangzhou.aliyuncs.com/ik9s/jenkins-demo:${build_tag}"
  22. }
  23. }
  24. stage('Deploy') {
  25. echo "5. Change YAML File Stage"
  26. def userInput = input(
  27. id: 'userInput',
  28. message: 'Choose a deploy environment',
  29. parameters: [
  30. [
  31. $class: 'ChoiceParameterDefinition',
  32. choices: "Dev\nQA\nProd",
  33. name: 'Env'
  34. ]
  35. ]
  36. )
  37. echo "This is a deploy step to ${userInput}"
  38. sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s.yaml"
  39. sh "sed -i 's/<BRANCH_NAME>/${env.BRANCH_NAME}/' k8s.yaml"
  40. echo "6. Deploy Stage"
  41. if (userInput == "Dev") {
  42. // deploy dev stuff
  43. } else if (userInput == "QA"){
  44. // deploy qa stuff
  45. } else {
  46. // deploy prod stuff
  47. }
  48. sh "kubectl apply -f k8s.yaml -n default"
  49. }
  50. }

然后构建面板如下:
image.png

然后查看Pod日志如下:

  1. [root@master jenkins]# kubectl logs jenkins-demo-789fdc6878-5pzbx
  2. Hello, KubernetesI'm from Jenkins CI!

2.2.4、Jenkinsfile

万里长征,貌似我们的任务完成了,其实不然,我们这里只是完成了一次手动的添加任务的构建过程,在实际的工作实践中,我们更多的是将 Pipeline 脚本写入到 Jenkinsfile 文件中,然后和代码一起提交到代码仓库中进行版本管理。现在我们将上面的 Pipeline 脚本拷贝到一个 Jenkinsfile 中,将该文件放入上面的 git 仓库中,但是要注意的是,现在既然我们已经在 git 仓库中了,是不是就不需要 git clone 这一步骤了,所以我们需要将第一步 Clone 操作中的 git clone 这一步去掉。
如下:

  1. node('joker-jnlp') {
  2. stage('Prepare') {
  3. echo "1.Prepare Stage"
  4. script {
  5. build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
  6. }
  7. echo "${build_tag}"
  8. }
  9. stage('Test') {
  10. echo "2.Test Stage"
  11. }
  12. stage('Build') {
  13. echo "3.Build Docker Image Stage"
  14. sh "docker build -t registry.cn-hangzhou.aliyuncs.com/ik9s/jenkins-demo:${build_tag} ."
  15. }
  16. stage('Push') {
  17. echo "4.Push Docker Image Stage"
  18. withCredentials([usernamePassword(credentialsId: 'AliRegistry', passwordVariable: 'AliRegistryPassword', usernameVariable: 'AliRegistryUser')]) {
  19. sh "docker login -u ${AliRegistryUser} registry.cn-hangzhou.aliyuncs.com -p ${AliRegistryPassword}"
  20. sh "docker push registry.cn-hangzhou.aliyuncs.com/ik9s/jenkins-demo:${build_tag}"
  21. }
  22. }
  23. stage('Deploy') {
  24. echo "5. Change YAML File Stage"
  25. def userInput = input(
  26. id: 'userInput',
  27. message: 'Choose a deploy environment',
  28. parameters: [
  29. [
  30. $class: 'ChoiceParameterDefinition',
  31. choices: "Dev\nQA\nProd",
  32. name: 'Env'
  33. ]
  34. ]
  35. )
  36. echo "This is a deploy step to ${userInput}"
  37. sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s.yaml"
  38. sh "sed -i 's/<BRANCH_NAME>/${env.BRANCH_NAME}/' k8s.yaml"
  39. echo "6. Deploy Stage"
  40. if (userInput == "Dev") {
  41. // deploy dev stuff
  42. } else if (userInput == "QA"){
  43. // deploy qa stuff
  44. } else {
  45. // deploy prod stuff
  46. }
  47. sh "kubectl apply -f k8s.yaml -n default"
  48. }
  49. }

然后我们更改上面的 jenkins-demo 这个任务,点击 Configure -> 最下方的 Pipeline 区域 -> 将之前的 Pipeline Script 更改成 Pipeline Script from SCM,然后根据我们的实际情况填写上对应的仓库配置,要注意 Jenkinsfile 脚本路径。
image.png

在实际的项目中,往往一个代码仓库都会有很多分支的,比如开发、测试、线上这些分支都是分开的,一般情况下开发或者测试的分支我们希望提交代码后就直接进行 CI/CD 操作,而线上的话最好增加一个人工干预的步骤,这就需要 Jenkins 对代码仓库有多分支的支持,当然这个特性是被 Jenkins 支持的。
然后新建一个 Jenkinsfile 文件,配置如下:

  1. node('joker-jnlp') {
  2. stage('Prepare') {
  3. echo "1.Prepare Stage"
  4. checkout scm
  5. script {
  6. build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
  7. if (env.BRANCH_NAME != 'master') {
  8. build_tag = "${env.BRANCH_NAME}-${build_tag}"
  9. }
  10. }
  11. }
  12. stage('Test') {
  13. echo "2.Test Stage"
  14. }
  15. stage('Build') {
  16. echo "3.Build Docker Image Stage"
  17. sh "docker build -t registry.cn-hangzhou.aliyuncs.com/ik9s/jenkins-demo:${build_tag} ."
  18. }
  19. stage('Push') {
  20. echo "4.Push Docker Image Stage"
  21. withCredentials([usernamePassword(credentialsId: 'AliRegistry', passwordVariable: 'AliRegistryPassword', usernameVariable: 'AliRegistryUser')]) {
  22. sh "docker login -u ${AliRegistryUser} registry.cn-hangzhou.aliyuncs.com -p ${AliRegistryPassword}"
  23. sh "docker push registry.cn-hangzhou.aliyuncs.com/ik9s/jenkins-demo:${build_tag}"
  24. }
  25. }
  26. stage('Deploy') {
  27. echo "5. Deploy Stage"
  28. if (env.BRANCH_NAME == 'master') {
  29. input "确认要部署线上环境吗?"
  30. }
  31. sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s.yaml"
  32. sh "sed -i 's/<BRANCH_NAME>/${env.BRANCH_NAME}/' k8s.yaml"
  33. sh "kubectl apply -f k8s.yaml --record"
  34. }
  35. }

在第一步中我们增加了checkout scm命令,用来检出代码仓库中当前分支的代码,为了避免各个环境的镜像 tag 产生冲突,我们为非 master 分支的代码构建的镜像增加了一个分支的前缀,在第五步中如果是 master 分支的话我们才增加一个确认部署的流程,其他分支都自动部署,并且还需要替换 k8s.yaml 文件中的环境变量的值。

三、BlueOcean

我们这里使用 BlueOcean 这种方式来完成此处 CI/CD 的工作,BlueOcean 是 Jenkins 团队从用户体验角度出发,专为 Jenkins Pipeline 重新设计的一套 UI 界面,仍然兼容以前的 fressstyle 类型的 job,BlueOcean 具有以下的一些特性:

  • 连续交付(CD)Pipeline 的复杂可视化,允许快速直观的了解 Pipeline 的状态
  • 可以通过 Pipeline 编辑器直观的创建 Pipeline
  • 需要干预或者出现问题时快速定位,BlueOcean 显示了 Pipeline 需要注意的地方,便于异常处理和提高生产力
  • 用于分支和拉取请求的本地集成可以在 GitHub 或者 Bitbucket 中与其他人进行代码协作时最大限度提高开发人员的生产力。

BlueOcean 可以安装在现有的 Jenkins 环境中,也可以使用 Docker 镜像的方式直接运行,我们这里直接在现有的 Jenkins 环境中安装 BlueOcean 插件:登录 Jenkins Web UI -> 点击左侧的 Manage Jenkins -> Manage Plugins -> Available -> 搜索查找 BlueOcean -> 点击下载安装并重启
7.1、Jenkins - 图21

3.1、创建Pipeline

点击创建:
image.png
image.png

获取Token的步骤:
image.png
image.png
image.png

然后获取Token:
image.png

创建完成如下所示:
image.png