前回まで
CloudFormation連載の続きです。
前回は、CloudFormationのテンプレートを使って、AWSインフラ環境にVPC、Subnet、InternetGateway、NATGatewayを構築しました。
今回はPublic/PrivateそれぞれのSubnet内にEC2を二つたてて、ローカル環境からSSHを使ってPubilc EC2→ Private EC2 の順でログインします。
CloudFormationの基礎知識や、テンプレートの基礎、実行方法は前回の記事をご確認ください。
事前準備. キーペアの作成
CloudFormationの作業に入る前に、マネージメントコンソールでEC2にログインするための鍵ファイルを作成します。
- 補足.調べたところ、キーペアはCloudFormationで動的に作成することもできるそうなのですが、手順が複雑化するので、本連載の後半で、余力があればトライすることにしようと思います。 qiita.com
AWSマネージメントコンソールでEC2サービスを開き、左メニューから「キーペア」を選択し、以下のように鍵を作成します。この時「名前」に入力した値は、後ほどテンプレートファイルで指定することになります。
「キーペアを作成」ボタンをクリックすると、ローカルディスクにpemファイルがダウンロードされますので、ログインのために保存しておきます。
テンプレートファイルに追記
前回まで記述したテンプレートファイルに加え、以下の内容を加筆していきます。
(Public)EC2とセキュリティグループ
早速、EC2の内容を追記していきます。最初は、Public Subnet 内に配置される、外部からSSHアクセス可能なEC2です。SSHを通すには、ポート22を許可するセキュリティグループをEC2に適用する必要がありますので、セキュリティグループも併せて作成します。
# EC2(public) cftPublicEc2: Type: AWS::EC2::Instance Properties: KeyName: cf-tutorial-ec2-key DisableApiTermination: false ImageId: ami-020283e959651b381 InstanceType: t2.micro SubnetId: !Ref cftPublicSubnet Monitoring: false SecurityGroupIds: - !Ref cftPublicEc2Sg UserData: !Base64 | #!/bin/bash -ex # put your script here Tags: - Key: Name Value: cf-tutorial-public-ec2 # SecurityGroup(public EC2) cftPublicEc2Sg: Type: AWS::EC2::SecurityGroup Properties: GroupName: cf-tutorial-public-ec2-sg GroupDescription: Allow SSH Access. VpcId: !Ref cftVpc SecurityGroupIngress: - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: 60.111.xxx.xxx/32 # ← 許可ソースするIPに発信元IP(自PCのIP)を設定 Tags: - Key: Name Value: cf-tutorial-public-ec2-sg
EC2のリソースでは、ImageId
と InstanceType
で対象のEC2を決定します。ImageId
は、AMIのイメージIDとなります。標準のカタログからも選択が可能であり、上記テンプレートで指定した「ami-020283e959651b381」は、以下の画面で確認した内容を指定しました。
InstanceType
は、リファレンスの一覧で確認し、そこから選択します。今回は、最小レベルの種類である「t2.micro」を指定しました。
KeyName
には、事前準備したキーペアの「名前」cf-tutorial-ec2-key
を指定します。
セキュリティグループリソースでは、VpcId
に 該当するVPCを指定し、SecurityGroupIngress
に インバウンドのルールを設定します。SecurityGroupIngress
には許可するポート番号と、CidrIp
(許可するソースIP)を設定します。
なお、アウトバウンドに指定を行う場合は、これに加え SecurityGroupEgress
を設定します。
ちなみに、なぜかわかりませんが GroupDescription
が必須だったりします。任意でよいので、今回は「Allow SSH Access.」と設定しました。
(Public)EC2にElastic IPアドレスを割り当てる
せっかくなので、PublicのEC2には、固定のグローバルIPアドレスを割当しましょう。前回NAT Gatewayでもやりましたが、AWSのElastic IPサービスを使って固有のIPを取得します。
# ElasicIp for public EC2 cftPublicEc2Eip: Type: AWS::EC2::EIP Properties: InstanceId: !Ref cftPublicEc2
記述自体はそれほど難しいことはなく、InstanceId
に、割り当てるEC2インスタンスを !Ref
で指定するだけです。これにより、Ipが自動割当てされます。
ただ、IPアドレスは自動割り当てとしているため、割り当てられたIPがいくつになるか知りたくなりますね。
割り当てられた Elastic IP をOutputする
せっかくなので、ここでテンプレートファイルの Outputs
セクションを使って割当られたIPアドレスを出力してみましょう。
# IP Address Allocated to Public EC2 Outputs: cftPublicEc2Eip: Value: !GetAtt cftPublicEc2Eip.PublicIp
上記の内容は、Resources
セクションと同列のインデントに記載することに注意して下さい。
cftPublicEc2Eip
の PublicIp
を !GetAtt
組込み関数を使って取得し、リソース名 cftPublicEc2Eip
に対する値(Value
)として設定します。
!GetAtt
でどのような値が取得できるかは、各リソースのリファレンスの「Return Values」(EIPのResult Valuesはこちら)に書かれています。参考にしてみましょう。
<Elastic IP の Return Values>
(Private)EC2とセキュリティグループ
Publicと同様にPrivateEC2を構築します。PublicEC2との違いのポイントは以下です。
- セキュリティグループで、PublicEC2のプライベートIPアドレスからのSSHのみ許可すること。
- グローバルIPアドレス(Elastic IP)は必要ないこと。
「PublicEC2のプライベートIPアドレス」 は、CloudFormationに自動割り振りされていますが、このIPはどのように取得するのでしょうか。
こちらも、前述の !GetAtt
組込み関数で取得することができます。
<EC2::Instance の Return Values>
これを踏まえ、テンプレートファイルを以下のとおり記載します。
# EC2(private) cftPrivateEc2: Type: AWS::EC2::Instance Properties: KeyName: cf-tutorial-ec2-key DisableApiTermination: false ImageId: ami-020283e959651b381 InstanceType: t2.micro SubnetId: !Ref cftPrivateSubnet Monitoring: false SecurityGroupIds: - !Ref cftPrivateEc2Sg UserData: !Base64 | #!/bin/bash -ex # put your script here Tags: - Key: Name Value: cf-tutorial-private-ec2 # SecurityGroup(private EC2) cftPrivateEc2Sg: Type: AWS::EC2::SecurityGroup Properties: GroupName: cf-tutorial-prviate-ec2-sg GroupDescription: Allow SSH Access from Public EC2 Only. VpcId: !Ref cftVpc SecurityGroupIngress: - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: !Sub - ${privateIp}/32 - { privateIp : !GetAtt cftPublicEc2.PrivateIp } Tags: - Key: Name Value: cf-tutorial-prviate-ec2-sg
SecurityGroupIngress
の CidrIp(送信元)には、前述のとおり「PublicEC2のプライベートIPアドレス」を指定したいわけですが、 CIDR表記とする必要があります。そこで今回は!Sub
関数を使いました。!Sub
は文字列の置換関数です。${privateIp}
の部分に、その下にある「privateIp」の設定内容が置換されます。設定内容には、!GetAtt cftPublicEc2.PrivateIp
からは「10.0.1.55」のようなIPアドレスが返却されるので、!Sub
関数のテンプレ―ト${privateIp}/32
の結果は「10.0.1.55/32」(CIDR表記) となります。
なお、この内容でデプロイした結果をマネージメントコンソールで見ると、このEC2にはパブリックIPが割当されていません。これは前回、Private Subnetでは、IPアドレスの割り当てを false
に設定したためです。これによって、外部からアクセスはできないことになります。
テンプレートファイル全文
ここまでの記述内容をまとめると次のようになります。
(このまま使用する場合は、cftPublicEc2Sg
のSecurityGroupIngress
のCidr
には、自PCのグローバルIPアドレスを設定してください)
▼(テンプレート全文はここをクリックしてください)
AWSTemplateFormatVersion: 2010-09-09 Resources: # VPC cftVpc: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/21 EnableDnsSupport: true Tags: - Key: Name Value: cf-tutorial-vpc # Internet Gateway cftIgw: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: cf-tutorial-igw AttachGateway: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref cftVpc InternetGatewayId: !Ref cftIgw # Subnet (public) cftPublicSubnet: Type: AWS::EC2::Subnet Properties: AvailabilityZone: ap-northeast-1a MapPublicIpOnLaunch: true VpcId: !Ref cftVpc CidrBlock: 10.0.1.0/24 Tags: - Key: Name Value: cf-tutorial-public-subnet cftPublicRtb: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref cftVpc Tags: - Key: Name Value: cf-tutorial-public-rtb cftPublicRoute: Type: AWS::EC2::Route Properties: RouteTableId: !Ref cftPublicRtb DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref cftIgw cftPublicRtAssoc: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref cftPublicSubnet RouteTableId: !Ref cftPublicRtb # Subnet (private) cftPrivateSubnet: Type: AWS::EC2::Subnet Properties: AvailabilityZone: ap-northeast-1a MapPublicIpOnLaunch: false VpcId: !Ref cftVpc CidrBlock: 10.0.2.0/24 Tags: - Key: Name Value: cf-tutorial-private-subnet cftPrivateRtb: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref cftVpc Tags: - Key: Name Value: cf-tutorial-private-rtb cftPrivateRoute: Type: AWS::EC2::Route Properties: RouteTableId: !Ref cftPrivateRtb DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref cftNatgw cftPrivateRtAssoc: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref cftPrivateSubnet RouteTableId: !Ref cftPrivateRtb # NAT Gateway cftNatgw: Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt cftNatgwEip.AllocationId SubnetId: !Ref cftPublicSubnet Tags: - Key: Name Value: cf-tutorial-natgw cftNatgwEip: Type: AWS::EC2::EIP Properties: Domain: vpc # EC2(public) cftPublicEc2: Type: AWS::EC2::Instance Properties: KeyName: cf-tutorial-ec2-key DisableApiTermination: false ImageId: ami-020283e959651b381 InstanceType: t2.micro SubnetId: !Ref cftPublicSubnet Monitoring: false SecurityGroupIds: - !Ref cftPublicEc2Sg UserData: !Base64 | #!/bin/bash -ex # put your script here Tags: - Key: Name Value: cf-tutorial-public-ec2 # SecurityGroup(public EC2) cftPublicEc2Sg: Type: AWS::EC2::SecurityGroup Properties: GroupName: cf-tutorial-public-ec2-sg GroupDescription: Allow SSH Access. VpcId: !Ref cftVpc SecurityGroupIngress: - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: 60.111.xxx.xxx/32 # ← 許可ソースするIPに発信元IP(自PCのIP)を設定 Tags: - Key: Name Value: cf-tutorial-public-ec2-sg # ElasicIp for public EC2 cftPublicEc2Eip: Type: AWS::EC2::EIP Properties: InstanceId: !Ref cftPublicEc2 # EC2(private) cftPrivateEc2: Type: AWS::EC2::Instance Properties: KeyName: cf-tutorial-ec2-key DisableApiTermination: false ImageId: ami-020283e959651b381 InstanceType: t2.micro SubnetId: !Ref cftPrivateSubnet Monitoring: false SecurityGroupIds: - !Ref cftPrivateEc2Sg UserData: !Base64 | #!/bin/bash -ex # put your script here Tags: - Key: Name Value: cf-tutorial-private-ec2 # SecurityGroup(private EC2) cftPrivateEc2Sg: Type: AWS::EC2::SecurityGroup Properties: GroupName: cf-tutorial-prviate-ec2-sg GroupDescription: Allow SSH Access from Public EC2 Only. VpcId: !Ref cftVpc SecurityGroupIngress: - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: !Sub - ${privateIp}/32 - { privateIp : !GetAtt cftPublicEc2.PrivateIp } Tags: - Key: Name Value: cf-tutorial-prviate-ec2-sg # ====================== # Outputs Statement # ====================== # IP Address Allocated to Public EC2 Outputs: cftPublicEc2Eip: Value: !GetAtt cftPublicEc2Eip.PublicIp
デプロイを実施
上記のテンプレートファイル(cf.yaml)を、AWS環境に対してデプロイします。
デプロイは次のコマンドで実行します。
> aws cloudformation deploy --template-file ./cf.yaml --stack-name cf-tutorial Waiting for changeset to be created.. Waiting for stack create/update to complete Successfully created/updated stack - cf-tutorial
CloudFormationが正常終了すれば、作業終了です。マネージメントコンソールから、各リソースを開いて、内容がテンプレートファイルのとおり作られているかチェックしてみましょう。
接続確認
SSH接続をするには、接続先となるEC2のIPアドレスを把握する必要があります。マネージメントコンソール画面のCloudFormationを開き、スタック cf-tutorial
の「出力」タブを選択してください。テンプレートファイルに Outputs
セクション を記述したことで、PublicEC2のグローバルIPアドレスが表示されています。
この値(グローバルIPアドレス)に対してSSH接続を行います。
PublicEC2にSSH接続
Powershell(またはコマンドプロンプト)を開き、キーペアをダウンロードしたフォルダまで移動してから、SSHコマンドで接続を行います。
PS> ssh -i cf-tutorial-ec2-key.pem ec2-user@3.115.208.35 The authenticity of host '3.115.208.35 (3.115.208.35)' can't be established. ED25519 key fingerprint is SHA256:0PtpyHKlYHCUUh7jnd/wD1TIOVqtkb6Y6/Cp6D3V0Ls. This key is not known by any other names Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added '3.115.208.35' (ED25519) to the list of known hosts. , #_ ~\_ ####_ Amazon Linux 2023 ~~ \_#####\ ~~ \###| ~~ \#/ ___ https://aws.amazon.com/linux/amazon-linux-2023 ~~ V~' '-> ~~~ / ~~._. _/ _/ _/ _/m/' [ec2-user@ip-10-0-1-49 ~]$
無事、PublicEC2 まで接続できたようです。
次の作業のため、一度、接続を切断します。
[ec2-user@ip-10-0-1-49 ~]$ exit logout Connection to 3.115.208.35 closed. PS>
PublicEC2に鍵を転送する
次に、PublicEC2に、PrivateEC2に接続するための鍵を転送します。ファイルの転送には、SCPコマンドを使用します。
PS> scp -i cf-tutorial-ec2-key.pem cf-tutorial-ec2-key.pem ec2-user@3.115.208.35:~ cf-tutorial-ec2-key.pem 100% 1675 95.9KB/s 00:00
これにより、PublicEc2内に、接続用の鍵が送り込まれました。
PublicEC2経由でPrivateEC2にSSH接続
- 最初に、ローカルPCでPowershellを開き、PublicEC2にSSH接続します。
PS> ssh -i cf-tutorial-ec2-key.pem ec2-user@3.115.208.35 The authenticity of host '3.115.208.35 (3.115.208.35)' can't be established. ED25519 key fingerprint is SHA256:0PtpyHKlYHCUUh7jnd/wD1TIOVqtkb6Y6/Cp6D3V0Ls. This key is not known by any other names Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added '3.115.208.35' (ED25519) to the list of known hosts. , #_ ~\_ ####_ Amazon Linux 2023 ~~ \_#####\ ~~ \###| ~~ \#/ ___ https://aws.amazon.com/linux/amazon-linux-2023 ~~ V~' '-> ~~~ / ~~._. _/ _/ _/ _/m/' [ec2-user@ip-10-0-1-49 ~]$
[ec2-user@ip-10-0-1-49 ~]$ sudo chmod 600 cf-tutorial-ec2-key.pem
[ec2-user@ip-10-0-1-49 ~]$ ssh -i cf-tutorial-ec2-key.pem ec2-user@10.0.2.54 The authenticity of host '10.0.2.54 (10.0.2.54)' can't be established. ED25519 key fingerprint is SHA256:uLWt5bgqBiGE4cQchtz2ihaoIzHOgrX4N8GNgMJkLqI. This key is not known by any other names Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added '10.0.2.54' (ED25519) to the list of known hosts. , #_ ~\_ ####_ Amazon Linux 2023 ~~ \_#####\ ~~ \###| ~~ \#/ ___ https://aws.amazon.com/linux/amazon-linux-2023 ~~ V~' '-> ~~~ / ~~._. _/ _/ _/ _/m/' [ec2-user@ip-10-0-2-54 ~]$
無事接続できました。画面だとわかりずらいですが、図にするとこんな感じです。
また念のため、このPrivateEC2から、外部アクセスができるかを確認しています。
# hostコマンドでgoogleページのIPアドレスをチェック [ec2-user@ip-10-0-2-54 ~]$ host www.google.co.jp www.google.co.jp has address 142.251.222.3 www.google.co.jp has IPv6 address 2404:6800:4004:81f::2003 # nslookupで応答を確認 [ec2-user@ip-10-0-2-54 ~]$ nslookup 142.251.222.3 3.222.251.142.in-addr.arpa name = nrt13s71-in-f3.1e100.net. Authoritative answers can be found from:
無事、応答できていることが分かります。
まとめ
前回と併せて、Cloud FormationでAWSの基本的なEC2構成のAWSインフラを構築できました。これまで手作業で構築していたEC2が、コマンド1回(2分程度)でできるので、簡単に検証環境などを作ることができます。これは便利!
次回は、Parameters、Mappingsといった、Resources以外のセクションも活用して、同じ構成を理想的なIaCに近づけていこうと思います。
なお、この結果については、以下のGitHubに掲載していますのでご確認ください。 github.com