AWSの環境構築をマネジメントコンソールから行うと、画面操作に多くの時間がかかったり、ミスが発生してしまうことが課題だと感じていました。また、手順をドキュメント化するのに掛かるコストも大きいです。
そのため、コマンドラインインターフェース(AWS CLI, ECS CLI)を用いてECSを構築してみます。
また、Windowsバッチ化も行ってみます。(正常系のみ考慮します。)
バッチを先に確認したい方は、こちらです。
構成図
前提条件
- Windows10 環境
- AWS アカウント
- AWS にプログラムからアクセス権限があるユーザー
- 依存ツール
- jq ※バッチファイルのみに使用
- Docker Desktop for Windows
- AWS CLI 2.2.18以上
- ECS CLI (インストール方法の説明あり)
ツールのバージョン
jq --version
jq-1.6
docker -v
Docker version 20.10.7, build f0df350
aws --version
aws-cli/2.2.27 Python/3.8.8 Windows/10 exe/AMD64 prompt/off
ecs-cli --version
ecs-cli version 1.21.0 (bb0b8f0)
環境構築
ECS CLIのインストールから説明します。
※Docker Desktop、AWS CLI、jqのインストールは省略します。
ECS CLIのインストール
ECS CLIのインストールのみ、Windows PowerShell で実行してください。
Amazon ECS CLI をダウンロード
ECS構築に使用するECS CLIをダウンロードします。
New-Item -Path 'C:\Program Files\Amazon\ECSCLI' -ItemType Directory
Invoke-WebRequest -OutFile 'C:\Program Files\Amazon\ECSCLI\ecs-cli.exe' https://amazon-ecs-cli.s3.amazonaws.com/ecs-cli-windows-amd64-latest.exe
GnuPG をダウンロード
こちらからGnuPGをダウンロードします。
Amazon ECS PGP パブリックキーを取得
PGP 署名での暗号化のため、パブリックキーを取得します。
参考:https://stackoverflow.com/questions/66217436/gpg-keyserver-receive-failed-no-name
公式ドキュメントではキーサーバーとしてhkp://keys.gnupg.netが指定されていますが、現在は廃止されています。
gpg --keyserver keyserver.ubuntu.com --recv BCE9D9A42D51784F
Amazon ECS CLI の署名をダウンロード
署名をダウンロードします。
検証の際にエラー原因となりますので、署名は C 直下にダウンロードしてください。
cd /
Invoke-WebRequest -OutFile ecs-cli.asc https://amazon-ecs-cli.s3.amazonaws.com/ecs-cli-windows-amd64-latest.exe.asc
署名を検証
署名を検証します。
出力に警告が表示されることがありますが、問題ありません。
gpg --verify ecs-cli.asc 'C:\Program Files\Amazon\ECSCLI\ecs-cli.exe'
実行アクセス権限をバイナリに適用
バイナリへの実行アクセス権限を適用します。
setx path "%path%;C:\Program Files\Amazon\ECSCLI"
インストール確認
※Windows PowerShell を再起動後に実行
ecs-cli --version
ECS CLIのインストールでつまづいたところ
- Amazon ECS PGP パブリックキーを取得の際に、ドキュメントで指定されているサーバ(hkp://keys.gnupg.net)が使えない
-
他のサーバを使用する
- keyserver.ubuntu.com
- keys.openpgp.org
- pgp.mit.edu
-
他のサーバを使用する
- Amazon ECS CLI の署名は C 直下にダウンロードして検証しなければいけない
ソースの作成
ecs-test
│ docker-compose.yml
│ ecs-params.yml
│ env.cmd
│ task-execution-assume-role.json
│
└─web
│ app.py
│ Dockerfile
│ requirements.txt
│
├─static
│ style.css
│
└─templates
index.html
Webアプリの作成
app.py
Pythonのflaskを使ってWebアプリを作成します。
ルートパスでindex.html
を返します。
ポートは5000番に設定します。
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def index():
return render_template("index.html")
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)
requirements.txt
Pythonの依存モジュールを記述するファイルです。
app.py
でflaskを使用するので記述しておきます。
Flask==1.1.2
index.html
Webアプリで表示する画面を定義するファイルです。
<html>
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" type="text/css" href="../static/style.css" />
<title>Test Page</title>
</head>
<section class="wrapper">
<div class="container">
<div class="content">
<h2 class="heading">ECS TEST PAGE</h2>
<p>Hello World!<br /></p>
</div>
</div>
</section>
</html>
style.css
HTMLファイルのデザインを定義するファイルです。
/* container */
.wrapper {
width: 100%;
}
.wrapper .container {
max-width: 1000px;
margin: 0px auto;
padding: 80px 0px;
}
/* content */
.wrapper .content {
padding: 50px;
text-align: center;
}
.wrapper .content .heading {
margin: 0px 0px 40px 0px;
font-size: 24px;
font-weight: normal;
text-align: center;
}
ECS構築に必要な各種定義ファイルの作成
Dockerfile
Webアプリのコンテナを定義するファイルです。
# Python3のイメージを使用
FROM python:3
# Dcokerfileと同じ階層のソースをコンテナイメージのweb配下に追加
ADD . /web
# コマンドの実行ディレクトリをwebに設定
WORKDIR /web
# Pythonのパッケージ管理ツールを最新化
RUN pip install --upgrade pip
# 依存モジュールのインストール
RUN pip install -r requirements.txt
# Webアプリ起動
CMD ["python", "app.py"]
docker-compose.yml
複数のコンテナ定義や起動オプションを設定できるファイルです。(今回は単数です。)
今回はECS構築に使用するので、予めECRリポジトリにイメージを保管しておき、そこからイメージを取得します。そのため、image
にはECRリポジトリのURIを指定します。
また、logging
にAWS上でのログ出力先などを指定します。
version: '3'
services:
web:
image: ${ecr_repository_uri}:latest
ports:
- '${container_port}:${container_port}'
logging:
driver: awslogs
options:
awslogs-group: ${logs_group_name}
awslogs-region: ${region}
awslogs-stream-prefix: ecs
ecs-params.yml
ECSで起動するコンテナのタスク定義と起動時のネットワーク設定などを記述するファイルです。
version: 1
task_definition:
task_execution_role: ${ecs_task_exec_role_name}
ecs_network_mode: awsvpc
task_size:
mem_limit: 0.5GB
cpu_limit: 256
run_params:
network_configuration:
awsvpc_configuration:
subnets:
- ${subnet_1}
- ${subnet_2}
security_groups:
- ${security_group_id}
assign_public_ip: ENABLED
task-execution-assume-role.json
ECSタスク実行ロールを作成する際の信頼ポリシーです。
このファイルを参照してロールの作成を行います。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
環境変数の設定
ECS構築に必要な環境変数を設定します。setから始まる行のイコール(=)から先を設定してください。
一度に設定できるようにバッチファイルで設定します。以下の表の値を除いて、変更して問題ありません。
変数名 | 値 | 理由 |
---|---|---|
container_name | web | docker-compose.ymlに依存しているため |
container_port | 5000 | Webアプリに依存しているため |
src_port | 80 | Webアプリとして公開するため |
dst_port | 80 | Webアプリとして公開するため |
protocol | HTTP | Webアプリとして公開するため |
@echo off
rem AWSのアカウントID 12桁(ECRのURIに使用)
set AWS_ACCOUNT_ID=
rem AWS CLIの設定(ECRへのコンテナイメージプッシュに使用)
set AWS_ACCESS_KEY_ID=
set AWS_SECRET_ACCESS_KEY=
set AWS_OUTPUT_FORMAT=json
rem 各リソースを作成するリージョン
set region=ap-northeast-1
rem 各リソースに付けるグループタグ
set tag_group=qiita
rem 各リソースに付ける名前タグ
set tag_name=ecs-test-qiita
rem サービスの命名規則
set service_name=ecsTestQiita
rem ECRリポジトリ名
set ecr_repos_name=ecs-test-qiita-repository
rem Dockerfileのフォルダパス
set DOCKERFILE_PATH=web
rem タスク実行ロール名
set ecs_task_exec_role_name=%service_name%TaskExecutionRole
rem ECSクラスター名
set ecs_cluster_name=%service_name%Cluster
rem ecs-cliの設定(ECSクラスターの作成に使用)
set ecs_cluster_config_name=%service_name%Config
set ecs_profile_name=%service_name%Profile
rem タスク定義名
set ecs_project_name=%service_name%Project
rem ロググループ名
set logs_group_name=/ecs/%service_name%LogsGroup
rem ロードバランサー名(名前は英字で始まる必要があり、小文字、数字、ハイフン (-)、下線 (_)、スラッシュ (/) のみ)
set load_balancer_name=ecs-test-qiita-alb
rem ターゲットグループ名(名前は英字で始まる必要があり、小文字、数字、ハイフン (-)、下線 (_)、スラッシュ (/) のみ)
set target_group_name=ecs-test-qiita-tg
rem コンテナ名=docker-compose.ymlのservicesで定義した名前(ロググループのパスの一部に使用される)
set container_name=web
rem コンテナの接続ポート
set container_port=5000
rem ロードバランサーとセキュリティグループに設定するポート
set src_port=80
set dst_port=80
rem ロードバランサーとターゲットグループに設定する通信プロトコル
set protocol=HTTP
ECS構築
ここからはコマンドプロンプトで実行します。
ecs-test
フォルダでコマンドプロンプトを開きます。
事前準備
env.cmd
を実行して環境変数を設定します。
env.cmd
ECRリポジトリの作成とAWS上にコンテナイメージを保存
ECSでコンテナを実行する際に使用するコンテナイメージを保管するため、ECRリポジトリを作成します。
aws ecr create-repository
オプション | 内容 | 必須 |
---|---|---|
--repository-name | リポジトリ名 | 〇 |
--tags | 設定するタグ | × |
aws ecr create-repository --repository-name %ecr_repos_name% --tags Key=Name,Value=%tag_name% Key=group,Value=%tag_group%
実行結果で出力されたECRリポジトリの URI(repository.repositoryUri
の値)を環境変数ecr_repository_uri
に設定します。
set ecr_repository_uri=<repository.repositoryUri>
Dcokerfileを基に、ローカルでコンテナイメージを作成します。
ECRリポジトリにプッシュするため、イメージタグにリポジトリURIを指定します。
docker build <Dockerfile_path>
オプション | 内容 | 必須 |
---|---|---|
-t | イメージタグ | × |
cd %DOCKERFILE_PATH%
docker build . -t %ecr_repository_uri%
cd ../
AWS CLIでECRにログインします。
AWS CLIの設定情報を登録してから実行してください。
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-quickstart.html
aws ecr get-login-password
オプション | 内容 | 必須 |
---|---|---|
--region | ECRのログインパスワードを取得するリージョン | 〇 |
--username | ユーザー名(AWSで固定) | 〇 |
docker login <login_url>
オプション | 内容 | 必須 |
---|---|---|
--password-stdin | ログインパスワードを標準入力で受け取る | 〇 |
aws ecr get-login-password --region %region% | docker login --username AWS --password-stdin %AWS_ACCOUNT_ID%.dkr.ecr.%region%.amazonaws.com
ECRリポジトリへプッシュします。
docker push <image_tag>
docker push %ecr_repository_uri%
ECSサービスの作成
ECS CLIの設定を作成して、ecs-cli up
コマンドでECSクラスター、タスク定義、サービス、Cloud Watchロググループを作成します。
ECSタスク実行ロールを作成
ECSのタスクを実行するために必要なロールを作成します。
ロールの信頼ポリシーはtask-execution-assume-role.json
を指定します。
aws iam create-role
オプション | 内容 | 必須 |
---|---|---|
--role-name | ロール名 | 〇 |
--assume-role-policy-document | 信頼ポリシー | 〇 |
--tags | 設定するタグ | × |
aws iam create-role --role-name %ecs_task_exec_role_name% --assume-role-policy-document file://task-execution-assume-role.json --tags Key=Name,Value=%tag_name% Key=group,Value=%tag_group%
作成したロールにECSタスク実行権限をアタッチします。
AWSにデフォルトで用意されているAmazonECSTaskExecutionRolePolicy
を使用します。
aws iam attach-role-policy
オプション | 内容 | 必須 |
---|---|---|
--role-name | ロール名 | 〇 |
--policy-arn | アタッチするポリシーのARN | 〇 |
aws iam attach-role-policy --role-name %ecs_task_exec_role_name% --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
クラスター設定を作成
ECS CLIでECSクラスターを作成する際の構成を設定します。
ecs-cli configure
オプション | 内容 | 必須 |
---|---|---|
--region | リージョン | 〇 |
--cluster | クラスター名 | 〇 |
--default-launch-type | デフォルト起動タイプ(EC2またはFARGATE) | 〇 |
--cluster | クラスター名 | 〇 |
ecs-cli configure --region %region% --cluster %ecs_cluster_name% --default-launch-type FARGATE --config-name %ecs_cluster_config_name%
CLI プロファイルを作成
ECS CLIでコマンドを実行する際の認証情報を作成します。
ecs-cli configure profile
オプション | 内容 | 必須 |
---|---|---|
--access-key | AWSアクセスキー | 〇 |
--secret-key | シークレットキー | 〇 |
--profile-name | プロファイル名 | 〇 |
ecs-cli configure profile --access-key %AWS_ACCESS_KEY_ID% --secret-key %AWS_SECRET_ACCESS_KEY% --profile-name %ecs_profile_name%
クラスターの作成
作成したクラスターの設定とプロファイルを使用して、ECSクラスターを作成します。
ECSクラスターをFARGATEで作成すると、VPC・セキュリティグループ・パブリックサブネットx2などが同時に作成されます。
ecs-cli up
オプション | 内容 | 必須 |
---|---|---|
--cluster-config | クラスターの構成名 | 〇 |
--ecs-profile | ECS CLIのプロファイル名 | 〇 |
ecs-cli up --cluster-config %ecs_cluster_config_name% --ecs-profile %ecs_profile_name% --tags Name=%tag_name%,group=%tag_group%
結果で出力された VPC ID, Subnet IDを環境変数vpc_id
,subnet_1
,subnet_2
に設定します。
set vpc_id=<VPC_ID>
set subnet_1=<Subnet_ID 1>
set subnet_2=<Subnet_ID 2>
VPCのデフォルトのセキュリティグループIDを取得
VPCに紐づくデフォルトのセキュリティグループIDを取得します。
aws ec2 describe-security-groups
オプション | 内容 | 必須 |
---|---|---|
--filters | Nameの項目をValueで指定 | × |
aws ec2 describe-security-groups --filters Name=vpc-id,Values=%vpc_id%
実行結果で出力されたセキュリティグループ ID SecurityGroups[0].GroupId
を環境変数security_group_id
に設定します。
set security_group_id=<SecurityGroups[0].GroupId>
セキュリティグループの設定
セキュリティグループにタグを設定します。(任意)
aws ec2 create-tags
オプション | 内容 | 必須 |
---|---|---|
--resources | タグを設定するリソースID | 〇 |
--tags | 設定するタグ | 〇 |
aws ec2 create-tags --resources %security_group_id% --tags Key=Name,Value=%tag_name% Key=group,Value=%tag_group%
自身の IP アドレスを調べます。
curl https://checkip.amazonaws.com
実行結果で出力されたIPアドレスを環境変数my_ip
に設定します。
set my_ip=<ip_adress>
セキュリティグループのインバウンドルールに自身のIPアドレスを登録します。
aws ec2 authorize-security-group-ingress
オプション | 内容 | 必須 |
---|---|---|
--group-id | セキュリティグループID | × |
--ip-permissions | IPアドレスのインバウンドルール | × |
--tag-specifications | タグを設定するリソースタイプとタグの内容 | × |
aws ec2 authorize-security-group-ingress --group-id %security_group_id% --ip-permissions IpProtocol=tcp,FromPort=%src_port%,ToPort=%dst_port%,IpRanges=[{CidrIp=%my_ip%/32,Description=shoma}] --tag-specifications ResourceType=security-group-rule,Tags=[{Key=Name,Value=%tag_name%},{Key=group,Value=%tag_group%}]
ロードバランサーとターゲットグループの作成
ロードバランサーを作成します。
aws elbv2 create-load-balancer
オプション | 内容 | 必須 |
---|---|---|
--name | ロードバランサー名 | 〇 |
--subnets | 接続するサブネット(ALBは2つ以上) | × |
--security-groups | 設定するセキュリティグループID | × |
--tags | 設定するタグ | × |
aws elbv2 create-load-balancer --name %load_balancer_name% --subnets %subnet_1% %subnet_2% --security-groups %security_group_id% --tags Key=Name,Value=%tag_name% Key=group,Value=%tag_group%
実行結果で出力されたロードバランサーの ARN LoadBalancers[0].LoadBalancerArn
を環境変数load_balancer_arn
に設定します。
set load_balancer_arn=<LoadBalancers[0].LoadBalancerArn>
ターゲットグループを作成します。
aws elbv2 create-target-group
オプション | 内容 | 必須 |
---|---|---|
--name | ターゲットグループ名 | 〇 |
--protocol | ターゲットの通信プロトコル | × |
--port | ターゲットのポート | × |
--target-type | ターゲットタイプ | × |
--vpc-id | ターゲットとするVPCのID | × |
--tags | 設定するタグ | × |
aws elbv2 create-target-group --name %target_group_name% --protocol %protocol% --port %dst_port% --target-type ip --vpc-id %vpc_id% --tags Key=Name,Value=%tag_name% Key=group,Value=%tag_group%
実行結果で出力されたターゲットグループの ARN TargetGroups[0].TargetGroupArn
を環境変数target_group_arn
に設定します。
set target_group_arn=<TargetGroups[0].TargetGroupArn>
ロードバランサーにリスナーを追加します。
リスナーとは、ロードバランサーに接続するための入り口のことです。
aws elbv2 create-listener
オプション | 内容 | 必須 |
---|---|---|
--load-balancer-arn | リスナーを追加するロードバランサーのARN | 〇 |
--protocol | × | |
--port | × | |
--default-actions | 〇 |
aws elbv2 create-listener --load-balancer-arn %load_balancer_arn% --protocol %protocol% --port %src_port% --default-actions Type=forward,TargetGroupArn=%target_group_arn%
ECSクラスターにサービスをデプロイ
クラスターにサービスをデプロイします。
同時にロググループも作成されます。
``
オプション | 内容 | 必須 |
---|---|---|
--project-name | タスク定義とサービスの名前(デフォルトは実行フォルダ名) | × |
--create-log-groups | 構成ファイル(docker-compose.yml)で指定されたロググループを作成 | × |
--cluster-config | ECSクラスター設定名 | × |
--ecs-profile | ECSプロファイル名 | × |
--target-group-arn | 関連付けるターゲットグループのARN | × |
--container-name | コンテナ名 | ×(※ロードバランサーまたはターゲットグループが指定されている場合は必須) |
--container-port | コンテナポート | ×(※ロードバランサーまたはターゲットグループが指定されている場合は必須) |
ecs-cli compose --project-name %ecs_project_name% service up --create-log-groups --cluster-config %ecs_cluster_config_name% --ecs-profile %ecs_profile_name% --target-group-arn %target_group_arn% --container-name %container_name% --container-port %container_port%
サービスの接続先情報確認
エンドポイントであるロードバランサーの接続先情報を確認します。
aws elbv2 describe-load-balancers
オプション | 内容 | 必須 |
---|---|---|
--load-balancer-arns | 対象ロードバランサーのARN | × |
aws elbv2 describe-load-balancers --load-balancer-arns %load_balancer_arn%
出力結果のLoadBalancers[0].DNSName
が接続先ですので、アクセスして確認してみましょう。
ECS構築でつまづいたところ
- ECR リポジトリの名前は大文字不可
- 名前は英字で始まる必要があり、小文字、数字、ハイフン (-)、下線 (_)、スラッシュ (/) のみ(参考:https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/repository-create.html)
- セキュリティグループのインバウンドルールに登録するIP アドレスはレンジが必要(/32)
- AWS CLI のバージョンによってはセキュリティグループにタグ付けするオプション
--tag-specifications
がバージョンによっては使用できない(2021/7/7にリリースされた2.2.18からの機能のため)
バッチの作成
ecs-test
+│ create.cmd
+│ delete.cmd
│ docker-compose.yml
│ ecs-params.yml
│ env.cmd
│ task-execution-assume-role.json
│
└─web
│ app.py
│ Dockerfile
│ requirements.txt
│
├─static
│ style.css
│
└─templates
index.html
構築バッチ
これまで使用したコマンドをcreate.cmd
に集約します。
手動実行との差異
- AWSの認証情報を標準入力から受け取り
- 各ツールのインストール確認(バージョンは未考慮)
- リソースのプロパティの一部は
resource_properties.cmd
に書き込み(削除時にも使用するため) - ロガーで各コマンドの実行時刻を標準出力
rem 実行したコマンドを標準出力しない
@echo off
rem 遅延関数(!example!)を有効化
setlocal enabledelayedexpansion
rem カレントディレクトリに移動(/dはドライブ変更可能に設定するオプション)
cd /d %~dp0
WHERE /Q jq
if not %ERRORLEVEL% == 0 (
call :loggerError "Please install jq : https://stedolan.github.io/jq/"
cmd /k
)
WHERE /Q aws
if not %ERRORLEVEL% == 0 (
call :loggerError "Please install AWS CLI : https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/install-cliv2.html"
cmd /k
)
WHERE /Q ecs-cli
if not %ERRORLEVEL% == 0 (
call :loggerError "Please install ECS CLI : https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ECS_CLI_installation.html"
cmd /k
)
WHERE /Q docker
if not %ERRORLEVEL% == 0 (
call :loggerError "Please install Docker Desktop for Windows : https://docs.docker.jp/docker-for-windows/install.html"
cmd /k
)
rem 環境変数を設定
call :loggerInfo "set enviroment"
set /p AWS_ACCOUNT_ID="AWS_ACCOUNT_ID: "
set /p AWS_ACCESS_KEY_ID="AWS_ACCESS_KEY_ID: "
set /p AWS_SECRET_ACCESS_KEY="AWS_SECRET_ACCESS_KEY: "
call env.cmd
rem リソースのプロパティファイル存在確認
call :loggerInfo "check properties file"
if not exist %resources_properties_file% type nul > %resources_properties_file%
echo rem START at %date% %time%>>%resources_properties_file%
rem AWS CLIの設定
call :loggerInfo "set AWS CLI configure"
(
echo %AWS_ACCESS_KEY_ID%
echo %AWS_SECRET_ACCESS_KEY%
echo %region%
echo %AWS_OUTPUT_FORMAT%
) | aws configure > nul
rem コンテナイメージを格納するECRリポジトリを作成
call :loggerInfo "create ECR repository"
for /f "usebackq" %%t in (`"aws ecr create-repository --repository-name %ecr_repos_name% --tags Key=Name,Value=%tag_name% Key=group,Value=%tag_group% " ^| jq .repository.repositoryUri`) do (
set ecr_repository_uri=%%t
rem ダブルクォーテーションを削除
call set ecr_repository_uri=%%ecr_repository_uri:"=%%
rem プロパティファイルに追記
echo set ecr_repository_uri=!ecr_repository_uri!>>%resources_properties_file%
)
rem コンテナイメージの作成
call :loggerInfo "build container image"
cd /d %~dp0
cd %DOCKERFILE_PATH%
docker build . -t %ecr_repository_uri% --no-cache
cd /d %~dp0
rem ECR へプッシュ
call :loggerInfo "push image to ECR repository"
aws ecr get-login-password --region %region% | docker login --username AWS --password-stdin %AWS_ACCOUNT_ID%.dkr.ecr.%region%.amazonaws.com
docker push %ecr_repository_uri%:latest
rem タスク実行ロールを作成(task-execution-assume-role.jsonを作成後に実行)
call :loggerInfo "create task execution role"
aws iam create-role --role-name %ecs_task_exec_role_name% --assume-role-policy-document file://task-execution-assume-role.json --tags Key=Name,Value=%tag_name% Key=group,Value=%tag_group% > nul
rem タスク実行ロールのポリシーをアタッチ
call :loggerInfo "attach role"
aws iam attach-role-policy --role-name %ecs_task_exec_role_name% --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
rem クラスター設定を作成
call :loggerInfo "create ECS Cluster settings"
ecs-cli configure --region %region% --cluster %ecs_cluster_name% --default-launch-type FARGATE --config-name %ecs_cluster_config_name%
rem アクセスキーとシークレットキーを使用して CLI プロファイルを作成
call :loggerInfo "create ecs-cli profile"
ecs-cli configure profile --access-key %AWS_ACCESS_KEY_ID% --secret-key %AWS_SECRET_ACCESS_KEY% --profile-name %ecs_profile_name%
rem Amazon ECS クラスターを作成, デフォルトVPCのIDを取得
call :loggerInfo "create ECS Cluster and get default VPC ID"
for /f "tokens=1,2* usebackq delims=^:" %%i in (`"ecs-cli up --cluster-config %ecs_cluster_config_name% --ecs-profile %ecs_profile_name% --tags Name=%tag_name%,group=%tag_group% " ^| findstr /r "created$"`) DO (
echo %%i | find "VPC" > nul
if not ERRORLEVEL 1 (
set vpc_id=%%j
call set vpc_id=%%vpc_id: =%%
echo VPC ID: !vpc_id!
rem プロパティファイルに追記
echo set vpc_id=!vpc_id!>>%resources_properties_file%
)
echo %%i | find "Subnet" > nul
if not ERRORLEVEL 1 if not defined subnet_1 (
set subnet_1=%%j
call set subnet_1=%%subnet_1: =%%
echo Subnet ID 1: !subnet_1!
rem プロパティファイルに追記
echo set subnet_1=!subnet_1!>>%resources_properties_file%
) else if not defined subnet_2 (
set subnet_2=%%j
call set subnet_2=%%subnet_2: =%%
echo Subnet ID 2: !subnet_2!
rem プロパティファイルに追記
echo set subnet_2=!subnet_2!>>%resources_properties_file%
)
)
rem セキュリティグループIDを取得
call :loggerInfo "get Security Group ID"
for /f "usebackq" %%t in (`"aws ec2 describe-security-groups --filters Name=vpc-id,Values=%vpc_id% " ^| jq .SecurityGroups[0].GroupId`) do (
set security_group_id=%%t
rem ダブルクォーテーションを削除
call set security_group_id=%%security_group_id:"=%%
rem プロパティファイルに追記
echo set security_group_id=!security_group_id!>>%resources_properties_file%
)
rem セキュリティグループにタグ付け
call :loggerInfo "attach tags to Security Group"
aws ec2 create-tags --resources %security_group_id% --tags Key=Name,Value=%tag_name% Key=group,Value=%tag_group%
rem 自身のIPアドレスを取得
call :loggerInfo "get own IP Address"
for /f "usebackq" %%t in (`curl https://checkip.amazonaws.com`) do (
set my_ip=%%t
)
rem セキュリティグループに自身のIPアドレスを登録
call :loggerInfo "register IP Address to Security Group"
aws ec2 authorize-security-group-ingress --group-id %security_group_id% --ip-permissions IpProtocol=tcp,FromPort=%src_port%,ToPort=%dst_port%,IpRanges=[{CidrIp=%my_ip%/32,Description=shoma}] --tag-specifications ResourceType=security-group-rule,Tags=[{Key=Name,Value=%tag_name%},{Key=group,Value=%tag_group%}]
rem ロードバランサーの作成
call :loggerInfo "create Application Load Balancer"
for /f "usebackq" %%x in (`"aws elbv2 create-load-balancer --name %load_balancer_name% --subnets %subnet_1% %subnet_2% --security-groups %security_group_id% --tags Key=Name,Value=%tag_name% Key=group,Value=%tag_group% " ^| jq .LoadBalancers[0].LoadBalancerArn`) do (
set load_balancer_arn=%%x
rem ダブルクォーテーションを削除
call set load_balancer_arn=%%load_balancer_arn:"=%%
rem プロパティファイルに追記
echo set load_balancer_arn=!load_balancer_arn!>>%resources_properties_file%
)
rem ターゲットグループの作成
call :loggerInfo "create Target Group"
for /f "usebackq" %%x in (`"aws elbv2 create-target-group --name %target_group_name% --protocol %protocol% --port %dst_port% --target-type ip --vpc-id %vpc_id% --tags Key=Name,Value=%tag_name% Key=group,Value=%tag_group% " ^| jq .TargetGroups[0].TargetGroupArn`) do (
set target_group_arn=%%x
rem ダブルクォーテーションを削除
call set target_group_arn=%%target_group_arn:"=%%
rem プロパティファイルに追記
echo set target_group_arn=!target_group_arn!>>%resources_properties_file%
)
rem ロードバランサーにリスナーを追加
call :loggerInfo "add listener to Application Load Balancer"
aws elbv2 create-listener --load-balancer-arn %load_balancer_arn% --protocol %protocol% --port %src_port% --default-actions Type=forward,TargetGroupArn=%target_group_arn%
rem クラスターにサービスをデプロイ
call :loggerInfo "deploy ECS Services"
ecs-cli compose --project-name service up %ecs_project_name% --create-log-groups --cluster-config %ecs_cluster_config_name% --ecs-profile %ecs_profile_name% --target-group-arn %target_group_arn% --container-name %container_name% --container-port %container_port%
rem クラスターの確認
call :loggerInfo "check ECS Cluster"
ecs-cli compose --project-name service ps %ecs_project_name% --cluster-config %ecs_cluster_config_name% --ecs-profile %ecs_profile_name%
call :loggerInfo "finish build and deploy Services"
call :loggerInfo "get DNS"
for /f "usebackq" %%x in (`"aws elbv2 describe-load-balancers --load-balancer-arns %load_balancer_arn% " ^| jq .LoadBalancers[0].DNSName`) do (
set load_balancer_dns=%%x
rem ダブルクォーテーションを削除
call set load_balancer_dns=%%load_balancer_dns:"=%%
rem プロパティファイルに追記
echo set load_balancer_dns=!load_balancer_dns!>>%resources_properties_file%
rem コンソールに表示
echo ALB DNS is !load_balancer_dns!
)
echo rem FINISH at %date% %time%>>%resources_properties_file%
cmd /k
:loggerInfo
echo %date% %time% [INFO] %~1
exit /b
:loggerError
echo %date% %time% [ERROR] %~1
exit /b
削除バッチ
削除バッチは、構築バッチ実行時に作成されたresource_properties.cmd
を使用します。
それを基に削除するリソースを指定します。
rem 実行したコマンドを標準出力しない
@echo off
rem カレントディレクトリに移動(/dはドライブ変更可能に設定するオプション)
cd /d %~dp0
rem 環境変数を設定
call env.cmd
if not exist %resources_properties_file% (
call :loggerError "NOT EXIST %resources_properties_file%"
cmd /k
)
call %resources_properties_file%
rem ロードバランサーの削除
call :loggerInfo "delete Application Load Balancer"
aws elbv2 delete-load-balancer --load-balancer-arn %load_balancer_arn%
rem ロードバランサー削除待ち
call :loggerInfo "wait 10 seconds"
timeout 10 > nul
rem ターゲットグループの削除
call :loggerInfo "delete Target Group"
aws elbv2 delete-target-group --target-group-arn %target_group_arn%
rem クラスターのサービスを削除
call :loggerInfo "delete servise on ECS Cluster"
ecs-cli compose --project-name %ecs_project_name% service down --cluster-config %ecs_cluster_config_name% --ecs-profile %ecs_profile_name%
rem クラスターを削除
call :loggerInfo "delete ECS Cluster"
ecs-cli down --force --cluster-config %ecs_cluster_config_name% --ecs-profile %ecs_profile_name%
rem ECRリポジトリの削除
call :loggerInfo "delete ECR repository"
aws ecr delete-repository --repository-name %ecr_repos_name% --force
rem ロググループを削除
call :loggerInfo "delete Logs Group"
aws logs delete-log-group --log-group-name %logs_group_name%
rem ECSタスク実行ロールの削除
call :loggerInfo "delete task execution role"
aws iam detach-role-policy --role-name %ecs_task_exec_role_name% --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
aws iam --region %region% delete-role --role-name %ecs_task_exec_role_name%
call :loggerInfo "finish delete Services"
cmd /k
:loggerInfo
echo %date% %time% [INFO] %~1
exit /b
:loggerError
echo %date% %time% [ERROR] %~1
exit /b
おわりに
コマンドのみでECSにWebアプリを構築してバッチ化まで行いました。
依存ツールが多いことやWindowsバッチが書きにくいと感じたため、次はLinuxベースのDockerコンテナで環境構築を行って、shellスクリプトで作成するのが良いと考えています。
また、バッチのようにコードベースで作成することに加えて、異常系を考慮したいと思いました。AWS CloudFormationやTerraformなど、IaC(Infrastructure as Code)ツールがありますので、そちらで実現してみたいと考えています。
参考
Amazon ECS CLI のインストール
Amazon ECS CLI の設定
gpg: keyserver receive failed: No name - stack overflow
チュートリアル: Amazon ECS CLI を使用して Fargate タスクのクラスターを作成する
MSI インストーラを使用した Windows での AWS CLI バージョン 2 のインストールまたは更新
Amazon EC2、VPC セキュリティグループルールにリソース識別子とタグを追加
CodeDeploy Amazon ECS デプロイ用のロードバランサー、ターゲットグループ、リスナーのセットアップ