實操進階 | Jenkins 和 Kubernetes 雲上的神祕代理
最近我們構建和部署服務的方式與原來相比簡直突飛猛進,像那種笨拙的、單一的、用於構建單體式應用程式的方式已經是過去式了。我們努力了這麼久,終於達到了現在的效果。現在的應用為了提供更好的拓展性和可維護性,都會去拆解成各種相互依賴小、解耦性強的微服務,這些服務有各自的依賴和進度。如果想去構建服務,從一開始,就應該使用 CI/CD 的方式;當然,如果你走上了這條路, Jenkins 就是你的良師益友。
如果你是做微服務,那讓我們在開始之前先花些時間想一想。如果只在 Jenkins 上構建單體式應用程式,那你肯定每天都會執行很多 Jenkins job, 而且要不厭其煩地執行很多次。所以,我們應該好好想清楚如何來做出一些改變。其實只需要付出一些努力,Jenkins 就可以很好地解決這種事情。
Jenkins 進階之路
作為DevOps 從業者,我遇到的最大問題是如何管理並優化自己的 Jenkins agent 結構。如果只是實驗性地用 Jenkins跑一些流水線,那根本不用考慮 agent 的事情。如果每天要跑成百上千條流水線的話,那怎麼去做優化就是一件非常非常重要的事情。在 Jenkins 進階之路中,我也嘗試了各種不同的方式來尋找Jenkins agent 的最佳使用方式。如果你也和我一樣經歷過,那下面這些事情你一定會很熟悉。
下面是我在這些年中使用 Jenkins 的各個階段:
1. 所有構建都在 master 節點上跑,在這個節點上執行所有的元件。(我給這個階段起了個可愛的名字, Hello Jenkins)
- 建立一個 Jenkins EC2 代理,並且在這個代理上執行所有的構建, 就是大而全,這個節點什麼都能做。如果需要同時做多條任務,那就把這個大而全的節點克隆一份。(這個階段我命名為Monster Agent.)
- 為每種服務建立不同的 Jenkins EC2 的節點(這個階段叫Snowflake Agent.) 4. 在容器中執行流水線的所有步驟。 比如,在 Jenkins 中使用 Docker Plugin 外掛將代理掛載到容器中,或者使用 multi-stage Dockerfiles 把所有構建,測試打包的流程都封裝起來。這兩種方法都是很好的容器抽象化的開端,並且允許輕鬆地將製品從一個容器複製到另一個容器。當然了,每種方法都需要訪問 Docker engine 。為了讓我的 Jenkins 代理能夠正常工作,現在我用以下幾種方式來管理 docker host:
* 在Jenkins 主容器中執行一個Docker engine - Docker in Docker (DinD);
* 把主機上的 Docker socket 掛載到容器中,讓容器能夠以 sidecar 的方式執行;
* 為 Jenkins 主伺服器配置單個外部 EC2 Docker 主機,以用於在容器中啟動構建;
* 使用 EC2 外掛和包含 Docker Engine 的 AMI 動態啟動代理,然後執行多階段 Dockerfile 中的所有步驟。
以上這些階段各有利弊,但都是為了讓我們從管理 Jenkins 節點中解放出來。不過,最近我又進階到了另外一個階段:Jenkins onKubernetes 。
一旦在 Jenkins 中把構建節點和 job 都容器化了,遷移工作平臺將變得十分簡單易行。這裡鄭重宣告一下,在使用這個方法前我一直沒有接觸過 Kubernetes。也就是說,在 Google Cloud Platform(GCP)GKE 中建立 Kubernetes 叢集,使用 Helm Chart啟動 Jenkins master ,並在 Kubernetes 叢集中的 Jenkins 代理中執行構建是非常簡單的。
流水線指令碼中啟動 K8s 中的代理
如何配置 Jenkins 才能使流水線指令碼能夠在 K8s 叢集中啟動 Jenkins 節點。首先要先安裝Kubernetes plugin 外掛。有意思的是,當我用 Helm chart 來安裝Jenkins 時,安裝好的 Jenkins 裡面已經有了該外掛。還有一個前提,啟動的 Jenkins 節點要和 Jenkins master 在同一個 K8s 叢集裡。
一旦在 K8s 中運行了 Jenkins master 節點,只需要簡單配置幾步,就能啟動一個小構建。
配置 Jenkins Master
為了保證 Jenkins 能夠訪問 K8s 叢集的資源,首先需要按照以下步驟建立一些憑據:
1. 進入 Jenkins 的 UI 介面,點選左邊導航欄裡的憑據連結
2. 點選 Stores scoped to Jenkins 列表下 global 中的 Add credentials (將滑鼠懸停在連結旁邊即可看到箭頭)
3. 點選新增憑證
4. 寫好 Kubernetes Service Account
5. 將範圍設定為全域性
6. 點選 OK 按鈕
這樣 Jenkins 就可以使用這個憑據去訪問 K8s 的資源。
在 Jenkins Master 中配置雲
下一步就是在 Jenkins 中設定雲的配置:
1. 進入 Jenkins UI 介面,點選 系統管理 → 系統設定;
2. 進入管理介面後查詢 『雲』(一般在下面),然後點選 『新增一個雲』,選擇 kubernetes 型別;
3. 如下這些是必填的引數:
Name 自定義, 預設是 kubernetes;
Kubernetes URLhttps://kubernetes.default -一般是從 service account 自動配置;
Kubernetes Namespace 一般是 default除非要在一個特殊的名稱空間 ,否則不要動;
Credentials 選擇上一步建立的憑據;
Jenkins URLhttp://<your_jenkins_hostnam e>:8080;
Jenkins tunnel <your_jenkins_hostname>:5555 - 用來和 Jenkins 啟動的 agent 進行互動的埠;
你看,只需要幾個引數就能在 K8s 叢集中啟動一些節點了,當然你的環境需要的話也可以做一些其他的調整。
現在你已經可以通過定義一些 pod 實現Jenkins master 訪問 K8s 叢集了。pod其實是 K8s 中的概念,在一個 pod 裡面會有一個或者多個容器,它們共享網路還有儲存,我們可以在這個 pod 中執行一些構建工作。每一個 Jenkins 節點都是作為 K8s pod 來啟動的。這個 pod 裡面經常會包含一個預設的 JNLP 容器,還有一些pod 模板中定義的容器。現在有至少兩種方法來定義pod template。
通過 Jenkins UI 配置一個 pod template
- Manage Jenkins → Configure Systems;
- 找到之前配置 Jenkins K8s 的地方;
- 點選 Add Pod Template button 選擇 Kubernetes Pod Template;
-
輸入下面的值:
Name 自定義;
Namespace default -除非想換個在上一步自定義的名稱空間;
Labels 自定義 - 這個將用來匹配你在 jenkinsfile 中的 label 值;
Usage 如果想讓這個 pod 作為預設節點的話,就選擇 "Use this node as much as possible", 如果選擇 "Only build jobs with label matching expressions matching this node" 的話 那就是隻有在 Jenkins 指令碼中定義的label匹配的構建才能使用這個節點;
The name of the pod template to inherit from這個可以置空,現在還用不到;
Containers 在 pod 中啟動的容器,下面會有詳細介紹;
EnvVars 在 pod 中注入的環境變數;
Volumes 在 pod 中掛載的任何一種卷;
需要記住,在一個 pod 中會有不止一個容器,它們都是共存的。如果你是用 Helm chart 安裝 Jenkins 的話,pod 中就會包含 JNLP 這個容器,這個容器也是 Jenkins agent 中必須包含的。為了完成更多服務的構建,還需要新增一些其他工具鏈的容器。
新增容器模板
- 進入 Jenkins UI 介面,回到上一步建立 pod template ;
- 點選 Add Container 按鈕, 選擇 Container Template;
-
輸入下面的值:
Name 自定義;
Docker image 根據需求來寫,比如在構建一個go 寫的應用時,可以輸入 golang:1.11-alpine3.8;
Label 表明要用在流水線指令碼中引用此容器模板的標籤字串;
Always pull image 如果想讓 pod 啟動的時候都去拉取映象就選擇這個;
你可以保留其他引數的預設值,但是可以看到該外掛可以對 pod 以及在其中執行的各個容器進行詳細控制。你可以通過此外掛設定在Kubernetes pod 配置中的任何值。你還可以通過輸入原始 YAML 來注入配置資料。無需因選項過多而分心,選擇配置它們中的一小部分就可以獲得工作環境。
單擊容器模板中的“新增環境變數”按鈕,將環境變數注入特定容器,也可以單擊模板中的“新增環境變數”按鈕,將環境變數注入所有的容器。
以下環境變數會自動注入預設的 JNLP 容器,來保障它能自動連線到 Jenkins 主伺服器:
JENKINS_URL JENKINS_JNLP_URL JENKINS_SECRET JENKINS_NAME
如果單擊“添加捲”按鈕,將看到幾個用於添加捲的選項,這裡使用 Host Path Volume 選項將 docker socket 安裝在 pod 中。然後,可以執行安裝了 Docker 客戶端的容器,並且來構建和推送 Docker 映象。
此時,我們為 Kubernetes 叢集建立了一個雲配置,並定義了一個由一個或多個容器組成的 pod。現在如何使用它來執行 Jenkins 工作?
很簡單,只需要我們在 Jenkins 流水線指令碼中通過標籤引用 pod 和容器就可以了。
本文中的示例是使用指令碼流水線,當然您可以使用宣告式流水線語法實現相同的結果:
node('test-pod') { stage('Checkout') { checkout scm } stage('Build'){ container('go-agent') { // This is where we build our code. } } }
用 jenkinsfile 來實現相同的功能
通過 UI 配置外掛現在看起來很不錯。但有一個明顯的問題,配置不能像原始碼一樣進行版本控制和儲存。幸運的是,您可以直接在 Jenkinsfile 中建立整個 pod 定義。哈哈,在 Jenkinsfile 中有什麼不能做的呢?
可以將 UI 或 YAML 定義中可用的任何配置引數新增到podTemplate
和containerTemplate
部分。
在下面的示例中,我已經定義了一個包含兩個容器模板的 pod。
pod 標籤將會用於節點,表示想要啟動此 pod 例項。
直接在節點內定義但沒有在容器塊中定義的任何步驟,都可以在預設的 JNLP 容器中執行。
容器塊用於表示該容器塊內的步驟應在具有給定標籤的容器內執行。我已經定義了一個標籤為golang
的容器模板,我將用它來構建 Go 可執行檔案,最終將其打包成 Docker 映象。在volumes
中,已經指出想要掛載主機的 Docker 套接字,但仍然需要 Docker 客戶端使用 Docker API 與它進行互動。因此,已經定義了一個標籤為docker
的容器模板,該模板使用安裝了 Docker 客戶端的映象。
podTemplate( name: 'test-pod', label: 'test-pod', containers: [ containerTemplate(name: 'golang', image: 'golang:1.9.4-alpine3.7'), containerTemplate(name: 'docker', image:'trion/jenkins-docker-client'), ], volumes: [ hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock', ], { //node = the pod label node('test-pod'){ //container = the container label stage('Build'){ container('golang'){ // This is where we build our code. } } stage('Build Docker Image'){ container(‘docker’){ // This is where we build the Docker image } } } })
在基於 Docker 的流水線指令碼中,我構建了 Docker 映象並將其推送到 Docker 倉庫,對我來說,能夠複製這些配置資訊非常重要。完成後,我已準備好使用gcloud
(Google Cloud SDK)構建映象,並將該映象推送到 Google Container Registry,以便部署到 K8s 群集。
使用 gcloud 映象指定了一個容器模板,並將 docker 命令更改為 gcloud 命令。、
就這麼簡單!
podTemplate( name: 'test-pod', label: 'test-pod', containers: [ containerTemplate(name: 'golang', image: 'golang:1.9.4-alpine3.7'), containerTemplate(name: 'gcloud', image:'gcr.io/cloud-builders/gcloud'), ], { //node = the pod label node('test-pod'){ //container = the container label stage('Build'){ container('golang'){ // This is where we build our code. } } stage('Build Docker Image'){ container(‘gcloud’){ //This is where we build and push our Docker image. } } } })
在Kubernetes 上執行 Jenkins master、Jenkins 代理,構建和部署示例應用程式其實只花了幾個小時。但之後,我花了一個週末的時間才深入瞭解該平臺。如果你學得夠快,相信你在幾天內就可以完全掌握並且靈活運用這個平臺了。