我们掌握了无需插件,短信和注册即可在GKE中进行部署的任务。用一只眼睛偷看詹金斯的外套

一切始于一个事实,即我们开发团队之一的团队负责人以测试模式要求公开他们的新应用程序,该应用程序已于前一天被打包。我把大约20分钟后,收到了更新应用程序的请求,因为在那里完成了一个非常必要的工作。我更新了。又过了几个小时……好吧,您



已经猜到接下来会发生什么……我必须承认,我很懒惰(我早些时候承认了吗?不?),而且,鉴于团队负责人可以接触到詹金斯,因此我想,我们拥有所有的CI / CD:让他尽可能地部署自己!我想起了一个笑话:给一个人一条鱼,他会整天吃饱;称一个人为Sated,他将一生被Sated。他去工作了,它能够使用任何成功组装的版本向其部署一个容器,并向其传递任何ENV(我的祖父,一位语言学家,过去的英语老师,现在将手指转向他的太阳穴,并在阅读完此书后非常有表情地看着我句子)。



因此,在一篇文章中,我将讨论我的学习方法:



  1. 从作业本身或其他作业动态更新Jenkins中的作业;
  2. 从安装了Jenkins代理的节点连接到云控制台(Cloud Shell);
  3. 将工作负载部署到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界面。



  1. 停止将来要从中连接到云控制台的VM实例。
  2. 打开实例详细信息,然后单击编辑
  3. 在页面的底部,选择实例访问范围完全访问所有Cloud API



    屏幕截图


  4. 保存您的更改并启动实例。


VM完成引导后,通过SSH连接到它并确保连接成功。使用命令:



gcloud alpha cloud-shell ssh


成功的连接看起来像这样




部署到GKE



由于我们正在努力以各种方式完全切换到IaC(基础设施即代码),因此我们将dockerfile存储在gita中。这是一方面。yaml文件描述了kubernetes中的部署,该文件仅由该任务使用,它本身也类似于代码。这是另一面。一般来说,我的意思是该计划如下:



  1. 我们获取BUILD_VERSION变量的值,以及可选的将通过ENV传递的变量的值
  2. 从Gita下载dockerfile。
  3. 生成yaml以进行部署。
  4. 通过scp将这两个文件上传到云控制台。
  5. 在此处构建一个容器并将其推送到Container注册表
  6. 我们将负载部署文件应用于Kuber。


让我们更具体一些。自从我们开始谈论ENV以来,假设我们需要传递两个参数的值:PARAM1PARAM2添加他们的部署任务,键入-String Parameter



屏幕截图




我们将通过简单地将echo重定向到文件来生成yaml 假设,当然,前提是你有PARAM1PARAM2在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安装了一些插件。但是我不喜欢插件。好吧,更确切地说,我只是出于绝望而求助于他们。



我只是想为我挑选一些新话题。上面的文本也是分享我的发现的一种方法,可以解决一开始就描述的问题。与那些根本不是凶残的狼分享。如果我的发现至少可以帮助某人,我会很高兴。



All Articles