ご挨拶
BeeX Advent Calendar 2022はじまるよおおおおお(/・ω・)/
どうもトップバッターのワンワンです🐶🐶🐶
BeeX Advent Calendar 2022
はじめに
普段、仕事柄CloudFormationを使用することが多くあるが、CloudFormationテンプレートのベストプラクティスな書き方って何だろうなと思うことが良くあるので、自分なりに思いついたベストプラクティスな書き方についてこの機会に色々と書いてみようと思います。
本日作成する環境
本日構築する環境としては、VPCを作って、EC2インスタンスを作成するというシンプルな構成です。詳細な構成図は下記となります。
また、上記とは別で、EC2インスタンスでSystemsManagerを活用したプライベートな接続を実施する為に、下記4つのVPCエンドポイントも作成します。
エンドポイントサービス名 | エンドポイントタイプ |
---|---|
com.amazonaws.[region].ssm | インターフェイス型 |
com.amazonaws.[region].ec2messages | インターフェイス型 |
com.amazonaws.[region].ssmmessages | インターフェイス型 |
com.amazonaws.[region].s3 | ゲートウェイ型 |
■参考URL
プライベートサブネットに配置したEC2にAWS Systems Manager Session Managerを使ってアクセスする
Systems Manager を使用してインターネットアクセスなしでプライベート EC2 インスタンスを管理できるように、VPC エンドポイントを作成するにはどうすればよいですか?
本日使用するCloudFormationテンプレート
(1)RootStack.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: "RootStack"
Parameters:
ProjectName:
Type: String
Default: qiita-dev
TemplateS3BucketName:
Type: String
Default: ""
VPCFileName:
Type: String
Default: "VPC.yml"
EC2FileName:
Type: String
Default: "EC2.yml"
KeyPairFileName:
Type: String
Default: "KeyPair.yml"
Resources:
VPC:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub "https://${TemplateS3BucketName}.s3.${AWS::Region}.amazonaws.com/${VPCFileName}"
Parameters:
ProjectName: !Ref ProjectName
KeyPair:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub "https://${TemplateS3BucketName}.s3.${AWS::Region}.amazonaws.com/${KeyPairFileName}"
Parameters:
ProjectName: !Ref ProjectName
EC2:
Type: AWS::CloudFormation::Stack
DependsOn:
- VPC
- KeyPair
Properties:
TemplateURL: !Sub "https://${TemplateS3BucketName}.s3.${AWS::Region}.amazonaws.com/${EC2FileName}"
Parameters:
ProjectName: !Ref ProjectName
VpcId: !GetAtt VPC.Outputs.vpcId
PrivateSubnetAId: !GetAtt VPC.Outputs.PrivateSubnetAId
PrivateSubnetCId: !GetAtt VPC.Outputs.PrivateSubnetCId
PublicSubnetAId: !GetAtt VPC.Outputs.PublicSubnetAId
PublicSubnetCId: !GetAtt VPC.Outputs.PublicSubnetCId
BastionSecurityGroup: !GetAtt VPC.Outputs.BastionSecurityGroup
LinuxEC2SecurityGroup: !GetAtt VPC.Outputs.LinuxEC2SecurityGroup
KeyPair: !GetAtt KeyPair.Outputs.KeyName
(2)VPC.yml
AWSTemplateFormatVersion: "2010-09-09"
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Environment Setting
Parameters:
- ProjectName
- Label:
default: Network Configuration
Parameters:
- VPCCIDR
- PrivateSubnetACIDR
- PrivateSubnetCCIDR
- PublicSubnetACIDR
- PublicSubnetCCIDR
Parameters:
ProjectName:
Type: String
VPCCIDR:
Type: String
Default: 10.192.0.0/16
PrivateSubnetACIDR:
Type: String
Default: 10.192.0.0/24
PrivateSubnetCCIDR:
Type: String
Default: 10.192.1.0/24
PublicSubnetACIDR:
Type: String
Default: 10.192.10.0/24
PublicSubnetCCIDR:
Type: String
Default: 10.192.11.0/24
Resources:
# ------------------------------------------------------------#
# Create VPC
# ------------------------------------------------------------#
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VPCCIDR
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub ${ProjectName}-VPC
# ------------------------------------------------------------#
# Create InternetGateway
# ------------------------------------------------------------#
InternetGateway:
Type: "AWS::EC2::InternetGateway"
Properties:
Tags:
- Key: Name
Value: !Sub "${ProjectName}-igw"
InternetGatewayAttachment:
Type: "AWS::EC2::VPCGatewayAttachment"
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
# ------------------------------------------------------------#
# Create Subnet
# ------------------------------------------------------------#
PrivateSubnetA:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select
- 0
- Fn::GetAZs: ""
CidrBlock: !Ref PrivateSubnetACIDR
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${ProjectName}-PrivateSubnetA
PrivateSubnetC:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select
- 1
- Fn::GetAZs: ""
CidrBlock: !Ref PrivateSubnetCCIDR
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${ProjectName}-PrivateSubnetC
PublicSubnetA:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select
- 0
- Fn::GetAZs: ""
CidrBlock: !Ref PublicSubnetACIDR
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${ProjectName}-PublicSubnetA
PublicSubnetC:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select
- 1
- Fn::GetAZs: ""
CidrBlock: !Ref PublicSubnetCCIDR
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${ProjectName}-PublicSubnetC
# ------------------------------------------------------------#
# Create Route Table
# ------------------------------------------------------------#
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${ProjectName}-PrivateRouteTable
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${ProjectName}-PublicRouteTable
# ------------------------------------------------------------#
# CREATE NAT Gateway AZ:A
# ------------------------------------------------------------#
NATGatewayA:
Type: "AWS::EC2::NatGateway"
Properties:
AllocationId: !GetAtt NATGatewayAEIP.AllocationId
SubnetId: !Ref PublicSubnetA
Tags:
- Key: Name
Value: !Sub "${ProjectName}-natgw-a"
NATGatewayAEIP:
Type: "AWS::EC2::EIP"
Properties:
Domain: VPC
# ------------------------------------------------------------#
# Route table settings
# ------------------------------------------------------------#
PublicRoute:
Type: "AWS::EC2::Route"
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref InternetGateway
PrivateRoute:
Type: "AWS::EC2::Route"
Properties:
DestinationCidrBlock: "0.0.0.0/0"
RouteTableId: !Ref PrivateRouteTable
NatGatewayId: !Ref NATGatewayA
# ------------------------------------------------------------#
# Associate Routetable with Subnet
# ------------------------------------------------------------#
PrivateSubnetARouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnetA
RouteTableId: !Ref PrivateRouteTable
PrivateSubnetCRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnetC
RouteTableId: !Ref PrivateRouteTable
PublicSubnetRouteATableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnetA
RouteTableId: !Ref PublicRouteTable
PublicSubnetRouteCTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnetC
RouteTableId: !Ref PublicRouteTable
# ------------------------------------------------------------#
# Create Security Group
# ------------------------------------------------------------#
EndpointSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub ${ProjectName}-EndpointSecurityGroup
GroupDescription: EndpointSecurityGroup
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${ProjectName}-EndpointSecurityGroup
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: !Ref VPCCIDR
SecurityGroupEgress:
- IpProtocol: '-1'
CidrIp: 0.0.0.0/0
BastionSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub ${ProjectName}-BastionSecurityGroup
GroupDescription: WindowsEC2SecurityGroup
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${ProjectName}-BastionSecurityGroup
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 389
ToPort: 389
CidrIp: !Ref VPCCIDR
- IpProtocol: tcp
FromPort: 3389
ToPort: 3389
CidrIp: !Ref VPCCIDR
SecurityGroupEgress:
- IpProtocol: '-1'
CidrIp: 0.0.0.0/0
LinuxEC2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub ${ProjectName}-LinuxEC2SecurityGroup
GroupDescription: LinuxEC2SecurityGroup
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${ProjectName}-LinuxEC2SecurityGroup
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: !Ref VPCCIDR
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: !Ref VPCCIDR
- IpProtocol: tcp
FromPort: 3000
ToPort: 3000
CidrIp: !Ref VPCCIDR
SecurityGroupEgress:
- IpProtocol: '-1'
CidrIp: 0.0.0.0/0
# ------------------------------------------------------------#
# Create VPCEndpoint
# ------------------------------------------------------------#
EndpointSSM:
Type: AWS::EC2::VPCEndpoint
Properties:
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref EndpointSecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm
SubnetIds:
- !Ref PrivateSubnetA
# - !Ref PrivateSubnetC
VpcEndpointType: Interface
VpcId: !Ref VPC
EndpointSSMMessages:
Type: AWS::EC2::VPCEndpoint
Properties:
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref EndpointSecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages
SubnetIds:
- !Ref PrivateSubnetA
# - !Ref PrivateSubnetC
VpcEndpointType: Interface
VpcId: !Ref VPC
EndpointEC2Messages:
Type: AWS::EC2::VPCEndpoint
Properties:
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref EndpointSecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.ec2messages
SubnetIds:
- !Ref PrivateSubnetA
# - !Ref PrivateSubnetC
VpcEndpointType: Interface
VpcId: !Ref VPC
EndpointS3:
Type: AWS::EC2::VPCEndpoint
Properties:
RouteTableIds:
- !Ref PrivateRouteTable
ServiceName: !Sub com.amazonaws.${AWS::Region}.s3
VpcEndpointType: Gateway
VpcId: !Ref VPC
# ------------------------------------------------------------#
# Output
# ------------------------------------------------------------#
Outputs:
vpcId:
Value: !Ref VPC
VPCCIDR:
Value: !Ref VPCCIDR
PrivateSubnetAId:
Value: !Ref PrivateSubnetA
PrivateSubnetCId:
Value: !Ref PrivateSubnetC
PublicSubnetAId:
Value: !Ref PublicSubnetA
PublicSubnetCId:
Value: !Ref PublicSubnetC
PrivateSubnetACIDR:
Value: !Ref PrivateSubnetACIDR
PrivateSubnetCCIDR:
Value: !Ref PrivateSubnetCCIDR
PublicSubnetACIDR:
Value: !Ref PublicSubnetACIDR
PublicSubnetCCIDR:
Value: !Ref PublicSubnetCCIDR
PrivateRouteTable:
Value: !Ref PrivateRouteTable
PublicRouteTable:
Value: !Ref PublicRouteTable
EndpointSSM:
Value: !Ref EndpointSSM
EndpointSSMMessages:
Value: !Ref EndpointSSMMessages
EndpointEC2Messages:
Value: !Ref EndpointEC2Messages
EndpointS3:
Value: !Ref EndpointS3
EndpointSecurityGroup:
Value: !Ref EndpointSecurityGroup
BastionSecurityGroup:
Value: !Ref BastionSecurityGroup
LinuxEC2SecurityGroup:
Value: !Ref LinuxEC2SecurityGroup
(3)KeyPair.yml
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
ProjectName:
Type: String
# ------------------------------------------------------------#
# Create KeyPair
# ------------------------------------------------------------#
Resources:
KeyPair1:
Type: AWS::EC2::KeyPair
Properties:
KeyName: !Sub ${ProjectName}-KeyPair
Outputs:
KeyName:
Value: !Ref KeyPair1
(4)EC2.yml
下記2台の構築をおこないます。
・WindowsServer2022の踏み台サーバ
・Redhat8の業務用システム
AWSTemplateFormatVersion: "2010-09-09"
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Environment Setting
Parameters:
- ProjectName
- Label:
default: Network Configuration
Parameters:
- VpcId
- PrivateSubnetAId
- PrivateSubnetCId
- PublicSubnetAId
- PublicSubnetCId
- Label:
default: EC2 Configuration
Parameters:
- Redhat8Ami
- LinuxEc2InstanceType
- LinuxEC2SecurityGroup
- Windows2022Ami
- BastionEc2InstanceType
- BastionSecurityGroup
- KeyPair
Parameters:
ProjectName:
Type: String
VpcId:
Type: AWS::EC2::VPC::Id
PrivateSubnetAId:
Type: String
PrivateSubnetCId:
Type: String
PublicSubnetAId:
Type: String
PublicSubnetCId:
Type: String
Redhat8Ami:
Type : String
Default: ami-0f903fb156f24adbf
LinuxEc2InstanceType:
Type: String
Default: t3.small
LinuxEC2SecurityGroup:
Type: String
Windows2022Ami:
Type : AWS::SSM::Parameter::Value<String>
Default: /aws/service/ami-windows-latest/Windows_Server-2022-Japanese-Full-Base
BastionEc2InstanceType:
Type: String
Default: t3.small
BastionSecurityGroup:
Type: String
KeyPair:
Type: String
Resources:
# ------------------------------------------------------------#
# EC2 settings
# ------------------------------------------------------------#
EC2IAMRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${ProjectName}-SSM-role
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- sts:AssumeRole
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
- arn:aws:iam::aws:policy/AmazonS3FullAccess
EC2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: /
Roles:
- Ref: EC2IAMRole
InstanceProfileName: !Sub ${ProjectName}-EC2InstanceProfile
# ------------------------------------------------------------#
# Create EC2 Instance
# ------------------------------------------------------------#
EC2Instance1:
Type: AWS::EC2::Instance
Properties:
NetworkInterfaces:
- SubnetId: !Ref PublicSubnetAId
GroupSet:
- !Ref BastionSecurityGroup
AssociatePublicIpAddress: true
DeviceIndex : 0
InstanceType: !Ref BastionEc2InstanceType
ImageId: !Ref Windows2022Ami
IamInstanceProfile: !Ref EC2InstanceProfile
BlockDeviceMappings:
- DeviceName: '/dev/sda1'
Ebs:
VolumeSize: 50
VolumeType: gp3
Encrypted: true
KeyName: !Ref KeyPair
Tags:
- Key: Name
Value: !Sub ${ProjectName}-Bastion
UserData:
Fn::Base64: !Sub |
<powershell>
# SSM Agent
$dir = $env:TEMP + "\ssm"
New-Item -ItemType directory -Path $dir -Force
cd $dir
(New-Object System.Net.WebClient).DownloadFile("https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/windows_amd64/AmazonSSMAgentSetup.exe", $dir + "\AmazonSSMAgentSetup.exe")
Start-Process .\AmazonSSMAgentSetup.exe -ArgumentList @("/q", "/log", "install.log") -Wait
# set timezone
Set-TimeZone -Id "Tokyo Standard Time"
# Windows Firewall
Set-NetFirewallProfile -Enabled false
# Add Windows Firewall Inboundrule
New-NetFirewallRule -DisplayName "ALL TCP V4" -Direction Inbound -Protocol TCP -LocalPort 0-65535 -Action Allow -Enabled true
New-NetFirewallRule -DisplayName "ALL UDP V4" -Direction Inbound -Protocol UDP -LocalPort 0-65535 -Action Allow -Enabled true
# hostname
Rename-Computer -NewName ${ProjectName}-Bastion -Force -Restart
</powershell>
EC2Instance2:
Type: AWS::EC2::Instance
DependsOn:
- EC2InstanceProfile
Properties:
SubnetId: !Ref PrivateSubnetAId
InstanceType: !Ref LinuxEc2InstanceType
ImageId: !Ref Redhat8Ami
SecurityGroupIds:
- !Ref LinuxEC2SecurityGroup
IamInstanceProfile: !Ref EC2InstanceProfile
BlockDeviceMappings:
- DeviceName: '/dev/sda1'
Ebs:
VolumeSize: 50
VolumeType: gp3
Encrypted: true
KeyName: !Ref KeyPair
Tags:
- Key: Name
Value: !Sub ${ProjectName}-Zabbix
UserData:
Fn::Base64: |
#!/bin/bash
REGION_NAME=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone | sed -e 's/.$//')
dnf install -y "https://s3.${REGION_NAME}.amazonaws.com/amazon-ssm-${REGION_NAME}/latest/linux_amd64/amazon-ssm-agent.rpm"
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
# Register the Microsoft RedHat repository
sudo curl https://packages.microsoft.com/config/rhel/8/prod.repo | sudo tee /etc/yum.repos.d/microsoft.repo
# Install PowerShell
sudo dnf install -y powershell
解説
以降は、上記のCloudFormationテンプレートでどの点を工夫しているのかについてにょろにょろ🐍と書いていきます。
(1)RootStack.yml 解説
(1.1)ネストされたスタックを用いた展開
AWSでは、ネストされたスタックという手法が存在します。複数のリソースを1つのCloudFormationで展開する際は、どうしてもコード量が多くなりがちがちで管理がしづらくなってしまいます。それを改善する手段の一つとして、リソース毎にテンプレートを分けて、親スタックから該当リソースを呼び出す手法があげられます。
そうすることで、1ファイルあたりのコード量が少なくなり、管理がしやすくなります。この手法を活用する際は、親スタック(今回はRootStack.yml)と子スタックに分け、展開していく感じです。今回はその手法を使うのですが、イメージ図は下記となります。
参考URL:ネストされたスタックの操作
(1.2)可変的な値は変数を利用する
「Parameters」セクションで変数を活用することで、何度も使用する値や可変的な値を変数に格納し、誤字の防止や汎用性を高めている。
Parameters:
ProjectName:
Type: String
Default: qiita-dev
TemplateS3BucketName:
Type: String
Default: ""
VPCFileName:
Type: String
Default: "VPC.yml"
EC2FileName:
Type: String
Default: "EC2.yml"
KeyPairFileName:
Type: String
Default: "KeyPair.yml"
(1.3)子スタックの呼び出し
「Resources」セクションで、各子スタックを呼び出す展開にしている。また、子スタックを呼び出す際に、親スタックで定義した「ProjectName」という変数(システム名)を代入している。
Resources:
VPC:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub "https://${TemplateS3BucketName}.s3.${AWS::Region}.amazonaws.com/${VPCFileName}"
Parameters:
ProjectName: !Ref ProjectName
(2)VPC.yml 解説
(2.1)サブネット作成時のAZ指定に関して
サブネット作成時にどこのAZに所属するかを指定するのだが、その時にAZ名を直接指定するのではなく、AWS側で用意されている組み込み関数の「Fn::GetAZs」を利用することで、指定したリージョン内のアベイラビリティーゾーンのリストを自動的に割り振ることができる。このような記述をすることで、別リージョンで同じテンプレートを使用する際に修正箇所が少なくなる為、このような書き方が推奨されている。
また、リージョン内でどのAZを選択できるかについては下記コマンドを参照してください。
※Fn::GetAZs 関数は、指定されたリージョンのAZをアルファベット順に並べるArrayを返す
■Fn::GetAZsを用いた定義
AvailabilityZone: !Select
- 0
- Fn::GetAZs: ""
■AWS CLIで利用できるAZを確認(東京リージョンの場合)
[cloudshell-user@ip-10-0-24-90 ~]$ aws ec2 describe-availability-zones \
> --region ap-northeast-1 \
> --query 'AvailabilityZones[].{RegionName:RegionName, ZoneName:ZoneName, ZoneId:ZoneId}' \
> --output table
----------------------------------------------------
| DescribeAvailabilityZones |
+----------------+-------------+-------------------+
| RegionName | ZoneId | ZoneName |
+----------------+-------------+-------------------+
| ap-northeast-1| apne1-az4 | ap-northeast-1a |
| ap-northeast-1| apne1-az1 | ap-northeast-1c |
| ap-northeast-1| apne1-az2 | ap-northeast-1d |
+----------------+-------------+-------------------+
■参考URL
Fn::GetAZs
Cloudformationのテンプレートに使える関数をまとめてみました ( 1 )
AWS CLIで自アカウントのAZ名とAZ IDのマッピングを確認する
(2.2)VPC.yml内でシステムで使用するSGを全て作成
私は、SGを作成する際は、一番初めに作成するVPCと合わせて作成するようにしてます。理由は、別のテンプレートそれぞれでSGを作成した場合、どこで作成したSGかが分かりづらくなってしますのでそうしてます。まあ、ここの部分に関しては好みの問題です。
(2.3)インタフェース型のエンドポイントを作成する際は、片AZのみに作成する(検証用の場合のみ)
インターフェイス型のエンドポイントは、立てているだけで1時間当たり0.014USDの費用が発生します。(東京リージョンの料金)
なので、検証用で使用する時などでは、下記のように2個目のサブネットはコメントアウトし、片AZのみで使用することで節約してます。(これはただの貧乏性なだけです(笑))
EndpointSSM:
Type: AWS::EC2::VPCEndpoint
Properties:
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref EndpointSecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm
SubnetIds:
- !Ref PrivateSubnetA
# - !Ref PrivateSubnetC
VpcEndpointType: Interface
VpcId: !Ref VPC
(3)KeyPair.yml 解説
(3.1)CloudFormationでキーペアを作成する
2022年4月29日にCloud Formation テンプレートを使用してキーペアを作成することができるようになりました。キーペアを作成後は、AWS Systems Manager の パラメータストアにキー情報が保管されるので、そこからキー情報をコピーし、サーバーへのログインが行えるようになりました。なので、キーペアの紛失などは無くなるのかなという感じです。実際にコピペするだけで、結構ラクなので、私はこの手法を採用してます。
参考URL:
AWS は EC2 キーペア用に新しい管理機能を追加
AWS::EC2::KeyPair
(4)EC2.yml 解説
(4.1)Windows内のユーザデータに関して
Windowsサーバーの作成時に以下の内容をユーザデータとして指定してます。
- SSM Agentインストール
- タイムゾーンを「asia/tokyo」に変更
- WindowsFirewallを無効化
- WindowsFirewallのインバウンドルールでTCP/UDPを全許可(念のため)
(4.2)Linux内のユーザデータに関して
Linuxサーバーの作成時に以下の内容をユーザデータとして指定してます。
- SSM Agentインストール
- Microsoft RedHat repositoryをインストールし、powershellをインストール ※理由については、以降の解説を参照。
■解説
SSM Agentが古い場合、System Manageを使用したログインが急にできるなくなることがあります。
その為、SSM Agentは定期的にバージョンアップすることが推奨されています。
※以下、画面参照。
SSM Agentのアップデートは、RunCommandの「AWS-UpdateSSMAgent」を実行し、アップデートすることができます。
しかし、デフォルトのままのRedHat8のインスタンスに、「AWS-UpdateSSMAgent」を実行すると、下記のエラーが発生します。
failed to run commands: fork/exec /usr/bin/pwsh: no such file or directory
上記エラーを解消する手段として、事前にpowershellをインストールする必要があります。
その為、CloudFormationのユーザデータ内に、powershellをインストールしてます。
詳細については、下記記事を参考にしてください。
LinuxインスタンスのSSM Run CommandでPowerShellスクリプトを実行する
AWS Systems Manager エージェント(SSM Agent)の現行のバージョンを確認して最新バージョンにアップデートする
最後に
私がCloudFormationテンプレートを書く際は、管理のしやすさや使いまわしの良さを意識し、作成するようにしてます。私自身まだまだベストプラクティスな書き方なんてできていないと思いますが、日々勉強しながら良くしていこうと思ってます。この記事を読んでここを改善した方が良いなどの意見などあれば、色々とコメントいただければ嬉しいです。最後までお読みいただき、ありがとうございました。ペコm(_ _)m