2016年11月21日

Keepalived 1.3.0 リリース!ヘルスチェック強化パッチが本家にマージされました

はてなブックマークに登録

@pandax381です。本日、Keepalived の新バージョン 1.3.0 がリリースされましたが、みなさんバージョンアップはお済みですか?

2016-11-20 | Release 1.3.0
New MAJOR release with stabilization fixes. Support to DBus. Conf extensions. Parser error log. Security extensions to run scripts more secure. Refer to ChangeLog for more infos.

(開発 ML へのアナウンスでは 2.0.0 が近々リリースされることも予告されています)

this quick email to announce new major keepalived release. We are planing with Quentin to push a new release soon as 2.0.0 release. This one fix and extend previous parts. It also came with a Security fix for those making extensive use of scripts.

今回リリースされた 1.3.0 での大きな変化は DBus 対応とスクリプト実行にセキュリティ機構が導入された点ですが、僕が投げたパッチもひっそりとマージされているのでアリバイ作り*1のために紹介します。

*1: インフラ部門で働くCプログラマの話 参照

ヘルスチェック強化パッチ

Keepalived にはリアルサーバを監視するヘルスチェック機能が備わっていますが、標準で対応しているプロトコルが「TCP」「HTTP(S)」「SMTP」のみと限定的です。HTTP と SMTP 以外は TCP の疎通確認以上のことをやりたければ、自前でヘルスチェック用のスクリプトを用意しなければなりません。

自前のヘルスチェックスクリプトを実行する MISC_CHECK は微妙な問題を抱えていたため、KLab では Keepalived のヘルスチェックを強化するパッチを開発・公開していました。(微妙な問題については後述します)

このパッチは、ヘルスチェックの対応プロトコルに「FTP」「DNS」「SSL」を追加するものです。記事の中では「まだDSASの本番環境には適用していません」となっていますが、その後まもなく本番環境にも導入され、現在も絶賛稼働中です。

独自パッチの宿命とも言えるのが、本家のバージョンアップへの追従です。公開しているパッチは 10 年も前に書いたものなので、そのままでは現在のバージョンに適用できませんが、社内では必要に迫られるたびにパッチを更新し続けています。それでも、新バージョンがリリースされて更新しようとした際にパッチ適用がネックになってしまうのはちょっとツライです。こうした独自パッチの宿命から解放されるには、独自パッチを卒業して本家にマージしてもらう他ありません。

結論から先に書くと、タイトルにもある通り独自パッチを卒業してめでたく本家にマージされ 1.3.0 から標準機能になりました。

これがヘルスチェックを強化するパッチのプルリクエストです。オリジナルのパッチは、FTP・DNS・SSL に対応させるものでしたが、現在 DSAS で使っているのは DNS だけということもあり、標準のヘルスチェック機能に DNS ヘルスチェック(DNS_CHECK)を追加する内容になっています。

DNS_CHECK の書式は以下の通りです。

    # one entry for each realserver
    real_server <IPADDR> <PORT>
    {
           # DNS healthchecker
           DNS_CHECK
           {
               # ======== generic connection options
               # Optional IP address to connect to.
               # The default is the realserver IP
               connect_ip <IP ADDRESS>
               # Optional port to connect to
               # The default is the realserver port
               connect_port <PORT>
               # Optional interface to use to
               # originate the connection
               bindto <IP ADDRESS>
               # Optional source port to
               # originate the connection from
               bind_port <PORT>
               # Optional connection timeout in seconds.
               # The default is 5 seconds
               connect_timeout <INTEGER>
               # Optional fwmark to mark all outgoing
               # checker packets with
               fwmark <INTEGER>

               # Number of times to retry a failed check
               # The default is 3 times.
               retry <INTEGER>
               # DNS query type
               #   A | NS | CNAME | SOA | MX | TXT | AAAA
               # The default is SOA
               type <STRING>
               # Domain name to use for the DNS query
               # The default is . (dot)
               name <STRING>
           }
    }

いろいろオプションがありますが、最低限「type」と「name」を適切に設定してあげれば動きます。

    realserver 192.0.2.100 53 {
           DNS_CHECK {
               type A
               name www.klab.com
           }
    }

ヘルスチェックの成功可否は、ANSWER SECTION に1件以上の回答がある応答を得られるかどうかで判断しています。したがって、レスポンスパケットを受け取っても ANSWER SECTION が空の場合にはそのパケットは無視します。実用性があるかどうかは別として、やろうと思えば DNS のレコードの登録状況によってヘルスチェックの結果を制御することも出来ます。

ついでにバグも退治しました

先に「自前のヘルスチェックスクリプトを実行する MISC_CHECK は微妙な問題を抱えていた」と書きましたが、これまでのバージョンの keepalived にはMISC_CHECK から実行されるヘルスチェック用のスクリプトが刺さるとプロセスが増殖してしまうというバグがありました。

例えば、以下のような設定で MISC_CHECK を実行すると簡単に再現できます。

MISC_CHECK {
    misc_path "/bin/sleep 3600"
    misc_timeout 10 
}

MISC_CHECK は、実行したスクリプトが misc_timeout を経過しても終了しない場合にはシグナルを送信して強制的に終了させる作りになっています。しかし、このシグナル送信の処理に問題があり、本来は misc_timeout が経過した後に強制終了させられるはずのプロセスが残り続け、順次新しいプロセスが生成されて結果的にプロセスが大量に増殖してしいます。

UID   PID  PPID  PGID   SID COMMAND
  0 41010     1 41010 41010 /sbin/keepalived
  0 41013 41010 41010 41010  \_ /sbin/keepalived
  0 41361 41013 41010 41010  |   \_ /sbin/keepalived
  0 41362 41361 41010 41010  |   |   \_ sh -c /bin/sleep 3600
  0 41363 41362 41010 41010  |   |       \_ /bin/sleep 3600
  0 41364 41013 41010 41010  |   \_ /sbin/keepalived
  0 41365 41364 41010 41010  |   |   \_ sh -c /bin/sleep 3600
  0 41366 41365 41010 41010  |   |       \_ /bin/sleep 3600
  0 41367 41013 41010 41010  |   \_ /sbin/keepalived
  0 41368 41367 41010 41010  |       \_ sh -c /bin/sleep 3600
  0 41369 41368 41010 41010  |           \_ /bin/sleep 3600
  0 41014 41010 41010 41010  \_ /sbin/keepalived
  0 41019     1 41010 41010 sh -c /bin/sleep 3600
  0 41020 41019 41010 41010  \_ /bin/sleep 3600
  0 41025     1 41010 41010 sh -c /bin/sleep 3600
  0 41026 41025 41010 41010  \_ /bin/sleep 3600
  0 41031     1 41010 41010 sh -c /bin/sleep 3600
  0 41032 41031 41010 41010  \_ /bin/sleep 3600

MISC_CHECK はヘルスチェック用のスクリプトを実行するために fork(2) してから system(3) を実行しているため、シグナルを送信するプロセスから見て終了させたいプロセスは「ひ孫」の関係にあります。もともとのコードでは、子プロセスに対してのみシグナルを送信していた*2ため、子プロセスのみが終了して孫プロセスとひ孫プロセスが残り続けてしまうというバグでした。

上記はこのバグを修正するためのプルリクエストです。MISC_CHECK が fork(2) した段階で、setpgid(2) を実行してプロセスグループを分離するようにしました。signal(2) はプロセスグループを指定することで、そのプロセスグループに属する全てのプロセスに対してシグナルを送ることができるので、子プロセスからひ孫プロセスまで全てのプロセスにシグナルを送信するように修正しました。

この修正もマージされていますので、1.3.0 からは安心して MISC_CHECK を利用できるようになりました。

*2: 実際には更にバグがあり、肝心の子プロセスも意図せずにシグナル(SIGTERM)を無視してしまう状態になっていて、最終手段として実装されていた強制終了のシグナル(SIGKILL)を受けて殺されているという微妙な状態でしたが、これも修正しました

苦労話など

既存のパッチを本家に取り込んでもらった風に書きましたが、実際にはスクラッチで書き直したので既存のコードはほとんど残っていません。

これには、keepalived が備えているヘルスチェックのフレームワークが TCP を前提としていて UDP のことを考慮しておらず、オリジナルのパッチではソケット周りの処理を全て自前で書いていたという理由があります。そのため、標準のヘルスチェックのコードと比べるとお作法的にお行儀が悪そうな状態で、そのままプルリクを投げたら嫌がられるかもという状態でした。とは言え、フレームワーク側が UDP を考慮していないため他にうまくやる方法もなく、あれこれ悩んだ末に keepalived 本体に手をつけてフレームワークの作りを修正することにしました。(ヘルスチェックの各機能はモジュールのような位置付けで機能追加であれば気軽にできるのですが、本体側の修正となると心理的なハードルがだいぶ上がります)

このコミットで、TCP しか考慮していなかったフレームワークを UDP にも対応させました。フレームワークを利用している既存のコードを変更しないように、インライン関数でラッパーを作成して互換性を維持するようにしています。この修正によって UDP ベースのヘルスチェッカーが作りやすくなっているので、もしかしたら対応プロトコルが増えるかもしれません。

あと、一番苦労したのはコード書くよりもプルリクエストのメッセージを書くことでした。なにより僕が英語が絶望的に出来ないというのもありますが、「それ MISC_CHECK でやって」と言われてしまうと終了してしまうので、標準のヘルスチェッカーに組み込んでもらいたいという熱い思いを伝えるのに注力しました。精度が上がる前の Google 翻訳に泣かされつつも、結果的に好意的に受け入れてもらえて本当に良かったです。

恐らく、これが自分のコードが取り込まれた中で一番有名で大きなプロダクトだと思うので、素直に嬉しいです。需要があるかどうかわかりませんが、DNSヘルスチェック機能を是非使ってみてください!

pandax381 at 20:29│Comments(0)TrackBack(0)

トラックバックURL

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔   
 
 
 
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント