#author("2018-03-12T00:04:54+09:00","default:nowsky","nowsky") *gdnsd [#p9c99800] オープンソースのグローバルロードバランサー(GSLB) ソースコード量が少なく、高速動作と脆弱性の発生しにくさが特長となっている。 ただし、ソースコードを減らす為にプログラムのロギング機能が省略されていたり、 細かいヘルスチェックはスクリプトを別途作成する必要がある為、 フル機能を使いこなすには知識・技術が必要なソフトウェアである。 gdnsdは通信事業者レベルでの採用実績がある。 また、ソースコードが少ない割には整理されている為、機能追加も比較的楽に出来る。 ~ *参考サイト [#h651c013] -[[gdnsd:+http://gdnsd.org/]] -[[GitHub gdnsd:+https://github.com/gdnsd]] -[[gdnsdでかんたんGSLB:+https://qiita.com/jh1vxw/items/7ce5d14e9f964f001257]] ~ * 構築環境 [#f4cf0e44] -[[配布サイト:+https://github.com/gdnsd/gdnsd/releases/]] -[[公式ドキュメント:+https://github.com/gdnsd/gdnsd/wiki]] #block |LEFT:80|LEFT:180|LEFT:180|c |CENTER:~Parameter|CENTER:~DNS-A|CENTER:~DNS-B|h |&color(#2020ff){OS};|openSUSE Leap 42.3|openSUSE Leap 42.3| |&color(#2020ff){GIP};|1.1.0.1|1.1.0.2| |&color(#2020ff){IP};|10.0.0.1|10.0.0.2| |&color(#2020ff){A-RR};|ns1.gslb.test.org|ns2.gslb.test.org| |&color(#2020ff){USER};|>|gdnsd| |&color(#2020ff){VERSION};|>|v2.4.0| |&color(#2020ff){PREFIX};|>|/usr/lcoal/gdnsd-2.4.0| |&color(#2020ff){CONFIG};|>|/usr/lcoal/gdnsd/etc/gdnsd/config| |&color(#2020ff){ZONE};|>|/usr/lcoal/gdnsd/etc/gdnsd/zones/gslb.test.org| |LEFT:80|LEFT:180|LEFT:180|c |CENTER:~Parameter|CENTER:~SERVER-A|CENTER:~SERVER-B|h |&color(#2020ff){IP};|192.168.0.1|192.168.0.2| |&color(#2020ff){A-RR};|sv1.gslb.test.org|sv2.gslb.test.org| #block(next) #ref(Linux/Source/gdnsd/wiki_gdnsd_01.png,45%,left,nowrap) #block(end) ~ * インストール [#g603a033] -&size(16){&font(b){1. インストール};}; gdnsdの最新機能を利用する為、今回はソースからビルドする。 ソースビルド時でもバージョン管理が行える様に、 prefix指定を変更した上でシンボリックリンクを張る事でプログラムの参照先を切り替える。 # zypper install libev4 libev-devel ragel # wget https://github.com/gdnsd/gdnsd/archive/v2.4.0.tar.gz # tar zxvf gdnsd-2.4.0.tar.gz # cd gdnsd-2.4.0 # ./configure --prefix=/usr/local/gdnsd-2.4.0 # make # make install -&size(16){&font(b){2. 起動準備};}; gdnsdを動かす為の実行ユーザを作成した上で起動スクリプトも準備する。 gdnsdはsystemd起動に最適化されている為、 従来のinitスクリプトでは無くサービスファイルから起動を行う。 # cd /usr/local # ln -s /usr/local/gdnsd-2.4.0 gdnsd # cd /etc # ln -s /usr/local/gdnsd/etc/gdnsd gdnsd # vi /usr/lib/systemd/system/gdnsd.service --- [Unit] Description=gdnsd Authoritative GSLB type DNS Server After=local-fs.target network.target [Install] WantedBy=multi-user.target [Service] Type=notify NotifyAccess=all ExecStart=/usr/local/gdnsd/sbin/gdnsd -c /etc/gdnsd -f start ExecStop=/usr/local/gdnsd/sbin/gdnsd -c /etc/gdnsd stop ## Security Setting MountFlags=slave DevicePolicy=closed PrivateDevices=true PrivateTmp=true ProtectSystem=full ProtectHome=true -&size(16){&font(b){3. コンフィグ};}; gdnsdのコンフィグは、&color(#ff0000){[${PREFIX}/etc/gdnsd/config]};から固定参照で読み込んでいるので、 上記設定に合わせてコンフィグを作成しないと、プログラムの起動に失敗する。 今回はprefixをシンボリックリンクで[/usr/local/gdnsd]に変更している為、 作成するコンフィグファイル名も&color(#ff0000){[/usr/local/gdnsd/etc/gdnsd/config]};になる。 コンフィグファイルにはgdnsdのヘルスチェック条件も書く必要があるが、 権威DNSサーバ機能のみ利用するならば、optionsのみ記載すればプログラムを起動出来る。 なお、コンフィグのコメント接頭文字は『#(シャープ)』となる。 # vi /usr/local/gdnsd/etc/gdnsd/config --- options => { username => gdnsd, chaos_response => "GSLB", listen => 0.0.0.0:53, http_listen => 0.0.0.0:3506, log_stats => 3600, # tcp_threads => 2, # udp_threads => 2, include_optional_ns => true, disable_text_autosplit => false, zones_strict_data => false, zones_strict_startup => true, zones_rfc1035_auto => false, state_dir => /var/lib/gdnsd, run_dir => /var/run/gdnsd, plugin_search_path => /usr/local/gdnsd/lib64/gdnsd, } #region(&color(#ff0000){設定の解説};) 全オプションはgdnsdの[[公式ドキュメント:+https://github.com/gdnsd/gdnsd/wiki/GdnsdConfig]]を確認する。 最低限の動作を行う場合、下記オプションを参考に設定を行う。 |LEFT:200|LEFT:600|c |CENTER:~OPTIONS HASH|CENTER:~DESCRIPTION|h |&color(#2020ff){username};|gdnsdを実行するユーザ名&br;グループ名はユーザに紐付いている物が自動適用される| |&color(#2020ff){chaos_response};|CHクラスの応答内容&br;権威DNSのバージョン確認クエリに、設定した文字列を返す| |&color(#2020ff){listen};|gdnsdの待ち受けIPアドレス・ポート番号&br;複数IPを待ち受けする場合はリスト構造にする| |&color(#2020ff){http_listen};|gdnsdヘルスチェック画面の待ち受けIPアドレス・ポート番号&br;複数IPを待ち受けする場合はリスト構造にする| |&color(#2020ff){log_stats};|DNSクエリの統計ログ出力間隔&br;値を"0"にすると、統計ログ出力が無効化される| |&color(#2020ff){tcp_threads};|リスナーアドレスに対して作成されるスレッド数&br;マルチコアCPUの場合、CPUコアの2~4倍にするとパフォーマンスが上がる| |&color(#2020ff){udp_threads};|リスナーアドレスに対して作成されるスレッド数&br;マルチコアCPUの場合、CPUコアの2~4倍にするとパフォーマンスが上がる| |&color(#2020ff){include_optional_ns};|DNSのAuthoritySectionにオプションのNSレコードを含めて応答する&br;応答速度とクエリサイズ最小化を優先する時は無効化(false)にする| |&color(#2020ff){disable_text_autosplit};|TXTレコードを255バイト毎に自動分割する設定を切り替える&br;通常は自動分割させる為、&color(#ff0000){自動分割を無効(false)};に設定する| |&color(#2020ff){zones_strict_data};|DNSゾーンファイルで重要度低のエラーが発生してもデーモンを起動する&br;"zones_strict_startup"の設定に依存する| |&color(#2020ff){zones_strict_startup};|gdnsd起動時にDNSゾーンファイルが正しく読めない時は起動停止させる&br;無効化(false)の場合、読込み失敗したDNSゾーンをスキップして起動させる| |&color(#2020ff){zones_rfc1035_auto};|DNSゾーンファイルの書き換えを検出し、DNSレコードに自動適用する&br;ファイル変更による意図しない適用が発生しやすいので無効(false)を推奨| |&color(#2020ff){state_dir};|gdnsdのデーモン起動情報を外部プログラムと連携する際に使用&br;ディレクトリが存在しない時は自動生成される| |&color(#2020ff){run_dir};|gdnsdのPIDファイル保存フォルダ&br;systemdからプロセス操作を行う際に必須となる| |&color(#2020ff){plugin_search_path};|ヘルスチェックモジュールの保存先ディレクトリ&br;基本的にPREFIX配下となる&color(#ff0000){"${PREFIX}/lib64/gdnsd"};を設定する| #endregion -&size(16){&font(b){4. ゾーンファイル};}; DNSゾーンファイルは、権威DNSとしてレコードを登録したいORIGINと同じファイル名にする。 例えば、[gslb.test.org]のゾーンを作成する場合、 DNSゾーンファイルは&color(#ff0000){[/usr/local/gdnsd/etc/gdnsd/zones/gslb.test.org]};となる。 ファイルの書式は基本的にBINDのゾーン記法と同じだが、 ヘルスチェックを元にした可変レコードの箇所のみ、gdnsd特有の設定を行う必要がある。 GSLBの特性上TTLを短くする必要がある為、ゾーンファイル内でTTLを明記して設定を切り替える。 参考として、DNSゾーンファイルの例を構築例に従って作成してみる。 # vi /usr/local/gdnsd/etc/gdnsd/zones/gslb.test.org --- $ORIGIN gslb.test.org. $TTL 3600 @ IN SOA ns1.gslb.test.org. root.gslb.test.org. ( 2018010101 ; serial 3600 ; refresh 1200 ; retry 1209600 ; expire 3600 ; minimum ) ;; GLUE RECORD ;; IN NS ns1.gslb.test.org. IN NS ns2.gslb.test.org. ns1 IN A 10.0.0.1 ns2 IN A 10.0.0.2 ;; CONTENTS sv1 IN A 192.168.0.1 sv2 IN A 192.168.0.2 ;; GSLB ;; $TTL 10 ;; この箇所にGSLBの可変レコードを記載 -&size(16){&font(b){5. 起動};}; 事前に作成したサービスファイルを用いてgdnsdを起動する。 ログはsyslogのdaemonファシリティに出力されるので、起動しない時は該当ログを確認する。 # systemctl unmask gdnsd # systemctl enable gdnsd # systemctl start gdnsd ~ * ヘルスチェック [#g6be9422] GSLBは下位サーバに対してヘルスチェックを行い、ステータスに応じた可変レコード応答が特徴となる。 負荷分散(ラウンドロビン)、冗長化(アクティブ・スタンバイ)など様々な構成を取る事が出来る。 gdnsdにもヘルスチェックが搭載されており、基本的なGSLBとして機能するようになっている。 また、標準のヘルスチェックで機能不足の場合、ヘルスチェックで外部コマンドを実行する事で、 環境に応じた機能拡張を行う事も出来る。 gdnsdのヘルスチェックを利用する時は、gdnsdのconfigファイルにヘルスチェック定義を設定した後、 DNSゾーンファイルで専用の可変レコードを設定する事により利用可能となる。 可変レコードは&color(#ff0000){Aレコードを定義するDYNAレコード};と、&color(#ff0000){CNAMEを定義するDYNCレコード};の2種類が存在する。 ただし、利用出来る可変レコードはプラグイン毎に制限されている為、詳細は公式ドキュメントを確認する。 DYNA/DYNCレコードは&color(#ff0000){["プラグイン種別"!"応答レコード定義名"]};で設定する。 DNSゾーンファイルには下記の様なレコードを追記する事となる。 www 10 DYNA multifo!tcp_80 blog 10 DYNA weighted!tcp_443 cname 10 DYNC weighted!tcp_cname -&size(16){&font(b){ラウンドロビン};}; #region(&color(#ff0000){設定内容};) ヘルスチェックを行いつつ、コンテンツサーバにラウンドロビンで振り分ける。 コンテンツサーバでWEB(TCP:80)を待ち受けている環境を想定して作成する。 gdnsdのヘルスチェック設定は、既存のconfigファイルに追記を行う事で定義する。 # vi /usr/lcoal/gdnsd/etc/gdnsd/config --- service_types => { tcp_80_http => { plugin => http_status, port => 80, url_path => /, ok_codes => [ 200, 301 ], up_thresh => 10, ok_thresh => 5, down_thresh => 5, interval => 5, timeout => 4, } } plugins => { multifo => { tcp_80 => { service_types => tcp_80_http, up_thresh => 0.001, SERVERA => 192.168.0.1, SERVERB => 192.168.0.2, } } } DNSゾーンファイルには、Aレコード・CNAMEの応答内容に従って可変レコードを記載する。 例えば、Aレコードとして可変応答させる場合は下記の様になる。 # vi /usr/local/gdnsd/etc/gdnsd/zones/gslb.test.org --- ;; DNSゾーンに追記 $TTL 10 www 10 DYNA multifo!tcp_80 #endregion -&size(16){&font(b){重み付け応答};}; #region(&color(#ff0000){設定内容};) 重み付けに応じて応答するレコードを変更する場合、ラウンドロビン設定内のIP指定部分をリストに変更する。 変更箇所はpluginsフィールド内のみで、service_typesは書き換えなくて良い。 下記設定では、SERVER-Aを1、SERVER-Bを3とした場合の重み付け応答となる。 # vi /usr/lcoal/gdnsd/etc/gdnsd/config --- plugins => { weighted => { tcp_80 => { service_types => tcp_80_http, up_thresh => 0.001, multi => false, SERVERA => [ 192.168.0.1, 1], SERVERB => [ 192.168.0.2, 3], } } } 重み付け応答にする為、監視モジュールを[multifo]から[weighted]に切り替えている為、 DNSゾーンファイルのDYNAレコード設定も変更する。 # vi /usr/local/gdnsd/etc/gdnsd/zones/gslb.test.org --- ;; DNSゾーンに追記 $TTL 10 www 10 DYNA weighted!tcp_80 #endregion -&size(16){&font(b){ping死活監視};}; #region(&color(#ff0000){設定内容};) gdnsdのヘルスチェックには、ping死活監視モジュールが存在しない。 その為、ping死活で応答レコードを変更する場合、外部コマンドモジュールを利用してpingを直接実行する。 外部コマンドに監視対象のIPアドレスを引き渡す場合、特殊変数の&color(#ff0000){"%%ITEM%%"};を指定する。 "%%ITEM%%"変数を指定する事で、configファイルの応答IPアドレスを監視対象としてスクリプトに引き渡す。 外部コマンドモジュールを利用する場合、パラメータ内で利用するコマンドのタイムアウト秒数が、 gdnsdヘルスチェックのタイムアウト秒数以上にならない様に注意する。 # vi /usr/lcoal/gdnsd/etc/gdnsd/config --- service_types => { icmp_command => { plugin => extmon, cmd => ["/usr/sbin/fping", "-t 3", "%%ITEM%%"], interval => 10, timeout => 4, up_thresh => 6, ok_thresh => 3, down_thresh => 3, } } plugins => { multifo => { icmp_check => { service_types => icmp_command, up_thresh => 0.001, SERVERA => 192.168.0.1, SERVERB => 192.168.0.2, } } } DNSゾーンファイルの内容も設定に応じて書き換える。 外部コマンドを利用しているが、DYNAレコードに影響のあるpluginsは大きく変更していない為、 従来通りの書式に従ってDYNAレコード設定を変更する。 # vi /usr/local/gdnsd/etc/gdnsd/zones/gslb.test.org --- ;; DNSゾーンに追記 $TTL 10 www 10 DYNA multifo!icmp_check #endregion -&size(16){&font(b){NAT配下監視};}; #region(&color(#ff0000){設定内容};) クラウドではグローバルIPとインスタンス内部アドレスを、NATで紐付けてアドレスを割り当てている場合が多い。 この時、NAT装置がNATループバック(ヘアピンNAT)に対応していない場合、 グローバルIPからインスタンス内部アドレスに変換が出来ず、&color(#ff0000){グローバルIPでヘルスチェックが出来なくなる。}; 解決方法は複数存在するが、楽な方法としては外部コマンドヘルスチェックを応用する事で、 インスタンス内部アドレスで監視しつつ、DNSの応答レコードをグローバルIPにする事が出来る。 下記は、Web(TCP:80)への死活監視をインスタンス内部アドレス(SERVER-A/B)で行いつつ、 DNSレコードとしてはグローバルIP(NAT)を応答する設定例となる。 # vi /usr/lcoal/gdnsd/etc/gdnsd/config --- service_types => { script_mon => { plugin => extmon, cmd => ["/usr/local/bin/script_mon.pl", "%%ITEM%%", "80"], interval => 10, timeout => 4, up_thresh => 6, ok_thresh => 3, down_thresh => 3, } } plugins => { multifo => { tcp_80 => { service_types => script_mon, up_thresh => 0.001, SERVERA => 1.1.0.1, SERVERB => 1.1.0.2, } } } DNSゾーンファイルは今までと同じように、plugins/service_typesを設定する。 この時、重み付け応答を行いたい場合、設定に合わせてDYNAレコードを修正する。 # vi /usr/local/gdnsd/etc/gdnsd/zones/gslb.test.org --- ;; DNSゾーンに追記 $TTL 10 www 10 DYNA multifo!tcp_80 外部コマンドは様々は作成方法があるが、筆者が作成したperlの作例を公開。 応答性重視の監視スクリプトとして利用する為、例外処理は全部省いている。 # vi /usr/local/bin/script_mon.pl --- #!/usr/bin/perl $GIP = $ARGV[0]; $PORT = $ARGV[1]; %LIP; $LIP{'1.1.0.1'} = "10.0.0.1"; $LIP{'1.1.0.2'} = "10.0.0.2"; $STAT = system("/usr/bin/nc -w 1 $LIP{$GIP} -z ${PORT}"); $STAT = $STAT >> 8; exit $STAT; #endregion -&size(16){&font(b){アクティブ・スタンバイ};}; #region(&color(#ff0000){設定内容};) 作成中... #endregion