Movatterモバイル変換


[0]ホーム

URL:


LoginSignup
20

Go to list of users who liked

16

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 5 years have passed since last update.

[AWS] Terraform で EFS 作って、EC2 起動時にマウントさせておく

Last updated atPosted at 2017-04-18

Terraform を使うと、EFS を作成して EC2 にマウントさせておくなんてことが簡単にできます。
Autoscaling 環境で Web ドキュメントルートを共有したい時とかに便利なんで、みんな使えばいいと思うよ。
なお、この記事の想定読者は AWS はダッシュボードからポチポチしてインスタンス立てたりしてるけど、そろそろインフラをコードで管理したいな。Terraform とか便利そうだねー使ってみたいねーって人です。
てわけで、単純に EC2 立ち上げても面白くないので EFS をマウントさせてみました。

そもそも、Terraform ってなんだ?って人は、以下のページとか参考になると思います。
Terraform簡易チュートリアル on AWS

実際の設定

Terraform は、特定のディレクトリ下にある拡張子が .tf なファイルを全部読み込んでいい感じにリソースを起動してくれます。なので、機能別に .tf 作成していってみましょう。

メイン設定

まず、メインの設定を作成。
プロバイダーとか、設定ファイル内で使用する変数とか設定していってみましょうか。

main.tf
# 今回のプロジェクト名variable "project" {}variable "domain" {}# AWS リソースを起動するリージョンとかの情報variable "region" { default = "us-west-2" }variable "azs" {    default {        "a" = "us-west-2a"        "b" = "us-west-2b"        "c" = "us-west-2c"    }}# AMI ID (Amazon Linux)variable "ami" {     default {        "us-west-2" = "ami-8ca83fec"    }}# EC2 接続用の SSH 鍵の公開鍵variable "ssh_public_key" {}provider "aws" {    region = "${var.region}"}

variable で設定した値は tf ファイル内で${var.region} のようにして参照可能です。
また、terraform の各種コマンドを実行する際に以下のようにパラメータとして変数を渡して上書きすることもできます。

$terraform plan\-var'project=example'\-var'domain=example.com'

同じディレクトリ内にterraform.tfvars というファイルがあれば、それを読み込んで値が上書きされたりします。この辺の詳細は以下を参照してください。
Input Variables - Terraform by HashiCorp

provider "aws" は、aws を使いますよって宣言です。
以下のように アクセスキーを書いておくこともできますが、それやるとうっかり github とかに公開した時とかに切ない目にあうのでやめたほうが吉でしょう。

provider "aws" {    access_key = "__ACCESS_KEY__"    secret_key = "__SECRET_KEY__"    region = "us-west-2"}

環境変数AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY を読み込んでいい感じでやってくれるので、僕はdirenv 使って作業ディレクトリ内で環境変数を変更することで対応してます。
(もちろん、この場合でも.gitignore.envrc を含めておいて間違って公開しないようにしないと切ない目にあうので注意)

VPC の作成

こんな感じの .tf ファイルで VPC と subnet が作成できます。

vpc.tf
## VPCresource "aws_vpc" "app" {    cidr_block           = "172.31.0.0/16"    enable_dns_hostnames = true    enable_dns_support   = true    instance_tenancy     = "default"    tags {        "Name" = "${var.project}"    }}## Subnetresource "aws_subnet" "a" {    vpc_id                  = "${aws_vpc.app.id}"    cidr_block              = "172.31.0.0/20"    availability_zone       = "${lookup(var.azs,"a")}"    map_public_ip_on_launch = true    tags {        "Name" = "${var.project}-subnet-a"    }}resource "aws_subnet" "b" {    vpc_id                  = "${aws_vpc.app.id}"    cidr_block              = "172.31.16.0/20"    availability_zone       = "${lookup(var.azs,"b")}"    map_public_ip_on_launch = true    tags {        "Name" = "${var.project}-subnet-b"    }}resource "aws_subnet" "c" {    vpc_id                  = "${aws_vpc.app.id}"    cidr_block              = "172.31.32.0/20"    availability_zone       = "${lookup(var.azs,"c")}"    map_public_ip_on_launch = true    tags {        "Name" = "${var.project}-subnet-c"    }}

resource "aws_subnet" の中に${aws_vpc.app.id} ってのが出てきましたね。
Terraform の中では、管理下にあるリソースの情報を他のリソースの設定でも参照することが可能です。
各リソースで使用できる値が異なってくるので、その辺は公式ドキュメント読みましょう。
例えばaws_vpc で使用できる値はaws_vpc を参照すればわかります。

また、${lookup(var.azs,"a")} ってのも出てきましたね。
これは Terraform の組み込み関数です、lookup は配列の中からキーをもとに値を探す関数です。
詳しくはBuilt-in Functions を読んでください。

ついでに Internet Gateway と Route Table も設定しておきましょう。

route-table.tf
## Internet Gatewayresource "aws_internet_gateway" "igw" {    vpc_id = "${aws_vpc.app.id}"}## Route Tableresource "aws_route_table" "rtb" {    vpc_id     = "${aws_vpc.app.id}"    route {        cidr_block = "0.0.0.0/0"        gateway_id = "${aws_internet_gateway.igw.id}"    }}resource "aws_route_table_association" "route_a" {    subnet_id = "${aws_subnet.a.id}"    route_table_id = "${aws_route_table.rtb.id}"}resource "aws_route_table_association" "route_b" {    subnet_id = "${aws_subnet.b.id}"    route_table_id = "${aws_route_table.rtb.id}"}resource "aws_route_table_association" "route_c" {    subnet_id = "${aws_subnet.c.id}"    route_table_id = "${aws_route_table.rtb.id}"}

IAM ロールの作成

次に EC2 に割り当てるための IAM ロールを作ってみましょう。
ポリシーは、AWS が用意している AmazonEC2RoleforDataPipelineRole と、EC2 から CloudwatchLogs にログを送信するためのカスタムポリシーを作ってアタッチしてみます。

iam-role.tf
## For EC2 instance Roleresource "aws_iam_role" "instance_role" {    name               = "instance_role"    path               = "/"    assume_role_policy = <<POLICY{  "Version": "2012-10-17",  "Statement": [    {      "Effect": "Allow",      "Principal": {        "Service": "ec2.amazonaws.com"      },      "Action": "sts:AssumeRole"    }  ]}POLICY}## AmazonEC2RoleforDataPipelineRoleresource "aws_iam_role_policy_attachment" "data-pipeline" {    role       = "${aws_iam_role.instance_role.name}"    policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforDataPipelineRole"}## PutCloudwatchLogsresource "aws_iam_policy" "put-cloudwatch-logs" {    name        = "AmazonEC2PutCloudwatchLogs"    description = ""    policy      = <<POLICY{    "Version": "2012-10-17",    "Statement": [        {            "Action": [                "logs:CreateLogGroup",                "logs:CreateLogStream",                "logs:PutLogEvents"            ],            "Effect": "Allow",            "Resource": "*"        }    ]}POLICY}resource "aws_iam_role_policy_attachment" "put-cloudwatch-logs" {    role       = "${aws_iam_role.instance_role.name}"    policy_arn = "${aws_iam_policy.put-cloudwatch-logs.arn}"}

aws_iam_roleassume_role_policy のところと、aws_iam_policypolicy のところでヒアドキュメントが出てきましたね。
こんな風に複数行にわたるインラインポリシーはヒアドキュメントで記述することが可能です。
また、以下のように別ファイルにしておいて読み込ませることも可能です。
管理しやすい方でやってください。

iam-role.tf
resource "aws_iam_role" "instance_role" {    name               = "instance_role"    path               = "/"    assume_role_policy = "${file("data/instance_role_assume_policy.json")}"}
data/instance_role_assume_policy.json
{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"ec2.amazonaws.com"},"Action":"sts:AssumeRole"}]}

セキュリティグループの作成

EC2 から EFS へのアクセスは 2049 番ポートを介して行われるので、EFS が所属するセキュリティグループに穴を開けないといけません。
EC2 は 80, 443, 22 を解放してみます。

security-group.tf
## For EC2resource "aws_security_group" "ec2" {    name        = "${var.project}-EC2"    description = "for ${var.project} EC2"    vpc_id      = "${aws_vpc.app.id}"    ingress = [        {            from_port       = 80            to_port         = 80            protocol        = "tcp"            cidr_blocks     = ["0.0.0.0/0"]        },        {            from_port       = 443            to_port         = 443            protocol        = "tcp"            cidr_blocks     = ["0.0.0.0/0"]        },        {            from_port       = 22            to_port         = 22            protocol        = "tcp"            cidr_blocks     = ["0.0.0.0/0"]        }    ]    egress {        from_port       = 0        to_port         = 0        protocol        = "-1"        cidr_blocks     = ["0.0.0.0/0"]    }}## For EFSresource "aws_security_group" "efs" {    name        = "${var.project}-EFS"    description = "for ${var.project} EFS"    vpc_id      = "${aws_vpc.app.id}"    ingress {        from_port       = 2049        to_port         = 2049        protocol        = "tcp"        security_groups = ["${aws_security_group.ec2.id}"]    }    egress {        from_port       = 0        to_port         = 0        protocol        = "-1"        cidr_blocks     = ["0.0.0.0/0"]    }}

EFS の作成

こんな感じで EFS が作成できます。
各サブネットごとにマウントターゲットを作成して、そいつをセキュリティグループに所属させる形ですね。

efs.tf
resource "aws_efs_file_system" "app" {  tags {        "Name" = "${var.domain}"  }}resource "aws_efs_mount_target" "app-a" {  file_system_id  = "${aws_efs_file_system.app.id}"  subnet_id       = "${aws_subnet.a.id}"  security_groups = ["${aws_security_group.efs.id}"]}resource "aws_efs_mount_target" "app-b" {  file_system_id = "${aws_efs_file_system.app.id}"  subnet_id      = "${aws_subnet.b.id}"  security_groups = ["${aws_security_group.efs.id}"]}resource "aws_efs_mount_target" "app-c" {  file_system_id = "${aws_efs_file_system.app.id}"  subnet_id      = "${aws_subnet.c.id}"  security_groups = ["${aws_security_group.efs.id}"]}

EC2 の作成

さて、いよいよ EC2 です。
ここでは、user-data を使って、初回ローンチ時に EFS をマウントさせてしまいます。
さらにマウントした EFS 内にhtml/ ってディレクトリを作成して、そいつを/var/www/html にシンボリックリンクしてみましょうか。
と言っても、こんな感じで大丈夫です。

ec2.tf
## IAM Instance Profileresource "aws_iam_instance_profile" "instance_role" {    name = "instance_role"    role = "${aws_iam_role.instance_role.name}"}## SSH Keyresource "aws_key_pair" "deployer" {  key_name   = "${var.project}"  public_key = "${var.ssh_public_key}"}## EC2resource "aws_instance" "app" {    ami                         = "${lookup(var.ami,var.region)}"    availability_zone           = "${aws_subnet.a.availability_zone}"    ebs_optimized               = false    instance_type               = "t2.micro"    monitoring                  = true    key_name                    = "${aws_key_pair.deployer.key_name}"    subnet_id                   = "${aws_subnet.a.id}"    vpc_security_group_ids      = ["${aws_security_group.ec2.id}"]    associate_public_ip_address = true    source_dest_check           = true    iam_instance_profile        = "${aws_iam_instance_profile.instance_role.id}"    disable_api_termination     = false    user_data                   = <<USERDATA# !/bin/bashaz="${aws_subnet.a.availability_zone}"efs_region="${var.region}"efs_id="${aws_efs_file_system.app.id}"efs_mount_target="${aws_efs_mount_target.app-a.dns_name}:/"efs_mount_point="/mnt/efs/$${efs_id}/$${az}"web_doc_root="/var/www/html"# EFS Mount/usr/bin/yum -y install nfs-utils || /usr/bin/yum -y update nfs-utilsif [ ! -d $${efs_mount_point} ]; then  mkdir -p $${efs_mount_point}ficp -pi /etc/fstab /etc/fstab.$(date "+%Y%m%d")echo "$${efs_mount_target}    $${efs_mount_point}   nfs4    defaults" | tee -a /etc/fstabmount $${efs_mount_point}# create Web document rootif [ -d $${web_doc_root} ]; then  rm -rf $${web_doc_root}fiif [ ! -d $${efs_mount_point}/html ]; then  mkdir $${efs_mount_point}/html  chown ec2-user:ec2-user $${efs_mount_point}/htmlfiln -s $${efs_mount_point}/html $${web_doc_root}chown -h ec2-user:ec2-user $${web_doc_root}USERDATA    root_block_device {        volume_type           = "gp2"        volume_size           = 10        delete_on_termination = true    }    tags {        "Name"          = "${var.domain}"    }}

user_data は長めのシェルスクリプトなので、可読性が悪いから${file("data/user_data.sh")} とかってやって別ファイルで管理したいですよね。
でも待ってください、ヒアドキュメントでやってるのは理由があるのです。

ヒアドキュメントで書くと、user_data 用のシェルスクリプトの中で Terraform の変数が使えます。
マウントするには EFS の ID とか、マウントターゲットの dns_name とか必要になってきますが、それを作成前に知らなくてもこのように書いておけるのです。便利ですね。
その代わり、user_data 用のシェルスクリプト内でローカルな環境変数を使いたい場合は$${efs_mount_point} のように書いてあげてくださいね。

ざっと、こんな感じです。
慣れちゃえば、tf ファイルを使い回しできるので便利ですよ。
また、すでに作成済みの AWS リソースを Terraform 管理下に置きたい場合は

$terraform import aws_instance.app${instance_id}

のようにして管理下に置くことができます。
管理されているリソースはterraform.tfstate というファイルに書き込まれます。
さらに別プロダクトになるのですがTerraforming と言うツールを使用すると、既存の AWS リソースから Terraform 用の tf ファイルを作成したり、terraform.tfstate を作成したりもできるので便利です。
Terraforming については、Terraforming で既存のインフラを Terraform 管理下におく を参考にしてください。

実際にリソース作成してみる

tf ファイル書いたら

$terraform plan

で、設定ファイルに誤りがないか?既存のリソースへの影響はどの程度あるのかが確認できます。
実際に反映させたい時は

$terraform apply

で、おっけ。

では、良い Terraform を!

20

Go to list of users who liked

16
0

Go to list of comments

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
20

Go to list of users who liked

16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?


[8]ページ先頭

©2009-2025 Movatter.jp