こんにちは。 サービスプラットフォーム部 SRE チームの谷合です。 普段はメガネやキャンプギア収集と、毎日のビールを楽しみに生きています。
前回は入社後意識的にやっていたボール拾いからトイルの改善に発展させた話を書きました。
今回はタイトルの通り、新規サービスの構成をアーリーフェーズで見直し、前例踏襲ではなく最適な構成に移行した取り組みをご紹介します。
序文
サービスが産声を上げた直後から数ヶ月のフェーズ。それは、システムが実際にユーザーに使われ始め、初期のアーキテクチャ設計の「答え合わせ」が始まるタイミングでもあります。
リリース前の制約のなかで決定したリリースストラテジーやシステム設計はやはり完璧ではなく、本格稼働して初めて見えてくる技術課題や、実際の運用に乗せてから改善したくなるポイントが次々と出てきます。
「サービス立ち上げ直後は機能開発を優先したい」状況になりやすいですが、今回はSREチームとして影響範囲を見極め、「まだシステムが小さく、しがらみが少ない今のうち」に根本的な構成変更へ踏み切りました。
なぜなら、サービスが成長し複雑化した後で運用やシステム構成を大きく変更するには、初期フェーズの何倍ものコストとリスクを伴うからです。
この判断を具体化した事例として、建設業界特化型 求人・転職サービス ビルダーワーク(以降Builder Work) のリリースを題材に、前例踏襲の構成を見直し、サービス特性に合わせて設計を最適化した背景と具体的な変更内容をご紹介します。
主に見直したのは、以下の2点です。
- リリースストラテジーを変更し、安全な限定公開を実現
- ドメインシャーディングを廃止し、外部からの通信経路を一本化
それぞれで実施した内容を、順にご紹介します。
リリースの改善
まず、今回のリリース改善の前提となる実行基盤を簡単に整理します。
アンドパッドでは、アプリケーションのコンテナをEKSで起動し、Ingressで外部公開しています。
Ingressの設定やバックエンドはAWS Load Balancer ControllerによってApplication Load Balancer(以降ALB)に反映されます。
また、その他周辺のAWSリソースはIaC(Terraform)で構成管理しています。
EKS採用やコンテナ化の背景は、以下の資料をご参照ください。
tech.andpad.co.jp speakerdeck.com
次に、以降の説明に出てくるツールを簡単に補足します。
Argo CDはGit上のマニフェストをKubernetesへ継続的に同期するGitOpsツールです。
Argo RolloutsはDeploymentリソースの代わりに段階的リリース(CanaryやBlueGreen)を実現するコントローラで、リリース時の切り戻しやトラフィックを制御しやすくします。
全社的には、アプリケーションのリリースにArgo CDとArgo Rolloutsを組み合わせたBlueGreenデプロイを採用するケースが多いです。
一方で今回のサービスは、初期リリース時点では通常のDeploymentリソースで運用していました。
なお、アンドパッドでは、以下の要件に対応するため、AWS Load Balancer Controllerのtarget-typeを instance ではなく ip で運用しています。(以降、インスタンスモード/IPモード)
- ノードを退役させやすくするために、アプリケーション終了時のルーティング制御をPodとALBに集約させたい
- ALBからgRPCでルーティングをするケースがあるため、ALBからノード(Serviceによるiptables proxy modeのL4負荷分散)を中継させずにPodに転送させたい
アーキテクチャの違いを簡単に図示すると、以下のようになります。
- インスタンスモード(target-type: instance)

- IPモード(target-type: ip)

インスタンスモードは「Node」をターゲットにするため、Podの入れ替えが発生してもALBのターゲット登録そのものは変わりにくい構造です。
一方でIPモードは「PodのIPアドレス」をターゲットにするため、リリースでPodが入れ替わるたびにALBのターゲット登録・解除も細かく追従する必要があります。
そのため、BlueGreenデプロイのように切り替えの瞬間に宛先が大きく動くパターンでは、登録解除と再登録のタイミング差が表面化しやすく、短時間のエラーにつながることがあります。
このIPモード環境において、弊社が広く採用しているArgo RolloutsのBlueGreenデプロイの場合、KubernetesのServiceリソースの役割(strategy.blueGreen.activeService / strategy.blueGreen.previewService フィールド)が固定であり、
Blue ↔︎ Green 切り替えを Serviceリソースの spec.selector.rollouts-pod-template-hash を切り替える ことで実施するため、ターゲット登録がなくALBが503応答する時間(以降、ダウンタイム)の発生が不可避になります。
この仕様についての詳細はAWS Load Balancer Controllerの以下のIssueをご覧ください。
また、Argo Rollouts 公式ドキュメントでも同じ内容について言及されています。
- Zero-Downtime Updates with AWS TargetGroup Verification
Targeting Pod IPs comes with an increased risk of downtime during an update, because the Pod IPs behind the underlying AWS TargetGroup can more easily become outdated from the actual availability and status of pods, causing HTTP 502 errors when the TargetGroup points to pods which have already been scaled down.
上述の通り、IPモード環境ではBlueGreenの切り替え時にダウンタイムが発生しやすいという問題があります。
この問題への対策として、すでに別のサービスでは strategy.canary(Ping-Pong feature)を採用しています。
Canaryストラテジーであれば、既存の限定公開の運用を維持しながら、ダウンタイムなしのリリースが可能です。
しかしこの仕組みは、弊社で広く展開されている「共通Helmテンプレート」には組み込まれていませんでした。
そのため、共通Helmテンプレートを利用してデプロイしているサービスでは、Canaryストラテジーを採用しにくい状況でした。
Builder Workでも限定公開の運用をしたいという要望があり、新たな仕組みを作るのではなく、Canaryストラテジーの既存知見を共通Helmテンプレートに組み込んで汎用化する方針で改修に踏み切りました。
具体的には、strategy.canary(Ping-Pong feature)を扱えるように共通Helmテンプレートを改修し、それを利用してBuilder WorkをDeploymentリソースからRolloutリソースへ段階的に移行しました。
なお、今回は通常のCanaryでなくPing-Pong featureを採用していますが、説明を含めると長大となるため、挙動をより詳しく知りたい場合は、公式ドキュメントをご参照ください。
また、手前味噌ですが私の検証スクラップにもPing-Pong featureのメリットや動作例を詳しく書いていますので、こちらも併せてご参照ください。
ここからは、Builder Workで実際に行った移行事例をご紹介します。
- Rolloutリソースへ移行する際に、既存のDeploymentリソースと共存できるようにする
- 将来別のサービスでDeploymentからRolloutへの移行をする際にも対応しやすいように、フラグ切り替えで段階的に移行できるように共通Helmテンプレートを改修する
このうち、SREチームが主体で進めたのは主に次の2点です。
- 共通Helmテンプレートで
migratingフラグを含む移行用テンプレートを設計・実装 strategy.canary(Ping-Pong feature)を共通利用できるvalues設計を整備し、Builder Work側へ適用
1の対応では、templateへ渡すvaluesに migrating フラグを新設し、Deploymentリソース用のtemplateの最上段に{{- if or .Values.migrating (and (not .Values.blueGreen.enabled) (not .Values.canary.enabled)) }} といった条件式を付与することでRolloutリソースと共存できるようにしました。
このフラグをtrueにすることで、既存のDeploymentリソースに紐づくServiceリソースの他にCanaryリリース用のStable/Canary Serviceリソースも作成されます。
falseにすることで、Deploymentリソースおよび、紐づくServiceリソースが削除されます。
# -- DeploymentリソースからRolloutリソースへ移行中か migrating: true
2の対応では、同じくtemplateへ渡すvaluesに strategy.canary 用の設定を追加しました。
これにより、「どのサービスでも同じ型でCanary運用できる」状態にしました。
ポイントは、環境ごとの差分をtemplate側に埋め込まず、valuesで切り替えられるようにした点です。
たとえば canaryReplicaCount や steps で検証手順を段階的に定義し、trafficRouting.alb.ingresses で対象Ingressを明示することで、Argo Rolloutsの挙動を宣言的に管理できるようにしています。
これにより、Builder Work固有の実装に閉じず、今後別サービスへ横展開する際も同じ運用パターンを再利用しやすくなりました。
canary: # -- Canary Releaseを有効にするか enabled: false # -- canary検証フェーズのPod数(RolloutリソースのsetCanaryScale.replicas) canaryReplicaCount: 1 # -- canaryリリースの各ステップ設定 steps: # -- Canaryリリースの各ステップを指定する - setWeight: 0 - setCanaryScale: # -- canary用Pod数を指定する replicas: 1 # -- 動作確認のためのpause - pause: {} - setCanaryScale: weight: 100 # -- ALBのingress名のリストを指定する trafficRouting: alb: # -- ALBのingress名のリストを指定する ingresses: - my-app-alb-ingress - another-alb-ingress # -- ALB の rootService で使う `ports` のキー名。multiport (`ports`) 利用時は必須。 # 単一ポート (legacy) のときは指定不要で `service.port` が使われる。 servicePortName: ""
これで、Argo CDのUI上でRolloutリソースが見えるようになり、Canaryリリースが可能となりました。
以降の運用は開発チームが中心となり、リリース時には限定公開したCanary Podで最終チェック、問題なければPromoteでの全体公開を行なっています。
ネットワーク構成の最適化
アンドパッドでは主に、ALBを入り口として、その先のアプリケーションやAmazon CloudFront(以降CloudFront)と協調する構成を多くとっています。
既存サービスのほとんどでは、CloudFrontはサービス立ち上げ後しばらくしてから後付けで導入されており、初期構成から組み込まれているケースは多くありません。
ドメインシャーディングが基本となったのは、S3やLambda@Edgeをオリジンとするディストリビューションと、アプリケーションサーバをオリジンとするディストリビューションが共存するケースで、パスの競合を避ける必要があったためです。
今回のBuilder Workも、他の構成に倣ってリリース当初はALBを入り口としてネットワークを構成していました。
ただし、Builder WorkはS3やLambda@Edgeをオリジンに加える見込みもなく、オリジンとAPIも同一サーバです。
社内でアプリケーションサーバのみをオリジンとするサービスはこれが初めてで、前例どおりのドメインシャーディング(フロント配信ドメインとAPIドメインを分ける構成)が常に最適とは言えない状況でした。
具体的には以下のようにドメインが分離されていました。
- www.hoge.jp → ALBでアプリケーションを公開(API)
- cdn.hoge.jp → CloudFrontアセット配信
この構成で、以下のような課題が生じました。
- APIとCloudFrontのドメインが異なることから、ブラウザから見た時にクロスオリジン構成となっており、CORSエラーとなる場合がある
- 開発環境ではAmazon Cognito(以降Cognito)で限定公開しているため、CloudFront経由でのアセット取得時にHOSTヘッダの違いで401エラーとなり、特定パスをIngressルールで迂回させる必要がある

これをAWSのアイコンで示すと以下のようになります。

この構成は上記のように複雑なネットワーク構成となっており、かつブラウザから見るとオリジンが2つあるように見えるため、CORSエラーが起きやすくなっていました。
さらにHostでCognitoを迂回させる必要があるため、Ingressルールの数が多く、初見では理解が難しい状況でした。
この課題を受け、ドメインシャーディングを行わない構成も検討しました。
具体的には、既存の www.hoge.jp のAlias先をALBからCloudFrontへ切り替え、ALBはCloudFrontのオリジンとしてのみ利用する案です。
通常のリクエスト経路をCloudFront経由に統一し、ALB -> アプリケーション の経路に一本化する方針です。
あわせて、AWS テクニカルアカウントマネージャー(AWS TAM)にも相談し、この方針の妥当性の評価、ベストプラクティスの確認、ドメインシャーディング採用時のPros&Consの整理などを行いました。 その結果、以下のように通信経路を一本化する構成を提案していただきました。
- ALB: PublicサブネットのInternet-facingから、PrivateサブネットのInternalへ変更
- CloudFront: Internal ALBをVPCオリジンとして利用
VPCオリジンについては、以下の公式ドキュメントを参照ください。
なお、ALBをInternet-facingのまま運用し、CloudFront-ALB間の通信をmTLSで制御する案も選択肢として共有されました。
一方で、Internet-facingを維持すべき要件は特になく、意図しないキャッシュバイパス経路のリスクも考慮し、ALBへのアクセスをCloudFront経由のみに限定できるVPCオリジン案を採用しました。
さらにコスト面も確認し、以下の点からメリットが大きいと判断しました。
- データ転送料は発生しない一方で、リクエストごとの課金は発生する
- CloudFrontとオリジン間の通信は無料
- ALBから直接インターネットへ通信する構成より、転送料金を抑えられる
以下の構成への変更をSREチームで決定しました。

これをAWSのアイコンで示すと以下のようになり、大幅にシンプルになります。

段階的なステップを踏みながら安全な移行を目指すため、SREチームだけでなく開発チームにも協力を仰ぎ、以下のように進めました。
- SREチーム
- ネットワーク構成説明資料を作成し、説明会の進行を担当
- ゼロダウンタイムで移行達成できるよう移行手順を整備
- 既存AWSリソースと共存できる移行方式を実装し、切り替えはRoute53のAレコードAliasとアセット取得先の変更に限定
- 開発チーム
- アプリケーション観点で影響範囲を確認し、変更が必要となる箇所ピックアップ
構成を大きく変更したことにより、構成の簡略化と、ブラウザから見たオリジンの単一化によるCORSエラーの抑制を達成できました。 さらに、401エラーの抑制のためのCognitoの迂回も必要がなくなりました。
一度リリースした構成を大きく変更するにあたり、SREチームとして開発チームとの合意形成と丁寧な進行を大切にしました。
背景と変更の意図を説明する場を設け、開発チームの理解と合意を得た上で協力して進めることで、ダウンタイムなくネットワーク構成の切り替えを完了でき、安定稼働させることができるようになりました。
さいごに
今回は、リリース直後のサービスにおいて、あえて早期に構成変更を実施した事例をご紹介しました。
「立ち上げ期はとにかく機能開発」に意識が向きがちです。
それでも、システムが小さく影響範囲が限定的な今だからこそ、比較的低いコストで前例踏襲の構成を見直し、サービス特性に合った設計へ最適化できました。
また、これらの構成変更を進めるにあたり、開発チームとのコミュニケーションを密に取り、納得感を持って一緒に進められたことが何よりの成功要因だと感じています。
今後も、開発者がより安心してスピーディーに価値を届けられる基盤づくりに、SREチーム一丸となって取り組んでいきます!
We are hiring!
アンドパッドでは、「幸せを築く人を、幸せに。」というミッションの実現のため、一緒に働く仲間を大募集しています。アンドパッドのSREチームにご興味がありましたら、以下のページからご応募ください。カジュアル面談も実施しています。