こしあん
2025-01-20

AWS BackupによるEC2のバックアップを試す


11{icon} {views}

Terraformを使い、タグやIAMロールなどを設定してEC2インスタンスを自動バックアップし、定期的なスナップショットの管理や復元を簡単に行える。また不要になったバックアップやスナップショットも自動的に整理されるため、運用コストを抑えながら安全にリソースを管理できる。

はじめに

  • AWS Backupって話には聞いてたけど、実際使ったことなかったのでEC2をバックアップしてみた。
  • EC2内で定期的に(1分おき)にファイルを生成させ、そのEC2をAWS Backupでバックアップするという単純なもの
  • AWS Backupは「特定のタグを含むEC2」という条件でやってみた

ディレクトリ構成

以下の2つのTerraformのファイルからなる

  • backup.tf
  • ec2.tf

backup.tfにはAWS Backupの設定や必要なロール、ec2.tfにはバックアップ対象のEC2やIAMロールの設定を書く。EC2内のアプリケーション(1分おきにファイルを生成)するのはユーザースクリプトに書く。

AWS Backup (backup.tf)

  • バックアップ時には「ボールド」というのを作成する。
  • ボールド以下にバックアッププランや、バックアップの条件を追加していく。
    • この例では3時間おきにバックアップ(cron式で定義)
    • BackupというタグがYesのもののみバックアップ(aws_backup_selectionで定義)
  • IAMロールについて
    • バックアップを取得するためなら、AWSBackupServiceRolePolicyForBackupのマネージドポリシーだけでOK
    • バックアップを復元するためなら、ひとまずAWSBackupServiceRolePolicyForRestoresのマネージドポリシーが必要
    • さらにEC2を起動するために、EC2のIAMロールに対してiam:PassRoleが必要。マネジメントコンソールでiam:PassRoleが必要ですと書いてあるのはこの意味。したがって、EC2のIAMロールをリソースとして、iam:PassRoleを許可すれば良い
resource "aws_backup_vault" "sample" {
  name = "SampleValut"
}

resource "aws_backup_plan" "ec2_backup_plan" {
  name = "ec2-backup-plan"

  rule {
    rule_name         = "3-hour-backup-rule"
    target_vault_name = aws_backup_vault.sample.name

    # 3時間ごとのスケジュールをcron式で指定
    schedule = "cron(0 */3 * * ? *)"

    # バックアップの保持期間(日数)
    lifecycle {
      delete_after = 7 # 例えば7日間保持
    }
  }
}

resource "aws_backup_selection" "ec2_backup_selection" {
  name         = "ec2-backup-selection"
  iam_role_arn = aws_iam_role.backup_role.arn
  plan_id      = aws_backup_plan.ec2_backup_plan.id

  selection_tag {
    type  = "STRINGEQUALS" # タグの比較タイプ
    key   = "Backup"
    value = "Yes"
  }
}

# AWS Backupがバックアップ操作を実行するためのIAMロールを作成
resource "aws_iam_role" "backup_role" {
  name = "TerraformBackupRole"

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

# バックアップ操作に必要なポリシーを付与
resource "aws_iam_role_policy_attachment" "backup_role_policy" {
  role       = aws_iam_role.backup_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup"
}

# 復元操作に必要なポリシーを付与
resource "aws_iam_role_policy_attachment" "backup_role_policy_for_restore" {
  role       = aws_iam_role.backup_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForRestores"
}

# EC2インスタンスのロールに対するiam:PassRoleを許可
resource "aws_iam_role_policy" "pass_role_policy" {
  name   = "PassRolePolicy"
  role   = aws_iam_role.backup_role.name
  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Effect   = "Allow",
        Action   = "iam:PassRole",
        Resource = aws_iam_role.ssm_role.arn
      }
    ]
  })
}

EC2側(ec2.tf)

普通にEC2を作っていくだけ。デバッグ用にSession Managerをつけている。これとは別に、プライベートサブネットにデプロイし、NATインスタンスを開通済み。

一番面倒くさいのはユーザースクリプト部分という、AWS Backupの本質とはあまり関係ない部分で、もとはcrontabに追加する設定だったのだが、あまりにバグるのでGPTに「cronを使わないでなんとかしろ」と書かせたもの。これでうまくいく。

##########################
# IAMロールおよびプロファイル設定
##########################
# EC2がSession Managerを利用するためのIAMロールを作成
resource "aws_iam_role" "ssm_role" {
  name = "ec2_sample_ssm_role"

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

# Session Manager用のポリシーをIAMロールにアタッチ
resource "aws_iam_role_policy_attachment" "ssm_attach" {
  role       = aws_iam_role.ssm_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

# IAMインスタンスプロファイルの作成
resource "aws_iam_instance_profile" "ssm_profile" {
  name = "ec2_sample_ssm_profile"
  role = aws_iam_role.ssm_role.name
}

resource "aws_security_group" "ec2_sg" {
  name        = "sample_ec2_sg"
  description = "A security group for EC2"
  vpc_id      = var.vpc_id

  egress {
    description      = "Allow all outbound traffic"
    from_port        = 0
    to_port          = 0
    protocol         = "-1"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }
}


##########################
# EC2インスタンス設定(user_data は LF 改行で保存する)
##########################
resource "aws_instance" "ubuntu_ssm" {
  ami                         = data.aws_ami.ubuntu.id
  instance_type               = "t3.small"
  subnet_id                   = var.subnet_id
  vpc_security_group_ids      = [aws_security_group.ec2_sg.id]
  iam_instance_profile        = aws_iam_instance_profile.ssm_profile.name
  associate_public_ip_address = false

  user_data = <<-EOF
#!/bin/bash
set -ex

# 1) ディレクトリを作り、所有者を ubuntu にしておく
mkdir -p /home/ubuntu/data
chown ubuntu:ubuntu /home/ubuntu/data

# 2) 毎分ファイルを生成するスクリプトを配置
cat << 'SCRIPT' > /usr/local/bin/create_dummy_file.sh
#!/bin/bash
mkdir -p /home/ubuntu/data
date > /home/ubuntu/data/$(date +%F_%T).txt
SCRIPT
chmod +x /usr/local/bin/create_dummy_file.sh

# 3) systemdのサービスユニット
cat << 'SERVICE' > /etc/systemd/system/create-dummy.service
[Unit]
Description=Create dummy file

[Service]
Type=oneshot
ExecStart=/usr/local/bin/create_dummy_file.sh
SERVICE

# 4) systemdのタイマーユニット
cat << 'TIMER' > /etc/systemd/system/create-dummy.timer
[Unit]
Description=Run create-dummy every minute

[Timer]
# 毎分00秒に起動 (例: 09:00:00, 09:01:00, 09:02:00, ...)
OnCalendar=*-*-* *:*:00
Unit=create-dummy.service

[Install]
WantedBy=multi-user.target
TIMER

# 5) systemdをリロードし、timerを有効化&起動
systemctl daemon-reload
systemctl enable create-dummy.timer
systemctl start create-dummy.timer
EOF

  tags = {
    Name   = "Ubuntu-SSM"
    Backup = "Yes"
  }
}

# 最新のUbuntu 24.04 LTSのAMIを取得する例
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"] # CanonicalのAMI所有者ID

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
  }

  filter {
    name   = "architecture"
    values = ["x86_64"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
}

バックアップ結果と復元

terraform applyをしてしばらく放置すると、以下のようにバックアップができている。

メインのEC2

ずっと最初からデプロイされているEC2にSession Managerで接続する。以下のように大量にファイル(789個)ができている。

root@ip-172-18-197-155:~# find /home/ubuntu/data -type f | wc -l
789
root@ip-172-18-197-155:~# ls -t /home/ubuntu/data | head -n 10
2025-01-20_06:48:03.txt
2025-01-20_06:47:04.txt
2025-01-20_06:46:14.txt
2025-01-20_06:45:14.txt
2025-01-20_06:44:27.txt
2025-01-20_06:43:03.txt
2025-01-20_06:42:04.txt
2025-01-20_06:41:14.txt
2025-01-20_06:40:07.txt
2025-01-20_06:39:27.txt

EC2の復元

復元ポイントをクリックすると復元できる。新たにEC2を作成するのとほぼ同じようなやり方。ここではバックアップ用のロール(TerraformBackupRole)を指定したが、別途復元用のロールを指定しても良い。

復元すると今までのEC2とは別にインスタンスが生成される。

復元されたインスタンスでのテスト

復元されたインスタンスにSession Managerで乗り込み、同様のコマンドを実行してみる。

root@ip-172-18-197-37:~# find /home/ubuntu/data -type f | wc -l
404
root@ip-172-18-197-37:~# ls -t /home/ubuntu/data | head -n 10
2025-01-20_06:56:05.txt
2025-01-20_00:21:03.txt
2025-01-20_00:20:23.txt
2025-01-20_00:19:27.txt
2025-01-20_00:18:03.txt
2025-01-20_00:17:03.txt
2025-01-20_00:16:13.txt
2025-01-20_00:15:23.txt
2025-01-20_00:14:27.txt
2025-01-20_00:13:03.txt

404個のテキストファイルがあり、ファイルの日付に断絶がある。これはバックアップの時間が9時のもので、15時すぎに復元したため。想定された挙動である。

削除時

terraform destroyするとボールドにバックアップが残っていると以下のようなエラーになる。これはS3のdestroy時と同じ挙動で、force_destroyを入れておくと一気に消すことが可能(本番時は非推奨)。

Backup vault cannot be deleted because it contains recovery points.

また、バックアップができると、スナップショット・AMIが生成されるが、ボールド側のバックアップを消すと、スナップショット・AMIも自動的に消されるため、AWS Backupを使うとリソースが散らかりにくいというメリットがある。

所感

  • 普通に便利そう
  • 機械的にスナップショットやAMIを定期的に取るんだったらこれでいいと思う。iam:PassRoleの権限がちょっとハマるかも
  • どっちかというと、EC2内でのユーザースクリプトで1分ごとにファイルを吐き出すという本質的でないところに大ハマリしたのがうーんという感。もうcronは使いたくない!w


Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内

技術書コーナー

北海道の駅巡りコーナー


Add a Comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です