こちらの記事では、普段はあまり意識することのないインスタンスプロファイルについて解説します。
インスタンスプロファイルは、TerraformなどのIaCツールを使用してEC2を構築する際に、必ず意識しなければならない重要な要素です。
AWSのマネジメントコンソールからIAM Roleを作成する場合、インスタンスプロファイルは自動で作成されるため普段意識することはありません。TerraformなどのIaCツールを使用する場合、インスタンスプロファイルを明示的に作成しないとEC2インスタンスが構築できません。
本記事では、インスタンスプロファイルとは何か?について説明し、Terraformでの構築手順を詳しく解説します。
Terraformの基本
はじめにTerraformの基本について解説します。
Terraformは、インフラ環境をコードで定義して構築するIaC(Infrastructure as Code)ツールの一つで、アメリカのソフトウェア企業であるHashiCorp社が開発したオープンソースツールです。HCL(HashiCorp Configuration Language)と呼ばれる独自の言語を使用しており、JSONやYAMLと同じような形式で記述をすることができ、読みやすく保守しやすいといった特徴があります。
本記事の後半にはTerraformを使用してインスタンスプロファイルの作成などを行います。Terraformの環境構築については、以下の記事で解説しています。
https://envader.plus/article/162
インスタンスプロファイルとは?
インスタンスプロファイルとは、EC2インスタンスの起動時にアタッチしたいIAM Roleの情報を起動するインスタンスに渡す役割を果たします。公式ドキュメントでは、インスタンスプロファイルはIAM Roleのコンテナであると表現されています。
マネジメントコンソールからIAM Roleを作成した場合、インスタンスプロファイルは作成したIAM Roleと同じ名前のインスタンスプロファイルを作成します。そのため、インスタンスプロファイルを普段は意識することが少ないです。
AWS Management Console を使用して Amazon EC2 のロールを作成する場合、コンソールはインスタンスプロファイルを自動的に作成し、そのインスタンスプロファイルにロールと同じ名前を付けます。
IaCではインスタンスプロファイルを個別に作成する必要がある
自動的にインスタンスプロファイルを作成してくれる機能は非常に便利ですが、注意しなければいけません。自動でインスタンスプロファイルを作成してくれるのは、あくまでマネジメントコンソールでIAM Roleを作成した場合のみです。
AWS CLIやIaCツールを使用してIAM Roleを作成し、EC2インスタンスへアタッチするためには明示的にインスタンスプロファイルを作成する必要があります。この点を見落とし、TerraformなどのIaCツールからIAM RoleをアタッチしてEC2インスタンスを起動しようとするとエラーが発生します。
AWS CLIまたはAWS API からロールを管理する場合、ロールとインスタンスプロファイルを別個のアクションとして作成します。ロールとインスタンスプロファイルには異なる名前を使用できるため、インスタンスプロファイルの名前とこれらのプロファイルに含まれるロールの名前を知っている必要があります。これにより、EC2 インスタンスを起動するときに適切なインスタンスプロファイルを選択できます。
出典: インスタンスプロファイルの管理 (AWS CLI または AWS API)
Terraformハンズオン
ここからはTerraformを使って、インスタンスプロファイル、EC2インスタンスを作成します。
今回のファイル構成
今回解説していくTerraformのファイル構成は、理解しやすいようリソースに関する記述はmain.tf
の中に全てまとめています。Terraformとプロバイダーの設定のみversions.tf
としてファイルを分けて管理しています。
versions.tfの作成
versions.tfを作成し、以下の内容を記述します。
# versions.tf
terraform {
required_version = ">= 1.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "5.3.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
main.tfファイルの全容
はじめにファイルの全容を記載します。
# main.tf
# ------------------------------
# locals variable
# ------------------------------
locals {
role_name = "ec2ssm-role"
instance_type = "t2.micro"
vpc_cidr = "10.0.0.0/16"
subnet_cidr = "10.0.1.0/24"
}
# ------------------------------
# Refer to the Data Sources
# ------------------------------
# IAM Policy Document
data "aws_iam_policy_document" "assumerole_ec2" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}
# EC2 AMI
data "aws_ami" "amlx2_latest" {
owners = ["amazon"]
most_recent = true
filter {
name = "name"
values = ["amzn2-ami-hvm-2.0.*.0-x86_64-gp2"]
}
}
# ------------------------------
# VPC
# ------------------------------
resource "aws_vpc" "main" {
cidr_block = local.vpc_cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "my_vpc"
}
}
# ------------------------------
# IGW
# ------------------------------
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.main.id
tags = {
Name = "main-igw"
}
}
# ------------------------------
# Subnet
# ------------------------------
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = local.subnet_cidr
availability_zone = "ap-northeast-1a"
tags = {
Name = "public-subnet"
}
}
# ------------------------------
# Route Table
# ------------------------------
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
tags = {
Name = "public-route-table"
}
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
# ------------------------------
# IAM Role
# ------------------------------
resource "aws_iam_role" "ssm" {
name = local.role_name
path = "/"
assume_role_policy = data.aws_iam_policy_document.assumerole_ec2.json
tags = {
Roles = "ssm"
}
}
resource "aws_iam_role_policy_attachment" "ssm" {
role = aws_iam_role.ssm.name
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
# ------------------------------
# Instance Profile
# ------------------------------
resource "aws_iam_instance_profile" "ssm" {
name = local.role_name
role = aws_iam_role.ssm.name
tags = {
Roles = "ssm"
}
}
# ------------------------------
# EC2
# ------------------------------
resource "aws_instance" "main" {
ami = data.aws_ami.amlx2_latest.id
instance_type = local.instance_type
iam_instance_profile = aws_iam_role.ssm.name
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.allow_ssm.id]
associate_public_ip_address = true
tags = {
Name = "main-ec2"
}
}
# ------------------------------
# Security Group
# ------------------------------
resource "aws_security_group" "allow_ssm" {
name = "allow_ssm"
description = "Allow SSM connections"
vpc_id = aws_vpc.main.id
egress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "allow-ssm-sg"
}
}
locals変数
locals {
role_name = "ec2ssm-role"
instance_type = "t2.micro"
vpc_cidr = "10.0.0.0/16"
subnet_cidr = "10.0.1.0/24"
}
main.tf
ファイルで使用するlocal変数を定義しています。Terraformの変数にはinput、local、outputの3つがありますが、今回はlocal変数で指定します。変数については以下の記事にて解説しています。
https://envader.plus/article/190
https://envader.plus/article/191
インスタンスプロファイルの作成
今回インスタンスプロファイルは以下のように定義しました。
resource "aws_iam_instance_profile" "ssm" {
name = local.role_name
role = aws_iam_role.ssm.name
tags = {
Roles = "ssm"
}
}
aws_iam_instance_profile
を定義することで、インスタンスプロファイルを作成することができます。role
で後述するIAM Role名を指定しています。
TerraformでEC2インスタンスにIAM Roleをアタッチする場合、このように明示的にインスタンスプロファイルを作成しないとplan
は通るがapply
したときにエラーが発生する。という事態におちいるため注意が必要です。
インスタンスプロファイルを作成しなかった場合、以下のエラーが発生します。
Error: creating EC2 Instance: InvalidParameterValue: Value (ec2ssm-role) for parameter iamInstanceProfile.name is invalid. Invalid IAM Instance Profile name
│ status code: 400, request id: 0cdac60b-0ce6-42d4-a3f9-409e8d938e8b
│
│ with aws_instance.main,
│ on main.tf line 134, in resource "aws_instance" "main":
│ 134: resource "aws_instance" "main" {
IAM Roleの作成
IAM Roleを作成します。
resource "aws_iam_role" "ssm" {
name = local.role_name
path = "/"
assume_role_policy = data.aws_iam_policy_document.assumerole_ec2.json
tags = {
Roles = "ssm"
}
}
assume_role_policy
では、このIAM Roleを引き受けることができる対象を定義しています。今回はdata
ブロックのidentifiers
で定義したEC2を対象として指定しています。
data "aws_iam_policy_document" "assumerole_ec2" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}
IAM PolicyをIAM Roleへアタッチする
続いてIAM Policyをアタッチします。IAM Policyを作成したIAM Roleにアタッチするには、aws_iam_role_policy_attachment
を使用します。
resource "aws_iam_role_policy_attachment" "ssm" {
role = aws_iam_role.ssm.name
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
role
の部分でどのIAM Roleにアタッチするかを指定しています。policy_arn
ではアタッチされるIAM PolicyのARNを指定しています。今回はAWS Systems Managerを利用して、EC2を管理するために必要なポリシーをアタッチしています。
IAM Roleの基本については以下の記事にて解説しています。
https://envader.plus/article/246
また応用的な考え方や、TerraformでのIAM Role、IAM Policyの作成方法については以下の記事をご覧ください。
https://envader.plus/article/247
EC2の作成
EC2を作成します。
resource "aws_instance" "main" {
ami = data.aws_ami.amlx2_latest.id
instance_type = local.instance_type
iam_instance_profile = aws_iam_role.ssm.name
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.allow_ssm.id]
associate_public_ip_address = true
tags = {
Name = "main-ec2"
}
}
ここでは、iam_instance_profile
でアタッチしたいIAM Roleを指定しています。
今回はSSMで管理できるインスタンスを作成するため、associate_public_ip_address
をtrue
に設定しパブリックIPアドレスを付与しています。
TerraformでEC2を作成する際には、associate_public_ip_address
をtrue
に設定しないとパブリックIPアドレスが付与されません。IAM RoleをインスタンスにアタッチしてもSSM接続できなくなってしまうため注意が必要です。
また、SSMをEC2インスタンスで利用する場合、セキュリティグループはアウトバウンドの443ポートを許可する必要があります。そのため、今回はインバウンドルールは設定せずにアウトバウンドの443ポートのみを許可しています。
resource "aws_security_group" "allow_ssm" {
name = "allow_ssm"
description = "Allow SSM connections"
vpc_id = aws_vpc.main.id
egress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "allow-ssm-sg"
}
}
まとめ
コンソール上で環境構築をする際には意識することのないインスタンスプロファイルですが、IaCで環境構築する際には明示的に作成する必要があります。
TerraformなどのIaCツールを使うことで、今まで意識しなくても良かったことに気付けることもあるため、こういった気付きもまた楽しみの一つではないでしょうか?
今回のサンプルコードを元に、インスタンスプロファイルを作成する場合と作成しない場合の比較を行ってみるのも楽しいと思います。
エンベーダー編集部
エンベーダーは、ITスクールRareTECHのインフラ学習教材として誕生しました。 「遊びながらインフラエンジニアへ」をコンセプトに、インフラへの学習ハードルを下げるツールとして運営されています。
関連記事
2020.02.25
完全未経験からエンジニアを目指す爆速勉強法
USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話
- キャリア・学習法
- エンジニア
2024.04.14
手を動かして身につける AWSでサーバレスのハンズオン
サーバレスコンピューティングの最大のメリットは、インフラの管理から解放されることです。開発者はサーバーのプロビジョニングや保守に関する心配をする必要がなく、アプリケーションのコードの作成とその実行に専念できます。より迅速にアプリケーションを市場に投入することが可能となり、イノベーションの速度を高めることができます。
- AWS
- ハンズオン
2023.11.21
【Part 3/4】AWSの環境構築で学ぶトラブルシューティング【Route53】
今回は、Route53を使用し、特定のインスタンスにドメインでアクセスできるようにする環境を構築します。AWSのプライベートネットワークを介した安全な通信を利用する方法を学習します。
- AWS
2023.04.24
Amazon CodeWhispererの特徴とは?VSCodeへの導入方法と合わせて解説
Amazon CodeWhispererとは、オープンソースリポジトリやAmazon内部のリポジトリ、APIドキュメントなどから収集した数十億行のソースコードを機械学習させ、自動でコードを生成および提案してくれるサービスです。
- AWS
- プログラミング