ヌーラボのインフラ運用最前線 2015 〜Terraform による AWS の構成の自動化〜

Terraformとは

TerraformとはAWS CloudFormationと似た機能を提供します。
インフラの構築・変更・バージョン管理を安全かつ効率的に行うためのツールで以下のような特徴があります。

  • AWS, Azure, Google Cloudなどの主要なIaaS/PaaS/SaaSを操作できる。
  • インフラの設定をコードとして残すことができることでバージョン管理したり、他のコードと比較できる。
  • Dry-Runがあり変更がある箇所の確認ができる。
  • リソースをグラフ化してくれる機能があり依存関係を可視化できる。ここでいうリソースというのはAWSであればEC2, EBS, ELB等のサービスやオブジェクトを指しています。
  • 差分変更に対応しており、Terraform が何を変更して、何を実行しようとしているのか明確化される。影響範囲が明確になることでオペミスも防ぐことができる。

Terraformとは何かについてはINTRODUCTION TO TERRAFORMに詳しく記載されています。

Terrafromを利用するようになった経緯

ヌーラボのサービスはAWSで運用されています。
Backlog は以下のように複数のクラスターを構成し、一つのクラスターで複数のスペースに対してサービスを提供しています。
一つのクラスターで提供するスペース数が一定数以上に成長すると、新しい環境を構築してそちらに切り替えるような運用をしています。

service-cluster

もともとヌーラボではInfrastructure as Codeを進めており、サーバ(インスタンス)単体ではAnsible / Fabricによって設定がコード化されてバージョン管理もされています。  

server-provisioning

 

 

今回新規で環境を作成するにあたり問題になったのはVPC構成、セキュリティグループ、ELB、RDSの設定でした。
既存環境の設定値を確認しつつ設定をあわせていくのは時間がかかります。
またリソースの作成はAWS マネジメントコンソールAWS CLIで行っておりミスが発生しやすい状況でした。

before

 

次回作成時に、同じ作業をしたくなかったこと、作業者が変わることもあり得ることを考えるとサーバー構成をコードとして残しておこうと考え構成管理ツールの検討をすることにしました。
AWS CloudFormationがまず思い浮かんだのですが、Dry-Runのような機能はなくひたすらTry-And-Errorをしてました。当然ですがちょっとJsonの構文を間違うとこけてしまったり、コメントを挿入できなかったりと煩わしさがありました。

そのような問題点もあったのでHashiCorp社から出ているTerraformはどうかと思い試験的に触り始めたところ問題となっていたところがカバーされていたので導入に至った経緯があります。

導入後は以下のようなイメージになります。

after

 

この構成にしたことでAWS マネジメントコンソールFabricでAWSリソースを準備していたものがterraform applyのみのコマンドでAWSリソースすべてが準備されます。
またTerraformを導入したことでツールの責務がはっきりして以下のようになりました。

  導入前 導入後
AWSリソース準備 AWS マネジメントコンソール/Fabric Terraform
サーバープロビジョニング Ansible Ansible
アプリケーションデプロイ Fabric Fabric

工夫したこと

リソースの分離をファイル単位で行い、使い回しができるように配慮した

Backlogチーム以外でも利用できるようになるべくAWSリソース単位でファイルを分割しておき組み合わせるだけでリソースが構成できるように配慮しました。
ファイル構成例を記載していますがterraform applyを実行したときに全ファイル読み込んですべてのリソースを作成してくれます。
depends_on設定をしておくと各リソースの依存関係を制御できすべてのリソースをいっきに作成することが可能です。

 

.
├── config.tf
├── terraform.tfstate
├── terraform.tfstate.backup
├── user_data
│   └── api.tpl
├── variables.tf
├── vpc-db-instance.tf
├── vpc-db-subnet-group.tf
├── vpc-elb.tf
├── vpc-igw.tf
├── vpc-instance-api.tf
├── vpc-peering.tf
├── vpc-route-table.tf
├── vpc-security-group-api-rule.tf
├── vpc-security-group-api.tf
├── vpc-security-group-base-rule.tf
├── vpc-security-group-base.tf
├── vpc-security-group-db.tf
├── vpc-subnet-assigned-route-table.tf
├── vpc-subnet.tf
└── vpc.tf

AWS CloudFormationを利用した際にファイルを分割した時は前回作成したリソースIDを記述しておく必要があったのですが、Terraformだとterraform.tfstateファイルに書出ししてくれてます。
後からファイルを分割・追加してもTerraformで付けた名前を使うことができ大変便利でした。コード例を書いてますがすでに作成されているサブネットIDにアクセスしたいときはaws_subnet.subnet-pri-first.idで取得できます。

# vpc-instance-api.tf APIインスタンスのリソース定義
# api Instance Template
resource "template_file" "api" {
    filename = "user_data/api.tpl"
    vars {
        num = "${var.vpc_config.num}"
    }
}
# api Instance
resource "aws_instance" "api-first" {
    depends_on = [
        "aws_subnet.subnet-pri-first",
        "aws_security_group.base",
        "aws_security_group.api",
        "aws_security_group.misc"
    ]
    ami = "${var.ec2_config.source_hvm_ami}"
    key_name = "${var.ec2_config.key_name}"
    instance_type = "${var.ec2_config.api_instance_size}"
    source_dest_check = 1
    subnet_id = "${aws_subnet.subnet-pri-first.id}"
    private_ip = "176.${var.vpc_config.num}.0.1"
    monitoring = 1
    user_data = "${template_file.api.rendered}"
    iam_instance_profile = "${var.ec2_config.iam_instance_profile}"
    vpc_security_group_ids = [
        "${aws_security_group.base.id}",
        "${aws_security_group.api.id}",
        "${aws_security_group.misc.id}"
    ]
    tags {
        Name = "api${var.vpc_config.num}1-${var.vpc_subnet_config.first_zone}"
    }
}

設定値は別ファイルに書き出しを行い一箇所で管理を行うことで、新しい環境を作る際に設定値変更のみでサーバー構築をできるようにしています。

# variables.tf 設定値を記載
# VPC Config is defined.
variable "vpc_config" {
    default = {
        num = "4"
        cidr_block = "176.4.0.0/16"
        peer_cidr_block = "176.0.0.0/16"
        peer_owner_id = "00000000000000"
        peer_vpc_id = "vpc-00000000"
    }
}

# VPC Subnet Config is defined.
variable "vpc_subnet_config" {
    default = {
        first_zone               = "c"
        second_zone              = "b"
        first_availability_zone  = "ap-northeast-1c"
        second_availability_zone = "ap-northeast-1b"
        pub_first_cidr_block     = "176.4.0.0/24"
        pub_second_cidr_block    = "176.4.1.0/24"
        pri_first_cidr_block     = "176.4.2.0/24"
        pri_second_cidr_block    = "176.4.3.0/24"
    }
}

# EC2 Config is defined.
variable "ec2_config" {
    default = {
        key_name = "matsuura"
        iam_instance_profile = "ec2-production"
        source_pv_ami = "ami-0000000"
        source_hvm_ami = "ami-1111111"
        nat_hvm_ami = "ami-1111111"
        nat_instance_size = "t2.micro"
        app_instance_size = "c4.large"
        log_instance_size = "t2.micro"
        search_instance_size = "m4.large"
    }
}

EIP/S3は管理対象外にした

構築時は何回もterraform applyterraform destroyを繰り返すことがあります。EIPに関しては構築時にDNSの登録をしないと行けないタイミングがあり、毎回削除するとDNSを変更しないといけないので管理対象から外しました。S3に関しては同じ名前を削除すると再度使用できないので管理から外しました。  

NAT インスタンスは個別に管理する

NAT インスタンスは削除時にあわせて RouteTable の削除も走るため、入替えを行う必要が出た場合はAWS マネジメントコンソールで削除を行うようにしました。

 

今後の展開

Terraformすごく便利ですがオペミスで全リソースを削除することも可能です。
Terraformで一度作成したリソースを管理外にするような工夫、もしくはよく増減するサーバー(アプリケーションサーバー)等は別途管理するファイルをわけて運用すべきか検討していきたいと思っています。
また、今後Terraformと似たようなツールは出てくる可能性があるので、よいものがあればすぐに移行できるよう、構成をテストできるツール(awspec)等を試していきます。

最後に

Terraform使用におけるベストプラクティスはまだまだ発見できていません。
非常にたくさんのサービスとリソースが扱えますので、使う側が正しく管理したい範囲を分離し使うことが大事だと考えており、試行錯誤していく中で現時点のベストを探していくしかないと思っています。

今回の記事が導入の一例として誰かの役に立てれば幸いです。

サーバー構築の部分を記載しましたがよかったら以下の記事も読んでみてください。

 


ヌーラボではインフラをコードで操ってやる!というインフラエンジニアを募集しています。是非ご応募ください。

 

より良いチームワークを生み出す

チームの創造力を高めるコラボレーションツール

製品をみる