はじめに
下記のQiita記事では、VPC環境を自動作成するAWS CloudFormationのサンプルコードを紹介しました。
今回は、上記のCloudFormationで作成したVPC環境上に、EC2インスタンスをプロビジョニングするためのサンプルコードを紹介します。
CloudFormationのクロススタック機能の利用
既にCloudFormationによって作成された環境を利用して、新たなリソースをプロビジョニングしたい場合、CloudFormationのクロススタック機能を利用します。クロススタック機能の使い方は、下記のAWS CloudFormationドキュメントを参照ください。
Walkthrough: Refer to resource outputs in another AWS CloudFormation stack
本記事では、下記のような流れでクロススタック機能を使用します。
- 事前にCloudFormationでVPC環境のスタックを作成しておく。(スタック名:MyNetwork-01)
- 上記で作成されたVPCのサブネット上に、EC2インスタンスをプロビジョニングするCloudFormationコードを追加で実行し、新たなスタックを作成する。(スタック名:MyEC2Instance-01)
クロススタック機能を使用したEC2プロビジョニング用サンプルコード
今回作成したサンプルコードは下記ようなYAMLファイルです。
AWSTemplateFormatVersion: '2010-09-09'
Description: Sample Script for Creating Public/Private EC2 Instance
Parameters:
MyNetworkStackName:
Description: This stack name is already created Network Stack for provisioning Public/Private EC2 Instance
Type: String
Default: MyNetwork-Stack01
Resources:
MyKey:
Type: AWS::EC2::KeyPair
Properties:
KeyName: MyEC2Key
PublicKeyMaterial: '{{resolve:ssm:publickeymaterial:1}}'
Tags:
- Key: Name
Value: My-Key
MyPublicInstance01:
Type: AWS::EC2::Instance
Properties:
CreditSpecification:
CPUCredits: standard
InstanceType: t2.micro
KeyName: !Ref MyKey
ImageId: ami-078296f82eb463377
NetworkInterfaces:
- AssociatePublicIpAddress: "true"
DeviceIndex: "0"
SubnetId:
Fn::ImportValue:
!Sub "${MyNetworkStackName}--PublicSubnetId01"
GroupSet:
-
Fn::ImportValue:
!Sub "${MyNetworkStackName}--PublicSubnetSecGrpId01"
Tags:
- Key: Name
Value: My-Public-instance-01
MyPrivateInstance01:
Type: AWS::EC2::Instance
Properties:
CreditSpecification:
CPUCredits: standard
InstanceType: t2.micro
KeyName: !Ref MyKey
ImageId: ami-078296f82eb463377
NetworkInterfaces:
- AssociatePublicIpAddress: "false"
DeviceIndex: "0"
SubnetId:
Fn::ImportValue:
!Sub "${MyNetworkStackName}--PrivateSubnetId01"
GroupSet:
-
Fn::ImportValue:
!Sub "${MyNetworkStackName}--PrivateSubnetSecGrpId01"
Tags:
- Key: Name
Value: My-Private-instance-01
MyPublicInstance02:
Type: AWS::EC2::Instance
Properties:
CreditSpecification:
CPUCredits: standard
InstanceType: t2.micro
KeyName: !Ref MyKey
ImageId: ami-078296f82eb463377
NetworkInterfaces:
- AssociatePublicIpAddress: "true"
DeviceIndex: "0"
SubnetId:
Fn::ImportValue:
!Sub "${MyNetworkStackName}--PublicSubnetId02"
GroupSet:
-
Fn::ImportValue:
!Sub "${MyNetworkStackName}--PublicSubnetSecGrpId02"
Tags:
- Key: Name
Value: My-Public-instance-02
MyPrivateInstance02:
Type: AWS::EC2::Instance
Properties:
CreditSpecification:
CPUCredits: standard
InstanceType: t2.micro
KeyName: !Ref MyKey
ImageId: ami-078296f82eb463377
NetworkInterfaces:
- AssociatePublicIpAddress: "false"
DeviceIndex: "0"
SubnetId:
Fn::ImportValue:
!Sub "${MyNetworkStackName}--PrivateSubnetId02"
GroupSet:
-
Fn::ImportValue:
!Sub "${MyNetworkStackName}--PrivateSubnetSecGrpId02"
Tags:
- Key: Name
Value: My-Private-instance-02
Outputs:
MyPublicInstanceIPaddr01:
Description: My Public Instance's Public IP address for AzName01
Value: !GetAtt MyPublicInstance01.PublicIp
MyPrivateInstanceIPaddr01:
Description: My Private Instance's Private IP address for AzName01
Value: !GetAtt MyPrivateInstance01.PrivateIp
MyPublicInstanceIPaddr02:
Description: My Public Instance's Public IP address for AzName02
Value: !GetAtt MyPublicInstance02.PublicIp
MyPrivateInstanceIPaddr02:
Description: My Private Instance's Private IP address for AzName02
Value: !GetAtt MyPrivateInstance02.PrivateIp
上記サンプルコードの主なポイントは下記の部分になります。
- Parametersセクション
- 既に作成されているVPC環境に対応するCloudFormationのスタック名を指定します。
- Resourcesセクション
-
AWS::EC2::KeyPair
- EC2インスタンスに接続するための公開鍵を指定します。新規にコード内で鍵を作成することも可能ですが、ここでは自分のPC上に既に存在している公開鍵(ssh-keygenコマンドで作成)をAWS上にアップロードして使用するようにしています。
なお、コード内に直接鍵の中身を記述するのはセキュリティ上良くないので、AWS Systems Managerのパラメータ・ストア内に公開鍵の中身を事前に保管しておきます。それを、PublicKeyMaterial: '{{resolve:ssm:publickeymaterial:1}}'のロジックを使用して値を入手できるように設定します。Terraformでは、file関数があるので自身のPC上にある公開鍵のファイル・パスを指定すればOKなのですが、CloudFormationにはそのような機能はなさそうです。
- EC2インスタンスに接続するための公開鍵を指定します。新規にコード内で鍵を作成することも可能ですが、ここでは自分のPC上に既に存在している公開鍵(ssh-keygenコマンドで作成)をAWS上にアップロードして使用するようにしています。
-
AWS::EC2::Instance
- EC2インスタンスのプロビジョニングに必要な情報を記載します。VPC環境作成用のCloudFormationコードの実行によって作成されたサブネットIDとセキュリティー・グループIDの情報を下記のようにImportValue関数を利用して取得します。
-
AWS::EC2::KeyPair
NetworkInterfaces:
- AssociatePublicIpAddress: "true"
DeviceIndex: "0"
SubnetId:
Fn::ImportValue:
!Sub "${MyNetworkStackName}--PublicSubnetId01"
GroupSet:
-
Fn::ImportValue:
!Sub "${MyNetworkStackName}--PublicSubnetSecGrpId01"
ImportValue関数の詳細については、下記リンク先のドキュメントを参照ください。
Fn::ImportValue
なお、上記リンク先のサンプルコードを模倣してImportValue関数部分を書こうとするとエラーになり動きません。色々試行錯誤を繰り返して、今回紹介したコードのような形で記載すると正常稼働することが確認できました。ですので、ImportValue関数を書いていて上手くいかない状況に陥った方は参考にしていただければと思います。
- Outputsセクション
- パブリック・インスタンスのパブリックIPアドレスと、プライベート・インスタンスのプライベートIPアドレスを出力するようにしています。
EC2インスタンス・プロビジョニング用サンプルコードの実行
既にVPC環境作成用CloudFormationコードの実行が終了していることを確認後、今回のサンプルコードを実行します。
- コードの実行
- スタック作成完了後のイメージ
- 「出力」タブの確認
サンプルコードのOutputsセクションに指定した、パブリック・インスタンスのIPアドレスとプライベート・インスタンスのIPアドレスの一覧が表示されます。
EC2インスタンスへの接続確認
ここでは上記の「出力」イメージにおけるAzName01のアベイラビリティー・ゾーンに作成されたパブリック・インスタンス(パブリックIP:18.181.201.108)、プライベート・インスタンス(プライベートIP:10.0.2.231)にそれぞれ接続できることを確認します。本記事ではTeratermを使用して接続確認を行います。(AzName02に対するものについては同様なので割愛します。)
-
パブリック・インスタンスへの接続確認
接続確認OKです。なお、プライベート・インスタンスに対するセキュリティー・グループにおいて、パブリック・インスタンスからのping応答を許可するように設定しているため、プライベート・インスタンスに対するpingも問題なく返ってくることが確認できます。
-
プライベート・インスタンスへの接続確認
プライベート・インスタンスには直接TeratermでSSH接続はできないため、パブリック・インスタンスにSSH接続を行った後、そこからプライベート・インスタンスに対してSSH接続を実施します。その為には、パブリック・インスタンス上で、プライベート・インスタンスにSSH接続するための秘密鍵を作成しておきます。(PC上にある秘密鍵をそのままエディタでコピーすればOKです。)
秘密鍵はMyEC2Key.pemというネーミングで作成しているので、同ネーミングでパブリック・インスタンス上に作成後、chmodコマンドで秘密鍵のアクセス権限をec2-userユーザーのみ読み書き権限を付与するように変更します。後は、作成した秘密鍵を指定の上、プライベート・インスタンスのIPアドレスに対して、SSHコマンドでログオンすればプライベート・インスタンスに接続可能です。
接続確認OKです。
おわりに
今回はCloudFormationのクロススタック機能を利用して、既にCloudFormationのコードで作成されているVPC環境上に、新たにEC2インスタンスをプロビジョニングするためのサンプルコードを紹介しました。コードを書く過程で、AWSのリソースに対する知識も色々得ることができるため、CloudFormationを使いこなせるようになると自身のAWSスキルの幅も拡げることができるかなと思いました。今後も他のAWSサービスを学んだ折りには、CloudFormationでのコード化にチャレンジしてみようと思います。