Jenkins使用Kubernetes(同/不同集群)启动动态slave的几种方式(kubectl,UI,Script)

需要用到两个插件:kubernetes-plugin,credential-plugin

https://github.com/jenkinsci/kubernetes-plugin

https://github.com/jenkinsci/credentials-plugin

Jenkins通过kubectl管理远程k8s

准备一个安装k8s的环境或者单独安装kubectl客户端的机器。可以执行kubectl命令。

  1. 登录到远程k8s上,将/opt/kubernetes/ssl/下面的三个文件ca.crt,kubernetes.crt和kubernetes.key 拷贝到kubectl客户端所在机器。
  2. 修改kubectl(操作机)上的~/.kube/config 文件,添加远程k8s的相关信息,如下图:


    1.png

3. 需要说明的是:在合并config相关配置的时候,需要注意context和user的相关配置,一定要对应。current-context 要改成远程的k8s的cluster name。这样执行kubectl get pods 就可以获取远程k8s的信息了.

Jenkins触发远程k8s

要完成这个目标,需要以下几步:

  • k8s集群
  • jenkins server
  • 动态的pod templete
  • 相关的认证

scenario 1: Jenkins 在k8s内部,作为k8s的一个服务

进入jenkins->全局配置->add cloud,如图所示:


localk8s.png

在jenkins url中填写当前k8s中jenkins service的url,其他选项默认就行。点击test connection,如果显示success,则表明可以互相通信。集群内部的好处就是无需单独配置认证。但是在启动动态slave的时候用的资源也是集群里的。

scenario 2: Jenkins 在k8s外部

那么在配置jenkins url这一项的时候,直接填写对应的jenkins server地址。

scenario 3: Jenkins和需要控制的k8s不在同一个集群

这里面写两种方式(1.页面配置。2.脚本实现)

首先确认的是,无论页面配置还是脚本,由于跨集群,我们都需要考虑认证的问题。

* 页面方式

  • 配置cloud(k8s)相关信息

出了前面提到的jenkins url的配置,此时由于k8s是远程的,需要配置远程k8s的相关信息。

    kubernetes name: 随便命名下,后面pod templete中会用到。
    kubernetes url: 远程k8s的url
    kubernetes server certificate key:远程k8s中config中ca.crt中的内容。
    Kubernetes Namespace:填写namespace,这个namespace一定要在k8s中存在
    Credentials:下面会提到,需要添加相关认证信息
    jenkins url:填写jenkins服务的地址
    jenkins tunnel:指定slave连接master的端口url。此处不要加http前缀

页面配置remote.png

这里面不得不提到的就是kubernetes server certificate key,如果这个没有配,会遇到如下错误:

nocertificateKey.png
  • 配置credential

进入jenkins credential配置页面,增加certificate。如图:


addcredential.png

图中需要上传pkcs12的证书。我们通过如下命令将远程的k8s的认证信息生成cert.pfx,并输入生成证书的certPassword(整个password我们会在页面add credential的时候用到)

    openssl pkcs12 -passout pass:${certPassword} -export -out cert.pfx -inkey kubernetes.key -in kubernetes.crt -certfile ca.crt

动态生成的certificate.png

一旦我们上传成功保存后,可以在全局配置ks8的地方,credentials的时候选择刚刚添加的credential.

credential.png

如果我们不配置这个credential的话,就会遇到如下的错误:


nocredential.png
  • 页面配置pod templete

在kubernetes里面add pod templete。如图所示配置,根据提示配置image,Container的模板名称为jnlp。当Container的模板名称为jnlp的时候,jenkins-slave才会使用下面配置的docker镜像来启动pod,如果不为jnlp,则会使用默认的镜像jenkins/jnlp-slave:alpine。当使用自定义的docker镜像来启动jenkins slave pod的时候,下面的command to run(默认值是 sh -c)和arguments to pass to the command(默认值是cat)两个值需要清空。否则会出现jenkins slave jnlp连接不上master的情况,尝试100次连接之后销毁pod,然后再创建一个pod继续尝试连接,无限循环。

podtemplete.png

其中也要配置volumns,为了能docker in docker使用,需要配置以下配置。

volumns.png
  • 测试调试

启动jenkins job, 运行job即可。

总结:页面配置操作相对来说比较简单,但是也有局限性,k8s必须已经存在,那么我们如果想动态的创建k8s,并且动态的增减slave,这种方式就不合适了。下面介绍脚本方式。

* 脚本方式

同样的四个部分:

  1. 配置cloud(k8s)相关信息
  2. 配置credential
  3. 编写pod templete
  4. 编写pipeline,调试

为了能够达到脚本话,我们需要研究插件的源码,然后根据源码中的类,进行相关配置,这也是我觉得相对来说比较难的地方。

def createCloud(String name, String serverUrl, String namespace, String jenkinsUrl, String jenkinsTunnel, String credentialsId, String serverCertificate) {
    def jenkins = Jenkins.get()
    def existingNames = jenkins.clouds.stream()
            .map {
        c -> c.name
    }
    .collect(Collectors.toList())
    KubernetesCloud source = new KubernetesCloud(name)
    source.setServerUrl(serverUrl)
    source.setNamespace(namespace)
    source.setJenkinsUrl(jenkinsUrl)
    source.setJenkinsTunnel(jenkinsTunnel)
    source.setCredentialsId(credentialsId)
    def kc = new KubernetesCloud(name, source)
    kc.setServerCertificate(serverCertificate)
    if (existingNames.contains(name)) {
        def oldCloud = jenkins.clouds.getByName(name)
        jenkins.clouds.replace(oldCloud, kc)
        println("replace cloud ${name}")
    } else {
        jenkins.clouds.add(kc)
        println("create cloud ${name}")
    }
}

有增加就会有删除,我们需要保证环境的干净,怎么进来的就怎么出去(“童子军规则”)。

def removeCloudConfig(cloudName) {
    def jenkins = Jenkins.get()
    def oldCloud = jenkins.clouds.getByName(cloudName)
    oldCloud.setServerCertificate(null)
    jenkins.clouds.remove(oldCloud)
    println("remove cloud ${cloudName}")
}

创建credential,这里主要介绍增加certificate。其他类型的可以自己参考插件,自己改写。

CertificateCredentialsImpl credentialGenerate(fileUrl, credentialId, password) {
    InputStream is = new URL(fileUrl).openStream()
    byte[] bys = IOUtils.toByteArray(is)
    SecretBytes uploadedKeystore = SecretBytes.fromBytes(bys)
    CertificateCredentialsImpl.UploadedKeyStoreSource storeSource = new CertificateCredentialsImpl.UploadedKeyStoreSource(uploadedKeystore)
    CertificateCredentialsImpl credentials = new CertificateCredentialsImpl(null, credentialId, null, password, storeSource)
    SystemCredentialsProvider.getInstance().getCredentials().add(credentials)
    SystemCredentialsProvider.getInstance().save()
    println('credential added successfully!')
    return credentials
}

删除credential方法:

def removeCredential(CertificateCredentialsImpl credentials) {
    List allCredentials = SystemCredentialsProvider.getInstance().getCredentials()
    List temp = new ArrayList()
    for (Credentials c : allCredentials) {
        if (c instanceof CertificateCredentialsImpl) {
            if (c.equals(credentials)) {
                temp.add(c)
            }
        }
    }
    SystemCredentialsProvider.getInstance().getCredentials().removeAll(temp)
    SystemCredentialsProvider.getInstance().save()
    println('remove credential successfully!')
}

增减方法都有了,下面就是要查找他们所需要的值。我们需要拿到生成的k8s的config中的ca.crt,通过一下方式赋值给cloud配置。

    serverCertificate = sh returnStdout: true, script: """
                sudo cat ca.crt
            """

还有credential的值,credentialId是jenkins为了识别系统中的credential而自动生成的唯一Id,我们可以用UUID来生成。那么它的内容,需要通过前文提到的shell命令

    openssl pkcs12 -passout pass:${certPassword} -export -out cert.pfx -inkey kubernetes.key -in kubernetes.crt -certfile ca.crt

的方式拿到,由于是加密证书文件,直接将他传入File,然后读取bytes,会有乱码产生。这边采用的是ftp中间过度一下,然后stream的方式读取。

    InputStream is = new URL(fileUrl).openStream()

万事具备,我们开始构造pipeline。

timestamps{
    node('test'){
        stage("download tool"){
            //获取页面输入的机器名,下载安装工具
        }
        stage("install k8s"){
            //安装k8s
        }
        stage("config remote cloud and certificate"){
            //编写获取k8s的config,生成cert等
        }
    }
    CertificateCredentialsImpl certificateCredentials = K8sUtil.credentialGenerate(fileUrl, credentialsId, certPassword)
    K8sUtil.createCloud(cloudName, serverUrl, namespace, jenkinsUrl, jenkinsTunnel, credentialsId, serverCertificate)
    podTemplate(cloud: cloudName, name: podName, label: label, yaml: """
    kind: Pod
    metadata:
      name: ${podName}
    spec:
      containers:
      - name: jnlp
        image: ************
        args: []
        tty: true
      volumes:
        - name: dockersock
          hostPath:
            path: /var/run/docker.sock
    """) {
    node(label) {
        stage('run remote dynamic slave') {
            container('jnlp') {
                sh """
                     echo 'success'
                     sleep 10
              """
            }
        }
    }
}
stage("remove cloud && credential") {
    K8sUtil.removeCloudConfig(cloudName)
    K8sUtil.removeCredential(certificateCredentials)
    }

}

上文中的K8sUtil,是把相关操作方法放置在sharedLibrary中。

pipeline.png
result.png

通过这种方式,我们只需要在pipeline中传入需要部署k8s的机器(master和node),就可以达到动态到k8s中启动slave,执行相关任务,最终也会清理相关配置,对于用户无感知,就跟本地启动slave一样。

可能有读者会问,插件里的代码我们如何进行实例定制开发呢?我研究了下,主要可以从它的单元测试入手,去分析它每个类之间的关系,然后有针对性的去实例化类。
总结不易,感觉有用就点个赞吧_

你可能感兴趣的:(Jenkins使用Kubernetes(同/不同集群)启动动态slave的几种方式(kubectl,UI,Script))