为了Terraform团队的运营,我们所采取的措施

概述

在这篇文章中,我们将总结如何在Terraform团队运作中实现持续apply。我们的基础设施环境是AWS,主要的做法与实践Terraform的第27章”持续apply和Terraform在哪里执行”(Continuous apply and where to execute Terraform)中所总结的几乎相同。在实际环境建设中,我们非常参考了这篇文章,希望它能作为一个样例对您有所帮助。

前提 tí)

当前使用的是实际的服务,AWS平台上有两个账户,分别用于生产环境和开发环境。生产环境包括production和staging,而开发环境则是development。terraform的源代码与应用代码一起通过GitHub进行管理,采用GitFlow进行运维。tf文件则通过terraform cloud进行管理,计划和应用则基本上由一人在本地进行。

文件夹的结构如下所示。

app/                        ... アプリケーションのコード
terraform/prod/             ... 本番AWSアカウント用のディレクトリ
              /common       ... 本番AWSアカウントで共通に使うもののtfファイル群
              /prod         ... production環境で使うもののtfファイル群
              /stg          ... staging環境で使うもののtfファイル群
         /dev/              ... 開発AWSアカウント用のディレクトリ
             /common        ... 開発AWSアカウントで共通に使うもののtfファイル群
             /dev           ... develop環境で使うもののtfファイル群

我們希望此次能夠實現的目標。

    • AWS上でplan,applyをさせたい

実行に時間がかかるものをローカルで実行されるのは途中で落ちないか不安

複数人運用しても事故が起こらないようにしたい

常に最新の状態でapplyがされるようにしたい
terraformの内容をレビューし合えるようしたい

本番環境以外はある程度柔軟に操作できるようにしたい

開発環境ではトライアンドエラーがしやすいようにしたい。

# 最后成功实现的事物

    • GitHubでプルリク時にCodeBuild上でplan実行、マージ時にapply実行

 

    • plan,apply実行時にはGitHubのコメントとSlack両方に通知する

GitHubだけだとマージ後にapplyを行った際、終わったかを見に行かなければいけないのが手間なので

本番と本番共通のterraformはmasterのマージ時にそれ以外はdevelopのマージ時に行う。
ローカルではapplyとplan両方が実行できてしまうが、applyは実行しないというルールにする

planができないのは不便なため。applyができるのは塞ぎたかったがうまくできず今後の検討事項

实施细节

请注意以下事项

请根据您的环境和需求来修改并经过充分的操作确认后使用所提供的代码。此外,由于此操作还没有完全成熟,所以未来可能需要进行修正。如果有关更好的方法的指导意见,将非常感激!

文件夹结构

在制定这次方案时,我们添加了以下内容。

terraform/continuous_apply/script/apply.sh        ... apply用のシェル
                                 /build.sh        ... applyかplanかで振り分ける用のシェル
                                 /install.sh      ... tfnotifyのインストール用
                                 /plan.sh         ... plan用のシェル
                          /_terraformrc           ... terraform cloudの設定ファイル用のテンプレート
                          /buildspec.yml          ... CodeBuild用のyaml
                          /tfnotify_github.yml    ... tfnotifyの設定ファイル、github用
                          /tfnotify_slack.yml     ... tfnotifyの設定ファイル、slack用

我会逐个查看每个文件。

在CodeBuild中执行的Shell脚本

在install.sh脚本中,我们正在安装tfnotify。如果已经有一个包含tfnotify的容器镜像,那就很好了,我们计划以后会做相应的适配。需要注意的是,由于我们安装的是最新版本的tfnotify,所以我们已经修改了实践Terraform中的示例脚本。

#!/bin/sh

VERSION="v0.6.1"
BASE_URL=https://github.com/mercari/tfnotify/releases/download
DOWNLOAD_URL="${BASE_URL}/${VERSION}/tfnotify_linux_amd64.tar.gz"
wget ${DOWNLOAD_URL} -P /tmp
mkdir -p /tmp/tfnotify_linux_amd64
tar zxvf /tmp/tfnotify_linux_amd64.tar.gz -C /tmp/tfnotify_linux_amd64
mv /tmp/tfnotify_linux_amd64/tfnotify /usr/local/bin/tfnotify

在 build.sh 脚本中,我们根据是否进行合并和合并时是否选择生产环境来进行分流。然而,目前还无法将计划分流。

#!/bin/sh
set -x

if [[ ${CODEBUILD_WEBHOOK_TRIGGER} = 'branch/master' ]]; then
  ${CODEBUILD_SRC_DIR}/terraform/continuous_apply/scripts/apply.sh master
elif [[ ${CODEBUILD_WEBHOOK_TRIGGER} = 'branch/develop' ]]; then
  ${CODEBUILD_SRC_DIR}/terraform/continuous_apply/scripts/apply.sh develop
else
  ${CODEBUILD_SRC_DIR}/terraform/continuous_apply/scripts/plan.sh
fi

计划将获取到有更改的文件夹,并执行terraform apply。为了通知GitHub和Slack,我将使用双重管道进行两次通知。

#!/bin/sh

DIRS=$(git --no-pager diff origin/develop..HEAD --name-only | xargs -I {} dirname {} | grep "terraform" | uniq)
if [ -z "$DIRS" ]; then
  echo "No directories for apply."
  exit 0
fi

for dir in $DIRS
do
  if [ ! -e $dir/terraform.tf ]; then
    continue
  fi

  echo $dir
  (cd $dir && terraform init -input=false -no-color)
  (cd $dir && terraform plan -input=false -no-color | tfnotify --config ${CODEBUILD_SRC_DIR}/terraform/continuous_apply/tfnotify_github.yml plan --message "$dir" | tfnotify --config ${CODEBUILD_SRC_DIR}/terraform/continuous_apply/tfnotify_slack.yml plan --message "$dir" )
done

在apply的shell中,基本流程与plan相同,但区分了是用于生产环境还是其他环境。

#!/bin/sh

MODE=$1
PROD_DIRS='terraform/prod/prod|terraform/prod/common'

echo "Mode: ${MODE}"

MESSAGE=$(git log ${CODEBUILD_SOURCE_VERSION} -1 --pretty=format:"%s")
CODEBUILD_SOURCE_VERSION=$(echo ${MESSAGE} | cut -f4 -d' ' | sed 's/#/pr\//')

get_dir_list () {
  if [ $1 = 'master' ]; then
    git --no-pager diff HEAD^..HEAD --name-only | xargs -I {} dirname {} | grep "terraform" | egrep "$2" | uniq
  else
    git --no-pager diff HEAD^..HEAD --name-only | xargs -I {} dirname {} | grep "terraform" | egrep -v "$2" | uniq
  fi
}

DIRS=$(get_dir_list $MODE $PROD_DIRS)
echo $DIRS

if [ -z "$DIRS" ]; then
  echo "No directories for apply."
  exit 0
fi

for dir in $DIRS
do
  if [ ! -e $dir/terraform.tf ]; then
    continue
  fi

  echo $dir
  (cd $dir && terraform init -input=false -no-color)
  (cd $dir && terraform apply -input=false -no-color -auto-approve | tfnotify --config ${CODEBUILD_SRC_DIR}/terraform/continuous_apply/tfnotify_github.yml apply --message "$dir" | tfnotify --config ${CODEBUILD_SRC_DIR}/terraform/continuous_apply/tfnotify_slack.yml apply --message "$dir" )
done

构建规范文件 buildspec.yml 和 tfnotify 的配置

以下是用于在CodeBuild中实现的构建规范(builspec)。
重点是在安装时安装了tfnotify,并添加了存放Terraform Cloud配置文件的处理过程。

version: 0.2

env:
  parameter-store:
    GITHUB_TOKEN: "/continuous_apply/github_token"
    TERRAFROM_CLOUD_TOKEN: "/continuous_apply/terraform_cloud_token"
    SLACK_TOKEN: "/continuous_apply/slack_token"

phases:
  install:
    commands:
      - ${CODEBUILD_SRC_DIR}/terraform/continuous_apply/scripts/install.sh
      - sed -e "s/_TOKEN_/${TERRAFROM_CLOUD_TOKEN}/" ${CODEBUILD_SRC_DIR}/terraform/continuous_apply/_terraformrc > ~/.terraformrc

  build:
    commands:
      - ${CODEBUILD_SRC_DIR}/terraform/continuous_apply/scripts/build.sh
credentials "app.terraform.io" {
  token = "_TOKEN_"
}

以下是tfnotify的設定。為了易讀性,我們在GitHub和Slack上更改了格式。

ci: codebuild
notifier:
  github:
    token: $GITHUB_TOKEN
    repository:
      owner: "(__secret__)"
      name: "(__secret__)"
terraform:
  plan:
    template: |
      {{ .Title }}
      {{ .Message }}
      {{if .Result}}<pre><code> {{ .Result }} </pre></code>{{end}}
      <details><summary>Details (Click me)</summary>
      <pre><code> {{ .Body }} </pre></code></details>
  apply:
    template: |
      {{ .Title }}
      {{ .Message }}
      {{if .Result}}<pre><code> {{ .Result }} </pre></code>{{end}}
      <details><summary>Details (Click me)</summary>
      <pre><code> {{ .Body }} </pre></code></details>
ci: codebuild
notifier:
  slack:
    token: $SLACK_TOKEN
    channel: "(__secret__)"
    bot: "(__secret__)"
terraform:
  plan:
    template: |
      {{ .Title }}
      {{ .Message }}
      {{if .Result}}
      ```
      {{ .Result }}
      ```
      {{end}}
      ```
      {{ .Body }}
      ```
  apply:
    template: |
      {{ .Title }}
      {{ .Message }}
      {{if .Result}}
      ```
      {{ .Result }}
      ```
      {{end}}
      ```
      {{ .Body }}
      ```

AWS的配置

我正在使用Terraform进行编写。实际上,我们需要一个用于生产和一个用于开发的环境,以下是用于开发环境的部分。
为了避免不必要的构建,在这里也通过修改文件夹进行过滤。

resource "aws_codebuild_project" "continuous_apply" {
  name         = "${var.project}-common-continuous-apply"
  service_role = module.continuous_apply_codebuild_role.iam_role_arn

  source {
    type      = "GITHUB"
    location  = "(_secret_)"
    buildspec = "terraform/continuous_apply/buildspec.yml"
  }

  artifacts {
    type = "NO_ARTIFACTS"
  }

  environment {
    type            = "LINUX_CONTAINER"
    compute_type    = "BUILD_GENERAL1_SMALL"
    image           = "hashicorp/terraform:0.12.25"
    privileged_mode = false
  }

  provisioner "local-exec" {
    command = <<-EOT
      aws codebuild import-source-credentials \
        --server-type GITHUB \
        --auth-type PERSONAL_ACCESS_TOKEN \
        --token $GITHUB_TOKEN
    EOT

    environment = {
      GITHUB_TOKEN = data.aws_ssm_parameter.github_token.value
    }
  }
}
resource "aws_codebuild_webhook" "continuous_apply" {
  project_name = aws_codebuild_project.continuous_apply.name

  filter_group {
    filter {
      type    = "EVENT"
      pattern = "PULL_REQUEST_CREATED"
    }

    filter {
      exclude_matched_pattern = false
      pattern                 = "^terraform/dev/"
      type                    = "FILE_PATH"
    }
  }

  filter_group {
    filter {
      type    = "EVENT"
      pattern = "PULL_REQUEST_UPDATED"
    }

    filter {
      exclude_matched_pattern = false
      pattern                 = "^terraform/dev/"
      type                    = "FILE_PATH"
    }
  }

  filter_group {
    filter {
      type    = "EVENT"
      pattern = "PULL_REQUEST_REOPENED"
    }

    filter {
      exclude_matched_pattern = false
      pattern                 = "^terraform/dev/"
      type                    = "FILE_PATH"
    }
  }

  filter_group {
    filter {
      type    = "EVENT"
      pattern = "PUSH"
    }

    filter {
      type    = "HEAD_REF"
      pattern = "develop"
    }

    filter {
      exclude_matched_pattern = false
      pattern                 = "^terraform/dev/"
      type                    = "FILE_PATH"
    }
  }
}

GitHub的配置设置

不会提供详细说明,但是为了事故防止,我们在分支中进行了以下设置。

    • Require pull request reviews before merging

 

    Require branches to be up to date before merging

总结

经过以上的工作,我们已经成功地建立起了一个持续申请的机制。虽然不能完全称之为牢固的运作和框架,但与之前只能由一个人来处理的情况相比,现在运用起来变得更加便利。希望您能参考并帮助我们构建这个机制。

请参考

我之前已经提到过,但是再次总结一下。这些内容非常有参考价值。谢谢!

実践Terraform 第27章 継続的apply
Terraform どこで実行していますか?
メルカリ Microservices Team による Terraform 運用とその中で開発したOSSの紹介
github.com/mercari/tfnotify

广告
将在 10 秒后关闭
bannerAds