【AWS】Internal ALBをCFnで作成してみる

CloudFormationでInternal ALBを作成してみたので、備忘録として書いておきます。


# 構築するリソース

## 前提

- EC2は既に構築済み
- 証明書はACMで作成済み
- ALBでTLS終端
- 構築するALBはInternal

## リソース

- Internal ALB
    - TargetGroup
        - ターゲットはEC2インスタンス
    - Listener
        - HTTP (HTTPSへのリダイレクト)
        - HTTPS
    - SecurityGroup

# CFnテンプレート

```yaml:alb.yml
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
  AlbName:
    Type: String
  VpcId:
    Type: AWS::EC2::VPC::Id
  TargetGroupName:
    Type: String
  SubnetId1:
    Type: AWS::EC2::Subnet::Id
  SubnetId2:
    Type: AWS::EC2::Subnet::Id
  ListenerCertificateArn:
    Type: String
  Ec2InstanceId:
    Type: AWS::EC2::Instance::Id
  AlbSecurityGroupName:
    Type: String
  AlbListenerSslPolicy:
    Type: String

Resources:
  AlbSecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupName:
        Ref: AlbSecurityGroupName
      GroupDescription: "this is test"
      SecurityGroupIngress:
        - CidrIp: "0.0.0.0/0"
          FromPort: 80
          IpProtocol: tcp
          ToPort: 80
        - CidrIp: "0.0.0.0/0"
          FromPort: 443
          IpProtocol: tcp
          ToPort: 443
      VpcId:
        Ref: VpcId


  # EC2をターゲットにする場合はTargetTypeをinstanceにする
  # TargetsのIdにEC2インスタンスIDを指定
  AlbTargetGroup:
    Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
    Properties:
      HealthCheckEnabled: true
      HealthCheckPath: /health.php
      Name:
        Ref: TargetGroupName
      Port: 80
      Protocol: HTTP
      Targets:
        - Id:
            Ref: Ec2InstanceId
          Port: 80
      TargetType: instance
      VpcId:
        Ref: VpcId

  # ALBなのでTypeにapplicationを指定
  # InternalなのでSchemeにinternalを指定
  InternalAlb:
    Type: "AWS::ElasticLoadBalancingV2::LoadBalancer"
    Properties:
      IpAddressType: ipv4
      Name:
        Ref: AlbName
      Scheme: internal
      SecurityGroups:
        - Ref: AlbSecurityGroup
      Subnets:
        - Ref: SubnetId1
        - Ref: SubnetId2
      Type: application

  # HTTPで受信したアクセスはHTTPSへリダイレクト
  # RedirectConfigでリダイレクト先を編集できる
  # 今回はリダイレクトする際のStatusCodeは301か302を選択できるが、違いはあれど好みの問題かなと個人的に思っている
  AlbListenerHttp:
    Type: "AWS::ElasticLoadBalancingV2::Listener"
    Properties:
      DefaultActions:
        - RedirectConfig:
            Port: 443
            Protocol: HTTPS
            StatusCode: HTTP_301
          Type: redirect
      LoadBalancerArn:
        Ref: InternalAlb
      Port: 80
      Protocol: HTTP

  # HTTPSのListenerは証明書が必要なので必ずCertificateArnを指定
  # HTTPSのアクセスの場合はターゲットグループに転送する = EC2にトラフィックを転送
  AlbListenerHttps:
    Type: "AWS::ElasticLoadBalancingV2::Listener"
    Properties:
      Certificates:
        - CertificateArn:
            Ref: ListenerCertificateArn
      DefaultActions:
        - TargetGroupArn:
            Ref: AlbTargetGroup
          Type: forward
      LoadBalancerArn:
        Ref: InternalAlb
      Port: 443
      Protocol: HTTPS
      SslPolicy: AlbListenerSslPolicy
```

# Parameter管理ファイル

以下はParametersを管理するためのファイルです。ファイルで管理しておけばどのような設定をデプロイしたか覚えておく必要はありません。忘れたらこのファイルを見ればいいのですから。

```ini:alb.stg.ini
AlbName=
VpcId=
TargetGroupName=
SubnetId1=
SubnetId2=
ListenerCertificateArn=
Ec2InstanceId=
AlbSecurityGroupName=
AlbListenerSslPolicy=
```

# デプロイ用シェルスクリプト

CFnのパラメータはiniファイルで管理し、かつコマンドライン引数からファイル名を割り出すためにiniファイルの命名規則を決めています。こうすればコマンドライン引数を多くせず使い回すことができます。
また、`$(find $base_dir -type f -name "*.yml" -exec basename {} .yml \;)`でCFnのテンプレートファイルが集約されていれば一覧を表示することができます。わざわざ書かなくても良かったんですが、ちょっとした遊び心ですw
aws cloudformation deployのオプションは[こちら](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/deploy/index.html)に詳細が書かれていますので参照してください。

```bash:deploy.bash
#!/bin/bash

# -----------------------------------------------------------
# CloudFormationを実行
#
# Usage:
# bash deploy.bash $profile $stage $service
#
# parameter-overridesのファイル命名規則 = $service.$stage.ini
# ex) alb.stg.ini (ステージングのALB用の設定ファイル)
#
# -----------------------------------------------------------

profile="$1"
service="$2"
stage="${3:-stg}"
base_dir="$(cd $(dirname $0); pwd)"


if [ -z "$profile" ]; then
  echo "profileを指定してください"
  exit 1
fi

if [ -z "$service" ]; then
  echo "サービス名を以下から選択してください"
  echo "$(find $base_dir -type f -name "*.yml" -exec basename {} .yml \;)"
  exit 1
fi

stack_name="$service-$stage"
params="$service.$stage.ini"

aws cloudformation deploy \
  --profile $profile \
  --region ap-northeast-1 \
  --template-file $base_dir/$service.yml \
  --stack-name $stack_name \
  --parameter-overrides $(cat $base_dir/$params | tr '\n' ' ') \
  --no-fail-on-empty-changeset
```

# Reference

- https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_ElasticLoadBalancingV2.html
- https://docs.aws.amazon.com/cli/latest/reference/cloudformation/deploy/index.html