はじめに
クラウド環境でインフラを自動構築する方法として、AnsibleやTerraformなどのツールがよく知られていますが、AWSにはCloudFormationというクラウドネイティブな自動化手段が用意されています。
CloudFormationでサービスの配備をするのはイメージが付く方もいると思いますが、CloudFormation内でセットアップスクリプトを定義して、起動したEC2でスクリプトを実行することができます。
今回は「CloudFormationでEC2を自動構築&セットアップする」というテーマで、Amazon Linux 2023のEC2インスタンスを最小構成で配備し、nginxのインストール・セットアップやCloudWatch Agentの設定までを一括で行う方法を紹介します。
「AWSらしいインフラ自動化の方法ってどんな感じなんだろう?」そんな疑問を持つ方に向けて、テンプレートと実行例を交えながら、CloudFormationの活用方法をわかりやすく解説します。
事前準備
以下の環境を準備してください。
- AWSアカウント
- AWS CLIの実行環境(必要な権限設定を含む)
- インターネットへのアクセスが可能なVPC
環境構成
CloudFormationで以下の環境を配備します。
テンプレート
配備するリソース一覧
配備するリソースは以下になります。
リソース | 説明 |
---|---|
IAMロール | EC2インスタンスにアタッチするIAMロールの作成 (CloudWatchやSSMの権限を付与) |
インスタンスプロファイル | IAMロールをEC2に渡すためのインスタンスプロファイル |
セキュリティグループ | アウトバウンドHTTPS、インバウンドHTTPを許可 |
EC2インスタンス | 指定したVPC/サブネットにEC2を配備 AWS::CloudFormation::Initで初回実行用のスクリプトを定義 nginxのアクセスログとエラーログをCloudWatchログに転送するよう設定 |
テンプレートの説明
テンプレートは以下を利用します。
AWSTemplateFormatVersion: '2010-09-09'
Description: EC2 with CloudFormation Init (Amazon Linux 2023 + nginx + CloudWatch Logs)
Parameters:
# VPC ID to launch the EC2 instance into
VpcId:
Type: AWS::EC2::VPC::Id
Description: VPC ID to launch the EC2 instance into
# Subnet ID within the specified VPC
SubnetId:
Type: AWS::EC2::Subnet::Id
Description: Subnet ID within the specified VPC
# SSH key pair name for logging into EC2 (must be pre-created)
KeyName:
Type: AWS::EC2::KeyPair::KeyName
Description: SSH Key for EC2
# EC2 instance type (based on cost and performance)
InstanceType:
Type: String
Default: t2.micro
AllowedValues: [t2.micro, t3.micro, t3.small]
Description: EC2 Instance Type
# Log group name to be created in CloudWatch Logs
LogGroupName:
Type: String
Default: ec2-cloudwatch-loggroup
Description: CloudWatch Logs group name
Resources:
# Create CloudWatch Logs log group
LogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Ref LogGroupName
RetentionInDays: 7
# Create IAM role to attach to EC2 (grants CloudWatch Agent and SSM permissions)
InstanceRole:
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/CloudWatchAgentServerPolicy
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
# Create instance profile to pass the IAM role to EC2
InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Roles: [!Ref InstanceRole]
Path: /
# Create security group (allow HTTP inbound and HTTPS outbound)
EC2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow HTTP inbound and HTTPS outbound
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
SecurityGroupEgress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
# Create EC2 instance
EC2Instance:
Type: AWS::EC2::Instance
Metadata:
AWS::CloudFormation::Init:
config:
packages:
yum:
nginx: []
amazon-cloudwatch-agent: []
files:
# AWS CLI config file
/root/.aws/config:
content: !Sub |
[default]
region = ${AWS::Region}
output = json
mode: '000600'
owner: root
group: root
# CloudWatch Agent config file
/opt/aws/amazon-cloudwatch-agent/bin/config.json:
content: !Sub |
{
"logs": {
"logs_collected": {
"files": {
"collect_list": [
{
"file_path": "/var/log/nginx/access.log",
"log_group_name": "${LogGroupName}",
"log_stream_name": "nginx-access-{instance_id}",
"timestamp_format": "%d/%b/%Y:%H:%M:%S %z"
},
{
"file_path": "/var/log/nginx/error.log",
"log_group_name": "${LogGroupName}",
"log_stream_name": "nginx-error-{instance_id}",
"timestamp_format": "%Y/%m/%d %H:%M:%S"
}
]
}
}
}
}
mode: '000644'
owner: root
group: root
# Script to list S3 buckets and log the output
/usr/local/bin/setup.sh:
content: !Sub |
#!/bin/bash
echo "[`date '+%Y-%m-%d %H:%M:%S'`] Listing S3 buckets" >> /var/log/script.log
aws s3 ls >> /var/log/script.log 2>&1
echo "[`date '+%Y-%m-%d %H:%M:%S'`] Done" >> /var/log/script.log
mode: '000755'
owner: root
group: root
commands:
# Start CloudWatch Agent
01_start_cw_agent:
command: |
/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
-a fetch-config \
-m ec2 \
-c file:/opt/aws/amazon-cloudwatch-agent/bin/config.json \
-s
# Run setup script
02_run_script:
command: /usr/local/bin/setup.sh
services:
sysvinit:
nginx:
enabled: true
ensureRunning: true
Properties:
InstanceType: !Ref InstanceType
KeyName: !Ref KeyName
IamInstanceProfile: !Ref InstanceProfile
ImageId: !Sub '{{resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64}}'
SubnetId: !Ref SubnetId
SecurityGroupIds:
- !Ref EC2SecurityGroup
UserData:
Fn::Base64: !Sub |
#!/bin/bash
dnf update -y
/opt/aws/bin/cfn-init -v \
--stack ${AWS::StackName} \
--resource EC2Instance \
--region ${AWS::Region}
Outputs:
InstanceId:
Value: !Ref EC2Instance
Description: ID of the created EC2 instance
PublicDNS:
Value: !GetAtt EC2Instance.PublicDnsName
Description: Public DNS name of the EC2 instance
テンプレートの実行
テンプレートをローカルに保存し、以下のAWS CLIコマンドでCloudFormationを実行します。
テンプレートファイルの名前は「ec2-init-template.yaml
」としてください。
aws cloudformation deploy --template-file ec2-init-template.yaml --stack-name MyEc2WithInitStack --capabilities CAPABILITY_NAMED_IAM --parameter-overrides VpcId=【VPCID】 SubnetId=【サブネットID】 KeyName=【キーペア名】 InstanceType=t2.micro LogGroupName=【ロググループ名】
以下でOutputsに指定したインスタンス情報を出力します。
aws cloudformation describe-stacks --stack-name MyEc2WithInitStack --query "Stacks[0].Outputs" --output table
-----------------------------------------------------------------------------------------------------------------
| DescribeStacks |
+--------------------------------------+-------------+----------------------------------------------------------+
| Description | OutputKey | OutputValue |
+--------------------------------------+-------------+----------------------------------------------------------+
| ID of the created EC2 instance | InstanceId | i-0123456789abcdef0 |
| Public DNS name of the EC2 instance | PublicDNS | ec2-12-34-56-78.xx-xxxx-x.compute.amazonaws.com |
+--------------------------------------+-------------+----------------------------------------------------------+
動作確認
OutputsのPublicDNSにアクセスします。アクセス確認はブラウザを利用します。
※curlでなどコマンドで確認してもいいです。
nginxの初期画面が表示されます。
CloudWatchログでアクセスログ/エラーログを確認できます。
環境の削除
以下のコマンドで配備したリソースを一括削除ができます。
aws cloudformation delete-stack --stack-name MyEc2WithInitStack
削除後、以下のコマンドで削除状況を確認できます。
aws cloudformation describe-stacks --stack-name MyEc2WithInitStack
削除ができていると以下のように指定したスタックIDのスタックが存在しない旨のエラーメッセージが出力されます。
An error occurred (ValidationError) when calling the DescribeStacks operation: Stack with id MyEc2WithInitStack does not exist
※StackStatusが「DELETE_IN_PROGRESS」である場合は削除中です。しばらくたってから再度確認してください。
まとめ
本記事ではCloudFormationを使い、EC2の配備+コマンド実行で、nginxのインストール/CloudWatchログにログの転送設定までを行いました。
この内容を応用することで、EC2の配備と他のミドルウェアのセットアップまで一括で可能となります。
補足として、OS再起動など、高度で複雑なセットアップについては本記事の内容は向いていません。
高度で複雑なセットアップには、「CodeDeploy」、「Systems Manager Automation」、「SSM Run Command」を利用することをおすすめします。
より実践的にAWSを学びたい方には、Udemyのオンラインコースの受講がおすすめです。
基礎からサービスごとの使い方、認定資格対策まで、幅広い内容がそろっており、ご自身のレベルや目的に合ったコースがきっと見つかります。
スキルアップやキャリアのステップアップを目指す方は、ぜひチェックしてみてください。