HashiCorp Advent Calendar 2015の5日目の担当が空いていたので書いてみました。


クラウドを利用されているみなさんは、リージョン障害などに備えるために複数のリージョンを利用されているかと思います。 複数のリージョンに展開する際、どのような方法で行ってますでしょうか?

CloudFormation Stackを複数のリージョンで作ったり、手動でポチポチやったり、SDKやAWSCLIを使ったスクリプトを使ったり…

Terraform を使えば簡単に複数のリージョンを統一的に扱うことが出来ます。

Terraform は複数のプロバイダ(AWSやGCPなど)を一つのテンプレートの中に混在することが出来ます。 また、1種類のプロバイダを複数使用することも出来るため、AWSにマルチリージョンな環境を構築できます。

AWS謹製のCloudFormationは、S3やRoute53などのグローバルリソースを除き、そのCloudFormation Stackを作成しているリージョンのリソースしか作成することが出来ません。 もちろん、GCPやDigitalOceanなど外部のサービスを利用することも出来ません。

このあたりは Terraform の強みですね。

マルチリージョン基本編

まずは基本的な複数のプロバイダの使い方についてです。

シングルリージョンでAWSを利用する場合は以下のように記述します。 aws_ で始まるリソースは aws プロバイダが利用されます。

provider "aws" {
    region = "ap-northeast-1"
}

resource "aws_instance" "instance-tokyo" {
    instance_type = "t2.micro"
    ami           = "ami-383c1956"
    tag {
        Name = "Tokyo Region"
    }
}

マルチリージョンで利用したい場合、provider "aws" を複数書けば良いというわけではありません。 と言ってもそんなに難しいことをやるわけではなく、どちらのプロバイダを利用するのかを識別するためにaliasを追加するだけです。

あとは Resource の定義でどのプロバイダを使用するかをproviderで指定します。

provider "aws" {
    region = "ap-northeast-1"
    alias = "tokyo"
}
provider "aws" {
    region = "ap-southeast-1"
    alias = "singapore"
}

resource "aws_instance" "instance-tokyo" {
    provider      = "aws.tokyo"
    instance_type = "t2.micro"
    ami           = "ami-383c1956"
    tag {
        Name = "Tokyo Region"
    }
}
resource "aws_instance" "instance-singapore" {
    provider      = "aws.singapore"
    instance_type = "t2.micro"
    ami           = "ami-c9b572aa"
    tag {
        Name = "Singapore Region"
    }
}

マルチリージョン応用編

マルチリージョンにする大きな理由として、冗長性の確保が挙げられると思います。 環境を冗長構成する場合、同じ物をスタンプのように複数のリージョンに展開していくかと思いますが、前述の方法ではprovideramiだけが違うリソースを大量に定義していかなければならず、非常にめんどくさいですし、修正時に一部のリージョンを変更し忘れるといったオペミスも発生しやすくなります。

リージョン毎に同じリソースを展開する場合、共通する部分をモジュールとしてまとめてしまうのが簡単です。

今回サンプルとして作成するテンプレートのディレクトリ構成は以下のようにします。

./global.tf
./main.tf
./regional-resource/main.tf
./regional-resource/variables.tf

モジュール側のリソース定義

  • ./regional-resource/main.tf
provider "aws" {
    region = "${var.region}"
}

resource "aws_instance" "instance" {
    instance_type               = "t2.micro"
    ami                         = "${lookup(var.ami_id, var.region)}"
    associate_public_ip_address = true
    tag {
        Name = "${var.name}"
    }
}

output "public_ip" {
    value = "${aws_instance.instance.public_ip}"
}
  • ./regional-resource/variables.tf
variable "region" {}
variable "name" {}
variable "ami_id" {
    default {
        ap-northeast-1  = "ami-383c1956"
        ap-southeast-1  = "ami-c9b572aa"
        ap-southeast-2  = "ami-48d38c2b"
        us-east-1       = "ami-60b6c60a"
        us-west-1       = "ami-d5ea86b5"
        us-west-2       = "ami-f0091d91"
        eu-west-1       = "ami-bff32ccc"
        eu-central-1    = "ami-bc5b48d0"
        sa-east-1       = "ami-6817af04"
    }
}

モジュールを使う側のリソース定義

  • ./main.tf
module "ap-northeast-1" {
    source = "./regional-resource"

    region   = "ap-northeast-1"
    name     = "Tokyo"
}
module "ap-southeast-1" {
    source = "./regional-resource"

    region   = "ap-southeast-1"
    name     = "Singapore"
}
  • ./global.tf
resource "aws_route53_zone" "primary" {
   name = "example.com"
}

resource "aws_route53_record" "www" {
   zone_id = "${aws_route53_zone.primary.zone_id}"
   name = "www.example.com"
   type = "A"
   ttl = "300"
   records = [
       "${module.ap-northeast-1.public_ip}",
       "${module.ap-southeast-1.public_ip}",
   ]
}

global.tf ではRoute53にゾーンを作成し、東京都シンガポールのインスタンスのIPアドレスを登録しています。

以上のテンプレートで出来上がる構成がこんな感じです。

multi region diagram

Provision

モジュールを使うと、単に terraform apply とするだけではダメで先に terraform get を行う必要があります。

$ terraform get -update=true
$ terraform plan -module-depth=-1
$ terraform apply

今回はローカルファイルシステムにあるモジュールを使いましたが、Gitなどからも取ってくることが出来ます。(Using Modules)

まとめ

このように、Terraformを使えば簡単にマルチリージョンな環境を手に入れることができます。 マルチリージョンどころかマルチクラウドな環境も、今回よりちょっと大変ではありますが実現できます。