首页 > 技术文章 > jenkins在aws eks中的CI/CD及slave

lizexiong 2021-06-08 17:06 原文

 

  本文档不讲解jenkins的基础用法,主要讲解配合k8s的插件,和pipeline的用途和k8s ci/cd的流程。

  以及部署在k8s集群内和集群外的注意事项。

1.准备工作

  以下在整个CI/CD流程中需要使用到的插件,可能有时候pipeline等插件没安装,这里不做记录。

插件 版本 用途
Kubernetes  1.18.1 1.Kubernetes集群中运行动态代理,简单来说就是使用改插件运行jenkins slave镜像 
Kubernetes Continuous Deploy  2.1.0

1.安装这个插件之后才能部署创建k8s 类型的里连接密钥. 
2.可以正常连接集群后,通过这个插件来将yml deploy至k8s集群集群中.
3.由于eks是aws自行开发的认证插件,所以该插件无法识别eks kubeconfig里面的用户,

然后默认会使用匿名用户去访问,所以eks想使用该插件,那么就要开放匿名用户权限   

Kubernetes Cli Plugin  1.7.0 1.安装这个插件后,可以创建又一种类型的k8s密钥.
2.通过验证后,可以执行k8s的查询命令.
Git Parameter Plug-In  0.9.11 1.可以动态获取git的分支和tag.
Dynamic Parameter/Dynamic choice parameter  0.1.1 1.安装一个 dynamic Parameter,dynamic choice pararmeter会同时安装.
2.dynamic choice parameter 是动态下拉框.
3.该插件在jenkins新版本中由于安全漏洞已下线,需要单独去下载
4.插件列表中有3个版本,只有0.1.1能够使用
jenkins  2.176.2  jenkins

 

2.Pipeline插件

  把pipeline放在第二章讲解是为了熟悉一些插件的使用方式和怎么使用pipeline,在后面整个构建过程会更方便.

  • Pipeline通过特定语法对简单到复杂的传输管道进行建模;

    声明式:遵循与Groovy相同语法。pipeline { }

    脚本式:支持Groovy大部分功能,也是非常表达和灵活的工具。node { }

  • Jenkins Pipeline的定义被写入一个文本文件,称为Jenkinsfile。

  pipeline有一点需要注意,和shell脚本执行方式不同,pipeline首先会检查全部的语法,只有出错一处就会提示错误.

  

2.1 简单jenkinsfile案例

  以下是一个简单pipeline的CI/CD流程的案例.

  从git拉取代码 → mvn编译  → 镜像打包 → 部署至k8s →  执行kubectl命令

 1 // 公共  
 2 def registry = "registry.ap-northeast-1.aliyuncs.com"  
 3 def git_address = "http://degitlab.mbgadev.cn/platform/resource.git"  
 4   
 5 // 项目  
 6 def project = "lcm-tw"  
 7 def app_name = "resource"  
 8 def image_name = "${registry}/${project}/${app_name}-${branch}:${BUILD_NUMBER}"  
 9 // 认证  
10 def secret_name = "aliyun-hub-jp"  
11 def docker_registry_auth = "f0bba775-b2b5-4376-a69e-b30265f21af9"  
12 def git_auth = "74c9c446-430d-4398-90b6-e25604a62b0c"  
13 def k8s_auth = "f50f35b2-8473-44e0-b750-16b8bed5b76c"  
14   
15      #重要主体代码,需要做的操作都在node(){}里面增加stage(){}就可以
16     node(""){  
17       // 第一步  
18       stage('拉取代码'){  
19          checkout([$class: 'GitSCM', branches: [[name: '${branch}']], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_address}"]]])  
20       }  
21       // 第二步  
22       stage('代码编译'){  
23           sh "mvn clean"  
24           sh "mvn compile"  
25           sh "mvn package -DskipTests"  
26       }  
27         
28       // 第三步  
29       stage('构建镜像'){  
30           withCredentials([usernamePassword(credentialsId: "${docker_registry_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {  
31                 sh """ 
32                      
33                     cp ../config/logback.xml ./ 
34                     sed -i 's#\$namespace#${namespace}#' logback.xml 
35                      
36                     docker login -u ${username} -p '${password}' ${registry} 
37                     echo ' 
38                     FROM registry.ap-northeast-1.aliyuncs.com/dena/alpine-oraclejdk8:jst 
39                     WORKDIR /opt 
40                     COPY resource-srv/target/*.jar /opt/resource-srv.jar 
41                     COPY resource-srv/dist/bootstrap.yml /opt/ 
42                     COPY logback.xml /opt 
43                     EXPOSE 8080 
44                     ENTRYPOINT ["java","-Dlogging.config=logback.xml","-Xms1024m","-Xmx1024m","-jar","/opt/resource-srv.jar"] 
45                 ' > Dockerfile 
46                 echo ${env} 
47                 docker build -t ${image_name} . 
48                 docker push ${image_name} 
49             """  
50             }  
51       }  
52       // 第四步  
53       stage('部署到K8S平台'){  
54           sh """ 
55             cp -rf ../config/${JOB_NAME}.yml ./ 
56             pwd 
57             sed -i 's#\$NAMESPACE#${namespace}#' ${JOB_NAME}.yml 
58             sed -i 's#\$IMAGE_NAME#${image_name}#' ${JOB_NAME}.yml 
59             sed -i 's#\$SECRET_NAME#${secret_name}#' ${JOB_NAME}.yml 
60           """  
61           kubernetesDeploy configs: "${JOB_NAME}.yml", kubeconfigId: "${k8s_auth}"  
62       }  
63         
64           //第五步  
65       stage('查看pod状态及最后100行日志'){  
66   
67           withKubeConfig(caCertificate: '', contextName: '', credentialsId: '46d2adc5-6c00-4988-a84a-26b83e44c62c', namespace: 'public', serverUrl: 'https://1E73E810A71671022BC2EB1A4F79C434.sk1.ap-northeast-1.eks.amazonaws.com') {  
68                 sh """ 
69                 sleep 60; 
70                 echo '============查看当前pod状态============'; 
71                 kubectl get pod -n ${namespace} | grep ${JOB_NAME}; 
72                 echo '============查看当前deploy状态============'; 
73                 kubectl get deploy -n ${namespace} | grep ${JOB_NAME}; 
74                 for i in `kubectl get pod -n ${namespace} | grep ${JOB_NAME} | awk '{print \$1}' | tail -1`;do echo "----------------------------------------------------------\$i----------------------------------------------------------";kubectl logs  --tail 100 \$i   -n test ;done 
75                    """  
76            }  
77       }  
78     } 

 

2.2 凭据

  参照2.1章节,12行为git_auth鉴权id

12.  def git_auth = "74c9c446-430d-4398-90b6-e25604a62b0c" 

 

  1.点击jenkins→凭据→凭据(jenkins)凭据→点击全局凭据→左侧栏添加凭据

 

  输入用户名和密码.

  由于在创建的时候是看不到密钥id的,所以在创建完成后重新编辑密钥可以看到生成的密钥ID

  点击更新就可以看到了

 

2.3 Git

  17-19为pipeline怎么使用git插件(当然也可以用SVN)

17.      stage('拉取代码'){  
18.         checkout([$class: 'GitSCM', branches: [[name: '${branch}']], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_address}"]]])  
19.      }  

  1.找到jenkins pipeline流水线语法  

  这个链接在一个pipeline任务里面才会有

  进入pipeline的测试界面

  2.演示git pipeline中的使用方式

  然后点击“生成流水线脚本”

  由于这里直接生成的脚本参数比较多,还有很多默认值,而且还有比如地址,git密钥都可以替换成为变量的方式,所以,这里稍作更改.

  所以我们可以删除多余的参数,然后将一些我们输入的参数替换成为变量.

17.      stage('拉取代码'){  
18.        #三个参数,分支,git密钥,git地址全部替换成为变量
19。         checkout([$class: 'GitSCM', branches: [[name: '${branch}']], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_address}"]]])  
20.      }  

 

2.4 Docker login

  案例27-50行的过程是镜像打包,如果设置镜像打包以及推送至仓库,那么就需要登录docker仓库,那么明文的密码不够安全,所以该插件可以将明文包裹成为密文配合在jenkins中使用.

27.          // 第三步  
28.          stage('构建镜像'){  
29.              withCredentials([usernamePassword(credentialsId: "${docker_registry_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {  
30.                    sh """ 
31.                         
32.                        cp ../config/logback.xml ./ 
33.                        sed -i 's#\$namespace#${namespace}#' logback.xml 
34.                         
35.                        docker login -u ${username} -p '${password}' ${registry} 
36.                        echo ' 
37.                        FROM registry.ap-northeast-1.aliyuncs.com/dena/alpine-oraclejdk8:jst 
38.                        WORKDIR /opt 
39.                        COPY resource-srv/target/*.jar /opt/resource-srv.jar 
40.                        COPY resource-srv/dist/bootstrap.yml /opt/ 
41.                        COPY logback.xml /opt 
42.                        EXPOSE 8080 
43.                        ENTRYPOINT ["java","-Dlogging.config=logback.xml","-Xms1024m","-Xmx1024m","-jar","/opt/resource-srv.jar"] 
44.                    ' > Dockerfile 
45.                    echo ${env} 
46.                    docker build -t ${image_name} . 
47.                    docker push ${image_name} 
48.                """  
49.                }  
50.          }  

 

  1.创建docker login凭证

  添加凭证方式和添加git凭证一样,添加一个docker的密钥,这里不需要输入地址,等会由插件把这个id转换为变量,那么就是密文了.

  2.演示docker login pipeline中的语法

  新增一个值,将刚才的保存的docker密钥转换成为 username和password2个变量

  然后生成流水线语法

  3.引用这个2个变量

  在代码的35行引用这2个变量

35.  docker login -u ${username} -p '${password}' ${registry} 

 

2.5 Kubernetes(Jenkins slave)

  到这里,大多情况,就需要去安装插件了.

  官方插件链接: https://github.com/jenkinsci/kubernetes-plugin

  本插件是连接k8s集群配合kubernetes Continuous Deploy来使用,使用这个插件才能新增一个云类型,去配置连接k8s.

 

  1.安装kubernetes

  Jenkins→系统管理→插件管理→可选插件

  安装完成就回出现podTemplate语法和系统管理的新建云。

  语法:

  新建云:

  系统管理→系统管理

  详细插件的用法会在jenkins slave篇章讲解。

 

2.6 Kubernetes Continuous Deploy

  本插件需要注意,由于该插件会去读取kubeconfig去访问k8s集群,如果是eks集群,aws是自行开发的认证插件,所以该插件无法读取出aws eks的 kubeconfig的用户,从而去使用匿名用户访问。

  以下是添加密钥完成后给的提示,该插件没有去使用这个密钥。

  所以这里需要开启匿名的访问权限,由于匿名用户访问没有安全性,所以建议k8s集群使用防火墙限制访问。

kubectl create  clusterrolebinding cluster-system-anonymous --clusterrole=cluster-admin --user=system:anonymous

  案例52-61行是使用此插件推送资源至目标k8s集群,kubernetes去连接k8s,但是推送资源需要本插件,本章核心主要就是59行代码

51.          stage('部署到K8S平台'){  
52.              sh """ 
53.                cp -rf ../config/${JOB_NAME}.yml ./ 
54.                pwd 
55.                sed -i 's#\$NAMESPACE#${namespace}#' ${JOB_NAME}.yml 
56.                sed -i 's#\$IMAGE_NAME#${image_name}#' ${JOB_NAME}.yml 
57.                sed -i 's#\$SECRET_NAME#${secret_name}#' ${JOB_NAME}.yml 
58.              """  
59.              kubernetesDeploy configs: "${JOB_NAME}.yml", kubeconfigId: "${k8s_auth}"  
60.          }  

 

  1.安装kubernetes Continuous Deploy

  Jenkins→系统管理→插件管理→可选插件

  安装完成后等待重启

  2.输入k8s凭证

  只有安装了该插件,才会有k8s的凭证类型

  该凭证一般在以下路径

  3.演示 Kubernetes continuous deploy在pipiline中的用法

  生成流水线流水线脚本

  然后我们删除默认,把值替换成为变量,就有了59行代码

59.   kubernetesDeploy configs: "${JOB_NAME}.yml", kubeconfigId: "${k8s_auth}"  

  注意:这里只有2个参数,而没有k8s地址的信息,怎么将该资源推送至k8s集群?

  因为在k8s_auth的信息里面已经有了k8s集群的地址信息。

 

2.6.1 推送失败

  在k8s1.16版本,jenkins以及插件有了很多更新,此时jenkins最新版本为 2.235.5,这时,使用kubernetes插件,一直会有以下报错.

  以下是网络上的解决办法(本人未测试)

  当jenkins为2.235.5时,jackson2 版本为2.11.2,snakeyaml 为1.26.4,k8s为1.16以上时,那么只有回滚插件版本才能解决。

 

2.7 Kubernetes Cli

  插件官方链接: https://github.com/jenkinsci/kubernetes-cli-plugin

  这一个插件和上一个不太相同,Kubernetes Continuous deploy的作用是让插件去调用k8s的api完成工作,该插件是获取k8s集群的权限,使用命令去访问k8s集群。

  代码78-89讲解了如何使用Cli 执行k8s的命令操作k8s集群,主要是80行代码

79.          stage('查看pod状态及最后100行日志'){  
80.      
81.              withKubeConfig(caCertificate: '', contextName: '', credentialsId: '46d2adc5-6c00-4988-a84a-26b83e44c62c', namespace: 'public', serverUrl: 'https://1E73E810A71671022BC2EB1A4F79C434.sk1.ap-northeast-1.eks.amazonaws.com') {  
82.                    sh """ 
83.                    sleep 60; 
84.                    echo '============查看当前pod状态============'; 
85.                    kubectl get pod -n ${namespace} | grep ${JOB_NAME}; 
86.                    echo '============查看当前deploy状态============'; 
87.                    kubectl get deploy -n ${namespace} | grep ${JOB_NAME}; 
88.                    for i in `kubectl get pod -n ${namespace} | grep ${JOB_NAME} | awk '{print \$1}' | tail -1`;do echo "----------------------------------------------------------\$i----------------------------------------------------------";kubectl logs  --tail 100 \$i   -n test ;done 
89.                       """  
90.               }  

 

  1.安装kubernetes Cli

  Jenkins→系统管理→插件管理→可选插件

  2.输入k8s凭证

  只有安装了该插件,才会有另一种 k8s的凭证类型,该凭据是secret密钥的类型,安装这个插件,在pipeline语法生成器的时候选择密钥才会显示出来,或者拷贝别人的代码然后把密钥id粘贴过去,这里为了走一个完整流程,所以这里多了一句嘴。

  获取secret密钥,该密钥简单来说就是k8s serveraccount的token,所以会有以下步骤,

  以下就是就是创建一个jenkins的server,然后与集群有权限的角色绑定,然后在获取他的token.

kubectl -n public  create serviceaccount jenkins-robot  
kubectl -n public  create clusterrolebinding jenkins-robot-binding --clusterrole=cluster-admin --serviceaccount=public:jenkins-robot kubectl -n public get serviceaccount jenkins-robot -o go-template --template='{{range .secrets}}{{.name}}{{"\n"}}{{end}}'  
 kubectl -n public get secrets jenkins-robot-token-rwvkv  -o go-template --template '{{index .data "token"}}' | base64 -d  

  密钥名根据实际情况做调整

  然后将token复制到jenkins的secret位置

  这样,pipeline语法的时候就有这个密钥可以选择了。

  3.演示 Kubernetes Cli在pipiline中的用法

  然后生成流水线脚本

  基本上保留凭据id和serverurl就可以连接至k8s集群了

          withKubeConfig(caCertificate: '', contextName: '', credentialsId: '46d2adc5-6c00-4988-a84a-26b83e44c62c', namespace: 'public', serverUrl: 'https://1E73E810A71671022BC2EB1A4F79C434.sk1.ap-northeast-1.eks.amazonaws.com') {}  

  4.报错提示

  一般出现以上报错,要么就是密钥没有正确,要么就是k8s地址没有正确,一般这里都是细节问题没有改好

 

2.8 Git Parameter Plug-In

  该插件不做详细讲解,由于该插件安装之后是一个多选框,而不是下拉框,所以这里只是记录一些注意事项。

  该插件会有如下不美观几个问题。

    1. 获取到的前端显示为多选框,不美观.

    2. 获取的分支带有origin,配合本文档的pipeline,pipeline需要修改.

    3. git认证问题,这里无法输入密码,要么在图形git插件认证后,要么使用pipeline的如下代码认证:

checkout([$class: 'GitSCM', branches: [[name: '${branch}']], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_address}"]]])  

 

2.9 Dynamic Parameter

2.9.1 插件安装

  1.先下载插件hpi文件到本地

jenkins插件下载地址

http://mirror.xmission.com/jenkins/plugins/

http://updates.jenkins-ci.org/download/plugins/

dynamicparameter下载地址:

http://mirror.xmission.com/jenkins/plugins/dynamicparameter/

依赖插件 role-strategy,同样参照下面方法上传插件(经过测试该版本不依赖),可能由于插件不在更新,Jenkins为更新版的缘故,但是改jenkins版本,只有0.1.1能够使用.

  2.手动上传插件

  目录:jenkins->插件管理->高级->高级->上传插件

  安装成功后,会显示蓝色的图标以及完成,如果失败是红色的,会显示详细信息

  然后去jenkins job的配置里,添加参数可以看到新增的Dynamic Parameter插件

 

2.9.2 插件使用

  Dynamic Parameter使用groovy语言

  Name中为变量名,Choices Script为脚本内容,最后一个变量值赋给定义的变量

  注意:

  Dynamic Choice Parameter是一个选择列表,需要定义的变量是一个列表型

  Dynamic Parameter是一个字符串,需要定义的变量是一个字符串

  最终展示的Dynamic Choice Parameter和Dynamic Parameter情况如下:

 

3.Jenkins slave

3.1 简介之kubernetes的详细使用

  由于jenkins特性的原因,会导致jenkins-slave重新拉取代码,所以这一篇章只是演示jenkins-slave怎么使用,具体需不需要使用根据实际环境定义.

  从上一章可以看到,Jenkins使用pipeline运行任务脚本的主体是node(){},然后在里面执行各个步骤stage('拉取代码'){ }等,所以一个简单的pipeline就是以下:

node(''){  
  stage('pipetest'){  
      sh """ 
         echo "pipetest" 
         """   
  }  
}  

  在第二章节里面可以看出,这是一个整体pipeline流程,也就是一个cd/cd的流程,但是,所有的一些都是由jenkins master来执行的,如果想只要jenkins master来管理任务分配,其余的去生成一个job的容器去处理任务处理完成销毁,那么就需要jenkins slave了。

  这里就需要用到kubernetes插件,因为有了这个插件才会有语法。

  具体如下图。

  由于以上图形操作使用比较复杂,所以这里,直接使用现有的配置做更改。(调试语法才推荐使用图形)

1.    #注意,这里因为是文本,所以注释使用#,在pipeline里面注释使用//
2.    #这里的label要与node()这里的对应,不然无法匹配到jenkins slave的任务  
3.    #cloud算是一些默认配置,namespace最好填写jenkisn Master所在的namespace,这里随便哪里都行,只要jenkins-slave能访问jenkins-master,建议和jenkins master在同一空间下。这里的命名空间就是这个slave将要运行的空
4.    podTemplate(label: 'jenkins-slave', cloud: 'kubernetes', namespace: 'public',containers: [  
5.        containerTemplate(  
6.            #这个name必须是jnlp,不然创建jenkins slave的时候无法匹配到  
7.            name: 'jnlp',   
8.            #这是自定义的jenkins slave镜像,里面安装了maven git kubectl等工具  
9.            image: "lizexiong/jenkins-slave-dena-jdk",  
10.            #总是获取新镜像,因为有时候需要自动去更新  
11.            alwaysPullImage: true,  
12.            #jenkins slave的资源限制  
13.            resourceLimitCpu: '500m', resourceLimitMemory: '1024Mi',   
14.            resourceRequestCpu: '200m', resourceRequestMemory: '218Mi'  
15.        ),  
16.      ],  
17.      volumes: [  
18.      #挂在本地docker文件,让jenkins slave可以执行docker命令  
19.       hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),  
20.        hostPathVolume(mountPath: '/usr/bin/docker', hostPath: '/usr/bin/docker')  
21.      ],  
22.    )  
23.      
24.      
25.    #pipeline执行的主体,ci/cd流程就在这里  
26.    {  
27.    #这里的名称一定要与podTemplate里面的label对应上,否则无法关联到任务  
28.        node('jenkins-slave'){  
29.            stage('测试jenkins slave'){  
30.                sh """ 
31.                    echo "jenkins slave结束后销毁" 
32.                """  
33.            }  
34.        }  
35.    }  

  Jenkins slave通过 Jenkins master的50000端口来通信,所以要开启这个端口;

  在系统管理→全局安全设置→代理

  以上是构建jenkins slave的pipeline的基础框架,但是部署jenkins master分为在集群内和集群外,

  所以延伸出2种方式,Jenkins master是部署在集群内还是集群外。以下讲解。

  (另外,k8s也分很多种云平台,比如eks,认证是自己开发出来的插件,可能和现在的插件无法完全契合)

 

3.2 EKS部署

3.2.1 Jenkins master镜像制作

1.     FROM lizexiong/alpine-oraclejdk8:cst  
2.    #ADD让tomcat文件夹下的会拷贝到目标目录,而不是直接把文件夹拷贝过去了  
3.    ADD tomcat/ /usr/local/tomcat/  
4.    RUN rm -rf /usr/local/tomcat/webapps/*  
5.    RUN apk add git  
6.    RUN apk add maven  
7.    RUN apk add libltdl  
8.    RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime  
9.    #配置k8s cli插件,可以执行kubectl命令  
10.    ADD kubectl /bin/  
11.    ADD jenkins /usr/local/tomcat/webapps/ROOT/  
12.    #自定义了maven 私有仓库的地址  
13.    ADD settings.xml /root/.m2/  
14.    #WORKDIR /usr/local/tomcat/bin  
15.    EXPOSE 8080  
16.    #jenkins slave使用的agent的端口  
17.    EXPOSE 50000  
18.    #ENTRYPOINT /usr/local/tomcat/bin/startup.sh && tail -f /usr/local/tomcat/logs/catalina.out  
19.    ENTRYPOINT  ["/usr/local/tomcat/bin/catalina.sh","run"]                         

 

3.2.2 Jenkins master K8s yml配置

  由于服务都是通用模版,这里也仅贴出开放的一些端口和磁盘挂载的方式

1.        ports:  
2.     - containerPort: 8080  
3.    #jenkins slave agent的端口  
4.     - containerPort: 50000  
5.     volumeMounts:  
6.     #因为在docker里面,所以做了数据系统分离,使用nfs保存jenkins数据  
7.     - mountPath: "/opt/jenkins/"  
8.       name: jenkins-data  
9.      #以下2个挂载是为了可以执行docker命令  
10.     - mountPath: /var/run/docker.sock  
11.       name: docker-sock  
12.     - mountPath: /usr/bin/docker  
13.       name: docker-cli  
14.    volumes:  
15.     - name: docker-sock  
16.       hostPath:  
17.         path: /var/run/docker.sock  
18.     - name: docker-cli  
19.       hostPath:  
20.         path: /usr/bin/docker  
21.     - name: jenkins-data  
22.       nfs:  
23.         path: /nfs/jenkins  
24.         server: 10.1.50.250  

 

3.2.3 Jenkins slave镜像制作

  因为jenkins slave是动态生成的,用完销毁,所以这里没有k8s配置

1.      #以下是jenkins slave默认的地址,可以根据这个镜像构建自建需要的镜像  
2.    #http://jenkins-test.mobage.cn/jenkins/jnlpJars/slave.jar  
3.    FROM lizexiong/alpine-oraclejdk8:cst  
4.      
5.    #这里是一个jenkins的bug,在启动的时候加入这个参数就能解决报错,该报错体现在maven编译那里  
6.    ENV JAVA_OPTS="-Dorg.jenkinsci.plugins.durabletask.BourneShellScript.HEARTBEAT_CHECK_INTERVAL=86400"  
7.    RUN mkdir -p /usr/share/jenkins  
8.    RUN apk add curl  
9.    RUN apk add maven  
10.    RUN apk add git  
11.    RUN apk add libltdl  
12.    COPY slave.jar /usr/share/jenkins/slave.jar  
13.    COPY jenkins-slave /usr/bin/jenkins-slave  
14.    #不管怎么启动,经过本人验证,只有当有任务正常接入的时候jenkins-slave才会正常工作,否则会因为没有持续输出导致容器退出失败  
15.    RUN chmod +x /usr/bin/jenkins-slave  
16.    ADD settings.xml /root/.m2/  
17.    #配置k8s cli插件,可以执行kubectl命令  
18.    ADD kubectl /bin/  
19.    ENTRYPOINT  ["jenkins-slave"]  

 

3.2.4 新建云

  安装kubernetes之后,在系统管理界面会出现一个新建云的方式,在podTemplate里面指定的要和这里的云对应,如果不新建一个云,那么就会有以下报错.

  所以说,新建云和podTemplate是联合使用的.

  podTemplate是jenkins-slave的信息。

  新建云是明确k8s地址和jenkins master地址的作用。

 

  1.新建云,系统管理→系统设置,下拉到最下面.

  以下主要是各个地方命名空间的问题,只要把握住,不管是podTempalte里面填写的命名空间还是新建云里面填写的,只要jenkins-slave能连接jenkins-master,那就没有问题。

 

3.2.5 Jenkins-slave的访问权限

  在运行job之前,可能是使用的default的serviceaccount,那么这个jenkins-slave无法去调用k8s的资源类型,会有以下报错。(AWSEKS里面是这样)

   {  "timestamp":"2018-12-05T08:18:31.503+0000",

   "status":500,

   "error":"Internal Server Error",

   "message":"Failure executing: GET at: https://kubernetes.default.svc/api/v1/namespaces/default/services. Message: Forbidden!Configured service account doesn't have access. Service account may have been revoked. services is forbidden: User "system:serviceaccount:default:default" cannot list resource "services" in API group "" in the namespace "default".",

   "path":"/services"}

  那么如果不去指定jenkins-slave的servicesaaccount的话,那么用以下简单的方法来处理就可以,提升全部用户为管理员权限(不建议生产环境使用)

kubectl create clusterrolebinding permissive-binding \ --clusterrole=cluster-admin \ --user=admin \ --user=kubelet \ --group=system:serviceaccounts  

 

3.2.6 运行一个job

  以上工作完成后,我们运行一个简单的示例看看是否能正常运行jenkins-slave.

  出现以下jenkins-slave就算构建成功了

 

3.2.7 Jenkins slave报错解决

  Jenkins slave无法连接jenkins master

  以下报错是jenkins在 public空间,但是jenkins slave被部署到了prod空间,slave找不到jenkins master,所以报错了,解决方式很简单,只要保证jenkins slave能连接到jenkins master就行了.

 

3.3 宿主机部署

  由于每个云平台认证插件不太相同,所以一下列出每个平台的对比

 

3.3.1 宿主机部署推送至EKS

  由于宿主机部署jenkins master这里不做讲解,而jenkins slave的镜像制作和pipeline和在k8s集群中完全一样,所以这里只写出不同的地方,而不同的地方只有一个。

  由于认证方式不同,无法获取eks的访问凭证,所以这里直接使用匿名来访问,并且禁用https的检查。

  这样的方式可能不太安全,但是截至2019年8月24日 15:47:43 jenkins密钥插件还不能完美的配置eks集群。

  以上这一点不同之处完成,就可以在eks中构建jenkins slave了。

 

3.3.2 宿主机部署推送至阿里云

 

4.持续部署持续交付

  本章节讲解完全的一套ci/cd流程,包括需要哪些参数,哪些插件都回详细说明,并且根据对应的更新情况有对应的更新项,具体如以下:

  (在下面实例中,不演示jenkins-slave的方式,jenkins-slave参阅第三章)

  1.2019年9月18日    发布与回滚.

 

4.1 发布与回滚

  这里使用resource项目来讲解,可能代码中的一些密钥id发生了改变,但是主体逻辑不变.

 

  1.创建一个流水线项目”,首先进入参数化构建过程.

  发布与回滚需要用到参数化构建过程中3个选项参数,一个运行时参数.(参数值最好不要用“-”,否则pipeline读取变量时无法识别)

  分支参数

  Git使用这个的变量来判断拉取哪个分支的代码.

  命名空间

  用这个控制服务部署在k8s的哪个命名空间下

  发布与回滚控制参数

  这个参数控制是否发布还是回滚

  回滚版本号

  该参数是一个url,需要自己去获取其中的版本号,如果status变量是deploy,这个参数可以忽略

  2.流水线语法

1.    // 公共  
2.    def registry = "registry.cn-hangzhou.aliyuncs.com"  
3.    def git_address = "http://degitlab.mbgadev.cn/platform/resource.git"  
4.    def k8s_address = "https://1E73E810A71671022BC2EB1A4F79C434.sk1.ap-northeast-1.eks.amazonaws.com"  
5.      
6.    // 项目  
7.    def project = "lcm-tw"  
8.    def app_name = "resource"  
9.    def image_name = "${registry}/${project}/${app_name}-${branch}:${BUILD_NUMBER}"  
10.      
11.      
12.    // 认证  
13.    def secret_name = "aliyun-hub-cn"  
14.    def docker_registry_auth = "6a657490-41e1-44bb-a7ad-5914ccb351b0"  
15.    def git_auth = "a424997d-4465-4ddf-9462-01ea8f576b06"  
16.    def k8s_auth = "827816b0-945f-48a2-bdf3-a8f098029f2c"  
17.    //k8s-cli插件需要使用到的密钥  
18.    def k8s_cli = "2b2b9edb-7b71-429e-9909-19de7ffce54e"  
19.      
20.      
21.    node(""){  
22.      
23.     //如果 status 等于deploy,那么才会继续拉取代码→ 代码编译 →  构建镜像,如果等于其它的,那么跳过这3个步骤  
24.     if ( "${status}" == "deploy" ) {  
25.       
26.      // 第一步  
27.      stage('拉取代码'){  
28.         //如果使用 动态获取git分支插件,可以在图形化或者这里来通过git的验证  
29.         checkout([$class: 'GitSCM', branches: [[name: '${branch}']], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_address}"]]])  
30.      }  
31.      // 第二步  
32.      stage('代码编译'){  
33.          sh "mvn clean"  
34.          sh "mvn compile"  
35.          sh "mvn package -DskipTests"  
36.      }  
37.        
38.      // 第三步  
39.      stage('构建镜像'){    //使用pipeline调试可以把密码id转换成用户名和密码字段  
40.          withCredentials([usernamePassword(credentialsId: "${docker_registry_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {  
41.                sh """ 
42.                     
43.                    #因为这里的日志格式发生了改变,这是java的logback模版,里面新增了一个 环境 的字段 
44.                    cp ../config/logback.xml ./ 
45.                    sed -i 's#\$namespace#${namespace}#' logback.xml 
46.                     
47.                    docker login -u ${username} -p '${password}' ${registry} 
48.                    echo ' 
49.                    FROM registry.ap-northeast-1.aliyuncs.com/dena/alpine-oraclejdk8:jst 
50.                    WORKDIR /opt 
51.                    COPY resource-srv/target/*.jar /opt/resource-srv.jar 
52.                    COPY resource-srv/dist/bootstrap.yml /opt/ 
53.                    COPY logback.xml /opt 
54.                    EXPOSE 8080 
55.                    ENTRYPOINT ["java","-Dlogging.config=logback.xml","-Xms2048m","-Xmx2048m","-jar","/opt/resource-srv.jar"] 
56.                ' > Dockerfile 
57.                echo ${env} 
58.                docker build -t ${image_name} . 
59.                docker push ${image_name}1 
60.            """  
61.            }  
62.      }  
63.    }   //if结束  
64.      
65.      // 第四步  
66.      stage('部署到K8S平台'){  
67.        
68.      //如果等于deploy,那么就会走完正常流程  
69.      if ( "${status}" == "deploy" ) {  
70.        
71.          sh """ 
72.            cp -rf ../config/${JOB_NAME}.yml ./ 
73.            sed -i 's#\$NAMESPACE#${namespace}#' ${JOB_NAME}.yml 
74.            sed -i 's#\$IMAGE_NAME#${image_name}#' ${JOB_NAME}.yml 
75.            sed -i 's#\$SECRET_NAME#${secret_name}#' ${JOB_NAME}.yml 
76.          """  
77.          
78.        }else if ( "${status}" == "rollback" ){  
79.          
80.          //否则从运行时参数里面过滤出版本号,然后生成新的镜像名称让k8s yaml去替换  
81.          //因为运行时参数获取的是一个url,这里把里面的版本号给过滤出来  
82.          def rollback_version_number = sh(script: "echo `echo ${rollback_version} | awk  -F '/' '{print \$6}'`", returnStdout: true).trim()  
83.          def rollback_image = "${registry}/${project}/${JOB_NAME}-${branch}:${rollback_version_number}"  
84.           
85.          sh """ 
86.            echo "${rollback_version_number}" 
87.            echo "${rollback_image}" 
88.            echo "${status}" 
89.            cp -rf ../config/${JOB_NAME}.yml ./ 
90.            sed -i 's#\$NAMESPACE#${namespace}#' ${JOB_NAME}.yml 
91.            sed -i 's#\$IMAGE_NAME#${rollback_image}#' ${JOB_NAME}.yml 
92.            sed -i 's#\$SECRET_NAME#${secret_name}#' ${JOB_NAME}.yml 
93.           """  
94.        }   //if结束  
95.          kubernetesDeploy configs: "${JOB_NAME}.yml", kubeconfigId: "${k8s_auth}"  
96.      }  
97.        
98.      //第五步  
99.      stage('查看pod状态'){  
100.            //k8s执行命令的插件,这里的密钥是k8s serveraccount的token值  
101.            withKubeConfig(caCertificate: '', contextName: '', credentialsId: "${k8s_cli}", namespace: 'public', serverUrl: "${k8s_address}") {  
102.            sh """ 
103.            sleep 60; 
104.            echo '等待60秒' 
105.            echo '============查看当前pod状态============'; 
106.            kubectl get pod -n ${namespace} | grep ${app_name}; 
107.            echo '============查看当前deploy状态============'; 
108.            kubectl get deploy -n ${namespace} | grep ${app_name}; 
109.            //这里输出pod的最后100行日志,由于在脚本式里面写出这行代码非常困难,所以保留,供后期参考 
110.            #for i in `kubectl get pod -n ${namespace} | grep ${app_name} | awk '{print \$1}' | tail -1`;do echo "----------------------------------------------------------\$i----------------------------------------------------------";kubectl logs  --tail 100 \$i   -n ${namespace} ;done 
111.               """  
112.            }  
113.        }  
114.    }  

  3.界面展示

 

5.Aws case

5.1 关于kubernetes continuous deploy 密钥

  李先生您好,

  很高兴今天和您通话。在电话中我们探讨了 EKS 管理集群的用户或 IAM 角色,其中需要注意的是当您创建一个 Amazon EKS 集群时,将在集群的 RBAC 配置中自动为创建集群的 IAM 实体用户或角色(例如,联合身份用户)授予 system:masters 权限。要授予其他 AWS 用户或角色与您的集群进行交互的能力,您必须编辑 Kubernetes 内的 aws-auth ConfigMap [1].   例如我们添加了 IAM 用户 arn:aws:iam::858659433780:user/zexiong.li 在 mapUsers 部分。

  我们通过屏幕共享查询了 aws-auth-cm.yaml 和 configmap 并确认您的 EKS 集群配置无误并且可以正常在跳板实例上操作 kubectl 相关命令。您遇到的主要问题是在利用 Jenkins 做持续部署导入 EKS 集群中时遇到如下报错:

  ERROR: ERROR: io.fabric8.kubernetes.client.KubernetesClientException: Failure executing: GET at: https://1e73e810a71671022bc2eb1a4f79c434.sk1.ap-northeast-1.eks.amazonaws.com/apis/apps/v1/namespaces/test/deployments/lcm-user . Message: Forbidden! User arn:aws:eks:ap-northeast-1:858659433780:cluster/ap-lcm-test-k8sMaster doesn't have permission. deployments.apps "lcm-user" is forbidden: User "system:anonymous" cannot get resource "deployments" in API group "apps" in the namespace "test".

  通过细致排查,我们发现您 jenkins 用到的插件 kubernetes-cd-plugin [2] 目前不支持 [3] 我们 EKS 使用的 IAM 授权工具 aws-iam-authenticator [4]. 我们建议您在 Github 社区论坛里继续跟踪此问题看插件是否有更新。于此同时您问到是否有其他方法可以绕过报错达到 EKS 给予用户 "system:anonymous" get resource "deployments" 的权限。我查了不少相关讨论,很多都需要涉及 API server 的改动在 EKS 无法实现因为 api server 属于控制平面 (control plane),是由我们 EKS 内部团队在负责。而且这些改动大多被 k8s 社区看做有潜在的安全隐患 [5].

  关于使用 EKS 和 Jenkins 的集成来实现持续交付的架构您可以参考我们在电话里提到的一篇中文博客 - 使用 Amazon EKS 和 Jenkins X 持续交付 [6]. 这篇博客从头到尾有演示步骤以及解释,和您目前的架构非常相像,也可以绕开上面的插件报错。另外,Jenkins 及其插件属于第三方软件超出了我们技术支持的范畴,不过我们会尽最大努力来帮助您。

  希望以上信息对您有所帮助。如需任何其他协助,请随时联系我们。

  谢谢!

  相关文献:

  [1] - https://docs.aws.amazon.com/zh_cn/eks/latest/userguide/add-user-role.html

  [2] - https://github.com/jenkinsci/kubernetes-cd-plugin

  [3] - https://github.com/jenkinsci/kubernetes-cd-plugin/issues/71

  [4] - https://github.com/kubernetes-sigs/aws-iam-authenticator

  [5] - https://github.com/kubernetes-sigs/apiserver-builder-alpha/issues/225#issuecomment-501444546

  [6] - https://aws.amazon.com/cn/blogs/china/continuous-delivery-eks-jenkins-x/

推荐阅读