こちらの記事では、普段はあまり意識することのないインスタンスプロファイルについて解説します。
インスタンスプロファイルは、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ツールを使うことで、今まで意識しなくても良かったことに気付けることもあるため、こういった気付きもまた楽しみの一つではないでしょうか?
今回のサンプルコードを元に、インスタンスプロファイルを作成する場合と作成しない場合の比較を行ってみるのも楽しいと思います。
【番外編】USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話
プログラミング塾に半年通えば、一人前になれると思っているあなた。それ、勘違いですよ。「なぜ間違いなの?」「正しい勉強法とは何なの?」ITを学び始める全ての人に知って欲しい。そう思って書きました。是非読んでみてください。
「フリーランスエンジニア」
近年やっと世間に浸透した言葉だ。ひと昔まえ、終身雇用は当たり前で、大企業に就職することは一種のステータスだった。しかし、そんな時代も終わり「優秀な人材は転職する」ことが当たり前の時代となる。フリーランスエンジニアに高価値が付く現在、ネットを見ると「未経験でも年収400万以上」などと書いてある。これに釣られて、多くの人がフリーランスになろうとITの世界に入ってきている。私もその中の1人だ。数年前、USBも知らない状態からITの世界に没入し、そこから約2年間、毎日勉学を行なった。他人の何十倍も努力した。そして、企業研修やIT塾で数多くの受講生の指導経験も得た。そこで私は、伸びるエンジニアとそうでないエンジニアをたくさん見てきた。そして、稼げるエンジニア、稼げないエンジニアを見てきた。
「成功する人とそうでない人の違いは何か?」
私が出した答えは、「量産型エンジニアか否か」である。今のエンジニア市場には、量産型エンジニアが溢れている!!ここでの量産型エンジニアの定義は以下の通りである。
比較的簡単に学習可能なWebフレームワーク(WordPress, Rails)やPython等の知識はあるが、ITの基本概念を理解していないため、単調な作業しかこなすことができないエンジニアのこと。
多くの人がフリーランスエンジニアを目指す時代に中途半端な知識や技術力でこの世界に飛び込むと返って過酷な労働条件で働くことになる。そこで、エンジニアを目指すあなたがどう学習していくべきかを私の経験を交えて書こうと思った。続きはこちらから、、、、
エンベーダー編集部
エンベーダーは、ITスクールRareTECHのインフラ学習教材として誕生しました。 「遊びながらインフラエンジニアへ」をコンセプトに、インフラへの学習ハードルを下げるツールとして運営されています。
関連記事
2020.02.25
完全未経験からエンジニアを目指す爆速勉強法
USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話
- キャリア・学習法
- エンジニア
2024.08.24
【Terraformハンズオン】NatGatewayを使ってプライベートな通信を実現してみよう
AWSを扱う実際の現場では、NAT Gatewayを使うことでより安全なインターネット通信を実現することができます。ぜひ一緒に理解を深めながら、Terraformでの実装方法を学んでいきましょう。
- AWS
- Terraform
2024.08.26
Azureサブスクリプションの理解とAWS・Google Cloudの管理単位の違い
この記事は、Azure、AWS、Google Cloudといった主要なクラウドプラットフォームを利用する方々、特にこれからクラウドを導入しようとしている企業のIT担当者やエンジニアを対象にしています。
- AWS
- GCP
- Azure
2024.03.25
AWSとAzureのストレージサービス徹底比較
本記事では、AWSとAzureが提供するストレージサービスの違いを明らかにし、各サービスの特徴、適用シナリオ、利点を解説します。クラウドストレージの初心者や選定を行う方々向けに、プロジェクトやビジネスニーズに最適なストレージ選択の決定に役立つ知識を提供します。
- AWS
- Azure