AWS Protonを実践してFargate＋Rails環境を構築する

以前のこちらの記事でAWS Protonを試しましたので、今回は実践編としてウェブアプリケーションを構築していきます。

https://qiita.com/goosys/items/ef66274663b3552be36f

# 目標

- Railsのアプリケーションをデプロイできる
- SSL
  - Protonで証明書の作成はしない
  - 別途行っておいてArnだけ設定する形 
- WAF
- RDS(Aurora MySQL)
  - パスワード自動生成 & SecretManagerに保管
  - 削除保護の有無を選択可
- ElastiCache(Redis)
  - sidekiqから利用
- Fargate
  - Container Insightsの有無を選択可
  - AutoScallingを設定可
  - ヘルスチェック
- コスト配分タグの付与（要所のみ）
- アプリケーションは Githubへpush → ビルド＆デプロイ → Slackへ通知のフロー
- テスト環境・ステージ環境構築時には課金を少な目にできる気配り

# 出来たもの

今回作成したテンプレートは、案件情報をマスクした上でProtonの公式サンプルに上書きして置いてあります。

https://github.com/goosys/aws-proton-sample-templates/tree/myproject

https://github.com/aws-samples/aws-proton-cloudformation-sample-templates/compare/main...goosys:myproject?expand=1

# 環境テンプレート

環境テンプレートはざっくり以下のような構成になりました。

![AWS Proton-Proton Environment.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64098/08dfa844-fc63-467b-0d30-766a5ec9beb6.png)

https://github.com/goosys/aws-proton-sample-templates/blob/myproject/loadbalanced-fargate-svc/loadbalanced-fargate-env/v1/infrastructure/cloudformation.yaml

## パラメーター

パラメーターで以下のように設定・選択することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64098/56c85a51-60aa-8498-0689-1ef53b32987b.png)

| パラメーター | 値 | |
| -- | -- | -- |
| CertificateManagerArn | Arn | SSL証明書のArn<br>ALBのHTTPS Target Groupに設定される |
| Elastic IPが必要かどうか | Yes/No | Yesであれば各PrivateSubnetにNATGatewayを置いてElasticIPを紐づける |
| DBエンジンモード | Serverless/Provisioned | Amazon Aurora Clusterのエンジンモード<br>本番用: Provisioned<br>テスト用: Serverless※1 |
| DBインスタンスクラス | small/medium/large | Provisioned選択時のインスタンスタイプ |
| DBキャパシティ | AutoPause/low/high | Serverless選択時のキャパシティ<br>※AutoPauseを選択すると一定時間アクセスがない場合は自動的に停止し課金もなくなる |
| Container Insightsの有効化 | enabled/disabled | FargateのContainer Insightsを有効化します |
| 削除保護が必要かどうか | Yes/No | YesにするとDBに削除保護を付与（YesのままでProtonから環境を削除しても失敗するので注意）|

使い分けとしては、以下のようなイメージです。

- テスト環境では**Serverless & AutoPause & Container Insightなし** で課金節約、削除保護なし
- 本番環境では**Provisioned & リリース初期はsmall、削除保護あり**
- Elastic IPはどうしても必要な案件のときだけ利用する（NATGatewayが高価なため）

また、今回はRoute53系やドメイン系は自動構築していません。
ドメインの取得がRoute53でされるかは案件によるのと、一回取得したドメインのサブドメインで次のサービスを作ることもあり、テンプレート化が複雑になるためです。
同様にSSL証明書も手作業で取得したもののArnをパラメーターで入力する形にしています。

## サービスインスタンス

サービスインスタンスはこのようになりました。

![template1-designer (11).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64098/b292e705-f44d-1c9d-54a8-da3a338d48f9.png)

https://github.com/goosys/aws-proton-sample-templates/blob/myproject/loadbalanced-fargate-svc/loadbalanced-fargate-svc/v1/instance_infrastructure/cloudformation.yaml

### ALB

ALBは、Protonの公式のサンプルを試したときはサービスインスタンス毎に作られていましたが、
今回は環境の方で構築済みのALB Listenerに対してサービスインスタンスで作成したFargateのServiceを登録（TargetGroupを作成して新規のListener Ruleに紐づけてListener Rulesに追加）していくようにしています。
この形だと、同環境内で別のサービスインスタンスを立てるときも同じALBを使えるため課金を抑えられるかなという目論見です。

### パラメーター

サービスインスタンスも、パラメーターで以下のように設定・選択することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64098/263e48c1-8d11-2b27-5c52-e8c8f3b5420b.png)

| パラメーター | 値 | |
| -- | -- | -- |
| サービスのドメイン | ドメイン | S3のCORSを設定したり、<br>Railsの`config.hosts`の設定に利用する |
| Fargateのタスクサイズ | small/medium/large | smallだとcpu: 512, memory: 1024 など |
| Autoscalling キャパシティ<br>（最小～最大） | 1～10 | |
| ビルドしたイメージのECR Arn | Arn | ※パイプラインから更新されるため手動設定不要 |
| Railsのログレベル | DEBUG～FATAL | |
| メンテナンスモード | YES/NO | |

### メンテナンスモード

メンテナンスモードに変更すると、
アプリケーションのコンテナを起動せずに、Nginxイメージでコンテナを起動します。
ソースコードのリポジトリの用意が出来ていない時期にインフラだけ作って疎通確認したいときなどに使用します。


# サービスパイプライン

パイプラインはビルドコマンドを少々変更しただけで公式サンプルほぼそのままなので割愛します。

https://github.com/goosys/aws-proton-sample-templates/blob/myproject/loadbalanced-fargate-svc/loadbalanced-fargate-svc/v1/pipeline_infrastructure/cloudformation.yaml

# Jinjaテンプレート

Protonのテンプレートでは [Jinja](https://jinja.palletsprojects.com/en/3.1.x/) というテンプレート構文でちょっとした文字列演算などができます。
構文は `{{ pythonのコード }}` となっています。
あまり使いどころもないですが、あると地味に便利だった箇所を抜粋してみます。

```yaml
# 「8時間」を指定したいけど、「秒」に換算するのがめんどいとき
SecondsUntilAutoPause: {{ 60 * 60 * 8 }}
```

```yaml
# Mappings内での文字列結合や演算（通常のCloudFormationではこの中ではJoin等が使えない）
Mappings:
  EnvironmentNameConfig:
    Environment:
      Name: '{{ environment.name}}'
  ResourceTagConfig:
    Project:
      Key: 'Cost:Project'
      Value: '{{ environment.inputs.project_base_name }}'
    Service:
      Key: 'Cost:Service'
      Value: '{{ environment.inputs.project_base_name }}-{{ environment.inputs.project_short_name }}'
    Env:
      Key: 'Cost:Env'
      Value: '{{ {"stg": "staging", "prod": "production"}[environment.inputs.service_environment] }}'
```

```yaml
# 公式サンプルにあった黒魔術
      # Note that the Name property has a 32 character limit, which could be
      # reached by using either {{service.name}}, {{service_instance.name}}
      # or a combination of both as we're doing here, so we truncate the name to 29 characters
      # plus an ellipsis different from '...' or '---' to avoid running into errors.
      Name: '{{(service.name~"--"~service_instance.name)|truncate(29, true, 'zzz', 0)}}'
```

# おわり

以上でアプリケーションは継続デプロイできるようになりました。
Protonの方も、Protonの画面上からポチポチとテンプレートのリポジトリを紐づけるだけで各種設定が完了し、Githubが更新されたら自動的に最新バージョンのDraftを用意してくれるようになります。あとはまたポチポチしていけばマイナーバージョンが切り替わるのでとても便利でした。

ただ、テンプレート名とメジャーバージョン番号をリポジトリのディレクトリ名にしておかないといけない仕様が全然Gitと相性が良くないなとは感じました。リネームしたら変更履歴追いにくいじゃん。という
この点はAWSにフィードバックを送信してみたので、今後良くなればなと願っています。