初めてAWSで環境構築することになって、それもCloudFormation(cfnと略すようです)でリソースを立てる必要のある方なんてそうそういないかもしれませんが、私がそのような境遇になったので、同じような方が迷わないよう、私の備忘録も兼ねて記しておきます。
ここまで丁寧に解説してくれているサイトは見つからなかったので。
インフラ構築の前提条件
1. 私の当時の経験
- Rails勉強して、Railsチュートリアル完走済、ポートフォリオはつくれる、程度のレベル
- CLFは取得済(どんな資格か気になる方はこちらを参照してみてください
これだけです。CloudFormationの存在自体はCLF取得の過程で知っていました。あれでしょ?コードでインフラ環境を定義できる、いわゆるIaC(Infrastructure as Code)ってやつでしょう?くらいの知識でしたがww
なのでこの記事でも、各サービスの説明は省いています。(ALBって何?みたいな説明)
2. 今回構築するAWS環境
今回は、VPCやIGW, VPNは既にあるという前提条件。
- パブリックサブネットをつくり、EC2を1台立ち上げ(踏み台サーバー)
- プライベートサブネットをつくり、EC2を1台立ち上げ(Webサーバー)
- Webサーバーからインターネットにアクセスできるよう、NATゲートウェイを設置
- 今回事情があり、WebサーバーにはVPNからHTTP(Port:8080)でアクセス。それをセキュアにする(HTTPSにする)ために、ロードバランサーを用意して、ACMで取得したSSL/TLS証明書を持たせる。(SSLオフロード機能)
はい、大きく、この4つだけです。ACMだけはCLF勉強時にも出てこなかったので未知でしたが、他はまあ、よくある、踏み台サーバーとWebサーバーの組み合わせです(当時の私が「踏み台サーバー」でググったことは内緒です)
が、ルートテーブル設定とか、EIP取得とか、DNS登録とか、色々な付帯作業があることを、その時の私は知らなかったんですね・・・本当に素人丸出しでしたが、具体的にどうやったかこの後、解説しますのでご安心ください!
初めてでもCloudFormation(cfn)でリソースを構築する手順
1. まずやること
触ったことない方は、せめてAWSの公式ハンズオンはやっておくと良いです。
申込みフォームの記入がやや面倒ですが、無料ですし、2~3時間くらいで1周できますよ(動画自体はもっと短いです)
これで雰囲気を掴めますし、基本形となるかなり単純な形のテンプレートの雛形がダウンロードできます。本記事もそうですが、ネット上にもテンプレートのサンプルってたくさん転がっているのですが、とにかく長くて、初めて見る人にとっては「おえ」ってなるので、短いものを理解して、それから自分でどんどん付け足していくのがオススメです。その点、上記ハンズオンで配布されている雛形は短いけれどもそれだけでリソースを構築できるので、雛形にするにはもってこいでした。
2. 進め方
上記ハンズオンの中でも紹介されていますが、テンプレートリファレンスがCloudFormationの辞書のような役割です。基本的にこのテンプレートリファレンスを見ながら進めていきます。現在英語版しかないみたいなので、chromeの機能で日本語訳してもらって見ていきました。
しかし、ほとんど初めてAWSを触る場合、リソースを立てるのにそもそもどんな設定が必要なのか?わかりませんよね。どれが必須の設定か、一応、リファレンスにも書いてあるのですが、
- 「条件付き必須」というのがあり、タイプによっては必須になる場合がある。
- 必須ではないけれども、指定しないと勝手にタイプが選ばれるため、実質的に必須の場合がある。
みたいなパターンもあるため、初めてなのにリファレンスだけ見て読み解いていくのは賢くありません。
そこで、実際にマネジメントコンソールから手動でリソースを立ち上げてみることをオススメします!正確には、立ち上げる一歩手前まで設定してみる、ということです。マネジメントコンソールから手動でリソースを立ち上げる場合、
- そのリソースのトップページにいき、「作成する」や「起動する」といったオレンジのボタンを押す。
- 設定をする。(この画面が複数ある場合もある)
- 「次へ」ボタンを押すと、設定の確認画面が現れる。
- 確認画面で、「作成する」や「起動する」といったオレンジのボタンを押す。
この順に立ち上げることがほとんどで、4で最後のオレンジのボタンを押さない限り、リソースが作られることはありません。途中でいつでもキャンセルできますし、少なくとも、「次へ」ボタンはいくら押しても大丈夫です。(なので、実際に立ち上げるわけではないので既存の環境に変更を加えることはありません。)
そうやって設定していくと、「これは必須ではないけど、選択肢として何があるから明示的に書いておくか」などの判断ができるようになります。
このように、
- テンプレートリファレンスを確認
- マネジメントコンソールで実際に手動でリソースの設定をしてみる
これを繰り返すことで、CloudFormationのテンプレートを作成していくのが最もオーソドックスかつ確実だと思います。
作ってみてダメなら作り直せば良い、という考え方もあるかと思いますが、ここを変更するには作り直さないといけない(一度削除しなくてはならない)こともあるので、少し慎重になっても良いかと思います。私はGitと連携してダブルチェックしてもらいました。品質の担保が難しいんですよね。ちなみに、構文エラーなどは、ちゃんとリソース作成前に事前にエラーを返してくれるので大丈夫です。
また、テンプレートの記述形式はJSONとYAMLを選べるのですが、圧倒的にYAML形式がオススメです。見やすいですし、閉じ括弧の入力ミスでエラーなんていうこともありませんから編集も容易です。組み込み関数の短縮形が使えるのも大きく、メリットばかりです。
万が一、JSON必須の場合も、自動変換ツールがありますので、それを利用されることをオススメします。
テンプレート解説(ブロック毎)
それでは、早速、ブロックごとに見ていきましょう!
0. パラメータの設定
最初の1行はフォーマットが決まっていますので、このとおりに書きます。しかも、2021年3月現在、”2010-09-09″のみが有効な値なので注意です。勝手にstack作成日とかに変更してはいけません。
繰り返し使う or 変わり得るものについては、パラメータで与えておくと良いです。
今回、Outputsの設定は行っていません。なくても一応リソースは出来ます。
AWSTemplateFormatVersion: 2010-09-09 # この日付は"2010-09-09"で固定(2021年3月現在)
Description: Comments # Stackに関するコメントが書ける。ただし、全角は文字化けするので注意。
Parameters: # 何度も出てくる or 変わり得るものについては、パラメーターで与えておくと良い。
sysname: # プロジェクトタグなどにシステムの名前をつけておけば、後からそのシステムに関するリソースをピックアップできる。
Type: String
Default: System's-Name
VPCID:
Type: String
Default: vpc's-id # 既存のVPCのIDをコピペ
AZname1:
Type: String
Default: ap-northeast-1a # 既存のAZの名前をコピペ
AZname2:
Type: String
Default: ap-northeast-1c # 既存のAZの名前をコピペ
ACMArnToELBin:
Type: String
Default: arn:aws:acm:ap-northeast-1:123456789... # ELBに持たせるACMのARNをコピペ
1. サブネットの作成(AWS::EC2::Subnet)
既存のVPCにサブネットを作成します。!Refとかは組み込み関数と呼ばれるものです。
Resources:
# Create subnets in the existing VPC
PublicSubnet01:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.1.1.0/26 # プライベートIPアドレスの範囲を指定
VpcId: !Ref VPCID # VPCを指定
AvailabilityZone: !Ref AZname1 # AZを指定
MapPublicIpOnLaunch: true # パブリックサブネットではtrueにする。
Tags: # Tagはあってもなくても問題ないし、後からでもつけられる。
- Key: Name
Value: !Sub ${sysname}-publicsubnet01
- Key: Project
Value: !Ref sysname
PrivateSubnet01:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.1.1.64/26
VpcId: !Ref VPCID
AvailabilityZone: !Ref AZname1
Tags:
- Key: Name
Value: !Sub ${sysname}-privatesubnet01
- Key: Project
Value: !Ref sysname
PublicSubnet02:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.1.1.128/26
VpcId: !Ref VPCID
AvailabilityZone: !Ref AZname2
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub ${sysname}-publicsubnet02
- Key: Project
Value: !Ref sysname
PrivateSubnet02:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.1.1.192/26
VpcId: !Ref VPCID
AvailabilityZone: !Ref AZname2
Tags:
- Key: Name
Value: !Sub ${sysname}-privatesubnet02
- Key: Project
Value: !Ref sysname
2. NATゲートウェイの作成(AWS::EC2::NatGateway)
プライベートサブネットに設置するWebサーバーからインターネットにアクセスできるよう、NATゲートウェイを設置します。NATゲートウェイにはEIPが必要なので、EIPも同時に生成します。EIPのIDを取得するには!GetAttを使ってください。!RefだとIPアドレスが与えられます。
# Create NAT gateway with a new Elastic IP address
EIP01: # NATゲートウェイにはEIPが必要。
Type: AWS::EC2::EIP # とりあえずEIPを作成(取得)する
Properties:
Domain: vpc # これは書かなくてもきっと大丈夫だが一応明記
Tags:
- Key: Name
Value: !Sub ${sysname}-eip01
- Key: Project
Value: !Ref sysname
NAT01:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt EIP01.AllocationId # ここで、EIPのIDを指定
SubnetId: !Ref PublicSubnet01 # NATゲートウェイを設置するパブリックサブネットを指定
Tags:
- Key: Name
Value: !Sub ${sysname}-nat01
- Key: Project
Value: !Ref sysname
3. ルートテーブルの作成(初心者要注意!)
さて、囲い(=サブネット)を立てて、門(ゲートウェイ)を設置したのでネットワーク周りはこれでOKかというと、全く、そんなことはありません。
まだ道(=ルート)がありません!
道がなければ、だれも敷地から出られないことになります。(正確には、何も設定しないとメインルートテーブルが関連付けられる。デフォルトのままだとローカルルートのみ。VPCの敷地内だったらアクセスできるよってやつ。これから設定するのはメインルートテーブルに対してカスタムルートテーブルと呼ばれているが、この記事では簡単のため単にルートテーブルと呼ぶこととする。)
ルートが必要なのはわかった、じゃあどう設定するか?これが素人にとっては少々複雑だったので、丁寧に解説していきます。
まず、トラフィックを許可する(アクセスできるようにする)ためには、「誰ならどこからどこへ行ってよい」という情報を特定の表に記載する、すなわち
- A ルートテーブル (= A行き先一覧表、地図と言ってもよい)に
- B 誰が (= IPアドレスなどの条件)
- C どこから (= 出発地、具体的にはサブネットのこと)
- D どこへ (= 行き先、具体的にはゲートウェイやインスタンスなど)
通ることを許可するかを指定する必要がある。(A~Dを設定して、かつ、関連付ける必要がある)
これを踏まえた上で、次の3つの作業(ブロック)でルートを設定することになります。
作業 | イメージ | A~Dのどれに当たるか | CloudformationにおけるResource type | |
1 | ルートテーブル(の枠組みの作成) | 行き先一覧表の枠だけつくっておくイメージ。VPCだけ指定。 | まずAだけ作成 | AWS::EC2::RouteTable |
2 | ルート(の作成) | ルートテーブルに「誰はどこへ行っていいよ」情報を書くイメージ。 | Aに、BとDの情報を追記 | AWS::EC2::Route |
3 | ルートテーブルをサブネットに関連付け | 誰は「どこから」どこへ行っていいよ、の「どこから」を指定する。 | AとCを関連付け | AWS::EC2:: SubnetRouteTableAssociation |
- まず、予めVPCを指定して、何も書かれていない空白のルートテーブル(A)を作成する。
- 次に、ルートテーブルに、「誰はどこへ行っていいよ」情報を追記する。これがルート。ルートはAとBとDの情報(誰がどこへ行っていいかをこのルートテーブルに書くよ!な情報)を持つ。ルートは複数追記可能。
- 最後に、「このルートテーブルはどこから行く場合の情報なのか」を追記する。これがルートテーブルをサブネットへ関連付ける、ということ。AとCの情報を持つ。1つのサブネットに対して、必ず1つのルートテーブルを関連付ける必要がある。
これで、関連付けられたサブネット(C)の中のリソースと、外部のDとのルートが開通して、アクセスできるようになります。(ちなみに、!Refを使っている限り、順番は入れ替え可能です。心配ならDependsOnを使うと良いでしょう。)
特に、ルートの作成に関しては、「書かれているルートテーブル」を指定することのみ必須になっているんですが、「誰が」「どこへ」行ってよいのか、すなわち「通って良い人の条件」と「行き先」の情報を持たないとルートを作成する意味がないので、実質、必須であるというのは押さえておく必要があります。(それらを指定する方法が色々あるため、「必須ではない」になっているだけ)
以上より、コードを見ると理解しやすいと思います。コメントで補足してあります。
# Create RouteTable for Public-Subnet
PublicRT: # VPCだけ指定してルートテーブルを作成
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPCID
Tags:
- Key: Name
Value: !Sub ${sysname}-publicrt
- Key: Project
Value: !Ref sysname
PublicRouteToIGW: # ルートテーブルを指定して、トラフィックの行き先と条件を指定
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref publicRT # ルートテーブルを指定(必須)
DestinationCidrBlock: 0.0.0.0/0 # IPアドレス=通ってよい人の条件(実質必須)
GatewayId: igw-0123456789 # InternetゲートウェイのID=行き先の情報(実質必須)
PublicSubnet01Association: # ルートテーブルの関連付け(ルートテーブルを指定して、トラフィックの出発点を指定)
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet01 # 指定したルートテーブルを関連付けるサブネットを指定
RouteTableId: !Ref publicRT # ルートテーブルを指定
# Create RouteTable for Private-Subnet
PrivateRT: # 今回はそれぞれのサブネットに対して、異なるルートテーブルを作成。
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPCID
Tags:
- Key: Name
Value: !Sub ${sysname}-privatert
- Key: Project
Value: !Ref sysname
PrivateRouteToNAT:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref privateRT
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NAT01
PrivateSubnet01Association:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet01
RouteTableId: !Ref privateRT
# Enable route transmission to VPN Gateway
VPNGatewayRoute: # これで「ルート伝播」がtrueになり、なんとすべてのVPNとのトラフィックを許可できる。面倒なRoute設定が不要!
Type: AWS::EC2::VPNGatewayRoutePropagation
Properties:
RouteTableIds:
- !Ref publicRT
- !Ref privateRT
VpnGatewayId: vpn-0123456789 # 既存のVPNゲートウェイのID
最初、定期券や空港の時刻表みたいに、出発地→到着地のリストみたいなのを想像してしまったのが誤解の元でした。しかし、これで無事ネットワーク周りは整備できました!(今回はVPC, IGW, VPNは既存のものを活用する形だったため、多少イージーモードだったかもしれません)
4. 踏み台/Webサーバー用のEC2インスタンスの作成(AWS::EC2::Instance)
やっと土地の整地(=サブネットやゲートウェイの設置)と道路整備(=ルートの設定)が終わったので、土地に家(=サーバー)を立てていくことが出来ます。
EC2は、色々設定があるので、マネジメントコンソールのハンズオンが大切ですね。要件定義書があるなら、そこに記載されているスペックを満たすよう設定できれば大丈夫です。
まず、EC2にはEBSをアタッチしますね。それには、EC2のオプションでAWS::EC2::Instance BlockDeviceMappingを使用します。ここ、最初わかりにくかったので。
また、当たり前らしいのですが、家には施錠(=セキュリティ対策)しないといけません。
今回は、下記2つで施錠します。
- キーペア(公開鍵&秘密鍵)の作成
- セキュリティーグループの作成(AWS::EC2::SecurityGroup)
そして、キーペアは、秘密鍵をダウンロードする必要があるため、キーペアはCloudFormationでは作れません!ここ、注意してください。したがって、マネジメントコンソールで予め作っておく必要があります!
セキュリティーグループは、ホワイトリストです。すなわち、「誰々は入っていいよ」のリストです。ルートテーブルでいくら道が整備されていても、ここで許可されていないと結局アクセスできないので重要です。(デフォルトでは誰も入れない)
踏み台サーバーのパブリックIPアドレスが変動すると厄介なので、EIPも作成します。EIPは作成したらリソースに割り当てないといけないのですが、NATゲートウェイに割り当てる際はNATゲートウェイ作成時、EC2インスタンスに割り当てる際はEIP作成時に割り当てるので注意です(ややこしい)。既存の、割り当てされていないEIPがあるなら、AWS::EC2::EIPAssociationを用いて後から割り当てることもできます。(割り当てされていないEIPがあると課金されてしまうので、あまりないとは思いますが・・・リソースのお引越しとかだとありそうですね。)
あとの細かい注意事項はコード中のコメントを御覧下さい。
# ------------------------------------------------------------#
# Server and SecurityGroup
# ------------------------------------------------------------#
# Create EC2 and SecurityGroup for Bastion-server
BastionSV: # 踏み台サーバーを作成
Type: AWS::EC2::Instance
Properties:
ImageId: ami-09d28faae2e9e7138 # AMIのIDを指定
InstanceType: t3.micro # タイプを指定
SubnetId: !Ref PublicSubnet01 # どこのサブネットにつくるか指定。踏み台サーバーはパブリックに配置。
KeyName: Key-Pair-Name # アクセスするための、予め作成しておいたキーペア名を指定=公開鍵を置ける
SecurityGroupIds: # セキュリティグループを指定。
- !Ref BastionSG
Tags:
- Key: Name
Value: !Sub ${sysname}-Bastionsv
- Key: Project
Value: !Ref sysname
BastionSG: # セキュリティグループ(外から内部にアクセス可能なホワイトリスト)を作成。
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: sg for bastion server # なくてよい
GroupName: !Sub ${sysname}-bastionsg # なくてよい
VpcId: !Ref VPCID # どこのVPCにつくるか指定
SecurityGroupIngress: # インバウンドルールの設定。既にあるSGに追加する場合は、AWS::EC2::SecurityGroupIngressを使用すれば良い。
- IpProtocol: tcp # VPNから踏み台サーバーへSSH接続を許可
CidrIp: 172.XXX.XXX.0/23
FromPort: 22
ToPort: 22
- IpProtocol: tcp # 自分のパソコンから踏み台サーバーへSSH接続を許可
CidrIp: 122.XXX.XXX.XXX/32 # 自分のパソコンのようなある特定のIPアドレスからのアクセスを許可する場合でも、/32が必要。
FromPort: 22
ToPort: 22
Tags:
- Key: Name
Value: !Sub ${sysname}-bastionsg
- Key: Project
Value: !Ref sysname
EIP02: # 踏み台サーバーのパブリックIPアドレスが変動すると厄介なので、EIPを作成する。
Type: AWS::EC2::EIP
Properties:
Domain: vpc
InstanceId: !Ref BastionSV # インスタンスに割り当てる場合は、ここで指定する。
Tags:
- Key: Name
Value: !Sub ${sysname}-eip02
- Key: Project
Value: !Ref sysname
# Create EC2 and SecurityGroup for Web-server
WebSV: # webサーバーを作成
Type: AWS::EC2::Instance
Properties:
ImageId: ami-059b6d3840b03d6dd
InstanceType: m5.xlarge
SubnetId: !Ref PrivateSubnet01 # プライベートサブネットに作成
KeyName: Key-Pair-Name # 踏み台サーバーと同一のキーペアを指定
BlockDeviceMappings: # ルートボリュームの仕様を指定できる。(デフォルトで良ければ特に指定不要)
- DeviceName: "/dev/sda1" # ルートボリュームの仕様を指定するなら必須。デフォルトで良ければ、実際にマネジメントコンソールで立ち上げてみて確認すること。
Ebs:
VolumeSize: 100 # 単位はGiB
VolumeType: gp2 # デフォルトがgp2だが、明示的に指定。
SecurityGroupIds:
- !Ref WebSG
Tags:
- Key: Name
Value: !Sub ${sysname}-websv
- Key: Project
Value: !Ref sysname
WebSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: sg for web server # なくてもよい
GroupName: !Sub ${sysname}-websg # なくてもよい
VpcId: !Ref VPCID
SecurityGroupIngress:
- IpProtocol: tcp # VPNからwebサーバーへSSH接続を許可
CidrIp: 172.XXX.XXX.0/23
FromPort: 22
ToPort: 22
- IpProtocol: tcp # 踏み台サーバからwebサーバーへSSH接続を許可
SourceSecurityGroupId: !Ref BastionSG
FromPort: 22
ToPort: 22
- IpProtocol: tcp # さらにELBからのHTTP接続を許可。プライベートIPアドレスは変わるため、SGで指定する。
SourceSecurityGroupId: !Ref ELBSG
FromPort: 8080 # 今回は8080を指定(おそらくなんでも良い)。
ToPort: 8080
Tags:
- Key: Name
Value: !Sub ${sysname}-websg
- Key: Project
Value: !Ref sysname
ec2は立ち上げたら、「初期設定」が必要。最低限
・OSの最新化
・JSTタイムゾーン変更
の2点だけはやっておくのが常識のようです。(本当は他にも色々あるのだが、その色々を言い出すときりがないしケースバイケースなことも多いので、「『いつか使うかも』は使わないと思え」の考えに則って、最低限とした)
CloudFormationでも、最初立ち上げ時、UserDataプロパティを使えば、EC2のプロビジョニングで実行できるシェルスクリプトが書けるので、普通は合わせて記載するようです。私は知らなかった今回は諸事情あり上記には書いていません。(”UserData”にシェルスクリプトが書ける、なんて初見じゃわからんわ!)
参考:https://qiita.com/algi_nao/items/8898afed7ce723ea7fbb
Amazon Linux 2のEC2:
$ sudo yum update -y
ダウンロードにこれくらい容量食うけどよろしい?みたいな確認があるだけだったので、オプション-yつけてしまってよいと思います。
参考:https://www.atmarkit.co.jp/ait/articles/1608/29/news019.html
Ubuntu 20.04 LTSのEC2:
$ sudo apt-get update
$ sudo apt-get upgrade
参考:https://qiita.com/kotarella1110/items/f638822d64a43824dfa4
JSTタイムゾーンの変更:https://hacknote.jp/archives/52688/
ちなみに、今回は踏み台サーバーを設置しているため、Webサーバーへ入るには多段SSH接続が必要です。やり方はこちら
5. SSLオフロードを目的としたELB(とACM)の作成(AWS::ElasticLoadBalancingV2::LoadBalancer)
最後の難関、やってきました。ロードバランサーは、元々は負荷分散によってパフォーマンスを向上したり可用性を上げるために用いるものですが、今回はHTTP接続しか出来ないwebアプリへセキュアな接続を実現するために(具体的には、ACMで作成したSSL/TLS証明書を持たせるために)、ELBの一種であるALBを使用します。(SSLオフロード機能)
しかも、今回、DNSサーバーはAWSのサービスであるRoute53ではなく、他社のDNSサーバーを使用するため、少々、複雑で、下記の条件を満たす必要がありました。
- HTTPSのリスナーをつくるには、ALBと、発行済みのACMが必要。
- ACMを「発行済み」にするには、DNSサーバーにドメインを登録しておかないといけない。
- DNSサーバーにドメイン登録をするには、「検証待ち」のACMと、ALBを作成しておく必要あり。
ここで、「リスナー」という言葉にピンとこなかった方は、下記記事で知識補完できます。
DNSサーバーへのドメイン登録はACMもALBも同時にしたいので、以下の順序で進めていきました。
- AWSマネジメントコンソールで、ACMの証明書を手動作成(Pending validation「検証待ち」になる)
- CloudformationでALBの作成を行う。リスナーの作成はしない。なので、証明書とリスナーの関連付けもしない。(書いてコメントアウトしておいてもよい。)
- DNSサーバーに、ACM、ALBのドメイン登録(それぞれ、登録が必要)
- ACMが「発行済み」になっていることを確認
- CloudFormation更新でALBにHTTPSのリスナーを追加
これでうまくいきます。
# ------------------------------------------------------------#
# Elastic Load Balancer (Application Load Balancer)
# ------------------------------------------------------------#
# Create ELB(ALB) ELBは、デフォルトでALBが選ばれます。
ELBinalb:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Sub ${sysname}-ELBinalb
Scheme: internal # これは必須ではないが、閉域網のロードバランサーにするなら指定必須(指定しないと、デフォルトのinternet facingになってしまう上、変更するにはELBの作り直しが必要=リソースIDなどが変わってしまうので注意)
Subnets: # 2つ以上のAZに属するそれぞれ1つ以上のサブネットを指定する必要がある。
- !Ref PrivateSubnet01
- !Ref PrivateSubnet02
SecurityGroups:
- !Ref ELBSG
Tags:
- Key: Name
Value: !Sub ${sysname}-ELBinalb
- Key: Project
Value: !Ref sysname
ELBinalbTargetGroup: # ELBがアクセスする負荷分散先をターゲットと呼ぶ。
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: !Sub ${sysname}-ELBinalbtargetgroup
VpcId: !Ref VPCID
Port: 8080 # 今回は8080を指定(おそらくなんでも良い)。
Protocol: HTTP # ターゲットへアクセスするプロトコル
Targets: # ターゲットは複数選択できる。(今回は1つ)
- Id: !Ref WebSV
Tags:
- Key: Name
Value: !Sub ${sysname}-ELBinalbtargetgroup
- Key: Project
Value: !Ref sysname
ELBSG: # ALBにもセキュリティーグループが必要。
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: sg for ELB
GroupName: !Sub ${sysname}-elbsg
VpcId: !Ref VPCID
SecurityGroupIngress:
- IpProtocol: tcp # VPNからELBへのHTTPS接続を許可
CidrIp: 0.0.0.0/0 # アドレス帯が不明だったため全部を指定している。
FromPort: 443
ToPort: 443
- IpProtocol: tcp # 踏み台サーバからELBへのHTTPS接続を許可
SourceSecurityGroupId: !Ref BastionSG
FromPort: 443
ToPort: 443
Tags:
- Key: Name
Value: !Sub ${sysname}-elbsg
- Key: Project
Value: !Ref sysname
# Attach SSL Certificate for ALB ・・・ACMが「発行済み」になるまではコメントアウトしておくこと。
ELBinalbListener: # ELBへアクセスする発信元をリスナーと呼ぶ。ELBとターゲットを紐付ける役割も果たす。
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn: !Ref ELBinalb # ELBを指定
Port: 443
Protocol: HTTPS # ELBへアクセスするプロトコル
Certificates: # HTTPSを選択する場合には証明書が必要。
- CertificateArn: !Ref ACMArnToELBin # ACMが「発行済み」になったら貼り付け。
DefaultActions:
- Type: forward
TargetGroupArn: !Ref ELBinalbTargetGroup # ターゲットを指定
ELBinalbListenerCertificate: # ACMで作成した証明書をELB(のリスナー)に持たせる。
Type: AWS::ElasticLoadBalancingV2::ListenerCertificate
Properties:
Certificates:
- CertificateArn: !Ref ACMArnToELBin
ListenerArn: !Ref ELBinalbListener
テンプレート完成! → スタック作成成功!
以上をまとめるとこんな感じになります。
AWSTemplateFormatVersion: 2010-09-09
Description: Comments
Parameters:
sysname:
Type: String
Default: System's-Name
VPCID:
Type: String
Default: vpc's-id
AZname1:
Type: String
Default: ap-northeast-1a
AZname2:
Type: String
Default: ap-northeast-1c
ACMArnToELBin:
Type: String
Default: arn:aws:acm:ap-northeast-1:123456789...
Resources:
# ------------------------------------------------------------#
# Network(Subnet/NATgateway/RouteTable)
# ------------------------------------------------------------#
# Create subnets in the existing VPC
PublicSubnet01:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.1.1.0/26
VpcId: !Ref VPCID
AvailabilityZone: !Ref AZname1
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub ${sysname}-publicsubnet01
- Key: Project
Value: !Ref sysname
PrivateSubnet01:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.1.1.64/26
VpcId: !Ref VPCID
AvailabilityZone: !Ref AZname1
Tags:
- Key: Name
Value: !Sub ${sysname}-privatesubnet01
- Key: Project
Value: !Ref sysname
PublicSubnet02:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.1.1.128/26
VpcId: !Ref VPCID
AvailabilityZone: !Ref AZname2
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub ${sysname}-publicsubnet02
- Key: Project
Value: !Ref sysname
PrivateSubnet02:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.1.1.192/26
VpcId: !Ref VPCID
AvailabilityZone: !Ref AZname2
Tags:
- Key: Name
Value: !Sub ${sysname}-privatesubnet02
- Key: Project
Value: !Ref sysname
# Create NAT gateway with a new Elastic IP address
EIP01:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
Tags:
- Key: Name
Value: !Sub ${sysname}-eip-01
- Key: Project
Value: !Ref sysname
NAT01:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt EIP01.AllocationId
SubnetId: !Ref PublicSubnet01
Tags:
- Key: Name
Value: !Sub ${sysname}-nat01
- Key: Project
Value: !Ref sysname
# Create RouteTable for Public-Subnet
PublicRT:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPCID
Tags:
- Key: Name
Value: !Sub ${sysname}-publicrt
- Key: Project
Value: !Ref sysname
PublicRouteToIGW:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref publicRT
DestinationCidrBlock: 0.0.0.0/0
GatewayId: igw-0123456789
PublicSubnet01Association:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet01
RouteTableId: !Ref publicRT
# Create RouteTable for Private-Subnet
PrivateRT:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPCID
Tags:
- Key: Name
Value: !Sub ${sysname}-privatert
- Key: Project
Value: !Ref sysname
PrivateRouteToNAT:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref privateRT
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NAT01
PrivateSubnet01Association:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet01
RouteTableId: !Ref privateRT
# Enable route transmission to VPN Gateway
VPNGatewayRoute:
Type: AWS::EC2::VPNGatewayRoutePropagation
Properties:
RouteTableIds:
- !Ref publicRT
- !Ref privateRT
VpnGatewayId: vpn-0123456789
# ------------------------------------------------------------#
# Server and SecurityGroup
# ------------------------------------------------------------#
# Create EC2 and SecurityGroup for Bastion-server
BastionSV:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-09d28faae2e9e7138
InstanceType: t3.micro
SubnetId: !Ref PublicSubnet01
KeyName: Key-Pair-Name
SecurityGroupIds:
- !Ref BastionSG
Tags:
- Key: Name
Value: !Sub ${sysname}-Bastionsv
- Key: Project
Value: !Ref sysname
BastionSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: sg for bastion server
GroupName: !Sub ${sysname}-bastionsg
VpcId: !Ref VPCID
SecurityGroupIngress:
- IpProtocol: tcp
CidrIp: 172.XXX.XXX.0/23
FromPort: 22
ToPort: 22
- IpProtocol: tcp
CidrIp: 122.XXX.XXX.XXX/32
FromPort: 22
ToPort: 22
Tags:
- Key: Name
Value: !Sub ${sysname}-bastionsg
- Key: Project
Value: !Ref sysname
EIP02:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
InstanceId: !Ref BastionSV
Tags:
- Key: Name
Value: !Sub ${sysname}-eip02
- Key: Project
Value: !Ref sysname
# Create EC2 and SecurityGroup for Web-server
WebSV:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-059b6d3840b03d6dd
InstanceType: m5.xlarge
SubnetId: !Ref PrivateSubnet01
KeyName: Key-Pair-Name
BlockDeviceMappings:
- DeviceName: "/dev/sda1"
Ebs:
VolumeSize: 100
VolumeType: gp2
SecurityGroupIds:
- !Ref WebSG
Tags:
- Key: Name
Value: !Sub ${sysname}-websv
- Key: Project
Value: !Ref sysname
WebSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: sg for web server
GroupName: !Sub ${sysname}-websg
VpcId: !Ref VPCID
SecurityGroupIngress:
- IpProtocol: tcp
CidrIp: 172.XXX.XXX.0/23
FromPort: 22
ToPort: 22
- IpProtocol: tcp
SourceSecurityGroupId: !Ref BastionSG
FromPort: 22
ToPort: 22
- IpProtocol: tcp
SourceSecurityGroupId: !Ref ELBSG
FromPort: 8080
ToPort: 8080
Tags:
- Key: Name
Value: !Sub ${sysname}-websg
- Key: Project
Value: !Ref sysname
# ------------------------------------------------------------#
# Elastic Load Balancer (Application Load Balancer)
# ------------------------------------------------------------#
# Create ELB(ALB)
ELBinalb:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Sub ${sysname}-ELBinalb
Scheme: internal
Subnets:
- !Ref PrivateSubnet01
- !Ref PrivateSubnet02
SecurityGroups:
- !Ref ELBSG
Tags:
- Key: Name
Value: !Sub ${sysname}-ELBinalb
- Key: Project
Value: !Ref sysname
ELBinalbTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: !Sub ${sysname}-ELBinalbtargetgroup
VpcId: !Ref VPCID
Port: 8080
Protocol: HTTP
Targets:
- Id: !Ref WebSV
Tags:
- Key: Name
Value: !Sub ${sysname}-ELBinalbtargetgroup
- Key: Project
Value: !Ref sysname
ELBSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: sg for ELB
GroupName: !Sub ${sysname}-elbsg
VpcId: !Ref VPCID
SecurityGroupIngress:
- IpProtocol: tcp
CidrIp: 0.0.0.0/0
FromPort: 443
ToPort: 443
- IpProtocol: tcp
SourceSecurityGroupId: !Ref BastionSG
FromPort: 443
ToPort: 443
Tags:
- Key: Name
Value: !Sub ${sysname}-elbsg
- Key: Project
Value: !Ref sysname
# Attach SSL Certificate for ALB
ELBinalbListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn: !Ref ELBinalb
Port: 443
Protocol: HTTPS
Certificates:
- CertificateArn: !Ref ACMArnToELBin
DefaultActions:
- Type: forward
TargetGroupArn: !Ref ELBinalbTargetGroup
ELBinalbListenerCertificate:
Type: AWS::ElasticLoadBalancingV2::ListenerCertificate
Properties:
Certificates:
- CertificateArn: !Ref ACMArnToELBin
ListenerArn: !Ref ELBinalbListener
変数やIPアドレスを、きちんとした値に置き換えればスタックを作成できるはずです。
最後まで読んでくださってありがとうございました!
コメント