特定タグがついたEC2を起動停止させるTerraformの話
JINSでクラウドエンジニアとかをやっている@tskomaです。
はじめに
EC2インスタンス、利用されていないタイミングは停止しておきたいですよね!
開発環境や日中帯しか使わないEC2などは課金がもったいないので、使ってない時間は停止しておく運用を以前から実施しており、仕組み自体はよくある管理サーバのEC2を立て、そこからaws ec2 start-instances
をcronで実施するものでした。
この仕組みでもやりたいこととしては問題ないのですが
- リストアなどでインスタンスIDが変わってしまうと再設定必要なのが面倒
- そもそもcron動いていない状態でもEC2が起動しっぱなしなのがもったいない
- せっかくならもうちょっとモダンな構成にしたいよね
ということで、もうちょっといい感じの仕組みに進化させてみました。
構成
構成図は比較的シンプルで以下の通りです。
流れはざっくり以下のような感じで、
- EC2に以下のようなタグをつける(SSM Agent入っている前提です)
- 起動させたいインスタンス
- Key : StartTime
- Value : 起動したい時間(例えば19:00(UTC)なら
19
)
- 停止させたいインスタンス
- Key : StopTime
- Value : 停止したい時間(例えば09:00(UTC)なら
9
)
- 起動させたいインスタンス
- 特定のタグ条件で動くSSMドキュメントを作成する
EC2を起動・停止するSSMドキュメントに以下のような定義を追加し、1.で設定したタグがついているEC2だけに起動・停止を実施するドキュメントを作成する。
Targets:
- Key: 'tag:Start(Stop)Time'
Values:
- '{{ Start(Stop)Time }}'
- 00:00 - 23:00に対応する24個のEventBridge Ruleを作成し、ターゲットを2.で作成したSSMドキュメントとして自動化パラメータで以下のような入力を渡す
{
"Start(Stop)Time": ["11"]
}
こうしてあげることで、11時(UTC)に実行されたEventBridgeが、StartTime:11というパラメータをSSM Automationに渡し、SSM AutomationはStartTime:11というタグが追加インスタンスにだけStartAutomationExecution
を実行することが可能になります。
4. あとは作ったEventBridgeを1時間おきにぶん回す
Terraform
という構成を、Terraformで実装すると以下のような感じになります。
実はそこそこ前に実装していたものなので、バージョン古めでごめんなさい。
対象 | バージョン |
---|---|
Terraform | 1.2.4 |
AWS | 4.18.0 |
また、起動させるケースのみピックアップしますが、停止する場合はStart
をStop
に読み替えてください。
叩くAPIがStopAutomationExecution
以外やっていることはだいたい同じです。
IAMロール
resource "aws_iam_role" "ssm" {
name = "ssm-role-pro"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = { Service = ["events.amazonaws.com","ssm.amazonaws.com"]}
Action = "sts:AssumeRole"
}
]
})
managed_policy_arns = [
"arn:aws:iam::aws:policy/service-role/AmazonSSMAutomationRole"
]
}
resource "aws_iam_role_policy" "ssm_policy" {
role = aws_iam_role.ssm.name
policy = <<POLICY
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Resource": ["*"],
"Action": "tag:GetResources"
}
]
}
POLICY
}
EventBridge Rule
resource "aws_cloudwatch_event_rule" "start" {
count = 24
name = "start-${count.index}-hour-pro"
schedule_expression = "cron(00 ${count.index} * * ? *)"
}
count = 24
にしてEventBridge Ruleを量産させた上、schedule_expression
とname
の中に${count.index}
を入れることでリソース名と実行時刻を一致させてます。
EventBridge Target
locals{
start_arn = "${aws_ssm_document.start_instance.arn}"
}
resource "aws_cloudwatch_event_target" "start" {
count = 24
arn = "${replace(local.start_arn, "document/", "automation-definition/")}"
rule = aws_cloudwatch_event_rule.start[count.index].name
role_arn = aws_iam_role.ssm.arn
input = <<DOC
{
"StartTime": ["${count.index}"]
}
DOC
}
ここに結構手こずったのですが、aws_cloudwatch_event_target
内でtargetのarnを指定する際にautomation-definition/
にしてあげないと「ここにinput入れるなー」と怒られてしまったため、arnを置換するやり方とっています。
その実装のために、local変数作ってreplace
やったりしていますが、もっとスマートな実装はありそう。
そもそも、どこかのバージョンでいい感じに改善されているかも?しれないので、もしご存じの方いれば教えてください...
SSM Document
resource "aws_ssm_document" "start_instance" {
name = "StartEC2Instances_UsingTags"
document_type = "Automation"
document_format = "YAML"
content = <<DOC
description: 'StartEC2Instances Using Tags:StartTime'
schemaVersion: '0.3'
assumeRole: '{{ AutomationAssumeRole }}'
parameters:
StartTime:
type: String
default: null
description: (Required) hour
AutomationAssumeRole:
type: String
description: (Optional) The ARN of the role that allows Automation to perform the actions on your behalf.
default: ''
mainSteps:
- name: StartEC2Instances
action: 'aws:executeAwsApi'
inputs:
Service: ssm
Api: StartAutomationExecution
DocumentName: AWS-StartEC2Instance
TargetParameterName: InstanceId
Targets:
- Key: 'tag:StartTime'
Values:
- '{{ StartTime }}'
DOC
}
ここは割とそのまんまで、Targets:
部分を追加しているくらいです。
やってみて
実装してみた結果として、以下あたり挙げられます。
- インスタンスにタグをつけるだけで起動・停止してくれるのは素直に便利
- 管理サーバが潰せるからEC2 x 1台分のコスト削減
絶賛、複数アカウントでEC2起動・停止をこの仕組みに切り替え、管理サーバを停止しようと頑張ってます。 - 今まではサーバにSSHでログインして、cronの設定見て、cronを変更して...だったのが、起動・停止したいインスタンスにタグをつけるだけになったことでマネコン触れる人ならば設定できるという点で、サーバの起動時間制限してねのハードルを下げることができた
- 当たり前ですが、Terraform化しておいたことで、マルチアカウント環境でも色々なAWSアカウントにこの仕組みをばら撒くのが簡単
- タグで制御することによってこんな感じでマネコンからどのインスタンスが何時に起動・停止されるかが見やすくなった
今まで、担当者から「このサーバって何時に起動してるんでしたっけー?」と聞かれるたびにサーバのcronを確認しにいっていた状況から、「マネコン見といて」で済むようになったのは非常に便利です。
Terraformの実装で地味に手こずる部分ありましたが、実装してしまえばメリットしかないなーです!
まとめ
タグベースでEC2インスタンスの起動・停止をサーバレスでできるいい感じの仕組みをTerraformで実装できました。
ながーく使っている仕組みを多少は新しい仕組みに置き換える活動はシンプルに楽しいですし、円安が進みまくる昨今ではちりつも方式でもコスト最適化を進めていくことはとても大事と思っているので、こういった改善活動は続けていきたいなーと思っています!
Discussion