jenkins 動態 slave

陳順吉發表於2019-03-27

上一篇文章中已經安裝好 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,一開始是離線狀態。

jenkins 動態 slave

等會它就消失了,因為構建結束。我們可以直接點選進入任務中檢視。

jenkins 動態 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 進行編譯。

其他的也沒啥好說的了,相信你看看就能懂。

相關文章