👌

CFnでクローン作成時にUser Dataでアプリを削除してみた

に公開

■経緯
現在担当している企業で、企業のメインサイトに障害が発生し、
アクセス出来なくなる事象が発生しました。

結局障害発生の原因は分からなかったのですが、
バックアップからリストアする事で復旧しました。

ただ、今後の事を考慮してメインサイトのクローンを
Event Bridge、Lambda、Cloud Formationで作成する方針になりましたが、
色々苦戦したので、記録します。
今回はEC2のクローンに絞って記載します。

■環境
AWS

■構成
Webサーバー:EC2
DB:Oracle for MySQL

■使用するAWSサービス
Event Bridge:時間になったらクローンサーバーを作成するようにスケジュール設定
Lambda:最新のバックアップからクローンを作成するようCloud FormationにIDを連携
Cloud Formation:EC2、RDSを作成
S3:Cloud Formationのyamlファイルの格納場所

1⃣私が書いたLambdaのコードは下記になります。

import boto3
import datetime

cf = boto3.client('cloudformation')
ec2 = boto3.client('ec2')

def lambda_handler(event, context):
    # ① イベントから TemplateURL を取得
    template_url = event.get('TemplateURL')
    if not template_url:
        raise Exception("TemplateURL が event に指定されていません")

    # ② 自分のアカウントが所有するすべての利用可能なAMIを取得(フィルタなし)
    images = ec2.describe_images(
        Owners=['self'],  # 自分が作成したAMIのみ対象
        Filters=[
            {'Name': 'state', 'Values': ['available']}
        ]
    )['Images']

    if not images:
        raise Exception("利用可能なAMIが見つかりません")

    # ③ 最新のAMIを選択(作成日時が最も新しいもの)
    latest_ami = max(images, key=lambda x: x['CreationDate'])['ImageId']

    # ④ タイムスタンプ付きのインスタンス・スタック名
    timestamp = datetime.datetime.utcnow().strftime("%Y%m%d%H%M")
    instance_name = f"ec2-clone-{timestamp}"
    stack_name = f"ec2-clone-stack-{timestamp}"

    # ⑤ CloudFormationスタック作成
    cf.create_stack(
        StackName=stack_name,
        TemplateURL=template_url,
        Parameters=[
            {'ParameterKey': 'InstanceName', 'ParameterValue': instance_name},
            {'ParameterKey': 'AMIId', 'ParameterValue': latest_ami}
        ],
        Capabilities=['CAPABILITY_NAMED_IAM']
    )

    return {
        'status': 'EC2クローン作成開始',
        'stack_name': stack_name,
        'ami_id': latest_ami
    }

2⃣私が書いたCloud Formationのyamlコードは下記になります。

AWSTemplateFormatVersion: '2010-09-09'
Description: EC2クローン作成

Parameters:
  InstanceName:
    Type: String
  AMIId:
    Type: String

Resources:
  ClonedEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref AMIId
      InstanceType: t3.nano
      SubnetId: subnet-0b4536555ab5ff7f4
      SecurityGroupIds:
        - sg-086365a4a63319c97
      Tags:
        - Key: Name
          Value: !Ref InstanceName

上記のコードでEC2は無事にクローンすることができたのですが、
問題が発生しました。
なんとクローン元のEC2には監視アプリが入っており、
クローン先にも当然監視アプリが複製されてしまうのですが、
監視対象のサーバーは1台だけであるため、
2台に監視エージェントが入っていることは完全にライセンス違反!
そのため、クローンした直後に監視を依頼している会社から電話連絡が入る始末!

そのため、クローン直後に監視エージェントをアンインストールする必要が出てきました。

対応策:EC2のUserDataの機能を使用して、起動時に監視エージェントをアンインストールする
処理を実装

家では監視アプリではなく、サクラエディタをクローン元にインストールしておいて、
クローン後にアンインストールされるかの検証を行いました。
サクラエディタをアンインストールするために、
修正したのはCloud Formationのyamlファイルのみです。
アプリ名は固定なので、Lambdaは修正する必要はなく、
修正したCloud FormationのyamlをS3に追加格納しました。

yamlファイルは「ec2-clone-sakuradelete-hostsadd.yaml 」です。
一応、ついでにhostsに文字を追記できるかも検証したので上記のファイル名です。

実際のyamlコードは下記です。

AWSTemplateFormatVersion: '2010-09-09'
Description: EC2クローン作成

Parameters:
  InstanceName:
    Type: String
  AMIId:
    Type: String

Resources:
  ClonedEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref AMIId
      InstanceType: t3.nano
      SubnetId: subnet-0b4536555ab5ff7f4
      SecurityGroupIds:
        - sg-086365a4a63319c97
      Tags:
        - Key: Name
          Value: !Ref InstanceName
      UserData:
        Fn::Base64: !Sub |
          # 1. hostsファイルのパス
          $hostsPath = "C:\Windows\System32\drivers\etc\hosts"

          # 2. 管理者権限でファイル末尾に "test" を追記
          Add-Content -Path $hostsPath -Value "`ntest"
          </powershell>

上記コードを作成するにあたって引っかかった点は下記です。

User Dataの仕様として、サーバーの初回起動時のみ処理が走るようになっています。
クローンを作成することになったサーバーOSはWindows Server 2012 R2だったのですが、
EC2インスタンス起動時の User Data(ユーザーデータ) の実行可否は、
EC2Configによって管理されており、下記のファイルで
実行可否が記載されています。

C:\Program Files\Amazon\Ec2ConfigService\Settings\Config.xml

Ec2HandleUserDataがtrueの時、
実行され、falseの時は実行されません。

<Config>
  ...
  <Ec2HandleUserData>true</Ec2HandleUserData>
  ...
</Config>

しかし、初回起動時は上記の値はtrueなのですが、
起動完了後はfalseになる仕様で、falseになった状態のバックアップを取って、
そのバックアップからクローンするので、クローン先の設定でもfalseとなっており、
実行されることはありません。

そのため、上記ファイルにて初回起動が完了してfalseになった後、
手動でtrueにする必要があります。
それに気づくのに随分時間がかかりました。。。

Discussion