Terraform, tfsec, tflint, aws-cli-v2, Docker(DooD)が使えるDocker環境を構築する
はじめに
業務でTerraform
を利用してAWSのインフラ構築する際、
バージョン管理はtfenvを使い、その他諸々のアプリケーションの環境はDockerで構築していました
Terraform
だけローカルマシン上に環境構築しなければならないのは微妙だし、
新規参画のメンバーにもちょっと優しくないです
なのでDockerさえ入っていればTerraform
を利用できるような環境を構築していこうとおもいます
また今回は、
・ECR
の作成をトリガーにnull_resource
を作成してlocal-exec
でdocker build
& aws-cli
でECR
にinitialのimageをpushする。
といったケースにも対応したいので、
Terraform
だけでなくaws-cli-v2
とDooD(Docker outside of Docker)
でDockerも利用できるようにします
2022/8/1追記
tfsecとtflintも実行できるようにしました
利用技術など
・docker, docker-compose
・Terraform(hashicorp/terraform:1.2.5)
・aws-cli-v2
・tfsec
・tflint
※ソースコード全文はこちら
最終的なディレクトリ構成はこんな感じです
Prod/Stg
のような環境ごとにtfstate
を分けて、
各リソースはmodules
配下に定義し各環境のmain.tf
から利用するパターンを想定しています
├── .gitignore
├── Dockerfile
├── Makefile
├── docker-compose.yaml
├── environments
│ ├── prod
│ │ ├── .env
│ │ ├── Makefile
│ │ ├── backend.tf
│ │ ├── logger
│ │ │ └── Dockerfile
│ │ ├── main.tf
│ │ ├── prod.tfvars
│ │ └── variables.tf
│ └── stg
│ │ ├── .env
│ ├── Makefile
│ ├── backend.tf
│ ├── logger
│ │ └── Dockerfile
│ ├── main.tf
│ ├── stg.tfvars
│ ├── terraform.tfstate
│ └── variables.tf
└── modules
└── ecr
├── main.tf
├── outputs.tf
└── variables.tf
さっそく準備する
Dockerfileを準備する
FROM hashicorp/terraform:1.2.5
ENV GLIBC_VER=2.34-r0
RUN apk update && \
apk --no-cache add \
docker-cli \
binutils \
make \
curl && \
curl -sL https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub -o /etc/apk/keys/sgerrand.rsa.pub && \
curl -sLO https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_VER}/glibc-${GLIBC_VER}.apk && \
curl -sLO https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_VER}/glibc-bin-${GLIBC_VER}.apk && \
apk add --no-cache glibc-${GLIBC_VER}.apk glibc-bin-${GLIBC_VER}.apk
# tfsec
ENV TFSEC_VER=1.26.3
RUN curl -Lso tfsec https://github.com/tfsec/tfsec/releases/download/v${TFSEC_VER}/tfsec-linux-amd64 && \
chmod +x tfsec && mv tfsec /usr/local/bin/
# tflint
ENV TFLINT_VER=0.39.1
RUN curl --fail --silent -L -o /tmp/tflint.zip https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VER}/tflint_linux_amd64.zip && \
unzip /tmp/tflint.zip -d /tmp/ && \
install -c -v /tmp/tflint /usr/local/bin/ && \
rm /tmp/tflint*
# tflint rule
ENV TFLINT_RULE_SET_AWS_VER=0.15.0
RUN mkdir -p ~/.tflint.d/plugins/github.com/terraform-linters/tflint-ruleset-aws/${TFLINT_RULE_SET_AWS_VER}/ && \
curl --fail --silent -L -o /tmp/tflint-ruleset-aws.zip https://github.com/terraform-linters/tflint-ruleset-aws/releases/download/v${TFLINT_RULE_SET_AWS_VER}/tflint-ruleset-aws_linux_amd64.zip && \
unzip /tmp/tflint-ruleset-aws.zip -d ~/.tflint.d/plugins/github.com/terraform-linters/tflint-ruleset-aws/${TFLINT_RULE_SET_AWS_VER}/ && \
rm /tmp/tflint-ruleset-aws.zip
ENTRYPOINT [ "ash" ]
docker-compose.yamlを準備する
一旦動作確認のため、以下のように定義します
個人的にvolume
はlong syntaxで書くのが読みやすくて好きです
version: "3.8"
services:
terraform-prod:
build:
context: .
dockerfile: Dockerfile
working_dir: /terraform/environments/prod
tty: true
env_file:
- environments/prod/.env
volumes:
- type: bind
source: /var/run/docker.sock
target: /var/run/docker.sock
terraform-stg:
build:
context: .
dockerfile: Dockerfile
working_dir: /terraform/environments/stg
tty: true
env_file:
- environments/stg/.env
volumes:
- type: bind
source: /var/run/docker.sock
target: /var/run/docker.sock
※のちほどProd/Stgごとに必要なディレクトリ/ファイルをマウントして起動できるようにします
-
env_file
について- Prod/StgでAWSアカウントが分かれている場合があると思うので、
環境ごとの認証情報はここに入れます。
以下のように定義しておくことで、aws-cli-v2
が勝手に読んで利用してくれますenviroments/stg/.envAWS_DEFAULT_REGION=ap-northeast-1 AWS_DEFAULT_OUTPUT=json AWS_ACCESS_KEY_ID=dummy AWS_SECRET_ACCESS_KEY=dummy
- Prod/StgでAWSアカウントが分かれている場合があると思うので、
- volumeマウントについて
- DooDのため、ローカルマシンの
docker.sock
をコンテナにbindマウントします
- DooDのため、ローカルマシンの
実際に使ってみる
Docker環境が構築できたので実際に
・TerraformでECRを作成
・aws-for-fluent-bit
のimageをbuild
・aws-cli
でECRにpush
上記を実行してみます
aws-for-fluent-bitのDockerfile作成
といっても今回はサンプルなのでこれだけです
※以下はStgの例です
FROM public.ecr.aws/aws-observability/aws-for-fluent-bit:stable
ECRのmodule作成
ECRの作成をトリガーにimageをbuildしてpushするmoduleを作成します
サンプルにしては冗長ですみません、適宜単一ファイルで定義してもらって大丈夫です
variable "name" {
description = "ECRで作成するリポジトリの名前"
type = string
}
variable "region" {
description = "リージョン"
type = string
}
variable "image_tag_mutability" {
description = "tagのmutabilityを指定する。IMMUTABLEの場合は、tagが常に一意になるよう運用する"
type = string
default = "IMMUTABLE"
validation {
condition = contains(["IMMUTABLE", "MUTABLE"], var.image_tag_mutability)
error_message = "Allowed values for \"ecr_image_tag_mutability\" are \"IMMUTABLE\" or \"MUTABLE\"."
}
}
variable "docker_build_context" {
description = "初期push時に参照するDockerfileのcontext"
type = string
}
# =================
# ECR
# =================
resource "aws_ecr_repository" "this" {
name = var.name
image_tag_mutability = var.image_tag_mutability
image_scanning_configuration {
scan_on_push = true
}
}
resource "null_resource" "initial_image_push" {
provisioner "local-exec" {
command = <<-EOF
docker build ${var.docker_build_context} -t ${aws_ecr_repository.this.repository_url}:initial; \
aws ecr get-login-password --region ${var.region} | docker login --username AWS --password-stdin ${aws_ecr_repository.this.repository_url}; \
docker push ${aws_ecr_repository.this.repository_url}:initial;
EOF
on_failure = fail
}
depends_on = [
aws_ecr_repository.this
]
}
output "name" {
value = aws_ecr_repository.this.name
}
Prod/Stg環境のmain.tfなど作成
今回は簡易化のためにtfstate
はローカルで管理します
※以下はStgの例です
region = "ap-northeast-1"
project_name = "project"
variable "region" {
description = "リージョン"
type = string
}
variable "project_name" {
description = "プロジェクトの名前"
type = string
}
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "4.17.0"
}
}
}
provider "aws" {
region = var.region
}
module "ecr_repo" {
source = "../../modules/ecr"
name = var.project_name
region = var.region
image_tag_mutability = "IMMUTABLE"
# build contextを引数で渡すのはメタくて微妙ですがサンプルということで許してください
docker_build_context = "./logger"
}
SHELL=ash
.PHONY: $(shell egrep -oh ^[a-zA-Z0-9][a-zA-Z0-9_-]+: $(MAKEFILE_LIST) | sed 's/://')
# ===================
# static checks
# ===================
sec:
tfsec
# severityはお好みで
sec-min:
tfsec --minimum-severity HIGH
lint:
tflint
fmt:
terraform fmt -check -recursive
check: fmt lint sec-min
# ===================
# terraform commands
# ===================
init: fmt
terraform init
plan-stg: check
terraform plan -var-file stg.tfvars
plan-stg-verbose: check
TF_LOG=debug terraform plan -var-file stg.tfvars
apply-stg: check
terraform apply -var-file stg.tfvars
plugin "aws" {
enabled = true
version = "0.15.0"
source = "github.com/terraform-linters/tflint-ruleset-aws"
}
config {
module = true
}
docker-compose.yamlを更新する
version: "3.8"
services:
terraform-prod:
build:
context: .
dockerfile: Dockerfile
working_dir: /terraform/environments/prod
tty: true
env_file:
- environments/prod/.env
volumes:
+ - type: bind
+ source: "environments/prod"
+ target: "/terraform/environments/prod"
+ - type: bind
+ source: "modules"
+ target: "/terraform/modules"
- type: bind
source: /var/run/docker.sock
target: /var/run/docker.sock
terraform-stg:
build:
context: .
dockerfile: Dockerfile
working_dir: /terraform/environments/stg
tty: true
env_file:
- environments/stg/.env
volumes:
+ - type: bind
+ source: "environments/stg"
+ target: "/terraform/environments/stg"
+ - type: bind
+ source: "modules"
+ target: "/terraform/modules"
- type: bind
source: /var/run/docker.sock
target: /var/run/docker.sock
動作確認
自分の場合はprojectのrootにMakefile
を置いて利用しています
SHELL=/bin/bash
.PHONY: $(shell egrep -oh ^[a-zA-Z0-9][a-zA-Z0-9_-]+: $(MAKEFILE_LIST) | sed 's/://')
set-up:
docker-compose build
tf-prod:
docker-compose run --rm terraform-prod
tf-stg:
docker-compose run --rm terraform-stg
% make set-up
% make tf-stg
/terraform/environments/stg # make init
/terraform/environments/stg # make plan-stg
/terraform/environments/stg # make apply-stg
AWSのコンソールから確認して正常にECRが作成され、imageがpushされていれば成功です🎉
おわりに
これで簡単かつローカルマシンを汚さずにTerraformを利用できます🎉
また、docker-composeでProd/Stgの環境ごとに.env
を渡してContainerを動かすようにしているので、各環境のAWS認証情報をいちいちローカルマシンで切り替えてTerraformを実行する必要もなくなりました
これも地味に嬉しいです!
※git-secretsなどで機微情報の誤commit対策は適切にやりましょう
参考にさせていただいた記事
Discussion