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

UUUM攻殻機動隊

UUUMのエンジニアによる技術ブログです

AWSでのAuto ScalingをConsul+Packer+Terraformで実現する

AWS インフラ

nazoです。

弊社のサービスの1つである おしゃべりマルチ のインフラを、Consul + Packer + Terraformで Auto Scaling できる環境にしましたので紹介します。

なぜその組み合わせ?

AWS の Auto Scaling 機能は、指定した条件時に指定の AMI を元にインスタンスを増やす、というものです。この元となる AMI のことをゴールデンイメージと呼ぶのですが、AMI である以上事前に作成しないといけません。

インスタンスを用意する方法としてはいくつかありますが、

  • 初期状態の AMI を起動させて、起動後に provisioning する
    • 起動にあまりに時間がかかってしまい、負荷増大時に間に合わない可能性が高くなる
  • アプリケーションまで含めた AMI を作って、起動したらすぐ立ち上がるようにする
    • アプリケーションコードの書き換えで毎回イメージを作らないといけなくなるため、デプロイに時間がかかりすぎる。また、デプロイ時に全インスタンスを差し替えないといけない

ということで、Packer でアプリケーション以外の用意が出来ている AMI を作って、起動後(またはアプリケーションのデプロイ時)にアプリケーションを取得する、というフローを採用しています。これ自体は割と一般的なフローかと思います。

AMI を作成するのを Packer で、それを配置するのを Terraform で、起動後の面倒を見るのを Consul で行う、という役割分担になっています。

Consul の使いどころ

Consul は、サービスの検出や障害検知など、「サービス(インスタンス)間の関わりを解決する」プロダクトだと私は認識しております。様々な機能がありますが、ここでは主に DNS、KV、event を紹介します。

DNS は、言うまでもなく名前解決に使用します。Consul が動いているノードは Consul 内で独自の名前を用意することができ、その名前からDNSクエリで IPアドレスを引くことができます。同一ネットワーク間で使うことはあまりないのですが、おしゃべりマルチでは IDCFクラウド も使用しているため、マルチクラウド間での名前解決に重宝しています。advertise_addr_wantranslate_wan_addrs を指定しておけば、同じドメイン名で、同一ネットワークではプライベートアドレス、外部ネットワークではグローバルアドレスを引くという挙動ができます。dnsmasq と併用して運用するのが一般的のようです。

KV (Key-Value Store) は、consul-template と併用して、設定の展開に使用しています。consul-template は、KV のデータ変更に連動して特定のテンプレートファイルを展開する、というものです。

例えば、データベースの設定に

[database]
database.master = {{ key "database/master-user" }}:{{ key "database/master-password" }}@tcp({{ key "database/master-host" }}:3306)/{{ key "database/master-database" }}

のようなテンプレートを用意しておき、これを展開する consul-template の設定を

template {
    source = "/etc/app/config/app.toml.template"
    destination = "/path/to/app/config/app.toml"
    command = "service app restart"
}

のようにしておくと、KV にそれらの値を入れると、

[database]
database.master = username:password@tcp(db-server.example.com:3306)/database_name

というファイルが自動生成され、アプリケーションが再起動します。設定を急に変更したくなった時(あまりないと思いますが)に KV の値を変えるだけでよく、また、設定内容が環境から切り離されるので、staging と production 環境で KV 以外は全く同じファイルを揃え、KV の値の違いだけで環境の違いを表現することができます。つまり、単一のインスタンスイメージ(AMI)から複数の環境を構築することが可能になります。Auto Scaling 環境では、必ず単一の AMI からインスタンスを起動する必要があるため、これは非常に便利に働きます。

KV への値の投入は、独自にスクリプトを書いて、それを実行しています。値の元データは Ansible の template で用意しているので、Ansible Vault 経由でパスワードなどの機密情報もそのまま管理することができます。投入スクリプトは数行で書ける(設定ファイルを読んで curl で叩くだけ)ので、用途に応じて作成すればいいかと思います。git2consul というものもあるようです。

event は、サーバーノードに特定のイベントを送ると、それが全ノードに伝搬する、という機能ですが、ここでは stretcher によるデプロイに使用しています。stretcher は、Consul のイベントの受信に連動して S3 に置いたアーカイブを元にデプロイするという処理を書くためのツールです。詳細については 作者による紹介記事 を読んでいただくのが早いと思いますが、おしゃべりマルチでは、consul-template で設定を記述していることもあり、symlink 切り替えでのデプロイをしつつ、consul-template が出力した設定ファイルを配置するような処理をデプロイ時に行うようにしています。

Consul + stretcher 以外にも、Auto Scaling 環境下でのデプロイ方法はいくつかありますし、rsync とかでも十分実用的ですが、ポイントは Auto Scaling 発動時のインスタンス起動時に、起動したインスタンスが自分から最新のアプリケーションを取得しに行けるようにしておくということです。これにより、原則として pull 型(各サーバーがそれぞれデータを取りに行く)のデプロイになると思います(高速化にもなります)。今回は Consul を採用したということで、そのまま連携しやすい stretcher を採用しました。

Packer + Ansible による AMI の作成と投入

AMI の作成は Packer で行いますが、Packer はイメージを作成するだけなので、provisioning には Ansible を使用しています。

Packer での AMI 作成 はよくできているので、特に書くことはないのですが、試行錯誤中に AMI を何度も作るのはイケてないので、Docker 用の設定も書いておき、Docker 上で環境構築が通るか試すようにしています。また、初期状態のイメージから AMI を何度も作ると時間がかかるので、ある程度社内標準のパッケージが入っている AMI を1つ用意しておき、そこから更に個別の AMI を作成します。前者のほうはあまり頻繁には更新しません。

AMI の差し替えは Terraform で行います。AWS の 起動設定 は、更新という処理がないため、起動設定を変更したい場合は「新しい起動設定を作って、それに差し替える」というちょっとした手間がかかるのですが、Terraform を使うことで、その手順が自動化されるので便利です。

以下のサンプルで、 ELB IAM セキュリティグループ等は作っていないので、事前に用意してください。

resource "aws_autoscaling_group" "app" {
    availability_zones = ["ap-northeast-1a", "ap-northeast-1c"]
    name = "app"
    max_size = 20
    min_size = 2
    health_check_grace_period = 300
    health_check_type = "EC2"
    force_delete = true
    desired_capacity = 2
    force_delete = true
    launch_configuration = "${aws_launch_configuration.app.name}"
    load_balancers = ["app-elb"]
    vpc_zone_identifier = ["subnet-xxxxxxxx"]

    lifecycle {
        create_before_destroy = true
    }

    tag {
        key = "Name"
        value = "app"
        propagate_at_launch = true
    }
}

resource "aws_launch_configuration" "app" {
    name_prefix = "app-"
    image_id = "${var.ami_id}"
    instance_type = "t2.micro"
    security_groups = ["sg-xxxxxxxx"]
    iam_instance_profile = "${var.iam_arn}"

    user_data = "${file("userdata.sh")}"

    root_block_device {
        volume_type = "gp2"
        volume_size = "16"
        delete_on_termination = true
    }

    lifecycle {
      create_before_destroy = true
    }
}

resource "aws_autoscaling_policy" "app_scale_out" {
    name = "Instance-ScaleOut-CPU-High"
    scaling_adjustment = 3
    adjustment_type = "ChangeInCapacity"
    cooldown = 300
    autoscaling_group_name = "${aws_autoscaling_group.app.name}"
}

resource "aws_autoscaling_policy" "app_scale_in" {
    name = "Instance-ScaleIn-CPU-Low"
    scaling_adjustment = -1
    adjustment_type = "ChangeInCapacity"
    cooldown = 300
    autoscaling_group_name = "${aws_autoscaling_group.app.name}"
}

resource "aws_cloudwatch_metric_alarm" "app_cpu_high" {
    alarm_name = "CPU-Utilization-High"
    comparison_operator = "GreaterThanOrEqualToThreshold"
    evaluation_periods = "1"
    metric_name = "CPUUtilization"
    namespace = "AWS/EC2"
    period = "300"
    statistic = "Average"
    threshold = "50"
    dimensions {
        AutoScalingGroupName = "${aws_autoscaling_group.app.name}"
    }
    alarm_actions = ["${aws_autoscaling_policy.app_scale_out.arn}"]
}

resource "aws_cloudwatch_metric_alarm" "app_cpu_low" {
    alarm_name = "CPU-Utilization-Low"
    comparison_operator = "LessThanThreshold"
    evaluation_periods = "1"
    metric_name = "CPUUtilization"
    namespace = "AWS/EC2"
    period = "300"
    statistic = "Average"
    threshold = "20"
    dimensions {
        AutoScalingGroupName = "${aws_autoscaling_group.app.name}"
    }
    alarm_actions = ["${aws_autoscaling_policy.app_scale_in.arn}"]
}

まとめ

Consul により、インスタンス間の各種問題が解消され、個別に SSH して設定する必要がなくなり、単一イメージから様々な環境を簡単に構築できるようになり、Auto Scaling にも対応することができました。AMI 自体に大幅な変更を入れたい場合は、ELB をもう1つ作って差し替えるというような運用も可能かと思います。

また、Packer での AMI 作成時に使用している Ansible の Role や、Terraform の設定であるところの tf ファイルなど、コードによる資産も蓄積されるため、今後の案件への流用も行いやすく、トータルでのコスト削減やメンバー間の知識共有にも影響が出てくると思います。

Consul、Packer、Terraform と、全て HashiCorp 社のプロダクトなので、HashiCorp プロダクトのクラウド環境に対する親和性の高さは素晴らしいと改めて感じました。

おしゃべりマルチでは IDCFクラウド も使用しているのですが、そちら向けの環境も Packer + Terraform + Ansible で行えるようになっています。CloudStack 独特の対応があるのですが、機会があればそちらも紹介したいと思います。

UUUM では、積極的にインフラの自動化を担当したいクラウドインフラエンジニアも募集しています。詳しくは以下をご覧下さい。