GitHubの機能を活用した Terraform CI/CD Template Repository
この記事はTerrafrom Advent Calendar 2021の7日目の記事です。
はじめに
2019年12月のTerraform Meetup Tokyo#3にて、「GitHub Actionsで Terraformをplan&applyしてみた」 でLTしてから早2年が過ぎました・・
この時はGitHub ActionsがGAした直後でしたが、2年の月日が経ち、Terraform公式のActionsも変わっていたり、他にも便利な機能が増えている為、Actionsを活用したTerraformのGitHubテンプレートリポジトリを作成してみました。
大規模で複雑なシステムにおいてはそのまま活用はできないかもしれませんが、これからTerraformを始める方や、簡単なシステムを作りたい方であればそのまま利用してもらえると思うので、是非参考にしていただければと思います。
想定する読者
- Terraformの基本的な知識はあり、これから活用していきたい
- GitHub Actionsを活用し、Terraform周りの運用を自動化したい
前提条件
- インフラはAWSを想定
- 本番(Production)、ステージング(Staging)、開発(Develop)の3環境を運用
- 3環境はAWSアカウントを分けて運用
- 環境毎の構成ファイルはディレクトリを分けて管理
テンプレートリポジトリ
リポジトリ構成
├── .github
│ ├── actions
│ │ ├── terraform-apply
│ │ │ └── action.yml
│ │ └── terraform-plan
│ │ └── action.yml
│ ├── dependabot.yml
│ └── workflows
│ ├── apply-develop.yml
│ ├── apply-production.yml
│ ├── apply-staging.yml
│ ├── plan-dependabot.yml
│ ├── plan-develop.yml
│ ├── plan-production.yml
│ └── plan-staging.yml
├── .tfsec
│ └── config.yml ### tfsec用定義ファイル
├── README.md
├── cf-github-actions-role.yml ### Terraform実行用ロールCFnテンプレート
├── develop ### 開発環境
│ ├── .tfsec
│ │ └── config.yml -> ../../.tfsec/config.yml
│ ├── backend.tf
│ ├── provider.tf -> ../shared/provider.tf
│ ├── terraform.tf -> ../shared/terraform.tf
│ ├── terraform.tfvars
│ └── variables.tf -> ../shared/variables.tf
├── production ### 本番環境
│ ├── .tfsec
│ │ └── config.yml -> ../../.tfsec/config.yml
│ ├── backend.tf
│ ├── provider.tf -> ../shared/provider.tf
│ ├── terraform.tf -> ../shared/terraform.tf
│ ├── terraform.tfvars
│ └── variables.tf -> ../shared/variables.tf
├── shared
│ ├── provider.tf
│ ├── terraform.tf
│ └── variables.tf
└── staging ### ステージング環境
├── .tfsec
│ └── config.yml -> ../../.tfsec/config.yml
├── backend.tf
├── provider.tf -> ../shared/provider.tf
├── terraform.tf -> ../shared/terraform.tf
├── terraform.tfvars
└── variables.tf -> ../shared/variables.tf
環境で共有する定義
バージョンや変数など、運用する上で環境差異をつけない項目については、各環境用ディレクトリにはシンボリックリンクを配置し、実体としては一つの定義ファイルで管理するようにしています。
shared/version.tf
terraform及びproviderのバージョンを指定します。
terraform {
required_version = "1.0.8"
required_providers {
aws = {
source = "hashicorp/aws"
version = "3.64.0"
}
}
}
shared/provider.tf
providerの設定を記載します。
provider "aws" {
default_tags {
tags = local.tags
}
}
shared/variables.tf
変数を定義します。
variable "environment" {
description = "environment prefix"
type = string
}
variable "service" {
description = "service name"
type = string
}
locals {
prefix = "${var.environment}-${var.service}"
tags = {
"Terraform" = "true"
"Environment" = var.environment
"Service" = var.service
}
}
リソース名やタグ情報として、環境とサービス名は利用することが多いので、変数としてenvironment
とservice
を定義しています。
また、設定した変数からローカル変数としてprefix
とtags
を定義しています。
-
local.prefix
... 環境とサービス名を繋げて、リソース名の識別子なとで利用 -
local.tags
... リソースに必須で設定したいタグを定義し、default_tags
に指定
.tfsec/config.yml
tfsecの設定を定義します。
Terraform Plan実行時に、静的セキュリティ解析を実行するために利用しています。
解析を除外したい条件など、セキュリティ指針は環境ごとでの差異はもたせず、同じファイルを参照するようにしています。
環境固有の定義
backend.tf
tfstateファイルを管理するS3バケット情報を定義します。
terraform {
backend "s3" {
bucket = "<S3 bucket name>"
key = "terraform.tfstate"
region = "<S3 bucket region>"
encrypt = true
}
}
terraform.tfvars
変数の値を定義します。
environment = "dev"
service = "sample"
変数については複数のリソースを作成していくうちに増加していくと思うので、変数追加時はvariables.tf
と合わせて修正します。
初期設定
リポジトリを作成後、Terraformを実行するために以下の初期設定が必要です。
tfstate保存バケット作成
Terraformのステートファイルを保存する為のS3バケットを各AWSアカウントに作成します。
作成したバケット名とリージョンの情報を各環境ディレクトリのbackend.tf
に記載します。
Terraform実行用ロール作成
GitHub ActionsからTerraformを実行するためにロールを作成します。
cf-github-actions-role.yml
CFnテンプレートを使用し、スタックを作成します。
スタックのパラメータとして以下を入力します。
パラメータ | 値 | 説明 |
---|---|---|
GitHubOrg | GitHub Organization Name | terraformコードを管理するOrganization |
RepositoryName | Terraform Repository Name | terraformコードを管理するリポジトリ |
OIDCProviderArn | ID Provider ARN | 既にIDプロバイダーを設定済みの場合に入力 |
尚、CFnテンプレートは以下の公式のSampleがあるのでそちらを利用できます。
差分としては、ロール名の指定とadminポリシーの付与が追加されています。
GitHub Secrets設定
GitHub Actionsのワークフロー内で指定するTerraform実行用ロールARNはGitHub Secrets
から取得するため、作成した各環境のTerraform実行用ロールをGitHub Secrets
に追加します。
パラメータ | 値 |
---|---|
DEV_AWS_ASSUME_ROLE_ARN | <開発環境に作成したTerraform実行用ロールのARN> |
STG_AWS_ASSUME_ROLE_ARN | <ステージング環境作成したTerraform実行用ロールのARN> |
PRO_AWS_ASSUME_ROLE_ARN | <本番環境作成したTerraform実行用ロールのARN> |
プルリクエスト作成時の動作
試しにS3バケットの定義を作成し、プルリクエストを作成します。
resource "aws_s3_bucket" "b" {
bucket = "my-tf-test-bucket"
acl = "private"
tags = {
Name = "test"
}
}
plan結果のコメント出力
planの出力について、コメントが追加されます。
fmt
やvalidate
も合わせて実行しており、エラーとなった場合はエラーメッセージがコメント出力されます。
tfsecによるセキュリティ静的解析
planと同時に、tfsecにより静的セキュリティ解析が行われます。
解析結果がコード内のコメントとして追記されます。
.tfsec/config.yml
にて動作を制御できるので、以下の例ではS3バケットのロギングの設定とバージョニング設定をチェック対象から除外しています。
---
exclude:
- aws-s3-enable-bucket-logging
- aws-s3-enable-versioning
マージ時の動作
マージするとapply
が実行されます。
AWSプロバイダーの自動アップデート
AWSプロバイダーのバージョンがリリースされると、develop/version.tf
を自動で書き換えプルリクを作成します。
プルリクが作成されると、アップデートしたバージョンでのplanが実行さる為、アップデートしたことによる既存への影響がない事を確認した上でマージします。
Dependabotの実装
バージョンのアップデートにはGitHubにてデフォルトで利用できるDependabot version updates
を利用しています。
version.tf
の実体ファイルが存在するshared
ディレクトリを指定し、毎朝9時にアップデートをチェックします。
version: 2
updates:
- package-ecosystem: "terraform"
directory: "/shared"
schedule:
interval: "daily"
time: "09:00"
timezone: "Asia/Tokyo"
InsightsのDependency graph画面にて動作結果が確認できます。
Check for updates
をクリックすると、手動でチェック処理をうご描くことができます。
やれてないこと
間に合わず実装できてないです・・
slack通知
Actionsの実行結果や、applyの結果などはSlack通知する方がいいと思います。
slack通知系やActionsが豊富なので、運用に従ってどういう通知をしたいか検討すればいいかなと思います。
AWSプロバイダーの自動アップデートマージ後のapply
AWSプロバイダーの自動アップデートのプルリクエストをマージしてもapplyまでは実行されないです。
バージョンを上げた場合の影響の有無を確認できればいいので、plan差分がなければマージし、次回のリソース作成時などに合わせてapplyされる動きになってます。
まとめ
なるだけ凝ったことは行わず、GitHub既存の機能やGitHub Actionsを活用した構成例を作ってみました。
Terraformの運用自動化についてはいろいろな情報があるので、他の記事も参考にしつつ「ぼくのかんがえたさいきょうのTerraformCI/CD構造」を目指してもらえればと思います。
追記
2022.01.10
plan,apply時のコメントの実装をtfcmtに変更
Discussion