
TerraformでAmazon EMR クラスターを構築
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
今月初めにリリースされたv0.7.5でTerraformがAmazon EMRに対応しました。2015年の5月には機能追加の要望が挙がっていたので、1年以上越しで機能追加が実現された形です。
Amazon EMR関連のリソース
新たに利用可能となったのは以下の2つのリソースです。
aws_emr_cluster
EMRクラスターを作成するためのリソースです。
設定項目
| 設定項目 | 説明 | 必須/オプション | デフォルト値 | 備考 | 
|---|---|---|---|---|
| name | クラスター名 | 必須 | - | - | 
| release_label | EMRのリリースラベル | 必須 | - | emr-5.0.0など | 
| master_instance_type | マスターノードのインスタンスタイプ | 必須 | - | - | 
| core_instance_type | コアノードのインスタンスタイプ | オプション | - | - | 
| core_instance_count | ノード数 | オプション | 0 | マスターノードとコアノードの合計数を指定 | 
| log_uri | EMRのログファイル保存先のS3バケット | オプション | - | 例:s3://emr-log/elasticmapreduce/ (指定がない場合はログファイルが生成されない)  | 
| applications | EMRクラスターにインストールするアプリケーション | オプション | - | Hadoop、Hive、Sparkなど | 
| ec2_attributes | EC2インスタンスの詳細設定 | オプション | - | 詳細な設定項目については下の「ec2_attributes」の表を参照 | 
| bootstrap_action | ブートストラップアクション | オプション | - | 詳細な設定項目については下の「bootstrap_action」の表を参照 | 
| configurations | アプリケーションの設定ファイル | オプション | - | json形式の設定ファイルを指定 | 
| service_role | EMRのサービスロール(EMRサービス用のIAMロール) | 必須 | - | - | 
| visible_to_all_users | クラスターをすべてのIAMユーザーに表示 | オプション | true | falseに設定するとクラスターを作成したIAMユーザーのみがそのクラスターを参照可能となる | 
| tags | タグ設定 | オプション | - | - | 
- ec2_attributes
 
| 設定項目 | 説明 | 必須/オプション | デフォルト値 | 備考 | 
|---|---|---|---|---|
| key_name | EC2キーペア名 | オプション | - | "hadoop"ユーザーでEC2にログインする際に使用する | 
| subnet_id | EMRクラスターを起動するサブネットID | オプション | デフォルトVPCのパブリックサブネット | - | 
| additional_master_security_groups | マネージドセキュリティグループの他にマスターノードに追加で割り当てるセキュリティグループIDのリスト | オプション | - | - | 
| additional_slave_security_groups | マネージドセキュリティグループの他にコアノードに追加で割り当てるセキュリティグループIDのリスト | オプション | - | - | 
| emr_managed_master_security_group | マスターノードのマネージドセキュリティグループID | オプション | - | 指定なしの場合はマネージドセキュリティグループが新規に作成される | 
| emr_managed_slave_security_group | コアノードのマネージドセキュリティグループID | オプション | - | 指定なしの場合はマネージドセキュリティグループが新規に作成される | 
| instance_profile | EC2に割り当てるインスタンスプロファイル名 | 必須 | - | - | 
- bootstrap_action
 
| 設定項目 | 説明 | 必須/オプション | デフォルト値 | 備考 | 
|---|---|---|---|---|
| name | ブートストラップアクション名 | オプション | - | - | 
| path | ブートストラップアクションで実行するスクリプトファイルのパス | オプション | - | S3上のファイルまたはローカルファイルを指定 | 
| args | ブートストラップアクションで実行するスクリプトの引数 | オプション | - | - | 
aws_emr_clusterリソースで作成できるのはマスターノードとコアノードです。タスクノードを追加するには後述のaws_emr_instance_groupを使います。
core_instance_countにはマスターノードとコアノードの合計数を指定します(マスターノードは常に1台なので、「1 + コアノード数」を指定します)。core_instance_countのデフォルト値は「0」のようですが、1以上を指定しないとエラーとなりますのでご注意ください。
applicationsは、Terraformのオンラインドキュメントをみると「Hadoop, Hive, Mahout, Pig, Spark」しか選択できないようにも読めますが、実際に試したところPrestoなど、Terraformのオンラインドキュメントに記載のアプリケーションも問題なくインストールできました。
EMRで利用可能なアプリケーションについてはAWSの公式ドキュメントを確認するのがよいかと思います。
Amazon EMR リリースについて - Amazon EMR
エクスポートされる属性
| 属性名 | 説明 | 備考 | 
|---|---|---|
| id | クラスターID | - | 
| name | クラスター名 | - | 
| release_label | リリースラベル | - | 
| master_instance_type | マスターノードのインスタンスタイプ | - | 
| core_instance_type | コアノードのインスタンスタイプ | - | 
| core_instance_count | ノード数 | - | 
| log_uri | EMRのログファイル保存先のS3バケット | - | 
| applications | EMRクラスターにインストールされたアプリケーション | - | 
| ec2_attributes | EC2インスタンスの詳細設定 | - | 
| bootstrap_action | ブートストラップアクション | - | 
| configurations | アプリケーションの設定 | - | 
| service_role | EMRのサービスロール(EMRサービス用のIAMロール) | - | 
| visible_to_all_users | クラスターをすべてのIAMユーザーに表示する(true or false) | - | 
| tags | タグ | - | 
aws_emr_instance_group
EMRクラスターにタスクノード(タスクインスタンスグループ)を追加するためのリソースです。
設定項目
| 設定項目 | 説明 | 必須/オプション | デフォルト値 | 備考 | 
|---|---|---|---|---|
| name | タスクインスタンスグループ名 | オプション | - | - | 
| cluster_id | タスクインスタンスグループを追加するEMRクラスターのID | 必須 | - | - | 
| instance_type | タスクノードのインスタンスタイプ | 必須 | - | - | 
| instance_count | タスクノード数 | オプション | - | - | 
- ec2_attributes
 
| 設定項目 | 説明 | 備考 | 
|---|---|---|
| name | タスクインスタンスグループ名 | - | 
| cluster_id | タスクインスタンスグループを追加したEMRクラスターのID | - | 
| instance_type | タスクノードのインスタンスタイプ | - | 
| instance_count | タスクノード数 | - | 
| running_instance_count | 実行中のタスクノード数 | - | 
| status | タスクインスタンスグループのステータス | PROVISIONING  BOOTSTRAPPING RUNNING RESIZING SUSPENDED TERMINATING TERMINATED ARRESTED SHUTTING_DOWN ENDED  | 
Terraformのテンプレートファイルの例
以下のテンプレートファイルを作成して、実際にEMRクラスターを構築してみました。
variable "names" {
  type = "map"
  default = {
    account_id = ""
    profile    = ""
    prefix     = ""
  }
}
variable "region" {
  default = "ap-northeast-1"
}
variable "vpc_cidr" {}
variable "source_ips" {
  type = "list"
}
variable "ssh_key_name" {}
data "aws_availability_zones" "available" {}
provider "aws" {
  region              = "${var.region}"
  profile             = "${var.names["profile"]}"
  allowed_account_ids = ["${var.names["account_id"]}"]
}
## S3 Bucket for ERM Logs
resource "aws_s3_bucket" "emr-log" {
  bucket        = "emr-logs-${var.names["account_id"]}"
  acl           = "private"
  force_destroy = true
}
## VPC
resource "aws_vpc" "emr-test" {
  cidr_block           = "${var.vpc_cidr}"
  instance_tenancy     = "default"
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags {
    Name = "${var.names["prefix"]}-vpc"
  }
}
## Internet GW
resource "aws_internet_gateway" "emr-test" {
  vpc_id = "${aws_vpc.emr-test.id}"
  tags {
    Name = "${var.names["prefix"]}-igw"
  }
}
## Subnet
resource "aws_subnet" "public" {
  count                   = 2
  vpc_id                  = "${aws_vpc.emr-test.id}"
  cidr_block              = "${cidrsubnet(var.vpc_cidr, 8, count.index)}"
  availability_zone       = "${data.aws_availability_zones.available.names[count.index]}"
  map_public_ip_on_launch = true
  tags {
    Name = "${format("${var.names["prefix"]}-public-subnet%02d", count.index + 1)}"
  }
}
## Route Table
resource "aws_route_table" "public" {
  vpc_id = "${aws_vpc.emr-test.id}"
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = "${aws_internet_gateway.emr-test.id}"
  }
  tags {
    Name = "${var.names["prefix"]}-public"
  }
}
resource "aws_route_table_association" "public" {
  count          = 2
  subnet_id      = "${element(aws_subnet.public.*.id, count.index)}"
  route_table_id = "${aws_route_table.public.id}"
}
## IAM role for EMR
data "aws_iam_policy_document" "emr-assume-role-policy" {
  statement {
    effect  = "Allow"
    actions = ["sts:AssumeRole"]
    principals = {
      type        = "Service"
      identifiers = ["elasticmapreduce.amazonaws.com"]
    }
  }
}
resource "aws_iam_role" "emr-service-role" {
  name               = "EMR_DefaultRole"
  assume_role_policy = "${data.aws_iam_policy_document.emr-assume-role-policy.json}"
}
resource "aws_iam_role_policy_attachment" "emr-service-role" {
  role       = "${aws_iam_role.emr-service-role.name}"
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonElasticMapReduceRole"
}
## IAM Role for EC2
data "aws_iam_policy_document" "ec2-assume-role-policy" {
  statement {
    effect  = "Allow"
    actions = ["sts:AssumeRole"]
    principals = {
      type        = "Service"
      identifiers = ["ec2.amazonaws.com"]
    }
  }
}
resource "aws_iam_role" "emr-ec2-role" {
  name               = "EMR_EC2_DefaultRole"
  assume_role_policy = "${data.aws_iam_policy_document.ec2-assume-role-policy.json}"
}
resource "aws_iam_role_policy_attachment" "emr-ec2-role" {
  role       = "${aws_iam_role.emr-ec2-role.name}"
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonElasticMapReduceforEC2Role"
}
resource "aws_iam_instance_profile" "emr-ec2-profile" {
  name  = "${var.names["prefix"]}-emr-ec2-profile"
  roles = ["${aws_iam_role.emr-ec2-role.name}"]
}
## EMR Cluster
resource "aws_emr_cluster" "emr-test-cluster" {
  name          = "emr-test-cluster"
  release_label = "emr-5.0.0"
  applications  = ["Hadoop", "Hive"]
  log_uri       = "s3://${aws_s3_bucket.emr-log.id}/elasticmapreduce/"
  service_role  = "${aws_iam_role.emr-service-role.arn}"
  master_instance_type = "m1.medium"
  core_instance_type   = "m1.medium"
  core_instance_count  = 2
  ec2_attributes {
    key_name = "${var.ssh_key_name}"
    subnet_id = "${aws_subnet.public.0.id}"
    instance_profile = "${aws_iam_instance_profile.emr-ec2-profile.name}"
  }
}
まとめ
EMRはデータ分析のプリプロセス(非構造化データ → 構造化データへの変換、整形、フィルタリングなど)実行のためオンデマンドでクラスターを起動&シャットダウンするケースが多いかと思います。
この場合はTerraformで構成管理、、は必要ないかもしれませんが、TerraformでVPCやセキュリティグループ含めてサクッとEMRの検証環境を起動できるようになったのは嬉しいポイントです。これまでEMRはなかなか触れる機会がなかったのですが、これを機に色々と検証していいきたいと思います。









