Host-based routingしたALBにRoute53 Health Checkをかける

タイトルの件について結論から言っておくと,スマートな解決策は今のところない. 一応回避することはなんとなくできるのだが,全然スマートではない.今後のアップデートに期待したい.

ALBのHost-based routing

AWS ALBには,host-based routingというものがある.使い方についてはこの辺を見てもらうとして.

dev.classmethod.jp

つまりは,一つのLoadBalancerでありならが,Host Headerによって複数のTarget Groupにリクエストを分けることができる. これによりコストカットができたり,サブドメインのサービス運用が楽になったり,嬉しいことは色々あると思う.

Route53 Health CheckとDNS Failover

Route53にはHealth Checkという機能がある.LoadBalancerにももちろんHealth Checkはあるのだが,これとは別にRoute53も似たような機能を提供している. 大きな違いは,Health Checkの主体の違いだが,詳しくはこの辺を見て欲しい.

dev.classmethod.jp

で,Route53 Health Checkを使いたい理由の80%くらいはDNS Failoverだと思っているんだけど,Route53でDNS Failoverという機能を使うときに,このRoute53 Health Checkが必要になる. Route53のDNS Failoverは

  • サーバAで通常サービスを運用しているときに
  • もしサーバAが何らかの原因によりダウンしてしまった場合
  • Route53レイヤーでサーバBにFailoverさせる

という機能を提供している. だいたいこれを使ってSorryページやメンテナンス画面みたいなものを表示したり,どこかにリダイレクトさせたりするのが一般的な使い方だ.

大抵の場合は,

  • 通常はRoute53をELBに向けておいて,サービスを運用し
  • 障害時にはCloudFrontで用意しているメンテナンス画面にFailoverさせる

といった使い方をする.

このとき,ELBやサーバが生きているかどうか を判定するために,Route53 Health Checkを利用する.というか,Route53レイヤーでFailoverさせるには,これを使う以外に方法がない.

ALBとRoute53 Health Checkの問題

Route53 Health Checkは,エンドポイントの指定方法として

  • IPでの指定
  • ホスト名での指定

の2パターンを提供している.

ELBをチェック対象としたい場合は,ELBのIPはAWS側でたまに変更されてしまうため,ホスト名で指定するしかない.そういうときのために,ELBはCNAMEを提供している.

ALBであっても,同様にCNAMEをRoute53 Health Checkに指定することになるだろう.

このときに,ALBを,Path-based routingしている場合には,何ら困ることがない.Route53 Health Check側で,チェックしたいサービスのパスを指定できるからだ.

しかし,Host-based routingの場合,ALBのCNAMEとHost-based routingで指定しているホスト名が違うため,チェックしたいサービスにリクエストを振り分けることができない

一つのALBで,

を運用したとしよう.もちろん,Host-based routingのルールは,hoge.example.comとfuga.example.comで作る.

しかし,ALBのCNAMEはAWS側で決められ,hogehoge.ap-northeast-1.elb.amazonaws.com というような形式をしており,このホストに合致するルールをHost-based routingに書くことは,まずないだろう(まず,そのルールを作ったとしても,どちらのTarget Groupにルーティングすればいいんだ?)

そして,Route53 Health Checkのホスト名指定は,チェックしたいエンドポイントのホスト名のみ指定可能で,Host Headerを指定することができない(ちなみにIP指定であればHost Headerを指定できる).

このため,Host-based routingのALBをRoute53 Health Checkのターゲットとすると,いつになってもHealthyになることがない.

対応策

クリティカルな方法は調べた限り存在しない.小手先でなんとかするしかない.

Host-based routingのデフォルトルールを使う

ALBのルーティングにはデフォルトルールがある.どのホストにも合致しない場合に,どこにルーティングするかということを指定できる.大抵の場合は404を使ったりするのだが…….

ここにチェックしたい対象のサービスを指定することで,Route53 Health Checkからのリクエストがルーティングされる.

ただし,デフォルトルールは当然のことながら,一つのTarget Groupしか指定できない. 当然,一つのALBで複数ホストを運用している場合(そうでなければHost-based routingなんてしないだろうが),どれか一つしかデフォルトルールに指定できないため,これらの複数ホストについてRoute53 Health Checkを指定することは不可能となる.

ALB側でSource IPを使ってルールを作る

ALBのルーティングルールには,Source IPというタイプも存在する.

そして,Route53 Health Checkは,ヘルスチェックのリクエストを投げてくるIPが,ある程度決まっている.

dev.classmethod.jp

なので,Source IPがRoute53 Health Checkだった場合だけ,チェックしたいサービスにルーティングする,というようなルールを追加することで,Health Checkを通すことができる.

しかし,やはりこの方法でも複数ホストを運用している場合,どれか一つしかRoute53 Health Checkを使えないということになる.どのホストのHealth Checkが,どのIPから飛んでくるかは全然わからないからね.

ここだけPath-based routingを使う

例えば一つのALBで運用しているのが,

だったとしよう.これらについてALBはHost-based routingさせている.

そして,Route53 Health Check用に,新たにPath-based routingのルールを追加する.

  • /hoge/* をhoge-target-groupに
  • /fuga/* をfuga-target-gorupに

振り分けるようにする.これらのルールは,Host-based routingのルールより,優先度が低くなるようにしておく(事故らないように).ついでに,Path-based routingに合わせて,Source IPのルールもANDで付与できるので,Route53 Health CheckのIPレンジを指定しておく.

こうすると,ホスト名が hogehoge.ap-northeast-1.elb.amazonaws.com みたいな,ALBのCNAMEだったとしても,パスが /hoge/*であればPath-basedのルールに引っかかり,hoge-target-groupにルーティングされる.

ただし, この場合,hoge-target-group内のアプリケーションは,/hoge/api/health_check みたいなパスをさばける必要がある. このためには,選択肢は2つくらいしかなくて,

  • アプリケーション自身が /hoge/api/health_check を捌けるようにしておく
  • アプリケーションの前段にnginxを用意し,rewriteのルールを書いておく

ALBでパスのrewriteができれば最高なのだが,現状これはできない. 俺は,このためだけにこんなパスを用意したくはない,ヘルスチェックのリクエストは,/api/health_check に来て欲しい,と思っているのでnginxでrewriteを書く派だ.

なので,

location /hoge/api/health_check {
    proxy_http_version 1.1;
    proxy_set_header Host $http_host;
    proxy_redirect off;

    rewrite /hoge/(.*) /$1 break;
    proxy_pass http://backend:8080;
}

みたいなルールを書いておく.

まとめ

Route53 Health Checkでできたら嬉しいこととしては,Host名指定のHealth CheckであってもHost Headerを別途指定できたら最高ということ. あとは,そもそもELBのHealth Checkが落ちた時点で,DNS Failoverのトリガーを発動できたら,もっとスマートで良い(が,ちょっとスコープが違うかもしれない).

ALBでできたら嬉しいこととしては,パスのrewriteができたら良い.ただし,こちらは,今回の問題解決手段としてはクリティカルではない割に,他の用途への影響がでかいので,そんなに嬉しいわけではない.

現状だと,Health Check用にPath-basedとIP-basedを組み合わせたルールを用意し,付与したPathをもとに戻すためのnginxが必要となってしまう.チェック対象が一つで良いなら,SourceIP指定を使ったらいいかも.