こんにちは!SRE部のcw-ozakiです。
creators-note.chatwork.com creators-note.chatwork.com creators-note.chatwork.com
今回はその3の続きで、Kubernetesクラスタの更新戦略に合わせてアプリケーションをどのようにして、クラスタを移行していくのか戦術を考えていきます。
前回の簡単なおさらい
ChatworkでのKubernetesクラスタは下記の方針で運用しています。
- マルチテナント・シングルクラスタ
- Blue/Greenでのクラスタを新規に立てる形でのアップグレード
- だいたい3ヶ月に1度の更新
この方針はアプリケーションを構築する際の制約になります。
ステートレスアプリケーションの場合はクライアントからの通信をいかにして新旧切り替えるのかというところだけになりますが、ステートフルアプリケーションの場合はそれに合わせてステートの移行を考える必要があったり、全体で1つしか動作しないといった制約がある場合はそこを解決する方法を考えなければいけません。 もし、これを解決できないならKubernetesに載せることを断念してEC2などの別の方式を採用してください。
また、3ヶ月に1度に更新するためできる限り、この更新作業の内容を減らさなければいけません。 これが例えば移行の準備と適用に数週間かかります!となると、移行時のメンバーの作業状況によってはそんな重いタスクは持てないということで、アップデートができなくなる可能性があります、
現在のChatworkの構成
PHPのレジェントシステムの一つのWeb部分(みなさんがお使いになられているチャット画面)は幸いなことにステートレスなWebアプリケーションになっています。
これは今時のCloud NativeなWebアプリケーションであればよくある構成ですね。 もし、まだこういった形になっていないステートレスなWebアプリケーションを利用している場合は、まずはステートを除去していくことから始めると良いと思います。
Web部分はステートレスアプリケーションのため、新しいクラスタを移行するにはユーザーからのリクエストを古いクラスタから新しいクラスタにルーティングするだけです。 リクエストをルーティングできる箇所としては
- Route53 -> CloudFront
- CloudFront -> ALB
- ALB Target Group
- Kubernetes
の4箇所になります。 それぞれの箇所でリクエストをルーティングしていく方法を解説していきます。
1. Route53 -> CloudFront
ユーザーが一番初めにアクセスするCloudFrontにRoute53で加重レコードを使ってリクエストをルーティングすることはできません。 これはCloudFrontの制約でドメインに対して一つしか作成するこしかできないからです。
そのため、ユーザーからリクエストをルーティングするにはこれより後ろのリソースで行う必要があります。
2. CloudFront -> ALB
CloudFrontのoriginを切り替えることで、複数のALBへのリクエストを切り替えられます。 ただし、これは0/1で新旧のクラスタどちらかにしか切り替えれず、かつCloudFrontの更新操作になるためそこそこ時間がかかります。
そこでより柔軟に、かつ素早く切り替えられるために、ALBのDNSを解決するためのRoute53の加重レコードを作成します。 その加重レコードをCloudFrontのoriginに指定することで、加重レコードの加重を変更するだけでCloudFrontのoriginの通信先を制御できます。
この方法はCloudFrontのDNSの名前解決に依存するため、ユーザーのDNS Cacheなどを考慮する必要がなくかなり素早く加重を切り替えられるメリットがあります。 しかし、デメリットとしてはCloudFrontのDNSの名前解決に依存するせいか、想定した量を超えるリクエストが割り当てられます。(加重が10%に対して50%ぐらいのリクエストがきたり)
これは恐らくCloudFront側の名前解決が一定間隔で行われ、その間は解決されたALBに対してリクエストが送り続けられるような動作しているのではないかと思われます。そのため、もしこの方法を採用する場合は対象を自動でスケールさせるのではなく、事前にある程度サービスを維持できる数の台数を確保する必要があります。
3. ALB Target Group
この機能は選定当時にリリースされていませんでしたが、今再選定するなら最有力になります。 ALBに複数のTarget Groupと、それぞれの加重を設定することでリクエストをルーティングできます。
ここは検証できていないため詳しくは語れませんが、DNSベースのルーティングよりは素早く、かつ加重通りにルーティングしてくれるのではと期待しています。
4. Kubernetes
KubernetesのレイヤーではIstioでマルチクラスタネットワークを組んだり、NGINX Ingressを使ってupstreamを新しいクラスタに切り替えるといったことも可能です。 ただ、どうしても大掛かりになりやすく、かつAZを跨いだ通信が余計に入りやすいことから2や3と比べるとデメリットが目立ちます。
とはいえ、マルチクラスタネットワーキングはCW社内でもたまに話題になるぐらいには欲しいケースがあるので、弊社のcw-sakamotoあたりがAWS AppMesh導入しました的な話を書いてくれると信じています。
ALBとRoute53の加重レコードをどのように作るのか
というわけで実質的に 2. CloudFront -> ALBしか選択肢がなかった形になります。 このとき新しいクラスタを作る度にALBとRote53の加重レコードを作る必要がありますが、正直3ヶ月に1度にこの作業をわざわざやりたくありません。
そこでえ役立つのがaws-load-balancer-controller(旧alb-ingress-controller)とexternal-dnsです。
これはKubernetesでannotationsを付与することで、ALBとRoute53の加重レコードを自動で生成してくれる便利なツールになります。
annotations: kubernetes.io/ingress.class: alb external-dns.alpha.kubernetes.io/hostname: [CloudFrontがOriginの解決に使うDNS名] external-dns.alpha.kubernetes.io/set-identifier: new-cluster-name external-dns.alpha.kubernetes.io/aws-weight: "0"
このように設定することで、新しいKubernetesクラスタを立ち上げて、アプリケーションのデプロイすれば、あとはRoute53の加重レコードを変更するかannotationsのexternal-dns.alpha.kubernetes.io/aws-weightを変えるだけでアプリケーションの移行をできます。(ここはexternal-dnsのpolicy次第で方法が変わります)
1つ注意点としてはアプリケーションのデプロイで加重レコードが作成されるので必ずexternal-dns.alpha.kubernetes.io/aws-weightは0にしてください。 例えばここが100で、古いクラスタへの加重レコードも100の場合はいきなり新しいクラスタに50%のリクエストがルーティングされます。不意にリクエストがルーテイングされないように新しいクラスタにデプロイするさいには0にすうるよに気をつけてください。
余談ですが、aws-load-balancer-controllerがTargetGroupのみ作成にも対応したので、3. ALB Target Groupでルーティングするという選択肢はありだと思います。 こちらの方法ならexternal-dnsは不要になりますし、DNSベースのルーティングより早く切り替えれそうという期待があるので、そのうち検証していけそうなら切り替えます。
まとめ
- CWはステートレスなWebアプリケーションだよ!
- CloudFrontと複数のALBを使ってRoute53の加重レコードで新旧クラスタへのリクエストのルーティングを制御しているよ!!
- aws-load-balancer-controllerとexternal-dnsは神
という訳で、今回はChatworkでのKubernetesクラスタの更新戦略に合わせたアプリケーションの移行戦術の話でした。 次回はPHPアプリケーションでのコンテナ設計の話になります。が、PHP Conference 2020 Re:bornの内容と被るのでこちらはPHP Conferenceが終わってからの投稿になります。 もし、もっと早く知りたい!!という方はPHP Conference 2020をご視聴ください。
www.wantedly.com ChatworkではSREメンバーを絶賛募集中です! このKubernetes化の話やChatworkの各種インフラや体制など興味があればカジュアルに話を聞きにきちゃってください。