1. ホーム
  2. 記事一覧
  3. Terraformで解決! AWS インスタンスプロファイルの作成方法

2023.12.26

Terraformで解決! AWS インスタンスプロファイルの作成方法

こちらの記事では、普段はあまり意識することのないインスタンスプロファイルについて解説します。

インスタンスプロファイルは、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_addresstrueに設定しパブリックIPアドレスを付与しています。

TerraformでEC2を作成する際には、associate_public_ip_addresstrueに設定しないとパブリック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のインフラ学習教材として誕生しました。 「遊びながらインフラエンジニアへ」をコンセプトに、インフラへの学習ハードルを下げるツールとして運営されています。

RareTECH 無料体験授業開催中! オンラインにて実施中! Top10%のエンジニアになる秘訣を伝授します! 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
  • プログラミング