已经猜到接下来会发生什么……我必须承认,我很懒惰(我早些时候承认了吗?不?),而且,鉴于团队负责人可以接触到詹金斯,因此我想,我们拥有所有的CI / CD:让他尽可能地部署自己!我想起了一个笑话:给一个人一条鱼,他会整天吃饱;称一个人为Sated,他将一生被Sated。他去工作了,它能够使用任何成功组装的版本向其部署一个容器,并向其传递任何ENV值(我的祖父,一位语言学家,过去的英语老师,现在将手指转向他的太阳穴,并在阅读完此书后非常有表情地看着我句子)。
因此,在一篇文章中,我将讨论我的学习方法:
- 从作业本身或其他作业动态更新Jenkins中的作业;
- 从安装了Jenkins代理的节点连接到云控制台(Cloud Shell);
- 将工作负载部署到Google Kubernetes Engine。
实际上,我当然有点狡猾。假设您的基础架构中至少有一部分位于google云中,因此您是其用户,并且当然拥有GCP帐户。但是,注意事项并非如此。
这是我的下一个备忘单。我只想在一种情况下写这样的笔记:我面前有一个问题,我最初不知道如何解决,该解决方案不是以最终形式谷歌搜索,所以我以部分谷歌搜索并最终解决了问题。这样一来,将来在我忘了如何做的时候,我不必再将所有内容都逐个谷歌搜索并编译在一起,我便为自己编写了备忘单。
Disclaimer: 1. « », best practice . « » .
2. , , , — .
Jenkins
我预见到您的问题:动态作业更新与它有什么关系?我使用句柄输入了字符串参数的值,然后继续!
答案是:我真的很懒,当人们抱怨时我不喜欢它:Misha,部署崩溃了,一切都消失了!您开始寻找,某些任务启动参数的值中有一个错字。因此,我宁愿尽可能完全地做所有事情。如果可以通过提供一个可供选择的值列表来防止用户直接输入数据,则我组织选择。
计划如下:在Jenkins中创建一个作业,在启动之前,可以从列表中选择一个版本,为通过ENV传递给容器的参数指定值,然后收集容器并将其推送到Container Registry。再往远处,该集装箱在库贝拉发射,工作中指定参数的工作量。
我们不会考虑在詹金斯中创建和配置工作的过程,这是不合时宜的。我们将从任务准备就绪的假设出发。要实现可更新的版本列表,我们需要做两件事:具有先验有效版本号的现有源列表和任务中Choice参数类型的变量。在我们的示例中,将变量命名为BUILD_VERSION,我们将不对其进行详细介绍。但是,让我们仔细看一下源列表。
没有太多选择。我立即想到两个:
- 使用Jenkins提供给用户的远程访问API;
- 查询远程存储库文件夹的内容(在我们的例子中,这是JFrog Artifactory,这并不重要)。
Jenkins远程访问API
按照既定的优良传统,我宁愿避免冗长的解释。
我将只允许自己自由翻译API文档第一页的第一段:
詹金斯(Jenkins)提供了一个API,供远程机器可读访问其功能。<...>远程访问以类似REST的样式提供。这意味着没有指向所有功能的单个入口点,而是使用诸如“ ... / api / ”之类的URL ,其中“ ... ”是应用API功能的对象。换句话说,如果当前正在讨论的部署任务在address处可用
http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build
,则该任务的API信息在
Next处可用,我们可以选择以哪种形式接收输出。让我们来谈谈XML,因为在这种情况下API仅允许过滤。
让我们尝试获取所有作业运行的列表。我们只对程序集名称(displayName)及其结果(result)感兴趣:http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/
http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]
发生了吗
现在,让我们仅过滤那些以SUCCESS结果结尾的启动。我们使用&排除参数并将其路径传递给不等于SUCCESS的值作为参数。是的是的。双重否定是一种说法。我们排除所有我们不感兴趣的东西:
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调用。好吧,因为如果成功构建了应用程序,则它已经打包并放入适当文件夹中的存储库中。就像,存储库默认情况下是应用程序工作版本的存储库。喜欢。好吧,让我们问他存储什么版本。我们将卷曲,grep和awk远程文件夹。如果有人对衬套感兴趣,那么它就在扰流板之下。
一线命令
: , , . :
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中的作业设置和作业配置文件
我们已经处理了版本列表的来源。现在,将结果列表拧入任务。对我来说,显而易见的解决方案是在应用程序构建工作中添加一个步骤。如果结果为“成功”,将执行的步骤。
打开组装任务设置,然后滚动到最底部。单击按钮:添加构建步骤->条件步骤(单个)。在步骤设置中,选择Current build status条件,设置SUCCESS值,如果Run shell命令成功执行要执行的操作。
现在有趣的部分。 Jenkins将作业配置存储在文件中。 XML格式。一路上
http://--/config.xml
因此,您可以下载配置文件,根据需要对其进行编辑,然后将其放置在提取位置。
记住,上面我们同意为版本列表创建BUILD_VERSION参数吗?
让我们下载配置文件并查看其中的内容。只是要确保该参数正确且正确。
剧透下方的屏幕截图。
您的config.xml代码段应该看起来一样。除了尚无选择元素的内容
你说服了吗?好的,我们正在编写一个脚本,该脚本将在构建成功的情况下执行。
该脚本将收到一个版本列表,下载一个配置文件,在需要的位置将版本列表写入其中,然后放回去。是。一切都正确。将XML版本列表写到已经有版本列表的地方(将在第一次启动脚本后发布)。我知道世界上仍然有一些狂热的正则表达式爱好者。我不属于他们。请在将编辑配置的计算机上安装xmlstarler。在我看来,避免使用sed进行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 '%s\n' "${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:
我们写了从詹金斯到配置的版本列表
: , . , 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 '%s\n' "${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
从理论上讲,如果您测试了根据以上示例编写的代码,那么在部署任务中,您应该已经具有一个带有版本的下拉列表。这是扰流板下方的屏幕截图。
正确填写的版本列表
如果一切正常,则将脚本复制并粘贴到“运行shell”命令中,然后保存更改。
云外壳连接
收藏家在我们的容器中。我们使用Ansible作为我们的应用程序交付和配置管理器。因此,在构建容器时,要想到三个选择:在Docker中安装Docker,在具有Ansible的计算机上安装Docker或在云控制台中构建容器。我们已经同意在本文中对Jenkins的插件保持沉默。记得?
我决定:好吧,既然“开箱即用”的容器可以在云控制台中组装,那么为什么要围起菜园呢?保持干净吧?我想在云控制台中使用Jenkins构建容器,然后从那里将它们射入Kuber。此外,Google在基础架构内部拥有丰富的渠道,这将对部署速度产生有益的影响。
连接到云控制台需要两件事:gcloud以及建立此连接的VM实例对Google Cloud API的访问权。
对于那些计划完全不从Google云连接的人
. , *nix' .
, — . — .
, — . — .
授予权限的最简单方法是通过Web界面。
- 停止将来要从中连接到云控制台的VM实例。
- 打开实例详细信息,然后单击编辑。
- 在页面的底部,选择实例访问范围完全访问所有Cloud API。
屏幕截图
- 保存您的更改并启动实例。
VM完成引导后,通过SSH连接到它并确保连接成功。使用命令:
gcloud alpha cloud-shell ssh
成功的连接看起来像这样
部署到GKE
由于我们正在努力以各种方式完全切换到IaC(基础设施即代码),因此我们将dockerfile存储在gita中。这是一方面。yaml文件描述了kubernetes中的部署,该文件仅由该任务使用,它本身也类似于代码。这是另一面。一般来说,我的意思是该计划如下:
- 我们获取BUILD_VERSION变量的值,以及可选的将通过ENV传递的变量的值。
- 从Gita下载dockerfile。
- 生成yaml以进行部署。
- 通过scp将这两个文件上传到云控制台。
- 在此处构建一个容器并将其推送到Container注册表
- 我们将负载部署文件应用于Kuber。
让我们更具体一些。自从我们开始谈论ENV以来,假设我们需要传递两个参数的值:PARAM1和PARAM2。添加他们的部署任务,键入-String Parameter。
屏幕截图
我们将通过简单地将echo重定向到文件来生成yaml 。假设,当然,前提是你有PARAM1和PARAM2在dockerfile,负载名称将是awesomeapp,并与指定版本的应用程序组装容器是在容器注册表沿路径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
使用gcloud alpha cloud-shell ssh连接到Jenkins代理后,交互模式不可用,因此我们使用--command参数将命令发送到云控制台。
我们从旧的dockerfile中清除云控制台中的主文件夹:
gcloud alpha cloud-shell ssh --command="rm -f Dockerfile"
我们使用scp将新下载的dockerfile放入云控制台的主文件夹中:
gcloud alpha cloud-shell scp localhost:./Dockerfile cloudshell:~
我们收集,标记容器并将其推送到Container注册表:
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-cluster),并为集群所在的项目使用名称(awesome-project)。
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"
我们开始任务,打开控制台输出,并希望看到成功的容器构建。
屏幕截图
然后成功部署组装好的容器
屏幕截图
我故意忽略了Ingress设置。出于一个简单的原因:一旦使用给定名称为工作负载配置了它,无论执行多少次此名称的部署,它都将保持运行状态。好吧,总的来说,这超出了历史的范围。
而不是结论
以上所有步骤可能都无法完成,而只是为他们的muuulon Jenkins安装了一些插件。但是我不喜欢插件。好吧,更确切地说,我只是出于绝望而求助于他们。
我只是想为我挑选一些新话题。上面的文本也是分享我的发现的一种方法,可以解决一开始就描述的问题。与那些根本不是凶残的狼分享。如果我的发现至少可以帮助某人,我会很高兴。