3
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【AWS】AWS CLIとECS CLIを使用したECS Webアプリ構築 ~バッチ化まで~

Last updated at Posted at 2021-08-31

AWSの環境構築をマネジメントコンソールから行うと、画面操作に多くの時間がかかったり、ミスが発生してしまうことが課題だと感じていました。また、手順をドキュメント化するのに掛かるコストも大きいです。

そのため、コマンドラインインターフェース(AWS CLI, ECS CLI)を用いてECSを構築してみます。
また、Windowsバッチ化も行ってみます。(正常系のみ考慮します。)

バッチを先に確認したい方は、こちらです。

構成図

ecs_alb_webapp.png

前提条件

  • Windows10 環境
  • AWS アカウント
  • AWS にプログラムからアクセス権限があるユーザー
  • 依存ツール

ツールのバージョン

jq
jq --version
jq-1.6
Docker
docker -v
Docker version 20.10.7, build f0df350
AWSCLI
aws --version
aws-cli/2.2.27 Python/3.8.8 Windows/10 exe/AMD64 prompt/off
ECSCLI
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)が使えない
  • 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番に設定します。

app.py
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を使用するので記述しておきます。

requirements.txt
Flask==1.1.2

index.html

Webアプリで表示する画面を定義するファイルです。

index.html
<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ファイルのデザインを定義するファイルです。

style.css
/* 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アプリのコンテナを定義するファイルです。

Dockerfile
# 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上でのログ出力先などを指定します。

docker-compose.yml
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で起動するコンテナのタスク定義と起動時のネットワーク設定などを記述するファイルです。

ecs-params.yml
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タスク実行ロールを作成する際の信頼ポリシーです。
このファイルを参照してロールの作成を行います。

task-execution-assume-role.json
{
  "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アプリとして公開するため
env.cmd
@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 リポジトリの名前は大文字不可
  • セキュリティグループのインバウンドルールに登録する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に書き込み(削除時にも使用するため)
  • ロガーで各コマンドの実行時刻を標準出力
create.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を使用します。
それを基に削除するリソースを指定します。

delete.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 CloudFormationTerraformなど、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 デプロイ用のロードバランサー、ターゲットグループ、リスナーのセットアップ

3
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?