Posted at

ECSで困ったときに読むための俺的Q&A

More than 1 year has passed since last update.

みなさんこんにちは

ここ一年でdockerを使った開発を進めた結果、ついには「デプロイもコンテナイメージでやったらええやん!」みたいに思い、そうするとAWSでECSというサービスがあるじゃあないですかとなって、実際に環境構築を試みたわけです。

謳い文句には「一瞬でできる!」とか「容易に」とか書かれていますが、やっぱり実際にはなれるまで時間がかかるわけです。

web上に転がっている情報は、どういうわけかaws cliの情報ばかりなので、超初心者であり普通のコンソール使おうとしている私にとっては、まあ、解読が難しいわけです。

そうしていると、変なノウハウが溜まっていくわけで、せっかくなのでこっちの方に書きなぐってしまおうと考えたわけです。

というわけでほぼ自分用のECSのQ&Aを作りました。

ただのバッドノウハウになっている可能性もありますが。


概念編

AWSの日本語って、単純に訳したかのような、ちょっと見慣れない or 聞きなれない表現があったりして、こちらの混乱を誘います。

そういう場合は一生懸命英語を読むわけですが、メモっておいたほうがいいかなと思いましたので、ここに記載しておこうと思います。


ECS (EC2 Container Service)

dockerコンテナを通して処理をしたりサービスを立ち上げたりと行った挙動をEC2上で容易に行うためのAWSのサービスです。

EC2上での操作は全部AWSでやってくれるので、こっちはコンテナイメージの作成に注力できるし、動作する際のパラメータも任意に決められます。

ちゃんと設定できればコンテナイメージを作ってそれをアップするだけで、簡単にアプリケーションがデプロイできる、みたいなシステムを構築することもできます。


ECR (EC2 Container Registry)

ECSはコンテナを操作するサービスだったのに対し、こちらはコンテナイメージをprivateな環境に格納しておけるサービスです。

また、当然ではありますが、ECSと連携することができて、ECSで使うコンテナイメージをECRから引っ張ってくるという使い方ができます。

私の場合、ECSでプライベートリポジトリを使う場合、ECRから取得する方法しか使ったことがないです。

ただ、ここにプッシュするためにはaws-cli の使用が必要になるようで、使ったことない人には敷居が高いです。


クラスター

ECSでコンテナを動かすためのホストマシンの集合体です。

クラスターを作るときに実際にホストマシンのサイズなどが指定でき、クラスターを作成するとAWSが提供するコンテナを動かすのに適したAMIでEC2インスタンスが起動します。

ちなみにこの時クラスターが持っているEC2のインスタンスのことをコンテナインスタンスって読んでいるようです。


タスク定義 (Task Definition)

タスク定義と言うのはアプリケーションの動作を一つ以上のコンテナで表現したものです。

ナンノコッチャというと、例えばWEBサーバの挙動を定義したければ、nginx+php-fpmの入ったコンテナを持ってきて、起動させてしまえばいいという感じです。

このとき、nginxとphp-fpmは別のコンテナで定義させておき、nginx -> php-fpmのリンクを設定することでも同じようなタスクを表現できます。

コンテナはアプリケーションの最小構成単位にまで落とし込んだほうがいいらしいので、分けられるのなら分けちゃったほうがいいかもですね。


リビジョン

タスク定義にはリビジョンというものが存在しています。タスク定義の変更は、新しいリビジョンを作成することによって行うことができます。

こいつの利点は、任意のリビジョンのタスクを実行できる点にあり、例えば間違った設定をして突如タスクが動かなくなってしまった場合は、一つ前のリビジョンを動かすことで、タスクの停止時間を抑えることができます。


サービス

クラスターの中の適当なインスタンスでタスクを起動させてもいいのですが、スケーリングとかELBの設定とかをいちいちやっているのは面倒くさいですよね。

そんなとき、各コンテナインスタンスにいい感じにタスクを配置させたり、ELBとの接続の設定を管理してくれたり、負荷が上がったときにスケーリングしてくれたりする機構がサービスとなります。

サービスにはタスクを一つ設定できますが、この時設定するタスクはリビジョン付きで設定します。

サービスは、タスクを更新 (=設定されているタスクのリビジョンを変更) した場合、突然いま動作している古いタスクを落とすわけではなく、新しいリビジョンのタスクを起動しつつ、必要タスク数を下回らないように注意しながら古いリビジョンのタスクを停止させていきます。

いわゆるブルーグリーン方式で、一時的に新旧のタスクを共存させることで、アプリケーションが一瞬でも完全停止しないように気を使っています。

突発的な作業のためには裸のタスク定義を使い、WEBサーバなど継続して動作すべきタスクに対してはサービスを作ってあげるといいと思います。


トラブルシューティング編

私が実際に動かしていて発生したトラブルに、どのように対応していったかを書き並べていきます。

事細かな詳細を書くこともできないので、ちょっとふわっとしています。

あと、大事なことですが、AWS マネジメントコンソール上での話です。


あれが削除できない

間違って作っちゃったものを削除してなかったことにしたいのは人間の心理ですが、わりとハードルが高いので困りものです。

え?そもそも削除しちゃだめ?ははは


タスク定義が削除できない

タスク定義が削除できない場合があります。

タスク定義を削除するためには、


  1. 有効なリビジョンが存在しない

  2. 全部のタスクが停止している

状態でなければなりません。

というわけで、クラスター上でまだ残っているタスク定義がないか調べた上で、動いているものがあったら停止していきましょう。

タスクが勝手に起動して停止できない!という場合はもう少し後ろの項を参照してください


サービスが削除できない

サービスは自身が管理しているタスクが起動していると、削除できません。

起動しているタスクを停止させてから削除しましょう。


やっぱり タスク or サービスが削除できない

タスク全部停止したのに、やっぱり削除できないやんけ!ってときは、もしかしたらタスクが自動的に起動している可能性があります。

サービスは自身が管理するタスクが停止し、必要タスク数を下回ってしまうと、自動的にタスクを起動して必要タスク数を満たすように努力します。

よって、一旦サービスの必要タスク数を0にしてから、起動中のタスクを落としていくようにすれば問題なしです。


クラスターが削除できない

VPCの設定でミスったり、よくわからないけど動かなかったりして、とりあえず今作ったクラスターを削除したいんだけど、どういうわけだか削除できない場合があります。

私の場合、クラウドフォーメーションの削除処理のときにセキュリティグループがほかから参照されていて削除できないという状況になっていました。

クラスターのコンテナインスタンスもセキュリティグループを持っており、同じVPC上のDBとかキャッシュを参照したいときにセキュリティグループのアクセス許可設定をするわけですが、そうしているとコンテナインスタンスに設定されているセキュリティグループが削除できず、結果としてクラスターも削除できないという状況になります。

他のセキュリティグループからコンテナインスタンスのセキュリティグループを手動で削除してからクラスターを削除してみましょう。


タスク定義が原因のトラブル

タスク定義って単純にdocker run するようなものだから、簡単ですよね?...というわけにも行かず、やっぱりローカルとは違うようねって思う今日このごろ

タスク定義で発生し得るミスなどを追ってみましょう。


タスクが一つしか起動できない

タスクなんてコンテナの集まりなのだから、リソースがある限りいくつも起動できるようにして欲しいところですが、実際に動かしてみると、コンテナが一つしか動いてくれないということがママあります。

私の場合は、コンテナ上のポートマッピングの際に、ホストに固定のポートを指定していることが原因でした。

同じタスクを起動させようとしても、すでにホストのポートが使われているので、起動できないというやつですね。

docker runでmysqlを別に2台立ち上げたときに、私も遭遇したことがあります。

こんなとき便利なのはポートの設定にダイナミックポートを設定してしまうことです。こうすることで、ホストの開いているポートを使ってポートマッピングしてくれます。

ダイナミックポートの設定方法は、コンテナのポートマップ設定のときに、ホストポートに0もしくは空欄にしておくことで、設定可能です。


タスクが動かない原因がわからない

タスクが起動できたかもしれないけど、いつの間にか落ちているっていうときは、ログを調べるべきなのですが、どんなふうにログを出したらよいかわからない方もいると思います。

コンテナログを簡単に出すならば、CloudWatchのログ機能を使うと、追跡がしやすいです。

まず、マネジメントコンソール上でCloudWatchのログ->アクション->ロググループの作成で適当なロググループを作っておきます。

次にタスク定義のコンテナのログ設定で、ドライバーを「awslog」にします。

すると、ロググループとリージョンを設定する項目が追加されるので、自分のリージョンと先程作成したロググループを入力して、リビジョンを作成します。

これで、新しいタスクを起動すると、ログが出力されるので、これを元にトラブルを解決しましょう。


サービスが更新されたのに、古いリビジョンのタスクが走り続けている

これはいくつか原因が考えられるので、コンソールのECSのサービスから、直近のイベントを眺めてみましょう。

サービスに設定されているリビジョンはすでに新しいリビジョンに更新されているにも関わらず、古いリビジョンが走り続けている場合は、新しいリビジョンのタスクが何らかの原因で起動していない可能性があります。

先に述べたとおり、サービスは私の場合、タスク( コンテナ )自体がエラーを出すときと、古いタスクがポートを専有しているため、新しいタスクが起動できないという2つの原因がありました。

ポートの問題に関しては上述したダイナミックポートを使い、コンテナ自身のエラーについては


サービスが原因のトラブル

サービスが原因になることってそんなにないのですが、ないわけではありません。


タスク定義が更新されたけど、古いタスクが走り続けている

タスク定義を更新してリビジョンが新しくなっても、サービスに設定されているタスクのリビジョンを変えなければ、サービスが管理しているタスクは更新されません。

サービスに設定されているタスクも更新しましょう。


古いタスクと新しいタスクが同時に走っている

サービスは新しいタスクに切り替えるときに、外見上アプリが完全停止しないように、新しいタスクが立ち上がってロードバランサに登録されるまでは、古いタスクを走らせ続けます。

これにより、アプリのダウンタイムを0にするだけでなく、新しいタスクが何らかの異常で停止した場合は、古いタスクをそのまま走らせ続けることで、長時間のアプリ停止を防ぐことができます。


まとめ

せっかくdockerで開発環境構築しているのなら、やはりデプロイもコンテナでやりたいですよね。

とりあえず身近で使いやすいコンテナ運用環境としてECSがあるので、どんどん使っていきたいです。

ちょっとストレージの部分がまだわからなかったりするので、そのへんは勉強しなきゃなぁと思っています。

今回はこんなところです。


参考

ECS

ECSのダイナミックポートについて