読者です 読者をやめる 読者になる 読者になる

Terraformの抽象度を高くする

こんにちは。SKAhackです。 今回はTerraformファイルの抽象度を高くすると嬉しいことを、Segmentが公開しているモジュールを例に紹介してみます。

まずはこちらを見てみてください。

// stackモジュールは基盤となる部分を定義
module "stack" {
  source      = "github.com/segmentio/stack"
  name        = "peroli-service"
  environment = "prod"
  key_name    = "bastion-ssh"
}

このTerraformファイルを定義して terraform plan をすると、VPC, Security Group, IAMロール, DNS, ログの保存場所としてS3, sshするための踏み台サーバーなど基盤に関わる部分全てが出来ることが分かります。この例はsegmentio/stackとしてSegmentが公開しているもので、実際にどのような用途で使われるかは後で少し触れます。

このように抽象度を高めると、サービスを提供するために必要なものだけ定義することができ、内部の設定を気にする必要がなくなります。 また、モジュールにオプションを用意しておくことである程度柔軟に利用できます。

Terraform module

Terraform moduleは、コンポーネントを組み合わせて再利用可能にするための仕組みです。 実際に便利なモジュールがterraform-community-modulesとして公開されていたりします。

このようにTerraformには便利な仕組みがあるのですが、個人的な観測範囲では便利に使われているところを見たことがありませんでした。 インターネット上に公開されているモジュールも、先程挙げたterraform-community-modulesのようにresourceをラップするようなモジュールが多い印象で、 そのくらいの粒度のままモジュールを利用しようと思うと、Terraformファイルが巨大になったり、サービスごとに設定が分散するというのを横目で見ていました。

The Segment AWS Stack

冒頭で紹介したSegmentが公開しているTerraform moduleは、簡単に構造を説明するとresourceをラップしたものを、別のモジュールで利用しているような、モジュールをネストした構造です。ネストと聞くとウッ...となりますが、コードを見ると分かるようにオブジェクト指向的に分割されているだけです。ここでもう少し詳細に見てみます。

冒頭のコードの下に以下を追加すると、1つのサービスが追加されます。具体的にはweatherというサービスがEC2 Container Service(ECS)上で動き、内部的に weather.stack.localでアクセス可能になります。

module "weather" {
  source         = "github.com/segmentio/stack//service"
  name           = "weather"
  image          = "peroli/weather"
  port           = 3000
  container_port = 3000
  dns_name       = "weather"

  environment     = "${module.stack.environment}"
  cluster         = "${module.stack.cluster}"
  zone_id         = "${module.stack.zone_id}"
  iam_role        = "${module.stack.iam_role}"
  security_groups = "${module.stack.internal_elb}"
  subnet_ids      = "${module.stack.internal_subnets}"
  log_bucket      = "${module.stack.log_bucket_id}"
}

さらにサービスモジュールを覗いてみると、moduleの中でELB用のモジュールを使っていたりします。

module "elb" {
  source = "../elb"

  name            = "${module.task.name}"
  port            = "${var.port}"
  environment     = "${var.environment}"
  subnet_ids      = "${var.subnet_ids}"
  security_groups = "${var.security_groups}"
  dns_name        = "${coalesce(var.dns_name, module.task.name)}"
  healthcheck     = "${var.healthcheck}"
  protocol        = "${var.protocol}"
  zone_id         = "${var.zone_id}"
  log_bucket      = "${var.log_bucket}"
}

このように1つの基盤となるモジュール、1つのサービスとなるモジュールのような粒度でもモジュールを書くことで、メンテナンスしやすく再利用可能なモジュールになっています。 もちろん、組織ごとに必要な設定は変わってくるので大きめの粒度のモジュールは必要に応じてカスタマイズするか、1から書くことになるかと思います。

ところで、Segmentが公開しているこのモジュールが何をするための物かというと、blog記事 The Segment AWS Stack にあるように、 サービスのほとんどをECS上で動かしており、その基盤の作成・サービスの追加や削除などインフラ構築のために作られています。 そして、その過程で出来たものを公開してくれています。記事に

It’s like a mini-Heroku that you host yourself. No magic, just AWS.

とあるように、ミドルウェアなど使っておらずAWSが提供しているサービスだけで完結しています。 なので、ECS上で何かをしたい方は、このモジュールに少し手を加えるだけで実際に動く環境が出来上がります。

ペロリでは、社内の開発者向けに気軽に使えるPaaSのようなものが欲しかったので参考にしています。 自由度も高く、設定もブラックボックス化しないので、今のところ良さそうな感じがしています。

さいごに

今回紹介したSegmentの記事はECSについて調べていたところ偶然見つけたのですが、個人的には知らないTerraformの書き方だ...!!と衝撃を受けました。 もしかしたら常識なのかもしれないですが、紹介した内容は見たことがなかったので紹介しました。ペロリ社内でもTerraformの死体が転がっているので、きれいに書き直そうとしています。

すこしまとめると・・・

Terraform moduleは適切な粒度で分割し、大きな粒度のモジュールがそれらを使うように書くことで、組織内で変更の必要がない部分は隠蔽することが出来ます。また、実際に使うTerraformファイルでは大きな粒度のモジュールだけ使うようにすることで、1つの基盤、1つのサービスのような粒度でインフラの構築を考えることが出来るようになります。

naruhodo。と思った方は参考にしてみてください。

© peroli, Inc.