読者です 読者をやめる 読者になる 読者になる

激闘90日!RDSmultiAZ化プロジェクトの軌跡

タイトルの日数は適当です。

前回の記事のブコメに「インフラパズル」に言及したものがあって結構嬉しいので、先日やっと終わった一連のインフラ改善の話を紹介しようと思います。

ことのはじまり

そもそもRDSのSingleAZ->MultiAZってボタン一発無停止適用じゃねえのという話ですが、まあ色々経緯があります。

1. 半年前ぐらい

(僕が現職に入社する前の話ですが)RDSのマスターノードに障害があり、replicaが昇格することになりました。

master replica
before db-master db-repl
after db-repl db-repl2

db-masterは破棄され、db-repl2が新設されて体制は維持されました。 しかし障害復旧を優先したため、db-replはSingleAZのままな上、"repl"なのにmasterというややこしい状態で、このまま恒久運用はできません。

2. 同時期

僕が入社した時点でdb-replを修繕するissueは作られていましたが、rename + multiAZ化を無停止でやるというのは字面より複雑な作業で

  • rename時には再起動が発生する
    • この再起動でmultiAZのfailoverが起こるかは確証なし
  • 事前にhostnameではなくipで参照するようにアプリを設定しておくことで、rename時の接続断時間を最小限にできる
  • multiAZが適用されるとfailoverによってipが変わってしまう可能性があるため、上記の方法を使うならrename > multiAZという順で作業する必要がある

このようなことがまとめられていました。決済サービスの性質上、たとえ計画であっても停止は極力避けなくてはならないため、いちいち話がややこしくなります。

renameの停止時間は相当短い(実験した限りでは2秒程度)ですが0ではなく、ユーザーに踏まれてしまうと500が出てしまうため、いい方法が思いつくまで塩漬けということになりました。

他の課題たち

RDSの問題はそれとしてほかにも課題はたくさんありました。

SPOFをなくす

幾つかSPOFになっている場所が残っているので、HAProxyの導入によってそれをなくそうというものです。具体的には以下のミドルウェアが対象でした。

  • LDAP
    • それ自体はmirrorが作られているもののクライアントから片方しか参照できていない
    • pam_ldap.confやldap.confの uri に2サーバーを列挙してもいいのだが、それだと先に参照される方のサーバーの障害時に毎回タイムアウト待ちが発生し続けてしまう
  • proxy
    • インターネットに出るためのhttpプロキシが1台しか構成されておらず、2台目を構築して多重化する必要がある
    • http_proxy環境変数などでは多重化やフェイルオーバーの仕組みは実現できない

どちらもHAProxyの導入で解決できますが、HAProxyをどこに置くかが問題です。独立したHAProxyサーバーを作ったとしても、そこがSPOFになってはあまり意味がありません。

f:id:feiz:20170107181725p:plain

pgbouncerサーバーを作る

pgbouncerが各アプリケーションサーバーのローカルにそれぞれインストールされていて非効率なので、専用のサーバーを立ててそこに接続させるようにするというものです。

導入当時はpgbouncerの安定性が評価できていなかったのと、(障害にケツを蹴られての)突貫での導入だったため、とりあえずの対策 + 安定性評価期間としてこのような構成になったと古文書に書いていました。

古文書によれば1台のサーバーで運用しようということでしたが、pgbouncerの安定性に問題はなくともインスタンスがふっとんだときに困るという話はあり、これも半塩漬け状態でした。

f:id:feiz:20170107183439p:plain

つながる点と点

これらは独立した問題から生じた課題で、当初は個別に対処を考えていましたが、ある日のissue棚卸し中に出た

「HAProxyを各サーバーに置けばいいんじゃね?」

という一言から一気に解決に向かいます。

  1. HAProxyを各サーバーに置けば、"HAProxyサーバーの障害"はなくなる
    • SPOF問題解決
  2. 今後多重化が必要なミドルウェアが増えたとしても、各サーバーのHAProxyに適切な設定をばら撒けばいかようにも対応できる
    • スゴイ
  3. pgbouncerも2台立ててHAProxyでroundrobinすればいいのでは?
    • pgbouncerサーバーSPOF問題も同時に解決
  4. pgbouncerのPAUSE/RESUME機能を使えばクエリをせき止めることができる
    • 現行でも可能だが、全アプリサーバーのpgbouncerにPAUSEを打たなくてはならず微妙だった
    • 2台なら手でもやれる
  5. PAUSEしてる間にRDSのリネームを行えば、再起動時にクライアントをぶった切ることはない
    • 完全無停止RDSリネームの目処がたった!
  6. RDSのリネームが終われば、あとはmultiAZ化するだけ!

f:id:feiz:20170107183436p:plain

こうして、3つの大きな問題を一挙に解決するプロジェクトが始まりました。

それから

ここからの実作業の話は、ansibleをごちゃごちゃいじった ぐらいしか話すことがないので割愛します。

改修とリリースは足掛け2ヶ月ぐらいかけて少しずつ行っていきました。もちろん全部無停止です。

  1. haproxyの配備(参照なし)
  2. アプリケーションがhaproxyを参照するように変更
  3. pgbouncerサーバーの作成
  4. haproxyにpgbouncerプロキシ設定を追加配備
  5. 外部サービス向けでないサーバーから少しずつ向けて様子見しながら段階的にリリース
  6. pgbouncerからRDSへの参照をipに変更
  7. PAUSE/RESUMEを使ってクエリをせき止めた状態でRDSをrename
  8. RDSをmultiAZ化する

最後のmultiAZ化をapplyしたときの様子が以下になります。

f:id:feiz:20170107174440p:plain

multiAZをオンにするだけでこんなに感動できる日が来るとは思いませんでした。

なぜ無停止にこだわるのか

ぶっちゃけ5分停止すればそれで済む修正をなぜここまでして無停止にこだわるか不思議に思う人もいるかもしれません。

まず、サービスを停止する難易度はサービスによって千差万別です。システム自体を停止することは雑でよければなんとでもなりますが、商用サービスである以上は利用者とか関係者との調整が絡んできます。

僕が見てきただけでも、Skypeで「ちょっと瞬断します」と5分前に連絡するだけで済むものから、親サービスの月イチメンテナンス日以外は基本的に不可能なものまで様々でした。決済はそれらよりさらに面倒なので、全力で回避する必要があります。

もう一つ大きな理由として、メンテナンス停止を伴うリリースには必然的に制限時間が設定されてしまうというものあります。

時間に追われて焦って作業すると、人間はすぐミスをします。倍の時間がかかるとしても無停止手順を組むことで、想定外のことが起こっても落ち着いて対処できます。

おわり

自分で考えたアーキテクチャがきれいにハマる瞬間は楽しいものです。単一障害点が減れば結果的に自分やチームを助けることにもなります。

去年末にかけては他にも幾つか大きなアーキテクチャ変更をやっているので、その話も気が向けばやろうと思います。