はじめに
こんにちは、2019年にSTROBELIGHTSチームからSmart-Cチームに異動しました、安藤です。
普段、バックエンドやフロントエンド周りの開発をしていますが、インフラ周りの改善もたまにやります。
クラウド上の監視設定の再利用性を高めるため、 複数のプロジェクトでTerraformの基盤構築からチームへの展開・運用を行いました。今回はCloudWatchに設定しているEC2の監視設定をTerraformでコード化したので、その際のディレクトリ構成や工夫した点についてお話します。
対象読者は次の条件のうちどれか一つを充たす方を想定しています
- Terraformを触ったことがある
- AWS上で監視設定を作ったことがある
概要
対象はRailsを使用している社内プロジェクト、クラウドはAWSを使用しています。
今回EC2のロールごとの監視設定がされているので、それをTerraformで書いていきます。
インフラ構成は、よくあるWebサービスのカタチ、今回はEC2に絞っています(表記はロール名) *1
- Deploy
- Puma
- Fluentd
- Sidekiq
各ロールに対して、それぞれ下記 監視項目をGUIで設定しています。
- CPU使用率
- メモリ使用率
- その他、Diskなど
アラートの発火条件
- ex) CPU使用率 80%を超えた状態が5分間続いた
今回、tfstateの保存先はS3にしました。
ディレクトリ構成
Ansibleみたいにロールを意識した構成を模索した結果、以下の構成にしました。 *2
この構成のメリットとしては、ロール単位での閾値や設定を柔軟にできます。
複数環境への適用は、workspaceを使います。*3
terraform ├── README.md ├── cloudwatch │ ├── README.md │ ├── backend.tf │ ├── deploy.tf │ ├── fluentd.tf │ ├── main.tf │ ├── puma.tf │ ├── sidekiq.tf │ ├── variables.tf │ └── versions.tf ├── docker-compose.yml ├── modules │ └── cloudwatch │ ├── main.tf │ └── variables.tf └── terraform.tfvars.production
主要なファイルを少し紹介します。
実行ファイルはpuma.tf, fluentd.tf, sidekiq.tf, deploy.tf
になり、
cloudwatch/main.tf
には、各環境で使う共通のものや外部から変更されたくないものをlocalsとして定義して使っています。
provider "aws" { region = var.region } locals { project = "ProductName" roles = { deploy = { name = "Deploy" } puma = { name = "Puma" } sidekiq = { name = "Sidekiq" } fluentd = { name = "Fluentd" } } }
modules/cloudwatch/main.tf
Moduleでリソースのテンプレートを作りました。
アラーム名(alarm_name) や 説明(description) の形式を統一でき、さらに必要な変数が呼び出し先で設定されていない場合にエラーを出してくれます。
locals { alarm_name = "${var.project}_${var.role}_${var.metric_name}_${var.state}" alarm_description = "Alarm if ${var.metric_name} too ${var.state} or metric disappears indicating instance is down" } resource "aws_cloudwatch_metric_alarm" "this" { count = length(var.instance_ids) dimensions = { InstanceId = var.instance_ids[count.index] } // 対象のインスタンス、test、 productionどの環境のアラームかが、通知でひと目で分かる alarm_name = "${local.alarm_name}_${var.private_ips[count.index]}_${terraform.workspace}" comparison_operator = var.comparison_operator evaluation_periods = var.evaluation_periods metric_name = var.metric_name namespace = var.namespace period = var.period statistic = var.statistic threshold = var.threshold treat_missing_data = var.treat_missing_data alarm_description = local.alarm_description ok_actions = var.ok_actions alarm_actions = var.alarm_actions }
puma.tf
の一部
主にタグを指定してインスタンスを取得し、moduleを読み込んで、必要な変数に値を設定しています。
data "aws_instances" "role_puma" { // 対象とするインスタンスは以下のタグで指定(EC2上のタグに、キー名: Project, Nameを設定しています) // Nameはロール名です instance_tags = { Project = local.project Name = local.roles.puma.name } } module "cpu_high_puma" { source = "../modules/cloudwatch" project = local.project role = local.roles.puma.name instance_ids = data.aws_instances.role_puma.ids private_ips = data.aws_instances.role_puma.private_ips metric_name = "CPUUtilization" namespace = "AWS/EC2" comparison_operator = "GreaterThanOrEqualToThreshold" state = "High" statistic = "Average" period = "300" threshold = 80 ok_actions = var.ok_actions // 外部から指定 alarm_actions = var.alarm_actions // 外部から指定 }
デプロイ
テスト環境への反映はローカルで手動で行い、本番環境はmasterにマージされた際に、CircleCIからデプロイするようにしています。
workflowは terraforrm init -> plan -> slack通知 -> CircleCI上でplan結果を目視で確認 -> approve -> apply
.circleci/config.yml
の一部
setup_terraform_variables: steps: - run: name: Setup Environment Variables # 環境変数の定義 command: | echo 'export AWS_ACCESS_KEY_ID="${TERRAFORM_AWS_ACCESS_KEY_ID}"' >> $BASH_ENV echo 'export AWS_SECRET_ACCESS_KEY="${TERRAFORM_SECRET_ACCESS_KEY}"' >> $BASH_ENV echo 'export AWS_DEFAULT_REGION="${TERRAFORM_DEFAULT_REGION}"' >> $BASH_ENV terraform-setup: executor: terraform # workspaceの選択, init..etc terraform-plan: executor: terraform steps: - checkout - setup_terraform_variables - attach_workspace: at: ~/repo - run: name: plan command: | source $BASH_ENV # exitcode, 0: nochanges, 1: error, 2: changes terraform plan -input=false -out=terraform.plan -detailed-exitcode cloudwatch > terraform.plan.result ; echo $? > exitcode cat terraform.plan.result working_directory: terraform - persist_to_workspace: root: ~/repo paths: - terraform terraform-apply: executor: terraform steps: - checkout - setup_terraform_variables - attach_workspace: at: ~/repo - run: name: apply command: | source $BASH_ENV terraform apply -input=false -auto-approve terraform.plan working_directory: terraform
工夫した点
planとapplyの結果を担保した点です。
terraform.planというファイルを出力し、Workspaceにおいて、terraform.planファイルを元にapplyすることで計画と実行に差分なく設定を反映することができます。
※exitcodeは別の用途で使うのでここでは無視して下さい
# plan ... command: | terraform plan -input=false -out=terraform.plan -detailed-exitcode cloudwatch > terraform.plan.result ; echo $? > exitcode ... # apply command: | terraform apply -input=false -auto-approve terraform.plan
チームへの展開
実装してリリースした後、運用や横展開も考えた際に個人での運用ではなく、チームでの運用が望ましいため、モブプロを行いました。
モブプロ
- 参加者
- Ansible経験者が何人かいますが、ほとんどTerraformは使ったことないメンバー 2,3人
- 簡単な説明
- Terraformとは
- バケット
- tfstateについて
- 実演
- 実際に本番環境などの監視設定をTerraformで変更してもらう
イメージ
思いの外、準備に時間がかかったり、進行が中々思うように行かなかったところもありますが、概ねやるべきポイントは行えました。
本番の追加改修はほんの少しだったので、もう少し応用的なものをやったら良かったんですが、他に監視の改修箇所がほとんどなく、チーム運用を前提としていた場合、中身自体は80%くらいほどのもので、20%の改修箇所をわざと残すとかした方がいいのかなと感じました。
また他のプロジェクトの横展開をする際にサポートとして入ろうと考えていたんですが、
今回EC2ごとの監視でオートスケール対応はやっていないため、対応してから横展開するのが望ましいことだったので、まだ横展開はしていないです。。*4
おわりに
基盤を作る上でディレクトリ構成はとても重要で、一番悩んだ部分でした。
どの粒度で設定を適用させるか、またプロジェクトのインフラ構成や監視設定、workspaceを使うかどうかで大きく変わってきます。調べるといろんな方がベストプラクティスなどを上げていて大変参考になりました。
新しく技術を投入する際はサービスの責任者または、技術的な責任者と予めヒアリングや相談は必要で、色んな人を巻き込んで仕事を進めていくのは結構難しかったりしますが、まずは会議を設定してしまえばあとはどうにでもなります。
参考資料
qiita.com dev.classmethod.jp buildersbox.corp-sansan.com blog.mitsuruog.info note.com
次はGCPでの商用サービスを対象とした、監視設定のTerraformのコード化のサポート、監視項目の改善、チームへの展開を書こうと思います。
公開は4月か5月頃の予定です。