インフラをTerraformで一元管理する
はじめに
このスクラップは、私が所属している会社のインフラを、Terraformで一元管理するようにした際に、躓いた箇所やメモを記録するためのものです。
参考
Terraform とは
Terraform とは、HashiCorp社が提供しているオープンソースの「Infrastructure as Code (IaC)
」ツールです。インフラストラクチャ(サーバー、ネットワーク、ストレージ、DNSなど)をコードとして記述し、自動化と再現性を持って管理・デプロイすることができます。
開発環境の構築
今回は WSL2 + Ubuntu + DevContainer + Docker
を使用して、開発環境を構築します。
Dockerイメージは公式のものがあるようなので、それを使用します。
Terraformのバージョンは最新のものを使用します。
最新のバージョンは以下から確認しましょう。
AWS CLI v2とHashiCorp Terraform公式Dockerイメージの相性問題
AWS CLI v2はglibc
環境が必要ですが、AlpineベースのTerraform公式Dockerイメージでは適切に構築できず、多くの課題がありました。その際に以下の記事を発見しました。
該当Issue
- GitHub Issue: glibcとAlpineの互換性問題について
結論
Terraform公式イメージで環境を構築しようと試みましたが、AlpineベースのOSではglibc
の依存関係などの問題が多く、最終的にDebian Slim
かUbuntu
の選択に至りました。
TerraformやAWS CLIの使用頻度と公式ガイドの採用例を考慮した結果、Ubuntu 22.04
を採用して環境を構築することにしました。
以下はそれぞれのファイルです。
Dockerfile
FROM ubuntu:22.04
# 環境変数
ENV LANG=ja_JP.UTF-8 \
TF_VERSION=1.9.8 \
AWS_CLI_URL=https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip \
HADOLINT_VERSION=v2.12.0
# システムのアップデートと基本ツールのインストール
RUN apt-get update && \
apt-get upgrade -y && \
apt-get install -y --no-install-recommends \
less \
vim \
curl \
unzip \
git \
make \
locales \
ca-certificates \
openssh-client && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# ロケールの設定
RUN locale-gen ${LANG} en_US.UTF-8 && \
update-locale LANG=${LANG} && \
update-ca-certificates
# AWS CLI v2 のインストール
WORKDIR /tmp/awscli
RUN curl --silent --show-error --fail "$AWS_CLI_URL" -o "awscliv2.zip" && \
unzip -q awscliv2.zip && \
./aws/install && \
rm -rf awscliv2.zip aws
# Terraform のインストール
WORKDIR /tmp/terraform
RUN curl --silent --show-error --fail -L "https://releases.hashicorp.com/terraform/${TF_VERSION}/terraform_${TF_VERSION}_linux_amd64.zip" -o "terraform.zip" && \
unzip -q -d /usr/local/bin/ terraform.zip && \
rm terraform.zip
# git-secrets のインストール
WORKDIR /tmp/git-secrets
RUN git clone https://github.com/awslabs/git-secrets.git . && \
make install && \
rm -rf /tmp/git-secrets
# hadolint のインストール
WORKDIR /tmp/hadolint
RUN curl --silent --show-error --fail -L "https://github.com/hadolint/hadolint/releases/download/${HADOLINT_VERSION}/hadolint-Linux-x86_64" -o /usr/local/bin/hadolint && \
chmod +x /usr/local/bin/hadolint
# vscode ユーザー作成と初期設定
WORKDIR /workspace
RUN adduser --disabled-password --gecos "" vscode && \
mkdir -p /home/vscode/.aws /workspace && \
chown -R vscode:vscode /home/vscode /workspace
# 初期化スクリプトのコピー
COPY .docker/terraform/init.sh /usr/local/bin/init.sh
RUN chmod +x /usr/local/bin/init.sh
# 作業ディレクトリ設定
USER vscode
WORKDIR /workspace
# エントリーポイントの設定
ENTRYPOINT ["/usr/local/bin/init.sh"]
compose.yml
services:
terraform:
container_name: 'terraform'
image: local/terraform
build:
context: .
dockerfile: .docker/terraform/Dockerfile
volumes:
- .:/workspace
- ./.docker/terraform/.aws/config.default:/home/vscode/.aws/config
- ./.docker/terraform/.aws/credentials.default:/home/vscode/.aws/credentials
environment:
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
tty: true
init.sh
#!/bin/bash
# AWS Config の設定
if [[ ! -f /home/vscode/.aws/config ]]; then
mv /home/vscode/.aws/config.default /home/vscode/.aws/config
fi
# AWS Credentials の設定
if [[ ! -f /home/vscode/.aws/credentials ]]; then
mv /home/vscode/.aws/credentials.default /home/vscode/.aws/credentials
sed -i "s/<access-key>/\${AWS_ACCESS_KEY_ID}/g" /home/vscode/.aws/credentials
sed -i "s/<secret-key>/\${AWS_SECRET_ACCESS_KEY}/g" /home/vscode/.aws/credentials
fi
# シェルを起動
exec bash
テストについて
以下のリポジトリはAWS LambdaのHello Worldプロジェクトで、TerraformとGitHub Actionsを使用したCI/CDパイプラインの構築例が含まれている
既存リソースの一括インポート
以下の記事を参考に行っていきます
一括インポートできない
次のエラーが発生し、一括インポートができない問題に直面しました
aws error initializing resources in service accessanalyzer, err: operation error AccessAnalyzer: ListAnalyzers, failed to resolve service endpoint, an AWS region is required, but was not found
そこで次のようにしてみたところインポート自体は成功した
$ terraformer import aws -r="ec2_instance,s3" --regions=ap-northeast-1
リージョンを明記するのも大事そう(明記しなかった場合s3が正しくインポートできなかった)
結論
いくつかのリソースで同時にインポートしようとするとエラーを吐くものがあったので、ひとつずつインポートすることにしました。
ただ、Cloud Map
のリソースがうまく取得できなかったので、これはterraform import
コマンドでインポートしました
ディレクトリ構造の検討
将来的にCloudflareやGCPも同じリポジトリで管理する予定のため、以下を参考にします
インポートしたリソースの整理
以下を参考に整理していきます
調査を進めると、どうやらterrraformerは銀の弾丸ではなかった模様・・・
結局インポートしたリソースを"使って"構築するという認識が正しいのかもしれない
そのままでは使えないですね
さらに、terraformerでインポートすると Terraform 0.12 環境の tfstate になるため、Terraform 1.0 までアップグレードする必要があるようです
まずはインポートしたすべてのリソースのバージョンを、最新の1.9.8にアップグレードしていきます
tfenv を利用し適宜 Terraform のバージョンを変更していますが、下位ディレクトリに .terraform-version ファイルがある場合にはそのバージョンに引っ張られるため、.terraform-version を変更し、作業前に意図したバージョンになっているかを $ terraform version で確認することをオススメします。
0.12 -> 0.13
$ cd generated/aws/ec2_instance
$ tfenv use 0.13.7
$ terraform 0.13upgrade
$ terraform init
$ terraform plan
$ terraform apply
Warning で非推奨の書き方をしている場合があるのでそれを修正しましょう。
Warning: Interpolation-only expressions are deprecated
on outputs.tf line 6, in output "aws_instance_tfer--i-06ab3a2a9cffebacd_hoge_id":
6: value = "${aws_instance.tfer--i-06ab3a2a9cffebacd_hoge.id}"
これは
value = aws_instance.tfer--i-06ab3a2a9cffebacd_hoge.id
と書き換えるだけです。
なお、このエラーの修正作業には以下のGPTs等を活用しました
0.13 -> 0.14
$ tfenv use 0.14.11
$ terraform init
$ terraform plan
$ terraform apply
0.14 -> 1.9.8
$ tfenv use 1.9.8
$ terraform init
$ terraform plan
$ terraform apply
これで 0.12 から最新のバージョンへアップグレードできます。
ローカルにある tfstate の terraform_version が 1.9.8 になっているはずです。
続けて、以下を参考にリソースを一つずつterraformで構築していきます
Terraformで既存のAWSリソースをインポート
次に、terraformerでインポートしたリソースを、各プロダクト名ごとに分割したディレクトリで再構築する作業を進めました。
再利用性を考慮し、利便性が高いリソースについてはモジュール化を行いながら、以下のコマンドを繰り返してリソースのインポートを実施しました。
インポートのコマンド例
Terraformで既存リソースを管理対象に追加するには、以下のようなコマンドを使用します。
モジュールの場合
terraform import module.<モジュール名>.<対応するAWSのリソース名>.this <引数に対応する値>
モジュールを使用しない場合
terraform import <対応するAWSのリソース名>.<terraformで管理する名前> <引数に対応する値>
ポイント
-
<モジュール名>
:再利用を目的としたモジュール名を指定。 -
<対応するAWSのリソース名>
:AWSリソース(例:aws_instance
,aws_s3_bucket
など)を指定。 -
<terraformで管理する名前>
:Terraform内でリソースを識別する名前。 -
<引数に対応する値>
:AWSリソースのIDやARNなど、インポート対象を一意に識別する値。
terraform init
、plan
、apply
の実行
インポート後、terraform init
でTerraform環境を初期化し、terraform plan
で状態の確認を行いました。エラーがないことを確認したらterraform apply
を実行して、管理対象に追加しました。
AWS Organizationsの導入
環境ごとにディレクトリを切って管理する方法を採用しましたが、AWSのベストプラクティスとしては環境ごとにAWSアカウントもわけたほうがいいそうです
というわけでAWS Organizationsを導入し、terraform上でそれらも管理できるようにします
ベストプラクティスについてもしっかり確認します
AWS SSO
AWS Organizationsで複数のアカウントを管理する場合、クレデンシャル管理が煩雑になるためAWS SSOも導入する必要がでてきました
AWS SSOとは
AWS Single Sign-On の略です。AWSの複数アカウントやアプリケーションへのアクセス管理を容易にし、1か所からSSOアクセスを提供するサービスです。
グループ設計について
スタートアップなど少人数での開発を前提とした企業やPJならまだしも、大規模開発/大企業/ミッションクリティカルなPJなどでIAM Policyを適当に設計するのはセキュリティ上かなり危険です。
セキュリティ(情報漏えい)事故の70%以上は内部犯行[1]と言われている昨今では、PJに参加した管理者/開発者/監査人含めて全ての人間が不正を起こす可能性を持っているという性悪説を前提に、権限管理を徹底すべきです
そのため、アクセス権限セットの設定では、PoLP: Principle of Least Privilege(最小権限/最小特権の原則)を必ず意識しましょう。
PJの初期段階でユーザー/グループを細かく設計し、役割ごとにアクセス権限セットのIAM Policyを適切なものに設定すべきです。
terraformで作る場合
マスターアカウントにログインした後に、新たに作成したAWSアカウントに切り替える
AWS IAM Identity Center
昔はAWS SSOと呼ばれていたました
https://zenn.dev/yaasita/articles/aa48bfa06b9a53
情報が少し古かったようです、AWS IAM Identity Centerについて調査をしました
有効化
ここはterraformではなくマネジメントコンソール上から有効化する必要があるみたいです
terraformで設定
AWS SSOでTerraformを使う
aws configure sso
ログインすると以下が更新される
~/.aws/config
terraformの設定
export AWS_PROFILE="<使用したいプロファイル名>"
terraform plan
この場合、main.tf
ではプロファイルの指定をしません
backendの設定
terraform init -backend-config="profile=<SSOプロファイル名>"
AWS Control Towerの設定
IAM Identity Centerで各環境用アカウントを作成し、各環境用のtfstate保存バケットを作成したタイミングでtfsec
によりセキュリティの問題があることを指摘されました
その中で、バケットのロギング用バケットを指定する必要があるのですが、これを一元管理するためにAWS Control Tower
というサービスも必要になりました
上記の記事を参考にし、ひとまずAuditアカウントとLogArchiveアカウントを作成し、Security
OUに集約しました
terraform上でランディングゾーンを作成しようとするとバグが発生する未解決の問題があるため、結局ランディングゾーンは手動で作成しました。
最初に作成したアカウントは自動作成されるOUに再度アタッチすることになったので、最初から手動で作成し、あとからterraform importする方がよかったです。
手動で作成を試みたところ以下のエラーに遭遇しました
ドキュメントなどによると、Organizations
のサービスの設定で有効化されている以下の二つを無効化する必要がありました
Config
だけ無効化してもいけるようです
以下を参考に進めます
成功し、最終的には以下のような構成になりました
Control Towerのサイドメニュー内の「組織」という項目から、すでにOrganizationsで作成済みのアカウントをControl Towerに登録していきます
登録を進めていると以下のエラーに遭遇しました
Management の登録に失敗しました。
AWS Control Tower detected errors that prevent the baseline version from activating on your OU. To continue, navigate to the OU detail page in the console and download a list of items to remediate.
どうやらrootアカウントではこの操作ができないようです
ルートではないアカウントで操作します
マルチアカウントでドメインはどこで持つの問題についても挙げられています
マルチアカウントでのドメインを所持する場所ですが、
各システムの本番環境でドメインを取得するのがよいです。
理由として
- 他の担当者が他のシステムのドメインをいじれる事の防止
- controll towerタワーの親アカウントは、権限付与と請求関係とする役割の分離
- システムを開発会社が変わり移譲する場合も各システム本番で持っていた方が楽(親会社から子会社)
という事が上げられます。
https://syoblog.com/aws-account/
以下を参考に、Control Towerを操作するIAMユーザーに「Service Catalog」内のポートフォリオへのアクセス権限をマネジメントコンソール上から与えます
無事に成功しました
S3バケットのアクセスログの集約について
S3のクロスアカウントレプリケーション
無事にAWS Control Towerの設定がおわって、各アカウントのS3バケットのログをLogArchiveアカウントに集約させようとしたところ、以下のエラーに遭遇しました
│ Error: creating S3 Bucket (tfstate-bucket) Logging: operation error S3: PutBucketLogging, https response error StatusCode: 400, RequestID: E6C0, HostID: FPrRDGkLEU=, api error InvalidTargetBucketForLogging: The owner for the bucket to be logged and the target bucket must be the same.
これは、S3アクセスログの直接集約は、ロギング元バケットと送信先バケットのオーナーが同一である必要がある制約があるため発生しているようです
しかし、LogArchive用アカウントにS3バケットのアクセスログを集約できなければ、アカウントを作った意味がありません。
そこで、この問題を解決するのがS3レプリケーションです
S3レプリケーションとは
S3レプリケーションとは、S3バケットに新しいオブジェクトがアップロードされたら、自動的にレプリケーション先のバケットにコピーが生成される仕組みです。
同じリージョン内でも、他のリージョンでも、他のアカウントにもレプリケーション先のバケットに指定できます。
ほとんどのオブジェクトは15分以内にレプリケートされますが、場合によっては、数時間かかることもあります。RTCを活用すれば99.99%のオブジェクトを15分以内にレプリケートすることができます。
上記を参考に設定を行います
LogArchiveアカウント側には以下を参考に、必要なIAMロールなどを作成します
tfsecをtrivyに置き換え
マルチアカウントでのRoute53の集約について
Amazon Route 53 Profile