プロセス自体を再起動せずに、更新されたresolv.confの新しいDNSサーバーアドレスをプロセスに強制的に使用させる方法

私はUnixシステム管理者として働いています。 プログラマーからのチケットがサービスメンテナンス部門に届き 、ヘッダーのアプリケーションサーバーログからの抜粋:「 pgbouncerはサーバーに接続できません 」。 pgbouncersのログを確認したところ、DNSにアクセスする際に定期的に検索エラーが発生することがわかりました。 これは、DNSサーバーの動作によるものではなく 、UDPプロトコル自体の信頼性が低いことが原因であることが判明しました。さまざまな理由でパケット損失が発生することがあります。
画像
その結果、pgbouncersを使用して各サーバーにキャッシュBINDをインストールすることが決定されました。 そして、ここで興味深い問題が発生しました:pgbouncer HUPシグナルの/etc/resolv.confファイルを再読み取りせず 、古いDNSサーバーへのアクセスを継続しました。 ただし、バウンサーをカテゴリ別に再起動することはできません。データベースとのセッションの中断に非常に敏感な問題のあるプロジェクトがあります。

この記事では、pgbouncerまたはライブラリコールgetaddrinfo()を使用する他のプログラムがresolv.confを再読み込みし、新しいDNSサーバーの使用をクライアントに完全に無痛で(ダウンタイムなしに)開始する方法を説明します。

さあ始めましょう


私の場合、pgbouncersはバージョン1.5.2でFreeBSDlibevent-1.4を使用してビルドされたとすぐに言わなければなりません。

pgbouncerのソースを見ると、 dnslookup.cファイルに次のコメントがあります。
/* * Available backends: * * udns - libudns * getaddrinfo_a - glibc only * libevent1 - returns TTL, ignores hosts file. * libevent2 - does not return TTL, uses hosts file. */ 

これは、pgbouncerがlibevent1でビルドされた場合、標準のlibcライブラリの関数getaddrinfo_a()が非同期アドレス解決に使用されることを意味します。
経験的に、非同期getaddrinfo_a()はlibcの通常のgetaddrinfo()関数を使用することがわかりました。 最後の関数にブレークポイントを設定します。 libcはデバッグシンボルなしで構築されているにもかかわらず、gdbはgetaddrinfo関数を知っているため、この事実により、デバッグシンボルを使用してpgbouncerを構築する必要がなくなります。

pgbouncerの設定に、存在しないドメインを参照する存在しないデータベースを追加します(テストに役立ちます):
 test = host=test.xaxa.blabla12313212.su user=pgsql dbname=template1 pool_size=10 

別のウィンドウで、pgbouncerを実行します。
 su -m pgbouncer -c '/usr/local/bin/pgbouncer /usr/local/etc/pgbouncer.ini' 

別のウィンドウで、gdbデバッガーを使用してプロセスに接続します。
 gdb /usr/local/bin/pgbouncer `cat /var/run/pgbouncer/pgbouncer.pid` 

ブレークポイントを設定し、プロセスをさらに実行します。
 (gdb) b getaddrinfo Breakpoint 1 at 0x800f862a4 (gdb) c Continuing. 

別のウィンドウで、解決しようとする試みを開始するために、存在しないドメインを使用してデータベースに接続しようとします。
 su -m pgbouncer -c 'export PGPASSWORD="123" && /usr/local/bin/psql -Utest test -h10.9.9.16 -p6000'; 

gdbで、私たちは雄牛の目に当たったことがわかります。
 Breakpoint 1, 0x0000000800f862a4 in getaddrinfo () from /lib/libc.so.7 (gdb) 


getaddrinfo()はどのように機能しますか?

マニュアルと検索エンジンを使用して、この関数は最初の呼び出しでresolv.confファイルを読み取り、メモリ内のデータの束で構造を初期化することがわかりました。その中にDNSサーバーのリストがあります。 次に、関数はリストの最初のアドレスを使用してアドレスを解決しようとします。 DNSサーバーが応答しない場合、関数はリストから次のDNSサーバーをアクティブにします。 そして円で。 この関数はresolv.confを一だけ読み取ります。

まず、 ネットワークオーダーまたはホストオーダーの形式で4バイトのDNSサーバーアドレスを見つけて、pgbouncerの仮想メモリにパッチを当てたいと思いました。 このために、Cメモリプログラムも作成されました。これにより、プロセスメモリをダンプし、特定のバイトオーダーを検索できました。 しかし、判明したように、この形式のメモリでこれらのアドレス見つけることは不可能です。 getaddinfo()のソースコードを理解することは、私の力を超えていることがわかりました。多くのテキストとあらゆる種類のgotoが、ほとんど私の心を壊しました。 さらに、私はプログラマーではなく、Cはほんの1か月前に学び始めました。

ところで、ptraceとprocfsを使用する私のプログラムは、libevent2から構築されたpgbouncerに適しています。DNSサーバーのIPアドレスは、正確に4バイトの形式で格納されます。 しかし、この経験の説明はこの記事の範囲を超えています。

どうする?

幸いなことに、検索エンジンの助けを借りて、標準ライブラリにres_init()保存関数が見つかりました。
res_init()ルーチンは、構成ファイルを読み取ります(存在する場合。参照
リゾルバ(5))デフォルトのドメイン名、検索リスト、インターネットを取得する
ローカルネームサーバーのアドレス

getaddrinfo()が初めて呼び出され、必要な構造を初期化するときに呼び出されるのはこの関数です!
関数を繰り返し呼び出すと、構造が再初期化され、resolv.confが再度読み込まれます。

実際に確認しましょう

トレーサーを「凍結された」pgbouncerに接続し、トレースダンプファイルのgrepを開始します。
 ktrace -f out.ktrace -p `cat /var/run/pgbouncer/pgbouncer.pid` kdump -l -f out.ktrace | grep resolv 

gdbのあるウィンドウで、res_init()関数を呼び出します。
 (gdb) call res_init() Breakpoint 1, 0x0000000800f862a4 in getaddrinfo () from /lib/libc.so.7 

トレース結果の出力があるウィンドウでは、次のように表示されます。
 37933 pgbouncer NAMI "/etc/resolv.conf" 


達成した目標

サーバーを削除したり、アクティブなtcp状態を壊したりせずに、resolv.confを再読み込みするプロセスを取得することができました。 凍結時に、リクエストも失われません。

ローカルキャッシュDNSをすぐに使用する場合は、次の手順を実行する必要があります。

  1. BINDフォワーダーで、以前にresolv.confで使用されておらず使用されない新しい(他の)稼働DNSサーバーにサーバーを変更してから、 rndc reloadを実行します。
  2. ローカルファイアウォールによる古いDNSサーバーへのアクセスの禁止(127.0.0.1を除く)
  3. 存在しないデータベースサーバーへのpgbouncer'a呼び出しを開始します。
     su -m pgbouncer -c 'export PGPASSWORD="123" && /usr/local/bin/psql -Utest test -h127.0.0.1 -p6000'; 

  4. pgbouncerがポート53で127.0.0.1にアクセスしていることをtcpdumpで確認します。
     tcpdump -n -i lo0 port 53 | grep xaxa "> 127.0.0.1.53" - 

    xaxapgbouncer.confのサーバー名の一部です
  5. ファイアウォールで古いDNSのマスクを解除する
  6. BINDフォワーダーの設定を元の状態にリセットする

そして最後

私の経験を繰り返したい場合は、テストベンチでトレーニングすることを強くお勧めします。
バッチモードでgdbのコマンドを「弾丸」化する場合、最初にgdbに文字を読み込む時間を与えてから、関数を呼び出す必要があることに注意してください。 mi作業中のpgbouncers。
gdbのバッチモードは次のように実行されます。
 printf 'shell sleep 3\ncall res_init()\ndetach\nquit\n' > /tmp/pb.gdb && gdb -batch -x /tmp/pb.gdb /usr/local/bin/pgbouncer `cat /var/run/pgbouncer/test.pid` 


私の経験が、誰かがオペレーティングシステムでプロセスがどのように機能するかをよりよく理解するのに役立つことを願っています。

Source: https://habr.com/ru/post/J209356/


All Articles