はじめに
前回の記事ではAmazon ECSの機能を全般的に広く浅く紹介しました。今回はAmazon ECSのタスク定義について詳しく調べてみました。
Amazon ECSのタスク定義についておさらい
Amazon ECSには大きく分けて4つの概念があります。
- クラスター: タスクまたはサービスの論理グループ
- サービス: タスクのスケジューラ
- タスク: タスク定義を元に作られるアプリケーションの実行単位
- タスク定義: DockerイメージやCPU、メモリの量などタスクの定義
クラスターの中にサービスがあり、サービスがタスクを立ち上げて維持します(サービスを介せずにタスクを直接立ち上げることもできます)。
Amazon ECSの概要については前回の記事をご覧ください。
タスク定義
タスク定義では以下のような内容を定義します。
- タスクの各コンテナで使用するDockerイメージ
- タスク及び各コンテナで使用するCPUやメモリの量
- タスクの起動タイプ(Fargate、EC2など)
- タスクのコンテナで使用するDockerネットワークモード
- タスクで使用するロギング設定
- タスクで使用されるIAMロール
1つのタスク定義の中に複数のコンテナを定義することができます。ただし、ECSでは1つのタスク定義だけでアプリケーションを構築するのではなく、複数のタスク定義を組み合わせてアプリケーションを構築することを推奨しています。
コンテナをどのようにタスク定義に振分けるかについては次で解説します。
アプリケーションのアーキテクチャ
タスク定義では、FargateもしくはEC2どちらかの起動タイプを選択します。
Fargate起動タイプ
公式ドキュメントによると、Fargate起動タイプは以下のワークロードに適しています。
- 運用上で低いオーバーヘッドを必要とする大規模なワークロード
- 時折バーストが発生する小さなワークロード
- 小さなワークロード
- バッチワークロード
AWS Fargateを使用する場合、複数をコンテナを単一のタスク定義に配置するか、それとも複数のタスク定義に分けて配置するか、どちらかを選択することになります。
以下のような要件がある場合は、同一のタスク定義にコンテナを配置するのが良いようです。
- 各コンテナが同じライフサイクルを共有している(起動と終了が同時)
- 実行基盤となるホストが同じになるようにコンテナを実行する(localhostポート上の別のコンテナを参照する)必要がある
- コンテナがリソースを共有する必要がある
- コンテナがデータボリュームを共有している
上記の要件が当てはまらない場合は、複数のタスク定義でコンテナを個別にデプロイするのが良いようです。別々にすることで、スケーリング、プロビジョニング、プロビジョニング解除を個別に行えるメリットがあります。
EC2起動タイプ
公式ドキュメントによると、EC2起動タイプは料金を最適化する必要があるような大規模なワークロードに適しています。他にも、GPUを使いたいケースの場合は、現状ではEC2起動タイプを選択する必要がありそうです。
タスク定義パラメータ
タスク定義は、タスクファミリ、IAMタスクロール、ネットワークモード、コンテナ定義、ボリューム、タスク配置の制約事項、起動タイプの各部分に別れています。この内、タスクファミリとコンテナの定義は必須項目ですがそれ以外の項目は省略することができます。
タスク定義はJSON形式で記述されます。ここではその内のいくつかを紹介します。
family
文字列型の必須項目です。
タスク定義を登録するときにはファミリー(複数バージョンのタスク定義の名前のようなもの)を指定する必要があります。登録したタスク定義にはリビジョン番号が与えられ、
ファミリー:リビジョン番号
の形式で表示されます。
taskRoleArn
文字列型の非必須項目です。
タスク定義を登録するときにIAMロールを割り当てることができ、タスクのコンテナにポリシーに指定されたAWS APIを呼び出すためのアクセス権限を付与できます。
詳しくはこちらをご覧ください。
executionRoleArn
文字列型の非必須項目です。
こちらもタスク定義を登録する際にIAMロールを割り当てるでき、Amazon ECSコンテナエージェントに権限を付与することができます。
詳しくはこちらをご覧ください。
networkMode
文字列型の非必須項目です。
タスクのコンテナで使用するDockerネットワークモードを指定することができます。有効な値としては
none
、
bridge
、
awsvpc
、
host
があり、デフォルトは
bridge
です。Fargate起動タイプを使用する場合は
awsvpc
を指定する必要があります。
awsvpc
を指定する場合は、タスクにElastic Network Interfaceが割り当てられます。そのため、タスク定義を使用したサービスの作成時、または、タスクの実行時にNetworkConfiguration(VPCやサブネット)を指定する必要があります。
cpuとmemory
どちらも文字列型の必須項目(Fargate起動タイプの場合)です。
cpu
はタスクに適用されるCPUユニットの制限で、CPUユニット数(256など)またはvCPU(.25 vCPUなど)で表します。
memory
はタスクに適用されるメモリの制限で、MiBを使用した整数(512など)またはGBを使用した文字列(0.5 GBなど)で表されます。
cpu
の値ごとにとれる
memory
の値の範囲が決まっていますので注意してください。
containerDefinitions
コンテナの定義に関わるオブジェクトの配列です。
Dockerイメージ、環境変数、secrets、ポートマッピングなど様々な設定項目があります。非常に多くの項目があるため詳細は割愛します。詳しくはこちらをご覧下さい。
定義例
参考までに前回の記事で作成したタスク定義のJSONを載せます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | { "ipcMode": null, "executionRoleArn": "arn:aws:iam::XXXX:role/ecsTaskExecutionRole", "containerDefinitions": [ { "dnsSearchDomains": null, "environmentFiles": null, "logConfiguration": { "logDriver": "awslogs", "secretOptions": null, "options": { "awslogs-group": "/ecs/echo-sample", "awslogs-region": "ap-northeast-1", "awslogs-stream-prefix": "ecs" } }, "entryPoint": [], "portMappings": [ { "hostPort": 80, "protocol": "tcp", "containerPort": 80 } ], "command": [], "linuxParameters": null, "cpu": 0, "environment": [], "resourceRequirements": null, "ulimits": null, "dnsServers": null, "mountPoints": [], "workingDirectory": null, "secrets": null, "dockerSecurityOptions": null, "memory": null, "memoryReservation": 128, "volumesFrom": [], "stopTimeout": null, "image": "XXXX.dkr.ecr.ap-northeast-1.amazonaws.com/ecs-echo-sample:latest", "startTimeout": null, "firelensConfiguration": null, "dependsOn": null, "disableNetworking": null, "interactive": null, "healthCheck": null, "essential": true, "links": null, "hostname": null, "extraHosts": null, "pseudoTerminal": null, "user": null, "readonlyRootFilesystem": null, "dockerLabels": null, "systemControls": null, "privileged": null, "name": "echo" } ], "memory": "512", "taskRoleArn": "arn:aws:iam::XXXX:role/ecsTaskExecutionRole", "family": "echo-sample", "pidMode": null, "requiresCompatibilities": [ "FARGATE" ], "networkMode": "awsvpc", "runtimePlatform": { "operatingSystemFamily": "LINUX", "cpuArchitecture": null }, "cpu": "256", "inferenceAccelerators": [], "proxyConfiguration": null, "volumes": [], "tags": [ { "key": "test key", "value": "test value" } ] } |
余談ですが、タスク定義のJSONを取得する方法はいくつかあります。例えば、既存のタスク定義の「新しいリビジョンの作成」画面の下部にある「JSONによる設定」を開くと表示されるもの。タスク定義の「JSON」タブに表示されているもの。
aws cli
で取得できるものなどです。これらはいずれも表示される項目が微妙に異なります(cliで取得できる値は特に異なる)。ここで表示しているのは「JSONによる設定」で表示されたJSONです。
ちなみに、JSONからタスク定義を作成する方法は2種類あります。1つ目は「JSONによる設定」に貼り付ける方法。2つ目は
aws cli
の
aws ecs describe-task-definition
コマンドで登録する方法です。
タスクでのデータボリュームの使用
ECSでは以下のデータボリュームをサポートしています。
- Fargate タスクストレージ
- Amazon EFS ボリューム
- FSx for Windows File Server ボリューム
- Docker ボリューム
- バインドマウント
ただし、Fargate起動タイプを選ぶ場合は、タスクストレージ、EFS、バインドマウントのみ利用できるようです。
Fargate タスクストレージ
Fargateでホストされている各タスクは、プロビジョニングされる際にバインドマウントのためのエフェメラル (ローカル)ストレージを受け取ります。これらをマウントし、タスク定義内で
volumes
、
moutPoints
および
volumesFrom
パラメータを使用しているコンテナ間で共有することができます。
ECSタスクはデフォルトで20GiBの(最大200GiBまで増やせる)エフェメラルストレージを受け取ります。
pull、compress、uncompressされたコンテナイメージはエフェメラルストレージに保存されます。つまり、コンテナイメージの容量+指定したエフェメラルストレージの容量がコンテナの総容量になります。
Amazon EFS ボリューム
EFS(Elastic File System)では、ECSタスクで使用するためのシンプルでスケーラブルなファイルストレージを提供します。このストレージ容量は伸縮性があり、ファイルの追加や削除に伴って自動的に拡大、縮小されます。
EFSを使用すると各タスクは配置されているインスタンスに関わらず、常に同じ永続ストレージにアクセスできます。
Docker ボリューム
組み込みの
local
ドライバー、またはサードパーティのボリュームドライバーを使用できます。Docker ボリュームはDocker で管理され、ディレクトリはボリュームデータを含むコンテナインスタンスの
/var/lib/docker/volumes
に作成されます。
Docker ボリュームを使用するには、タスク定義で
dockerVolumeConfiguration
を指定します。
バインドマウント
ホスト(FargateまたはEC2インスタンス)上のファイルまたはディレクトリがコンテナにマウントされます。
デフォルトではバインドマウントは、それらを使用しているコンテナのライフサイクルに紐付けられています。タスクが停止するなど、バインドマウントを使用するすべてのコンテナが停止すると、データが削除されます。
環境変数の引き渡し
環境変数は以下の方法でコンテナに渡すことができます。
- コンテナ定義パラメータの
environment
に個別に定義する - コンテナ定義パラメータの
environmentFiles
にファイルを指定する
environmentに個別に定義する
以下のように
name
と
value
を定義します。これは
docker run
の
--env
オプションにマッピングされます。
1 2 3 4 5 6 7 8 9 10 11 12 | { "containerDefinitions": [ { "environment": [ { "name": "env name", "value": "env value" } ] } ] } |
environmentFilesにファイルを指定する
S3にホストされている.envファイルを指定します。これは
docker run
の
--env-file
オプションにマッピングされます。
1 2 3 4 5 6 7 8 9 10 11 12 | { "containerDefinitions": [ { "environmentFiles": [ { "value": "arn:aws:s3:::s3_bucket_name/envfile_object_name.env", "type": "s3" } ] } ] } |
コンテナへの機密情報の受け渡し
AWS Secret Managerのsecrets、またはAWS System Manager Parameter Storeのパラメータに機密情報を保存した上で、コンテナ定義から参照することによってコンテナに機密情報を取り込む事ができます。
以下の方法でsecretsををコンテナに公開できます。
- containerDefinitionsのsecretsパラメータを使用して、機密情報を環境変数としてコンテナに挿入する
- containerDefinitionsのsecretOptionsを使用して、ログ設定へ機密情報を挿入する
タスク定義でSecret Managerシークレットを参照する
コンテナ定義でSecret Managerシークレットを参照する方法を示します。
1 2 3 4 5 6 7 8 | { "containerDefinitions": [{ "secrets": [{ "name": "environment_variable_name", "valueFrom": "arn:aws:secretsmanager:region:aws_account_id:secret:secret_name-AbCdEf" }] }] } |
ほかにも、シークレット内の特定のキーの参照や特定のバージョンシークレットを参照することもできます。詳しくはこちらをご覧下さい。
環境変数として機密情報を挿入
コンテナ定義でコンテナに設定する環境変数名と、コンテナに渡す機密情報が含まれているSystems Manager Parameter Storeのパラメータの完全なARNを使用してsecretを指定します。
1 2 3 4 5 6 7 8 | { "containerDefinitions": [{ "secrets": [{ "name": "environment_variable_name", "valueFrom": "arn:aws:ssm:region:aws_account_id:parameter/parameter_name" }] }] } |
Secrets ManagerとParameter Storeどちらが良いか
本記事の内容から外れている為多くは語りませんが、小規模Webシステムなどパラメータへのアクセス頻度が少ない場合はParameter Storeを使い、より多くの取得リクエストがある場合はSecrets Managerを使うのが良さそう(ただし有料)です。
この2つの比較についてはこちらの記事をご覧ください。
さいごに
ECSのタスク定義について深堀りしました。次回は同じくECSのサービスについて調べみたいと思います。