こしあん
2025-02-16

CloudFormationのメタデータでEC2ユーザーデータの制限を回避する


23{icon} {views}


CloudFormationのMetadata機能を使い、EC2にYAML形式で構成を適用することでユーザーデータの16KB制限を回避できる。Terraformと組み合わせてPyTorchを自動インストールする例を示し、保守性と再利用性を向上させる手法を紹介する。

はじめに

  • EC2のユーザーデータには、インスタンス初期化時に読まれるが、Base64でエンコードして16KBまでしか作れない
  • エンコードやプレースホルダーなどバグる要因が多い。これまで試したときも結構ここのデバッグが時間かかった
  • CloudFormation限定の機能で、EC2のユーザーデータをメタデータとしてYAML形式に分離することができる。これにより16KBの制限を突破でき、保守性や再利用性が良くなる。

作るもの

  • EC2 ImageBuilderを試すのときに作った、最新のAmazon Linux 2023にPyTorchを入れる例を試す
  • CloudFormationでSession Managerで接続可能なEC2+関連するロールやセキュリティグループを作成する
  • CloudFormationのデプロイはTerraformで行う

コード

CloudForamtionのテンプレート

  • EC2Instance -> Metadata以下がユーザースクリプトの部分で、ここにYAML形式で記述する
  • ユーザーデータはcfn-initのコマンドを呼び、スタック名とリソース名を参照される。これによりメタデータがユーザーデータとして動的に読み込まれる
AWSTemplateFormatVersion: '2010-09-09'
Description: >
  EC2インスタンスを作成するCloudFormationテンプレート
  ・入力パラメータ(VPC ID、サブネットID、AMI)を利用
  ・インバウンドなし、アウトバウンド全許可のセキュリティグループを作成
  ・Session Manager利用用のIAMロールとインスタンスプロファイルを定義
  ・EC2インスタンスのMetadataにCloudFormation Initを定義し、ユーザーデータから実行

Parameters:
  VpcId:
    Type: AWS::EC2::VPC::Id
    Description: VPC ID(Terraformから渡されます)
  SubnetId:
    Type: AWS::EC2::Subnet::Id
    Description: サブネットID(Terraformから渡されます)
  AMI:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/al2023-ami-minimal-x86_64
    Description: EC2で使用するAMI(Amazon Linux 2023 の最小AMI)

Resources:
  MySecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: "Allow all outbound traffic, deny all inbound traffic"
      VpcId: !Ref VpcId
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0
      # インバウンドルールを定義しない(デフォルトは拒否)

  SessionManagerRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: 
                - ec2.amazonaws.com
            Action: sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

  InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
        - !Ref SessionManagerRole

  EC2Instance:
    Type: AWS::EC2::Instance
    Metadata:
      AWS::CloudFormation::Init:
        config:
          commands:
            update:
              command: "dnf update -y"
            install_pip:
              command: "dnf install -y python3-pip"
            install_torch:
              command: "pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu"
    Properties:
      ImageId: !Ref AMI
      InstanceType: t3.medium
      SubnetId: !Ref SubnetId
      SecurityGroupIds:
        - !Ref MySecurityGroup
      IamInstanceProfile: !Ref InstanceProfile
      UserData: 
        Fn::Base64: !Sub |
          #!/bin/bash
          /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource EC2Instance --region ${AWS::Region}
      Tags:
        - Key: Name
          Value: "SessionManagerEC2"

Terraformでのデプロイ

CloudFormationのTerraformでのデプロイ。パラメーターをCloudFormationに渡す。Terraformを中継している理由はCloudFormation単体だと、扱いが大変だから。

AMIのIDは、パラメーターストアの値を渡せばよい。実際のAMI IDを渡すとエラーになる。

variable "ami" {
  description = "EC2で使用するAMIのID"
  type        = string
  # 公式のパラメーターストアのIDを参照
  # https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/parameter-store-public-parameters-ami.html
  default = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64"
}

resource "aws_cloudformation_stack" "session_manager_stack" {
  name          = "session-manager-ec2-stack"
  template_body = file("${path.module}/cloudformation-template.yaml")

  parameters = {
    VpcId    = var.vpc_id
    SubnetId = var.subnet_id
    AMI      = var.ami
  }

  capabilities = ["CAPABILITY_NAMED_IAM"]
}

結果

Session ManagerでEC2に入り、以下のコードを実行すると、最新版のPyTorchがインストールされているのがわかる。

sh-5.2$ python3
Python 3.9.20 (main, Jan 25 2025, 00:00:00)
[GCC 11.4.1 20230605 (Red Hat 11.4.1-2)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import torch
>>> torch.__version__
'2.6.0+cpu'

所感

  • これだけ見るとユーザーデータの面倒くささが軽減されて、CloudFormationいいじゃんとなる
  • しかし、CloudFormationの特有の面倒くささが結構あって
    • 基本CloudFormationは日本語文字を想定してなくて文字化けするし、GroupDescriptionになると明確にエラーになる。ChatGPTがグループ名を日本語で出してしまうとエラーがよく出る
    • ドリフトを直すとき(ユーザーデータをデバッグしてEC2を終了させてapplyみたいな場面)では、手順が結構長くなってしまう。terraform applyみたいに簡単にはいかない
  • このメタデータの機能をTerraformで使えば最高なのだが残念ながらそういう機能はない。試そうとしている記事はある
  • 結論からいうと、どっちもどっちという感じかなーと。そもそもユーザーデータに直書き必要な設計にしてるのが良くないし、どうしてもそういうことするならCodeBuildやDeployを使うべきな気がする。問題で出てきたからやってみたという位置づけです


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

技術書コーナー

北海道の駅巡りコーナー


Add a Comment

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