はじめに
技術評論社様より発刊されているSoftware Designの2024年5月号より「レガシーシステム攻略のプロセス」と題した全8回の連載が始まりました。
本連載では、ZOZOTOWNリプレイスプロジェクトについて紹介します。2020年に再始動したZOZOTOWNリプレイスでは、「マイクロサービス化」が大きなカギとなりました。今回は、SRE部が行った、リプレイス方針の決定から導入ツールの選定、マイクロサービスのリリース方法の改善までを紹介していきます。
目次
- はじめに
- 目次
- ZOZOTOWNリプレイスにおけるSRE部の方針
- IaCの導入
- CI/CDの導入
- Canary Releaseの導入
- Progressive Deliveryの導入
- GitOpsの導入
- マイクロサービスのリリース方法の改善
- おわりに
こんにちは。株式会社ZOZOの技術本部 SRE部の籏野(@gold_kou)と堀口(@makocchi6)と申します。第2回は、ZOZOTOWNリプレイスにおける、SRE部によるIaC(Infrastructureas Code)やCI/CD関連の取り組みを中心にまとめます。
ZOZOTOWNリプレイスにおけるSRE部の方針
ZOZOTOWNの開発に従事するSite Reliability Engineerは、技術本部のSRE部という組織に所属しています。2020年にZOZOTOWNのリプレイスを本格的に再開した際に、SRE部としていくつかの方針を決定しました。
まずは、AWSの導入です。ZOZOTOWNはもともと、オンプレミスのインフラ構成になっており、スケールアウトに課題がありました。たとえば、大規模なセールでは平常時の3倍以上のリクエストを受けますが、そのピークトラフィックを捌さばくために相応のインフラ設備を購入する必要があります。これには数ヵ月といったリードタイムが発生するため、準備も入念に行う必要があるうえ、スケールアウトが不十分でシステムエラーが多発するケースもありました。AWSを導入することでその問題を解決することにしました。
また、マイクロサービスはコンテナで管理することにしました。コンテナ技術を利用することで、処理速度および起動速度が速い、Amazon ECR(以下、ECR)などのイメージレジストリを利用できる、などさまざまなメリットを享受できます。
そして、コンテナ化されたマイクロサービスはAmazon EKSのKubernetes(以下、K8s)クラスター上で稼働させることにしました。ZOZOでは、このマイクロサービスが稼働するK8sクラスターを「プラットフォーム基盤」と呼んでいます。プラットフォーム基盤は、マルチテナンシー方式で、複数のマイクロサービスをnamespace単位で区切り、単一のK8sクラスター上で稼働させています。マルチテナンシー方式を採用した理由は、管理のしやすさやリソース効率の良さなどに加えて、リプレイスの速度を上げるためです。当時のZOZOの状況では、マルチテナンシー方式を採用することで、マイクロサービスの構築をパターン化して量産する体制を整えやすいと判断しました。
ほかにもこの時点で、IaC、CI/CD、Canary Release、Progressive Deliveryの導入を決定しました。また、それらを導入する中で、GitOpsの導入やマイクロサービスのリリース方法を改善するようになりました。本記事では、これらの取り組みを紹介します。
IaCの導入
IaCとは
IaCは、インフラ構成をコード化して、そのプロビジョニングを自動化する手法です。次の効果が期待できます。
- Gitによるバージョン管理とレビュー環境の提供
- 構築作業におけるヒューマンエラーの削減
- 新規メンバーのキャッチアップが容易
- 自動化による開発効率とリリーススピードの向上
- コードの再利用
- インフラへのCI/CD導入
プラットフォーム基盤におけるIaC
プラットフォーム基盤のインフラはほとんどがIaC化されています。具体的には、AWSリソースはAWS CloudFormation(以下、CloudFormation)、K8sリソースはK8sのマニフェスト、DatadogリソースとPagerDutyリソースとSentryリソースはTerraformによりIaC化されています。
CI/CDの導入
CI/CDとは
CI/CDは、CI(継続的インテグレーション)とCD(継続的デリバリー)の組み合わせです。CIはコードの変更を起点にコードの静的分析、ビルド、テスト、成果物の生成などの実行を自動化する手法です。一方、CDはCIで検証・テストされたコードや成果物を目的の環境に自動でデプロイする手法です。CI/CDを導入することで、本来は人が行う必要のない作業が自動化され、結果的に空いた時間で本質的な作業に人が取り組めます。これは、メンバーのモチベーション向上と、組織全体の生産性向上につながります。
プラットフォーム基盤におけるインフラのCI/CDでは、AWSリソースとK8sリソース、Terraformリソースの変更を検知して、リソースを更新します(図1)。前提として、IaCが導入されている必要があります。
GitHub Actions
CI/CDのツールはGitHub Actions(GHA)を採用しました。2020年当時は、GHAがまだサービスとしてリリースされて間もなく、マトリックスビルドができるなどの利点はあるものの、機能的に他のツールと比べて大きな優位性はないという社内評価でした。しかしながら、GitHubとの統合性の高さや将来性をふまえGHAを採用しました。
変更のあるインフラリソースのみをCIの対象とする工夫
前提として、プラットフォーム基盤のインフラを管理するGitHubリポジトリは、直下に「cloudformation」「k8s」「terraform」というそれぞれのIaCのディレクトリが存在する構成になっています(図2)。そしてそれぞれの配下に、環境(dev、stg、prdなど)やマイクロサービスなどに応じたディレクトリが存在します。
この工夫では、OSSのtj-actions/changedfilesを利用し、Pull Request(以下、PR)で変更のあったディレクトリ情報のみを取得して、その情報を後続のJobへ渡すようにしました。後続のJobでは、変更のあったディレクトリに関するインフラリソースに関してのみ処理されます。結果として、すべてのインフラリソースに対してCIを実行していたころよりも、大幅にCIの時間を短縮できました。また、マイクロサービスがどれだけ増えても実行時間が長期化することはなくなりました。
Canary Releaseの導入
Canary Releaseとは
Canary Releaseは、新しいバージョンのアプリケーションを段階的にデプロイする手法です。段階的にリリースすることで、新しいアプリケーションにバグがあった場合でもユーザーへの影響を最小限に抑えられます。また、リリース作業をする人(以下、リリーサー)の心理的負担も軽減できます。
ZOZO API GatewayとALBによるCanary Release
最初に、内製のZOZO API GatewayとAWSのApplication Load Balancer(以下、ALB)によるCanary Releaseをプラットフォーム基盤に導入しました。
ZOZO API Gatewayの加重ルーティング機能を利用して、各マイクロサービスのCanary Releaseを実現しました*1。
そして、ALBの加重ルーティング機能を利用して、ZOZO APIGateway自体のCanary Releaseを実現しました。ZOZO API Gatewayの前段にはIngressリソースであるALBが存在します。ZOZO API GatewayのPrimaryリソースとCanaryリソースをそれぞれ用意し、それらをターゲットグループとして任意の加重率を設定します。加重率の設定は、リスト1のようにIngressリソースのannotationで行います。この設定を適用すると、AWS Load BalancerControllerはIngressリソースの変更を検知し、ALBのListener Ruleを更新します。
alb.ingress.kubernetes.io/actions.forward-external-traffic: | { "Type":"forward", "ForwardConfig":{ "TargetGroups":[ { "ServiceName":"zozo-api-gateway-primary", "ServicePort":"80", "Weight":90 }, { "ServiceName":"zozo-api-gateway-canary", "ServicePort":"80" "Weight":10 } ] } }
リスト1 ALBの加重ルーティング設定例
IstioによるCanary Release
前述したZOZO API GatewayとALBによるCanary Releaseを導入した後に、プラットフォーム基盤にIstioを導入しました。マイクロサービス間通信およびプラットフォーム基盤外への通信において、一貫した通信制御を提供するサービスメッシュが必要だったためです。
Istioの導入に伴い、Canary Releaseの手法を、IstioによるCanary Releaseに変えました。リスト2は、Istioの DestinationRule と Virtual Serviceにより、ZOZO API GatewayをCanary Releaseする設定例です。DestinationRuleでは、hostやsubsetsのprimaryとcanaryを定義します。VirtualServiceでは、destination ごとにDestinationRuleで定義したhostとsubsetを指定し、weightで加重率を設定します。この設定を適用すると、istiodにより自動的にistioproxyのconfigが更新され、ZOZO API Gatewayへのトラフィック加重率が変更されます。
apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: destinationrule spec: host: zozo-api-gateway.ns.svc.cluster.local subsets: - name: primary labels: version: zozo-api-gateway - name: canary labels: version: zozo-api-gateway-canary --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: virtualservice spec: hosts: - zozo-api-gateway.example.com // 省略 http: - route: - destination: host: zozo-api-gateway.ns.svc.cluster.local subset: primary weight: 90 - destination: host: zozo-api-gateway.ns.svc.cluster.local subset: canary weight: 10 // 省略
リスト2 IstioによるCanary Releaseの設定例
ZOZO API Gatewayだけでなく、ほかのマイクロサービスも同様の方法で Canary Releaseできます。
Progressive Deliveryの導入
Progressive Deliveryとは
Progressive Deliveryは、Canary Releaseも含め、より広範に新しいバージョンのアプリケーションを安全にリリースするための概念です。そこには、Blue/GreenデプロイメントやA/Bテストなどに加えて、Canary Releaseの自動化も含まれます。
前述のとおり、プラットフォーム基盤には IstioによるCanary Releaseを導入しました。しかしながら、Canary Releaseの進行における判断コストや加重ルーティングの進行、切り戻しなどの作業に関して運用コストが高いという課題がありました。そこで、Progressive DeliveryによるCanary Releaseの自動化を導入しました。
Progressive Deliveryツールの選定
Progressive Deliveryツールの候補としてArgo Rollouts、Spinnakerなどがありましたが、ZOZOのプラットフォーム基盤では次の理由からFlaggerを採用しました。
- Istio との連携をサポートしており、自動でVirtualServiceの加重を変更できるため
- Datadogのメトリクス取得をサポートしており、判断基準に使用できるため
いずれも、もともとは人が実施していた作業を自動化するだけなので、導入イメージが湧きやすかったという点が大きかったです。
Flagger
Flaggerは、Progressive Deliveryを実現するKubernetes Operatorです。Flaggerを導入する主なメリットは2つです。
1つ目は、Canary Release作業の工数削減です。Flaggerはメトリクスの取得・分析、判断、加重率の変更作業などをすべて自動化してくれます。
2つ目は、Canaryリソースのコスト削減です。K8sのHorizontalPodAutoscalerの仕様上、min Replicasを0にはできません。したがって、Canary Release時以外でもCanaryのPodを最低1つは常時起動しておく必要がありました。しかし、Flaggerを導入すればそれが必要なくなります。
なお、プラットフォーム基盤では、Flaggerは次のように動作します(図3)*2。
- 加重変更 : VirtualServiceのweightを変更
- スケールアウト/イン : K8sの設定を変更
- メトリクス取得 : Datadogにクエリを発行
- 通知・アラート : Slackに通知
GitOpsの導入
GitOpsとは
GitOpsは、Gitリポジトリを唯一の真実の情報源(Single Source of Truth)とし、K8sクラスターが自身の状態をGitリポジトリと同期するCD方式です。いわゆるPull型と呼ばれ、定期的にGitリポジトリをチェックし、変更があった場合はその変更を自動的に反映します。
一方、これまでプラットフォーム基盤にはCIOpsと呼ばれる、Push型のCD方式を導入していました。GitHubのPRのマージをトリガーにCIが走り、K8sクラスターへGHAがapplyをしていました。つまり、GHAにはapplyを実行するための強い権限が付与されていました。
CIOpsからGitOpsにすることで、CIはGHAの責務、CDはGitOpsツールの責務として分離し、GHAから強い権限を剥がせます。また、ワークフローのシンプル化や高速化も期待できます。
GitOpsツールの選定
ZOZOのプラットフォーム基盤ではFlux2を採用しました。類似のOSSとしてArgoCDが挙げられます。Flux2を採用した理由は、すでにプラットフォーム基盤で利用している FlaggerがFlux2と同じFlux Projectに所属しており、親和性の高さを期待できたからです。
Flux2
Flux2は、GitOps Toolkitと呼ばれるいくつかのコンポーネントにより動作します。たとえばSource ControllerやKustomize Controllerです。
Source Controllerは、Gitリポジトリ、Helmリポジトリ、バケットなどからアーティファクトを取得します。プラットフォーム基盤では、GitRepositoryというカスタムリソースを使用して、プラットフォーム基盤のインフラを管理しているGitリポジトリからK8sマニフェストを取得しています。
Kustomize Controllerは、Source Controllerによって取得したマニフェストをfetchし、それらをクラスターに適用します。Kustomizeという機能を利用して、マニフェストのカスタマイズやパッチ適用も行えます。Kustomizationというカスタムリソースを管理します。
リスト3はzozo-web-gateway
というマイクロサービスのGitRepositoryとKustomizationの例です。GitRepositoryは、プラットフォー ム基盤のインフラを管理するGitHubリポジトリのreleaseブランチに対して、secretに保存されているSSH鍵を使って1分間隔でfetchする設定です。Kustomizationは、そのGit Repositoryのk8s/prd/zozo-web-gateway
というディレクトリに存在するK8sマニフェストを1分間隔でfetchして、クラスターに展開する設定です*3。
apiVersion: source.toolkit.fluxcd.io/v1 kind: GitRepository metadata: name: zozo-web-gateway namespace: zozo-web-gateway spec: interval: 1m0s ref: branch: release secretRef: name: zozo-web-gateway-flux-secrets-202307120000 url: ssh://git@github.com/xxx --- apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization metadata: name: zozo-web-gateway namespace: zozo-web-gateway spec: interval: 1m0s path: ./k8s/prd/zozo-web-gateway prune: false sourceRef: kind: GitRepository name: zozo-web-gateway suspend: false
リスト3 GitRepositoryとKustomizationのYAML設定例
マイクロサービスのリリース方法の改善
改善前の課題
プラットフォーム基盤の構築当初は、masterブランチからreleaseブランチ宛のPR(以下、Release PR)を作成すると本番環境のCIパイプラインが動作し、PRをマージするとCDパイプラインが動作するという方法でリリースしていました。この手法のメリットは、リリース前の動作確認が可能で、リリース手順も簡単であることでした。しかし、1つのRelease PRで複数のチームの複数のマイクロサービスのリリースを管理することになるため、リリース時のチーム間での調整コストが発生したり、リリーサーを制限できなかったりなどの課題がありました。
課題を解決したCDパイプライン
Required reviewers
GitHubのEnvironmentsに、Deployment protection rulesという機能が存在します。たとえば、Required reviewersというruleを使用すると、そのEnvironmentに関して特定のチームや個人による承認を必須とします。図4は、pf_infra_sreというEnvironmentの例です。Web UI上で設定と承認ができます。リスト4のように、Required reviewsが設定されたEnvironmentをGHAのjobで指定すると、そのjobは承認されるまで実行されなくなります。
release-approval: runs-on: ubuntu-latest environment: name: pf_infra_sre
リスト4 Environmentを使用したjobの例
このjobをデプロイ関連のjobのneedsに指定すれば、結果的にリリーサーを特定のチームや個人に絞れます。
OCIRepositoryに変更
Flux2のSource ControllerをGitRepositoryからOCIRepositoryに変更しました。OCI Repositoryは、GitRepositoryと同じくSource Controllerが管理するカスタムリソースの1つです。OCI(Open Container Initiative)互換のレジストリを参照します。プラットフォーム基盤にFlux2を導入した当初はOCIRepositoryという選択肢がありませんでしたが、この時点では利用できるようになっていました。
GitRepositoryのままだと、PRをマージしたタイミングでGitHubリポジトリを参照してデプロイされてしまいます。これでは、そのGitHubリポジトリの権限を持つスタッフは誰でもPRをマージできてしまうので、Required reviewersでリリーサーを制限しようとしても意味がありません。
そこで、Required reviewersでの承認後にOCIRepositoryへKustomizeマニフェストをpushするGHAのjobを構築しました。OCI RepositoryにはECRを利用し、KustomizationがECR上で管理されているマニフェストを取得して、クラスターへ適用します。また、OCI Repositoryを採用することで、SSH鍵の管理が不要になりました。
改善の結果
以上の改善により、リリーサーを制限できるようになりました。また、Release PRを使う必要がなくなったので、マイクロサービスのリリースにおけるこれまで発生していたチーム間での調整作業が不要になりました*4。
おわりに
第2回では、ZOZOTOWNリプレイスにおけるIaCやCI/CD関連の取り組みを中心に紹介しました。ZOZOTOWNのリプレイスにより、「柔軟なシステム」「技術のモダン化」「開発生産性の向上」「採用強化」という4つの効果が期待できます。今回の取り組みの紹介を例に挙げると、AWSやK8sなどのクラウド化により、スケーリングなどの観点で柔軟なシステムの構築が可能になりました。IaCやCI/CDに関する複数のモダンな技術を取り入れ、開発生産性が向上しました。そして、これまでの取り組みをテックブログで公開したり、イベントで発表したりしました。その結果、テック業界におけるZOZOの技術プレゼンスが向上し、採用強化につながりました。リプレイス前は10名以下だったインフラチームには、現在SRE部として30名以上が所属しています。
SRE部はZOZOTOWNの成長のため、今回紹介した事例に限らず、さまざまな課題の解決や取り組みをしてきました。今後もZOZOTOWNのリプレイスを進めていきます。
本記事は、技術本部 SRE部 ECプラットフォーム基盤SREブロックの籏野 光輝と、技術本部 SRE部 商品基盤SREブロック ブロック長の堀口 真によって執筆されました。
本記事の初出は、Software Design 2024年6月号 連載「レガシーシステム攻略のプロセス」の第2回「ZOZOTOWNリプレイスにおけるIaCやCI/CD関連の取り組み」です。
ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。