這篇文章主要為大家展示了“AWS上云原生Jenkins的示例分析”,內(nèi)容簡(jiǎn)而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“AWS上云原生Jenkins的示例分析”這篇文章吧。
創(chuàng)新新互聯(lián),憑借十多年的做網(wǎng)站、成都網(wǎng)站建設(shè)經(jīng)驗(yàn),本著真心·誠(chéng)心服務(wù)的企業(yè)理念服務(wù)于成都中小企業(yè)設(shè)計(jì)網(wǎng)站有近千家案例。做網(wǎng)站建設(shè),選創(chuàng)新互聯(lián)。
我們使用 Jenkins 搭建持續(xù)交付流水線,和其他很多團(tuán)隊(duì)一樣,這些年我們圍繞 Jenkins 創(chuàng)建了很多工作流程和自動(dòng)化。Jenkins 是我們團(tuán)隊(duì)取得成功的關(guān)鍵,讓我們能夠在上一季度順利進(jìn)入生產(chǎn)677次,搭建及部署時(shí)長(zhǎng)平均為12分鐘。
我們的大部分應(yīng)用和基礎(chǔ)設(shè)施可以看作云原生,但當(dāng)時(shí) Jenkins 服務(wù)并不完全適合這個(gè)分類:服務(wù)在單個(gè)服務(wù)器上運(yùn)行,同時(shí)很多任務(wù)直接在 master 上運(yùn)行,其部分手動(dòng)配置包括 secret、插件、定時(shí)任務(wù)和 startup hacking 的普通膨脹,該膨脹是自2014年首次搭建起不斷累積而成。
Jenkins 不僅變成了單體服務(wù)和單點(diǎn)故障,而且拆除及重建 Jenkins 對(duì)企業(yè)也是很大的風(fēng)險(xiǎn)。
我們決定必須做出改變。這篇博客說(shuō)明了我們?nèi)绾芜\(yùn)用 Terraform、Packer、Docker、Vault、和 ELB、ASG、ALB 或 EFS 等 AWS 服務(wù)實(shí)現(xiàn) Jenkins Cloud-native,以及我們一路走來(lái)的收獲。
當(dāng)時(shí)不得不面對(duì)的關(guān)鍵問(wèn)題是:如果我們將 Jenkins 服務(wù)置于一個(gè)容器/自動(dòng)縮放實(shí)例中,我們需要恢復(fù)何種狀態(tài)?
問(wèn)題的答案并不簡(jiǎn)單,值得一提的是,有個(gè) Jenkins 特別興趣小組(SIG)已經(jīng)識(shí)別出所有導(dǎo)致這一 Jenkins 狀態(tài)的存儲(chǔ)組件。這是一個(gè)很棒的起點(diǎn),因?yàn)槲覀冎辽俚么_保那篇文章列出的所有存儲(chǔ)類型都考慮在內(nèi)。
這不是新問(wèn)題。很多團(tuán)隊(duì)使用 Docker 容器運(yùn)行 Jenkins,官方 Jenkins Docker 鏡像也得到良好維護(hù)。如《Jenkins Dokcer 鏡像》文檔中解釋的:
docker run -p 8080:8080 -p 50000:50000 -v jenkins_home:/var/jenkins_home jenkins/jenkins:lts
這會(huì)把 workspace 存在 /var/jenkins_home。所有的 Jenkins 數(shù)據(jù)(包括插件和配置)都存在上述目錄里。創(chuàng)建一個(gè)明確的 volume 可以方便管理和附加到另一個(gè)容器進(jìn)行升級(jí)。
上述示例裝載主機(jī)上的 jenkins_home,其中包括所有 Jenkins 狀態(tài)。然后該目錄可以存在一個(gè)外部磁盤上,比如 Kubernetes 持久化存儲(chǔ)卷?;蛘?,如果 Jenkins 在 EC2 上運(yùn)行,該目錄可存在一個(gè)外部 EBS 或 EFS 卷上。
這是一種有效的方法,但我們認(rèn)為這個(gè)方法不能達(dá)到我們的標(biāo)準(zhǔn),因?yàn)?jenkins_home 不僅包括狀態(tài),還包括配置。Block storage 擁有大量用戶案例,但一個(gè)小小的配置修改就必須進(jìn)行 snapshot 恢復(fù)操作,這似乎并不算是好的解決方案。此外,我們并不是想轉(zhuǎn)移問(wèn)題:外部存儲(chǔ)無(wú)法免去手動(dòng)配置、憑據(jù)儲(chǔ)存在文件系統(tǒng)等問(wèn)題。
過(guò)去,我們用了 Jenkins 備份插件,該插件基本上把配置修改備份在源碼控制里,允許配置恢復(fù)。這個(gè)插件的設(shè)計(jì)想法很棒,但我們決定不使用它,因?yàn)槲覀儫o(wú)法輕松控制哪些數(shù)據(jù)實(shí)現(xiàn)備份,而且該插件自2011年就沒(méi)有任何更新了。
這樣的話,如果我們把 jenkins_home 創(chuàng)建成個(gè)人 Git repo,并自動(dòng)提交對(duì) Jenkins 所做的修改呢?此處的關(guān)鍵是排除單獨(dú)儲(chǔ)存的任何二進(jìn)制文件、secrets 或大型文件(稍后詳細(xì)介紹)。我們的 .gitignore 文件如下所示:
/.bash_history /.java/ /.kube/ /.ssh/ /.viminfo /identity.key.enc /jobs/ /logs/ /caches/ # Track static worker and exclude ephemeral ones /nodes/** !nodes/static-node/config.xml /org.jenkinsci.plugins.github_branch_source.GitHubSCMProbe.cache/ /plugins/ /saml-idp-metadata.xml /saml-jenkins-keystore.jks /saml-jenkins-keystore.xml /saml-sp-metadata.xml /scm-sync-configuration/ /scm-sync-configuration.success.log /secret.key /secret.key.not-so-secret /secrets/ /updates/ /workspaces/
幾乎所有的純文本配置都正在 Git 實(shí)現(xiàn)持久化。為了給 Jenkins 提供這一配置,我們要做的就是檢查 startup 上的 repo;事情漸漸成形。
Jenkins 要訪問(wèn)很多地方,也就是說(shuō)我們需要一個(gè)安全的 secret 存儲(chǔ)空間。因?yàn)槲覀兪?nbsp;HashiCorpVault 的重度用戶,所以自然而然就選了這個(gè)工具,不過(guò)遺憾的是,Vault 無(wú)法涵蓋所有場(chǎng)景。比如,scm-branch-source 流水線插件需要 SCM 的認(rèn)證憑據(jù),并默認(rèn)為 Jenkins 憑據(jù)插件。每次從 Vault 動(dòng)態(tài)檢索這些,我們都需要同步一個(gè)倉(cāng)庫(kù),這可能導(dǎo)致錯(cuò)誤,也會(huì)需要額外的精力去維護(hù)。
這就是為什么我們采用 Vault 與 Jenkins 憑據(jù)混合的方法: 1. 在 startup 實(shí)例中,Jenkins 進(jìn)行認(rèn)證,VAult采用 IAM 認(rèn)證方法。 2. 一個(gè)引導(dǎo)腳本檢索 Jenkins master.key 和憑據(jù)插件所用的其他加密密鑰。更多詳情請(qǐng)參閱這篇文章。 3. 儲(chǔ)存在 jenkins_home/credentials.xml 上的憑據(jù)現(xiàn)在可由 Jenkins 解密和訪問(wèn)。
用 Vault 完全取代憑據(jù)插件是我們未來(lái)可能探索的問(wèn)題,不過(guò)我們很開(kāi)心這個(gè)方法滿足了安全性要求, 同時(shí)能輕松與 Jenkins 的其余功能實(shí)現(xiàn)集成。
問(wèn)題從這一步開(kāi)始變得棘手:jenkins_home/jobs and jenkins_home/workspaces 都含有介于非結(jié)構(gòu)化數(shù)據(jù)、創(chuàng)建制品和純文本之間的混合體。這個(gè)信息很有價(jià)值,可以幫助我們審計(jì)、理解之前的流水線 build。這些 build 尺寸很大,而且不太適合 SCM 同步,因此這兩個(gè)目錄都排除在 .gitignore 之外了。
那我們把這些儲(chǔ)存在哪兒呢?我們認(rèn)為 block storage 最適合存儲(chǔ)這種數(shù)據(jù)。作為 AWS 的重度用戶,使用 EFS 完全說(shuō)得通,因?yàn)?EFS 的文件存儲(chǔ)可擴(kuò)展、可用性高并可以通過(guò)網(wǎng)絡(luò)訪問(wèn),非常易于使用。我們使用 Terraform 整合了 AWS EFS資源,并用 AWS 備份服務(wù)制定了一份定期備份計(jì)劃。
在 startup,我們將 EFS 卷 、符號(hào)鏈接 jenkins_home/jobs 和 jenkins_home/workspaces 裝載到 EFS 目錄上,然后啟動(dòng) Jenkins 服務(wù)。
接下來(lái),Jenkins 服務(wù)是唯一可以讀寫任務(wù) /workspace 數(shù)據(jù)的界面。值得一提的是,我們有一個(gè) Jenkins 任務(wù)定期刪除幾周前的任務(wù)和 workspace 數(shù)據(jù),這樣數(shù)據(jù)不會(huì)一直增加。
你可能想知道這些是如何湊在一起的?我甚至沒(méi)說(shuō)過(guò)在哪里運(yùn)行 Jenkins!我們廣泛使用 Kubernetes,花了一些時(shí)間思考將 Jenkins 作為容器來(lái)運(yùn)行,可我們決定使用 Packer 和 EC2 來(lái)運(yùn)行 Jenkins master,用短暫 EC2 實(shí)例運(yùn)行這些任務(wù)。
盡管將 master 和 worker 雙雙作為容器運(yùn)行的想法很有用,但我們?cè)诋?dāng)前 Kubernetes 集群里沒(méi)有找到存儲(chǔ) Jenkins 的地方。而且只是為了 Jenkins 就新建一個(gè)集群似乎有點(diǎn)兒“殺雞用牛刀”。此外,我們想保留從其余服務(wù)中解耦的基礎(chǔ)設(shè)施的關(guān)鍵部分。這樣的話,如果 Kubernetes 升級(jí)對(duì)我們的 app 有影響,我們希望至少可以運(yùn)用 Jenkins 進(jìn)行回滾。 運(yùn)行“Docker in Docker”還有另一個(gè)問(wèn)題,這個(gè)問(wèn)題有解,不過(guò)還是需要說(shuō)明一下,因?yàn)槲覀兊?build 經(jīng)常用到 Docker 命令。
其體系架構(gòu)如下:
能使用 EC2 實(shí)例讓過(guò)渡更順暢:我們當(dāng)時(shí)通過(guò) Jenkins EC2 插件用臨時(shí) worker node 運(yùn)行流水線工作,并在聲明式流水線代碼上調(diào)用了這一邏輯,所以不必重構(gòu)就能用 Dokcer 代理節(jié)點(diǎn)是一個(gè)加分項(xiàng)。其余工作就是 Packer 和 Terraform 代碼,這是我們已經(jīng)很熟悉的部分了。
因?yàn)椴寮彩菭顟B(tài)!我們?cè)谶@個(gè)項(xiàng)目里想要解決的問(wèn)題之一就是更好地審計(jì)、管理插件。在手動(dòng)場(chǎng)景中,插件管理可能不受控制,很難了解安裝插件的時(shí)間和原因。
大多數(shù) Jenkins 級(jí)別的插件配置可以在常規(guī) Jenkins 配置 xml 文檔中找到,但安裝插件也導(dǎo)致 jar 制品、元數(shù)據(jù)、圖片和其他文件存在 jenkins_home/plugin 目錄。
一種方法是在 EFS 中存儲(chǔ)插件,不過(guò)我們想將 EFS 使用率保持在最低水平,這無(wú)法解決問(wèn)題,只是轉(zhuǎn)移問(wèn)題。這就是為什么我們選擇對(duì)插件安裝進(jìn)行“Packer 化”。
基本上,在我們的 AMI 定義中,有一個(gè)插件文件羅列了插件和版本,大致如下:
# Datadog Plugin required to send build metrics to Datadog datadog:0.7.1# Slack Plugin required to send build notifications to Slack slack:2.27
然后,我們的 AMI provision 腳本解析該文件,用 Jenkins CLI 安裝插件和所選版本:
# Wrapper function for jenkins_cli jenkins_cli() { java -jar "$JENKINS_CLI_JAR" -http -auth "${user}:${pw}" "$@" }for plugin in "${plugins[@]}"; do echo "Installing $plugin" jenkins_cli install-plugin "$plugin" -deploy done
然后,任何需要安裝的新插件或升級(jí)到當(dāng)前安裝版本的版本升級(jí)都需要 GitHub Pull Request,這會(huì)觸發(fā)搭建新 AMI。完美!
根據(jù)定義,Jenkins 要安裝很多軟件才能創(chuàng)建、測(cè)試和部署。首先,我們不想讓 master node 運(yùn)行任何任務(wù),所以我們避免安裝任何與任務(wù)相關(guān)的軟件。Master 的主要任務(wù)是在其他短暫 worker node 上提供界面、編排 builds。
這意味著我們可以在 worker node 上安裝所需工具,但我們決定盡可能多地使用 docker run。這是因?yàn)槲覀兪鞘褂?Scala、Java、Node、Golang、Python等其他編程語(yǔ)言的多語(yǔ)言組織。為所有這些軟件棧維護(hù)不同 build 工具可能讓 worker node 設(shè)置變得有點(diǎn)兒復(fù)雜。
以 JavaScript 為例,我們想讓 Jenkins 針對(duì) install 和 test 等 app 運(yùn)行 yarn 命令。簡(jiǎn)單將加載檢查過(guò)的 repo 目錄作為一個(gè) volume 安裝到 Docker 容器里,從該容器中運(yùn)行任何命令。以下為運(yùn)用 Groovy 工作流代碼的例子:
def node(command, image) { def nodeCmd = [ 'docker run -i --rm', '-u 1000', // Run as non-root user '-v ~/.npmrc:/home/node/.npmrc:ro', '-v ~/.yarn:/home/node/.yarn', '-e YARN_CACHE_FOLDER=/home/node/.yarn/cache', "-v ${env.WORKSPACE}:/app", '--workdir /app', "${image}" ].join(' ') sh "${nodeCmd} ${command}" }
然后,我們檢查倉(cāng)庫(kù)后可以調(diào)用這個(gè)功能:
checkout scm node('yarn install --frozen-lockfile', 'node:12.6.0-alpine')
漂亮收尾!因?yàn)槌?Docker 后臺(tái)程序或 kubectl,我們不必在 worker machine 上安裝、維護(hù)所用工具的多個(gè)版本。我們也相信 build 命令在本地和 CI 環(huán)境之間是一致的,因?yàn)橛玫氖峭粋€(gè) Docker 鏡像。
運(yùn)用臨時(shí) node 創(chuàng)建時(shí)要記得緩存依賴。比如,一個(gè) worker node 重建后,我們丟失了 sbt 緩存,由于緩存必須重建,這導(dǎo)致創(chuàng)建時(shí)間變慢。如果外部依賴不可用,這甚至?xí)?dǎo)致失敗。我們決定將相關(guān)依賴緩存在另一個(gè)外部 EFS 上,以求獲得更快、更可靠的 build。
Jenkins 是一個(gè)很棒的工具,但在管理外部狀態(tài)上略有不足,因此以 cloud native 的方式創(chuàng)建 Jenkins 較有難度。我們的方法并不完美,但我們相信這個(gè)方法結(jié)合了兩者的精華,而且確保安全性、操作簡(jiǎn)單、有彈性。令人高興的是,我們完成這個(gè)項(xiàng)目,并把所有的生產(chǎn) build 遷移到新的 Jenkins 服務(wù)之后,可以終止 master server,讓自動(dòng)縮放在幾分鐘內(nèi)完成重建,而不會(huì)影響以前儲(chǔ)存的狀態(tài)。
以上是“AWS上云原生Jenkins的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!