1. aokad

    No comment

    aokad
Changes in title
-AWS CLI で Amazon ECS のタスクを実行する (第3回)
+AWS CLI で Amazon ECS のタスクを実行する (第2回)
Changes in body
Source | HTML | Preview
@@ -1,323 +1,328 @@
AWS CLI を使って AWS ECS でタスクを実行するまでを解説します。
このドキュメントを読むためには AWS EC2、AWS S3 および docker に対する基本的な知識が必要です。
サービス、メトリクス、タスクのスケジューリングについては解説しません。
-前記事[AWS CLI で AWS batch にジョブを送信する (全3回)](https://qiita.com/aokad/private/f28ffa5ae984678817f6)と多少リンクしていますが、未読でも問題なく読めるように作成したつもりです。
+前記事[AWS CLI で AWS batch にジョブを送信する (全3回)](https://qiita.com/aokad/items/f28ffa5ae984678817f6)とリンクしていますが、未読でも問題なく読めるように作成したつもりです。
参考:[EC2 タスクを使用した AWS CLI のウォークスルー](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ECS_AWSCLI_EC2.html)
## 目次
全4回です。
1. [はじめに](https://qiita.com/aokad/private/b3762e3cc10db6104858)
-2. [タスクを実行してみる](https://qiita.com/aokad/private/afe568d57605ad3d6d4b)
-3. スクリプト化する ← イマココ
-
+2. タスクを実行してみる ← イマココ
+3. [スクリプト化する](https://qiita.com/aokad/private/0eacf96fcb2378c7a36c)
+
4. [汎用 docker イメージを使用する](https://qiita.com/aokad/private/0e6a22082122cebc30bb)
AWS CLI 公式リファレンスはこちら [aws ecs](https://docs.aws.amazon.com/cli/latest/reference/ecs/index.html)
-## 前回までの課題
+## 1. AWS CLI の準備
-前回までに AWS CLI を順に実行してタスクを実行するまで実現しました。
-しかし、インスタンスの起動やタスクの終了など待ちが発生するので、実際のシステムに組み込むためには「待ち」をどうにかして組み込む必要があります。
-今回は、wait サブコマンドで実現する方法を記載します。
+手順はこちらを参照してください。 [AWS CLI のインストールと設定](https://qiita.com/aokad/items/88fe9312eea1c12ba8a2)
-## インスタンスの起動を待つ
+今回は JSON を扱うために `jq` コマンドも使用しますのでインストールしてください。
-AWS CLI には wait コマンドが各サブコマンドに実装されています。
+https://stedolan.github.io/jq/download/
-以下は ecs run-instances した後、`run` になるまで待ち、さらに `status-ok` になるまで待つ例です。
+## 2. docker イメージを作成する(オプション)
-```Bash
-# インスタンス起動
-aws ec2 run-instances (省略) > run-instances.log
-# インスタンス ARN 取得
-INSTANCE_ID=$(jq -r '.Instances[0].InstanceId' run-instances.log)
-# インスタンス起動を待つ
-aws ec2 wait instance-running --instance-ids ${INSTANCE_ID}
-# インスタンスステータスチェック完了を待つ
-aws ec2 wait instance-status-ok --include-all-instances --instance-ids ${INSTANCE_ID}
-```
-
-## タスクの終了を待つ
-
-次にタスクの終了を待ちます。
-
-```Bash
-# タスク実行
-aws ecs run-task (省略) > run-task.log
-# タスク ARN 取得
-TASK_ARN=$(jq -r '.tasks[0].taskArn' run-task.log)
-# タスク終了を待つ
-aws ecs wait tasks-stopped --tasks ${TASK_ARN} --cluster ${CLUSTER_ARN}
-```
-
-ここで問題が発生します。どうやら `aws ecs wait` には最大待ち時間 (10分) があるようです。
-10分経つとまだタスクが終わっていなくても "Waiter InstanceTerminated failed: Max attempts exceeded" というメッセージを表示して抜けてきてしまいます
+今回の作業手順ではこちらで用意した docker イメージ [aokad/aws-wordcount](https://hub.docker.com/r/aokad/aws-wordcount/~/dockerfile/) を使用しますので、この作業は不要です。
+独自イメージを使用する場合は [上記イメージの DockerFile](https://raw.githubusercontent.com/aokad/qiita_aws_batch/master/Dockerfile) を参考にしてください
-私たちのグループで作成しているタスクには数十分かかるものもありますので、これは困ります。
-ということで、`ecs wait describe-tasks` コマンドでタスクの状態を見て RUNNING であれば再度 `ecs wait tasks-stopped` としましたが、他に解決方法があるような気もします。
+この記事においてイメージに必要なもの(最終的にはなくてもよいです)
-wait コマンドをリトライする例
+ - AWS CLI ... `aws configure` はしないこと(タスク実行時に自動で一時的なアカウントが設定されます)
+ - docker run の時に実行するスクリプトファイル
-```Bash
-while :
-do
- # タスクの情報取得
- aws ecs describe-tasks --tasks ${TASK_ARN} --cluster ${CLUSTER_ARN} \
- > describe-tasks.log
-
- # タスクの情報からステータスを取得
- TASK_STATE=$(jq -r '.tasks[0].lastStatus' describe-tasks.log)
-
- # RUNNING でなければ抜ける
- if test "${TASK_STATE}" != "RUNNING"; then
- break
- fi
-
- # タスク終了を待つ
- aws ecs wait tasks-stopped --tasks ${TASK_ARN} --cluster ${CLUSTER_ARN}
-done
-```
+AWS ECR を使用する場合は [AWS CLI で Amazon ECR に docker イメージを push する](https://qiita.com/aokad/items/17a06c2384041bd60d16) を参照してください。
-## パイプライン
+## 3. AWS 情報を環境変数にセット
-コピペ実行できるようにアカウントの違いを環境変数にセットしておきます。
+この記事ではコピペ実行できるようにアカウントの違いを環境変数にセットしておきます。
- AWS_ACCOUNTID ..... 自分のアカウントID
- AWS_REGION ..... `aws configure` で設定したリージョン
- SUBNET1,2,3 ..... 「AWSコンソール」→「VPCダッシュボード」→「VPC」からデフォルトVPCのサブネットを確認してください
- SECURITYGROUPID ..... 「AWSコンソール」→「VPCダッシュボード」→「セキュリティグループ」からデフォルトのセキュリティグループをのIDを確認してください。新しく作成する場合は22番ポートを開けてください。
- KEY_NAME ... 作成するインスタンスの SSH キーです。無ければ作製してください。
- S3_BUCKET ... 作業用の S3 バケットです。任意の名称で作成してください。
- - AMI_ID ... 作成するEC2 インスタンスの Amazon ECS AMI の ID です。東京リージョン (ap-northeast-1) は `ami-a99d8ad5` ですが、別リージョンを使用する場合は[こちら](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ECS_AWSCLI_EC2.html#AWSCLI_EC2_launch_container_instance)から選択してください。
設定例
```Bash
export AWS_ACCOUNTID=123456789012
export AWS_REGION=ap-northeast-1
export SUBNET1=subnet-123a456b
export SUBNET2=subnet-789c012d
export SUBNET3=subnet-345e678f
export SECURITYGROUPID=sg-11335577
export KEY_NAME=mykey
export S3_BUCKET=mybucket
-export AMI_ID=ami-a99d8ad5
+AMI_ID=ami-a99d8ad5
```
-上記環境変数をセットできたら、以下をまとめて実行できるはずですが、スクリプトは[ここ](https://raw.githubusercontent.com/aokad/qiita_aws_batch/master/run-ecs.sh)に置いてありますので、ダウンロードして `bash run-ecs.sh` したほうが確実かもしれません。
+## 4. クラスターを作成
-ダウンロードしたスリプトは先頭に環境変数をセットしていますので、編集してお使いください
+ラスターは名前の定義だけです
```Bash
-####################################
-# クラスターの作成
-####################################
-
aws ecs create-cluster \
--cluster-name myCluster \
> create-cluster.log
+```
+作成したクラスターの ARN を取得します。
+
+```Bash
CLUSTER_ARN=$(jq -r '.cluster.clusterArn' create-cluster.log)
+```
+
+<FONT SIZE=1>
+
+備考:
+ARN とは、Amazon リソースネームの略で、AWS リソースを一意に識別するためのものです。
+</FONT>
+
+作成したクラスターは「AWSコンソール」→「Elastic Container Service」→「Amazon ECS」→「クラスター」から確認できます。
+
+![amazon-ecs-1.png](https://qiita-image-store.s3.amazonaws.com/0/226829/f5ab71e5-e539-392d-6e87-d75c8aad7648.png)
-####################################
-# タスク定義を作成
-####################################
-
-# ロググループを作成
-LOG_GROUP_NAME=mytask-$(date "+%Y%m%d-%H%M%S%Z")
-aws logs create-log-group --log-group-name ${LOG_GROUP_NAME}
-
+## 5. タスク定義を作成
-# コンテナ定義を作成
+まず、タスク定義に必要なコンテナ定義を作成します。
+
+```Bash
ECSTASKROLE="arn:aws:iam::${AWS_ACCOUNTID}:role/AmazonECSTaskS3FullAccess"
cat << EOF > task_definition.json
{
"containerDefinitions": [
{
"name": "mytask-definision",
"image": "aokad/aws-wordcount:0.0.1",
"cpu": 1,
"memory": 800,
"essential": true,
"entryPoint": [
"ash",
"-c"
],
"command": [
"ash run.sh \${INPUT} \${OUTPUT}"
],
"environment": [
{
"name": "INPUT",
"value": ""
},
{
"name": "OUTPUT",
"value": ""
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "mytask",
"awslogs-region": "${AWS_REGION}",
"awslogs-stream-prefix": "ecs-test"
}
}
}
],
"taskRoleArn": "${ECSTASKROLE}",
"family": "mytask"
}
EOF
+```
+
+タスク定義を登録します。
-# タスク定義を作成
+```Bash
aws ecs register-task-definition \
--cli-input-json file://task_definition.json \
> register-task-definition.log
+```
+作成したタスク定義の ARN を取得します。
+
+```Bash
TASK_DEFINITION_ARN=$(jq -r '.taskDefinition.taskDefinitionArn' register-task-definition.log)
+```
+
+## 6. ロググループを作成
-####################################
-# EC2 インスタンスを起動する
-####################################
+AWS Batch は自動で作ってくれましたが、Amazon ECS では自分で作ります。
-# ユーザデータを作成
+```Bash
+aws logs create-log-group --log-group-name mytask
+```
+
+## 7. EC2 インスタンスを起動する
+
+AWS Batch は自動で作ってくれましたが、Amazon ECS では自分で作ります。
+Amazon ECS AMI を使用してインスタンスを起動します。
+
+Amazon ECS AMI はリージョンごとにimage-idが違います。今回は東京リージョン (ap-northeast-1) を使用していますが、別リージョンを使用する場合は[こちら](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ECS_AWSCLI_EC2.html#AWSCLI_EC2_launch_container_instance)から選択してください。
+
+```Bash
+AMI_ID=ami-a99d8ad5
+```
+
+今回の例では 30GByte のディスクをアタッチし、docker コンテナから使用できるストレージも 30G に増加します。
+そのためのユーザデータを作成します。
+
+ユーザデータとは、AWS EC2 でインスタンス起動時に実行できるスクリプトです。
+詳しくはこちら:[Linux インスタンスでの起動時のコマンドの実行](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/user-data.html)
+
+```Bash
cat << EOF > userdata.sh
Content-Type: multipart/mixed; boundary="==BOUNDARY=="
MIME-Version: 1.0
--==BOUNDARY==
Content-Type: text/cloud-boothook; charset="us-ascii"
# Install nfs-utils
cloud-init-per once yum_update yum update -y
cloud-init-per once install_nfs_utils yum install -y nfs-utils
cloud-init-per once docker_options echo 'OPTIONS="\${OPTIONS} --storage-opt dm.basesize=30G"' >> /etc/sysconfig/docker
#!/bin/bash
# Set any ECS agent configuration options
echo "ECS_CLUSTER=${CLUSTER_ARN}" >> /etc/ecs/ecs.config
--==BOUNDARY==--
EOF
+```
-# インスタンスを起動
+EC2 インスタンスを起動します。
+
+```Bash
aws ec2 run-instances \
--image-id ${AMI_ID} \
--security-group-ids ${SECURITYGROUPID} \
--key-name ${KEY_NAME} \
--user-data "file://userdata.sh" \
--iam-instance-profile Name="ecsInstanceRole" \
--instance-type t2.micro \
--block-device-mappings "[{\"DeviceName\":\"/dev/xvdcz\",\"Ebs\":{\"VolumeSize\":30,\"DeleteOnTermination\":true}}]" \
--count 1 \
> run-instances.log
INSTANCE_ID=$(jq -r '.Instances[0].InstanceId' run-instances.log)
+```
-# 起動完了を待つ
-aws ec2 wait instance-running --instance-ids ${INSTANCE_ID}
-aws ec2 wait instance-status-ok --include-all-instances --instance-ids ${INSTANCE_ID}
+起動したインスタンスに名前を付けておきます。(オプション)
-# 起動したインスタンスに名前を付ける
+```Bash
aws ec2 create-tags --resources ${INSTANCE_ID} --tags Key=Name,Value=ecs-task-instance
+```
+
+インスタンスが起動完了するまで待ちましょう。
-####################################
-# タスク実行
-####################################
+成功するとクラスターに登録されています。
-# サンプルを S3 にアップロード
+![amazon-ecs-2.png](https://qiita-image-store.s3.amazonaws.com/0/226829/f17e17d7-64d9-839f-4ad8-334f1c782c89.png)
+
+
+## 8. タスクの実行
+
+ジョブを投入する前に、入出力用の S3 バケットを作成し、適当なデータを置いておきます。
+
+```Bash
cat << EOF > Humpty.txt
Humpty Dumpty sat on a wall,
Humpty Dumpty had a great fall.
All the king's horses and all the king's men
Couldn't put Humpty together again.
EOF
aws s3 cp Humpty.txt s3://${S3_BUCKET}/
+```
+
+いよいよタスクを実行してみます。
-# タスク実行
+```Bash
cat << EOF > containerOverrides.json
{
"containerOverrides": [
{
"name": "mytask-definision",
"environment": [
{
"name": "INPUT",
"value": "s3://${S3_BUCKET}/Humpty.txt"
},
{
"name": "OUTPUT",
"value": "s3://${S3_BUCKET}/Humpty.count.ecs.txt"
}
]
}]
}
EOF
aws ecs run-task \
--cluster ${CLUSTER_ARN} \
--task-definition ${TASK_DEFINITION_ARN} \
- --overrides file://containerOverrides.json \
- > run-task.log
+ --overrides file://containerOverrides.json
+```
-TASK_ARN=$(jq -r '.tasks[0].taskArn' run-task.log)
+作成したタスクは「AWSコンソール」→「Elastic Container Service」→「Amazon ECS」→「クラスター」から確認できます。
-# タスク終了を待つ
-while :
-do
- aws ecs describe-tasks --tasks ${TASK_ARN} --cluster ${CLUSTER_ARN} \
- > describe-tasks.log
+![amazon-ecs-3.png](https://qiita-image-store.s3.amazonaws.com/0/226829/99283191-0039-fca9-8499-1c8962190ae2.png)
- TASK_STATE=$(jq -r '.tasks[0].lastStatus' describe-tasks.log)
+タスク終了後に新しくタスクを投入したい場合は、この「8. タスクの実行」だけを繰り返せばよいですが、並列実行させるためには「7. EC2 インスタンスを起動する」も行う必要があります。
- if test "${TASK_STATE}" != "RUNNING"; then
- break
- fi
+s3://${S3_BUCKET}/Humpty.count.txt が出力されていれば成功です。
+なお、スクリプトの実行ログは「AWSコンソール」→「ClowdWatch」→「ログ」から確認できます。
- aws ecs wait tasks-stopped --tasks ${TASK_ARN} --cluster ${CLUSTER_ARN}
-done
+## 9. 片付け
-####################################
-# 片付け
-####################################
+AWS batch はコンピュート環境やタスク定義には料金がかからず実際に立ち上げたインスタンスとデータ転送量、AWS ECR イメージサイズ、ClowdWatch(ログ)に課金されます。
-# EC2 インスタンスを削除
+
+インスタンスを削除します。
+AWS Batch は自動で削除してくれましたが、Amazon ECS では自分で削除します。
+ひとまずこれで安心です。
+
+```Bash
aws ec2 terminate-instances --instance-ids ${INSTANCE_ID}
-aws ec2 wait instance-terminated --instance-ids ${INSTANCE_ID}
+```
+タスク定義、クラスターを削除します。
+
+AWSコンソールから削除してもよいですが、AWS CLI でのコマンド例も記載しておきます。
+
+```Bash
# タスク定義を削除
aws ecs deregister-task-definition --task-definition ${TASK_DEFINITION_ARN}
# クラスターを削除
-aws ecs delete-cluster --cluster ${CLUSTER_ARN}
+aws ecs delete-cluster --cluster ${CLUSTER_ARN}
```
-上記スクリプトが1回で成功した場合は問題ありませんが、何回かやり直した場合はタスク定義のリビジョンが複数できていると思いますので AWS コンソールから手動で削除してください。
-
-logは自動削除していませんので、手動で消します。
+AWSが出力した log を削除します
「AWSコンソール」→「ClowdWatch」→「ログ」→該当するロググループをクリック→ログストリームを選択して、「ログストリームの削除」
「AWSコンソール」→「ClowdWatch」→「ログ」→該当するログを選択→ログストリームを選択して、「アクション」→「ロググループの削除」
AWS CLI で実行する場合は以下です。
```Bash
# ログストリームを削除
# 複数ある場合はそれぞれ削除してください
aws logs delete-log-stream --log-group-name mytask --log-stream-name ecs-test/mytask-definision/{36桁のコード}
# ロググループを削除
aws logs delete-log-group --log-group-name mytask
```
---
-今回は wait コマンドを使用して、タスク実行のスクリプト化を行いました
-次回[汎用 docker イメージを使用する](https://qiita.com/aokad/private/0e6a22082122cebc30bb)では、AWS CLI や docker run の時に実行するスクリプトファイルが入っていない docker イメージを使用する方法を記載します。
+今回はコマンドを順に解説しました。
+インスタンスの起動やタスクの終了など待ちが発生するので、実際に使うためには待ちをどうにかして実装する必要がありそうです
+次回[スクリプト化する](https://qiita.com/aokad/private/0eacf96fcb2378c7a36c)では、wait サブコマンドで実現する方法を記載します。