我们在 GKE 中创建部署任务,无需插件、短信或注册。 让我们看看詹金斯的夹克下面

这一切都始于我们一个开发团队的团队负责人要求我们测试他们的新应用程序,该应用程序在前一天已经容器化。 我发布了。 大约20分钟后,收到更新应用程序的请求,因为那里添加了一个非常必要的东西。 我续订了。 又过了几个小时……好吧,你可以猜到接下来会发生什么……

我必须承认,我很懒(我之前不是承认过吗?没有?),并且考虑到团队领导可以访问 Jenkins,其中我们拥有所有 CI/CD,我想:让他部署为如他所愿! 我想起一个笑话:授人以鱼,可食一日;授人以鱼,可食一日。 称一个人为美联储,他将终生被美联储。 然后去了 在工作中耍花招,它将能够将包含任何成功构建版本的应用程序的容器部署到 Kuber 中并向其传输任何值 ENV (我的祖父,一位语言学家,过去是一位英语老师,现在读完这句话后,会用手指转动太阳穴,非常富有表情地看着我)。

因此,在这篇笔记中我将告诉你我是如何学习的:

  1. 从作业本身或其他作业动态更新 Jenkins 中的作业;
  2. 从安装了Jenkins代理的节点连接到云控制台(Cloud shell);
  3. 将工作负载部署到 Google Kubernetes Engine。


事实上,我当然有些不诚实。 假设您至少拥有 Google 云中的部分基础设施,因此,您是其用户,当然,您拥有 GCP 帐户。 但这不是本笔记的主题。

这是我的下一个备忘单。 我只想在一种情况下写这样的笔记:我遇到了一个问题,一开始我不知道如何解决它,解决方案没有在谷歌上搜索到现成的,所以我在谷歌上部分地搜索它并最终解决了问题。 这样在将来,当我忘记我是如何做到的时,我不必再一次又一次地用谷歌搜索所有内容并将其编译在一起,我自己写这样的备忘单。

免责声明: 1. 这张纸条是“为我自己”、为角色而写的 最佳实践 不适用。 我很高兴看到评论中的“这样做会更好”选项。
2.如果笔记的应用部分被认为是盐,那么,像我之前所有的笔记一样,这个是弱盐溶液。

动态更新 Jenkins 中的作业设置

我预见到你的问题:动态工作更新与此有什么关系? 手动输入字符串参数的值即可开始!

我回答:我真的很懒,我不喜欢他们抱怨:Misha,部署崩溃了,一切都消失了! 您开始查找,发现某些任务启动参数的值存在拼写错误。 因此,我更喜欢尽可能高效地完成所有事情。 如果可以通过提供可供选择的值列表来阻止用户直接输入数据,那么我会组织选择。

计划是这样的:我们在 Jenkins 中创建一个作业,在启动之前,我们可以从列表中选择一个版本,指定传递给容器的参数值 ENV,然后它收集容器并将其推送到容器注册表中。 然后从那里容器在cuber中启动,如下所示 工作量 使用作业中指定的参数。

我们不会考虑在 Jenkins 中创建和设置工作的过程,这是题外话。 我们假设任务已准备就绪。 要实现带有版本的更新列表,我们需要两件事:一个具有先验有效版本号的现有源列表和一个变量,例如 选择参数 在任务中。 在我们的示例中,将变量命名为 构建_版本,我们就不详细讨论了。 但让我们仔细看看源列表。

没有那么多选择。 我立刻想到两件事:

  • 使用 Jenkins 为其用户提供的远程访问 API;
  • 请求远程存储库文件夹的内容(在我们的例子中是 JFrog Artifactory,这并不重要)。

Jenkins 远程访问 API

根据既定的优良传统,我宁愿避免冗长的解释。
我只允许自己免费翻译第一段的一部分 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']

成功列表截图
我们在 GKE 中创建部署任务,无需插件、短信或注册。 让我们看看詹金斯的夹克下面

好吧,只是为了好玩,让我们确保过滤器没有欺骗我们(过滤器从不说谎!)并显示“不成功”的列表:

http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]&exclude=freeStyleProject/allBuild[result='SUCCESS']

不成功列表截图
我们在 GKE 中创建部署任务,无需插件、短信或注册。 让我们看看詹金斯的夹克下面

远程服务器上文件夹的版本列表

还有第二种获取版本列表的方法。 与访问 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元素的内容还缺失之外
我们在 GKE 中创建部署任务,无需插件、短信或注册。 让我们看看詹金斯的夹克下面

你确定吗? 就是这样,让我们​​编写一个构建成功时将执行的脚本。
该脚本将接收版本列表,下载配置文件,将版本列表写入我们需要的位置,然后将其放回去。 是的。 这是正确的。 在已经有版本列表的地方(将来,在第一次启动脚本之后)编写 XML 版本列表。 我知道世界上仍然有正则表达式的狂热粉丝。 我不属于他们。 请安装 XML斯塔勒 到将编辑配置的机器。 在我看来,避免使用 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 '%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

理论上,如果您已经测试了根据上面示例编写的代码,那么在部署任务中您应该已经有一个包含版本的下拉列表。 就像扰流板下的屏幕截图一样。

正确完成的版本列表
我们在 GKE 中创建部署任务,无需插件、短信或注册。 让我们看看詹金斯的夹克下面

如果一切正常,则将脚本复制粘贴到 运行外壳命令 并保存更改。

连接到 Cloud shell

我们有容器中的收集器。 我们使用 Ansible 作为我们的应用程序交付工具和配置管理器。 因此,当谈到构建容器时,我会想到三个选项:在 Docker 中安装 Docker、在运行 Ansible 的机器上安装 Docker,或者在云控制台中构建容器。 我们同意在本文中对 Jenkins 插件保持沉默。 记住?

我决定:好吧,既然可以在云控制台中收集“开箱即用”的容器,那为什么还要麻烦呢? 保持干净,对吗? 我想在云控制台中收集 Jenkins 容器,然后从那里将它们启动到立方体中。 此外,谷歌在其基础设施内拥有非常丰富的渠道,这将对部署速度产生有益的影响。

要连接到云控制台,您需要做两件事: 和访问权 谷歌云API 对于将与其建立相同连接的 VM 实例。

对于那些根本不打算从 Google 云进行连接的人
谷歌允许在其服务中禁用交互式授权。 这将允许您甚至从咖啡机连接到控制台,如果它运行 *nix 并且本身有一个控制台。

如果我需要在本说明的框架内更详细地讨论这个问题,请在评论中写下。 如果我们获得足够的选票,我将撰写有关此主题的更新。

授予权限的最简单方法是通过 Web 界面。

  1. 停止您随后将连接到云控制台的虚拟机实例。
  2. 打开实例详细信息并单击 改变.
  3. 在页面最下方选择实例访问范围 对所有云 API 的完全访问权限.

    截图
    我们在 GKE 中创建部署任务,无需插件、短信或注册。 让我们看看詹金斯的夹克下面

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

VM 完成加载后,通过 SSH 连接到它并确保连接没有错误。 使用命令:

gcloud alpha cloud-shell ssh

成功的连接看起来像这样
我们在 GKE 中创建部署任务,无需插件、短信或注册。 让我们看看詹金斯的夹克下面

部署到 GKE

由于我们正在千方百计地努力完全切换到IaC(Infrastucture as a Code),因此我们的docker文件存储在Git中。 这是一方面。 而 kubernetes 中的部署是通过 yaml 文件来描述的,该文件仅供本次任务使用,其本身也像代码一样。 这是从另一边看的。 总的来说,我的意思是,计划是这样的:

  1. 我们取变量的值 构建_版本 以及(可选)将传递的变量的值 ENV.
  2. 从 Git 下载 dockerfile。
  3. 生成用于部署的 yaml。
  4. 我们通过 scp 将这两个文件上传到云控制台。
  5. 我们在那里构建一个容器并将其推送到容器注册表中
  6. 我们将负载部署文件应用到cuber。

让我们更具体一些。 一旦我们开始谈论 ENV,那么假设我们需要传递两个参数的值: 参数1 и 参数2。 我们添加他们的部署任务,输入 - 字符串参数.

截图
我们在 GKE 中创建部署任务,无需插件、短信或注册。 让我们看看詹金斯的夹克下面

我们将通过简单的重定向生成 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"

我们运行任务,打开控制台输出并希望看到容器的成功组装。

截图
我们在 GKE 中创建部署任务,无需插件、短信或注册。 让我们看看詹金斯的夹克下面

然后组装好的容器部署成功

截图
我们在 GKE 中创建部署任务,无需插件、短信或注册。 让我们看看詹金斯的夹克下面

我故意忽略了这个设置 入口。 原因很简单:一旦设置完毕 工作量 使用给定名称,无论您执行多少次使用该名称的部署,它都将保持运行状态。 嗯,总的来说,这有点超出了历史的范围。

而不是结论

上述所有步骤可能都无法完成,而只是为 Jenkins 安装了一些插件,即他们的 muuulion。 但由于某种原因我不喜欢插件。 嗯,更准确地说,我只是出于绝望才求助于它们。

我只是喜欢为我挑选一些新话题。 上面的文字也是分享我在解决一开始描述的问题时所取得的发现的一种方式。 与那些像他一样在 devops 中根本不是可怕的狼的人分享。 如果我的发现至少能帮助某人,我会很高兴。

来源: habr.com

添加评论