本連載では、以下のイメージの構成にあるAWSリソース基盤自動化環境の構築を実践しています。
前回は、各AWSリソースへ設定するセキュリティグループ、FrontendサブネットにアタッチするNATGatewayを構築するテンプレートを実装しました。続く今回は、Frontend/Backendサブネットに配置するアプリケーションロードバランサー(ALB)を構築するCloudFormationテンプレートを作成します。
なお、実際のソースコードはGitHub上にコミットしています。以降のソースコードでは本質的でない記述を一部省略しているので、実行コードを作成する場合は、必要に応じて適宜GitHub上のソースコードも参照してください。
ALBスタック構築テンプレート
ALBは、連載「AWSで作るクラウドネイティブアプリケーションの基本」の第5回で実施したのと同じ要領で、FrontendサブネットとBackendサブネット双方に構築します。CloudFormationで構築する場合、リソースタイプが「AWS::ElasticLoadBalancingV2::LoadBalancer」となるロードバランサー自体の定義と、リクエストフォワード先の対象となるターゲットグループ定義「AWS::ElasticLoadBalancingV2::TargetGroup」、リクエストを受け付けるリスナー定義「AWS::ElasticLoadBalancingV2::Listener」、およびURLパスパターンに応じたルーティング定義などを行うルール「AWS::ElasticLoadBalancingV2::ListenerRule」の設定が必要になります。プロパティとして設定可能な属性は、各リンク先の通りです。 今回は、実装するテンプレートを以下のように2つに分けて実装してみます。
- ベースとなるALBおよびデフォルトターゲットグループとリスナー定義
- サービスアプリケーションごとに追加するターゲットグループ、リスナールール定義
1つのテンプレートで全ての定義をひとまとめにしておくのもシンプルでわかりやすくて良いのですが、サービスアプリケーションを追加する必要が生じるケースなどもあるので、リソース定義を再利用して追加対象だけを再実行すればよいテンプレート構成にしておいたほうが効率的です。また、同様の理由から実装の際、開発環境は「EnvType」としてパラメータ化しておきます。
まず、ALBとデフォルトターゲットグループ、リスナー定義のテンプレートのサンプルは以下の通りです。
AWSTemplateFormatVersion: '2010-09-09'
Mappings: #(A)
DeployEnvironmentMap:
Production:
"Protocol": "HTTPS"
"Port": 443
Staging:
"Protocol": "HTTP"
"Port": 80
Dev:
"Protocol": "HTTP"
"Port": 80
Parameters:
// omit
EnvType: #(B)
Description: Which environments to deploy your service.
Type: String
AllowedValues: ["Dev", "Staging", "Production"]
Default: Dev
FrontendHealthCheckPath: #(C)
Description: Frontend WebApp health check path
Type: String
MinLength: 1
MaxLength: 255
AllowedPattern: ^[-\.\/a-zA-Z0-9]*$
Default: /frontend/portal
BackendDefaultHealthCheckPath: #(D)
Description: Backend Service Default health check path
Type: String
MinLength: 1
MaxLength: 255
AllowedPattern: ^[-\.\/a-zA-Z0-9]*$
Default: /backend/api/v1/healthcheck
Resources:
FrontendALB: #(E)
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: FrontendALB
Subnets:
- Fn::ImportValue: !Sub ${VPCName}-PublicSubnet1
- Fn::ImportValue: !Sub ${VPCName}-PublicSubnet2
SecurityGroups:
- Fn::ImportValue: !Sub ${VPCName}-SecurityGroupFrontendALB
FrontendALBDefaultTargetGroup: #(F)
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: FrontendALBTargetGroup
VpcId:
Fn::ImportValue: !Sub ${VPCName}-VPCID
Port: !FindInMap [DeployEnvironmentMap, !Ref EnvType, Port] #(G)
Protocol: !FindInMap [DeployEnvironmentMap, !Ref EnvType, Protocol]
HealthCheckPath: !Ref FrontendHealthCheckPath
HealthCheckIntervalSeconds: 60
HealthyThresholdCount: 2
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: '60'
FrontendALBListener: #(H)
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn: !Ref FrontendALB
Port: !FindInMap [DeployEnvironmentMap, !Ref EnvType, Port]
Protocol: !FindInMap [DeployEnvironmentMap, !Ref EnvType, Protocol]
DefaultActions:
- Type: forward
TargetGroupArn: !Ref FrontendALBDefaultTargetGroup
BackendALB: #(I)
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: BackendALB
Scheme: internal
Subnets:
- Fn::ImportValue: !Sub ${VPCName}-PrivateSubnet1
- Fn::ImportValue: !Sub ${VPCName}-PrivateSubnet2
SecurityGroups:
- Fn::ImportValue: !Sub ${VPCName}-SecurityGroupBackendALB
BackendALBDefaultTargetGroup: #(J)
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: BackendALBDefaultTargetGroup
VpcId:
Fn::ImportValue: !Sub ${VPCName}-VPCID
Port: !FindInMap [DeployEnvironmentMap, !Ref EnvType, Port]
Protocol: !FindInMap [DeployEnvironmentMap, !Ref EnvType, Protocol]
HealthCheckPath: !Ref BackendDefaultHealthCheckPath
HealthCheckIntervalSeconds: 60
HealthyThresholdCount: 2
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: '60'
BackendALBListener: #(K)
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn: !Ref BackendALB
Port: !FindInMap [DeployEnvironmentMap, !Ref EnvType, Port]
Protocol: !FindInMap [DeployEnvironmentMap, !Ref EnvType, Protocol]
DefaultActions:
- Type: forward
TargetGroupArn: !Ref BackendALBDefaultTargetGroup
Outputs:
FrontendALB: #(L)
Description: Frontend ALB
Value: !Ref FrontendALB
Export:
Name: !Sub ${VPCName}-Frontend-ALB-${EnvType}
BackendALB: #(M)
Description: Backend ALB
Value: !Ref BackendALB
Export:
Name: !Sub ${VPCName}-Backend-ALB-${EnvType}
FrontendALBDNS: #(N)
Description: Public DNS Name
Value: !GetAtt FrontendALB.DNSName
Export:
Name: !Sub ${VPCName}-FrontendALBDNS-${EnvType}
BackendALBDNS: #(O)
Description: Private DNS Name
Value: !GetAtt BackendALB.DNSName
Export:
Name: !Sub ${VPCName}-BackendALBDNS-${EnvType}
FrontendALBDefaultTargetGroup: #(P)
Description: Frontend TagetGroup Default
Value: !Ref FrontendALBDefaultTargetGroup
Export:
Name: !Sub ${VPCName}-Frontend-ALB-DefaultTargetGroup-${EnvType}
BackendALBDefaultTargetGroup: #(Q)
Description: Backend TagetGroup Default
Value: !Ref BackendALBDefaultTargetGroup
Export:
Name: !Sub ${VPCName}-Backend-ALB-DefaultTargetGroup-${EnvType}
// omit
上記のテンプレートの記述の基本となるポイントは、以下の通りです。
記述 | 説明 |
---|---|
A | ALBでは通信で使うプロトコルを指定しますが、開発環境や商用環境でHTTP/HTTPSで分けて構成します。Mappings要素でそうした場合分けに応じてパラメータをまとめて定義しておくことで、見通しの良いテンプレート実装が可能です。なお、Mappingsの使用方法については表下の囲み記事「Mappingsについて」で後述します |
B | Aで定義したMappingsで使用するパラメータを引数で切り替えるためのパラメータを「EnvType」として定義しておきます。ここでは「AllowedValues」で、Mappingsのキーとしていた要素の入力のみを許可するパラメータ定義にしておきます |
C | フロントエンドサブネットに配置するALBのデフォルトターゲットグループに設定するヘルスチェックパスをパラメータ定義します。「AllowedPattern」の正規表現で英数文字と記号「-(ハイフン)」「.(ドット)」「/(スラッシュ)」を含めた任意の文字列を許可するよう定義します |
D | バックエンドエンドサブネットに配置するALBのデフォルトターゲットグループに設定するヘルスチェックパスをパラメータ定義します。AllowedPatternの正規表現で英数文字と記号「-(ハイフン)」「.(ドット)」「/(スラッシュ)」を含めた任意の文字列を許可するよう定義します |
E | フロントエンドサブネットに配置するALBの定義を行います。詳細は「AWS::ElasticLoadBalancingV2::LoadBalancer」を参考にしてください。サブネットやセキュリティグループは前回設定したテンプレートからクロススタックリファレンスで参照します |
F | フロントエンドサブネットALBのデフォルトターゲットグループの設定を行います。詳細は「AWS::ElasticLoadBalancingV2::TargetGroup」を参照してください。URLパスに応じて、ALBは設定したEC2やECSにフォワードできますが、それらの集合はターゲットグループとしてまとめられています。デフォルトターゲットグループは条件に一致するパスが存在しなかったときにフォワードされるEC2、ないしはECSアプリケーションのターゲットグループだと捉えてください |
G | フロントエンドALBからターゲットグループへフォワードする際のポートやプロトコルをAで定義したMapping要素を使って取得するために、FindInMap関数を使って取得します。なお、関数の第2引数のみに限り、Ref参照が可能で、「Parameters」で指定したパラメータ引数に応じて、設定値を切り替えることができます |
H | フロントエンドALBのリスナーを定義します。詳細は「AWS::ElasticLoadBalancingV2::Listener」を参考にしてください。リスナーはALBに対するリクエストにおけるプロトコルやポート、フォワードするターゲットグループなどを決定します。なお、オプションとしてはフォワードのほかに、リダイレクトや「Coginito」、「OpenIDConnect」を使った認証処理などもあります |
I | Eと同様、バックエンドエンドサブネットに配置するALBの定義を行います。詳細は「AWS::ElasticLoadBalancingV2::LoadBalancer」を参考にしてください。サブネットやセキュリティグループは前回設定したテンプレートからクロススタックリファレンスで参照します |
J | Fと同様、バックエンドエンドサブネットALBのデフォルトターゲットグループの設定を行います。詳細は「AWS::ElasticLoadBalancingV2::TargetGroup」を参照してください。URLパスに応じて、ALBは設定したEC2やECSにフォワードできますが、それらの集合はターゲットグループとしてまとめられています。デフォルトターゲットグループは条件に一致するパスが存在しなかったときにフォワードされるEC2、ないしはECSアプリケーションのターゲットグループだと捉えてください |
K | Hと同様、バックエンドALBのリスナーを定義します。詳細は「AWS::ElasticLoadBalancingV2::Listener」を参考にしてください。リスナーはALBに対するリクエストにおけるプロトコルやポート、フォワードするターゲットグループなどを決定します。なお、オプションとしてはフォワードのほかに、リダイレクトや「Coginito」、「OpenIDConnect」を使った認証処理などもあります |
L | Eで定義したフロントエンドALBをクロススタックリファレンスの(ほかのテンプレートに値を渡す)ために出力します |
M | Iで定義したバックエンドエンドALBをクロススタックリファレンスの(ほかのテンプレートに値を渡す)ために出力します |
N | Eで定義したフロントエンドALBのDNSをクロススタックリファレンスの(ほかのテンプレートに値を渡す)ために出力します |
O | Iで定義したバックエンドエンドALBのDNSをクロススタックリファレンスの(ほかのテンプレートに値を渡す)ために出力します |
P | Fで定義したフロントエンドALBのターゲットグループをクロススタックリファレンスの(ほかのテンプレートに値を渡す)ために出力します |
Q | Jで定義したバックエンドエンドALBのターゲットグループをクロススタックリファレンスの(ほかのテンプレートに値を渡す)ために出力します |
Mappingsについて
Mappingsは単純な連想配列(キーバリュー型のマップ)ではなく、もう1階層の構造が加わった2次元連想配列です。下記の例のように、Mappingsでは複数の2次元連想配列構造を取り、各マップごとに、複数のキーでさまざまなキーバリューデータを取得できるようになっています。
Mappings:
DeployEnvironmentMap:
Production:
"Protocol": "HTTPS"
"Port": 443
Staging:
"Protocol": "HTTP"
"Port": 80
Parameters:
EnvType:
Description: Which environments to deploy your service.
Type: String
AllowedValues: ["Dev", "Staging", "Production"]
Default: Dev
上記のデータを取得する際は、以下のようなFindInMap関数を使用します。
Port: !FindInMap [DeployEnvironmentMap, !Ref EnvType, Port]
ここでポイントとなるのは第2引数で、パラメータなどテンプレートの外から外部入力値に応じて、設定値を切り替えたい場合に効果を発揮します。
上記の例では、第1引数で参照するマッピング定義を選択し、第2引数はパラメータEnvTypeを参照しています。EnvTypeはパラメータ要素に定義されている項目でデフォルトは「”Dev”」ですが、実行時に「”Production”」などの値を引数にしてテンプレート実行することで、第3引数として設定する値を容易に切り替えられる仕組みになっています。
作成したテンプレートに対して、以下のようにスタック名とテンプレートパスを変更して、ヘルパースクリプトを実行します。パラメータはデフォルト値を利用するので省略します。
#!/usr/bin/env bash
stack_name="mynavi-sample-alb"
template_path="sample-alb-cfn.yml"
aws cloudformation deploy --stack-name ${stack_name} --template-file ${template_path} --capabilities CAPABILITY_IAM
実行が正常に終了すると、ALBとデフォルトターゲットグループ、リスナーが作成されます。
続いて、サービスアプリケーションごとに追加するターゲットグループとリスナールールのテンプレートを作成します。各サービスごとに追加すればよいので、パスパターンやヘルスチェックパス、条件適用のプライオリティなど、必要なパラメータをMappingsに定義します。なおその際、インプットパラメータに応じて、適用されるキーバリューデータが変わるように実装します。また、フロントエンド、バックエンドのいずれに配置するのかもオプションで選べるように作成しておきます。
AWSTemplateFormatVersion: '2010-09-09'
Mappings:
TargetGroupDefinitionMap: #(A)
FrontendWebApp:
"PathPattern": "/frontend/*"
"HealthCheckPath": "/frontend/healthcheck"
"Priority": 1
BackendUserService:
"PathPattern": "/backend/api/v1/user*"
"HealthCheckPath": "/backend/api/v1/healthcheck"
"Priority": 1
BackendSampleService:
"PathPattern": "/backend/api/v1/sample*"
"HealthCheckPath": "/backend/api/v1/healthcheck"
"Priority": 2
DeployEnvironmentMap: #(B)
Production:
"Protocol": "HTTPS"
"Port": 443
Staging:
"Protocol": "HTTP"
"Port": 80
Dev:
"Protocol": "HTTP"
"Port": 80
Parameters:
// omit
SubnetType: #(C)
Description: Which subnet to deploy your service.
Type: String
AllowedValues: ["Frontend", "Backend"]
Default: Backend
EnvType: #(D)
Description: Which environments to deploy your service.
Type: String
AllowedValues: ["Dev", "Staging", "Production"]
Default: Dev
ServiceName: #(E)
Description: Deploy service name
Type: String
MinLength: 1
MaxLength: 255
AllowedPattern: ^[a-zA-Z][-a-zA-Z0-9]*$
Default: BackendUserService
Resources:
TargetGroup: #(F)
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: !Sub ${EnvType}-${ServiceName}-tg
VpcId:
Fn::ImportValue: !Sub ${VPCName}-VPCID
Port: !FindInMap [DeployEnvironmentMap, !Ref EnvType, Port]
Protocol: !FindInMap [DeployEnvironmentMap, !Ref EnvType, Protocol]
HealthCheckPath: !FindInMap [TargetGroupDefinitionMap, !Ref ServiceName, HealthCheckPath]
HealthCheckIntervalSeconds: 60
HealthyThresholdCount: 2
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: '60'
ListenerRule: #(G)
Type: AWS::ElasticLoadBalancingV2::ListenerRule
Properties:
Actions:
- Type: forward
TargetGroupArn: !Ref TargetGroup
Conditions:
- Field: path-pattern
PathPatternConfig:
Values:
- !FindInMap [TargetGroupDefinitionMap, !Ref ServiceName, PathPattern]
ListenerArn:
Fn::ImportValue: !Sub ${VPCName}-${SubnetType}-ALB-Listener-${EnvType}
Priority: !FindInMap [TargetGroupDefinitionMap, !Ref ServiceName, Priority]
Outputs: #(H)
TargetGroup:
Description: TargetGroup Service
Value: !Ref TargetGroup
Export:
Name: !Sub ${VPCName}-${SubnetType}-${ServiceName}-TargetGroup-${EnvType}
上記のテンプレートの記述のポイントは、以下の通りです。
記述 | 説明 |
---|---|
A | フロントエンドに配置するWebアプリケーションやバックエンドサービスごとにフォワード先を決定するパスパターンや、ヘルスチェックパス、パスマッチングのプライオリティをMappingsで定義します |
B | ベースとなるALBの定義と同様、デプロイ環境に応じてプロトコルやポートをMappingsで定義します |
C | ターゲットグループやリスナーを設定するALBがあるサブネットをパラメータとして設定できるよう、Parameters要素に定義します |
D | ベースとなるALBの定義と同様、開発環境や商用環境でHTTP/HTTPSにするか入力パラメータで切り替えるために、Parameters要素に定義します |
E | 入力パラメータに応じて、Aで定義した設定値を切り替えられるように、Parameters要素に定義します |
F | 追加するターゲットグループを定義します。詳細は「AWS::ElasticLoadBalancingV2::TargetGroup」を参照してください。なお、定義はFindInMap関数で入力パラメータに応じて動的に切り替えて設定されます |
G | URLパスパターンやフォワードのルール定義を行います。詳細は、「AWS::ElasticLoadBalancingV2::ListenerRule」を参照してください。なお、定義はFindInMap関数で入力パラメータに応じて動的に切り替えて設定されます |
H | Fで定義したターゲットグループをクロススタックリファレンスの(ほかのテンプレートに値を渡す)ために出力します |
作成したテンプレートに対して、以下のようにスタック名とテンプレートパスを変更してヘルパースクリプトを実行します。
#!/usr/bin/env bash
stack_name="mynavi-sample-tg-userservice"
template_path="sample-tg-cfn.yml"
parameters="SubnetType=Backend EnvType=Dev ServiceName=BackendUserService"
aws cloudformation deploy --stack-name ${stack_name} --template-file ${template_path} --parameter-overrides ${parameters} --capabilities CAPABILITY_IAM
実行が正常に終了すると、ターゲットグループとリスナールールが作成されます。作成するアプリケーションやサービスに応じて、サブネット/サービス名を変更して複数回パラメータを変えて実行してみてください。
以上、今回はMappings要素を使いながら、ALBおよびサービスアプリケーションごとのターゲットグループとリスナーをCloudFormationテンプレートで構築しました。次回は、RDSを構築するスタックテンプレートを作成します。
著者紹介
川畑 光平(KAWABATA Kohei) - NTTデータ 課長代理
金融機関システム業務アプリケーション開発・システム基盤担当を経て、現在はソフトウェア開発自動化関連の研究開発・推進に従事。
Red Hat Certified Engineer、Pivotal Certified Spring Professional、AWS Certified Solutions Architect Professional等の資格を持ち、アプリケーション基盤・クラウドなどさまざまな開発プロジェクト支援にも携わる。2019 APN AWS Top Engineers & Ambassadors選出。
本連載の内容に対するご意見・ご質問は Facebook まで。