asobannのAWS ECSデプロイでやったことと引っかかったこと その2
その1でasobannをAWS ECSに手作業でインフラ構築できた ので、これを自動化したい。CloudFormationかTerraFormを使うといいらしいと聞いて、あまり考えずにCloudFormationを使ってみることにした。これも初めて。
CloudFormationにアプローチしてたくさん間違える
軌道に乗るまでに、こんなことをした。
- デザイナーを用いたチュートリアルで流れを把握
- デザイナー上で自分で1から作ろうとしてみて、なにを作ればいいのか把握し切れてないことに気づく(コンソールからECSのクラスターを作ったときは、裏側で作ったり設定したりしてくれていたものがあって、それがわかってない)
- アプローチについて調べていたらこちらの記事からテンプレートのサンプル にたどり着き、これを参考にすることにした
- 手で作ったECSのクラスターからCloudFormationテンプレートを生成しておき、参照できるようにした
3番で見つけたテンプレートのサンプルが死ぬほど便利で、助かりました。
- どんなリソースが必要で、どうリソース同士をつなげるのか書いてある
- テンプレートファイルが分割してあり、わかりやすいし、分割するときの書き方もわかる
- YAMLの書き方、!Refや!Subの書き方の例が豊富にある
必要なリソース種類がわかれば、個々のリソースに必要な設定はリファレンスを見るだけなので、あまり苦労がない。
変える部分を見落とさないためにも、自分の理解のためにも、写経のように見ながら再入力しつつテンプレートを書くことに。理解は深まったけど、見落とし、ぽかミス、勘違いなどでたくさんトライ&エラーを繰り返すことになったのでありました。
- 複数のアベイラビリティゾーンとサブネットを作って渡さないと、AutoScalingGroupでエラーになる(最低2つ、みたいな)。ここでサブネットを増やしたとき、AWS::EC2::SubnetRouteTableAssociationも合わせて増やさないと、ルーティングが狂って変な挙動になる(EC2インスタンスがクラスターのインスタンスにならない、サブネットのルーティングテーブルが勝手に変化する(ように見える)、EC2インスタンスにsshできたりできなかったりする)。エラーにはならなかったので気づきにくい。
- Service Discoveryで指定したホスト名が解決できず、VPCの設定で「DNS解決」と「DNSホスト名」の両方を有効にしたら解決した。テンプレートでは AWS::EC2::VPCに対してEnableDnsHostnamesとEnableDnsSupportを両方trueにする。(DNSホスト名が必要なのは解せないけど、こうしたら動いた)。
- Service DiscoveryのためにPrivate NamespaceリソースをCloudFormationで作成すると、HostedZoneが作られる(明示的に作らなくてよい)。サンプルにはなかったのでちょっと迷ったところ。
- LoadBalancerからTargetGroup(アプリ)にアクセスするには、SecurityGroupの設定が必要。LB用のSGとアプリのSGが異なるなら、アプリのSGはLBのSGからのアクセスを明示的に許可する必要がある。手で作ったときはアプリとLBを同じSGにしていたので、気づかなかった。なお本来は同じSGであっても通信は許可されていなくて、これはSecurity Groupについての基礎知識っぽいのだけど、VPC作成時にデフォルトでできるSecurity Groupでは自動で許可されているので、どうやら気がつかなかったみたい。SGさんいつも厳しい。いつか仲良くなれるだろうか。
- CloudFormationテンプレートを変更してスタックを更新するとき、Task Definitionが変わったりしてタスクが再起動することがあるが、このとき新しいタスクを起動→古いタスクを終了、という順序で動く(これはデプロイの設定で変わるような気がするんだけど、ちゃんと調べていない)。そのとき、新しいタスクを起動する分の余裕(CPUとメモリ)がコンテナインスタンス(EC2インスタンス)にないと、当然ながら起動しなくて、スタックの更新自体も先に進まなくなる(手で元のタスクを停止するとうまくいったりした気がする)。
- よくわからなくなったら、コンソールからスタックを削除して作り直すと、きれいにうまくいく場合がある。後から考えると、同じ論理ID(テンプレート中のリソース名)で後から矛盾する設定変更をしたせいだった気がする。スタックの削除は普通自動で進み放っておけば終わる(5~10分くらいだった。コンソール上で進捗を追える)のだけど、たまに削除が進まなくなることがあった。VPCが消えないことが多かった気がする。対象リソースをコンソールから手で削除すると、スタックの削除も無事に完了した。
- なお4番の、構築済みのClusterからCloudFormationのテンプレートを作ってくれるので、それで構築済みのものを見て参考にしようと思ったら、肝心なリソースがぜんぜん入ってなくてダメだった。リソースは手で選ぶので漏れたのかもしれないけど、再確認はしてないのでよくわからない。動いてるやつを元に作ったテンプレートを直すだけって、すごいいいアイデアだと思ったのになあ。
あとはまあ、テンプレートを更新して実際にCloudFormationで更新するのに、実行開始→作業中…→失敗!になるまでの時間が割とかかる(10分とか)のが、トライ&エラーを繰り返すのに地味に厳しかった。また失敗したときの理由や状態を調べる方法が、なかなかつかめなかった。
- コンソール上でCloudFormationのスタックを指定してイベントを見る
- ネストしたテンプレートは別のスタックになっているので、そのイベントを見る。削除済みとかのものを見ると新しいことがわかるときもある。
- 作成したClusterをコンソールで開いて、Serviceを指定して、そこからイベントを見る
- 作成したClusterをコンソールで開いて、Taskを指定して、そこからLogsを見る。反映が遅いことがあるので、ページ下の方のコンテナから詳細を開いてCloudWatchのログを表示した方がいいかも(CloudWatchを設定している必要がある)。Stoppedのタスクを見ると新しいことがわかるときもある。
- EC2インスタンスにsshして調べる。インスタンス自体のログは/var/logに出ているし、docker execしてコンテナの中を見ることもできる
インスタンスやコンテナのことはあるていど探れたが、ネットワーク周りはどうやって調査したらいいのかまだよくわからない。また、ECSのタスクが起動しないで、必要な要件を満たすインスタンスがないよというメッセージを出すのがとても困った。いちおうここにガイドラインがあるけどあんまり役に立たない。
続く
これで環境はほぼ構築できたのだが、まだ動かない。クラスター内でアプリがMongoDBとRedisを見つけるところで、追加の作業(実装も含む)が必要になった。またMongoDBのデータがコンテナ内なので、なにかあったらデータが消えてしまう。その話はまた次回。