亚马逊AWS官方博客

构建 Lambda 和容器环境持续部署流水线

越来越多的企业将其应用架构转向容器化和无服务器化,其中在亚马逊云科技上被广泛使用的容器平台是 Amazon Elastic Kubernetes Service(Amazon EKS),而无服务应用的平台是 Amazon Lambda。企业将其平台做了容器化/无服务器化改造后,一些应用的组件会分别部署到容器平台和无服务器平台,例如前端使用 Amazon Lambda+Amazon API Gateway 或者 Amazon Lambda URL+Amazon Cloudfront,后端使用 Amazon EKS 容器平台,这时面临需要同时管理和维护两套架构,应用部署时如何构建部署流水线发布的问题。

本文介绍如何在 Amazon EKS 上通过 ArgoCD 配合 Github Action,AWS Controllers for Kubernetes(ACK),Kustomize 等组件构建容器和无服务器应用的发布平台。本文已假定读者有一定 Amazon EKS 的使用基础和 CI/CD 的基本概念,文中涉及到的基础工具和组件安装请查阅相应文档,本文不再赘述。

1. 方案介绍

该方案利用 Github Action,Kustomize,ArgoCD 的工具,实现容器资源和 Amazon 云资源的自动部署,最终可以实现开发人员只需要修改其应用代码提交/合并后会触发应用的部署,代码会根据应用的类型(kind)部署对应的资源。

1.1 方案架构

其中主要的集成和发布流程如下:

  1. 开发人员提交应用代码变更,指定文件夹的变更,触发 Github Action 操作
  2. Github Action 完成镜像的构建并推送至 Amazon Elastic Container Registry(ECR)
  3. Github Action 将构建以后 Image 的 Tag 作为变量提供给 Kustomize
  4. Kustomize 组合 kustomization.yaml 和 app yaml,构建最终部署 yaml 文件推送到部署 Repo
  5. ArgoCD 定期执行 Sync 动作,持续监测 Github Repo 特定文件目录的变更
  6. ArgoCD 监测到新的部署代码后通过 K8S API 执行部署动作
  7. 如果类型是 ACK 内定义的资源例,如 Lambda,RDS 等则通过对应的 ACK service controller 部署新版本应用
  8. 如果 EKS 内置资源类型则直接通过 K8S API 部署新版应用

1.2 方案组件

  • Github Action:GitHub Actions 是一个持续集成和持续交付(CI/CD)平台,由 GitHub 官方提供。它允许你直接在 GitHub 仓库中设置工作流程(workflow),自动执行构建、测试和部署等操作。在这个方案里我们主要用到其中的持续集成步骤。
  • Kustomize:Kustomize 是一个用于定制 Kubernetes 配置的工具。它提供了一种声明式的方法来管理 Kubernetes 对象,而无需使用模板。Kustomize 允许用户通过 kustomization.yaml 文件来定义和修改 Kubernetes 资源,从而实现配置的复用和定制。从 Kubernetes 1.14 版本开始,Kustomize 已经内置于 kubectl 中。
  • ArgoCD:Argo CD 是一个用于 Kubernetes 的声明式 GitOps 持续交付工具,自动化应用程序的部署和生命周期管理,可以轻松完成对多个集群应用自动化部署和持续监控。
  • AWS Controllers for Kubernetes(ACK):ACK 允许您直接从 Kubernetes 定义和使用 AWS 服务资源。使用 ACK,您可以为您的 Kubernetes 应用程序利用 AWS 管理的服务,而无需在集群之外定义资源或运行集群内提供数据库或消息队列等支持功能的服务。
  • Lambda Web Adapter:此方案中我们使用了 Amazon Lambda Web Adapter 部署 Lambda 的 Http 应用,借助 Lambda Web Adapter 可以在不改造已有 WEB 应用程序代码的基础上,使用熟悉的框架构建 Web 应用程序,并在 Amazon Lambda 上运行。

2. 部署流程

2.1 GitAction 配置

Git Action workflow 的关键代码部分定义如下:

    - name: Build, tag, and push image to Amazon ECR
      id: build-image  ## 构建镜像并推送
      env:
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        IMAGE_TAG: ${{ github.sha }}
      run: |
        # Build a docker container and
        # push it to ECR so that it can
        # be deployed to ECS.
        docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
        docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
        echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
      working-directory: ./python-app

    - name: Update Kustomize image
      run: | ## 替换变量为构建后的ImageID
        kustomize edit set image ${{ steps.build-image.outputs.image }}
      working-directory: ./Kustomize/base

    - name: Apply Kustomize configuration
      run: | ## 构建最终生产yaml文件
        kustomize build ./base
        mkdir -p ./prod
        kustomize build ./base > ./prod/lambda-app.yml
      working-directory: ./Kustomize/

    - name: Commit and Push changes
      run: | ## 提交到部署仓库
        git config --global user.name "github-actions[bot]"
        git config --global user.email "github-actions[bot]@users.noreply.github.com"
        git add ./prod/lambda-app.yml
        git commit -m "Update Kustomize output"
        git push -u origin main
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      working-directory: ./Kustomize/

在一个应用做了变更以后,我们可以看到 Github Action 中会产生相应的 log,方便我们观察构建过程。

2.2 Kustomize 的安装配置

从 Kubernetes 1.14 版本开始,Kustomize 已经内置于 kubectl 中。在此方案中,我们在 Github Action 通过引入以下代码,使得 Github Action 可以执行 Set 和 Build 的行为。

    - name: Set up Kustomize
      uses: imranismail/setup-kustomize@v2
      with:
        kustomize-version: '4.0.5'

一般情况下,我们可以建立一个如下的仓库目录,通过不同的目录区分部署的阶段(Stage)。实际情况可以根据企业自身情况规划,总体原则是能在不同环境之间复用配置,同时针对特定场景提供额外配置(例如 overlays)。

├── base (基础模板)
│   ├── deploy.yaml
│   ├── ingress.yaml
│   ├── kustomization.yaml
│   ├── nginx.conf
│   ├── kustomizeconfig
│       └──mykind.yaml    
│   └── service.yaml
└── overlays
├── pre (预配置模板)
│   ├── deploy-patch.yaml
│   ├── ing-patch.yaml
│   ├── kustomizeconfig
│       └──mykind.yaml  
│   └── kustomization.yaml
├── prod (生产配置模板)
│   ├── deploy-patch.yaml
│   ├── ing-patch.yaml
│   ├── kustomizeconfig
│       └──mykind.yaml  
    └── kustomization.yaml

在实际使用时,我们只需要定义类似如下的模板文件,即可对模板进行更新。

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../../base
patches:
  - path: patches.yaml

在 workflow 中,对于一般的变量替换,我们使用 kustomize edit set 即可将变量文件写入 kustomization.yaml 文件中,后期使用 kustomization.yaml 配合其他资源文件即可完成最终部署 yaml 文件的构建。

⚠️特别注意:在此方案中的 Lambda 资源由于不是 EKS/K8S 内建资源,属于 Custom Resource Definition(CRD),因此无法直接使用预制的结构,使用 kustomize edit set 进行替换,这时需要声明自定义类型进行转换。

具体需要到 kustomizeconfig 路径下创建 mykind.yaml 文件,并在文件中定义资源的结构。在此方案中具体定义如下:

images:
  - path: spec/code/imageURI
    kind: Function

同时在 kustomization.yaml 文件中需要在结尾配置中声明这个类型:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- function.yaml
configurations:
- kustomizeconfig/mykind.yaml

2.3 ArgoCD 部署和配置

ArgoCD 的安装部署非常简单,可以参考 https://argo-cd.readthedocs.io/en/stable/getting_started/,建议也同步安装好 ArgoCD Cli。

部署完成后可以使用”kubectl port-forward -n argocd service/argocd-server 8080:80″将 ArgoCD 的访问页面映射到本地访问 8080 端口访问,可以使用”kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath=”{.data.password}” | base64 -d” 获取 admin 的密码。

登录完成就可以进入部署应用界面部署应用。

其中在 SyncPolicy 中可以设置是否为自动同步,建议在生产环境加入一个审批过程然后再部署。

在源的配置中可以选择指定的目录和分支。

2.4 AWS Controllers for Kubernetes(ACK)的部署和配置

ACK 在此方案中的主要作用是部署容器环境之外的亚马逊云科技资源,例如 Amazon Lambda,Amazon RDS 数据库等。

其主要原理是利用 ACK 服务控制器(ACK Service controller)对 Amazon 各种云服务进行部署和管理。截止此文发稿,目前 ACK 已经支持 20 类亚马逊云服务。具体服务的支持情况可以参考此链接。ACK 的安装过程可以参考此文档:https://aws-controllers-k8s.github.io/community/docs/user-docs/install/,推荐使用 helm 方式安装。

由于每个服务器控制器部署时需要的权限不同,因此需要根据需要部署和管理的服务不同,分配不同的权限策略。以 ACK Lambda controller 为例,需要在{ack-lambda-controller}角色中增加 AmazonEC2ContainerRegistry 的读取权限,否则 Lambda 无法从镜像中拉取镜像部署。

2.5 Lambda Web Adapter+Cloudfront 的部署

Lambda Web Adapter 非常适合改造已有的 web 业务,只需要在之前容器 Dockerfile 中加入一行代码即可(arm 和 x86 架构不同),例如:

FROM public.ecr.aws/docker/library/node:20-slim
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.3 /lambda-adapter /opt/extensions/lambda-adapter ## <---插入此行即可
ENV PORT=7000
WORKDIR "/var/task"
ADD src/package.json /var/task/package.json
ADD src/package-lock.json /var/task/package-lock.json
RUN npm install --omit=dev
ADD src/ /var/task
CMD ["node", "index.js"]

官方仓库中提供了非常多的示例可以参考,在生产部署是需要注意临时文件的管理,目前 Lambda 只允许写入到/tmp 路径(需要在 Lambda 配置中设定)。

LWA 发布的页面可以通过 Lambda URL 配合 Cloudfront 进行发布,而且配合 Cloudfront 的连接复用也可以达到动态访问加速的效果,如果 Lambda 发布的 web 应用只使用基本的 get 请求,可以通过 Cloudfront Origin Access Control(OAC)进行访问控制,否则需要将 Lambda URL 设置为公开访问,或额外配置验证方法。

如果希望通过 Cloudformation 快速部署 LambdaURL+Cloudfront,可以参考此 Stack 例子

3. 方案流程验证

  1. 提交更新
...
remote: Resolving deltas: 100% (3/3), completed with 3 local objects.
To https://github.com/brilliantwf/lambda-workflow.git
   576ae06..cf6f9c6  main -> main

在 Github Action 中可以看到已触发更新的 workflow。

此时在 ECR 中可以看到,对应的 Image 做了更新,并标记了最新的 tag。

  1. ArgCD 触发同步

此时可以看到 ArgCD 应用的同步状态已变更为 OutOfSync,说明应用代码有变更。

此时点击 SYNC 按钮,等待片刻即可看到应用部署完成。

点击应用详情里,可以看到部署的 manifest。

  1. 通过 Cloudfront 访问即可访问到更新后的页面

容器环境的更新和上述过程类似,只需要更新 Deployment Repo 中对应的目录即可自动触发后续步骤。最终效果类似如下图:

4. 总结

由于 Amazon Lmabda 的无服务器技术的计费是按照 Amazon Lambda 的资源配置和运行时长计费,一般每次请求的调用时间以毫秒计算,相较于容器和 EC2 其计费颗粒度更细,因此使用无服务器化相较于容器化会更进一步降低成本,企业可以根据其业务情况,对 http 业务做无服务器化改造,对于需要长链接和 TCP 支持业务,仍然可以考虑将这些业务部署在容器化环境。本方案提供了一个可以使用一套同时无服务器和容器化的业务进行部署和管理的参考架构,可以降低混合环境的维护复杂度,实际生产环境可以根据情况增加持续集成部分的测试和审核,在容器和环境利用 Kyverno 等组件规范化容器资源策略。

4. 参考资料

  1. 文中提到的相关代码:https://github.com/brilliantwf/lambda-workflow
  2. 使用 Lambda Web Adapter 构建 web 应用:https://aws.amazon.com/cn/blogs/china/building-web-applications-on-lambda-using-the-lambda-web-adapter/
  3. GitOps delivery with Amazon EKS Blueprints and ArgoCD:https://aws.amazon.com/cn/blogs/containers/continuous-deployment-and-gitops-delivery-with-amazon-eks-blueprints-and-argocd/

本篇作者

王非

亚马逊云科技解决方案架构师,负责基于 AWS 云计算方案的架构咨询和设计实现,同时致力于物联网服务的应用以及推广和推进企业服务迁移上云进程。