タイトルの件について結論から言っておくと,スマートな解決策は今のところない. 一応回避することはなんとなくできるのだが,全然スマートではない.今後のアップデートに期待したい.
ALBのHost-based routing
AWS ALBには,host-based routingというものがある.使い方についてはこの辺を見てもらうとして.
つまりは,一つのLoadBalancerでありならが,Host Headerによって複数のTarget Groupにリクエストを分けることができる. これによりコストカットができたり,サブドメインのサービス運用が楽になったり,嬉しいことは色々あると思う.
Route53 Health CheckとDNS Failover
Route53にはHealth Checkという機能がある.LoadBalancerにももちろんHealth Checkはあるのだが,これとは別にRoute53も似たような機能を提供している. 大きな違いは,Health Checkの主体の違いだが,詳しくはこの辺を見て欲しい.
で,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で,
- hoge.example.com -> hoge-target-group
- fuga.examlpe.com -> fuga-target-group
を運用したとしよう.もちろん,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が,ある程度決まっている.
なので,Source IPがRoute53 Health Checkだった場合だけ,チェックしたいサービスにルーティングする,というようなルールを追加することで,Health Checkを通すことができる.
しかし,やはりこの方法でも複数ホストを運用している場合,どれか一つしかRoute53 Health Checkを使えないということになる.どのホストのHealth Checkが,どのIPから飛んでくるかは全然わからないからね.
ここだけPath-based routingを使う
例えば一つのALBで運用しているのが,
- hoge.example.com
- fuga.example.com
だったとしよう.これらについてALBはHost-based routingさせている.
そして,Route53 Health Check用に,新たにPath-based routingのルールを追加する.
振り分けるようにする.これらのルールは,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指定を使ったらいいかも.