7年続いたサービスをEC2構成からECS構成へ乗り換えた話

この記事は Tech KAYAC Advent Calendar 2021 の20日目の記事です。

こんにちは、バックエンドエンジニアの @commojun です。今年のTech KAYAC Advent Calendarは3度めの参戦です!よろしくお願いいたします!

本日の記事は、昨年の記事の続きで、Amazon EC2のプロダクトをAmazon ECS構成へと乗り換えた話になります!

techblog.kayac.com

目次

背景

弊社のサービスは、クラウドプラットフォームとして主にAWSを利用しており、比較的長く運用が続いているサービスは仮想サーバーであるAmazon EC2上に構築されていますが、比較的最近立ち上げられたプロジェクトでは、コンテナベースでのシステム構築の潮流にのっとり、Amazon ECSを利用してコンテナベースのシステム構築を取り入れています。

私が従事している「ぼくらの甲子園!ポケット」は、2014年9月からサービスを開始し、おかげさまで(なんと!)7周年を迎えることができました。 当時はAmazon ECSというサービスはありませんでしたので、Amazon EC2 + Amazon Linuxという構成にPerlで書かれたサーバサイドアプリケーションが稼働しており、調整を繰り返しながらサービスを継続させていました。

Amazon Linuxのサポート終了

Amazon Linuxは2020年12月末でサポートを終了し、現在は重大なセキュリティアップデートなど、最低限のサポートのみがなされるメンテナンスサポート期間に移行しました。2023年6月には、いよいよ完全にサポートを終了することを予告しています。Amazon EC2 + Amazon Linuxの構成に長らく頼っていた「ぼくらの甲子園!ポケット」も、構成を見直す必要性が出てきたということになります。

Amazon Linuxの後継OSであるAmazon Linux 2に乗り換えて、引き続きAmazon EC2での運用を続けるという手もありましたが、昨今、コンテナベースでのサーバ構築が当たり前となりつつあることと、社内の他プロジェクトでの知見もあることから、Amazon ECSを利用した構成に乗り換えるという決断をしました。

ついでにPerlのバージョンもあげた

システム構成を見直す機会は、依存するモジュールやミドルウェアを見直す機会でもあります。7年分たまったPerlのバージョンの大アップデートを行った話は、昨年の記事をぜひご覧ください。

苦労したポイント

ECS構成に乗り換えるために苦労したポイントは多くあったのですが、さすがに全てをこの記事に書ききれる気がしませんので、長く続いたサービスの移行だから大変だったと個人的に感じたポイントを3点ほど抜粋させてください。

1,デプロイ方法がめっちゃ変わる

コンテナベースのシステムへと乗り換えることの本質的な部分なので、当然といえば当然なのですが、デプロイ方法が大きく変わりました。

これまでのEC2構成では、 stretcherというデプロイツールを利用していました。詳しくはこちらの記事をご覧ください。

techblog.kayac.com

EC2構成においてこの方法は画期的でしたが、ECS構成ではコンテナ自体を入れ替えることでデプロイするという考え方になるため、デプロイに関連するスクリプトやフローを全て見直しました。

デプロイのために都度コンテナイメージを焼く

ECSでは、いわばコンテナをデプロイするというデプロイ方法となるため、なにか更新をかける度、新たなコンテナイメージをビルドする必要があります。プロジェクトのデプロイ頻度は週に4~5回ほどあるため、コンテナイメージを焼くワークフローを作り込む必要がありました。

そこで、コンテナイメージのビルドはcircleci上で行うことにしました。github上のリリース用ブランチに対してコミットがあった際に、そのコミットに対応するコンテナイメージをビルドし、Amazon ECRへpushするというワークフローを構築しました。

2階建て作戦

単純にcircleciにビルドさせるだけでは時間がかかりすぎるという問題がありました。特に緊急時などは、リリース用ブランチへのコミットから、デプロイ完了までの時間をなるべく短くする必要があります。運用上、大半のビルドはサーバアプリケーションのコードを新しくすることが目的です。アプリケーションを動かすために依存するLinuxのパッケージやモジュールを毎回のビルドで1からインストールする必要はありません。そこで、イメージを2階建て構成にして、以下のような流れでビルドするようにしました。

  • baseイメージ

1日1回、夜中にビルドする。サーバアプリケーションを動作させるために依存するパッケージ、モジュールや、awscliといった必要となるツール類のインストールをする。

  • appイメージ

DockerfileのFROM命令に上記baseイメージを指定し、リリース用ブランチにコミットがある度にビルドをする。ここではアプリケーションコードをコンテナ内にコピーすることが主で、それ以外は最低限必要な環境変数やエントリポイントの設定などを行う。

このようにビルドを二段構えにすることで、ビルドにかかる時間ひいてはデプロイにかかる時間をなるべく短縮する工夫をしました。

2,batchサーバどうするの問題

「ぼくらの甲子園!ポケット」は、野球の試合進行をcrontabで設定されたスクリプトで動かすなど、多くのバッチ処理に支えられているアプリケーションです。crontabが設定されているbatchサーバ(EC2インスタンス)は、基本的にダウンタイムも、スクリプトの多重実行も想定されておらず、SPOFとなっていました。

EC2構成におけるbatchサーバでは、gitリポジトリで管理された、crontabエントリを列挙したテキストファイルを読み込むことによってエントリの追加・削除を行っていました。ECS構成においても、できれば同様の管理方法としたいです。

この問題を解決しつつ、コンテナ時代のサーバ構成に乗り換えるため、次のような仕組みを構築しました。

sqsjfr + SQS + sqsjkr 作戦

sqsjfrとは、crontabの書式を解釈し、cron式に設定された時刻が来ると、Amazon SQSに対してジョブメッセージを送信できるソフトウェアです。

github.com

sqsjkrとは、Amazon SQSからジョブメッセージを受け取り、任意のコマンドを実行するソフトウェアです。

github.com

f:id:commojun:20211219170132p:plain
sqsjfr+SQS+sqsjkrでバッチサーバを実現する

まず、sqsjfrを常駐させたECSタスク(cron)がスケジューラの役割を果たし、設定された時刻が来たらAmazon SQS FIFOキューに実行するべきスクリプトを示したメッセージを送信します。Amazon SQSは、受け取ったメッセージをsqsjkrが常駐しているECSタスク(worker)に送信します。workerタスクは、受け取ったメッセージに従ってスクリプトを実行します。

単純にcrontabの機能を実現させるのであれば、crondを常駐させるコンテナを立ち上げることも可能ですが、ここでAmazon SQSを仲介させることに大きな意味があります。Amazon SQS FIFOキューには同一メッセージの重複排除機能があり、複数箇所から同じメッセージを受け取っても、一度のコマンド実行の命令として解釈可能となります。つまり、sqsjfrが常駐するECSタスクを冗長化できるようになります。sqsjkrも同じく冗長化できることを目的とした設計となっているため、これらを組み合わせてbatchサーバのSPOF問題を解決しています。

さらに、sqsjfrはcrontabエントリを列挙したテキストファイルをそのまま解釈可能なので、EC2構成で利用していたcrontabの設定ファイルをほぼそのまま流用してデプロイできるといったところも、ECS構成への移行の大きな助けとなりました。

3,泥臭い戦い

ECS構成への乗り換え作業は、いわゆるインフラ部分を乗り換える作業となるため、アプリケーションとインフラ(OS・ミドルウェア)との接続部分を一通り見直すことになります。具体的に何をどう直したという説明をするのは難しいのですが、「この環境変数、本来の使われ方をしていないぞ?」「このconfig、すごく場当たり的な書き方をしてるぞ?」といったような、長年積もってなかなか見直されにくい負債が次々と見つかりました。これらを見直し、アプリケーションとその動作環境との繋がり方をあるべき姿へとリファクタリングする作業にかなりの労力と時間を割きました。

この作業には、アイデアも作戦も特に通用しないように思います。同じプロジェクトで十分な経験を積んで、全体を把握している人間が気合と根性で全部見て全部直す、というのが唯一の攻略法なのではないかとやっていて思いました。

ecspressoの存在

ECSタスクのコンテナの作り込みや、デプロイフローを構築するにあたって、ecspressoというツールが大きな存在でした。

zenn.dev

ecspresso の開発思想は「ECS のデプロイに関わる最小限のリソースのみを管理するツール」です。

とあるように、terraformやawscliと違って、ECSタスクにまつわる設定やコマンドのみに関心事を集中させ、試行錯誤を重ねることができました。実際に本番で運用しているデプロイフローにもecspressoのコマンドが盛り込まれています。

非エンジニアにもわかってもらおう

普段の私の職種はサーバサイドアプリケーションエンジニアであり、特にECS移行作業がなければ、もっと快適にゲームを遊んでもらえるような新機能開発や、予定された通りにイベントを本番に反映する定型業務が期待されています。

ECS移行作業は、非エンジニアの人にその意義をすんなりと感じてもらうことは難しいです。「ECS作業はユーザにとってどのようなメリットがありますか?」という質問をしばしば受け、なかなか歯切れ良い回答を返せないということが何度かあり、これは良くないと思いました。「Amazon Linuxのサポートが切れるから」と一言告げただけで、新機能開発よりもECS移行を優先すべきだと感じるディレクターは存在しません。ゲーム体験がほとんど変わらないのになぜインフラを乗り換えなければならないのか?長期的に人的リソースを割いてまで、Amazon Linux2ではなくECSになぜ挑戦するのか?そういった問いに対して、エンジニアでない人にでもわかる言葉で説明しなければならないと感じ、プロジェクトメンバー全員に説明の機会を設けることにしました。

「たとえ」をうまく使う

弊社の非エンジニア社員は、プログラミングに関する基礎知識はなくとも基本的なPC関連のリテラシーは高い方です。また、ゲーム会社であるため、ゲームに関する知識もかなりのレベルで共通認識となっています。説明をしたい相手のこういった背景を踏まえた「たとえ」を使って、ECS移行作業の意義をなるべく実感してもらう説明を考えます。

なぜ乗り換える必要があるのか?

Amazon Linuxのサポートが切れても何がまずいのか?OSのセキュリティアップデートが提供されなくなった場合、新たに登場したマルウェアや、新たに発見されたセキュリティホールを突いた攻撃を受けるリスクがあるという点は、PCを普段から扱う人ならほとんどの人が馴染みのあるwindowsにおいても同じです。たとえというレベルでもないのですが、旧世代のwindowsのサポート終了については、度々ニュース等でも取り上げられるので、windowsに置き換えて考えてもらうだけで感覚的に危機感を感じてもらえました。

f:id:commojun:20211219142634p:plain
windowsに例えればヤバさを感じてもらえる

なぜECSに取り組むのか?

そもそも、ECS(Elastic Container Service)の根底の概念であるコンテナ技術について理解をしてもらう必要があります。サーバインスタンスを起動し、OSから動作環境・ミドルウェアまですべてそのアプリケーション専用にカスタマイズするという方法が主流であった時代から、コンテナイメージという形式であれば、(基本的には)Dockerが動作していればどこでもアプリケーションが動作できるという時代にシフトしたという、時代の変遷について考えると、ゲーム機の進化の歴史がそれに近しいように思いました。

1970年代のゲーム機は、スペースインベーダーなどに見られるように、筐体とゲームは一体であり、そのゲームを遊ぶためにデザインされたものでした。

Space Invaders.JPG
Tomomarusan - This is the creation of Tomomarusan CC 表示 2.5, リンクによる

その後、1983年に登場したファミリーコンピュータに代表されるように、カセット取替式という考え方が登場しました。一度ゲーム機本体を揃えてしまえば、あとは比較的安価なカセットのみを追加購入するだけで新しいゲームを遊ぶことができるというメリットが大きく、その後、(主に家庭用ですが)ゲームといえば、ゲーム機本体+ゲームソフトに分かれたものであるということが常識となりました。

Nintendo-Famicom-Console-Set-FL.png
Evan-Amos - 投稿者自身による作品, パブリック・ドメイン, リンクによる

このゲーム機の歴史を、ゲーム機本体=Dockerが動作するオペレーティングシステム、ゲームソフト=コンテナイメージに置き換える形で、サーバ界のコンテナ技術の登場に関連付けて、コンテナ技術を利用したサーバ構成が今後いかに当たり前になるのかを説明しました。

f:id:commojun:20211219160131p:plain
コンテナベースのサーバ構築がいかに当たり前になってきたか

そして、世の中的に当たり前の技術*1を使い続けることが、プロジェクトに従事するエンジニアのキャリアとして、ひいては組織全体の長期的な成長に対していかに重要であるのか、という説明をしました。

f:id:commojun:20211219164046p:plain
多くの人が使う技術に乗っかる重要性

最後に

部分的にしか説明できませんでしたが、ECS移行作業の片鱗を感じていただけましたでしょうか。ECS移行作業はとても時間がかかり、作戦がきれいにハマる箇所もあれば、どうしてもパワーで押し切らねばならない箇所もあり、いろいろと勉強になりました。

ECS移行作業の途中でその意義を説明するということをしましたが、普通であれば順番がおかしいですね。着手する前に説明をしたためて、理解してもらった上でやっとリソースや予算を確保してもらっていざ着手という流れが普通だと思います。その点は、とりあえずやってみてから考える的な弊社の風土が現れているかな、と思います。順番はどうあれ、自分のやっている仕事を他人に理解してもらうことは非常に重要だと感じました。専門的すぎるからと、非エンジニアの人に仕事の内容やその意義を理解してもらうことを怠ると、最終的に新しい技術への新陳代謝ができない組織になり、成長の可能性を閉ざしてしまうと思います。一線級のエンジニアが現役のまま経営も兼ねている組織であればあまり問題になることはなさそうですが、そのような組織も多くはないと思います。であるからこそ、今後のエンジニアはこのような説明責任も担っていくべきなのかな、と個人的には思います。

*1:Perlは第一線級で活躍する超超超人気言語です