這一切都始於我們一個開發團隊的團隊負責人要求我們測試他們的新應用程序,該應用程式在前一天已經容器化。 我發布了。 大約20分鐘後,收到更新應用程式的請求,因為那裡添加了一個非常必要的東西。 我續訂了。 又過了幾個小時……好吧,你可以猜到接下來會發生什麼……
我必須承認,我很懶(我之前不是承認過嗎?沒有?),並且考慮到團隊領導可以訪問 Jenkins,其中我們擁有所有 CI/CD,我想:讓他部署為如他所願! 我想起一個笑話:授人以魚,可食一日;授人以魚,可食一日。 稱一個人為美聯儲,他將終生被聯準會。 然後去了 在工作中耍花招,它將能夠將包含任何成功建置版本的應用程式的容器部署到 Kuber 並向其傳輸任何值 ENV (我的祖父,一位語言學家,過去是一位英語老師,現在讀完這句話後,會用手指轉動太陽穴,非常富有表情地看著我)。
因此,在這篇筆記中我將告訴你我是如何學習的:
- 從作業本身或其他作業動態更新 Jenkins 中的作業;
- 從安裝了Jenkins代理的節點連接到雲端控制台(Cloud shell);
- 將工作負載部署到 Google Kubernetes Engine。
事實上,我當然有些不誠實。 假設您至少擁有 Google 雲端中的部分基礎設施,因此,您是其用戶,當然,您擁有 GCP 帳戶。 但這不是這本筆記的主題。
這是我的下一個備忘單。 我只想在一種情況下寫這樣的筆記:我遇到了一個問題,我最初不知道如何解決它,解決方案沒有在谷歌上找到現成的,所以我在谷歌上部分地搜索它並最終解決了問題。 這樣,將來當我忘記自己是如何做到的時,我就不必再一次又一次地用谷歌搜索所有內容並將其編譯在一起,我自己寫這樣的備忘單。
免責聲明: 1. 這張紙條是「為自己」、為角色而寫的 最佳實踐 不適用。 我很高興看到評論中的“這樣做會更好”選項。
2.如果筆記的應用部分被認為是鹽,那麼,像我之前所有的筆記一樣,這個是弱鹽溶液。
動態更新 Jenkins 中的作業設置
我預見到你的問題:動態工作更新與此有什麼關係? 手動輸入字串參數的值即可開始!
我回答:我真的很懶,我不喜歡他們抱怨:Misha,部署崩潰了,一切都消失了! 您開始查找,發現某些任務啟動參數的值有拼字錯誤。 因此,我更喜歡盡可能有效率地完成所有事情。 如果可以透過提供可供選擇的值列表來阻止使用者直接輸入數據,那麼我會組織選擇。
計劃是這樣的:我們在 Jenkins 中建立一個作業,在啟動之前,我們可以從清單中選擇一個版本,指定傳遞給容器的參數值 ENV,然後它收集容器並將其推送到容器註冊表中。 然後從那裡容器在cuber中啟動,如下所示 工作量 使用作業中指定的參數。
我們不會考慮在 Jenkins 中建立和設定工作的過程,這是題外話。 我們假設任務已準備就緒。 要實現帶有版本的更新列表,我們需要兩件事:一個具有先驗有效版本號的現有來源列表和一個變量,例如 選擇參數 在任務中。 在我們的範例中,將變數命名為 建置_版本,我們就不詳細討論了。 但讓我們仔細看看來源列表。
沒有那麼多選擇。 我立刻想到兩件事:
- 使用 Jenkins 為其用戶提供的遠端存取 API;
- 請求遠端儲存庫資料夾的內容(在我們的例子中是 JFrog Artifactory,這並不重要)。
Jenkins 遠端存取 API
根據既定的優良傳統,我寧願避免冗長的解釋。
我只允許自己免費翻譯第一段的一部分
Jenkins 提供了一個 API,用於遠端機器可讀存取其功能。 <...> 遠端存取以類似 REST 的方式提供。 這意味著所有功能都沒有單一入口點,而是像“.../api/“, 在哪裡 ”..." 表示應用 API 功能的物件。
換句話說,如果我們目前正在討論的部署任務可以在 http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build
,那麼此任務的 API 口哨可在 http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/
接下來,我們可以選擇以什麼形式接收輸出。 讓我們專注於 XML,因為在這種情況下 API 只允許過濾。
讓我們嘗試取得所有作業運行的清單。 我們只對程序集名稱感興趣(顯示名稱)及其結果(導致):
http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]
明白了嗎?
現在讓我們只過濾那些最終得到結果的運行 成功。 讓我們使用論證 &排除 作為參數,我們將向其傳遞一個不等於的值的路徑 成功。 是的是的。 雙重否定是一個陳述。 我們排除所有我們不感興趣的內容:
http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]&exclude=freeStyleProject/allBuild[result!='SUCCESS']
成功列表截圖
好吧,只是為了好玩,讓我們確保過濾器沒有欺騙我們(過濾器從不說謊!)並顯示「不成功」的清單:
http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]&exclude=freeStyleProject/allBuild[result='SUCCESS']
不成功列表截圖
遠端伺服器上資料夾的版本列表
還有第二種取得版本清單的方法。 與存取 Jenkins API 相比,我更喜歡它。 嗯,因為如果應用程式成功構建,則意味著它已打包並放置在存儲庫中的相應資料夾中。 就像,儲存庫是應用程式工作版本的預設儲存。 喜歡。 好吧,讓我們問他存儲中有哪些版本。 我們將curl、grep 和awk 遠端資料夾。 如果有人對 oneliner 感興趣,那麼它就在劇透下面。
一行命令
請注意兩件事:我在標題中傳遞連接詳細信息,並且不需要資料夾中的所有版本,並且我僅選擇一個月內創建的版本。 編輯命令以滿足您的實際情況和需求:
curl -H "X-JFrog-Art-Api:VeryLongAPIKey" -s http://arts.myre.po/artifactory/awesomeapp/ | sed 's/a href=//' | grep "$(date +%b)-$(date +%Y)|$(date +%b --date='-1 month')-$(date +%Y)" | awk '{print $1}' | grep -oP '>K[^/]+' )
在 Jenkins 中設定作業和作業設定檔
我們找出了版本列表的來源。 現在讓我們將結果清單合併到任務中。 對我來說,顯而易見的解決方案是在應用程式建置任務中新增一個步驟。 如果結果為“成功”,則將執行的步驟。
開啟裝配任務設定並捲動到最底部。 單擊按鈕: 新增建置步驟 -> 條件步驟(單一)。 在步驟設定中,選擇條件 目前建置狀態,設定值 成功,如果成功則執行的操作 運行外殼命令.
現在是有趣的部分。 Jenkins 將作業配置儲存在檔案中。 採用 XML 格式。 一路上 http://путь-до-задания/config.xml
因此,您可以下載配置文件,根據需要進行編輯,然後將其放回原來的位置。
請記住,我們在上面同意我們將為版本清單建立一個參數 建置_版本?
讓我們下載設定檔並看看裡面的內容。 只是為了確保參數到位並且是所需的類型。
劇透下的截圖。
您的 config.xml 片段應該看起來相同。 除了choices元素的內容還缺失之外
你確定嗎? 就是這樣,讓我們編寫一個在建置成功時將執行的腳本。
該腳本將接收版本列表,下載配置文件,將版本列表寫入我們需要的位置,然後將其放回去。 是的。 這是正確的。 在已經有版本清單的地方(將來會在第一次啟動腳本之後)編寫 XML 版本清單。 我知道世界上仍然有正規表示式的狂熱粉絲。 我不屬於他們。 請安裝
在劇透下,我展示了完整執行上述序列的程式碼。
將遠端伺服器上資料夾中的版本清單寫入配置
#!/bin/bash
############## Скачиваем конфиг
curl -X GET -u username:apiKey http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_k8s/config.xml -o appConfig.xml
############## Удаляем и заново создаем xml-элемент для списка версий
xmlstarlet ed --inplace -d '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a[@class="string-array"]' appConfig.xml
xmlstarlet ed --inplace --subnode '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]' --type elem -n a appConfig.xml
xmlstarlet ed --inplace --insert '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a' --type attr -n class -v string-array appConfig.xml
############## Читаем в массив список версий из репозитория
readarray -t vers < <( curl -H "X-JFrog-Art-Api:Api:VeryLongAPIKey" -s http://arts.myre.po/artifactory/awesomeapp/ | sed 's/a href=//' | grep "$(date +%b)-$(date +%Y)|$(date +%b --date='-1 month')-$(date +%Y)" | awk '{print $1}' | grep -oP '>K[^/]+' )
############## Пишем массив элемент за элементом в конфиг
printf '%sn' "${vers[@]}" | sort -r |
while IFS= read -r line
do
xmlstarlet ed --inplace --subnode '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a[@class="string-array"]' --type elem -n string -v "$line" appConfig.xml
done
############## Кладем конфиг взад
curl -X POST -u username:apiKey http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_k8s/config.xml --data-binary @appConfig.xml
############## Приводим рабочее место в порядок
rm -f appConfig.xml
如果您更喜歡從 Jenkins 獲取版本的選項,並且您像我一樣懶,那麼劇透下面是相同的程式碼,但來自 Jenkins 的列表:
將 Jenkins 的版本清單寫入配置
請記住這一點:我的程序集名稱由序號和版本號組成,並用冒號分隔。 因此,awk 會切除不必要的部分。 對於您自己,請更改此行以滿足您的需求。
#!/bin/bash
############## Скачиваем конфиг
curl -X GET -u username:apiKey http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_k8s/config.xml -o appConfig.xml
############## Удаляем и заново создаем xml-элемент для списка версий
xmlstarlet ed --inplace -d '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a[@class="string-array"]' appConfig.xml
xmlstarlet ed --inplace --subnode '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]' --type elem -n a appConfig.xml
xmlstarlet ed --inplace --insert '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a' --type attr -n class -v string-array appConfig.xml
############## Пишем в файл список версий из Jenkins
curl -g -X GET -u username:apiKey 'http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]&exclude=freeStyleProject/allBuild[result!=%22SUCCESS%22]&pretty=true' -o builds.xml
############## Читаем в массив список версий из XML
readarray vers < <(xmlstarlet sel -t -v "freeStyleProject/allBuild/displayName" builds.xml | awk -F":" '{print $2}')
############## Пишем массив элемент за элементом в конфиг
printf '%sn' "${vers[@]}" | sort -r |
while IFS= read -r line
do
xmlstarlet ed --inplace --subnode '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a[@class="string-array"]' --type elem -n string -v "$line" appConfig.xml
done
############## Кладем конфиг взад
curl -X POST -u username:apiKey http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_k8s/config.xml --data-binary @appConfig.xml
############## Приводим рабочее место в порядок
rm -f appConfig.xml
理論上,如果您已經測試了根據上面範例編寫的程式碼,那麼在部署任務中您應該已經有一個包含版本的下拉清單。 就像擾流板下的螢幕截圖一樣。
正確完成的版本列表
如果一切正常,則將腳本複製並貼上到 運行外殼命令 並儲存變更。
連接到 Cloud shell
我們有容器中的收集器。 我們使用 Ansible 作為我們的應用程式交付工具和設定管理員。 因此,當涉及到建置容器時,我會想到三個選項:在 Docker 中安裝 Docker、在執行 Ansible 的機器上安裝 Docker,或在雲端控制台中建置容器。 我們同意在本文中對 Jenkins 插件保持沉默。 記住?
我決定:好吧,既然可以在雲端控制台中收集「開箱即用」的容器,那為什麼還要麻煩呢? 保持乾淨,對嗎? 我想在雲端控制台中收集 Jenkins 容器,然後從那裡將它們啟動到立方體中。 此外,谷歌在其基礎設施內擁有非常豐富的管道,這將對部署速度產生有益的影響。
要連接到雲端控制台,您需要做兩件事: 雲 和訪問權 Google Cloud API 對於將與其建立相同連線的 VM 實例。
對於那些根本不打算從 Google 雲端連線的人
谷歌允許在其服務中停用互動式授權。 這將允許您甚至從咖啡機連接到控制台,如果它運行 *nix 並且本身有一個控制台。
如果我需要在本說明的框架內更詳細地討論這個問題,請在評論中寫下。 如果我們獲得足夠的選票,我將撰寫有關此主題的更新。
授予權限最簡單的方法是透過 Web 介面。
- 停止您隨後將連接到雲端控制台的虛擬機器執行個體。
- 打開實例詳細資訊並點擊 修改.
- 在頁面最下方選擇實例存取範圍 對所有雲端 API 的完全存取權限.
截圖
- 儲存變更並啟動實例。
VM 完成載入後,透過 SSH 連接到它並確保連線沒有錯誤。 使用命令:
gcloud alpha cloud-shell ssh
成功的連結看起來像這樣
部署到 GKE
由於我們正在千方百計地努力完全切換到IaC(Infrastucture as a Code),因此我們的docker檔案儲存在Git中。 這是一方面。 而 kubernetes 中的部署是透過 yaml 檔案來描述的,該檔案僅供本次任務使用,本身也像程式碼一樣。 這是從另一邊看的。 總的來說,我的意思是,計劃是這樣的:
- 我們取變數的值 建置_版本 以及(可選)將傳遞的變數的值 ENV.
- 從 Git 下載 dockerfile。
- 產生用於部署的 yaml。
- 我們透過 scp 將這兩個檔案上傳到雲端控制台。
- 我們在那裡建立一個容器並將其推送到容器註冊表中
- 我們將負載部署檔案套用到cuber。
讓我們更具體一些。 一旦我們開始談論 ENV,那麼假設我們需要傳遞兩個參數的值: 參數1 и 參數2。 我們新增他們的部署任務,輸入 - 字串參數.
截圖
我們將透過簡單的重定向產生 yaml 錯過 歸檔。 當然,假設您的 dockerfile 中有 參數1 и 參數2負載名稱將是 很棒的應用程式,而應用指定版本的組裝容器位於 容器註冊表 在途中 gcr.io/awesomeapp/awesomeapp-$BUILD_VERSION哪裡 $BUILD_VERSION 剛從下拉清單中選擇。
團隊列表
touch deploy.yaml
echo "apiVersion: apps/v1" >> deploy.yaml
echo "kind: Deployment" >> deploy.yaml
echo "metadata:" >> deploy.yaml
echo " name: awesomeapp" >> deploy.yaml
echo "spec:" >> deploy.yaml
echo " replicas: 1" >> deploy.yaml
echo " selector:" >> deploy.yaml
echo " matchLabels:" >> deploy.yaml
echo " run: awesomeapp" >> deploy.yaml
echo " template:" >> deploy.yaml
echo " metadata:" >> deploy.yaml
echo " labels:" >> deploy.yaml
echo " run: awesomeapp" >> deploy.yaml
echo " spec:" >> deploy.yaml
echo " containers:" >> deploy.yaml
echo " - name: awesomeapp" >> deploy.yaml
echo " image: gcr.io/awesomeapp/awesomeapp-$BUILD_VERSION:latest" >> deploy.yaml
echo " env:" >> deploy.yaml
echo " - name: PARAM1" >> deploy.yaml
echo " value: $PARAM1" >> deploy.yaml
echo " - name: PARAM2" >> deploy.yaml
echo " value: $PARAM2" >> deploy.yaml
連線後使用 Jenkins 代理 gcloud alpha 雲外殼 ssh 交互模式不可用,所以我們使用參數向雲端控制台發送命令 - 命令.
我們從舊的 dockerfile 中清理雲端控制台中的主資料夾:
gcloud alpha cloud-shell ssh --command="rm -f Dockerfile"
使用 scp 將新下載的 dockerfile 放入雲端控制台的 home 資料夾中:
gcloud alpha cloud-shell scp localhost:./Dockerfile cloudshell:~
我們收集、標記容器並將其推送到容器註冊表:
gcloud alpha cloud-shell ssh --command="docker build -t awesomeapp-$BUILD_VERSION ./ --build-arg BUILD_VERSION=$BUILD_VERSION --no-cache"
gcloud alpha cloud-shell ssh --command="docker tag awesomeapp-$BUILD_VERSION gcr.io/awesomeapp/awesomeapp-$BUILD_VERSION"
gcloud alpha cloud-shell ssh --command="docker push gcr.io/awesomeapp/awesomeapp-$BUILD_VERSION"
我們對部署文件執行相同的操作。 請注意,以下命令使用發生部署的叢集的虛構名稱(awsm 集群)和項目名稱(很棒的項目),集群所在的位置。
gcloud alpha cloud-shell ssh --command="rm -f deploy.yaml"
gcloud alpha cloud-shell scp localhost:./deploy.yaml cloudshell:~
gcloud alpha cloud-shell ssh --command="gcloud container clusters get-credentials awsm-cluster --zone us-central1-c --project awesome-project &&
kubectl apply -f deploy.yaml"
我們運行任務,打開控制台輸出並希望看到容器的成功組裝。
截圖
然後組裝好的容器部署成功
截圖
我故意忽略了這個設置 入口。 原因很簡單:一旦設定完畢 工作量 使用給定名稱,無論您執行多少次使用該名稱的部署,它都將保持運行狀態。 嗯,總的來說,這有點超出了歷史的範圍。
而不是結論
上述所有步驟可能都無法完成,而只是為 Jenkins 安裝了一些插件,即他們的 muuulion。 但由於某些原因我不喜歡插件。 嗯,更準確地說,我只是出於絕望才求助於它們。
我只是喜歡為我挑選一些新話題。 上面的文字也是分享我在解決一開始描述的問題時所取得的發現的一種方式。 與那些像他一樣在 devops 中根本不是可怕的狼的人分享。 如果我的發現至少能幫助某人,我會很高興。
來源: www.habr.com