nazoです。
kubernetesの勢いが強い昨今ですが、AWSではやはりECSが便利です。
ECSは簡単に使えるものの、より便利に使うにはいくつか抑えておくポイントがあります。今回はその点をいくつか紹介したいと思います。
前提知識
ECS
ECSには「クラスタ」「サービス」「タスク定義」という概念があります。
1つの「クラスタ」には複数の「サービス」が入り、「サービス」は「タスク定義」で定義されたタスクが起動、常駐します。
「クラスタ」には複数のEC2インスタンスが登録され、登録されているEC2インスタンスの中でサービスは起動します。どのEC2インスタンスがどのクラスタに登録されるかは、EC2内にいるecs-agentコンテナがクラスタに通知する仕組みになっています。通常はEC2のユーザーデータでクラスタ名を指定します。
ALB
ALBは、リスナーポートをターゲットグループに割り当てることができます。
リスナーポートが受けるプロトコルと、ターゲットグループから送り出すプロトコルは同一である必要はありません。ユーザー向けSSL認証をALBで完了させて後続ではHTTPで接続すると、内部ではSSL証明書を持つ必要がなくなります(SSL Termination)。ACM証明書と組み合わせると、メンテナンスフリーでSSL接続を行うことができます。
ALBとECS
ECSのサービスを作成する際に、どのターゲットグループ(CLBの場合はCLB自体)と連動させるかを指定することができます。サービスとターゲットグループの関連は1:1で、作成後に変更はできませんので、ターゲットグループを変更したい場合は一度サービスを削除する必要があります。ちなみにサービスを削除するには動作中のタスクを0にする必要があるので、基本的には変えないほうがいいでしょう。
サービスとターゲットグループが連動している場合、同時に「どのコンテナのポートをターゲットグループにマッピングするか」を指定することができます。タスク定義側でホストポートを0にしたものを指定することで、ホスト内でポートがランダムに割り当てられ、そのポートで自動的にALBと通信します。同一コンテナを同一ホストで複数動かす必要がある場合や、複数のWebサービスのコンテナを同一ホストで動かす必要がある場合などに、ポート番号が被らずにコンテナを立ち上げることができます。
別のECSサービス間で通信したい
ECSのサービス内のコンテナ同士は同一のインスタンスに必ず配置されるため通信が可能ですが、アプリケーションを複数サービスにすると同一インスタンスに配置されている保証がないため、通信できるかどうか不明になります。
あまりそのようなケースは推奨しませんが、もしこのような状況が発生した場合にパブリックとプライベートで同一エンドポイントで通信できるようにするためには、Internal ALBを途中に配置します。
Internal ALBは内部向けにしかならないので、別途外部向けALBが必要になります。また、ALBとALBを直結させることはできないので、そこを中継するためのコンテナが必要になります。
図にすると以下のような感じです。
あとはRoute53のPrivate DNS機能を仕様して、同一ドメイン名でローカルからの場合はInternal ALBを向くようにすると、内部間でも問題なく通信することが可能です。
タスク配置
ECSには、「どういう法則でインスタンスにコンテナを配置するか」という「タスク配置戦略(placementStrategy)」と、「どういう条件のインスタンスにコンテナを配置するか」という「タスク配置制約(placementConstraints)」があります。
タスク配置戦略とタスク配置制約はサービスの作成時に指定しますが、途中で変更することはできなく、変更するにはサービスを一度削除してから作り直す必要があります。
タスク配置戦略(placementStrategy)
タスク配置戦略は、同じサービスのタスクを複数立ち上げる時にどこに配置されるかというもので、random(デフォルト:全てのEC2インスタンスの中からランダムに配置される)、spread(指定された条件でバランス良く配置する)、binpack(なるべく1つのインスタンスに偏らせる)という3種類から指定します。これらを組み合わせることもできます。
例えば、アベイラビリティーゾーン(AZ)でspread分散させた上でインスタンスIDでspread分散させると、AZ間で確実に等間隔になった状態で、さらにそのAZ内のインスタンス間で均等に分散されます。通常はこの指定がお勧めです。
binpackは使い所が難しいですが、主にコストを削減したい場合に使用します。CPUまたはメモリの使用量を見て1つのインスタンスになるべく詰め込むという挙動をするので、インスタンスの無駄が少ないですが、そのまま指定すると可用性は皆無になります。
変わった使い方としては、AZでspreadした状態でbinpackすると、50%の可用性を残した状態でインスタンスを詰めることができます。どちらにしてもオートスケールとbinpackの相性が悪いのであまりお勧めできませんが、事情があって費用削減したいような場合には便利かと思います。
タスク配置制約(placementConstraints)
コンテナの配置には戦略以外にも制約の指定が可能で、特定のインスタンスタイプのみといったものから、任意の属性を付けてそこにのみ配置するということが可能です。そこで標準で「配置する」属性にしておいた上で、スケールインさせたいインスタンスからその属性を取り上げることによって、今後そのインスタンスに配置されることはなくなります。
ただしスケールインさせたいインスタンス内にコンテナが残っていた場合、手動でECS側でスケールアウト・スケールインを繰り返して、そこに配置されなくなるのを待つという操作が必要になります。また、自動でスケールインさせることはできません。(lambda等でそういうプログラムを書けば可能ですが)
古いインスタンスを外したい場合や、負荷の都合で特定のサービスだけインスタンスを分けたい時とかに使うと効果的です。
また、distinctInstance 制約は、1台のインスタンスに1つのタスクしか起動しないようにします。例えばユーティリティ的なタスクや、ボリュームをマウントするようなタスクを配置する場合に使うと有効です。
サービスを無停止で作り直す
タスク配置の変更などでどうしてもサービスを作り直したいけど無停止で行いたい場合は、以下の方法で実現することができます。
- 全く同じサービスを別名でもう1つ作る
- 古いサービスを消す
- 古いサービスを(設定変更するなどして)新しく作る
- 別名サービスを消す
オートスケーリング
ECSはオートスケーリングに対応していますが、ECSのコンテナを配置するには配置するためのEC2インスタンスが必要であり、それぞれは基本的に連動していません。お互いが協調するようにオートスケーリングを設定する必要があります。
この点に関しては日本語解説が多数存在するので改めて説明はしませんが、簡単に説明しておくと以下のような感じです。必ず動作検証は行っておきましょう。
- ECS : CPUやメモリなどの利用率を見てスケールアウト・スケールインする
- EC2 : ECSのCPUまたはメモリの空きを見てスケールアウト・スケールインする
ecs-agent環境変数
ECSとEC2インスタンスの連携は、EC2内で動いている ecs-agent というコンテナが行っていますが、ここでは 様々な環境変数を指定することによって動作の調節が可能です 。重要なものをいくつか紹介したいと思います。
環境変数 | 説明 |
---|---|
ECS_CLUSTER | ECSのクラスタ名を指定します。通常指定しないということはないと思います。 |
ECS_RESERVED_PORTS | コンテナインスタンスのスケジューリングに使わないポート番号を指定します。ALBでポートを自動割り当てしている場合に、インスタンス内で予め動作しているサービスがある場合、そのサービスで使用するポートを追加しておくことによって、コンテナに割り当てられるポートがそのポートと被らないようにすることができます。 |
ECS_AVAILABLE_LOGGING_DRIVERS | ECSのログドライバーを指定します。CloudWatch Logsを使う場合は awslogs 、fluentd を使う場合は fluentd といったところです。gcplogs は使用することができません。 |
ECS_ENGINE_TASK_CLEANUP_WAIT_DURATION | タスクが停止してからコンテナが削除されるまでの時間です。この時間が長いとコンテナが長く残るので調査しやすいですが、ディスク消費が増えます。短くするとすぐディスクが開放されますが、調査が難しくなります。 |
ECS_CONTAINER_STOP_TIMEOUT | コンテナが正常終了しない場合に強制終了させるまでの時間です。あまり長くないほうが良いです。 |
ECS_DISABLE_IMAGE_CLEANUP | イメージの自動クリーンアップを無効にします。 |
ECS_INSTANCE_ATTRIBUTES | タスク配置制約で使用する属性を指定します。 |
まとめ
大体のことは 公式のドキュメント に書いてありますので、まずはそちらを一通り読むことをお勧めします。ここで解説していることも日本語で大体網羅しています。
ECSをうまく使うことによって、EC2に直接手を入れることなくアプリケーションを運用することが簡単になります。仕様を理解して活用していきましょう。