Linuxインスタンスを初期化する4つの方法
EC2インスタンスの起動時に初期化処理を実行する方法を考えます。
EC2インスタンスを構築時に初期化する以下の4つの手法を取り上げます。
- ユーザーデータを使用する方法
- cfn-initを使用する方法
- SSM State ManagerでAnsible Playbookを実行する方法
- SSM State Managerでシェルスクリプトを実行する方法
初期化処理の方法はいくつかありますので、用途に応じた選択が可能です。
適切な方法を選択することで、効率よくインスタンスを管理・操作することができます。
構築する環境
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カスタムリソースを使用します。
詳細につきましては以下のページをご確認ください。
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スタックを作成する方法については、以下のページをご確認ください。
AWS Management Consoleから各リソースを確認します。
CodeBuildを確認します。
確かにCodeBuildプロジェクトが実行されています。
つまりCloudFormationカスタムリソースによって、CloudFormationスタック作成時に自動的に開始されたということです。
このプロジェクトはインスタンス3用のAnsible Playbookを作成するためのものです。
プロジェクト実行時のログを確認します。
確かにPlaybookが作成された後、これをZIP化してS3バケットにアップロードしています。
S3バケットを確認します。
確かに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に関する詳細は以下のページをご確認ください。
初期化処理時のログを確認します。
[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の実行状況を確認します。
Instance3に対してRun Commandが実行され、正常に終了したことがわかります。
実行結果の詳細を確認することもできます。
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の実行状況を確認します。
Instance4に対して実行され、成功していることがわかります。
詳細ページは以下の通りです。
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バケットにログを出力することができます。
適切な初期化処理方法を選択することで、効率よくインスタンスを管理・操作することができるでしょう。