|
|
ECSクラスターは以下のコマンドで作成する。
aws ecs create-cluster --cluster-name ecs-test-cluster
ECS(Docker)のホストとしてEC2インスタンス(EC2コンテナと呼ばれる)を使う場合、EC2インスタンスを作成してECSクラスターに登録する必要がある。
EC2コンテナインスタンスの作成元となるAMIは、Amazon
ECS-optimized AMIを使う。
このAMIでは、EC2インスタンス起動時にECSプロセスが実行され、ECSクラスターへの登録が行われる。
登録に成功すると、ECSのウェブコンソールの該当クラスターの表示で、EC2インスタンスの個数が増える。
EC2コンテナインスタンスには/etc/ecs/ecs.configという設定ファイルがあり、登録先となるECSクラスター名をそこに書く。
EC2インスタンス起動時にecs.configを作成するよう、EC2インスタンス作成時のユーザーデータスクリプトを記述する。
# EC2インスタンスの作成 aws ec2 run-instances --image-id AMI 〜 --user-data ユーザーデータスクリプト.sh
#!/bin/bash echo ECS_CLUSTER=ecs-test-cluster >> /etc/ecs/ecs.config
ちなみに、--user-dataで指定したユーザーデータスクリプトは、EC2インスタンスの/var/lib/cloud/instance/user-data.txtというファイルで保存されている。
ecs.configにはプロキシーの設定を書くことも出来る。
→Amazon Linux HTTP プロキシのユーザーデータスクリプトの例
EC2コンテナを起動したのにECSクラスターに登録されない場合、登録に失敗していると思われる。
EC2コンテナインスタンスのec2-userにログインし(EC2インスタンス作成時にキーペアを指定しておき、それでログインする)、ログを確認する。
view /var/log/ecs/ecs-agent.log
ECSクラスターへの登録の為にhttps://ecs.ap-northeast-1.amazonaws.com/にアクセスするようだが、エラーになっている場合、そこに繋がらない可能性が高い。
例えばプロキシーを使っている場合、/etc/ecs/ecs.configにプロキシーの設定が書けるので、それを変更する。
ecs.configを変更した後は以下のコマンドでECSプロセスを再起動する。
sudo systemctl restart ecs
参考:aikan-umetsuboさんのECSインスタンスがクラスターに登録されない
Docker上で実行するコマンドは、タスクとして定義しておく。
(タスク定義自体にはECSクラスターは関係しない)
タスク定義はjsonファイルで、使用するDockerイメージや稼動するホストの種類(EC2かFargateか)および実行するコマンドを記述する。
{ "family": "test-task", "containerDefinitions": [ { "name": "test-app", "image": "123456789123.dkr.ecr.ap-northeast-1.amazonaws.com/test-ecs:latest", "essential": true, "entryPoint": ["sh", "-c"], "command": ["exit 123"] } ], "requiresCompatibilities": [ "EC2" ], "cpu": "256", "memory": "512" }
aws ecs register-task-definition --family test-task --cli-input-json file://~/タスク定義.json
imageにAmazon ECRに登録したDockerイメージを指定する。
requiresCompatibilitiesがホストの種類(EC2やFARGATE)。
cpuは必要とするCPUで、「vCPU×1024」を指定する。
memoryは使用するメモリー。単位はMiB(メビバイト)。
commandに実行するコマンドを書く。
entryPointとcommandが合体して実行される。
上記の例だと、「sh -c 'exit 123'
」が実行されることになる。(sh
-cは、実行するコマンドを引数の文字列で指定するもの)
(タスク実行時に引数の値を変えたい場合は、run-taskのオーバーライド機能を使う)
ECSでは(Dockerでは?)、標準出力に出力された文字列を取得できないので、試しに実行する場合はexitコマンドで戻り値を見るのが良いと思う。
→CloudWatchを使用して標準出力の内容を取得する例
DockerコンテナのホストとしてFargateを使う場合は、networkModeやexecutionRoleArnも指定する。
{ 〜 "requiresCompatibilities": [ "FARGATE" ], "networkMode": "awsvpc", "cpu": "256", "memory": "512", "executionRoleArn": "arn:aws:iam::123456789123:role/ecsTaskExecutionRole" }
以下のコマンドでタスクを実行する。
aws ecs run-task --cluster ecs-test-cluster --task-definition test-task --propagate-tags TASK_DEFINITION
タスクを実行すると、実行情報がjson形式で返ってくる。
その中にtaskArn(タスクID)がある。
run-taskはタスクの実行を開始するだけで、終了は待たない。(処理本体は非同期で実行される)
なので、taskArnを使って結果を確認する。
以下のようにすると、taskArnだけを取得できる。(エラーが発生するとTASK_ARNが空になる上に、エラー理由が握りつぶされるので、あまり良くないが)
TASK_ARN=$(aws ecs run-task 〜 --query "tasks[].taskArn" --output text)
タスク定義でFARGATEが指定されている場合、DockerコンテナのホストはFargateになる。
ただ、Fargateも実体としてはEC2インスタンスが起動されると思う。
FargateのEC2インスタンスにサブネットやらセキュリティーグループやらを指定したい場合は、run-taskの--network-configurationオプションのawsvpcConfigurationで指定する。
タスク定義で指定されたmemoryがホストで確保できない場合(他のタスクが使用中で自分の分が確保できない場合)は、以下のようなjsonが返ってくる。[2021-07-13]
(aws ecs run-task自体の終了コードは0)
{ "tasks": [], "failures": [ { "arn": "arn:aws:ecs:ap-northeast-1:123456789123:container-instance/〜", "reason": "RESOURCE:MEMORY" } ] }
ホスト(コンテナインスタンス)が複数台ある場合は、複数行出力されることがある。
ホストが起動していない場合はreasonが「AGENT」になる。
タスクの実行時に、タスク定義の一部を上書きする機能がある。
実行するコマンドの引数を変えたい場合等に利用する。
{ "containerOverrides": [ { "name": "test-app", "command": ["exit 22"] } ] }
aws ecs run-task 〜 --overrides file://~/オーバーライド.json
上記の例だと、コマンドそのものがオーバーライド.jsonのものに上書きされる。
ファイルを使わず直接記述することも出来る。
aws ecs run-task 〜 --overrides '{"containerOverrides": [{"name": "test-app", "command": ["exit 22"]}]}'
VALUE=22 OVERRIDE=$(cat << EOF { "containerOverrides": [ { "name": "test-app", "command": ["exit $VALUE"] } ] } EOF ) aws ecs run-task 〜 --overrides "$OVERRIDE"
タスク定義とオーバーライドの組み合わせの例。
タスク定義 | オーバーライド | 備考 |
---|---|---|
"command": ["exit 123"] |
なし | 「exit 123」が実行される。 |
"command": ["exit 123"] |
"command": ["exit 22"] |
「exit 22」が実行される。 |
"command": ["exit 123"] |
"command": ["exit $TEST"], |
環境変数を使用する例。 「exit 33」が実行される。 |
"command": ["exit $TEST"] |
"environment": [ |
タスク定義で環境変数を使用し、オーバーライドで環境変数を定義する例。 「exit 44」が実行される。 |
タスクの実行状況・実行結果は以下のコマンドで確認する。
aws ecs describe-tasks --cluster ecs-test-cluster --tasks $TASK_ARN
{ "tasks": [ { "attachments": [], "availabilityZone": "ap-northeast-1c", "clusterArn": "arn:aws:ecs:ap-northeast-1:123456789123:cluster/ecs-test-cluster", 〜 "containers": [ { "containerArn": "arn:aws:ecs:ap-northeast-1:123456789123:container/ecs-test-cluster/〜", "taskArn": "arn:aws:ecs:ap-northeast-1:123456789123:task/ecs-test-cluster/〜", "name": "test-app", "image": "123456789123.dkr.ecr.ap-northeast-1.amazonaws.com/test-ecs:latest", 〜 "lastStatus": "STOPPED", "exitCode": 123, 〜 "cpu": "0" } ], "cpu": "256", 〜 "lastStatus": "STOPPED", "launchType": "EC2", "memory": "512", 〜 "stopCode": "EssentialContainerExited", "stoppedAt": "2021-07-10T10:19:01.908000+09:00", "stoppedReason": "Essential container in task exited", "stoppingAt": "2021-07-10T10:19:01.908000+09:00", "tags": [], "taskArn": "arn:aws:ecs:ap-northeast-1:123456789123:task/test-cluster/〜", "taskDefinitionArn": "arn:aws:ecs:ap-northeast-1:123456789123:task-definition/test-task:24", "version": 3 } ], "failures": [] }
lastStatusが状態を表している。主に、実行中はPENDING、終了するとSTOPPEDになる。(→タスクのライフサイクル)
stopCodeはエラーコードのようなもの。たぶんEssentialContainerExitedは正常終了。
stoppedReasonはエラーメッセージ。
exitCodeはコマンドの戻り値(終了コード)。
実行が終わるまでポーリングするなら、以下のような感じか。
while true do STATUS=$(aws ecs describe-tasks 〜 --query "tasks[].lastStatus" --output text) if [ "$STATUS" = "STOPPED" ]; then break else sleep 3 fi done
この例では3秒間隔にしてみたが、run-taskのドキュメントによると、数秒から始めて間隔を増やしていき、最大は5分間隔だそうだ。
実行が失敗した場合、stopCodeやstoppedReasonにエラーコード・エラー理由が入ってくる。
"containers": [ { "lastStatus": "STOPPED", "reason": "CannotStartContainerError: Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused \"process_linux.go:319: getting the final child's pid from pipe caused \\\"read init-p: connection reset by peer\\\"\": u", } ], "lastStatus": "STOPPED", "stopCode": "TaskFailedToStart", "stoppedReason": "Task failed to start",
コンテナのエラーメッセージ(containersのreason)の中に「停止タスクのエラーコード」が入っていることがある。
例えばCannotPullContainerError(DockerイメージがECRから持ってこられない)とか。
describe-tasksでタスクの実行結果が確認できるが、エラーが発生した場合は結果のjson(containersのreason)にエラー理由 が出力される。[2021-07-13]
また、EC2コンテナインスタンスで実行した場合は、EC2インスタンスの/var/log/ecs/ecs-agent.logでもエラーが確認できる。
(jsonに入ってくるエラー理由のメッセージは長すぎると途中でカットされるので、ecs-agent.logの方が全文を見ることができて良いかも)
タスク定義(あるいはオーバーライド)のmemoryが小さすぎると、以下のようなエラーが発生する。
level=info time=2021-07-10T08:10:35Z msg="Task engine
[arn:aws:ecs:ap-northeast-1:123456789123:task/ctc-ecs-digdag-test-cluster/〜]:
error transitioning container [ctc-ecs-digdag-test-app (Runtime ID: 〜)] to
[RUNNING]: Error response from daemon: OCI runtime create failed:
container_linux.go:349: starting container process caused
\"process_linux.go:319: getting the final child's pid from pipe caused \\\"read
init-p: connection reset by peer\\\"\": unknown" module=docker_task_engine.go
level=info time=2021-07-10T08:10:35Z msg="Managed task
[arn:aws:ecs:ap-northeast-1:123456789123:task/ctc-ecs-digdag-test-cluster/〜]:
Container [name=ctc-ecs-digdag-test-app runtimeID=〜]: handling container change
event [RUNNING]" module=task_manager.go
level=warn time=2021-07-10T08:10:35Z msg="Managed task
[arn:aws:ecs:ap-northeast-1:123456789123:task/ctc-ecs-digdag-test-cluster/〜]:
error starting/provisioning container[ctc-ecs-digdag-test-app (Runtime ID: 〜)];
marking its desired status as STOPPED: Error response from daemon: OCI runtime
create failed: container_linux.go:349: starting container process caused
\"process_linux.go:319: getting the final child's pid from pipe caused \\\"read
init-p: connection reset by peer\\\"\": unknown" module=task_manager.go
ECSタスクのコマンドが標準出力に出力したメッセージは、そのままでは取得できない。
CloudWatchにログとして出力して、そちらから取得するのが一般的なようだ。
参考:michimaniさんのECS タスクを作って実行して CloudWatch でログを確認するまでを AWS CLI だけでやってみた
CloudWatchにログ出力する場合、まず、出力先となるロググループ名を定義する。
そして、タスク定義の中で、ログ出力先としてロググループ名を指定する。
aws logs create-log-group --log-group-name test-log-group
ロググループを作っておかないと、そのロググループ名を使ったタスクの実行時に以下のようなエラーが発生する。
CannotStartContainerError: Error response from daemon: failed to initialize logging driver: failed to create Cloudwatch log stream: ResourceNotFoundException: The specified log group does not exist.
{
"family": "test-task",
"containerDefinitions": [
{
"name": "test-app",
"image": "123456789123.dkr.ecr.ap-northeast-1.amazonaws.com/test-ecs:latest",
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "test-log-group",
"awslogs-stream-prefix": "log-prefix"
}
},
"essential": true,
"entryPoint": ["sh", "-c"],
"command": ["echo Hello-example-ecs-app!"]
}
],
"requiresCompatibilities": [
"EC2"
],
"cpu": "256",
"memory": "512"
}
なお、オーバーライドではログの定義を上書き(変更)することが出来ない。
(オーバーライドでコマンドの引数を変えるのと同様に、ログの出力先を変えたいことはあると思うのだが…。ロググループ名は変えられないとしても、せめて接頭辞くらい変えられたらいいのに)
出力されたログはCloudWatchのウェブコンソールから見られる他、以下のコマンドでも確認できる。
aws logs describe-log-streams --log-group-name test-log-group
{ "logStreams": [ { "logStreamName": "log-prefix/test-app/abcdef12345678901234567890abcdef", "creationTime": 1625891374154, "firstEventTimestamp": 1625891375047, "lastEventTimestamp": 1625891375047, "lastIngestionTime": 1625891375112, "arn": "arn:aws:logs:ap-northeast-1:123456789123:log-group:test-log-group:log-stream:log-prefix/test-app/abcdef12345678901234567890abcdef", 〜 } ] }
ログストリーム名が「ログストリーム接頭辞/アプリケーション名/ID」という構造で付けられる。
同じロググループに複数のログ出力を行うと、logStreamsにそれら全てが含まれる。(IDが異なるので区別はつく)
aws logs get-log-events --log-group-name test-log-group --log-stream-name log-prefix/test-app/abcdef12345678901234567890abcdef
{ "events": [ { "timestamp": 1625891375047, "message": "Hello-example-ecs-app!", "ingestionTime": 1625891375112 } ], 〜 }
このmessageが、ECSタスクのコマンドが標準出力に出力した内容となる。
json形式だと扱いづらいので、以下のようなコマンドなら最新のメッセージだけ取得できる。
LOG_ST_NAME=$(aws logs describe-log-streams --log-group-name test-log-group --query "max_by(logStreams[?starts_with(logStreamName, 'log-prefix/')], &lastEventTimestamp).logStreamName" --output text) aws logs get-log-events --log-group-name test-log-group --log-stream-name "$LOG_ST_NAME" --query 'events[].message' --output text
ちなみに、--queryで指定するクエリーはJMESPath構文というらしい。かなり色々なことが出来る。