AWS事業本部のイシザワです。
コンソールやIaCでEC2インスタンス内のOSの設定やパッケージインストール、アプリケーションデプロイ等々のシステム層以上の設定をする方法はいくつかあります。
User dataを使う方法やCloudFormationでcfn-initを使う方法もありますが、今回はSSM State Managerを使ってEC2インスタンスにNGINXをインストールしたいと思います。
cfn-initでなくSSM State Managerを使う利点は以下のAWSブログが詳しいです。
CloudFormation で cfn-init に代えて State Manager を利用する方法とその利点
以下はブログからの抜粋です。
State Managerを使用すると、cfn-initと同様に多くのタスクを実行できますが、さらに追加のメリットがあります。
- ログをAmazon Simple Storage Service (S3)に集約
- CloudFormationテンプレートの簡素化
- 設定コンプライアンスの可視性の向上
- AWS CloudTrailと統合され改善された監査
- 初期プロビジョニング後のスケジュールに基づく設定
- タグを使用して起動された新しいインスタンスの設定
- 同時実行値とエラーしきい値の設定
ソースコード
Terraformのソースコードは以下のGitHubレポジトリにあります。
SSM State ManagerでEC2インスタンスへの関連付けを作成することで、初期構築時にインスタンスに対してAnsibleを実行します。 Ansibleのプレイブックはリポジトリ内にあります。
今回の構成ではState ManagerはS3バケットからAnsibleプレイブックを取得するよう設定しています。 そのため、AnsibleのプレイブックをZIPで圧縮してS3バケットにアップロードする必要があるのですが、Terraformを使えばこの工程も含めて一撃でシステムを構築することができます。
ソースコードの解説
ソースコードの一部をピックアップして解説します。
Ansibleプレイブックのアップロード
data.archive_file.ansible
でローカルのAnsibleプレイブックをZIPに圧縮し、ZIPファイルをaws_s3_object.ansible
でS3バケットにアップロードします。
s3.tf
data "archive_file" "ansible" {
type = "zip"
source_dir = "./ansible"
output_path = local.playbook_archive_file_path
}
resource "aws_s3_object" "ansible" {
bucket = module.ansible_bucket.name
key = local.playbook_file_name
source = local.playbook_archive_file_path
etag = data.archive_file.ansible.output_md5
depends_on = [
module.ansible_bucket
]
}
IAMロール
EC2インスタンスにはS3バケットからAnsibleプレイブックを取得する権限と、ログ保管用S3バケットにSSMのログファイルを格納する権限が必要です。
SSMの機能を使用するため、IAMロールにAWS管理ポリシーのAmazonSSMManagedInstanceCore
をアタッチする必要もあります。
iam.tf
data "aws_iam_policy_document" "read_bucket" {
statement {
effect = "Allow"
actions = [
"s3:GetObject",
]
resources = [
"${module.ansible_bucket.arn}/*",
]
}
statement {
effect = "Allow"
actions = [
"s3:GetObject",
"s3:PutObject",
"s3:PutObjectAcl",
]
resources = [
"${module.ssm_log_bucket.arn}/*",
]
}
}
resource "aws_iam_policy" "read_bucket" {
name = "read_bucket_policy"
policy = data.aws_iam_policy_document.read_bucket.json
}
resource "aws_iam_role" "web_server" {
name = "web_server_role"
assume_role_policy = data.aws_iam_policy_document.assume_role.json
managed_policy_arns = [
aws_iam_policy.read_bucket.arn,
"arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore",
]
}
SSM State Manager
State Managerの関連付けを作成します。ソースファイルにS3にアップロードしたZIPファイルを指定します。
ssm.tf
resource "aws_ssm_association" "ansible" {
name = "AWS-ApplyAnsiblePlaybooks"
parameters = {
SourceType = "S3",
SourceInfo = jsonencode({
path = "https://s3.amazonaws.com/${module.ansible_bucket.name}/${local.playbook_file_name}"
}),
PlaybookFile = "site.yml",
InstallDependencies = "True",
}
targets {
key = "InstanceIds"
values = [aws_instance.web_server.id]
}
output_location {
s3_bucket_name = module.ssm_log_bucket.name
}
depends_on = [
aws_s3_object.ansible
]
}
動作確認
以下はterraform apply
コマンドの実行結果です。IPアドレスはマスクしています。
$ terraform apply
(中略)
Apply complete! Resources: 18 added, 0 changed, 0 destroyed.
Outputs:
instance_ip = "aaa.bbb.ccc.ddd"
State Managerの関連付けの実行に数分かかります。しばらく経ってからcurl
コマンドで↑で出力されたIPに対してHTTPリクエストをするとレスポンスが返ってきます。
$ curl http://aaa.bbb.ccc.ddd
<html><h1>Hello World!</h1></html>
終わりに
SSM State Managerを使うことで、Terraformでの初期構築時にEC2インスタンスの初期化をしてみました。
この記事が誰かのお役に立てれば幸いです。