客座文章最初由Eficode Praqma云基础设施和DevOps顾问Michael Vittrup Larsen在Eficode Praqma上发表。
https://www.praqma.com/stories/testing-kubernetes-deployments-within-ci-pipelines/
低开销,按需在CI工作节点上使用KIND部署Kubernetes集群
如何使用KIND(Kubernetes in Docker)部署低开销、按需Kubernetes集群在CI流水线中测试诸如Helm chart和YAML清单之类的Kubernetes工件。
容器在打包应用程序方面非常流行,因为它们解决了依赖关系管理问题。打包在容器中的应用程序包括所有必要的运行时依赖项,因此可以跨执行平台移植。换句话说,如果它能在我的机器上工作,它很可能也能在你的机器上工作。
自动化测试在DevOps中是普遍存在的,我们应该将我们的测试打包,就像我们打包我们的应用程序一样:如果某个测试在我的机器上可靠地验证,那么它在你的机器上也应该同样有效,不管你本地安装了哪些库和工具。
测试用的容器
下图演示了一个流水线(或者可能是两个,取决于你组织流水线的方式),上面的部分在容器中构建并打包应用程序,下面的部分对将用于验证应用程序的测试进行相同的操作。只有在基于容器的测试通过时,应用程序才会得到提升。
如果我们假设应用程序是一个网络附加服务,黑盒测试可以通过网络连接执行,像上面这样的设置很容易通过以下方式实现:
如下图所示。
上面列出的步骤2到4也可以用docker-compose定义描述,其中包含两个服务,例如(测试容器通过环境变量配置应用程序网络位置):
version: '3.7'
services:
test:
image: test-container:latest
environment:
APPLICATION_URL: http://application:8080
depends_on:
- application
application:
image: application:latest
ports:
- 8080:8080
使用两个容器的测试现在可以执行:
docker-compose up --exit-code-from test
在CI流水线中测试Kubernetes工件
上面描述的过程对于“容器级别”的测试非常有效。但是,如果CI流水线的输出工件包括Kubernetes工件,例如YAML清单或Helm chart,或者需要部署到Kubernetes集群中进行验证,该怎么办呢?我们如何在这些情况下进行测试?
一种选择是部署一个Kubernetes集群,CI流水线可以部署到这个集群上。然而,这给了我们一些问题需要考虑:
我们还可以根据需要为每个CI作业创建Kubernetes集群。这就要求:
对于某些测试场景,我们需要一个类似于生产的集群,我们将不得不考虑上述解决方案之一,例如特征测试或可伸缩性测试。然而,在许多情况下,我们希望CI流水线执行的测试可以在单个CI工作节点的能力范围内进行管理。下面的部分描述如何在具有容器功能的CI工作节点上创建按需集群。
使用KIND的按需私有Kubernetes集群
Kubernetes-in-Docker(KIND)是使用Docker-in-Docker(DIND)技术实现的Kubernetes集群。Docker-in-docker意味着我们可以在容器内运行容器,而那些内部容器只在外部容器内可见。KIND使用它通过使用外部容器实现Kubernetes集群节点来实现集群。当在节点上启动Kubernetes POD时,它是通过外部节点容器中的容器实现的。
通过KIND,我们可以在CI工作节点的容器功能之上创建按需和多节点的Kubernetes集群。
一个KIND Kubernetes集群
集群容量显然将受到CI工作节点容量的限制,但除此之外,Kubernetes集群将具有生产集群的许多功能,包括HA功能。
让我们演示如何测试用Helm部署到一类集群的应用程序。这个应用程序是k8s-sentence-age应用程序,可以在Github上找到,包括一个Github action,它实现了本博客中描述的CI流水线。该应用程序是一个简单的服务,可以返回0到100之间的随机数(年龄),并提供适当的Prometheus兼容指标。
安装KIND
KIND是一个单独的可执行文件,名为kind,它基本上与CI工作节点上的容器运行时通信。它将使用包含Kubernetes控制平面的容器镜像为集群中的每个节点创建一个(外部)容器。作为Github action的一部分安装kind的例子可以在这里找到。
https://github.com/praqma-training/k8s-sentences-age/blob/master/test/setup-kind.sh
创建一个集群
使用kind工具,我们的CI流水线可以创建一个单一节点Kubernetes集群,使用以下命令:
kind create cluster --wait 5m
如果我们的测试需要多节点集群,我们也可以创建它们。多节点集群需要一个列出节点角色的配置文件:
# config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker
通过上面的配置文件,我们可以用下面的命令创建一个三节点集群:
kind create cluster --config config.yaml
我们可以指定Kubernetes节点应该使用的类型容器镜像,从而控制Kubernetes的版本:
kind create cluster --image "kindest/node:v1.16.4"
有了它,作为CI流水线的一部分,我们可以轻松地测试Kubernetes的多个版本的兼容性。
构建应用程序镜像并使它们供KIND使用
示例k8s-sentences-age应用程序打包在一个名为“age”的容器中,应用程序的测试打包在一个名为“age-test”的容器中。这些容器按照通常的方式建造如下:
docker build -t age:latest ../app
docker build -t age-test:latest .
我们可以通过以下命令将这些镜像的新版本提供给我们的KIND Kubernetes节点:
kind load docker-image age:latest
kind load docker-image age-test:latest
将镜像加载到KIND集群节点将镜像复制到集群中的每个节点。
运行一个测试
我们的流水线将使用它的Helm chart部署应用程序,并针对这个部署的应用程序实例运行测试。
使用应用程序Helm chart部署应用程序意味着,在部署到Kubernetes时,我们不仅要测试应用程序容器,而且还要验证Helm chart本身。Helm chart包含定义应用程序Kubernetes蓝图的YAML清单,这对于验证尤其重要——不仅针对不同版本的Kubernetes,而且在各种配置中,例如Helm chart的值的排列。
我们使用以下Helm命令安装应用程序。请注意,我们覆盖了镜像存储库、标签和pullPolicy的Helm chart默认设置,以便使用本地镜像。
helm install --wait age ../helm/age \
--set image.repository=age \
--set image.tag=latest \
--set image.pullPolicy=Never
测试容器使用Kubernetes Job资源部署。Kubernetes Job资源定义运行到完成并报告完成状态的工作负载。作业将使用我们之前构建的本地“age-test”容器镜像,并使用环境变量中提供的URL连接到应用程序POD。URL引用由Helm chart创建的Kubernetes服务。
apiVersion: batch/v1
kind: Job
metadata:
name: component-test
spec:
template:
metadata:
labels:
type: component-test
spec:
containers:
- name: component-test
image: age-test
imagePullPolicy: Never
env:
- name: SERVICE_URL
value: http://age:8080
- name: METRICS_URL
value: http://age:8080/metrics
restartPolicy: Never
使用以下命令部署作业:
kubectl apply -f k8s-component-test-job.yaml
检查测试结果
在检查结果之前,我们需要等待组件测试工作完成。kubectl工具允许在不同资源上等待各种条件,包括作业完成。例如,我们的流水线将通过以下命令等待测试完成:
kubectl wait --for=condition=complete \
--timeout=1m job/component-test
组件测试作业将测试结果作为其日志的一部分。为了将这些结果作为流水线输出的一部分,我们使用kubectl打印作业的日志,并使用标签选择器选择作业pod。
kubectl logs -l type=component-test
组件测试的总体状态从作业POD字段.status.succeeded读取,并存储在一个SUCCESS变量中,如下所示。如果状态指示失败,流水线终止并提供一个错误:
SUCCESS=$(kubectl get job component-test \
-o jsonpath='{.status.succeeded}')
if [ $SUCCESS != '1' ]; then exit 1; fi
echo "Component test successful"
完整的流水线可以在Github上的k8s-sentences-age库中找到。
这里值得注意的是,helm test的作用是启动测试工作并验证结果。Helm test是将测试正式集成到Helm chart中的一种方式,这样chart的用户就可以在安装chart后运行这些测试。因此,在Helm chart中包含测试,并提供测试容器给Helm chart的用户是很有意义的。要将上面的测试作业包含到Helm chart中,我们只需要添加如下所示的注释,并将YAML文件作为chart的一部分。
...
metadata:
name: component-test
annotations:
"helm.sh/hook": test
当一个KIND集群不够时
在某些情况下,CI工作节点上的本地Kubernetes集群可能不适合你的测试目的。这可能是:
然而,在很多情况下,使用某种Kubernetes集群进行测试是理想的,例如,当你有Kubernetes相关的工件需要测试,如Helm chart或YAML清单,以及外部CI/staging Kubernetes集群涉及太多维护开销或资源效率低的情况下。