既存サービスの1つをEKSに移行しました

こんにちは。関西支社の hatajoe です。

先月からSREチームへ異動しました。
関西支社としては1人目のSREで、今後関西でSRE組織を拡大するためにも良いロールモデルになれるよう頑張りたいと思います。困っちゃうな〜。

今回は、自分がしばらく携わっていた既存サービスのEKS移行についての思い出話を書きたいと思います。

最近の freee インフラ事情

移行話をする前に、最近の freee のインフラ事情をざっくりですが前置きます。

freee の展開するサービスは AWS を利用して提供しています。
各AWSリソースはほとんど全てが terraform によって構成されており、全て1つのレポジトリに集約する形で管理されています。
EKSについては別管理となっており、eksctl, kubectl, helm, helmfile などをラップした内製システムが存在します。こちらも社内の全EKSクラスタ設定が1つのレポジトリに集約されています。アプリケーションのデプロイなどもこちらのレポジトリを通じて行われます。
EKS環境のモニタリングや監視については Datadog を採用してお り、設定は terraform によって構成され、他と同様に1つのレポジトリで全て管理されています。

freee のEKS環境をとりまくインフラは、これら3つのレポジトリに対して(正確には terraform module として切り出されたレポジトリもある)プルリクエストを作ることで構成を変更する、Gitopsを意識した運用となっています。

freee の GitOps については SRE の renjikari が CNDK 2019 で紹介させて頂いた資料があるのでこちらをご覧頂ければと思います。

Kuberneteの運用を支えるGitOps

移行の背景

今回移行を行ったサービスは Railsアプリケーションを EC2インスタンスに直接デプロイする形の割とトラディショナルな構成の上で提供しているものでした。

移行の背景としては次のようなものが挙げられます:

  • k8s+コンテナ運用による社内CI/CD基盤の標準化が進められる
  • アプリケーションエンジニアに対してインフラ構成変更についての権限を移譲しやすくなる

いずれにしても、サービスに対する可用性・保守性性の向上、アプリケーション開発の速度を損なわないインフラ環境というものを目的とし、それらを実現していくための1つの施策としてのEKS移行という位置づけとなります。

k8s+コンテナ運用による社内CI/CD基盤の標準化が進められる

現在、freee には EKS環境でサービスを提供しているものとそうでないものが混在しており、これらを両方運用していく上で各種オペレーションが複雑化しているという課題があります。その結果、自分のように新しくチームに参加したメンバーがそれらをキャッチアップすることに時間が掛かってしまったり、インフラ構成に対する各種ツールセットのメンテナンスコストがかさむといったことが見えてきました。

社内のサービスがEKS環境へ移行が進むにつれて、k8s+コンテナに対するインターフェースを中心とした社内CI/CD基盤の標準化が進められてきました。
これらは k8s+コンテナインターフェースの特徴を活かし、サービスごとに微妙に異なるデリバリープロセスや、インフラ管理におけるツールセットの分散などを吸収できるような設計となっています。
今後はこちらの共通基盤に投資し、あわせて各サービスのEKS移行を進めることでインフラ管理に対する保守性の向上や、CI/CD基盤開発による全体最適が掛かりやすい状態を見込んでいます。

SREで現在開発を進めている社内CI/CD基盤の1つに、GitHub Actions を使用したコンテナイメージビルドパイプラインがあります。
こちらについては SRE の manabusakai, tmnakagawa が Kubernetes Meetup Tokyo #32 で紹介させて頂いた資料がありますのでこちらをご覧ください。

アプリケーションエンジニアに対してインフラ構成変更についての権限を移譲しやすくなる

EKS移行前のサービスでは、インフラ構成の変更や環境変数の設定などは権限の都合上SREが依頼ベースで作業する必要がありました。
様々なチームから依頼が発生するするため作業完了までのリードタイムが多く発生してしまうなど、アプリケーション開発のボトルネックとなりやすいワークフローの1つとなっています。
EKSへ移行を済ませることで、kubernetes の特徴を活かし、これまでSREで作業を行っていた環境変数の設定や、pod のリソースキャパシティ調整、HPAによるスケーリング調整などをクラスタの namespace ごとにアプリケーションエンジニアへ権限移譲しやすくなります。
kubernetes リソースは yaml ファイルとして宣言的に管理されており、構成変更もCIによって行われるため比較的安全にオペレーション出来るのと、例として挙げた類の変更については学習コストもほぼ発生せずオペレーションが可能になります。 権限移譲が行われることで、アプリチームとしてはオンデマンドな対応が可能となり、SREチームとしては他の作業に集中できるなどのメリットを見込んでいます。

移行の手順

ここからは実際に移行する際に気をつけたことや、実施したことを時系列で紹介します。 移行の流れは以下のようになります。

  • 検証用環境の作成と移行
  • ステージング環境の作成
  • ステージング環境での性能試験
  • ステージング環境での移行リハ
  • 本番環境移行

本番環境の移行は、Route53 record と Target group のリクエスト重み付けを利用して徐々に新環境へ移行する手段を選択しました。移行はメンテ中などサービスがダウンしてるタイミングで行うことも出来ましたが、通常時のリクエストを新環境で少しずつ受け何か問題が起きたら即切り戻せっるメリットがあることからダウンタイム無しで移行する戦略を取りました。

Route53からリクエストを旧環境と新環境に移していく
旧環境と新環境のイメージ図

検証用環境の作成と移行

まずは移行に際して必要な事項を洗い出すため、検証用の社内プライベートなクラスターを作成しサービスが起動するまでを目指しました。
freee では本番環境、本番環境と同じコードがデプロイされているステージング環境の他にインテグレーション環境と呼ばれるリリース前の機能に対するQAを実施する環境がいくつかあります。こちらの1つをEKS環境へ移行する作業です。
何をもって正常に移行できたと判断するかは難しいポイントです。移行対象のサービスは様々な社内サービスに対して依存しており、それらの疎通確認や、定期バッチ、非同期ワーカーの処遇を考えなければいけません。
これらの課題に対して、トライアンドエラーによるインクリメンタルなクラスタ作成作業と、QAチームと協力し正常な状態定義の作成を並行して行いました。

freee では期のクォーターの30%ほどを使って品質改善を行う取り組みを一部のチームで実施しており、今回の移行はその枠にて行われました。
その中で有志が集まった即席チームという体裁で、全員が EKS や kubernetes に精通したメンバーというわけではありませんでした。
また、社内に既存サービスのEKS移行の事例が無かったことから、どうしても作業は手探りとなりました。1つ問題を解決しては次の問題にあたるといった泥臭い作業が続きます。その過程でメンバーそれぞれにたまっていく知見や、同じ志をもって取り組んだムーブメント感など今思えば懐かしい。freee にはこういったチャレンジをスモールに始める事のできる文化やチャンスがあって、個人的には良いエンジニア文化の1つだと思っています。

このフェーズで大切だったのは正常な状態の定義で、これは本番移行時までずっと使われる重要なドキュメントだったように思います。

ステージング環境の作成

インテグレーション環境の次はステージング環境に挑みました。
本番環境と同じコードがデプロイされている環境、ステージング。とはいえ、そのインフラ構成も完全に同じというわけではありませんでした。
チームとしては、ステージング環境の移行は本番環境のリハーサルであると捉え、まずは想定される本番環境移行手順となるべく変わらないようステージング環境を本番環境の構成に合わせる作業を行いました。元々合っていて然るべきという意見もあるかもしれませんね。それはそうという感情もありつつ、長い歴史の中で今こうして freee というプロダクトを世に提供出来ていることに比べれば些細な問題で、今どうするか、これからどうしていくかということを真剣に考えることの出来た良いプロジェクトでありました。

それはさておき、このあたりで議論したのが pod や node に対する水平スケーリング戦略でした。
既存環境においては Auto Scaling Group(ASG)によるスケーリング戦略を取っていましたが、EKS環境においては Cluster Autoscaler(CA) と Horizontal Pod Autoscaler(HPA)を採用しました。HPA によってスケールした pod がリソース的に node に乗り切らなくなると CA によって node がスケールするという具合です。おそらく EKS環境においては一般的な戦略かと思われます。

HPA は pod の使用するリソース(CPU、メモリ)以外にもカスタムメトリクスによるスケーリングが可能です。
こちらを利用することでより理想的なスケーリングルールを定義出来るというチャレンジで、我々は Datadog のメトリクスを HPA のカスタムメトリクスとして利用する手法を採用しました。例えば、HTTPリクエスト数による web pod のスケールや、非同期ジョブのキュー数による worker pod のスケールが設定可能です。

これらの調査と検証を進めたフェーズで、具体的に本番環境移行に対する現実感も増してきた時期という気がします。

ステージング環境での性能試験

本番環境の移行を見据えて、これまで準備してきたスケーリング設定やモニタリングアラート、現在のピークタイム負荷に対するリソースキャパシティプランニングを見据えた性能試験を実施しました。
スケーリング設定やモニタリングアラートの試験は特に問題なく行うことができましたが、リソースキャパシティを測る部分においては十分に実施できなかったことを認めます。本番環境と同等のリクエスト数を試験的に流すことは可能でも、ありとあらゆるリクエストタイプを本番同等に実行可能なシナリオを定義することは非常に難しく、1つのAPIエンドポイントをベースにリクエスト数を変えながら検証を行いました。
本番環境の移行は旧環境から新環境へリクエストの重みを数%ずつ増やしながらダウンタイム無しで移行する戦略を想定していたので、最悪EKS側の負荷が問題になった場合においては即EKS側へのリクエストを0%に戻すことで対処できるという算段で進めました。試験は gatling というツールを使用して行いました。

リソースキャパシティについてはそこそこに、pod や node が想定通りにスケールアウトするか、ログは収集出来ているか、モニタリング・アラートは機能するかという点はしっかり検証を行いました。

ステージング環境での移行リハ

本番環境での移行手順を確立するため、ステージング環境にて移行リハーサルを複数回行いました。
本番環境とステージング環境のネットワーク構成を比較し、移行手順を1つずつステージング環境で実施しながら本番環境でも適用できるかを確認していくという地道な作業となりました。このタイミングで本番環境の移行手順、問題が発生した場合の切り戻し手順とエスカレーション先などを全て定義し、関連する他サービスのチームにも具体的なスケジュールを共有しながらついにここまで来たかという緊張感漂う瞬間でありました。

本番環境移行

コロナの影響で全員がリモートの中移行作業が開始されました。
旧環境から新環境へ数%ずつリクエストの重みを変更し、アラートに注意しながら少しずつ移行を進めました。
ここまで来るとやることはシンプルで、作成した移行手順書を元にチームメンバをオペレーターと状態監視に分けて移行を完了しました。

以降作業をやっていたリモート会議のスクリーンショット。満面の笑み。
移行後の様子

今後の話

ひとまず移行は完了しましたが、その過程全てが想定通りうまくいったわけではありませんでした。
現在も、CA や HPA 周りのスケール調整、監視アラートしきい値の調整、その他にもまだ課題は残っていますが、有志が集まったプロジェクトから始まりここまでやり遂げることができ、組織的にも個人的にも得られた知見は多くあったのかなと思っています。

今後は、kubernetes を中心とした社内 CI/CD 基盤開発をより加速しつつ、組織として kubernetes 運用の段階的なレベルアップ、そしてまだ残っているEKSベースでない既存サービスの移行など山盛りの課題に向かって日々取り組んで行きます。

こういったまだまだ freee の DevOps の理想形を追い求めることの出来る環境や、移行についてのより詳しい話など興味を持って頂いた方がいらっしゃれば、是非軽い気持ちでご連絡ください。私も色々なチームの事例を是非聞いてみたいので情報交換出来ると嬉しいです!

We are hiring!

freee では複数のポジションで一緒に働く仲間を募集しています。「興味あるよ」という方は是非こちらのページもご覧ください。

jobs.freee.co.jp