TerraformでAmazon EMR クラスターを構築

2016.10.28

今月初めにリリースされた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クラスターを構築してみました。

variables.tf

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" {}

main.tf

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はなかなか触れる機会がなかったのですが、これを機に色々と検証していいきたいと思います。