使用 Kubernetes 构建 CI 作业及处理通用进程——第 1 部分

Blog
Author:
Ori HochOri Hoch
Published On:
7月 22, 2021
Estimated reading time:
2 minutes

Kubernetes,想必大家都不陌生。据 CNCF 2020 年的调查数据,Kubernetes 的发展势不可挡,本文的大部分读者也很可能正在使用,或正在将工作负载转移到 Kubernetes。在运行工作负载,准备、或设置开发环境时, Kubernetes 的优势很明显。另外,它也能帮助其他进程任务和构建作业。对于任何 DevOps 工程师来说,Kubernetes 集群原始、按需配用的计算能力都是工作中不可或缺的部分。

Kubernetes 内容系列由上下两部分组成,主要向大家展示如何在不同用例中使用 Kubernetes。本篇博客内容是的第一部分,第二部分的内容,请点击链接查看。

另外,如果你感兴趣的话,还有一些 Kubernetes 的入门内容:Docker vs Kubernetes ——对立还是统一

我们的目标

在深入了解技术细节之前,先要厘清目标。我们真正想要实现什么?以及为什么?

首先需要了解我们希望运行的进程作业类型。最简单的例子是构建作业,需要为不同的操作系统架构编译代码。通过并行运行每个操作系统架构编译,此类作业的并行化可轻松实现。构建作业的例子可以进一步扩展到 DevOps 工程师可能遇到的其他进程作业,如通用数据进程任务或其他耗时进程。

在我之前的博客中,我写了一些简单的,主要适用于单机并行化的解决方案,以及如何利用 CI 系统的特性进行并行化。这些方法基本能满足大部分的 CI 需求来说,但在面对 CI 系统的上下文时,可能会有所不足。对比之下,Kubernetes 解决方案完全不受限制,而且在功能和计算能力方面更加灵活。因此,Kubernetes 是一个很实用的工具,值得花时间去琢磨,以便在需要时能用上。

在这篇文章中,我将回顾 Kubernetes 的基本构建块——Docker容器和Kubernetes pod。在下一篇博客中,我将使用 Kubernetes Job 对象演示更强大、更丰富的抽象功能。本文的讲解,我先假定大家只对 Linux、Git 和 Bash 脚本有一些基础知识,但并不了解 Docker Kubernetes

运行示例代码

运行代码需要提前完成以下操作,建议你在阅读本文时运行这些命令:

以上所有命令都应该从存储库的根目录运行。因此,在拆分代码存储库之后,需要先进行复制,并在代码存储库根目录下打开一个终端。

To make the code samples easier to run, set the following environment variables in your shell (replace YourGitHub* values with your relevant details):

为了使代码示例更容易运行,请在 shell 中设置以下环境变量(用相关具体信息替换 YourGitHub* 值):

/export GITHUB_TOKEN=YourGitHhubPersonalAccessToken 
export GITHUB_USER=YourGitHubUserName/

同时,设置以下环境变量,让示例代码更加简洁,也更容易处理。

/export BUILDER_IMAGE=ghcr.io/orihoch/k8s-ci-processing-jobs-builder/

基本的工作单元——Docker 映像/容器

Kubernetes 的基本工作单元是 Docker 映像,用于在集群上运行容器。用户需要为要单独为各个类型的作业定义映像,可以是简单的构建作业(如本例),或者是更复杂的测试、集成、部署任务。

我们的示例将使用一个简单的 Go 构建作业,为各操作系统架构构建 Hello-World 二进制文件。注意,虽然我只在示例中选择了 Go 构建作业,但这个作用也可以是 C++ 或任何其他的构建或进程任务。Docker 映像是通过 Dockerfile 设置的,我们可以在这里为任务安装所需的系统依赖项。针对本示例,我使用了 golang 的一个基础映像,其中包含了所有必需的依赖项。然后,添加需要进一步编译的 Golang 源代码和 entrypoint.sh 脚本,该脚本是一个 Bash 脚本,用于处理编译:

# builder/Dockerfile 
FROM golang:1.16 
RUN apt-get update && apt-get install -y jq
COPY main.go src/ 
COPY entrypoint.sh ./ 
ENV GOOS=linux ENV GOARCH=amd64 
ENTRYPOINT ["./entrypoint.sh"]

entrypoint.sh 脚本有两个功能:一是列举可用的操作系统架构,二是构建作业:

/#!/usr/bin/env bash
# builder/entrypoint.sh
if [ "${1}" == "--list" ]; then
exec go tool dist list
else
export GOOS="$(echo "${1}" | cut -d"/" -f1)" &&\
export GOARCH="$(echo "${1}" | cut -d"/" -f2)" &&\
REPO_USER="${2}" &&\
TAG="${3}" &&\
if [ "${GOOS}" == "windows" ]; then EXT=.exe; fi &&\
echo GOOS=$GOOS GOARCH=$GOARCH REPO_USER=$REPO_USER TAG=$TAG EXT=$EXT &&\
UPLOAD_URL="$(curl -s -u "${REPO_USER}:${TOKEN}" -H "Accept: application/vnd.github.v3+json"
"https://api.github.com/repos/${REPO_USER}/k8s-ci-processing-jobs-examples/releases/tags/${TAG}" | jq -r .upload_url | cut -d'{' -f1)" &&\
echo UPLOAD_URL=$UPLOAD_URL &&\
echo Compiling... &&\
go build -o bin/main src/main.go &&\
echo OK &&\
echo Publishing... &&\
curl -s -u "${REPO_USER}:${TOKEN}" -X POST -H "Accept: application/vnd.github.v3+json" \
-H "Content-Type: application/x-executable" --data-binary @bin/main \
"${UPLOAD_URL}?name=hello-world-${GOOS}-${GOARCH}${EXT}" &&\
echo OK
fi/

你可以在  builder/ 目录下查看代码存储库中的所有映像文件。

映像可以在本地运行,进行功能测试,并在部署到 Kubernetes 之前确保脚本是正确的。映像将发布到 GitHub Docker 注册表(你可以点击链接,查看执行这个操作的 CI 脚本)。

运行以下命令,列出生成脚本支持的操作系统架构:

/docker run $BUILDER_IMAGE --list/

docker run 命令根据 $BUILDER_IMAGE (ghcr.io/orihoch/k8s-ci-processing-job -builder) 中定义的映像创建一个容器。-list 参数传递到容器,该容器由映像控制,并列出兼容的操作系统架构(点击链接,可查看进行该项操作的代码)。

要构建和发布二进制文件,首先需要在 GitHub 存储库中进行版本发布(这是你之前拆分的 k8s-ci-processing-jobs-builder 存储库),假设我们发布了一个名为“v0.0.1”的版本。

以下命令将使用不同的操作系统架构,按顺序运行几个构建:

/docker run -e TOKEN=$GITHUB_TOKEN $BUILDER_IMAGE linux/386 $GITHUB_USER v0.0.1 &&\
docker run -e TOKEN=$GITHUB_TOKEN $BUILDER_IMAGE linux/amd64 $GITHUB_USER v0.0.1 &&\
docker run -e TOKEN=$GITHUB_TOKEN $BUILDER_IMAGE darwin/arm64 $GITHUB_USER v0.0.1 &&\
docker run -e TOKEN=$GITHUB_TOKEN $BUILDER_IMAGE windows/arm $GITHUB_USER v0.0.1/

几个参数的具体解释:

  • -e TOKEN=$GITHUB: 我们将一个环境变量传递到包含 GitHub 令牌的容器中,以便构建器脚本可以将已编译的资产添加到发行版中。
  • $BUILDER_IMAGE windows/arm $GITHUB_USER v0.0.1: Builder image 后面是指需要编译的操作系统架构参数,以及需要添加资产的 github 用户和发布名称。

一旦容器完成运行,我们就能在 GitHub 存储库版本中看到已发布的工件。

使用 Kubectl 将容器部署到 Pod

一旦你有了 Docker  映像,你需要找到方法以在集群上运行。Kubernetes 集群中,任何工作负载的最基本构建块都是 pod。利用 kubectl CLI,你可以在集群上快速部署容器。

首先,在 GitHub 存储库上创建一个新版本,假设我们将其命名为“v0.0.2”,这样我们就可以看到已发布的工件。

Kubectl run 命令可以快速将 pod 部署到集群。运行以下命令,在集群上部署单个 pod

/kubectl run builder-linux-386 --restart=Never --env=TOKEN=$GITHUB_TOKEN \
--image=$BUILDER_IMAGE -- linux/386 $GITHUB_USER v0.0.2/

我们可以解释一下这个命令中的参数信息:

  • builder-linux-386要创建的 pod 的名称,名称不能重复,注意在同一个命名空间中不能有两个同名的 pod
  • –restart=NeverKubernetes 默认情况下会主动重启 pod,对于只需要运行一次的构建脚本,我们要禁用该功能。
  • –env=TOKEN=$GITHUB_TOKEN在容器添加一个支持的环境变量
  • –image=$BUILDER_IMAGE需要部署的映像
  • — linux/386 $GITHUB_USER v0.0.2:放在所有参数后,作为参数传递给映像。这些参数与我们在 docker run 命令中使用的参数相同。

运行下列命令,启动另外两个 pod, 进行不同的操作系统架构编译:

/kubectl run builder-linux-amd64 --restart=Never --env=TOKEN=$GITHUB_TOKEN \
--image=$BUILDER_IMAGE -- linux/amd64 $GITHUB_USER v0.0.2/
kubectl run builder-darwin-arm64 --restart=Never --env=TOKEN=$GITHUB_TOKEN \
--image=$BUILDER_IMAGE -- darwin/arm64 $GITHUB_USER v0.0.2

我们可使用下列命令检查创建的 pod 的状态:

/kubectl get pods/

你应该能看到 3 个 pod 并行运行,且状态在一直变化,直到最终的 “完成”状态。另外,你应该能够在 GitHub 上看到发布版本的二进制文件。

需要注意的是,记得在 pod 完成运行后进行删除,及时清理以防止集群出现混乱。可以使用以下命令完成:

当构建中断时会发生什么?工作负载调试

一般来说,构建很少会中断,但保险起见,我们看看如何调试 Kubernetes 中运行的 pod

首先需要部署一个 builder pod,因为它使用的是不兼容的操作系统架构,所以无法正常运行。

/kubectl run builder-linux-quantum --restart=Never --env=TOKEN=$GITHUB_TOKEN \
--image=$BUILDER_IMAGE -- linux/quantum $GITHUB_USER v0.0.2/

检查 pod 状态

/kubectl get pod builder-linux-quantum/

可以看到状态显示为‘错误’,查看 builder job 日志了解更多细节:

/kubectl logs builder-linux-quantum/

这个错误显示,是不支持 linux/quantum 架构(正如预期,目前还不支持编译quantum 计算架构)。

有时日志的信息还不够,我们需要在 pod 上运行一些代码,我们可以覆盖默认入口点,并运行交互式 shell

/kubectl run builder-linux-quantum-shell --restart=Never --env=TOKEN=$GITHUB_TOKEN \
--image=$BUILDER_IMAGE -it --command -- bash/

现在,你应该能看到 pod 内的交互式 shell 会话,接着,运行 pod 入口点脚本进行调试:

/./entrypoint.sh linux/quantum YourGitHubUserName v0.0.2/

记住使用以下命令,及时清理创建的 pod,以防止集群中出现混乱:

/./entrypoint.sh linux/quantum YourGitHubUserName v0.0.2/
kubectl delete pods builder-linux-quantum builder-linux-quantum-shell

总结

在本文中,我们学习了如何将工作负载部署到 Kubernetes。考虑到每个人在 Docker Kubernetes 的技能和经验程度参差不齐,因此大家吸收的内容估计也不一样。如果你发现上述内容有所缺失,可以阅读完整的官方文档进行深入学习。Docker 和 Kubernetes 的文档很齐全,适合不同技能程度的爱好者:

诚然,容器和 pod 这些基本构建块的作用不小,但也有一些局限性。最主要的就是都需要大量的手动操作——每个 pod 都必须亲自启动。因此,如果按照示例运行,当你需要构建44 个兼容的操作系统架构时,你必须启动 44 pod。当然,你也可以使用一些 Bash Python 脚本来完成,但是你还是要跟踪这些作业,并重试失败的作业。除了基础 pod 外,Kubernetes 还提供了几个抽象功能,这些 pod 可以弥补上面提到的不足。在下一篇博客中,我将展示如何使用 Kubernetes job object 扩展我们的示例。

Whitepaper download