• 在Google Kubernetes集群创建分布式Jenkins(二)


    上一篇博客在Google Kubernetes集群创建分布式Jenkins(一)-CSDN博客我介绍了如何在GCP的K8S集群上部署一个分布式的Jenkins,并实现了一个简单的Pipeline的运行。

    在实际的开发中,我们通常都会按照以下的CICD流程来设置Pipeline

    在我司的实际实践中,通常包括如下的步骤:

    1. 往Git仓库提交开发分支的代码,创建Pull request来进行code review
    2. Git仓库配置hook,当收到PR时触发jenkins任务,拉取代码到Jenkins进行编译和UT检查
    3. Jenkins把UT检查结果回写到PR的comments,如果UT检查通过,自动Approve+1
    4. 当其他人对PR的code review完成,Approve+2,自动或手动合并代码到主分支。
    5. 合并到主分支触发Jenkins的集成测试任务以及镜像构建任务。
    6. 镜像构建任务完成后,把打包的镜像推送到registry
    7. 更新部署的manifest,更新镜像的标签
    8. 把应用部署到GKE

    这里我将按照以上任务来设置相应的pipeline

    Jenkins安装插件

    要完成以上的任务,我们需要安装以下的一些插件

    Generic webhook trigger plugin

    Github plugin

    Credential binding plugin

    Git仓库设置webhook

    我选择GitHub作为示例,在github上建立一个简单的Java Springboot项目,实现一个rest接口,完成一个简单的计算任务,代码仓库在https://github.com/gzroy/webdemo.git。在这个repo的页面上,点击Settings->webhooks,然后选择添加webhook,在Payload URL里面输入jenkins URL,例如我的Jenkins URL是http://123.123.123.123/jenkins/generic-webhook-trigger/invoke?token=abc,ContentType选择application/json,然后点击“Let me select individual events”,选择触发hook的事件。这里我选择Pull request。

    设置CICD项目

    因为我打算以后其他项目都统一用这个CICD的流程,所以新建一个CICD的项目,把所需要的Jenkins pipeline的代码都放到这个项目中。以后其他项目只需要引用这个项目的相关文件即可完成CICD的任务。代码仓库在https://github.com/gzroy/cicd.git

    创建PR自动触发测试的Pipeline

    我们要实现的功能是,当开发者新建一个分支,修改完代码,commit提交并创建PR之后,应该要自动触发CI的pipeline来进行UT测试,并把测试结果写到PR的comments里面,这样其他reviewer可以查看这个测试的结果。

    在CICD项目里面,我新建了一个ut_pipeline.groovy文件,内容如下:

    1. pipeline {
    2. agent{
    3. kubernetes{
    4. yaml '''
    5. apiVersion: v1
    6. kind: Pod
    7. spec:
    8. volumes:
    9. - name: maven-pv-storage
    10. persistentVolumeClaim:
    11. claimName: maven-repo-storage
    12. containers:
    13. - name: maven
    14. image: maven:3.8.3-openjdk-17
    15. tty: true
    16. imagePullPolicy: "IfNotPresent"
    17. volumeMounts:
    18. - mountPath: '/root/.m2/repository'
    19. name: maven-pv-storage
    20. command:
    21. - cat
    22. '''
    23. }
    24. }
    25. triggers {
    26. GenericTrigger(
    27. genericVariables: [
    28. [key: 'action', value: '$.action', expressionType: 'JSONPath'],
    29. [key: 'clone_url', value: '$.pull_request.base.repo.clone_url', expressionType: 'JSONPath'],
    30. [key: 'ref', value: '$.pull_request.head.ref', expressionType: 'JSONPath'],
    31. [key: 'sha', value: '$.pull_request.head.sha', expressionType: 'JSONPath'],
    32. [key: 'number', value: '$.number', expressionType: 'JSONPath'],
    33. [key: 'comments_url', value: '$.pull_request.comments_url', expressionType: 'JSONPath']
    34. ],
    35. token: 'abc'
    36. )
    37. }
    38. environment {
    39. CREDENTIAL = credentials("${CREDENTIAL_ID}")
    40. }
    41. stages{
    42. stage("git checkout") {
    43. when {
    44. expression {
    45. return action=="opened" || action=="synchronize"
    46. }
    47. }
    48. steps {
    49. script {
    50. git(
    51. url: clone_url,
    52. credentialsId: CREDENTIAL_ID,
    53. branch: ref
    54. )
    55. }
    56. }
    57. }
    58. stage("test"){
    59. when {
    60. expression {
    61. return action=="opened" || action=="synchronize"
    62. }
    63. }
    64. steps{
    65. container('maven') {
    66. script{
    67. sh 'mvn test '
    68. }
    69. }
    70. }
    71. post {
    72. failure {
    73. sh """
    74. (curl -L -X POST \
    75. -H \"Accept: application/vnd.github+json\" \
    76. -H \"Authorization: Bearer ${env.CREDENTIAL_PSW}\" \
    77. -H \"X-GitHub-Api-Version: 2022-11-28\" \
    78. ${comments_url} \
    79. -d \'{\"body\": \"UT test failure for commit ${sha}\"}\')
    80. """
    81. }
    82. success {
    83. sh """
    84. (curl -L -X POST \
    85. -H \"Accept: application/vnd.github+json\" \
    86. -H \"Authorization: Bearer ${env.CREDENTIAL_PSW}\" \
    87. -H \"X-GitHub-Api-Version: 2022-11-28\" \
    88. ${comments_url} \
    89. -d \'{\"body\": \"UT test success for commit ${sha}\"}\')
    90. """
    91. }
    92. }
    93. }
    94. }
    95. }

    解释一下这个程序。在Agent模块里面定义了一个运行Jenkins Agent的Pod,其中container除了默认的agent container之外,还包括一个maven container,在test这个stage里面,将运行这个maven container的mvn test命令来执行UT测试。

    在triggers模块中,定义了GenericTrigger从webhook的回调API中获取body的数据,把其中需要的数据赋值到变量,给后续的stage使用。

    在environment模块中,定义了一个名为CREDENTIAL的全局变量,通过credentials helper函数来获取Jenkins保存的凭证,这里的CREDENTIAL_ID是在Jenkins System里面定义的全局变量。注意这里必须要加上双引号,使得Groovy可以预先用Jenkins变量的值来替代这个变量,如果用单引号则不行。

    在Stages模块中,定义了两个Stage,这两个操作都是当PR新建或已有PR上发生新的commit时才执行。第一个Stage执行从Github仓库checkout的操作。第二个Stage是执行maven测试,在Post里面定义了测试成功或失败,需要给PR加上相应的comment。这里都使用了Github API来进行操作,我们需要预先把Github用户的access token保存到Jenkins的credentials中。

    回到Jenkins的控制台,新建一个webdemo_ut的Job,选择Pipeline类型,然后在Github项目里面填入https://github.com/gzroy/webdemo.git,在构建触发器里面选择Generic Webhook Trigger,Token也要填上,这个Token需要和Github配置webhook的token一致。定义Pipeline里面选择Pipeline script from SCM,填入CICD项目的地址和Script文件的路径,Branch to build里面输入*/*,然后保存即可。

    之后我们在https://github.com/gzroy/webdemo.git这个项目上可以执行新建分支,然后改动代码,提交之后创建一个PR,可以看到Jenkins将自动触发一个任务,进行测试后把结果回写到PR的comments中。

    合并到主分支自动触发打包的pipeline

    创建一个package_pipeline.groovy的文件,当收到Webhook回调时,判断如果是Merge的事件,那么将拉取主分支的代码进行package,并进行Docker构建,然后把镜像推送到相应的registry。

    以下是pipeline的代码:

    1. pipeline {
    2. agent{
    3. kubernetes{
    4. yaml '''
    5. apiVersion: v1
    6. kind: Pod
    7. spec:
    8. volumes:
    9. - name: maven-pv-storage
    10. persistentVolumeClaim:
    11. claimName: maven-repo-storage
    12. containers:
    13. - name: maven
    14. image: maven:3.8.3-openjdk-17
    15. tty: true
    16. imagePullPolicy: "IfNotPresent"
    17. volumeMounts:
    18. - mountPath: '/root/.m2/repository'
    19. name: maven-pv-storage
    20. command:
    21. - cat
    22. - name: kaniko
    23. image: gcr.io/kaniko-project/executor:debug
    24. command:
    25. - sleep
    26. args:
    27. - 9999999
    28. serviceAccountName: "jenkins-sa"
    29. '''
    30. }
    31. }
    32. triggers {
    33. GenericTrigger(
    34. genericVariables: [
    35. [key: 'action', value: '$.action', expressionType: 'JSONPath'],
    36. [key: 'clone_url', value: '$.pull_request.base.repo.clone_url', expressionType: 'JSONPath'],
    37. [key: 'ref', value: '$.pull_request.head.ref', expressionType: 'JSONPath'],
    38. [key: 'name', value: '$.pull_request.base.repo.name', expressionType: 'JSONPath']
    39. ],
    40. token: 'abc'
    41. )
    42. }
    43. environment {
    44. CREDENTIAL = credentials("${CREDENTIAL_ID}")
    45. PACKAGE_STATUS = "success"
    46. }
    47. stages{
    48. stage("git checkout") {
    49. when {
    50. expression {
    51. return action=="closed"
    52. }
    53. }
    54. steps {
    55. script {
    56. git(
    57. url: clone_url,
    58. credentialsId: CREDENTIAL_ID,
    59. branch: ref
    60. )
    61. }
    62. }
    63. }
    64. stage("package"){
    65. when {
    66. expression {
    67. return action=="closed"
    68. }
    69. }
    70. steps{
    71. container('maven') {
    72. script{
    73. sh 'mvn clean package'
    74. sh 'mkdir target/extracted'
    75. sh 'java -Djarmode=layertools -jar target/*.jar extract --destination target/extracted'
    76. }
    77. }
    78. }
    79. }
    80. stage("Build image and push to registry") {
    81. when {
    82. expression {
    83. return action=="closed"
    84. }
    85. }
    86. steps {
    87. container('kaniko') {
    88. script {
    89. sh "/kaniko/executor --verbosity debug --context `pwd` --destination asia-southeast1-docker.pkg.dev/PROJECT_ID/REPO_NAME/${name}:0.0.1"
    90. }
    91. }
    92. }
    93. }
    94. }
    95. }

    这个pipeline的Container里面增加了一个Kaniko的镜像,这是一个开源的项目,可以在K8S的环境下来打包镜像。在Stage package里面,除了用maven来创建jar文件,还解压了其分层信息,这样可以帮助我们更好的构建镜像。在最后的stage里面,调用kaniko镜像来进行镜像构建。

    需要注意的是,在agent template里面我们还需要指定这个Pod所使用的service account,这个Service account已经和GCP的service account进行了绑定,并且GCP的service account已经赋予Storage Object Admin的权限,这样就可以用workload identity的方式来访问Google的container registry了。

    新建一个webdemo_package的job,配置和上面类似,只是pipeline script选择package_pipeline.groovy文件。当我们合并PR到主分支之后,就会触发一个pr closed的webhook API调用,可以看到Jenkins将自动触发package任务,生成镜像并推送到registry。

    部署镜像的pipeline

    这个步骤比较简单,就略过了,只要修改一下部署的manifest文件,把镜像的版本改为上一步构建的镜像版本即可。

    总结

    以上就是设置一个自动触发Jenkins任务的CICD流程。在下一篇博客,我将继续介绍一下如何在Jenkins设置terraform pipeline,来部署GCP的相关服务。

  • 相关阅读:
    GEE:绘制土地利用类型面积分布柱状图
    map-reduce中的组件
    浅析 SQL Server 的 CROSS APPLY 和 OUTER APPLY 查询 - 第二部分
    Python数据分析实战-dataframe分组提取每一组的首条记录(附源码和实现效果)
    spring 源码实现
    Type-challehges learning: pick
    在列表末尾追加元素的方法myList.append()
    加密,各种加密,耙梳加密算法(Encryption)种类以及开发场景中的运用(Python3.10)
    微信每天被加上限是多少?
    Swagger API 文档
  • 原文地址:https://blog.csdn.net/gzroy/article/details/134242144