这里有一个困扰许多开发团队的常见情况。你通过你的CI/CD管道运行一个应用程序,所有的测试都通过了,这很好。但是,当你把它部署到实际的目标环境中时,应用程序就不能像预期的那样运作。你不能总是预测当你的应用程序被推送到现场时将会发生什么。解决办法是什么?烟雾测试的目的是通过运行涵盖应用程序的关键组件和功能的测试用例,及早发现这些类型的故障。它们还能确保应用程序在部署的情况下按预期运行。当实施时,烟雾测试通常在每个应用程序构建中执行,以验证基本但关键的功能,然后再进入更广泛和耗时的测试。烟雾测试有助于建立快速的反馈循环,这对软件开发的生命周期至关重要。
在这篇文章中,我将演示如何在CI/CD管道的部署阶段添加烟雾测试。烟雾测试将测试应用程序部署后的简单方面。
用于烟雾测试的技术
这篇文章将参考以下技术。
- GitHub
- CircleCI
- Docker
- Kubernetes
- Google Kubernetes Engine (GKE)
- Bash
- smoke.sh - asm89的开源烟雾测试框架
- Pulumi
前提条件
这篇文章依靠的配置和代码在我之前的文章《使用 "基础设施即代码 "自动发布你的管道》中有所体现。完整的源代码可以在这个 repo 中找到。
从烟雾测试中获得最大收益
烟雾测试对于暴露意外的构建错误、连接错误,以及在新版本部署到目标环境后验证服务器的预期响应是非常好的。例如,一个快速、简单的烟雾测试可以验证一个应用程序是否可以访问,并以预期的响应代码进行响应,如OK 200,300,301,404 等。这篇文章中的例子将测试部署的应用是否以OK 200 服务器代码响应,并验证默认页面内容是否呈现出预期的文本。
在没有烟雾测试的情况下运行CI/CD管线
让我们来看看一个管道配置的例子,它被设计为运行单元测试、构建并将Docker镜像推送到Docker Hub。该管道还使用基础设施即代码(Pulumi)来配置一个新的谷歌Kubernetes引擎(GKE)集群,并将该版本部署到该集群。这个管道配置例子没有实现烟雾测试。请注意,如果你运行这个特定的管道示例,将创建一个新的GKE集群,并将继续存在,直到你手动运行pulumi destroy 命令,终止其创建的所有基础设施。
请注意。 不终止基础设施将导致意外的成本。
version: 2.1
orbs:
pulumi: pulumi/pulumi@2.0.0
jobs:
build_test:
docker:
- image: cimg/python:3.8.1
environment:
PIPENV_VENV_IN_PROJECT: 'true'
steps:
- checkout
- run:
name: Install Python Dependencies
command: |
pipenv install --skip-lock
- run:
name: Run Tests
command: |
pipenv run pytest
build_push_image:
docker:
- image: cimg/python:3.8.1
steps:
- checkout
- setup_remote_docker:
docker_layer_caching: false
- run:
name: Build and push Docker image
command: |
pipenv install --skip-lock
pipenv run pip install --upgrade 'setuptools<45.0.0'
pipenv run pyinstaller -F hello_world.py
echo 'export TAG=${CIRCLE_SHA1}' >> $BASH_ENV
echo 'export IMAGE_NAME=orb-pulumi-gcp' >> $BASH_ENV
source $BASH_ENV
docker build -t $DOCKER_LOGIN/$IMAGE_NAME -t $DOCKER_LOGIN/$IMAGE_NAME:$TAG .
echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
docker push $DOCKER_LOGIN/$IMAGE_NAME
deploy_to_gcp:
docker:
- image: cimg/python:3.8.1
environment:
CLOUDSDK_PYTHON: '/usr/bin/python2.7'
GOOGLE_SDK_PATH: '~/google-cloud-sdk/'
steps:
- checkout
- pulumi/login:
version: "2.0.0"
access-token: ${PULUMI_ACCESS_TOKEN}
- run:
name: Install dependencies
command: |
cd ~/
pip install --user -r project/requirements.txt
curl -o gcp-cli.tar.gz https://dl.google.com/dl/cloudsdk/channels/rapid/google-cloud-sdk.tar.gz
tar -xzvf gcp-cli.tar.gz
echo ${GOOGLE_CLOUD_KEYS} | base64 --decode --ignore-garbage > ${HOME}/project/pulumi/gcp/gke/cicd_demo_gcp_creds.json
./google-cloud-sdk/install.sh --quiet
echo 'export PATH=$PATH:~/google-cloud-sdk/bin' >> $BASH_ENV
source $BASH_ENV
gcloud auth activate-service-account --key-file ${HOME}/project/pulumi/gcp/gke/cicd_demo_gcp_creds.json
- pulumi/update:
stack: k8s
working_directory: ${HOME}/project/pulumi/gcp/gke/
workflows:
build_test_deploy:
jobs:
- build_test
- build_push_image
- deploy_to_gcp:
requires:
- build_test
- build_push_image
这个管道将新的应用发布部署到新的GKE集群,但我们不知道在所有这些自动化完成后,应用是否真的启动和运行。我们怎样才能知道应用程序是否已经部署并在这个新的GKE集群中正常运行?烟雾测试是一个很好的方法,可以快速、方便地验证应用程序在部署后的状态。
如何编写烟雾测试?
第一步是开发测试案例,定义验证应用程序功能所需的步骤。确定你要验证的功能,然后创建场景来测试它。在本教程中,我有意描述了一个非常小的测试范围。对于我们的示例项目,我最关心的是验证应用程序在部署后是否可以访问,以及所提供的默认页面是否显示了预期的静态文本。
我更喜欢列出我想测试的项目,因为它适合我的开发风格。大纲显示了我在为这个应用程序开发烟雾测试时考虑的因素。下面是我为这个烟雾测试开发测试用例的一个例子。
- 什么语言/测试框架?
- Bash
- smoke.sh
- 这个测试应该在什么时候执行?
- 在GKE集群被创建后
- 将要测试什么?
- 测试:应用程序部署后是否可以访问?
- 预期的结果。服务器响应代码
200
- 预期的结果。服务器响应代码
- 测试。默认页面是否呈现 "欢迎来到CI/CD "的文字?
- 预期的结果。
TRUE
- 预期的结果。
- 测试。默认页面是否显示文本 "版本号:"
- 预期的结果。
TRUE
- 预期的结果。
- 测试:应用程序部署后是否可以访问?
- 测试后的行动(无论通过或失败都必须发生)
- 将测试结果写到标准输出
- 销毁GKE集群和相关基础设施
- 运行
pulumi destroy
- 运行
我的测试用例大纲(也叫测试脚本)在本教程中是完整的,清楚地显示了我对测试感兴趣的内容。在这篇文章中,我将使用一个基于bash的开源烟雾测试框架编写烟雾测试,该框架名为 smoke.sh由asm89编写。对于你自己的项目,你可以用你喜欢的任何语言或框架来写烟雾测试。我选择smoke.sh ,因为它是一个容易实现的框架,而且是开源的。现在让我们来探讨如何使用smoke.sh 框架来表达这个测试脚本。
使用smoke.sh创建烟雾测试
smoke.sh 框架的文档描述了如何使用它。接下来的示例代码块显示了我是如何使用在示例代码的 repo 的test/ 目录中找到的smoke_test 文件的。
#!/bin/bash
. tests/smoke.sh
TIME_OUT=300
TIME_OUT_COUNT=0
PULUMI_STACK="k8s"
PULUMI_CWD="pulumi/gcp/gke/"
SMOKE_IP=$(pulumi stack --stack $PULUMI_STACK --cwd $PULUMI_CWD output app_endpoint_ip)
SMOKE_URL="http://$SMOKE_IP"
while true
do
STATUS=$(curl -s -o /dev/null -w '%{http_code}' $SMOKE_URL)
if [ $STATUS -eq 200 ]; then
smoke_url_ok $SMOKE_URL
smoke_assert_body "Welcome to CI/CD"
smoke_assert_body "Version Number:"
smoke_report
echo "\n\n"
echo 'Smoke Tests Successfully Completed.'
echo 'Terminating the Kubernetes Cluster in 300 second...'
sleep 300
pulumi destroy --stack $PULUMI_STACK --cwd $PULUMI_CWD --yes
break
elif [[ $TIME_OUT_COUNT -gt $TIME_OUT ]]; then
echo "Process has Timed out! Elapsed Timeout Count.. $TIME_OUT_COUNT"
pulumi destroy --stack $PULUMI_STACK --cwd $PULUMI_CWD --yes
exit 1
else
echo "Checking Status on host $SMOKE... $TIME_OUT_COUNT seconds elapsed"
TIME_OUT_COUNT=$((TIME_OUT_COUNT+10))
fi
sleep 10
done
接下来,我将解释这个smoke_test文件中的内容。
对smoke_test文件的逐行描述
让我们从文件的顶部开始。
#!/bin/bash
. tests/smoke.sh
这个片段指定了要使用的Bash二进制文件,还指定了要导入/包含在smoke_test 脚本中的核心smoke.sh 框架的文件路径。
TIME_OUT=300
TIME_OUT_COUNT=0
PULUMI_STACK="k8s"
PULUMI_CWD="pulumi/gcp/gke/"
SMOKE_IP=$(pulumi stack --stack $PULUMI_STACK --cwd $PULUMI_CWD output app_endpoint_ip)
SMOKE_URL="http://$SMOKE_IP"
这个片段定义了环境变量,这些变量将在整个smoke_test 脚本中使用。下面是每个环境变量及其用途的列表。
PULUMI_STACK="k8s"是Pulumi用来指定Pulumi应用栈的。PULUMI_CWD="pulumi/gcp/gke/"是通往Pulumi基础设施代码的路径。SMOKE_IP=$(pulumi stack --stack $PULUMI_STACK --cwd $PULUMI_CWD output app_endpoint_ip)是Pulumi的命令,用于检索GKE集群上的应用程序的公共IP地址。这个变量在整个脚本中被引用。SMOKE_URL="http://$SMOKE_IP"指定GKE集群上的应用程序的URL端点。
while true
do
STATUS=$(curl -s -o /dev/null -w '%{http_code}' $SMOKE_URL)
if [ $STATUS -eq 200 ]; then
smoke_url_ok $SMOKE_URL
smoke_assert_body "Welcome to CI/CD"
smoke_assert_body "Version Number:"
smoke_report
echo "\n\n"
echo 'Smoke Tests Successfully Completed.'
echo 'Terminating the Kubernetes Cluster in 300 second...'
sleep 300
pulumi destroy --stack $PULUMI_STACK --cwd $PULUMI_CWD --yes
break
elif [[ $TIME_OUT_COUNT -gt $TIME_OUT ]]; then
echo "Process has Timed out! Elapsed Timeout Count.. $TIME_OUT_COUNT"
pulumi destroy --stack $PULUMI_STACK --cwd $PULUMI_CWD --yes
exit 1
else
echo "Checking Status on host $SMOKE... $TIME_OUT_COUNT seconds elapsed"
TIME_OUT_COUNT=$((TIME_OUT_COUNT+10))
fi
sleep 10
done
这个片段是所有魔法发生的地方。这是一个while 循环,一直执行到一个条件为真或脚本退出。在这种情况下,该循环使用一个curl 命令来测试应用程序是否返回一个OK 200 响应代码。因为这个管道是从头开始创建一个全新的GKE集群,所以在我们开始烟雾测试之前,谷歌云平台的一些事务需要完成。
- GKE集群和应用服务必须已经启动并运行。
- 用curl请求的结果填充
$STATUS变量,然后测试200的值。否则,循环将$TIME_OUT_COUNT变量增加10秒,然后等待10秒重复curl请求,直到应用程序有反应。 - 一旦集群和应用程序启动、运行和响应,
STATUS变量将产生一个200响应代码,剩下的测试将继续进行。
smoke_assert_body "Welcome to CI/CD" 和smoke_assert_body "Version Number: " 语句是我测试欢迎和版本号文本是否在被调用的网页上呈现的地方。如果结果为假,测试将失败,这将导致管道失败。如果结果是真的,那么应用程序将返回一个200 响应代码,我们的文本测试将导致TRUE 。我们的烟雾测试将通过并执行pulumi destroy 命令,终止为这个测试案例创建的所有基础设施。由于对这个集群没有进一步的需求,它将终止在这个测试中创建的所有基础设施。
这个循环还有一个elif (else if)语句,检查应用程序是否超过了$TIME_OUT 的值。elif 语句是异常处理的一个例子,它使我们能够控制发生意外结果时的情况。如果$TIME_OUT_COUNT 的值超过了TIME_OUT 的值,那么pulumi destroy 命令就会被执行并终止新创建的基础设施。然后,exit 1 命令使你的管道构建过程失败。 无论测试结果如何,GKE集群都会被终止,因为在测试之外,这个基础设施确实没有存在的必要。
在管道中添加烟雾测试
我已经解释了烟雾测试的例子和我开发测试案例的过程。现在是时候把它集成到上面的CI/CD管道配置中了。我们将在deploy_to_gcp 工作的pulumi/update 步骤下面添加一个新的run 步骤。
...
- run:
name: Run Smoke Test against GKE
command: |
echo 'Initializing Smoke Tests on the GKE Cluster'
./tests/smoke_test
echo "GKE Cluster Tested & Destroyed"
...
这个片段演示了如何将smoke_test 脚本集成并执行到现有的CI/CD管道中。添加这个新的运行块可以确保每个管道的构建都会在一个实时的GKE集群上测试应用程序,并提供应用程序通过所有测试案例的验证。你可以确信,特定的版本在部署到经过测试的目标环境时,会有名义上的表现,在这种情况下,目标环境是谷歌Kubernetes集群。
总结
综上所述,我已经讨论并展示了在CI/CD管道中使用烟雾测试和基础设施即代码来测试目标部署环境中的构建的优势。在目标环境中测试一个应用程序,可以深入了解它在部署时的表现。将烟雾测试集成到CI/CD管道中,为应用程序的构建增加了另一层信心。