2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Terraformを使ってEC2でEFSをマウントする方法

Last updated at Posted at 2023-12-08

はじめに

TerraformでEC2を作成する時に初回起動時にスクリプトを実行したい場合のUser Dataの記載方法の引継ぎになりますが、
今回は、Terraformを使ってEC2でEFSをマウントする方法について解説します。Terraformを使用して、手軽に環境を構築し、EFSをEC2にマウントする手順をご紹介します。

目次

  • 作成するリソース
  • 作成するファイル
  • 解説

作成するリソース

  • VPC
  • サブネット
  • ルートテーブル
  • セキュリティーグループ
  • インターネットゲートウェイ
  • EFS
  • EC2

作成するファイル

  • variables.tf
  • main.tf
  • script.sh
  • outputs.tf

variables.tf

variables.tf
#network関連
variable "region" {
  default = "us-east-1"  # Change this to your desired AWS region
}

variable "vpc_cidr" {
  default = "10.0.0.0/16"  # Change this to your desired VPC CIDR block
}

variable "public_subnet_cidrs" {
  default = ["10.0.1.0/24", "10.0.2.0/24"]  # Change these to your desired public subnet CIDR blocks
}

variable "private_subnet_cidrs" {
  default = ["10.0.3.0/24", "10.0.4.0/24"]  # Change these to your desired private subnet CIDR blocks
}





#EFS関連

variable "subnets" {
  description = "List of subnet IDs where EFS mount targets will be created."
  type        = list(string)
  default     = ["subnet-12345678", "subnet-87654321"] # Replace with your actual subnet IDs
}

variable "efs_name" {
  description = "Name of the Amazon EFS file system."
  type        = string
  default     = "your-efs-name" # Replace with your desired EFS name
}

variable "tag" {
  description = "Tag for the Amazon EFS file system."
  type        = string
  default     = "your-tag" # Replace with your desired tag
}

variable "security_group_id" {
  description = "ID of the security group for EFS mount targets."
  type        = string
  default     = "sg-0123456789abcdef0" # Replace with your actual security group ID
}








#EC2関連

variable "instance_type" {
  default = "t2.micro" # Replace with your desired EC2 instance type
}

variable "availability_zone" {
  default = "us-east-1a" # Replace with your desired availability zone
}

variable "subnets" {
  default = aws_subnet.private[*].id
}

variable "security_group_ids" {
  default = [aws_security_group.ec2.id] # Replace with your actual security group ID
}

variable "key_pair_name" {
  default = "YOUR_KEY_PAIR_NAME" # Replace with your actual key pair name
}

variable "efs_dns_name" {
#efsのモジュールのoutputから${var.efs_dns_name} 経由で参照させる
}

main.tf

main.tf
#network関連
# Create VPC
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags = {
    Name = "main-vpc"
  }
}

# Create internet gateway
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
  tags = {
    Name = "main-igw"
  }
}

# Create public subnets
resource "aws_subnet" "public" {
  count             = length(var.public_subnet_cidrs)
  vpc_id            = aws_vpc.main.id
  cidr_block        = var.public_subnet_cidrs[count.index]
  availability_zone = element(data.aws_availability_zones.available.names, count.index)
  map_public_ip_on_launch = true
  tags = {
    Name = "public-subnet-${count.index + 1}"
  }
}

# Create private subnets
resource "aws_subnet" "private" {
  count             = length(var.private_subnet_cidrs)
  vpc_id            = aws_vpc.main.id
  cidr_block        = var.private_subnet_cidrs[count.index]
  availability_zone = element(data.aws_availability_zones.available.names, count.index)
  map_public_ip_on_launch = false
  tags = {
    Name = "private-subnet-${count.index + 1}"
  }
}

# Create route tables
resource "aws_route_table" "public" {
  count  = length(var.public_subnet_cidrs)
  vpc_id = aws_vpc.main.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }
  tags = {
    Name = "public-route-table-${count.index + 1}"
  }
}

resource "aws_route_table" "private" {
  count  = length(var.private_subnet_cidrs)
  vpc_id = aws_vpc.main.id
  tags = {
    Name = "private-route-table-${count.index + 1}"
  }
}

# Associate subnets with route tables
resource "aws_route_table_association" "public" {
  count           = length(var.public_subnet_cidrs)
  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public[count.index].id
}

resource "aws_route_table_association" "private" {
  count           = length(var.private_subnet_cidrs)
  subnet_id      = aws_subnet.private[count.index].id
  route_table_id = aws_route_table.private[count.index].id
}

# Create security groups
resource "aws_security_group" "ec2" {
  vpc_id = aws_vpc.main.id
  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  # Add other ingress/egress rules as needed for your EC2 instances
  tags = {
    Name = "ec2-security-group"
  }
}

resource "aws_security_group" "efs" {
  vpc_id = aws_vpc.main.id
  ingress {
    from_port           = 2049
    to_port             = 2049
    protocol            = "tcp"
    security_group_ids  = [aws_security_group.ec2.id]  # Allow EC2 instances to access EFS
  }
  # Add other ingress/egress rules as needed for your EFS
  tags = {
    Name = "efs-security-group"
  }
}

resource "aws_security_group" "load_balancer" {
  vpc_id = aws_vpc.main.id
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  # Add other ingress/egress rules as needed for your load balancer
  tags = {
    Name = "load-balancer-security-group"
  }
}











#EFS関連
# Amazon EFS file system
resource "aws_efs_file_system" "efs" {
  performance_mode = "generalPurpose" # Replace with your desired performance mode
  throughput_mode  = "bursting"       # Replace with your desired throughput mode
  encrypted        = true
  lifecycle_policy {
    transition_to_ia = "AFTER_7_DAYS" # Replace with your desired lifecycle transition setting
  }

  tags = {
    Name = "YOUR_EFS_NAME" # Replace with your desired EFS name
    Tag  = "YOUR_TAG"      # Replace with your desired tag
  }
}

# Amazon EFS backup policy
resource "aws_efs_backup_policy" "policy" {
  file_system_id = aws_efs_file_system.efs.id

  backup_policy {
    status = "ENABLED" # Replace with "DISABLED" if you don't want to enable backups
  }
}

# Amazon EFS mount targets
resource "aws_efs_mount_target" "efs-mount" {
  count            = length(var.subnets)
  file_system_id   = aws_efs_file_system.efs.id
  subnet_id        = element(var.subnets, count.index)
  security_groups  = [aws_security_group.ec2.id]  # Replace with your actual security group ID
}

# Amazon EFS file system policy
resource "aws_efs_file_system_policy" "efa_policy" {
  file_system_id = aws_efs_file_system.efs.id
  policy = <<-EOF
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": ["elasticfilesystem:ClientMount"],
          "Principal": {
            "AWS": "*"
          }
        }
      ]
    }
  EOF
}










#EC2関連
# Create an EC2 instance

data "template_file" "user_data" {
  template = "${file("./script.sh")}"
  #template = "${file("${path.module}/script.sh")}" # 必要に応じてmoduleディレクトリに対し、shellスクリプトが存在するパスを変えてください。
  vars = {
      efs_dns_name = "${var.efs_dns_name}"
  }
}


resource "aws_instance" "ec2" {
  ami                         = data.aws_ssm_parameter.ec2_ami_id.value
  instance_type               = var.instance_type
  availability_zone           = var.availability_zone
  disable_api_termination     = false
  ebs_optimized               = false
  associate_public_ip_address = true
  iam_instance_profile        = aws_iam_instance_profile.InstanceProfile.name
  hibernation                 = false
  subnet_id                   = element(var.subnets, 0)  # Assuming only one private subnet is used
  vpc_security_group_ids      = var.security_group_ids
  key_name                    = var.key_pair_name

  # EBS block device configuration
  ebs_block_device {
    device_name           = "/dev/sdm"
    volume_size           = 50
    volume_type           = "gp2"
    encrypted             = true
    delete_on_termination = true
  }

  # Tags for the EC2 instance
  tags = {
    Name = "YOUR_INSTANCE_NAME" # Replace with your desired instance name
    Tag  = "YOUR_TAG"           # Replace with your desired tag
  }

  user_data = data.template_file.user_data.rendered
}

# IAM Role for the EC2 instance
resource "aws_iam_role" "InstanceRole" {
  name = "YOUR_ROLE_NAME" # Replace with your desired IAM role name

  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Action = "sts:AssumeRole",
        Effect = "Allow",
        Principal = {
          Service = "ec2.amazonaws.com"
        }
      }
    ]
  })

  managed_policy_arns = [
    "arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy",
    "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore",
    "arn:aws:iam::aws:policy/AmazonElasticFileSystemFullAccess"
  ]

  tags = {
    Tag = "YOUR_TAG" # Replace with your desired tag
  }
}

# IAM Instance Profile for the EC2 instance
resource "aws_iam_instance_profile" "InstanceProfile" {
  name = "YOUR_INSTANCE_PROFILE_NAME" # Replace with your desired instance profile name
  role = aws_iam_role.InstanceRole.name
  path = "/"
  tags = {
    Tag = "YOUR_TAG" # Replace with your desired tag
  }
}

script.sh

script.sh
#!/bin/bash

# アップデートのインストール
sudo yum update -y

# /efs ディレクトリが存在するか確認し、存在しない場合は作成する
if [ ! -d "/efs" ]; then
  sudo mkdir /efs
  sudo chown ec2-user:ec2-user /efs
fi

# Amazon EFS ユーティリティがインストールされているか確認し、インストールされていない場合はインストールする
if ! command -v mount.efs &> /dev/null; then
  sudo yum install -y amazon-efs-utils
fi

# /efs が既にマウントされているか確認し、されていない場合はマウントする
if ! mountpoint -qd /efs; then
  # EFS DNS 名をefsのモジュールから${var.efs_dns_name} 経由で参照させる
  mount_output=$(sudo mount -t efs ${var.efs_dns_name}:/ /efs/ 2>&1)

  if [ $? -eq 0 ]; then
    # マウント成功時のメッセージとコマンドの出力をファイルに書き込む
    echo -e "EFS successfully mounted on $(date)\n\nMount Command Output:\n$mount_output" | sudo tee /efs/mount_confirmation.txt
  else
    # マウント失敗時のメッセージとコマンドの出力をファイルに書き込む
    echo -e "EFS mount failed on $(date). Check the mount command and EFS configuration.\n\nMount Command Output:\n$mount_output" | sudo tee /efs/mount_confirmation.txt
    # もしくは、エラーログを標準エラー出力に表示する
    # echo -e "EFS mount failed on $(date). Check the mount command and EFS configuration.\n\nMount Command Output:\n$mount_output" >&2
  fi
fi


# ブート時に自動的にマウントされるように /etc/fstab にエントリを追加する
echo "${var.efs_dns_name}:/ /efs efs defaults,_netdev 0 0" | sudo tee -a /etc/fstab

outputs.tf

outputs.tf
#Network関連
output "vpc_id" {
  value = aws_vpc.main.id
}

output "public_subnet_ids" {
  value = aws_subnet.public[*].id
}

output "private_subnet_ids" {
  value = aws_subnet.private[*].id
}

output "ec2_security_group_id" {
  value = aws_security_group.ec2.id
}

output "efs_security_group_id" {
  value = aws_security_group.efs.id
}

output "load_balancer_security_group_id" {
  value = aws_security_group.load_balancer.id
}

#EFS関連
output "efs_id" {
  description = "ID of the created Amazon EFS file system."
  value       = aws_efs_file_system.efs.id
}

output "efs_dns_name" {
  description = "DNS name of the created Amazon EFS file system."
  value       = aws_efs_file_system.efs.dns_name
}

output "efs_mount_target_ids" {
  description = "IDs of the created Amazon EFS mount targets."
  value       = aws_efs_mount_target.efs-mount[*].id
}

解説

1. なぜ別のシェルファイル(script.sh)を使用するのか?

Terraformは、インフラストラクチャのコードを管理するためのツールですが、ユーザーデータの直接埋め込みは非効率的であり、可読性が低くなります。そのため、ユーザーデータを別ファイル(ここではscript.sh)に分離することが一般的です。これにより、Terraformのメインコードがシンプルで保守しやすくなります。

2. Bashスクリプト(script.sh)の動作

Bashスクリプト(script.sh)はEC2インスタンス内で実行され、以下の手順を実行します。

a. アップデートのインストール

EC2インスタンスのパッケージを最新の状態に更新します。

b. /efs ディレクトリの作成

/efs ディレクトリが存在しない場合、作成し、適切な権限を設定します。

c. Amazon EFS ユーティリティのインストール

mount.efs がインストールされていない場合、Amazon EFS ユーティリティをインストールします。

d. /efs のマウント

/efs がまだマウントされていない場合、指定されたEFS DNS名を使用してEFSを /efs にマウントします。また、確認ファイルも作成します。

e. ブート時の自動マウント

/etc/fstab にエントリを追加して、EC2インスタンスのブート時にEFSが自動的にマウントされるようにします。

3. Terraformコードのメインファイル(main.tf)

a. ネットワーク関連

Terraformを使用して、VPC、サブネット、ルートテーブル、セキュリティーグループ、インターネットゲートウェイを作成します。

b. EC2関連

EC2インスタンスの作成と設定を行います。Bashスクリプト(script.sh)を使用してEC2インスタンスに必要な構成を適用します。IAMロールやプロファイルも構成されます。

#####c. EFS関連
Amazon EFSファイルシステムと関連するリソース(マウントターゲット、バックアップポリシーなど)を作成します。

4. Terraformの変数(variables.tf)

a. なぜ変数を使用するのか?

変数を使用することで、コードの再利用性が向上し、パラメータを簡単に変更できます。例えば、AWSリージョンやCIDRブロック、インスタンスタイプなどを変更する際に便利です。

b. 特定の変数の注意点

var.efs_dns_name はEFSモジュールのoutputから参照され、EC2インスタンスのユーザーデータに渡されます。これにより、EFSのDNS名、ID、ARNなどの情報を手動で設定する手間が省け、Terraformが自動的にそれらの情報を挿入します。

具体的には、以下の流れがあります:

TerraformがEFSを作成し、そのDNS名が output でEFSモジュールから取得されます。
EC2インスタンスの user_data はBashスクリプト(script.sh)として定義され、その中で${var.efs_dns_name} として参照されます。
BashスクリプトがEC2インスタンス内で実行される際、${var.efs_dns_name} は実際のEFSのDNS名に置き換えられます。
これにより、EC2インスタンスは自動的に正しいEFSにマウントされ、手動でDNS名やIDを設定する手間が省かれます。
この仕組みにより、構築プロセスが自動化され、コードのメンテナンスが簡素化されます。手動で入力するリスクが低減し、一貫性が保たれるため、作業効率が向上します。

5. Terraformの出力(outputs.tf)

Terraformの実行後に表示される情報を定義します。例えば、作成したVPCのID、EFSのIDやDNS名、マウントターゲットのIDなどがここで出力されます。

これで、Terraformを使ってEC2でEFSをマウントするTerraformの基本的なセットアップが完了しました。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?