[ECS] タスクのマイグレーションから触れるAWS Fargate #reinvent

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちはこむろです。AWS re:Invent 2017では様々な機能がリリースされ、未だ興奮冷めやらぬといった状況です。特に既存のサービスに追加された強力な機能が非常に多く、AWS Fargateもそんな強力な追加機能の一つです。

ちなみに自分はECSはそこまでハードに使っているわけでもなく、開発環境の一部にDockerを導入している程度の知識です。Fargateの登場により、今まで鬼門であったクラスタインスタンスEC2の管理・運用の手間がなくなるとのことで、今後の開発への導入を画策している次第です。

Fargateおさらい

ECSではコンテナを起動するためのクラスタインスタンスEC2が必要でした。そのため、結果としてインフラストラクチャとしてのEC2を管理・メンテナンスせざるを得ませんでした。Fargateの登場により、クラスタインスタンスEC2がなくてもContainerを直接起動することができるようになりました。今まで自分で管理していたEC2が不要になります。

これにより待機時のインスタンス稼働によるコスト圧縮が可能に、さらにOperation Human Errorなどに起因するサービス障害、稼働率の低下、人による管理コストの低下などあらゆる面で有利になると思われます。

ただし、コストに関しては注意が必要です。24時間常時動作させるContainerに関しては、ECSでクラスタインスタンスを起動したほうがコストの低減が図れるようです。そのため、すべてのアプリケーションがFargate一択で良いわけではなく、この辺りはアプリケーションの性格により使い分けが必要になりそうです。

詳細はこれらのリンクを参照いただくととてもわかり易いと思います。

ECSの起動オプションの一つ

FargateはECSの起動オプションの一つです。Launch typeをFargateにするのといくつかのオプションを追加するだけで既存のECS TaskをFargateで起動することが可能です。

既存のタスクはFargateで起動する場合には、そのままでは機能できず新しいRevisionを定義し、Launch Typeの指定、ネットワーキングモードを awsvpc へ、さらにSecurity Groupなどの指定が必要でした。それ以外は特に気にすることなく、そのままタスクを実行するだけでインスタンスが存在しないにも関わらず正常にサービスが起動されました。不思議!

わかりやすいダッシュボード

インスタンス立ち上げてないし、CPU UtilizationもMemory Utilizationもないですね。まあ当然。

Task Migration

今までEC2で動作していたTaskをFargateで実行してみます。

そのままでは起動できない

今までECSで動作していたTaskはそのままでは起動できません。

新しいrevisionを作れと言われています。

新しいrevisionでFargateを指定する

新しいrevisionを作成します。Fargateを指定してみるも思いっきりネットワークモードが bridge じゃダメと怒られます。素直に awsvpc を選択しましょう。

vCPUとMemoryの指定

単位がEC2の場合と異なりますが、選択肢になってるのでわかりやすくなっています。CLIからの実行の場合は選択肢が出てくれるわけではないので注意が必要かもしれませんが。

ちなみにEC2を選択していると以下のような表示。

直接値を入力できるため自由度はEC2の指定の方が高いようです。

作成で完了

他は変更する部分がなかったのでそのまま 作成 をクリック。作成が完了しました。

これでFargateで起動可能なTask定義のrevisionが完成しました。

Fargateで再度起動する

再度Fargateを指定して起動してみます。

今度は特に怒られずに手順がススメられました。

VPCの設定、Subnetの設定、Security Groupの設定をサクサクおこなっていきます。新しく作成されるSecurity GroupはすべてのIPから80ポートを受け付ける設定になっているようです。

作成で完了。

あとはRUNNINGステータスとなるまで待ちます。

Task定義JSONのDiff

ところで起動するTaskの定義にはどんな違いがあるのでしょうか。

同じSampleアプリの定義でECSとFargateの変更をした際にJsonがどのように変化するのかを確認してみました。手順として正しいのか分かりませんが、新しいRevisionを作成する際にEC2とFargateを選択する事ができたので、revision:1をFargateで作成し、revision:2の際にEC2で作成するようにしました。

片やEC2クラスタで起動し、片やFargateで起動しています。いずれもALBに属し、同じSampleAppをDeployしている状態です。

EC2クラスタでの起動

今まで通りクラスタインスタンスのEC2を立ち上げ、その中でServiceを立ち上げます。HostからContainerのPort Mappingなど諸々準備をする必要があり、面倒といえば面倒です。

{
  "executionRoleArn": "arn:aws:iam::xxxxxxxxxxxx:role/ecsTaskExecutionRole",
  "containerDefinitions": [
    {
      "dnsSearchDomains": null,
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/sample-app-definition",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "ecs"
        }
      },
      "entryPoint": [
        "sh",
        "-c"
      ],
      "portMappings": [
        {
          "hostPort": 80,
          "protocol": "tcp",
          "containerPort": 80
        }
      ],
      "command": [
        "/bin/sh -c \"echo '<html> <head> <title>Amazon ECS Sample App</title> <style>body {margin-top: 40px; background-color: #333;} </style> </head><body> <div style=color:white;text-align:center> <h1>Amazon ECS Sample App</h1> <h2>Congratulations!</h2> <p>Your application is now running on a container in Amazon ECS.</p> </div></body></html>' >  /usr/local/apache2/htdocs/index.html && httpd-foreground\""
      ],
      "linuxParameters": null,
      "cpu": 256,
      "environment": [],
      "ulimits": null,
      "dnsServers": null,
      "mountPoints": [],
      "workingDirectory": null,
      "dockerSecurityOptions": null,
      "memory": null,
      "memoryReservation": 512,
      "volumesFrom": [],
      "image": "httpd:2.4",
      "disableNetworking": null,
      "essential": true,
      "links": [],
      "hostname": null,
      "extraHosts": null,
      "user": null,
      "readonlyRootFilesystem": null,
      "dockerLabels": null,
      "privileged": null,
      "name": "sample-app"
    }
  ],
  "placementConstraints": [],
  "memory": "512",
  "taskRoleArn": "arn:aws:iam::xxxxxxxxxxxx:role/ecsTaskExecutionRole",
  "compatibilities": [
    "EC2"
  ],
  "taskDefinitionArn": "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:task-definition/sample-app-definition:2",
  "family": "sample-app-definition",
  "requiresAttributes": [
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.task-iam-role"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "ecs.capability.execution-role-awslogs"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.logging-driver.awslogs"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.docker-remote-api.1.21"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.docker-remote-api.1.19"
    }
  ],
  "requiresCompatibilities": [
    "EC2"
  ],
  "networkMode": "bridge",
  "cpu": "256",
  "revision": 2,
  "status": "ACTIVE",
  "volumes": []
}

Fargateでの起動

こちらはEC2のインフラ設定の代わりにクラスタインスタンス側で行っていたネットワーク設定等が追加されます。

{
  "executionRoleArn": "arn:aws:iam::xxxxxxxxxxxx:role/ecsTaskExecutionRole",
  "containerDefinitions": [
    {
      "dnsSearchDomains": null,
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/sample-app-definition",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "ecs"
        }
      },
      "entryPoint": [
        "sh",
        "-c"
      ],
      "portMappings": [
        {
          "hostPort": 80,
          "protocol": "tcp",
          "containerPort": 80
        }
      ],
      "command": [
        "/bin/sh -c \"echo '<html> <head> <title>Amazon ECS Sample App</title> <style>body {margin-top: 40px; background-color: #333;} </style> </head><body> <div style=color:white;text-align:center> <h1>Amazon ECS Sample App</h1> <h2>Congratulations!</h2> <p>Your application is now running on a container in Amazon ECS.</p> </div></body></html>' >  /usr/local/apache2/htdocs/index.html && httpd-foreground\""
      ],
      "linuxParameters": null,
      "cpu": 256,
      "environment": [],
      "ulimits": null,
      "dnsServers": null,
      "mountPoints": [],
      "workingDirectory": null,
      "dockerSecurityOptions": null,
      "memory": null,
      "memoryReservation": 512,
      "volumesFrom": [],
      "image": "httpd:2.4",
      "disableNetworking": null,
      "essential": true,
      "links": [],
      "hostname": null,
      "extraHosts": null,
      "user": null,
      "readonlyRootFilesystem": null,
      "dockerLabels": null,
      "privileged": null,
      "name": "sample-app"
    }
  ],
  "placementConstraints": [],
  "memory": "512",
  "taskRoleArn": null,
  "compatibilities": [
    "EC2",
    "FARGATE"
  ],
  "taskDefinitionArn": "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:task-definition/sample-app-definition:1",
  "family": "sample-app-definition",
  "requiresAttributes": [
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.docker-remote-api.1.18"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "ecs.capability.task-eni"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "ecs.capability.execution-role-awslogs"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.logging-driver.awslogs"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.docker-remote-api.1.21"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.docker-remote-api.1.19"
    }
  ],
  "requiresCompatibilities": [
    "FARGATE"
  ],
  "networkMode": "awsvpc",
  "cpu": "256",
  "revision": 1,
  "status": "ACTIVE",
  "volumes": []
}

それぞれのDiffを確認していきます(revisionの違いは当然なので無視します)

networkMode

EC2では bridge が選択されています。Fargateでは新たに追加された awsvpc が選択されていました。思いっきりコンソールで怒られた場所ですね。

requiresAttributes

requiresAttributesはいくつかのDiffがありました。

  • "ecs.capability.task-eni" : Fargateでは、requiresAttributeにこのパラメータが追加されています。
  • "com.amazonaws.ecs.capability.task-iam-role" : Fargateではこちらのパラメータが存在しません。その代わりに以下のパラメータが追加されています。
  • "com.amazonaws.ecs.capability.docker-remote-api.1.18" : Fargateに存在しますが、EC2での指定では存在しません。

ドキュメントを漁ったところであまり有用な情報がありませんでした。

requiresCompatibilities

EC2では EC2, Fargateでは FARGATE が指定されています。

compatibilities

EC2では EC2 のみ、Fargateでは EC2 の他 FARGATE も指定されているのが確認できます。利用可能なLaunch Typeが指定されるようです。この場合、Fargateの方はEC2も利用可能となっているのですが、設定値がEC2の方を包括してるってことでしょうか。

Amazon EC2 Container Service » API Reference » Data Types » TaskDefinition , Amazon EC2 Container Service » API Reference » Data Types » Attribute , Amazon ECS コンテナエージェントの設定 あたりが該当ドキュメントだと思ったのですが詳細が分からず・・・。このあたりの設定値の詳細な解説があるドキュメントの場所を知っている方がいれば教えていただければ幸いです。

Compute環境のOption

Fargateではインフラストラクチャの設定をvCPUとMemoryの組み合わせで指定するとのこと。さらに一定の規則に合致したものでないと起動できないとのことです。

CPU (vCPU) Memory Value(GB)
0.25 0.5, 1, 2
0.5 1, 2, 3, 4
1 Min. 2GB and Max. 8GB, in 1GB increments
2 Min. 4GB and Max. 16GB, in 1GB increments
4 Min. 8GB and Max. 30GB in 1GB increments

vCPUが 0.25, 0.5 を選択する際には選択肢がそこそこ絞られています。1, 2, 4の場合、それぞれMemoryサイズは1GBずつの区切りでのみ指定が可能です。中途半端な小数点での指定は不可能です。EC2での起動時にはCPUをUnit数で指定してたと思います。それぞれの対応表はこちらにありました。以下引用します。

  • 256 (.25 vCPU) - Available memory values: 512MB, 1GB, 2GB
  • 512 (.5 vCPU) - Available memory values: 1GB, 2GB, 3GB, 4GB
  • 1024 (1 vCPU) - Available memory values: 2GB, 3GB, 4GB, 5GB, 6GB, 7GB, 8GB
  • 2048 (2 vCPU) - Available memory values: Between 4GB and 16GB in 1GB increments
  • 4096 (4 vCPU) - Available memory values: Between 8GB and 30GB in 1GB increments

今回はvCPUを0.25, Memoryは512MBで指定しているため、一番最小の構成で起動しているようです。

触ってみての感想

Task Migrationはコンソールの指示にそってポチポチやっていくだけで、簡単にFargateで起動することができました。今回は非常に単純なSampleApplicationでの実行だったため特に落とし穴などにハマらなかった印象ですが *1、実際に運用しているTaskでは一筋縄ではいかない現象はあるかもしれません。ただクラスタインスタンスのEC2を少しでも少なくしたい場合はチャレンジする価値はあるかと思います。

実際EC2の一覧には何も存在しないにも関わらずhttpサーバーが起動しているのは、アプリケーションエンジニアとしては余計なリソースを見る必要がなくなるため、大きな負荷が1つなくなった印象です *2。情報は多いことに越したことはありませんが、見る必要のない人間へ情報を隠すこともとても重要です。人間の処理能力には限界があるのですから。

こういったサービスの機能強化で、エンジニアがよりビジネスドメインの実装などに集中できるようになるのはとても歓迎すべきことだと思います。積極的に利用しフィードバックを行い、より安定した安全な機能となってくれることを願っています。

参照

脚注

  1. 実際は謎のエラーに当たりましたが、該当するTask自体9ヶ月以上前に作成したものなので、Migrationするには古すぎたのかもしれません。
  2. リソースの中へ直接SSHしてOSレベルのログが見えなくなるという不安もまたありますが。そこはジレンマ