現場でAWSバックアップを利用する機会があったので、自宅でもEFSをバックアップしてみた。

概要

現場でEFSを対象としている既存のAWSバックアップ(Vaultなど)をcloudformationの管理下におこうというのがことの始まりでした。 当初cloudformationのテンプレートを作成しそこにインポートしようとしたのですが、 既存のAWSバックアップはEFS側で「自動バックアップ」を有効化した際にAWS側で自動で作成されるBackUpのようで、 これをユーザー側でスタックにインポートなどはできないようでした。(AWSサポートからの回答) ですので、現場としてはEFSの「自動バックアップ」を無効化し、専用のBackUpVault/BackUpPlan/BackUpSelectionを作成しバックアップしたいEFSを対象とすることにしました。

これを自宅でもやってみます。 ただ、cloudformationではなくterraformで構築します。

この記事はそのメモやまとめです。 自分の環境用のメモでもあるので、省略している記載もあります。 すでに設定済みの項目がいくつかある。 試行錯誤しながらやったので、手順として不要かもしれない。あくまでメモ用。

EFS

まずはバックアップ対象のテスト用のEFSを作成します。 requirements.tfに以下を記載して、terraform_remote_stateで、 他のterraformディレクトリで作成してあるmainのVPCとSubnetの情報を参照できるようにします。 (あらかじめVPCとSubnet側でoutputを定義しておきます)

# VPC/subnet情報を取得
data "terraform_remote_state" "vpc" {
  backend = "s3"
  config = {
    bucket = "xxxx"
    key    = "terraform_my_workspace/terraform.tfstate"
    region = "ap-northeast-1"
  }
}

出来上がったEFSのコードは以下です。出来だけコストはかけたくなく暗号化など行っていません。

# ----------------------------------------- #
# EFS
# ----------------------------------------- #
resource "aws_efs_file_system" "low_cost" {
  creation_token   = "my-efs"
  performance_mode = "generalPurpose"
  throughput_mode  = "bursting"
  encrypted        = false

  tags = {
    Name = "low-cost-efs"
    BackupEnabled = "true"
    Environment   = "test"
    Owner         = "terraform"
    Project       = "terraform_test"
  }
}

# EFSマウントターゲット(プライベートサブネットに作成)
resource "aws_efs_mount_target" "private_a" {
  file_system_id  = aws_efs_file_system.low_cost.id
  subnet_id       = data.terraform_remote_state.vpc.outputs.private_subnet_ids[0]
  security_groups = [aws_security_group.efs_sg.id]
}

resource "aws_efs_mount_target" "private_c" {
  file_system_id  = aws_efs_file_system.low_cost.id
  subnet_id       = data.terraform_remote_state.vpc.outputs.private_subnet_ids[1]
  security_groups = [aws_security_group.efs_sg.id]
}

# ----------------------------------------- #
# EFS SG
# ----------------------------------------- #
resource "aws_security_group" "efs_sg" {
  name        = "efs-sg"
  description = "Allow NFS access for EFS"
  vpc_id      = data.terraform_remote_state.vpc.outputs.vpc_id

  ingress {
    description = "NFS"
    from_port   = 2049
    to_port     = 2049
    protocol    = "tcp"
    cidr_blocks = [data.terraform_remote_state.vpc.outputs.vpc_cidr_block]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "efs-sg"
  }
}

上記をapplyして、EFSを構築しておきます。

BackUp

次のBackupのVault/Plan(Rule)/Selectionを作成します。
ざっくりとした認識ですが、

Vault → バックアップ格納先
Plan → Planの中にRuleがあり、Rule毎にいつバックアップを実行してどのVaultに格納するかなどを定義する
Selection → なにをバックアップするか対象を指定する

と言った感じです。

SelectionではIAMロールの指定も必要なので一緒に作成します。 ポリシーについてはすでにAWS側で用意されている以下のポリシーがあるので、それを利用します。

arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup
arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForRestores

Planの注意点として、ruleの時間は何も指定しないとUTC時刻でバックアップを実行します。
なのでわかりやすくschedule_expression_timezoneで東京時刻にしてあげたほうがいいと思います。

  rule {
    rule_name                    = "immediate_test_backup"
    target_vault_name            = aws_backup_vault.efs_backup_vault.name
    schedule                     = "cron(30 9 * * ? *)"
    schedule_expression_timezone = "Asia/Tokyo"
    start_window                 = 60
    completion_window            = 120

出来上がったEFSのコードは以下です。 補足として、テスト用の「immediate_test_backup」とデイリーで実行される「daily_backup」の2つを作成し、
EFSのIDが一致しており、BackupEnabled = “true"のタグがついている場合にバックアップされます。

# ----------------------------------------- #
# BackupVault
# ----------------------------------------- #

resource "aws_backup_vault" "efs_backup_vault" {
  name = "efs-backup-vault"
}

# ----------------------------------------- #
# BackupPlan
# ----------------------------------------- #

resource "aws_backup_plan" "efs_backup_plan" {
  name = "efs-backup-plan"

  rule {
    rule_name                    = "immediate_test_backup"
    target_vault_name            = aws_backup_vault.efs_backup_vault.name
    schedule                     = "cron(30 9 * * ? *)"
    schedule_expression_timezone = "Asia/Tokyo"
    start_window                 = 60
    completion_window            = 120

    lifecycle {
      delete_after = 7
    }

    recovery_point_tags = {
      BackupType = "Test"
    }
  }

  # 通常の日次バックアップ(コールドストレージ制約を満たす設定)
  rule {
    rule_name                    = "daily_backup"
    target_vault_name            = aws_backup_vault.efs_backup_vault.name
    schedule                     = "cron(0 2 * * ? *)" # 毎日午前2時
    schedule_expression_timezone = "Asia/Tokyo"

    lifecycle {
      cold_storage_after = 30  # 30日後にコールドストレージ
      delete_after       = 120 # 120日で削除(90日間隔を満たす)
    }

    recovery_point_tags = {
      BackupType = "Daily"
    }
  }
}

# ----------------------------------------- #
# BackupSelection
# ----------------------------------------- #

resource "aws_iam_role" "backup_role" {
  name = "efs-backup-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "backup.amazonaws.com"
        }
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "backup_policy" {
  role       = aws_iam_role.backup_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup"
}

resource "aws_iam_role_policy_attachment" "restore_policy" {
  role       = aws_iam_role.backup_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForRestores"
}

resource "aws_backup_selection" "efs_backup_selection" {
  iam_role_arn = aws_iam_role.backup_role.arn
  name         = "efs-backup-selection"
  plan_id      = aws_backup_plan.efs_backup_plan.id

  resources = [
    aws_efs_file_system.low_cost.arn
  ]

  condition {
    string_equals {
      key   = "aws:ResourceTag/BackupEnabled"
      value = "true"
    }
  }
}

上記でapplyしてリソースを作成する。 テスト用のバックアップの時間後、しばらくしてGUIを確認するとバックアップに成功していた。

001

詰まったところ

schedule_expression_timezoneが認識されない

terraformでschedule_expression_timezoneを記載planをかけるとこの記載がエラーとなりました。

╷
│ Error: Unsupported argument
│ 
│   on aws_efs_backup.tf line 28, in resource "aws_backup_plan" "efs_backup_plan":
│   28:     schedule_expression_timezone = "Asia/Tokyo"
│ 
│ An argument named "schedule_expression_timezone" is not expected here.
╵
╷
│ Error: Unsupported argument
│ 
│   on aws_efs_backup.tf line 46, in resource "aws_backup_plan" "efs_backup_plan":
│   46:     schedule_expression_timezone = "Asia/Tokyo"
│ 
│ An argument named "schedule_expression_timezone" is not expected here.

terraformのドキュメントをみてもしっかりと定義されていました。 https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_plan#schedule_expression_timezone-1

どうやらこのセクションは比較的新しく取り込まれたらしく、terraformのversionを上げてみると動きました。

terraform init -upgrade

Selectionの詳細確認

Selectionの詳細確認をCLIで行うのははちょっと面倒でした
SelectionIdはGUI上では表示されないので、コマンドを2回ほど実行する必要がある。

	list-backup-plans で対象backupplanのIDを調べる
	list-backup-selections で上記backupplanのIDを利用してbackupselectionのIDを調べる
at 13:31:15 ⬢ [Docker] ❯ aws backup --region ap-northeast-1 list-backup-plans
{
    "BackupPlansList": [
        {
            "BackupPlanArn": "arn:aws:backup:ap-northeast-1:xxxx:backup-plan:2a26a5b0-a5ce-4e45-b377-46e52848026c",
            "BackupPlanId": "2a26a5b0-a5ce-4e45-b377-46e52848026c",
            "CreationDate": "2025-06-02T13:28:16.688000+09:00",
            "VersionId": "NjI0NGZiZWEtMWQzYy00MjgwLWJiMzctMzkyNWViM2RiN2Vl",
            "BackupPlanName": "efs-backup-plan"
        }
    ]
}
terraform/terraform_test on  master [📝🌚‍] via 💠 default 

at 13:33:28 ⬢ [Docker] ❯ aws backup --region ap-northeast-1 list-backup-selections --backup-plan-id 2a26a5b0-a5ce-4e45-b377-46e52848026c | jq
{
  "BackupSelectionsList": [
    {
      "SelectionId": "9676adfe-96cf-4fe8-b92d-c9dc88d1402d",
      "SelectionName": "efs-backup-selection",
      "BackupPlanId": "2a26a5b0-a5ce-4e45-b377-46e52848026c",
      "CreationDate": "2025-06-02T12:11:22.139000+09:00",
      "IamRoleArn": "arn:aws:iam::xxxx:role/efs-backup-role"
    }
  ]
}
terraform/terraform_test on  master [📝🌚‍] via 💠 default 
at 13:33:29 ⬢ [Docker] ❯ 

を使用して初めてIDがわかる。 更に詳細を見たい場合は、get-backup-selectionで確認できる。

	get-backup-selection で上記backupplanのIDとbackupselectionのIDを利用する
terraform/terraform_test on  master [📝🌚‍] via 💠 default took 2s 
at 14:23:58 ⬢ [Docker] ❯ aws backup --region ap-northeast-1 get-backup-selection --backup-plan-id 2a26a5b0-a5ce-4e45-b377-46e52848026c --selection-id 9676adfe-96cf-4fe8-b92d-c9dc88d1402d | jq
{
  "BackupSelection": {
    "SelectionName": "efs-backup-selection",
    "IamRoleArn": "arn:aws:iam::xxxx:role/efs-backup-role",
    "Resources": [
      "arn:aws:elasticfilesystem:ap-northeast-1:xxxx:file-system/fs-05d3e738e6a46d167"
    ],
    "ListOfTags": [],
    "NotResources": [],
    "Conditions": {
      "StringEquals": [
        {
          "ConditionKey": "aws:ResourceTag/BackupEnabled",
          "ConditionValue": "true"
        }
      ],
      "StringNotEquals": [],
      "StringLike": [],
      "StringNotLike": []
    }
  },
  "SelectionId": "9676adfe-96cf-4fe8-b92d-c9dc88d1402d",
  "BackupPlanId": "2a26a5b0-a5ce-4e45-b377-46e52848026c",
  "CreationDate": "2025-06-02T12:11:22.139000+09:00"
}
terraform/terraform_test on  master [📝🌚‍] via 💠 default 
at 14:24:03 ⬢ [Docker] ❯ 

他にやりたいこと

  • vaultで他リージョンへのレプリケーション設定を有効にする
  • Selectionでtag名に「develop*」の名前がつくリソースのみをバックアップの対象とする

終わりに

現場でAWS BackUpを触る機会があったので、自宅でも触ってみました。
一度も触ったことがなかったサービスなので、非常によう経験に鳴ったかと思います。