ZEN Study SRE 10年のインフラ・CI/CDの変遷

インフラ・CI/CDの変遷

ZEN Studyが10周年を迎えるにあたり、インフラやCI/CDの仕組みがどう変わってきたかを振り返ります。

2016年〜: 立ち上げ期

2016年のサービス開始時のZEN Studyは、EC2上にマイクロサービス群を直接デプロイする構成でした。本番環境にコンテナ系の技術は導入されておらず、アプリケーションのデプロイはCapistranoで行っていました。EC2インスタンス上のミドルウェアはitamaeで構成管理をしており、Rubyのバージョンアップ作業では各サービスの本番インスタンスに1台ずつSSH接続してitamaeレシピを実行するという手順をとっていました。

dev環境の一部リソースはTerraformで管理されていましたが、本番環境のAWSリソースはWebコンソール上で直接変更を反映する形で運用されていました。変更承認には手順書のレビューという形を取っていましたが、内容が不十分だったり、後から見たときに変更の経緯がわからないものも多くありました。

ビルドやデプロイの自動化にはJenkinsを利用していました。プラグインやジョブの設定、ユーザーの権限管理は基本的に手動で行っており、プラグインの自動更新の問題によりJenkinsの再起動がままならなくなることもありました。また、Jenkins環境のランタイム導入などはサーバーにSSH接続して直接コマンドを実行する運用でした。

CIには社内のオンプレミス環境で稼働するDrone CIを利用していました。Drone CIはDockerコンテナ上でビルドを実行するツールで、ビルド定義をコード化でき、サイドカーとしてRDBコンテナを追加できるなど柔軟な構成が可能でした。しかし、マシンリソースの消費が大きく、並列ビルドを実行するとリソース不足に陥るため並列数を1に制限して運用していました。Docker in Docker(dind)の運用負荷も高く、セルフマネージドな構成の維持に多くの工数を要していました。AndroidやiOSのビルドについては、社内のオンプレミスサーバー上に構築したJenkinsで行っていました。

こうした運用を続ける中で、構成管理が属人的になっていることやコンピューティングリソースをうまく活用できていないことが課題になっていました。EC2のインスタンスタイプではアプリケーション特性に合わせた最適化に限界があり、コンテナ技術への移行が必要な状況でした。

2017年頃〜: Kubernetes導入期

2017年のKubernetes移行後の構成図

前述の課題を解決するため、Docker SwarmやMesosとの比較検討の末にKubernetesが選定されました。2017年3月頃にdev環境の構築が開始され、2017年8月頃には本番環境での稼働が始まりました。一部のマイクロサービスから順次移行を進め、残りのサービスは2018年以降に移行する計画で進められました。

しかし、このKubernetes環境はControl Planesを含むすべての構成要素を自前で構築・運用するいわゆるhard wayの構成でした。2017年当時、AWS上でKubernetesのマネージドサービスであるAmazon EKSはまだ提供されておらず、AWS上でKubernetesを利用するには自前で構築するほかありませんでした。Kubernetes周辺のエコシステムやツール群も発展途上であり、クラスタの運用やデプロイ、ログの確認といった多くの作業を手作業に頼らざるを得ない状況でした。

自前構築ゆえにクラスタアップグレードの手順が煩雑で、Kubernetesの各コンポーネントバイナリ(hyperkube)の入れ替えやetcdのマイグレーションなど多くの手順を踏む必要がありました。アップグレード手順の確立に時間を割けないまま別の優先度の高い案件に追われ、結果として作成当初のバージョン1.7のまま運用を続けることになりました。バージョンが古いままだと新しいエコシステムのツールが利用できず、アップグレードの手順もバージョンが離れるほど複雑になるという悪循環に陥っていました。

この悪循環の影響はetcdの運用にも表れていました。etcdのクラスタでは原因不明のメモリリークが発生し、定期的に再起動する運用を組み込まなければなりませんでした。3台構成のetcdクラスタでASG上のインスタンス入れ替えを行った際には、デタッチ中のインスタンスも含めて6台が存在する状態となり、etcd-aws-clusterのスクリプトが不正な設定を生成してしまう障害も起きました。

また、flannelが各ノードに割り当てる /24 サブネット内でPodのIPアドレスが重複してしまう問題もありました。これはPod削除時にCNIのリソース解放処理が適切に呼ばれないケースがあることと、host-local IPAMプラグインのリース管理の不備が組み合わさった問題でした。Pod削除時に解放されなかったIPのリースファイルが蓄積し続け、/24 の254個を使い切ると既存のPodと同じIPが割り当てられてしまいます。これらは新しいバージョンのKubernetesやCNIプラグインでは修正されていましたが、バージョン1.7では修正が適用できませんでした。該当するPodが発生した際には手動で削除するワークアラウンドで対処していました。

デプロイについては、引き続きJenkinsで行っていました。Pipeline Jobにより、Jenkinsのインスタンスから各種Kubernetesクラスタに kubectl apply する形でマニフェストを適用していました。しかし、デプロイのタイミングで複数のYAMLファイルや環境変数を動的に合成してからapplyする仕組みだったため、リポジトリ上のファイルだけでは最終的に適用されるマニフェストの内容を把握できないという課題がありました。

2021年頃〜: IaC整備・CI刷新期

2021年のIaC・CI刷新後の構成図

また、リリース以降ほとんど行われていなかったインフラ構成のコード化にも取り組みました。2021年頃にTerraformを本番導入し、AWSリソースの変更に対してコードレビューによる管理ができるようになりました。導入当初はリソースの数が膨大で、チームメンバーの入れ替わりにより経緯が不明なものも多かったため、Terraformerのインポート機能で既存リソースを一括コード化するところから始めました。その後、数年の運用を経てサービス単位でのモジュール化を進めるなど、管理しやすさを向上させるためのリファクタリングも行っています。

CIについては、Drone CIのセルフマネージドな構成の運用負荷が高い問題を解決するために、AWS CodeBuildへ移行しました。他にも候補はありましたが、すでに社内で稟議が通っていたAWS上のマネージドサービスであるCodeBuildを採用しました。CodeBuildは完全マネージドでランタイムの運用が不要であり、ビルドごとに独立した環境が用意されるため、Drone CIで問題となっていたリソース不足や並列数の制約から解放されました。また、ビルド定義をリポジトリ内のbuildspec.ymlとして管理できる点もメリットでした。

CodeBuildのプロジェクト管理はTerraformで行っており、各チームがCIを利用したい場合はmoduleを呼び出す形でプロジェクト定義のPRを送り、SREがレビュー・マージして terraform apply することでCodeBuildプロジェクトが作成される運用になっていました。

ただし、CodeBuildにも課題はありました。Terraformによるプロジェクト設定とbuildspec.ymlによるビルド定義の両方を管理する必要があり、変更時に双方の整合性を取る運用が求められました。また、phaseが4つ固定であるという制約や、ログ出力にエスケープシーケンスが混入するなどの使い勝手の面でも課題を感じていました。加えて、CodeBuildをCIとして利用する際の情報が不足している状況で、ビルド定義の書き方や運用のベストプラクティスを模索する必要もありました。

2023年11月頃〜: Amazon EKS移行期

2023年のAmazon EKS移行後の構成図

前述の自前Kubernetesが抱えていた問題(バージョンアップの困難さやetcdの障害)に対する対処の限界から、マネージドサービスであるAmazon EKSへの移行を決定しました。EKSではControl Planesの運用がAWSによって管理されるため、etcdの運用課題が解消されました。また、EKSが提供するマネージドなアップグレードパスにより、Kubernetesのバージョンアップを継続的に行えるようになりました。さらに、Blue/Green構成により無停止でクラスタを切り替えられる設計としたことで、移行のリスクを最小限に抑えることができるようになりました。一方で、EKSへの移行でクラスタ自体の運用課題は解消された反面、クラスタ内のサービス間通信の可視化や障害検知といった運用面でレガシーな部分が残っており、これらは継続的な改善課題となっています。

この移行に関しては下記記事にて取り上げていますので、ご参照ください。 https://blog.nnn.dev/entry/2024/04/17/110000

Kubernetesへのマニフェスト適用については、EKSへの移行にあわせてArgoCDを導入しました。前述のとおり、自前Kubernetes時代はデプロイ時にマニフェストを動的に合成する仕組みだったため、実際にクラスタに適用される内容がリポジトリから読み取れない状態でした。ArgoCDの導入により、Gitリポジトリの内容がそのままクラスタの状態を表すGitOps運用に切り替わりました。

EKS移行後のデプロイフローでは、アプリケーションリポジトリへのマージをトリガーにJenkinsがDockerイメージのビルドやアセットのアップロードを実行し、完了後にマニフェストを管理するリポジトリへPRを作成・マージします。ArgoCDがその変更を検知してクラスタに反映するという構成に整理されました。

ビルドパイプラインについては、Jenkins Configuration as Code(JCasC)を導入した新しいJenkinsを構築しました。ジョブ定義やクレデンシャルをコードで管理できるようになり、以前のようにプラグインやジョブを手動で管理する状態からは脱却しました。ただし、JCasCの知見が社内にあまりなかったため、運用は手探りの部分も多くありました。

2024年6月〜: サイバー攻撃後の再構築

2024年のGHEC移行後の構成図

サイバー攻撃からの復旧・刷新プロセスで、GitHubがGitHub Enterprise Server(GHES)からGitHub Enterprise Cloud(GHEC)に移行しました。

GitHub Enterprise Cloudへの移行により、これまで十分に利用できていなかったGitHub Actionsが使えるようになりました。Jenkinsが担っていたDockerイメージのビルドやマニフェストを管理するリポジトリへのPR作成、CodeBuildで実施していたCIをGitHub Actionsに集約し、PRの作成およびマージ時に自動でCI/CDが動作するフローを整備しました。これにより、それまでJenkins、CodeBuild、ArgoCDと複数のツールにまたがっていたCI/CDが、GitHub ActionsとArgoCDの組み合わせに整理されました。

これから取り組んでいきたいこと

Terraformによるインフラ管理については、前述のとおりTerraformerで一括インポートしたコードをベースに運用を続けてきました。ようやくサービス単位でのモジュール化に着手し始めたところで、まだまだ整理しきれていない部分が残っています。

EKSクラスタの運用面では、サービスメッシュやオブザーバビリティ向けのコントローラー、各種Operatorといったものの整備がまだ追いついていません。トラフィック管理やサービス間通信の可視化、障害検知などで手作業が残っており、このあたりを改善していきたいと考えています。

サービスの成長とともにインフラ・CI/CDも変わり続けてきました。これからも安定した開発・運用基盤を目指して改善を続けていきます。