Kubernetes1.15.2使用jenkins 动态 slave

上一篇文章中已经安装好 jenkins,这次我们来实现动态 slave。

当我们使用动态 slave 时,每启动一个 job 都会创建一个 pod,其中这个 pod 的主容器自然就是 jenkins slave。有主容器自然就有其他容器,什么情况下会出现其他容器呢?

比如我得启动一个 git 容器拉代码,拉下来代码之后需要使用 maven 进行构建,这时就需要启动 maven 容器了。等你构建完了之后还得打成镜像吧,还得上传吧,所以还得需要 docker 容器。

所以这样一来一个 pod 中就有四个容器了,有意思的是,当你拉代码之后,之后的容器都会将代码目录挂载进去,并且默认就在这个目录下面工作。这样一来就十分方便了,多个容器可以如同一个容器般的操作。

但是你无法挂载目录进镜像中,也就是说你打好的 war 包带不出来,你只能打成镜像或者上传到 ftp 等。

说了这么多,相信你对其已经有了大致的了解,那让我们直接开整。

创建云

登录 jenkins 之后,首先需要安装 Kubernetes 这个插件,安装完成之后,我们点击 Manage Jenkins -> Configure System -> Cloud -> add -> Kubernetes。

开始新建一个云:

  • Name:kubernetes,这个名字随意;

  • Kubernetes URLhttps://kubernetes.default

  • Disable https certificate check:勾选;

  • Credentials:Add -> Jenkins

    • Domain:Global credentials;

    • Kind:Kubernetes Service Account;

    • Scope:System;

  • Jenkins URL:如果你启动的容器能够解析你的 jenkins 域名,那么就写域名(可以是 http),否则就写 service。我这里使用 http://jenkins,感觉直接写 service 更好;

  • Jenkins tunnel:和上面一样的道理,我这里使用 jenkins:50000

  • Connection Timeout:0;

  • Read Timeout:0;

  • Concurrency Limit:10,指定并发,应该是同时创建的 slave 的数量。

只需要配置这些,其他默认就好。

创建 job

接着创建第一个 job,一个名为 test 的 pipeline 任务。我们直接来到 Pipeline 这里,你这可以直接将 pipeline 脚本贴在 Script 中,也可以使用 Pipeline script from SCM,将 jenkinsfile 放在 git/svn 上。

我这里选择将 jenkinsfile 放在 git 上,这样的好处是每次你对 jenkinsfile 的修改都会记录下来。

  • Definition:Pipeline script from SCM;

  • SCM:Git;

  • Repositories:

    • Repository URL:我觉得可以使用 http 地址,这样可以输入用户名和密码;

    • Credentials:创建一个 Username with password 类型的 credentials,然后输入你 gitlab 的用户名和密码即可;

    • Branches to build:应该都是 master 吧;

  • Script Path:脚本的名称,你 jenkinsfile 名字是什么这里就写什么。

jenkinsfile

以下是 jenkinsfile 的内容:

def label = "test-${UUID.randomUUID().toString()}"

podTemplate(label: label, yaml: """
apiVersion: v1
kind: Pod
metadata:
  labels:
    jenkins: slave
spec:
  imagePullSecrets:
  - name: nexus-pull
  containers:
  - name: jnlp
    image: registry.ntpstat.com:2222/jenkins/jnlp-slave:3.10-1-alpine
    args:
    - \$(JENKINS_SECRET)
    - \$(JENKINS_NAME)
"""
) {
    node(label) {
        stage('test') {
            echo "test"
        }
    }
}
复制代码

说明:

  • label 就是 slave 的名字,使用一堆随机字符避免重复;

  • podTemplate:关键字,用来定义 pod 中所有需要的镜像;

  • yaml:通过 yaml 关键字可以直接使用 kubernetes 语法;

  • node:通过它来选择 pod,表示下面的 stage 都在 pod 中执行。

由于是从私有镜像仓库中拉镜像,所以这里我定义了一个 imagePullSecrets,相信看了我前面文章的同学都懂。动态 slave 情况下,slave 节点必须要启动,哪怕你觉得它啥也没干,所以这里定义了 slave 这一个容器。

必须传递给 slave 容器两个参数,因为 slave 容器在启动之后必须连接 master,而这两个参数是由 kubernetes 插件自动注入的。事实上,插件会传递多个参数,只不过其他的我们用不上而已。

因为只是测试,所以这里就简单的输出 test,看看是否能够直接输出。

保存之后回到主界面之后,直接点击构建 test 任务,过会就能够看到在左侧出现一个新的 slave,一开始是离线状态。

等会它就消失了,因为构建结束。我们可以直接点击进入任务中查看。

可以看出编译成功。

使用 maven

测试成功之后,我们直接实战。我会首先使用 git 镜像拉取项目代码,然后使用 maven 构建。由于 maven 镜像配置文件是默认的,所以我会使用 configMap 创建一个我线上实现的配置文件,挂载到 Maven 镜像中覆盖其配置文件。

maven 编译过程中会从 nexus 下载依赖包,虽然配置文件中指定了 nexus 的地址,但是如果每次构建下载的依赖包都放在容器中,那么此次构建完毕之后这些下载的依赖包就会被清掉,无法二次利用。所以我们将 nfs 的一个目录直接映射到 maven 镜像的 /root/.m2 目录下。

由于此时用来进行构建实战的项目有些复杂,它会生成五个包,因此这里会有循环的操作。循环生成五个镜像,并发布到私有仓库。

在之后的发布中,我会启动 docker 镜像,但是会将本地的 docker 域套接字映射进去,这样 docker 镜像仓库的操作和宿主机就没什么区别了。

def label = "maven-${UUID.randomUUID().toString()}"
def warDir = "warDir"
def dirPrefix = "hehe_"
def targetDir = "/target"

podTemplate(label: label, yaml: """
apiVersion: v1
kind: Pod
metadata:
  labels:
    jenkins: slave
spec:
  imagePullSecrets:
  - name: nexus-pull
  containers:
  - name: jnlp
    image: registry.ntpstat.com:2222/jenkins/jnlp-slave:3.10-1-alpine
    args:
    - \$(JENKINS_SECRET)
    - \$(JENKINS_NAME)
  - name: git
    image: registry.ntpstat.com:2222/alpine/git
    command:
    - cat
    tty: true
  - name: maven
    image: registry.ntpstat.com:2222/maven:3-alpine
    command:
    - cat
    tty: true
    volumeMounts:
    - name: maven-xml
      mountPath: /usr/share/maven/conf/settings.xml
      readOnly: true
      subPath: settings.xml
    - name: maven-data
      mountPath: /root/.m2
  - name: docker
    image: registry.ntpstat.com/library/docker:18.06
    command:
    - cat
    tty: true
    volumeMounts:
    - name: docker-socket
      mountPath: /var/run/docker.sock
      readOnly: true
  volumes:
  - name: maven-xml
    configMap:
      name: maven-conf
  - name: maven-data
    nfs:
      server: registry.ntpstat.com
      path: /opt/kubernetes/maven-data
  - name: docker-socket
    hostPath:
      path: /var/run/docker.sock
"""
) {
  wars = ['fuckGod-sync', 'fuckGod-job', 'fuckGod-mq', 'fuckGod-main', 'fuckGod-web']
  node(label) {
    stage('拉代码') {
      checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: '19f20e6e-910e-42cd-b395-e4f82c76fd89', url: 'http://git.ntpstat.com/hehe/hehe.git']]])
      container('git') {
        sh "cp Dockerfile /"
        version = sh(script: "git tag | tail -1", returnStdout: true).trim().split('-')[1]
        sh "git checkout version-${version}"
        sh "cp /Dockerfile ."
      }
    }
    stage('maven 编译') {
      container('maven') {
        sh 'mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent package -Dmaven.test.failure.ignore=true'
        wars.each { item ->
          dir(dirPrefix + item + targetDir) {
            sh "mkdir ${warDir}"
            sh "unzip *.war -d ${warDir}"
          }
        }
      }
    }
    stage('打镜像') {
      container('docker') {
        withDockerRegistry(credentialsId: 'nexus-pull', url: 'https://registry.ntpstat.com') {
          wars.each { item ->
            dir(dirPrefix + item + targetDir) {
              imageName = "registry.ntpstat.com/tomcat/${item}:${version}"
              sh "cp ../../Dockerfile ."
              sh "docker build -t ${imageName} ."
              sh "docker push ${imageName}"
            }
          }
        }
      }
    }
  }
}
复制代码

从 gitlab/svn 上面拉代码的语法可以使用 Pipeline Syntax,选择 checkout 自动生成。

为了方便使用,我将 dockerfile 放在了项目的根目录。

我们这里的 git 在上线前会打个 tag,因此我在拉代码之后会切换到这个 tag 进行编译。

Last updated