Linuxインスタンスの初期化方法4選

Linuxインスタンスを初期化する4つの方法

EC2インスタンスの起動時に初期化処理を実行する方法を考えます。

EC2インスタンスを構築時に初期化する以下の4つの手法を取り上げます。

  1. ユーザーデータを使用する方法
  2. cfn-initを使用する方法
  3. SSM State ManagerでAnsible Playbookを実行する方法
  4. SSM State Managerでシェルスクリプトを実行する方法

初期化処理の方法はいくつかありますので、用途に応じた選択が可能です。
適切な方法を選択することで、効率よくインスタンスを管理・操作することができます。

構築する環境

Diagram of four ways to initialize Linux instance with CloudFormation.

VPC内の2つのサブネットを作成します。1つはインターネットにアクセス可能なパブリックサブネットとします。
サブネットにEC2インスタンスを4つ配置します。いずれのインスタンスも最新のAmazon Linux 2ベースとします。

インスタンス3および4はSystem Managerを使用して初期化処理を行います。

インスタンス3はAnsible Playbookを実行する形で初期化処理を行いますが、このPlaybookをS3バケットに保存します。
なおこのPlaybookはCodeBuildを使用して作成し、同バケットに配置します。

検証のシナリオ

4つのEC2インスタンスに対して、先述の4つの方法で初期化処理を実行します。
以下を共通して実施します。

  • yumをアップデートする。
  • yumでApacheをインストールし、起動・有効化する。
  • インスタンスIDをindex.htmlに書き込み、Apacheのルートページとする。

環境構築用のCloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。

以下のURLにCloudFormationテンプレートを配置します。

https://github.com/awstut-an-r/awstut-fa/tree/main/004

テンプレートファイルのポイント解説

今回の環境を構成するための、各テンプレートファイルのポイントを取り上げます。

ユーザーデータを使用して初期化処理を定義する

Resources:
  Instance1:
    Type: AWS::EC2::Instance
    Properties:
      IamInstanceProfile: !Ref InstanceProfile1
      ImageId: !Ref ImageId
      InstanceType: !Ref InstanceType
      NetworkInterfaces:
        - AssociatePublicIpAddress: true
          DeviceIndex: 0
          SubnetId: !Ref PublicSubnet
          GroupSet:
            - !Ref InstanceSecurityGroup
      UserData: !Base64 |
        #!/bin/bash -xe
        yum update -y
        yum install -y httpd
        systemctl start httpd
        systemctl enable httpd
        ec2-metadata -i > /var/www/html/index.html
Code language: YAML (yaml)

ユーザーデータを使用することで、インスタンスの初期化処理を定義することができます。

Amazon EC2 でインスタンスを起動するとき、起動後にそのインスタンスにユーザーデータを渡し、一般的な自動設定タスクを実行したり、スクリプトを実行したりできます。

起動時に Linux インスタンスでコマンドを実行する

UserDataプロパティでユーザーデータを定義することができます。ユーザーデータに指定する内容はプレーンテキストではなく、Base64でエンコードされている必要があります。

ユーザーデータは、base64 でエンコードされている必要があります。Amazon EC2コンソールは、base64 エンコードを実行したり、base64 エンコード入力を受け入れたりできます。

インスタンスユーザーデータの使用

今回はCloudFormationの組み込み関数Fn::Base64を使用して、初期化処理をエンコードして渡します。

cfn-initを使用して初期化処理を定義する

Resources:
  Instance2:
    Type: AWS::EC2::Instance
    Metadata:
      AWS::CloudFormation::Init:
        config:
          packages:
            yum:
              httpd: []
          services:
            sysvinit:
              httpd:
                enabled: "true"
                ensureRunning: "true"
          commands:
            001make-index.html:
              command: ec2-metadata -i > /var/www/html/index.html
    Properties:
      IamInstanceProfile: !Ref InstanceProfile1
      ImageId: !Ref ImageId
      InstanceType: !Ref InstanceType
      NetworkInterfaces:
        - AssociatePublicIpAddress: true
          DeviceIndex: 0
          SubnetId: !Ref PublicSubnet
          GroupSet:
            - !Ref InstanceSecurityGroup
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash -xe
          yum install -y aws-cfn-bootstrap

          /opt/aws/bin/cfn-init -v \
          --stack ${AWS::StackName} \
          --resource Instance2 \
          --region ${AWS::Region} \
Code language: YAML (yaml)

CloudFormationのヘルパースクリプトの一種であるcfn-initを使用することで、インスタンスの初期化処理を定義することができます。

cfn-init ヘルパースクリプトは、AWS::CloudFormation::Init キーからテンプレートメタデータを読み取り、それに応じて次のような操作を行います。

AWS CloudFormation のメタデータの取得と解析

パッケージのインストール

ディスクへのファイルの書き込み

サービスの有効化/無効化と開始/停止

cfn-init

ユーザーデータで同スクリプトの実行を定義します。メタデータとして同スクリプトで実行する処理を定義します。

AWS::CloudFormation::Init タイプを使用して、cfn-init ヘルパースクリプト用のメタデータを Amazon EC2 インスタンスに取り込みます。テンプレートが cfn-init スクリプトを呼び出す場合、スクリプトは AWS::CloudFormation::Init メタデータキーをルートとするリソースメタデータを検索します。

AWS::CloudFormation::Init

configプロパティ以下で、実行する処理を該当するセクションに分けて定義します。

設定は、いくつかのセクションに分かれています。(中略) cfn-init ヘルパースクリプトは、次の手順でこれらの設定セクションを処理します。パッケージ、グループ、ユーザー、ソース、ファイル、コマンド、そしてサービスの順です。

AWS::CloudFormation::Init

今回は以下の通り定義します。

  • packagesセクション:Apacheをインストールする。
  • servicesセクション:Apacheを起動・有効化する。
  • commandsセクション:インスタンスIDをindex.htmlに書き込み、Apacheのルートページとする。

SSM State ManagerでAnsible Playbookを実行して初期化する

Resources:
  ApplyAnsiblePlaybooksAssociation:
    Type: AWS::SSM::Association
    Properties:
      AssociationName: !Sub "${Prefix}-playbook-association"
      Name: AWS-ApplyAnsiblePlaybooks
      OutputLocation:
        S3Location:
          OutputS3BucketName: !Ref BucketName
          OutputS3KeyPrefix: !Sub "playbook-association-log"
      Parameters:
        Check:
          - "False"
        ExtraVariables:
          - SSM=True
        InstallDependencies:
          - "True"
        PlaybookFile:
          - !Ref PlaybookFileName
        SourceInfo:
          - !Sub '{"path": "https://${BucketName}.s3.${AWS::Region}.amazonaws.com/${PlaybookPackageName}"}'
        SourceType:
          - S3
        Verbose:
          - -v
      Targets:
        - Key: InstanceIds
          Values:
            - !Ref Instance3
      WaitForSuccessTimeoutSeconds: !Ref WaitForSuccessTimeoutSeconds
Code language: YAML (yaml)

SSM State Managerを使用することで、インスタンスの初期化処理を定義することができます。

AWS Systems Manager の一機能であるステートマネージャーは、Amazon Elastic Compute Cloud (Amazon EC2) とハイブリッドインフラストラクチャを定義された状態に保つプロセスを自動化する、安全かつスケーラブルな設定管理サービスです。

AWS Systems Manager ステートマネージャー

AWSとしては、CloudFormationのcfn-initよりも、SSM State Managerによる初期化を推奨しています。

CloudFormationとState Managerの組み合わせには多くのパターンがありますが、我々が推奨するのは、CloudFormationでAWSリソースを定義し、Systems Managerを使ってOS上の設定管理を実施する方法です。(中略) cfn-initの代わりにState Managerを使用することを検討してください。

CloudFormation で cfn-init に代えて State Manager を利用する方法とその利点

SSM State Managerによってインスタンスを初期化するためには、State Manager関連付けを定義する必要があります。関連付けを作成する上でポイントとなるパラメータは、SSMドキュメントです。

AWS Systems Manager ドキュメント (SSM ドキュメント) は、Systems Manager がマネージドインスタンスで実行するアクションを定義します。Systems Manager には、実行時にパラメータを指定して使用できる事前設定済みのドキュメントが 100 件以上含まれています。

AWS Systems Manager ドキュメント

インスタンス③では、Ansible Playbookを使って初期化を実行します。そのためNameプロパティに「AWS-ApplyAnsiblePlaybooks」を指定します。

またOutputLocationプロパティで処理時のログの保存先を指定します。今回は後述のPlaybookファイルと同じ位置にログを出力させるように設定します。

Targetsプロパティで関連付ける対象のインスタンスを指定します。今回はインスタンスIDを使って指定します。

Parametersプロパティで、Playbookファイルのファイル名や、同ファイルの設置場所等を指定します。今回は「Ansible プレイブックを実行する関連付けを作成する (CLI)」を参考に設定しました。

インスタンス用のIAMロールを定義します。

Resources:
  InstanceRole2:
    Type: AWS::IAM::Role
    DeletionPolicy: Delete
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service:
                - ec2.amazonaws.com
      Policies:
        - PolicyName: SSMStateManagerPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:PutObject
                  - s3:PutObjectAcl
                  - s3:ListBucket
                Resource:
                  - !Sub "arn:aws:s3:::${BucketName}"
                  - !Sub "arn:aws:s3:::${BucketName}/*"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
Code language: YAML (yaml)

SSM State Managerを使用して初期化するためには、インスタンスに2つの権限を付与する必要があります。

1つ目はAnsible Playbookおよびログを設置するS3バケットへのアクセス権です。インラインポリシーにて必要な権限を定義します。

2つ目はSSMマネージドインスタンスの条件を満たすための権限です。SSM State Managerの対象とできるインスタンスは、SSMマネージドインスタンスです。

マネージドインスタンスは、AWS Systems Manager 用に設定されたマシンです。Amazon Elastic Compute Cloud (Amazon EC2) インスタンスまたはハイブリッド環境内のオンプレミスのマシンを、マネージドインスタンスとして設定できます。

マネージドインスタンス

Amazon Linux 2ベースのインスタンスの場合、AWS管理ポリシーAmazonSSMManagedInstanceCoreの権限を付与することによって、SSMマネージドインスタンスとして扱うことができます。

なお今回実行するPlaybookはCodeBuildを使用して作成し、S3バケットに配置します。

Resources:
  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Artifacts:
        Type: NO_ARTIFACTS
      Cache:
        Type: NO_CACHE
      Environment:
        ComputeType: !Ref ProjectEnvironmentComputeType
        EnvironmentVariables:
          - Name: BUCKET_NAME
            Type: PLAINTEXT
            Value: !Ref BucketName
          - Name: PLAYBOOK_FILE_NAME
            Type: PLAINTEXT
            Value: !Ref PlaybookFileName
          - Name: PLAYBOOK_PACKAGE_NAME
            Type: PLAINTEXT
            Value: !Ref PlaybookPackageName
        Image: !Ref ProjectEnvironmentImage
        ImagePullCredentialsType: CODEBUILD
        Type: !Ref ProjectEnvironmentType
      LogsConfig:
        CloudWatchLogs:
          GroupName: !Ref LogGroup
          Status: ENABLED
      Name: !Sub "${Prefix}-project"
      ServiceRole: !GetAtt CodeBuildRole.Arn
      Source:
        Type: NO_SOURCE
        BuildSpec: |
          version: 0.2

          phases:
            pre_build:
              commands:
                - |
                  cat << EOF > $PLAYBOOK_FILE_NAME
                  - hosts: all
                    gather_facts: no
                    become: yes

                    tasks:
                      - name: update yum
                        yum: name=*
                      - name: install the latest version of Apache
                        yum: name=httpd state=latest
                      - name: start and enable Apache
                        service: name=httpd state=started enabled=yes
                      - name: make index.html for awstut.net
                        shell: ec2-metadata -i > /var/www/html/index.html
                  EOF
            build:
              commands:
                - zip -r $PLAYBOOK_PACKAGE_NAME $PLAYBOOK_FILE_NAME
                - aws s3 cp ./$PLAYBOOK_PACKAGE_NAME s3://$BUCKET_NAME/
      Visibility: PRIVATE
Code language: YAML (yaml)

BuildSpecプロパティがポイントです。
ビルド時に実行する内容が記述されています。
pre_buildフェーズでPlaybook(playbook.yml)を作成します。
buildフェーズでPlaybookをZIP化し、S3バケットに配置します。

このCodeBuildプロジェクトをCloudFormationスタック作成時に自動的に実行します。
これはCloudFormationカスタムリソースを使用します。

詳細につきましては以下のページをご確認ください。

あわせて読みたい
CFNのWaitConditionを使用して、Lambdaデプロイパッケージのビルドを待つ 【CFNのWaitConditionを使用して、Lambdaデプロイパッケージのビルドを待つ】 CloudFormationを使用して、Lambda関数を作成することを考えます。 以下のページでご紹介...

SSM State Managerでシェルスクリプトを実行して初期化する

Resources:
  RunShellScriptAssociation:
    Type: AWS::SSM::Association
    Properties:
      AssociationName: !Sub ${Prefix}-shellscript-association
      Name: AWS-RunShellScript
      OutputLocation:
        S3Location:
          OutputS3BucketName: !Ref LogBucketName
          OutputS3KeyPrefix: !Sub "${Prefix}/shellscript-association-log"
      Parameters:
        commands:
          - "sudo yum update -y"
          - "sudo yum install -y httpd"
          - "sudo systemctl start httpd"
          - "sudo systemctl enabled httpd"
          - "ec2-metadata -i > /var/www/html/index.html"
      Targets:
        - Key: InstanceIds
          Values:
            - !Ref Instance4
      WaitForSuccessTimeoutSeconds: !Ref WaitForSuccessTimeoutSeconds
Code language: YAML (yaml)

基本的には、先ほどのAnsible Playbookの場合と同様です。

今回はシンプルにシェルスクリプトの実行を目的とした「AWS-RunShellScript」をSSMドキュメントに指定します。

Parametersプロパティ内のcommandsプロパティで実行するコマンドを定義することができます。

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

CloudFormationスタックを作成し、スタック内のリソースを確認する

次にCloudFormationスタックを作成します。

AWS CLIからCloudFormationスタックを作成する方法については、以下のページをご確認ください。

あわせて読みたい
CloudFormationのネストされたスタックで環境を構築する 【CloudFormationのネストされたスタックで環境を構築する方法】 CloudFormationにおけるネストされたスタックを検証します。 CloudFormationでは、スタックをネストす...

AWS Management Consoleから各リソースを確認します。

CodeBuildを確認します。

Detail of CodeBuild 01

確かにCodeBuildプロジェクトが実行されています。
つまりCloudFormationカスタムリソースによって、CloudFormationスタック作成時に自動的に開始されたということです。

このプロジェクトはインスタンス3用のAnsible Playbookを作成するためのものです。
プロジェクト実行時のログを確認します。

Detail of CodeBuild 02

確かにPlaybookが作成された後、これをZIP化してS3バケットにアップロードしています。

S3バケットを確認します。

Detail of S3 01

確かにS3バケットにplaybook.zipが保存されています。
これは先ほどのCodeBuildプロジェクトによって作成されたものです。

挙動確認1:ユーザーデータを使用してインスタンスを初期化する

準備が整いましたので、実際に挙動を確認します。

まずインスタンス1(i-0a9b66cd7cedd7c32)にアクセスします。
EC2インスタンスへのアクセスはSSM Session Managerを使用します。

$ aws ssm start-session --target i-0a9b66cd7cedd7c32
...
sh-5.2$
Code language: Bash (bash)

正常にアクセスすることができました。

SSM Session Managerに関する詳細は以下のページをご確認ください。

あわせて読みたい
LinuxインスタンスにSSM Session Manager経由でアクセスする 【LinuxインスタンスにSSM Session Manager経由でアクセスする】 EC2インスタンスにSSM Session Manager経由でアクセスする構成を確認します。 Session Manager は完全...

初期化処理時のログを確認します。

[ec2-user@ip-10-0-1-253 ~]$ sudo cat /var/log/cloud-init-output.log
...
+ yum update -y
...
Complete!
+ yum install -y httpd
...
Installed:
  httpd.x86_64 0:2.4.48-2.amzn2
...
Complete!
+ systemctl start httpd
+ systemctl enable httpd
...
+ ec2-metadata -i
Code language: Bash (bash)

/var/log/cloud-init-output.logで、初期化時のログを確認することができました。

このようにユーザーデータで初期化した場合は、別途設定しない限り、処理の対象となるインスタンスのローカルにログが設置されることになります。

挙動確認2:cfn-initを使用してインスタンスを初期化する

インスタンス2(i-0e809df5ce3202e36)にアクセスします。

$ aws ssm start-session --target i-0e809df5ce3202e36
...
sh-5.2$
Code language: Bash (bash)

初期化時のログを確認します。

[ec2-user@ip-10-0-1-82 ~]$ sudo cat /var/log/cfn-init.log
2021-10-03 05:02:50,379 [INFO] -----------------------Starting build-----------------------
2021-10-03 05:02:50,380 [DEBUG] Not setting a reboot trigger as scheduling support is not available
2021-10-03 05:02:50,381 [INFO] Running configSets: default
2021-10-03 05:02:50,382 [INFO] Running configSet default
2021-10-03 05:02:50,384 [INFO] Running config config
2021-10-03 05:02:53,990 [DEBUG] Installing/updating ['httpd'] via yum
2021-10-03 05:02:57,144 [INFO] Yum installed ['httpd']
2021-10-03 05:02:57,144 [DEBUG] No groups specified
2021-10-03 05:02:57,144 [DEBUG] No users specified
2021-10-03 05:02:57,144 [DEBUG] No sources specified
2021-10-03 05:02:57,144 [DEBUG] No files specified
2021-10-03 05:02:57,144 [DEBUG] Running command 001make-index.html
2021-10-03 05:02:57,144 [DEBUG] No test for command 001make-index.html
2021-10-03 05:02:57,182 [INFO] Command 001make-index.html succeeded
2021-10-03 05:02:57,182 [DEBUG] Command 001make-index.html output:
2021-10-03 05:02:57,182 [DEBUG] Using service modifier: /sbin/chkconfig
2021-10-03 05:02:57,182 [DEBUG] Setting service httpd to enabled
2021-10-03 05:02:57,278 [INFO] enabled service httpd
2021-10-03 05:02:57,278 [DEBUG] Using service runner: /sbin/service
2021-10-03 05:02:57,297 [DEBUG] Starting service httpd as it is not running
2021-10-03 05:02:57,370 [INFO] Started httpd successfully
2021-10-03 05:02:57,371 [INFO] ConfigSets completed
2021-10-03 05:02:57,371 [DEBUG] Not clearing reboot trigger as scheduling support is not available
2021-10-03 05:02:57,371 [INFO]
-----------------------Build complete-----------------------
Code language: Bash (bash)
[ec2-user@ip-10-0-1-82 ~]$ sudo cat /var/log/cfn-init-cmd.log
2021-10-03 05:02:50,382 P2445 [INFO] ************************************************************
2021-10-03 05:02:50,382 P2445 [INFO] ConfigSet default
2021-10-03 05:02:50,384 P2445 [INFO] ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2021-10-03 05:02:50,384 P2445 [INFO] Config config
2021-10-03 05:02:53,991 P2445 [INFO] ============================================================
2021-10-03 05:02:53,991 P2445 [INFO] yum install httpd
2021-10-03 05:02:57,135 P2445 [INFO] -----------------------Command Output-----------------------
...
2021-10-03 05:02:57,142 P2445 [INFO]    Installed:
2021-10-03 05:02:57,142 P2445 [INFO]      httpd.x86_64 0:2.4.48-2.amzn2
...
2021-10-03 05:02:57,143 P2445 [INFO]    Complete!
2021-10-03 05:02:57,144 P2445 [INFO] ------------------------------------------------------------
2021-10-03 05:02:57,144 P2445 [INFO] Completed successfully.
2021-10-03 05:02:57,144 P2445 [INFO] ============================================================
2021-10-03 05:02:57,145 P2445 [INFO] Command 001make-index.html
2021-10-03 05:02:57,181 P2445 [INFO] Completed successfully.
Code language: Bash (bash)

/var/log/cfn-init.logおよび/var/log/cfn-init-cmd.logで、初期化時のログを確認することができました。

このようにcfn-initで初期化した場合は、別途設定しない限り、処理の対象となるインスタンスのローカルにログが設置されることになります。

挙動確認3:SSM State ManagerでAnsible Playbookを実行してインスタンスを初期化する

インスタンス3(i-0972c3d05d89efeba)にアクセスします。

$ aws ssm start-session --target i-0972c3d05d89efeba
...
sh-5.2$
Code language: Bash (bash)

次にAWS Management ConsoleからSSM Run Commandの実行状況を確認します。

Results of the execution of ApplyAnsiblePlaybooks.

Instance3に対してRun Commandが実行され、正常に終了したことがわかります。

実行結果の詳細を確認することもできます。

Detail page of the result of running ApplyAnsiblePlaybooks.

AnsibleのPlaybookを実行するために2つのステップに分けてコマンドが実行されたことがわかります。

オプションを有効化した場合、S3バケットにログを出力することができます。

s3://[bucket-name]/[folder-name]/[command-id]/[instance-id]/awsrunShellScript/runShellScript/stdout

今回の実行ログの中身は以下の通りです。

$ cat stdout
...
Installed:
  ansible.noarch 0:2.9.25-1.el7
...
Running Ansible in /var/lib/amazon/ssm/i-079d3fa8b1503d6b2/document/orchestration/4649a395-5815-400d-bf21-596b1c098a20/downloads
Archive:  ./playbook.zip
  inflating: playbook.yml
Using /etc/ansible/ansible.cfg as config file

PLAY [all] *********************************************************************

TASK [update yum] **************************************************************
...

TASK [install the latest version of Apache] ************************************
...

TASK [start and enable Apache] *************************************************
...

TASK [make index.html] ******************************************
...

PLAY RECAP *********************************************************************
localhost                  : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
Code language: Bash (bash)

ログを確認すると、Ansibleのインストール後、Playbookをダウンロードし、Taskを順番に実行していることがわかります。

このようにSSM State Managerで初期化した場合は、S3バケットにログを出力することができます。

挙動確認4:SSM State Managerでシェルスクリプトを実行してインスタンスを初期化する

インスタンス4(i-02fb072859880b2d8)にアクセスします。

$ aws ssm start-session --target i-02fb072859880b2d8
...
sh-5.2$
Code language: Bash (bash)

次にAWS Management ConsoleからSSM Run Commandの実行状況を確認します。

Results of the execution of AWS-RunShellScript.

Instance4に対して実行され、成功していることがわかります。

詳細ページは以下の通りです。

On the AWS-RunShellScript execution result detail page.

1ステップで実行されたことがわかります。

こちらも実行結果をS3バケットに保存するように設定しています。ログは以下の位置に設置されます。

s3://[bucket-name]/[folder-name]/[command-id]/[instance-id]/awsrunShellScript/0.awsrunShellScript/stdout

中身は以下の通りです。

$ cat stdout
Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
...
Installed:
  httpd.x86_64 0:2.4.48-2.amzn2
Complete!
Code language: Bash (bash)

ログを確認すると、指定したコマンドの標準出力が書き込まれていることがわかります。

このようにSSM State Managerで初期化した場合は、S3バケットにログを出力することができます。

まとめ

EC2インスタンスを初期化する4つの方法を確認しました。

ユーザーデータおよびcfn-initを使用する方法では、処理の対象となるインスタンスのローカルにログが設置されることになります。

SSM State ManagerではAnsible Playbookやシェルスクリプトで初期化することができ、S3バケットにログを出力することができます。

適切な初期化処理方法を選択することで、効率よくインスタンスを管理・操作することができるでしょう。