亚马逊AWS官方博客

使用Terraform与事件驱动的AWS CodeBuild提升云上数据应用运维效率

背景信息

企业客户在云上部署的一系列数据应用的过程中,数据开发团队往往负责脚本内容,而其背后一系列云上资源的管理通常由一支云运维职能团队通过IaC(Infrastructre as Code)实现。然而,当数据开发团队开发及部署相应脚本内容时,不可避免会涉及到云上资源的变动,如Glue、Lambda的资源增改等。这就造成了两个团队在职能边界上的紧耦合:数据开发团队的迭代内容都需要提报需求至云运维团队进行相应IaC的运维,双方都增加了工作量。

优化方案概述

为了减轻数据应用代码增改给双方带来的额外压力,本文通过一个案例入手,优化数据应用增改及部署过程中的关键流程:数据开发团队通过接口化的形式调用相应Terraform module,配合AWS CodePipeline或EventBridge驱动的事件模式来实现CI/CD流水线。

此案例中,云运维团队负责IaC模块的部署和运维,使用Terraform Cloud Workspace进行IaC代码发布与管理。数据开发团队负责针对具体的ETL任务场景开发Glue脚本,使用CodeCommit进行代码管理,使用CodeBuild实现CI/CD内容,最后通过CodePipeline或EventBridge实现CI/CD流水线的串联。两支团队配合实现以下场景:

某企业hr部门需要将某数据源摄取至MySQL中供下游数据应用使用。在数据工程师完成Glue脚本开发后,使用云运维团队开发的Glue模版批量新建Glue脚本(Python shell模版)资源。在后续数据工程师新建或修改Glue脚本时,这一套流水线能自动捕获CodeCommit中的变更内容,并同步内容至s3。s3的变更将会直接反应至Terraform中触发新建/更新资源的功能,不需要IaC开发/云运维团队的介入。”

下文的优化方案将会清晰定义云运维团队和数据开发团队在开发、维护云上数据应用时的责任边界。

实施步骤

(1) 统一流程与规范

数据开发团队与云运维团队间确认关键流程及步骤,包括CI/CD流水线如何实现,Glue脚本的上传方式和存储位置,资源所需的配置信息(如实例类型,所需的IAM权限,网络)等。

(2) Terraform脚本开发

云运维团队负责Glue资源的IaC脚本开发,内容包括:配置参数,新增/变更资源的代码。开发好的内容会统一放置在glue-etl目录下。此目录中内容示例如下:

|____glue-etl
| |____output.tf
| |____data.tf
| |____main.tf
| |____Readme.md
| |____policy.tf
| |____variables.tf

云运维团队对glue-etl模块进行封装(module)以及发布至Terraform Cloud相应的Workspace内。

上述glue-etl模块中包含以下内容:

  • tf中包含了此module输出的一系列参数。
  • tf包含了对AWS环境中已有的一些资源的引用,如:当前所在区域,当前用户信息,Glue脚本所需要访问的数据库所在的Secret Manager密钥串,和部署Glue资源所需的子网组等必要信息。
  • tf中包含了Glue执行时所需的IAM角色对应的相关IAM Policy集合。
  • tf中包含了需要用户调用此module需要传入的一系列配置参数。
  • tf文件中包含了根据上述配置批量创建Glue资源的主要流程。使用for_each和count关键词,可以实现批量新建单规格的Glue Connection、Glue作业以及Glue Crawler、Catalog等。

由于篇幅问题,上述.tf具体代码内容已略去。

(3) 监听s3内容变更

在云运维团队完成glue-etl module的开发与上传至Terraform Workspace后,数据开发团队需要初始化一份.tf文件,使用local关键词将脚本上传路径(如下方代码块中的变量bucket_name, job_path_prefix和line_of_business中)添加到.tf文件中。

locals {
  bucket_name = "sample-bucket-glueetl"
  job_path_prefix = toset(["hr-mysql-source1-python-scripts"])
  line_of_business = "hr-department"
}

第二步,通过Terraform提供的data.aws_s3_bucket_objects获取Glue脚本在s3上的存放路径。

data "aws_s3_bucket_objects" "glue_job_objects_for_people_mdm_staging" {
  for_each = local.job_path_prefix
  bucket   = local.bucket_name
  prefix   = "${local.line_of_business}/${each.key}"
}

下一步,配置Glue module所需的输入参数。以下示例中展现了如何通过字符串操作将Glue作业名与上传的脚本名进行对应(映射规则可以自定,本例中以.py文件前缀作为Glue作业名,见Figure 8),并放入job-name-map的local变量中。在实际应用中,您有可能需要配置不止一个local变量作为module的输入参数。

locals {
  job_name_map  = { 
for job_prefix in 
[for job_name in 
[for py_name in data.aws_s3_bucket_objects.glue_job_objects_for_people_mdm_staging["hr-mysql-source1-python-scripts"].keys : split("/", py_name)[2]
] : split(".", job_name)[0]
] : job_prefix => "${job_prefix}.py" if job_prefix != "" }
}

最后,通过调用在Terraform Cloud Workspace中的module(此例中为glue-etl)批量创建某一规格下的Glue Python shell脚本。

module "glue-etl-type1" {
  source                                  = "app.terraform.io/repo/glue-etl/aws"
  subnet_list                             = ["subnet-1","subnet-2","subnet-3"]
  bucket_name                             = local.bucket_name
  line_of_business                          =  local.line_of_business
  secret_manager_id                       = "some-secretmanager-id"
  if_connection                           = true
  conn_name                               = local.connection_name_staging
  glue_job_name_list_for_python  = local.job_name_map
  max_concurrent_runs_for_python = 4
  max_retries_for_python         = 0
}

(4) 实现CodeBuild驱动的CI/CD流水线

本文使用EventBridge来串联CodeCommit与CodeBuild,您也可以根据使用习惯选择AWS CodePipeline实现同样的功能。在开始之前,请您务必确保相应的AWS CodeCommit与CodeBuild已经被初始化。

设置CodeCommit仓库增、改事件触发的EventBridge规则,如下所示。

{
  "source": [
    "aws.codecommit"
  ],
  "detail-type": [
    "CodeCommit Repository State Change"
  ],
  "detail": {
    "event": [
      "referenceCreated",
      "referenceUpdated"
    ]
  }
}

为此规则配置Input Transformer,分别定义输入路径及输入模版,如下所示:

{"referenceType":"$.detail.referenceType","region":"$.region","repositoryName":"$.detail.repositoryName","account":"$.account","referenceName":"$.detail.referenceName"}

{"environmentVariablesOverride": [
      {
          "name": "REFERENCE_NAME",
          "value": <referenceName>
       },
      {
          "name": "REFERENCE_TYPE",
          "value": <referenceType>
       },
      {
          "name": "REPOSITORY_NAME",
          "value": <repositoryName>
       },
      {
          "name": "REPO_REGION",
          "value": <region>
       },
       {
          "name": "ACCOUNT_ID",
          "value": <account>
       }
 ]}

配置buildspec.yml,体现CI/CD流水线具体流程。本例中,流水线内容包括:

  • 安装git-remote-codecommit以及其他代码中所需的Python依赖包(本例中使用Makefile安装依赖)或命令(如本例中的Terraform)
  • 实现对ETL脚本或.tf文件代码的CI过程,如代码质量检查,语法检查,安全漏洞扫描,Unit Test等
  • 当CI过程结束后,同步CodeCommit中更新的代码至存放Glue内容的s3路径中。当s3收到更新代码后,进行以下操作:
    • Terraform的语法检查(terraform fmt, validate & lint)
    • 资源变更检查(terraform plan)
    • 最终发布(terraform apply)
AWS CodeBuildversion: 0.2

env:
  variables:
    TF_VERSION: "1.0.6"
    
phases:
  install:
    runtime-versions:
      python: 3.8
    commands:
      - pip install git-remote-codecommit
      - make install
  pre_build:
    commands:
      - echo Hello pre build
      - cd /usr/bin
      - "curl -s -qL -o terraform.zip https://releases.hashicorp.com/terraform/${TF_VERSION}/terraform_${TF_VERSION}_linux_amd64.zip"
      - unzip -o terraform.zip
      - cd -
  build:
    commands:
      - echo build
      - make format
      - make lint
      - make test
      - env
      - git clone -b $REFERENCE_NAME codecommit::$REPO_REGION://$REPOSITORY_NAME
      - dt=$(date '+%d-%m-%Y-%H:%M:%S');
      - echo "$dt" 
      - aws s3 sync . s3://sample-bucket-glueetl/hr-mysql-source1-python-scripts/
      - terraform init
      - terraform fmt -recursive
      - terraform validate
      - terraform apply -auto-approve
  post_build:
    commands:
      - echo post build 
      - echo "terraform fmt & validate apply completed on `date`"
      - echo "Makefile completed on `date`"

将buildspec.yml文件上传至CodeCommit对应仓库内,新建CodeBuild项目并指向该仓库,使用EventBridge作为事件触发器监听CodeCommit内容变更,并将事件输出至CodeBuild,实现一整套CI/CD流水线。架构如下所示:

注意事项

  • 为了实现上述解决方案,您需要注意各AWS服务间的访问权限,所需的IAM角色执行权限是否足够等问题。
  • 本文讨论的方法对于不同配置的Glue脚本,无法实现资源新建的完全自动化。需要数据开发团队重新调用对应Terraform module并按需重复上述流程。
  • 本文提供的方案仅针对使用Amazon Code组件管理代码版本以及发布的场景。对于外部代码管理组件及CI/CD工具,本文不做进一步探讨。

总结

本文通过一个具体的案例,展现了数据开发人员通过Terraform Cloud Workspace调用远端IaC模块(module),结合EventBridge驱动的AWS CodeCommit和AWS CodeBuild开发CI/CD流水线,自动捕获数据应用脚本内的变更内容并批量创建相应的云上资源。通过对数据应用相关的资源管理与代码变更发布流程的自动化,云运维团队减轻了代码资产新增/变更带来的管理压力 – 他们不再需要关心数据应用中的代码增改带来的额外工作量,而数据开发团队也可以专注于ETL脚本的代码开发及运维,不需要担心代码变更对云上资源带来的后续影响。

参考文档

[1] 利用AWS Code组件向s3自动备份资料

[2] 利用Input Transformer定制EventBridge的事件信息

本篇作者

毛元祺

AWS专业服务团队数据科学家。负责统计学习、机器学习、数据挖掘以及云上数据平台设计方面的相关咨询服务。服务行业囊括医疗,金融,无人驾驶等,积累了丰富的开发运维经验。

梁宇

AWS 专业服务团队DevOps顾问,主要负责DevOps技术实施。尤为热衷云原生服务及其相关技术。在工作之余,他喜欢运动,以及和家人一起旅游。