アイリッジ開発者ブログ

アイリッジに所属するエンジニアが技術情報を発信していきます。

EC2の既存システムをECSにリプレイスした話

アイリッジ プロダクト開発グループの高田です。

EC2からECSにインフラをリプレイスすることは割とあるケースだと思います。 これから似たようなことやる人にとって、少しでも参考になる記事になれば幸いです。

対象読者
  • EC2でサーバを立てたことがある人
  • ECSでコンテナを立てたことある人

目次

  • なぜやったのか
  • リプレイス前後のインフラについて
  • 結果と課題

なぜやったのか

コスト(サーバ費用と運用の手間)を下げるためです。チーム内ではとりわけ運用コストを下げるため、前々から既存サービスのFargate化を行ってきました。

運用コスト

デプロイに手間と時間がかかる問題

私が普段携わってる自社サービスは複数のコンポーネントによって成り立っています。その内多くがDocker化されていて比較的デプロイが簡単にできるようになっていますが、一番デプロイ頻度の高いコンポーネントがまだDocker化されておらず、デプロイにも時間と手間がかかっている状態でした。

コンポーネントによってデプロイ手順がバラバラになっている問題

また上述した通り複数のコンポーネントがある中で、それぞれのデプロイ手順がバラバラになっている状態でした。触ったことない人がリリースやCI/CDの改修を行うときはそれなりに学習コストがかかってしまいます。故に開発サイクルを上げるためにこのデプロイ手順をできるだけ統一させる必要がありました。加えて稼働歴もそれなりに長いシステムになるので、そもそもデプロイ自体を改善する必要性も大きい状態でした。

今回もその取り組みの一環として当初はFargateでリプレイスさせる予定でした。しかし、カーネルパラメータのチューニングが必要になったためECS化に至りました。FargateではsystemControlesパラメータの設定をサポートしていません。

ECSにした場合、インスタンスの管理をしないといけない分運用コストはFargateの時ほどは下げられないのですが、以下のような理由でサーバ代の削減が見込めます。

サーバ代

コンピューティングリソースを効率よく使っていく

ECS化することでスケールイン/アウトを今よりも手軽に行えるようになりますし、スケジューリングや負荷などの条件に応じてそれを自動で行うといった取り組みにもチャレンジできるようになります。

スポットインスタンスの活用

コンテナ化されたシステムはスポットインスタンスと相性が良いです。スポットインスタンスを活用することでリザーブドインスタンス(RI)を使う以上にコスト削減を行うことが可能です。

リプレイスによる変更

ECS_tech_blog.png

リプレイス前と比べるとリプレイス後の方はリソースの種類が増えました。

特にコンテナインスタンスのための設定が想像以上に多かったです。

アプリケーション側

ロジックの大きな改修はありませんでした。 変えたところといえば、それまではファイルに吐かれるようにしていたアプリケーションログを、stdout形式にしてCloudWatchから見れるようにするための改修くらいでした。現在はそれをFirelensからDatadogに転送する改修が行われている最中です。

Firelens の発表 – コンテナログの新たな管理方法 | AWS

ECS

ECSに関しては、Nginx、アプリケーション(Django)、Datadogの3つのコンテナを設定したタスク定義を作って、それをサービスで稼働させています。

現状タスクはコンテナインスタンス1台につき1タスクの割合で配置されています。ECS化にあたって、まずは無事稼働させ続けることがマストだったので、タスク配置の最適化やオートスケーリングの設定はスコープから外しています。

ただ負荷試験の結果では、タスクを小さくして数を増やした方が処理能力が高い結果が出ています。運用が安定してきたらこの辺りの最適化を行なっていく予定です。

また上述した通り、FargateはsystemControlesパラメータをサポートしていないからECSにしたわけですが、ecs-cli composeコマンドでもsystemControlesがサポートされておらず、CIのタスク定義を登録させる処理で悩みました。 結果一度登録したものをdescriptionコマンドで取得し、サポートしていないため入れられなかったオプションの設定を後からjqコマンドで挿入してファイルを生成し直すシェルを挿入することで今回は対応させました。

CodeDeploy

アプリケーションのデプロイはCodeDeployのBlueGreenデプロイで行なっています。ターゲットグループが二つ存在するのはこのデプロイで必要だったからです。

CodeDeployによって、新しいタスクが使われていない方のターゲットグループにひも付き、LoadBalancerから流れてくるトラフィックが新しい方のターゲットグループに向くといった挙動をします。切り戻しを行うにしてもボタン一つでバチっと切り替わってくれるため個人的にはすごい好きです。

AutoScalingGroupとCloudFormation

このプロダクトでは各リソースをTerraformで管理しています。 ただ今回の場合、AutoScalingGroupの管理はCloudFormationを使っており、そのCloudFormationの管理と実行をTerraformで行なっています。 なぜこうしたのかというと、コンテナインスタンスに対する更新をCloudFormationのUpdatePolicy属性を使って設定を指定したかったからです。例えばRollingUpdateさせる際にMaxBatchSizeで更新するインスタンスの最大数を指定したり、MinInstancesInServiceで更新中にInServiceとなる必要があるインスタンスの最小数を指定できたりします。

今回のコンポーネントの設定では、MaxBatchSize: 2, MinInstancesInService: 12にしており、本番稼働中でも大きな影響が出ないように少しずつ更新させるようにしています。

AutoScalingGroupのTerminateHook

インフラ構成図の右下の方にSNSとLambdaがありますが、それらは以下を行うために用意しました。

Amazon ECS におけるコンテナ インスタンス ドレイニングの自動化方法 | Amazon Web Services ブログ

ECSではコンテナインスタンスとタスクの管理は別で行う必要があります。コンテナインスタンスのTerminateによって意図せずタスクが強制終了されないようにしないといけません。 上記のブログに書いてあるような設定を入れることで、

インスタンスが終了する際にTerminateHookが発火
 ↓
終了対象のインスタンス上でrunning状態のタスクをチェック、あれば空きスペースに移動
 ↓
コンテナインスタンスをTerminate

このようにタスクの実行状況を気にすることなくコンテナインスタンスを終了させることができるようになります。

SpotInstanceの設定

Amazon ECS が ECS サービスを実行しているスポットインスタンスの自動ドレインをサポート | AWS

今年の9月のアップデートによって特別な設定が必要なくなって嬉しいです。(Lambdaでドレイニングさせる処理を入れる予定だった)

spotfleetを使うと中断通知を再現できるので、実際にタスクがDrainingによって終了していることを確認できます。

ちなみに弊社の本番環境で稼働しているスポットインスタンスは7台ありますが、月1,2回中断通知が来ています。

トラブル

EC2で設定しているIAMロールの設定を見落としていた

EC2に設定しているIAMロールの中に、pubsubにアクセスするためのSecretManagerへのアクセスを許可するルールが設定されていたのですが、こちらの設定を見落としていました。リリースした後一部のリクエストでエラーを出してしまいました。 気をつける以外だと、エンドポイントチェックに入れておくべきでした。

EC2側のALBを指定されているドメインが実は複数存在した

リリースが無事完了し、平行稼働期間も終わりに差し掛かり、さあいよいよ古いリソースを消すぞという時に起こったトラブルです。 Route53の中で対象サービスのドメイン(×××.fanship.jpとする)に対して、ALBのエンドポイントが指定されている形になりますが、実は対象サービス以外のドメイン(△△△.fanship.jp)が縮退対象のALBを参照していました。 縮退させる際にALBのログも確認していたわけですが、サービス名(×××.fanship.jp)のurlでしか見ていなかったためそもそもgrep対象からは漏れていました。 今思うとサブドメではなく、せめてドメイン名の方でログを確認していれば気づけたのですが...

ちなみに設定し直す際に知ったのですが、Route53ではレコードセットのエイリアスに同じホストゾーンの別のレコードセットを指定できます。 ですので今回の場合、

×××.fanship.jp → dualstack.xxx.elb.amazonaws.com.

△△△.fanship.jp → dualstack.xxx.elb.amazonaws.com.

となっていたものを

×××.fanship.jp → dualstack.xxx.elb.amazonaws.com.

△△△.fanship.jp → ×××.fanship.jp

上記のように設定し直しました。

結果と課題

運用コストとサーバ費用を下げるという話でしたが、では結果どのように変わったかという話です。

まず運用コストに関して、既存構成で一番課題だったアプリケーションのデプロイは以下のように変わりました。

  • リプレイス前(※確認作業は除く)

    • デプロイ時間: 1h10min (CI/CDの実行に約1h + インスタンスの入れ替え作業約10min)
    • デプロイ作業: pipelineの実行。完了後、踏み台サーバにsshし、16個のシェルをコピペで実行する
  • リプレイス前(※確認作業は除く)

    • デプロイ時間: 30min
    • デプロイ作業: pipelineの実行。pipelineの中で4回ボタンを押す

これだけ見ると当初の課題はだいぶ解決したように見えます。 ただAutoScalingGroupやCloudFormationなど今までは使っていなかったAWSのリソースを多用しているので、その分それらを触る時の学習コストは高くなりました。

サーバー費用に関しては以下のようになりました。

費用/年
リプレイス前 8,064USD t2.large(1年分のRI=1台672USD)×12台
リプレイス後 9,688USD オンデマンドt2.large(1,065USD)×7台 + スポットt2.large(319USD)×7台 ※RIではない

これだけ見るとむしろ上がっています。ただこれはECS化当初の構成のまま1年運用したケースになります。ECSでの運用が安定してきたタイミングで、タスクを調節することでインスタンス数を減らしたり、スポットインスタンスの割合を増やしたり、逆にオンデマンド側を決め打ちしてRI(あるいはSaving Plan)に切り替えればリプレイス前以上に削減することは難しくないと思います。

ちなみにリプレイス前もリプレイス後も実績値ではなく、あくまで目安なのでご了承ください。

何れにせよ、コンポーネント一つをリプレイスしたインパクトは実はそれほど大きくはなくて、これを横展開していくことでコストや運用面での効果を実感できるようになります。

運用系の仕事はクリティカルな問題がやはりどうしても優先になってしまうので、今回みたいな運用の効率化系タスクはなかなか後回しにされてしまいがちです。自社システムのECS化も続けていかないと負債になりかねないので、ここで頓挫させないようにしたい所存です。