文章目录

  • 一、Kubernetes-plugin 使用
  • 二、环境依赖
  • 2.1、填写kubernetes插件配置
  • 2.2、支持的凭据
  • 三、Kubernetes插件参数
  • 3.1、podTemplate - Pod和容器模板
  • 3.2、container 容器模板的定义
  • 3.3、Liveness Probe Usage
  • 四、Pipeline 示例
  • 4.1、Scripted Pipeline
  • 4.2、Declarative Pipeline
  • 4.3、Jenkins share library Groovy


一、Kubernetes-plugin 使用

  • 说明: Kubernetes-plugin 它是在Kubernetes集群中运行动态代理的Jenkins插件, 该插件为每个启动的代理创建一个Kubernetes Pod,由Docker映像定义运行,并在每次构建后停止。
  • 备注: 代理是使用JNLP启动的,因此预期图像会自动连接到Jenkins主机,以下是重要的环境变量(我们说过不建议使用自己jnlp覆盖默认的jnlp容器)。
# jenkins/inbound-agent
JENKINS_URL: Jenkins web interface url
JENKINS_SECRET: the secret key for authentication
JENKINS_AGENT_NAME: the name of the Jenkins agent
JENKINS_NAME: the name of the Jenkins agent (Deprecated. Only here for backwards compatibility)
  • 插件地址: https://github.com/jenkinsci/kubernetes-plugin
  • 该镜像是Jenkins自定义jnlp容器模板,主要用于Jenkins工作节点容器化使用,主要包含Gitlab_release发布、 docker 容器管理、kubectl 集群管理功能。

二、环境依赖

2.1、填写kubernetes插件配置

  • 可在Jenkins UI并导航至Manage Jenkins-> Configure System-> Cloud-> Kubernetes并输入相应的Kubernetes URL和Jenkins URL;

jenkins基于k8s容器构建 jnlp jenkins k8s插件_运维

2.2、支持的凭据

  • Username/password - 用户名密码
  • Secret File (kubeconfig file) - 秘密文件(kubeconfig文件)
  • Secret text (Token-based authentication) (OpenShift) - 秘密文本(基于令牌的身份验证)(OpenShift)
  • Google Service Account from private key (GKE authentication) - 来自私钥的Google服务帐户(GKE身份验证)
  • X.509 Client Certificate - X.509客户端证书

三、Kubernetes插件参数

可以根据提供的选项生成Template Yaml

jenkins基于k8s容器构建 jnlp jenkins k8s插件_devops_02

3.1、podTemplate - Pod和容器模板

...

podTemplate(label: label, cloud: 'kubernetes',
    nodeSelector: "jenkins=salve",
    containers: [
        containerTemplate(
            name: 'jnlp',
            image: 'jenkins:jnlp-slave',
            ttyEnabled: true,
            privileged: false,
            alwaysPullImage: false,
            ),
	    containerTemplate(name: 'node10', image: 'node:10', alwaysPullImage: false, ttyEnabled: true, command: 'cat',resourceLimitCpu: '1500m')
    ],
    volumes: [
        hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')
    ]
)

...
  • podTemplate 是用于创建代理的pod的模板可以通过用户界面或管道进行配置, 无论哪种方式它都可以访问以下字段
  • cloud :在Jenkins设置中定义的云的名称。默认为kubernetes
  • name : Pod 名称
  • namespace : 名称空间设置
  • label : 标签值可以设置为唯一的值,以避免跨构建冲突,或省略,并在步骤中定义POD_LABEL。
  • yaml : 资源清单声明
  • yamlFile : 指定资源清单的文件
  • yamlMergeStrategy : 控制yaml定义是覆盖还是与从用声明的pod模板继承的yaml定义合并inheritFrom。默认为override()。
  • showRawYaml : 启用或禁用原始Yaml文件的输出默认为true (后续实践)
  • serviceAccount : k8s中创建的服务帐户
  • nodeSelector :节点选择器
  • nodeUsageMode : 节点调用模式(NORMAL它控制Jenkins是只调度标签表达式匹配的作业,EXCLUSIVE尽可能多地使用该节点。)
  • volumes :为pod定义各种类型的卷并在所有容器中挂载卷
  • envVars :应用于所有容器的环境变量(envVar 环境变量其值是内联定义的, secretEnvVar 一个环境变量其值是从Kubernetes机密派生的。)
  • imagePullSecrets : 从私有的镜像仓库中拉取镜像的凭据
  • annotations : Pod 注解
  • InheritFrom :继承的一个或多个Pod模板的列表
  • slaveConnectTimeout : 代理程序联机超时时间(单位s)
  • podRetention :控制保留代理Pod的行为(Can be 'never()', 'onFailure()', 'always()', or 'default()'),如果为空则按照下面的active Deadline Seconds参数进行。
  • activeDeadlineSeconds : pod将在这个截止日期过后被删除。
  • idleMinutes : 允许Pod保持活动状态以供重用,直到在执行最后一步以来经过了配置的分钟数为止。
  • runAsUser : 用户ID,用于将pod中的所有容器运行为。
  • runAsGroup : 组ID,用于将Pod中的所有容器运行为。
  • hostNetwork : 使用主机网络。
  • container : 用于创建pod容器的容器模板 (见下文) - (重点)。
  • defaultContainer : 指定Pod中默认容器则在stages中不用加入container进行指定选择;

3.2、container 容器模板的定义

...

    containers: [
        containerTemplate(
            name: 'jnlp',
            image: 'jenkins:jnlp-slave',
            ttyEnabled: true,
            privileged: false,
            alwaysPullImage: false,
            ),

...
  • containerTemplate 是一个将被添加到pod中的容器模板它属于container中数组对象。同样它可以通过用户界面或管道进行配置,并允许你设置以下字段:
  • name : 容器的名称。
  • image : 容器的图像。
  • envVars : 应用于容器的环境变量(补充和覆盖在pod级别设置的env var)。
  • envVar : 环境变量,其值是内联定义的。
  • secretEnvVar : 一个环境变量,其值是从Kubernetes机密派生的。
  • workingDir : 工作空间目录
  • command : 容器将执行的命令。
  • args : 传递给命令的参数。
  • ttyEnabled : 标记以指示应启用tty。
  • livenessProbe : 要添加到容器中的exec
  • liveness探针的参数 :(不支持httpGet liveness探针)
  • ports : 暴露容器上的端口。
  • alwaysPullImage : 容器在启动时将拉出图像。
  • runAsUser : 用于运行容器的用户ID。
  • runAsGroup : 运行容器的组ID。

3.3、Liveness Probe Usage

...

containerTemplate(
  name: 'node10',
  image: 'node:10',
  ttyEnabled: true,
  command: 'cat',
  livenessProbe: containerLivenessProbe(
    execArgs: 'some --command',
    initialDelaySeconds: 30,
    timeoutSeconds: 1,
    failureThreshold: 3,
    periodSeconds: 10,
    successThreshold: 1
  )
)

...
  • 默认情况下代理连接超时设置为100秒。在某些情况下您想要设置一个不同的值,如果可以可以将system属性设置org.csanchez.jenkins.plugins.kubernetes.PodTemplate.connectionTimeout为一个不同的值。

四、Pipeline 示例

4.1、Scripted Pipeline

podTemplate(
  cloud: 'kubernetes',
  name: 'jenkins-slave',
  namespace: 'devops',
  label: 'k8s-slave',
  // 容器及容器模板定义
  containers: [
    containerTemplate(
      name: 'maven',
      image: 'maven:3.6-jdk-8-alpine',
      ttyEnabled: true,
      command: 'cat',
      privileged: false,
      alwaysPullImage: false,
      workingDir: '/home/jenkins/agent',
      resourceRequestCpu: '100m',
      resourceLimitCpu: '500m',
      resourceRequestMemory: '100Mi',
      resourceLimitMemory: '500Mi',
      envVars: [
        envVar(key: 'MYSQL_ALLOW_EMPTY_PASSWORD', value: 'true')
       //, secretEnvVar(key: 'MYSQL_PASSWORD', secretName: 'mysql-secret', secretKey: 'password')
      ]
      //,ports: [portMapping(name: 'mysql', containerPort: 3306, hostPort: 3306)]
    )
    //     ),
    // containerTemplate(
    //         name: 'jnlp',
    //         image: 'registry.cn-hangzhou.aliyuncs.com/google-containers/jnlp-slave:alpine',
    //         args: '${computer.jnlpmac} ${computer.name}',
    //         command: ''
    //     )
  ]
  ,volumes: [
    hostPathVolume(hostPath: '/nfsdisk-31/appstorage/mavenRepo', mountPath: '/home/jenkins'),
    hostPathVolume(hostPath: '/var/run/docker.sock', mountPath: '/var/run/docker.sock'),
    /*
    emptyDirVolume(mountPath: '/etc/mount1', memory: false),
    secretVolume(mountPath: '/etc/mount2', secretName: 'my-secret'),
    configMapVolume(mountPath: '/etc/mount3', configMapName: 'my-config'),
    hostPathVolume(mountPath: '/etc/mount4', hostPath: '/mnt/my-mount'),
    nfsVolume(mountPath: '/etc/mount5', serverAddress: '127.0.0.1', serverPath: '/', readOnly: true),
    persistentVolumeClaim(mountPath: '/home/jenkins', claimName: 'jenkins', readOnly: false),
    */
  ],
  annotations: [
    podAnnotation(key: "maven-pod", value: "Kubernetes-jenkins-Test")
  ],
  showRawYaml: 'flase'
  //,defaultContainer: 'maven'   # scripted pipeline 不支持该参数
  //,imagePullSecrets: [ 'pull-secret' ]
)
{
 // 注意此次node中则为Pod模板名称
 node ('k8s-slave') {
    // container 中指定Pod中容器名称
    container('maven') {
      stage('maven') {
        container('maven') {
          sh "hostname && ip addr"
          sh ("mvn -version")
          sh "env"
        }
      }
   }
  }
}

4.2、Declarative Pipeline

pipeline {
  agent {
    kubernetes {
      cloud 'kubernetes'
      namespace 'devops'
      workingDir '/home/jenkins/agent'
      // 继承模板
      inheritFrom 'jenkins-slave'
      defaultContainer 'maven'
      // yamlFile 'KubernetesPod.yaml'
      yaml """\
apiVersion:
kind: Pod
metadata:
  labels:
    jenkins: "slave"
    jenkins/label: 'k8s-slave'
spec:
  containers:
  - name: 'maven'
    image: 'maven:3.6-jdk-8-alpine'
    imagePullPolicy: 'IfNotPresent'     # 镜像拉取策略
    command:
    - cat
    tty: true
    """.stripIndent()
    }
  }
  stages {
    stage ('declarative Pipeline - kubernetes') {
      steps {
        echo "declarative Pipeline - kubernetes"
        container ('maven') {
          sh "echo ${POD_CONTAINER}"
          sh "mvn -version"
        }
      }
    }
  }
}
  • 你也可以使用yamlFile将pod模板保存在单独的KubernetesPod.yaml文件中.
pipeline {
  agent {
    kubernetes {
      yamlFile 'KubernetesPod.yaml'
    }
  }
  stages {
     ...
  }
}

4.3、Jenkins share library Groovy

#!groovy

@Library('jenkinslibrary@master') _

//func from shareibrary
def build = new org.devops.build()
def deploy = new org.devops.deploy()
def tools = new org.devops.tools()
def gitlab = new org.devops.gitlab()
def toemail = new org.devops.toemail()
def sonar = new org.devops.sonarqube()
def sonarapi = new org.devops.sonarapi()
def nexus = new org.devops.nexus()
def artifactory = new org.devops.artifactory() 
def k8s = new org.devops.kubernetes()

def runOpts
//env
String buildType = "${env.buildType}"
String buildShell = "${env.buildShell}"
String deployHosts = "${env.deployHosts}"
String srcUrl = "${env.srcUrl}"
String branchName = "${env.branchName}"
String artifactUrl = "${env.artifactUrl}"


if ("${runOpts}" == "GitlabPush"){
    branchName = branch - "refs/heads/"
    
    currentBuild.description = "Trigger by ${userName} ${branch}"
    gitlab.ChangeCommitStatus(projectId,commitSha,"running")
    env.runOpts = "GitlabPush"

    
} else {
   userEmail = "123@qq.com"
}


//pipeline
pipeline{
    agent { node { label "build"}}
    
    
    stages{

       stage("GetCode"){
            steps{
                script{
                    println("${branchName}")
                
                    tools.PrintMes("获取代码","green")
                    checkout([$class: 'GitSCM', branches: [[name: "${branchName}"]], 
                                      doGenerateSubmoduleConfigurations: false, 
                                      extensions: [], 
                                      submoduleCfg: [], 
                                      userRemoteConfigs: [[credentialsId: 'gitlab-admin-user', url: "${srcUrl}"]]])
                }
            }
        }
        stage("Build&Test"){
            steps{
                script{
                    tools.PrintMes("执行打包","green")
                    build.Build(buildType,buildShell)
                    
                }
            }
        }
       
       
        //并行
        stage('parallel01') {
            parallel {
                stage("QA"){
                    steps {
                        script{
                            tools.PrintMes("搜索项目","green")
                            result = sonarapi.SerarchProject("${JOB_NAME}")
                            println(result)
                            
                            if (result == "false"){
                                println("${JOB_NAME}---项目不存在,准备创建项目---> ${JOB_NAME}!")
                                sonarapi.CreateProject("${JOB_NAME}")
                            } else {
                                println("${JOB_NAME}---项目已存在!")
                            }
                            
                            tools.PrintMes("配置项目质量规则","green")
                            qpName="${JOB_NAME}".split("-")[0]   //Sonar%20way
                            sonarapi.ConfigQualityProfiles("${JOB_NAME}","java",qpName)
                        
                            tools.PrintMes("配置质量阈","green")
                            sonarapi.ConfigQualityGates("${JOB_NAME}",qpName)
                        
                            tools.PrintMes("代码扫描","green")
                            sonar.SonarScan("test","${JOB_NAME}","${JOB_NAME}","src","${branchName}")
                            
        
                            sleep 30
                            tools.PrintMes("获取扫描结果","green")
                            result = sonarapi.GetProjectStatus("${JOB_NAME}")
                            
                            
                            println(result)
                            if (result.toString() == "ERROR"){
                                toemail.Email("代码质量阈错误!请及时修复!",userEmail)
                                error " 代码质量阈错误!请及时修复!"
                                
                                
                            } else {
                                println(result)
                            }
                        }
                   }
                }
            
            
               
                //构建镜像
                stage("BuildImages"){
                    steps{
                        script{
                            tools.PrintMes("构建上传镜像","green")
                            env.serviceName = "${JOB_NAME}".split("_")[0]
                           
                            withCredentials([usernamePassword(credentialsId: 'aliyun-registry-admin', passwordVariable: 'password', usernameVariable: 'username')]) {
                               
                               env.dockerImage = "registry.cn-beijing.aliyuncs.com/devopstest/${serviceName}:${branchName}"
                               sh """
                                   docker login -u ${username} -p ${password}  registry.cn-beijing.aliyuncs.com
                                   docker build -t registry.cn-beijing.aliyuncs.com/devopstest/${serviceName}:${branchName} .
                                   sleep 1
                                   docker push registry.cn-beijing.aliyuncs.com/devopstest/${serviceName}:${branchName}
                                   sleep 1
                                   #docker rmi registry.cn-beijing.aliyuncs.com/devopstest/${serviceName}:${branchName}
                                """
                            }
                        }
                    }
                }
            }
        }
       
        //发布
        stage("Deploy"){
            steps{
                script{
                    tools.PrintMes("发布应用","green")
                    
                
                    //获取旧镜像
                    yamlData = readYaml file: "k8stemplate.yaml"
                    
                    println(yamlData[0])
                    println(yamlData[0]["spec"]["template"]["spec"]["containers"][0]["image"])
                    
                    oldImage = yamlData[0]["spec"]["template"]["spec"]["containers"][0]["image"]
                    
                    //替换镜像
                    sourceData = readFile file: 'k8stemplate.yaml'
                    println(sourceData)
                    println(sourceData.getClass())
                    sourceData = sourceData.replace(oldImage,dockerImage)
                    println(sourceData)
                    
                    writeFile file: 'k8stemplate.yaml', text: """${sourceData}"""
                    
                
                    sh """
                        #cat k8stemplate.yaml
                        kubectl apply -f k8stemplate.yaml
                    """
                    
    
                }
            }
        }
       
        //接口自动化测试
        stage("InterfaceTest"){
            steps{
                script{
                    tools.PrintMes("接口测试","green")
    
                }
            }
        }
    }
    post {
        always{
            script{
                println("always")
            }
        }
        
        success{
            script{
                println("success")
                if ("${runOpts}" == "GitlabPush"){
                    gitlab.ChangeCommitStatus(projectId,commitSha,"success")
                }
                toemail.Email("流水线成功",userEmail)
            
            }
        
        }
        failure{
            script{
                println("failure")
                if ("${runOpts}" == "GitlabPush"){
                    gitlab.ChangeCommitStatus(projectId,commitSha,"failed")
                }
                toemail.Email("流水线失败了!",userEmail)
            }
        }
        
        aborted{
            script{
                println("aborted")
                if ("${runOpts}" == "GitlabPush"){
                    gitlab.ChangeCommitStatus(projectId,commitSha,"canceled")
                }
               toemail.Email("流水线被取消了!",userEmail)
            }
        
        }
    
    }
    
    
}