はじめに
こんにちは、GxPの@ttanaka-gxpです。
この記事はグロースエクスパートナーズ Advent Calendar 2021の16日目です。
今回は以前から興味のあったAWS Copilotについて、ちょうど業務の中でECS上で動作させるAPIを構築する作業があり、そちらで構築しながら試させてもらった内容を、サンプルアプリも交えつつまとめようと思います。
AWS Copilot CLIについて
公式から抜粋
Copilot CLI は、AWS App Runner、Amazon ECS および AWS Fargate を利用したプロダクションレディなコンテナ化されたアプリケーションのビルド、リリース、そして運用のためのツールです。 開発のスタートからステージング環境へのプッシュ、そして本番環境へのリリースまで、Copilot はアプリケーション開発ライフサイクル全体の管理を容易にします。
具体的にはソースコードとそれを動作させるためのDockerfileがあれば、いくつかのコマンド実行と定義ファイルの修正だけでコンテナアプリケーションとして動作させるための一通りのインフラ構築やCI/CDパイプラインの構築など行ってくれるツールです。
環境構築
- 実行環境はWindows10
-
リリースノートから
copilot-windows.exe
をダウンロードし、copilot.exe
にリネームしてパスを通すだけです。 - 公式のインストール手順はこちら
- Home brewでのインストールが可能
- Windows、Mac、Linuxと主要な環境にてバイナリをダウンロードしてインストールも可能
AWS Copilotに登場する概念
主要な概念
概念 | 内容 |
---|---|
Applications | その他の概念をまとめる外枠のようなもの |
Environments | 開発環境、検証環境、本番環境といったServiceやJobが動作する環境 EnvironmentはVPC単位で分離される |
Services | AWS上で動作させたい1サービスを表す Copilotで一番重要な概念です 4つタイプが選べるので詳細は後述します |
Jobs | AWS上で動作させたい1つのバッチジョブを表す 具体的にはイベント起動のECSタスク(現時点ではスケジュール実行のみ) 今回は触れません |
Pipelines | 各EnviromentのService、Jobに対するビルド・デプロイを実現するCI/CDパイプラインを表す 具体的にはソースステージ、ビルドステージ、デプロイステージの3層構造のCode Pipeline 今回は触れません |
概念の関連性
例としてWebアプリとバッチアプリが動作するシステムで、それぞれ開発環境、本番環境があり、各アプリに対するCICDが行える構成の場合におけるイメージ図です。(あくまで例でサンプルアプリではJobとPipelineには触れてません)
Servicesで選択できる4つのタイプについて
タイプ | 構成 | 用途 |
---|---|---|
Request-Driven Web Service | App Runner | インターネットからアクセス可能なサービスの構築 ※1 |
Load Balanced Web Service | ALB + ECSサービス | インターネットからアクセス可能なサービスの構築 ※1 |
Backend Service | Service Discovery + ECSサービス | インターナルなアクセスを想定したサービスの構築 |
Worker Service | SQS + ECSサービス | Pub/Subアーキテクチャにおけるサブスクライバーの構築 |
※1 Request-Driven Web ServiceとLoad Balanced Web Serviceの違いについて
Request-Driven Web Serviceは、App Runner自体がロードバランサ―やオートスケール周りの機能を包括して提供しており手軽にスケーラブルな構成を構築できます。
ただし、WAFが適用できなかったり、カスタマイズできる範囲が限られていたりするので、細かい設定を行いたい場合はLoad Balanced Web ServiceでALB+ECSサービスの構成の方が向いているかと思います。
Load Balanced Web Serviceで構築する
完成後のインフラ構成図
api1とapi2は同じSpringアプリをデプロイ、環境変数を読んでHTMLに表示するサンプル実装になってます。
後述のCopilot関連の定義ファイルも含んだサンプル実装はこちらにPushしてあります。
Application作成
copilot app init copilot-demo --resource-tags owner=t.tanaka
--resource-tags
を指定しておくと、Environment、Serviceで作成されるすべてのリソースにも一律タグを付けられるので便利です。
Environment作成
copilot env init --name dev --app copilot-demo --default-config
dev環境を作成しています。
この時点でVPC周辺のリソース、ECSクラスターが作成されます。(色々作るので数分かかる)
Serviceを2つ作成
copilot svc init --name api1 --app copilot-demo --dockerfile ./Dockerfile --port 8080 --svc-type "Load Balanced Web Service"
copilot svc init --name api2 --app copilot-demo --dockerfile ./Dockerfile --port 8080 --svc-type "Load Balanced Web Service"
api1、api2という名前のServiceを作成しています。
この時点でAWS上にService毎のECRが作成され、ローカルにServiceの詳細を定義するための定義ファイルが生成されます。
.\copilot\${service名}\manifest.yml
manifest.ymlを編集
修正後のapi1のmanifest.yml
name: api1
type: Load Balanced Web Service
http:
path: 'api1'
healthcheck: '/api1/health'
image:
build: Dockerfile
port: 8080
cpu: 256 # ECSタスクに割り当てるCPUユニット
memory: 512 # ECSタスクに割り当てるメモリ
count: 1 # サービスの必要タスク数
network:
vpc:
placement: private # タスクをプライベートサブネットに配置する(デフォルトはパブリック)
variables: # 環境変数を定義する
APP_NAME: api1 # Service毎にHTMLの表示を変えるための環境変数
dev環境へデプロイする
copilot svc deploy --name api1 --env dev
copilot svc deploy --name api2 --env dev
※Dockerビルドも行われるのでアプリのビルド成果物を取り込む場合は事前にビルドしておくこと。
コマンド毎に下記が行われます。(10分位かかります)
- ローカルでdockerビルドしてECRへプッシュ
- ALB、ターゲットグループ、ECS周りのリソース作成
- タスクが起動し先ほどプッシュしたイメージがデプロイ
お掃除
copilot app delete
Are you sure you want to delete application copilot-demo? [? for help] (Y/n)
と聞かれるのでy
を入力するとcopilot-demo
のApplicationに紐づくEnviroment、Serviceが一式削除されます。
使った上で気になった点
ALBのIP制限に制約がある
manifest.ymlにhttp.allowed_source_ips
という設定がありALBに対するIP制限を設定可能だが、これはALBのリスナールールの送信元IPを条件として設定しています。
リスナールールは、IP以外の条件ルールも設定可能だが1つのルールに設定可能な条件は5つまでという制約があり、Copilotでデプロイした場合のデフォルトで、manifest.ymlに定義したhttp.path
のルートとルート以降で2つがパス条件として使われ、DNSを設定している場合は更にホストヘッダーも条件として登録されるため、実質2つしかIP条件として設定できないことになります。
下記スクショの赤枠がデフォルトで設定される条件です。
(案件で構築時の設定なのでGithubのサンプルとは差異があります)
単純にIPによるアクセス制限を入れいたいだけであればセキュリティグループで設定したいところですが、私が試したCopilotのバージョン時点ではALBのセキュリティグループに制限を入れる方法はありませんでした。
(案件の時はIPが2つだと足りなかったので手でセキュリティグループに制限を追加しています)
エラー時はCloudFormationのスタックを見る
manifest.ymlで構文エラーはコンソールに表示されるログから原因がわかるので対応できるのですが、デプロイ先環境にてリソース作成上限にあったってしまうような、実行してみないとわからない類のエラーについては正直コンソールのログだけでは詳細が分からないことが何度かありました。
プライベートサブネットにタスクを配置する構成の場合、パブリックサブネットにNAT Gatewayがデプロイされるのですが、下記はNAT GatewayにアタッチするEIPがリソース作成上限に引っかかってしまった際のログです。
赤文字のところでエラーになっているのはわかるが、詳細が分かりません。
CloudFormationのスタックを見てみたところThe maximum number of addresses has been reached.
と出ており、この記事にたどり着き原因がわかりました。
他にも大阪リージョンにデプロイしようとしてCloudMapがリージョンとしてサポートされていなく、デプロイできなかった時などもスタックのログを見ることで大体のエラーの解消出来ました。
カスタムドメインを設定したい場合はapp init時に指定する必要がある
カスタムドメインを設定する場合は下記の設定が必要です。(公式はこちら)
- app init時のパラメータで
--domain ${カスタムドメイン}
を渡して実行 - manifest.ymlに
http.alias
にカスタムドメインを指定
app init時にドメインの指定をしなかった場合はapp deleteしてinitし直す必要があるようです。
最初manifest.ymlの書き方しか見ていなく作り直すことになりました…
ただ、deleteしてもmanifest.ymlは残り、svc initしても既に同じService名でフォルダがある場合はmanifest.ymlを上書きしないので、そのまま流用して再作成できます。
まとめ
CloudFormationやTerraformといったIaCツールで1から定義する場合、結構な労力が必要になるので数コマンドで一通りの環境が構築できるのはかなり手軽に感じています。
トラブルシューティングで、AWS全般の知識はある程度必要になるかと思いますが、IaCや手で構築するにしても必要なレベルの知識だと思うのでそこまで大きな問題ではないと思います。
ただ、何度かスクラップ&ビルドで作り直したりしたのでいくら同じ定義で環境が作れるとしても、本番環境で扱うのはまだちょっとリスクがありそうな印象です。
あと、ALB周りの設定をもう少し弄れるようになってほしいなというのが個人的な感想です。
今回、案件で作った検証環境ではDBが不要だったので触れませんでしたが、RDS、S3、DynamoDBといったストレージ周りと連携する機能やCICDを構築する機能などがあり、本格的に使っていくならこの辺りも検証して記事にしていきたいと思います。(きっと…)