メドピア開発者ブログ

集合知により医療を再発明しようと邁進しているヘルステックカンパニーのエンジニアブログです。読者に有用な情報発信ができるよう心がけたいので応援のほどよろしくお願いします。

TerraformでCloudWatch EventsのEBSスナップショット定期作成機能を設定する

はじめに

世の中の医療・ヘルスケア情報を医師たちが実名で解説するWEBメディア、イシコメ開発担当の大谷です。 イシコメは少数の開発者で開発・運営しているため、省力化のためTerraformなどのツールによりインフラ管理を自動化しています。

今回は、TerraformでEBSのスナップショットをサーバレスで定期的に作成する方法について調査したため、その方法を共有します。

本稿ではTerraform自体の解説は行いません。 Terraformを使ったことが無い方は、公式サイトのチュートリアルが分かりやすいので是非試してみて下さい。

尚、使用したTerraformのバージョンはv0.6.16です。

手順

CloudWatch EventsのEBSスナップショット作成機能をTerraformで設定します。 必要なのは.tfファイル一つですが、その中に記述する要素について順を追って説明します。

1. AWSのproviderを準備する

いつもの設定です。

variable "access_key" {}
variable "secret_key" {}
variable "aws_account_id" {}
variable "region" {
  default = "ap-northeast-1"
}

provider "aws" {
  access_key = "${var.access_key}"
  secret_key = "${var.secret_key}"
  region = "${var.region}"
}

2. 取得対象のEBSのIDを指定する

ハードコードにしてもいいですが、実用的ではないので変数化してしまいます。

variable "ebs_id" {}

3. IAM Roleを作成する

CloudWatch EventsがEBSを作成できるように権限を設定します。

resource "aws_iam_role" "cloudwatch_events_automatic_execution" {
  name = "cloudwatch_events_automatic_execution"
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
      {
          "Action": "sts:AssumeRole",
          "Principal": {
            "Service": "events.amazonaws.com"
          },
          "Effect": "Allow"
      }
  ]
}
EOF
}

resource "aws_iam_role_policy" "cloudwatch_events_automatic_execution_policy" {
  name = "cloudwatch_events_automatic_execution_policy"
  role = "${aws_iam_role.cloudwatch_events_automatic_execution.id}"
  policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "ec2:Describe*",
                "ec2:CreateSnapshot"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}
EOF
}

4. EBSスナップショット作成のスケジュールを設定する

aws_cloudwatch_event_ruleリソースでCloudWatch Eventsのルールを作成します。 ここでは主に先程作成したIAM Roleの指定と、実行間隔を設定することになります。 今回は、1日1回実行するようにしてみましたが、変更したい場合はCloudWatch Eventsの仕様に従ってschedule_expressionオプションを変更して下さい。

resource "aws_cloudwatch_event_rule" "take_snapshots_every_day" {
  name = "take_snapshots_every_day"
  description = "Fires every day"
  schedule_expression = "rate(1 day)"
  role_arn = "${aws_iam_role.cloudwatch_events_automatic_execution.arn}"
}

5. EBSスナップショット作成用のイベントターゲットを設定する

ここでスナップショット作成対象EBSボリュームのIDを${var.ebs_id}ではなく${aws_ebs_volume.your_volume.id}のような形でtfファイルの他の場所で作成したEBSボリュームのIDで置き換えるとインフラ一式の作成にあわせてIDが自動で設定されるので便利だと思います。

resource "aws_cloudwatch_event_target" "take_snapshots_every_day" {
  rule = "${aws_cloudwatch_event_rule.take_snapshots_every_day.name}"
  arn = "arn:aws:automation:${var.region}:${var.aws_account_id}:action/EBSCreateSnapshot/EBSCreateSnapshot_${aws_cloudwatch_event_rule.take_snapshots_every_day.name}"
  input = "\"arn:aws:ec2:${var.region}:${var.aws_account_id}:volume/${var.ebs_id}\""
}

実行してみる

今回はaccess_keyなどの設定をsecrets.tfvarsファイルに書いて実行しました。

aws_account_id = "<< substitute me >>"
access_key = "<< substitute me >>"
secret_key = "<< substitute me >>"
ebs_id = "<< substitute me >>"

plan

% terraform plan -state=terraform.tfstate -var-file=secrets.tfvars
Refreshing Terraform state prior to plan...


The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

+ aws_cloudwatch_event_rule.take_snapshots_every_day
    arn:                 "" => "<computed>"
    description:         "" => "Fires every day"
    is_enabled:          "" => "1"
    name:                "" => "take_snapshots_every_day"
    role_arn:            "" => "${aws_iam_role.cloudwatch_events_automatic_execution.arn}"
    schedule_expression: "" => "rate(1 day)"

+ aws_cloudwatch_event_target.take_snapshots_every_day
    arn:       "" => "arn:aws:automation:ap-northeast-1:xxxxxxxxxxxx:action/EBSCreateSnapshot/EBSCreateSnapshot_take_snapshots_every_day"
    input:     "" => "\"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:volume/vol-xxxxxxxx\""
    rule:      "" => "take_snapshots_every_day"
    target_id: "" => "<computed>"

+ aws_iam_role.cloudwatch_events_automatic_execution
    arn:                "" => "<computed>"
    assume_role_policy: "" => "{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n      {\n          \"Action\": \"sts:AssumeRole\",\n          \"Principal\": {\n            \"Service\": \"events.amazonaws.com\"\n          },\n          \"Effect\": \"Allow\"\n      }\n  ]\n}\n"
    name:               "" => "cloudwatch_events_automatic_execution"
    path:               "" => "/"
    unique_id:          "" => "<computed>"

+ aws_iam_role_policy.cloudwatch_events_automatic_execution_policy
    name:   "" => "cloudwatch_events_automatic_execution_policy"
    policy: "" => "{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Action\": [\n                \"ec2:Describe*\",\n                \"ec2:CreateSnapshot\"\n            ],\n            \"Effect\": \"Allow\",\n            \"Resource\": \"*\"\n        }\n    ]\n}\n"
    role:   "" => "${aws_iam_role.cloudwatch_events_automatic_execution.id}"


Plan: 4 to add, 0 to change, 0 to destroy.

apply

% terraform apply -state=terraform.tfstate -var-file=secrets.tfvars
aws_iam_role.cloudwatch_events_automatic_execution: Creating...
  arn:                "" => "<computed>"
  assume_role_policy: "" => "{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n      {\n          \"Action\": \"sts:AssumeRole\",\n          \"Principal\": {\n            \"Service\": \"events.amazonaws.com\"\n          },\n          \"Effect\": \"Allow\"\n      }\n  ]\n}\n"
  name:               "" => "cloudwatch_events_automatic_execution"
  path:               "" => "/"
  unique_id:          "" => "<computed>"
aws_iam_role.cloudwatch_events_automatic_execution: Creation complete
aws_iam_role_policy.cloudwatch_events_automatic_execution_policy: Creating...
  name:   "" => "cloudwatch_events_automatic_execution_policy"
  policy: "" => "{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Action\": [\n                \"ec2:Describe*\",\n                \"ec2:CreateSnapshot\"\n            ],\n            \"Effect\": \"Allow\",\n            \"Resource\": \"*\"\n        }\n    ]\n}\n"
  role:   "" => "cloudwatch_events_automatic_execution"
aws_cloudwatch_event_rule.take_snapshots_every_day: Creating...
  arn:                 "" => "<computed>"
  description:         "" => "Fires every day"
  is_enabled:          "" => "1"
  name:                "" => "take_snapshots_every_day"
  role_arn:            "" => "arn:aws:iam::xxxxxxxxxxxx:role/cloudwatch_events_automatic_execution"
  schedule_expression: "" => "rate(1 day)"
aws_iam_role_policy.cloudwatch_events_automatic_execution_policy: Creation complete
aws_cloudwatch_event_rule.take_snapshots_every_day: Still creating... (10s elapsed)
aws_cloudwatch_event_rule.take_snapshots_every_day: Creation complete
aws_cloudwatch_event_target.take_snapshots_every_day: Creating...
  arn:       "" => "arn:aws:automation:ap-northeast-1:xxxxxxxxxxxx:action/EBSCreateSnapshot/EBSCreateSnapshot_take_snapshots_every_day"
  input:     "" => "\"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:volume/vol-xxxxxxxx\""
  rule:      "" => "take_snapshots_every_day"
  target_id: "" => "<computed>"
aws_cloudwatch_event_target.take_snapshots_every_day: Creation complete

Apply complete! Resources: 4 added, 0 changed, 0 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: terraform.tfstate

awsコマンドで確認

コマンドラインで確認すると、CloudWatch EventsのルールとIAM Roleの一式が作られています。

% aws iam get-role --role-name cloudwatch_events_automatic_execution
{
    "Role": {
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Action": "sts:AssumeRole",
                    "Effect": "Allow",
                    "Principal": {
                        "Service": "events.amazonaws.com"
                    }
                }
            ]
        },
        "RoleId": "XXXXXXXXXXXXXXXXXXXXX",
        "CreateDate": "2016-05-31T12:54:18Z",
        "RoleName": "cloudwatch_events_automatic_execution",
        "Path": "/",
        "Arn": "arn:aws:iam::xxxxxxxxxxxx:role/cloudwatch_events_automatic_execution"
    }
}
% aws iam get-role-policy --role-name cloudwatch_events_automatic_execution --policy-name cloudwatch_events_automatic_execution_policy
{
    "RoleName": "cloudwatch_events_automatic_execution",
    "PolicyDocument": {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Action": [
                    "ec2:Describe*",
                    "ec2:CreateSnapshot"
                ],
                "Resource": "*",
                "Effect": "Allow"
            }
        ]
    },
    "PolicyName": "cloudwatch_events_automatic_execution_policy"
}
% aws events list-rules
{
    "Rules": [
        {
            "ScheduleExpression": "rate(1 day)",
            "Name": "take_snapshots_every_day",
            "RoleArn": "arn:aws:iam::xxxxxxxxxxxx:role/cloudwatch_events_automatic_execution",
            "State": "ENABLED",
            "Arn": "arn:aws:events:ap-northeast-1:xxxxxxxxxxxx:rule/take_snapshots_every_day",
            "Description": "Fires every day"
        }
    ]
}
% aws events list-targets-by-rule --rule take_snapshots_every_day
{
    "Targets": [
        {
            "Input": "\"arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:volume/vol-xxxxxxxx\"",
            "Id": "terraform-xxxxxxxxxxxxxxxxxxxxxxxxxx",
            "Arn": "arn:aws:automation:ap-northeast-1:xxxxxxxxxxxx:action/EBSCreateSnapshot/EBSCreateSnapshot_take_snapshots_every_day"
        }
    ]
}

おわりに

今回は、TerraformでCloudWatch EventsによるEBSボリュームのスナップショット作成機能を設定してみました。 マネジメントコンソールからなら簡単に設定できるのにTerraformでやろうとすると色々調べて回らないといけないので大変でした。 しかし、一度コードに落としてしまえば使いまわせるのがTerraformの良いところですね。

ところで、この方法は設定しなければいけない要素が少なくて手軽ではあるんですが、古いスナップショットを手動で作成しなければいけなかったりと万能ではありません。 より細かい制御がしたい場合はLambdaを使うことになります。 Lambdaも同様にTerraformで設定できるので、興味が湧いたら調べてみて下さいませ。

それでは

ソースコード

今回実行したtfファイルのサンプルコードです。

variable "access_key" {}
variable "secret_key" {}
variable "aws_account_id" {}
variable "region" {
  default = "ap-northeast-1"
}
variable "ebs_id" {}

provider "aws" {
  access_key = "${var.access_key}"
  secret_key = "${var.secret_key}"
  region = "${var.region}"
}

resource "aws_iam_role" "cloudwatch_events_automatic_execution" {
  name = "cloudwatch_events_automatic_execution"
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
      {
          "Action": "sts:AssumeRole",
          "Principal": {
            "Service": "events.amazonaws.com"
          },
          "Effect": "Allow"
      }
  ]
}
EOF
}

resource "aws_iam_role_policy" "cloudwatch_events_automatic_execution_policy" {
  name = "cloudwatch_events_automatic_execution_policy"
  role = "${aws_iam_role.cloudwatch_events_automatic_execution.id}"
  policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "ec2:Describe*",
                "ec2:CreateSnapshot"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}
EOF
}

resource "aws_cloudwatch_event_rule" "take_snapshots_every_day" {
  name = "take_snapshots_every_day"
  description = "Fires every day"
  schedule_expression = "rate(1 day)"
  role_arn = "${aws_iam_role.cloudwatch_events_automatic_execution.arn}"
}

resource "aws_cloudwatch_event_target" "take_snapshots_every_day" {
  rule = "${aws_cloudwatch_event_rule.take_snapshots_every_day.name}"
  arn = "arn:aws:automation:${var.region}:${var.aws_account_id}:action/EBSCreateSnapshot/EBSCreateSnapshot_${aws_cloudwatch_event_rule.take_snapshots_every_day.name}"
  input = "\"arn:aws:ec2:${var.region}:${var.aws_account_id}:volume/${var.ebs_id}\""
}