硬核干货| 如何利用K3s低成本在流水线中添加测试

作者介绍
Petro Kashlikov,AWS技术客户经理。十分热衷于容器技术,并与客户一起设计、部署和管理他们的工作负载/架构。

现代化的微服务应用程序堆栈、CI/CD流水线、Kubernetes作为编排引擎以及每天成千上百的部署……这些听起来十分美好,直到你发现你的Kubernetes开发或staging环境被这些部署弄得混乱不堪并且一个开发团队所做的更改会影响你的Kubernetes环境。在本文中,我们将了解为什么这些外部更改会影响我们的Kubernetes环境以及如何避免这一影响。

之所以会出现这一问题,是因为在将镜像推送到镜像仓库并部署我们的资源之前,我们在流水线中进行了各种代码检查和镜像扫描。但是,因为流水线内部没有可用的Kubernetes集群,因此在流水线本身中没有进行适当的集成或单元测试。实际上,我们是在部署后测试我们的更改。

面对这个问题,有一个解决方案就是在每次构建、测试更改时配置一个干净的Kubernetes集群,然后再将其清除。但是这十分耗时也不划算。相反,我们可以使用由Rancher Labs推出的开源、轻量级的Kubernetes发行版K3s,与Amazon和AWS CodePipeline一起解决这个问题。

什么是K3s?

K3s是一个开源、轻量的Kubernetes发行版,大小小于100MB,专为物联网、边缘和CI/CD环境设计。启动时间仅需40秒左右。

更有趣的是,对于CI/CD用例,我们可以在Docker容器内运行K3s。Rancher还提供了另一个名为k3d的工具,它是一个轻量级的wrapper,可以在Docker容器内运行K3s。在这种情况下,这个package的大小约为10MB,启动时间更快,约为15-20秒。

现在我们开始了解如何实现这一解决方案。

前期准备

要完成这一教程,我们需要:

  • 一个AWS账号
  • 一个Github账号
  • 安装并配置AWS CLI、kubectl、eksctl功能。你可以根据以下链接指引安装eksctl:

https://docs.aws.amazon.com/eks/latest/userguide/getting-started-eksctl.html

配置Amazon EKS集群

有很多方法可以进行配置,包括使用AWS管理控制台、AWS CLI等。我们推荐使用eksctl,但不管你喜欢什么方式都可以使用,并根据你的喜好修改节点类型和区域。集群配置一般需要15分钟左右。

eksctl create cluster \
--name k3s-lab \
--version 1.16 \
--nodegroup-name k3s-lab-workers \
--node-type t2.medium \
--nodes 2 \
--alb-ingress-access \
--region us-west-2

在本练习中,我们使用 t2.medium 实例系列。如果你在生产环境中启动Amazon EKS集群,请记住使用适当的实例类型。

集群配置完成后,我们使用命令来验证它是否已经启动,以及是否正确配置了 kubectl:

kubectl get nodes

我们的输出应该如下所示:

NAME                             STATUS   ROLES     AGE       VERSION
ip-192-168-12-121.ec2.internal   Ready    <none>    82s       v1.16.8-eks-e16311
ip-192-168-38-246.ec2.internal   Ready    <none>    80s       v1.16.8-eks-e16311

设置AWS CodePipeline

1、 设置ACCOUNT_ID变量:

ACCOUNT_ID=$(aws sts get-caller-identity --output text --query 'Account')

2、 在CodePipeline中,我们使用AWS CodeBuild来部署示例Kubernetes服务。这需要一个能与Amazon EKS集群交互的AWS IAM(身份识别与访问管理)角色,并添加一个内联策略,以便在CodeBuild阶段使用。该策略将允许AWS CodeBuild通过kuebctl与Amazon EKS集群进行交互。执行以下命令来创建角色并附加策略:

TRUST="{ \"Version\": \"2012-10-17\", \"Statement\": [ { \"Effect\": \"Allow\", \"Principal\": { \"AWS\": \"arn:aws:iam::${ACCOUNT_ID}:root\" }, \"Action\": \"sts:AssumeRole\" } ] }"
echo '{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "eks:Describe*", "Resource": "*" } ] }' > /tmp/iam-role-policy
aws iam create-role --role-name EksWorkshopCodeBuildKubectlRole --assume-role-policy-document "$TRUST" --output text --query 'Role.Arn'
aws iam put-role-policy --role-name EksWorkshopCodeBuildKubectlRole --policy-name eks-describe --policy-document file:///tmp/iam-role-policy

3、 既然我们已经创建了IAM角色,那么我们将为Amazon EKS集群添加这一角色到aws-auth ConfigMap。一旦包含此角色,kubectl就可以通过IAM角色与Amazon EKS集群进行交互。

ROLE="    - rolearn: arn:aws:iam::$ACCOUNT_ID:role/EksWorkshopCodeBuildKubectlRole\n      username: build\n      groups:\n        - system:masters"
kubectl get -n kube-system configmap/aws-auth -o yaml | awk "/mapRoles: \|/{print;print \"$ROLE\";next}1" > /tmp/aws-auth-patch.yml 
kubectl patch configmap/aws-auth -n kube-system --patch "$(cat /tmp/aws-auth-patch.yml)"

4、 接下来,我们将fork示例Kubernetes服务以便我们可以修改repo并触发构建。登录到Github并fork示例服务到所选账户。参考Kubernetes服务示例了解更多信息。镜像仓库被fork后,将其克隆到本地环境,这样我们就可以使用我们最喜欢的IDE或文本编辑器来处理文件。

git clone https://github.com/YOUR-USERNAME/eks-workshop-sample-api-service-go.git

5、 为了让CodePipeline能够接收来自GitHub的回调,我们必须生成一个个人的访问令牌。一旦创建,访问令牌将被存储在一个安全的程序集(enclave)中,并被重复使用。只有在第一次运行时,或者需要生成新的密钥时,才需要这一步。

6、 接着,我们将使用AWS CloudFormation创建Codepipeline。导航到AWS管理控制台以创建CloudFormation堆栈。控制台启动后,键入Github用户名、个人访问令牌(在前一个步骤已经创建)以及Amazon EKS集群名称(k3s-lab)。然后,选择确认并点击创建堆栈。这一步大概需要花费10分钟。

创建CodePipeline之后,我们可以在CodePipeline控制台中创建状态并使用以下命令验证部署是否应用到我们的集群中:

kubectl describe deployment hello-k8s

将k3d添加到AWS Codepipeline

现在,我们在fork的repo里修改buidspec.yaml并使用k3d添加单元测试。

我们将逐步介绍所需的修改,这些修改可以手动完成。或者,在本节末尾也为你提供了完整的buildspec.yml文件。

1、 在CodeBuild环境中安装k3d:

- curl -sS https://raw.githubusercontent.com/rancher/k3d/main/install.sh | TAG=v1.7.0 bash

2、 在构建阶段创建k3s集群并等待集群启动,这大概需要花费20秒:

- k3d create
- sleep 20

3、 为k3s集群配置kubectl

- export KUBECONFIG="$(k3d get-kubeconfig --name='k3s-default')"

4、 默认情况下,Amazon EKS 集群节点被 eksctl 配置为可以访问从 Amazon Elastic Container Registry (Amazon ECR) 镜像库中拉取镜像。然而,非Amazon EKS 集群需要为此进行额外配置。在文档中找到这个配置的说明。因为有几个步骤,我把它们移到一个单独的脚本(create_secret.sh)中,并在buildspec.yml文件里面调用:

- ./create_secret.sh

将文件create_secret.sh添加到forked repository的工作文件夹中,其上下文如下:

SECRET_NAME=$AWS_REGION-ecr-registry
 TOKEN=`aws ecr get-authorization-token --output text --query authorizationData[].authorizationToken | base64 -d | cut -d: -f2`
 echo "ENV variables setup done."
 kubectl create secret docker-registry $SECRET_NAME \
 --docker-server=https://$REPOSITORY_URI \
 --docker-username=AWS \
 --docker-password="${TOKEN}" \
 --docker-email=DUMMY_DOCKER_EMAIL
 kubectl patch serviceaccount default -p '{"imagePullSecrets":[{"name":"'$SECRET_NAME'"}]}'

5、 将我们的流水线应用程序资源到k3d集群并等待资源启动,这大概需要花费20秒:

- kubectl apply -f hello-k8s.yml
- sleep 20

配置测试

在这一步中,我们可以运行我们的单元/集成测试。对于本例,我们已经提供了一个简单的脚本来hit我们服务的endpoint。我们可以从我们的堆栈中部署其他必要的微服务或服务进行集成测试。

- ./unit_test.sh

将文件 unit_test.sh 添加到 forked repository 的工作文件夹中,其上下文如下:

#!/bin/sh
set -e
api_host=$(kubectl get svc hello-k8s -o json | jq -r .status.loadBalancer.ingress[].ip)
curl -m 2 $api_host

检查测试是否成功

最后一步是检查测试是否成功并且将我们的应用程序部署到Amazon EKS集群。如果测试失败,我们的Codepipeline就会失败,不会部署到Amazon EKS。CodeBuild有个内置的变量CODEBUILD_BUILD_SUCCEEDING来指示构建阶段的状态。我们会在代码中使用它:

- bash -c "if [ /"$CODEBUILD_BUILD_SUCCEEDING/" == /"0/" ]; then exit 1; fi" 
- echo Build stage successfully completed on `date`

buildspec.yml

---
version: 0.2
phases:
  install:
    commands:
      - curl -sS -o aws-iam-authenticator https://amazon-eks.s3-us-west-2.amazonaws.com/1.10.3/2018-07-26/bin/linux/amd64/aws-iam-authenticator
      - curl -sS -o kubectl https://amazon-eks.s3-us-west-2.amazonaws.com/1.14.6/2019-08-22/bin/linux/amd64/kubectl
      - curl -sS https://raw.githubusercontent.com/rancher/k3d/main/install.sh | TAG=v1.7.0 bash
      - chmod +x ./kubectl ./aws-iam-authenticator
      - export PATH=$PWD/:$PATH
      - apt-get update && apt-get -y install jq python3-pip python3-dev && pip3 install --upgrade awscli
  pre_build:
      commands:
        - TAG="$REPOSITORY_NAME.$REPOSITORY_BRANCH.$ENVIRONMENT_NAME.$(date +%Y-%m-%d.%H.%M.%S).$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | head -c 8)"
        - sed -i 's@CONTAINER_IMAGE@'"$REPOSITORY_URI:$TAG"'@' hello-k8s.yml
        - $(aws ecr get-login --no-include-email)
  build:
    commands:
      - docker build --tag $REPOSITORY_URI:$TAG .
      - docker push $REPOSITORY_URI:$TAG
      # Creating k3d cluster
      - k3d create
      # Waiting for cluster creation for 20 seconds
      - sleep 20
      # Configuring kubectl for k3d cluster
      - export KUBECONFIG="$(k3d get-kubeconfig --name='k3s-default')"
      # Creating secret as per https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-image-pull-secret-to-service-account
      # to enable k3d cluster pull images from ECR
      - ./create_secret.sh
      # Applying our service and deployment manifest
      - kubectl apply -f hello-k8s.yml
      # Waiting for pods and service to come up
      - sleep 20
      # Running unit test
      - ./unit_test.sh
  post_build:
    commands:
      # Checking if build phase including unit test completed successfully, if not we don't proceed with deployment
      - bash -c "if [ /"$CODEBUILD_BUILD_SUCCEEDING/" == /"0/" ]; then exit 1; fi"
      - echo Build stage successfully completed on `date`
      - CREDENTIALS=$(aws sts assume-role --role-arn $EKS_KUBECTL_ROLE_ARN --role-session-name codebuild-kubectl --duration-seconds 900)
      - export KUBECONFIG=$HOME/.kube/config
      - export AWS_ACCESS_KEY_ID="$(echo ${CREDENTIALS} | jq -r '.Credentials.AccessKeyId')"
      - export AWS_SECRET_ACCESS_KEY="$(echo ${CREDENTIALS} | jq -r '.Credentials.SecretAccessKey')"
      - export AWS_SESSION_TOKEN="$(echo ${CREDENTIALS} | jq -r '.Credentials.SessionToken')"
      - export AWS_EXPIRATION=$(echo ${CREDENTIALS} | jq -r '.Credentials.Expiration')
      - aws eks update-kubeconfig --name $EKS_CLUSTER_NAME
      - kubectl apply -f hello-k8s.yml
      - printf '[{"name":"hello-k8s","imageUri":"%s"}]' $REPOSITORY_URI:$TAG > build.json
artifacts:
  files: build.json

所有更改都完成并且新文件已经在我们本地forked repository之后,我们需要提交这些更改,这样CodePipeline可以接收它们,并将它们应用到我们的流水线中。

git add .
git commit -m "k3d modified pipeline"
git push

当我们推送这些更改之后,我们可以访问CodePipeline控制台并检查流水线的状态和日志。

图片

在Build部分选择Details。在这里,我们可以在Build Logs下检查我们的流水线运行过程中发生了什么。

图片

清 理

为了避免产生多余的费用,我们需要进行一些清理步骤:

1、 删除为CodePipeline创建的CloudFormation堆栈。打开CloudFormation管理控制台,选择eksws-codeepipeline堆栈旁边的方框,选择删除,然后在弹出的窗口中确认删除。

图片

2、 删除Amazon ECR repository。打开Amazon ECR管理控制台,选择以eksws开头的repository名称旁边的方框。选择 "Delete",然后确认删除。

图片

3、清空并删除 CodeBuild 用于构建工件的 Amazon S3 bucket。bucket 名称以 eksws-codepipeline 开头。

选择 bucket,然后选择 Empty。选择Delete来删除该 bucket。

图片

4、 最后,使用以下命令删除Amazon EKS集群:

eksctl delete cluster --name=k3s-lab

总 结

在本文中,我们探讨了如何使用开源、轻量级的Kubernetes发行版K3s,将单元和集成测试添加到Amazon EKS CI/CD流水线中。如果你在Amazon EKS部署中使用了不同的CI/CD工具,你也可以轻松地将K3s纳入其中。

原文链接:
https://aws.amazon.com/cn/blogs/opensource/using-the-k3s-kubernetes-distribution-in-an-amazon-eks-ci-cd-pipeline/

posted @ 2021-05-08 21:20  k3s中文社区  阅读(232)  评论(0编辑  收藏  举报