KITA Eng.

北海道でサーバー技術者として歩み出したひとが綴るblog。

UbuntuでPostgreSQLのストリーミングレプリケーションをするときの微妙にはまる!?落とし穴

PostgreSQL 9.5でストリーミングレプリケーションの構築を試してみました。いわゆるDBのマスター&スレーブ構成ってやつでしょうか。 これ→25.2. ログシッピングスタンバイサーバ

その作業の中で、普通ははまらないであろう落とし穴に落ちたので記録しておきます。

僕の落ちた落とし穴に落ちる可能姓のある人

僕が落ちた落とし穴に落ちる可能姓があるのは、以下の様な状況の人です。

  • OSがUbuntu
  • PostgreSQLをパッケージでインストールしている
  • 複数のバージョンのPostgreSQLがインストールされている
    (しかもマスタースレーブ構成を構築するPostgreSQLが一番最初にインストールしたバージョンじゃない)
  • /etc/postgresql-common/user_clustersで使用するPostgreSQLのバージョンを指定していない

ということが重なると今回の落とし穴に落ちる可能姓が大きいです。

はまった落とし穴

PostgreSQL 9.4での情報ですが、以下の方法の通り進めていきました。 hkou.hatenablog.com

ちなみに、Let's Postgresのページでも同様のやり方説明がありました。 lets.postgresql.jp

マスター側の設定は何事もなく終了し、「ベースバックアップをスレーブに転送する」をしようと、

user@localhost:~$ sudo su - postgres
postgres@localhost:~$ pg_basebackup -h exsample.com -U replicationuser -D /var/lib/pgsql/9.5/main/ -P -v --xlog
Password:
pg_basebackup: incompatible server version 9.5.1

という挙動を示してくれました。 pg_basebackup: incompatible server version 9.5.1

何事ぞ。

ejje.weblio.jp

どうやらバージョンが違うといっているようですが、マスター側のDBは9.5.1だし、スレーブ側でも、

postgres@localhost:~$ psql --version
psql (PostgreSQL) 9.5.1

といわれます。 スレーブDB側の環境では確かに9.4、9.5の二つが動いていますが・・・。一応、Google先生に問い合わせてみるもそれらしい回答がない・・・。困ったなぁと思ったときに、そういえばUbuntuのPostgrSQLってなんだか特殊なんだったということを思い出しました。

lets.postgresql.jp

上にあるように複数バージョンのPostgreSQLを運用しやすいように?なんかうまいことやってくれているらしいのですが、これが時にやっかいごとを招くようです。

落とし穴からの飛び出し方

落とし穴に入っているかは、このコマンドを叩いてみれば一発です。pg_basebackupのバージョンを確認してみます。pg_basebackupの説明ページでほとんど使うことはないだろうけどみたいに紹介されているオプション-vが役立つときです。

postgres@localhost:~$ pg_basebackup -v
pg_basebackup (PostgreSQL) 9.4.6

なんで9.4.6なの!?

理由はちょっと置いといて、Ubuntu で PostgreSQL を使ってみよう(続編) — Let's Postgresが言うには、 /usr/lib/postgresql/version/binにそれぞれのオリジナルの実行ファイルがあるようなので、

postgres@localhost:~$ /usr/lib/postgresql/9.5/bin/pg_basebackup -h exsample.com -U replicationuser -D /var/lib/pgsql/9.5/main/ -P -v --xlog
Password: 
transaction log start point: 0/7000028 on timeline 1
151435/151435 kB (100%), 1/1 tablespace                                         
transaction log end point: 0/7000130
pg_basebackup: base backup completed

ということでうまくいきました。 ちなみに、/etc/postgresql-common/user_clustersで使用するPostgreSQLのバージョンを指定していれば、問題ないようです。

その後の作業は上のリンク先の通りにやったら無事完了でした。めでたしめでたし。

グローバルIPアドレス国別割り当てのデータを生成するスクリプトが書き上がるまでの道のりは険しかった…(解決編)

kitaeng.hateblo.jp

RIRsが提供してくれている、RIR statistics exchange formatになっているIPアドレスの割り当て状況のデータを加工するスクリプトを書きました。思考、試行過程をちょっと備忘録的に記録しておきますの第二弾です。

RIR statistics exchange formatを扱う時の問題点

詳細は第一弾で書いたとおりですが、一応ざっくりだけ。 kitaeng.hateblo.jp

RIR statistics exchange formatのレコードの中には、1レコードに対して一つの「ネットワークアドレス/CIDRブロック」の形で表せないレコードが存在しています。

例えばこんなレコードがあります。valueというのがIPアドレスの個数です。

registry cc start value date status
ripencc CH 195.242.163.0 768 20080617 assigned

768個って、上の表では出てきません。768 = 256 * 3なので、なるべくブロックが細かくならないように分けるとすると、/23/24で分割してあげないといけません。

registry cc start value date status
ripencc CH 195.242.163.0 256 20080617 assigned
ripencc CH 195.242.164.0 512 20080617 assigned

解決策1~まず始めに考えた方法

問題点としてあげたようなレコードは全レコードの中の大半を占めているという程の量ではなくて、普通に1レコードが一つの「ネットワークアドレス/CIDRブロック」の形で表せるものが大半なので、

  • 一つの「ネットワークアドレス/CIDRブロック」で表せるもの
  • 複数の「ネットワークアドレス/CIDRブロック」で表さなければならないもの

でレコードの処理方法を分ける事にしました。

一つの「ネットワークアドレス/CIDRブロック」で表せるものについては、CIDRブロックとホスト数の対応表を作って置いて、ホスト数(value)に基づいて、開始アドレス(=ネットワークアドレス)に対応するCIDRブロックを結合して出力することにしました。

複数の「ネットワークアドレス/CIDRブロック」で表さなければならないものについては、ホスト数(value)の値をうまく分割できるようなCIDRブロック表に現れるホスト数を見つけるために、valueの値を、CIDRブロック表にあるホスト数の少ない方から順に割っていって、割り切れなくなる(余りが0ではなくなる)ものの一つ前のCIDRブロックで分割することにしました。

一応コードとしては書き上がって、エラーもなく出力が得られたので、ウキウキしたのですが、PostgreSQLに読み込ませてからいろいろとSELECTをしてみていたら、なんだかデータがおかしいような気がしてきました。。。。

なんてこった!!

賢明な方は、すでに自分が陥った失態に気がついていることでしょう。

「RIR statistics exchange formatを扱う時の問題点」の説明のところを見直して見てください。分割すべきCIDRブロックは必ずしも同じ大きさのもので等分するのではなくて、複数の大きさのブロックをつかって分けた方が、効率的な分割になるのです。

等分するとなると結構小さなブロックが選ばれる事が多くて、/10とかは選ばれにくいわけです。そうなると、ある程度多い数のホスト数の部分を分割するときには、分割数が等分だと100個とかになることがありました。後々検索に書けるデータとして使うのには、レコード数はむやみにやたらと増えない方がベターな気がします。

ということで、この解決策1はボツになりました。

解決策2~別にホスト数とCIDRブロックの対応表なんていらないじゃん!

解決策1がボツになったことで、どうしたものかと考え直していたら、そもそも、ホスト数とCIDRブロックの対応表からマッチさせる必要などないのではないかと気がつきました。

ホスト数が分かれば、CIDRブロックなんて数値計算で導出できるではないかと。

計算方法はいくつかあるのでしょうが、科学計算分野でもよく使われるというPythonらしい感じで(!?)いくと、

Python 3.4.3
>>> import math
>>> cidr = 32 - math.log(value,2)

ホスト数(value)の底が2の対数を取って、32から引けば、そのホスト数をぴったり入れるCIDRブロックが分かります。一つのCIDRブロックで表現できないようなホスト数の場合は、この計算の結果が、整数にならないので、そこで判別ができます。

で、整数にならなかったものはどうするの??

例えば、このレコードの場合は、上の計算結果が小数になります。

registry cc start value date status
ripencc CH 195.242.163.0 768 20080617 assigned
Python 3.4.3
>>> import math
>>> cidr = 32 - math.log(768,2)
22.415037499278846

常に切り上げてあげれば、最大のCIDRブロックは出そうです。あとは、ブロックの大きさを一つずつ小さくしながら、ホスト数が0になるまで減らしていけばいいと思うのですが…。

この処理を書こうとしたら、for、while、if地獄に落ちいって、どうもいい書き方が生み出せませんでした。

ということで、解決策2もボツです。

解決策3~3度目の正直!Pythonには、ipaddressという便利なモジュールがあった!

うーん。困ったとうなりながら、最後の出力の部分で使えそうだなと思って目星をつけていたipaddressというPythonのモジュールのドキュメントを読んでいると…。

21.28. ipaddress — IPv4/IPv6 操作ライブラリ — Python 3.5.1 ドキュメントの、「21.28.5. その他のモジュールレベル関数」という項目で、

ipaddress.summarize_address_range(first, last)(原文)
first と last で指定された IPアドレス帯に対するイテレーターを返します。 first はアドレス帯の中の最初の IPv4Address か IPv6Address で、 last はアドレス帯の中の最後の IPv4Address か IPv6Address です。 first か last がIPアドレスでない場合や、2つの型が揃っていない場合に、 TypeError を発生させます。 last が first より大きくない場合や、 first アドレスのバージョンが 4 でも 6 でもない場合は ValueError を発生させます。

という関数があるという紹介がありました。

自分の欲していた機能ではないか!!しかもこれを使えば、別に、

  • 一つの「ネットワークアドレス/CIDRブロック」で表せるもの
  • 複数の「ネットワークアドレス/CIDRブロック」で表さなければならないもの

で処理を分けなくてもいい(=コードが短くなる)ではないか!と感激したのでした。

ipaddressモジュールのおかげで、随分と楽に出来上がりました。Python3.x系からの標準ライブラリのようなのが、難点ですが、Ubuntuとかだと、次の16.04とかではPython3.xがメインのほうになるとかならないとかってのを見たような気がしないでもないので…。

ということで、完成したのが、 kitaeng.hateblo.jp

で紹介しているスクリプトです。

グローバルIPアドレス国別割り当てのデータを生成するスクリプトが書き上がるまでの道のりは険しかった…(原因編)

kitaeng.hateblo.jp

RIRsが提供してくれている、RIR statistics exchange formatになっているIPアドレスの割り当て状況のデータを加工するスクリプトを書きました。思考、試行過程をちょっと備忘録的に記録しておきます。

RIR statistics exchange formatについて

このフォーマットの仕様が道のりを険しくする最大の要因です。

普段IPアドレスの一覧を作るときや、iptablesとかの設定に入れるときは、

  • "192.168.2.1" <-- 個別のIPそれぞれ
  • "192.0.2.0/24" <--ブロックでまとめて

と表現することが多いと思います。IPアドレスの国別IPアドレス割り当てなんて話になれば、膨大な数のIPアドレスを扱うことになるので、個別のIPを列挙していくというよりも、ブロックでまとめての表現をつかうのがベターと思うのですが……。

RIR statistics exchange formatでは、割り当て開始のIPアドレスと、そこから何個分を割り当てているかという形で表現されています。そこから何個分を割り当てているかというところが鬼門です。

CIDR表記で扱う時の各CIDRに対応するIPアドレスの個数は、2 ^ (32 - CIDR)で表せて、

CIDR IPアドレス CIDR IPアドレス
/32 1 /16 65,536
/31 2 /15 131,072
/30 4 /14 262,144
/29 8 /13 524,288
/28 16 /12 1,048,576
/27 32 /11 2,097,152
/26 64 /10 4,194,304
/25 128 /9 8,388,608
/24 256 /8 16,777,216
/23 512 /7 33,554,432
/22 1,024 /6 67,108,864
/21 2,048 /5 134,217,728
/20 4,096 /4 268,435,456
/19 8,192 /3 536,870,912
/18 16,384 /2 1,073,741,824
/17 32,768 /1 2,147,483,648
/0 4,294,967,296

という具合になるわけですが、RIR statistics exchange formatでは、この表に出てくるIPアドレス数以外の個数が現れます。

えっ!?」ってなりますよね?

例えばこんなレコードがあります。valueというのがIPアドレスの個数です。

registry cc start value date status
ripencc CH 195.242.163.0 768 20080617 assigned

768個って、上の表では出てきません。768 = 256 * 3なので、なるべくブロックが細かくならないように分けるとすると、/24/23で分割してあげないといけません。

registry cc start value date status
ripencc CH 195.242.163.0 256 20080617 assigned
ripencc CH 195.242.164.0 512 20080617 assigned

こんな具合に書いていてくれればいいんですけど、そうじゃない……。

上のCIDR一覧に出てくるような数字だけが、valueに入っているなら、単純にvalueの部分を対応するCIDRに置換していって、開始IPアドレスstartと結合してあげれば、"192.0.2.0/24"の形になってくれるので、簡単だったんですが…。

手作業で例外部分だけ直すなんてのはちょっと美しくないのでどうしたものかとなったわけです。

皆さんならばどう解決しますか??

つづく...

グローバルIPアドレス国別割り当てのデータを生成するスクリプトを書いた

サーバーの保守をやっていると、接続元IPアドレスで国外からのアクセスかどうかを調べることが多々あります。1、2件調べるだけならwhoisコマンド使って確認すればいいんですけど、whoisの結果って表示書式がバラバラでかなり見にくいですよね。

それに、アクセスログから収集した多数のIPアドレスについて確認するとなると…。なかなか面倒な話です。IPアドレスと割り当て先の国の対応表でもあれば、うまいことマッチングさせられるので楽なんだけどなと思って、Google先生に問い合わせると、いろいろとご回答をいただきました。

これらのページで公開されているリストを使ってしまうというのもいいのですが、RIRsから、日々更新されているデータがもとになっているっぽいので、プログラム書きの練習がてら、RIRs提供データから、国コードとIPアドレスの対応表を作成するスクリプトPythonで書いてみました

RIR statistics exchange formatからIPv4アドレスと国コードの対応表を作るPython3スクリプト

紆余曲折あって出来上がったのが、こちらのスクリプトです。Python3には、IPアドレスを扱うのに便利なipaddressというモジュールがあるということを発見して、これを使って書いてみました。

ちなみに、どうやらipaddressモジュールは、Python 2.x系では標準サポートされていないっぽい?です。

17行目の、rirsFile = 'rirsfiles'で元となるRIR statisticsのデータファイルを指定してあげます。
18行目の、outFile = 'ipdata.csv'で出力するcsvデータの保存先ファイルを指定してあげます。

各RIRが提供しているRIR statisticsのデータは、

からそれぞれ最新版がダウンロードできます。各RIRのローカル時刻で23:59時点の情報から生成されるデータのようです。毎日更新されているらしいです。IPv4以外の情報も混じっていますが、IPv4についての部分のみ抽出するようにしてあるので、前処理不要です。しいて言えば、5つのRIRからダウンロードしてきたデータを単純に結合しておくと、全部分をいっぺんにできます。

RIR statistics exchange formatについては、APNICのページに見やすく成型されたドキュメントがあります。→RIR statistics exchange format | APNIC

出力されるCSVファイル

上記スクリプトで生成されるcsvファイルは、以下から出力ファイルがダウンロードできるようにしてみました。データの正確性については保証できませんが、ご自由にごりようください。データは、毎日午前3時30分ごろに更新されます。上記スクリプトから生成されるのは、改行コードがLFのものだけですが、事後処理でCR+LF版も生成してあります。

自動化のしくみとか
  1. 最新版ファイルのダウンロード
  2. ダウンロードデータからcsvファイル作成
  3. Postgresqlへの読み込み

を自動化させようと思ってごにょごにょしてます。おおむね完成というところです。一応、Githubレポジトリを作ってみたのでどうぞ。Pythonとshのコラボ仕様となっています。

github.com

Postgresに取り込んでいるのは、最初にやりたかった「アクセスログから収集した多数のIPアドレスについて確認」をするために、検索ならば、DBにデータ入れといたほうがいろいろとよかろうという、思い込みによるものです。

DBがPostgresqlなのは、データ型にipaddressに関する型があって、なんだか便利そうだったのでpostgresqlです。

記事書きながら一応調べなおしたら、mysqlには型はないけど、一応簡単に扱えるらしいですね...。→ SQLにおけるIPアドレスの比較 | Yakst

あれ・・・

マッチングに使うデータの生成まではできましたが、まだ当初目的「アクセスログから収集した多数のIPアドレスについて確認」を達成していません。もう一息。

Let's EncryptのSSL証明書を自動更新して、結果をメール通知するスクリプトを書いてみた(WEBROOT、NGINX用)

2015/12/03にパブリックベータ版になったLet's Encrypt。以下のように掲げられているような理念だそうですね。

Let's Encrypt は、認証局(CA)として「SSL/TLSサーバ証明書」を無料で発行するとともに、証明書の発行・インストール・更新のプロセスを自動化することにより、TLSHTTPSTLSプロトコルによって提供されるセキュアな接続の上でのHTTP通信)を普及させることを目的としているプロジェクトです。( Let's Encrypt 総合ポータル )

内部的に使うシステムとかだと、自己証明書(オレオレ証明書とかいう)でもいいんでしょうけど、それなりに大きな組織が使うのにオレオレ証明書なのってどうなんだってのがあったりなかったりした記憶があります。

最近使い始めたSoft Ether VPN Serverとかだと、Soft Ether VPN Clientをいれればオレオレ証明書でも怒られませんが、Windows10とかiPhoneの標準装備VPNクライアントでつなごうとすると、オレオレ証明書だとつなげませんでした。なんか回避策はあるのかもしれませんが…。

そんなときには、結構便利なサービスになるんだろうなと思いますが、証明書の有効期限が90日間というのがネックです。といっても、ほんの1行のコマンドで取得、更新、廃止ができるようにするのが到達点のようなので、更新の自動化さえしてしまえば、有効期限が短いのは大した問題ではないのですが、パブリックベータがスタートした段階では、自動化をどうするかのHow toは公式さんにはありませんでした。が、今日のぞいてみたらシンプルなスクリプトが紹介されていました

前置きが長くなりましたが、証明書を更新し、更新結果をメール通知までしてくれるようにちょとスクリプトを書いてみましたよって話です。

前提として…

そもそも、Let's Encryptってなんだよとか、どうやって導入するんだよということは、以下のページにわかりやすくまとまっているので、こちらを参考にしましょう。

blog.apar.jp

初回取得後以降の自動更新スクリプト

公式さんのスクリプトを大改造!?して、証明書の更新処理、WEBサーバーの再起動(更新した証明書を読み込ませる)、結果をメール通知してくれるスクリプトを書きました。

29~33行目の設定部分を環境に合わせて修正してください。

# CONFIGER SECTION #----------------------------------------------------------#

readonly CMD_PATH=/usr/local/letsencrypt
readonly LOG_FILE=/var/log/letsencrypt/renew.log

readonly SERVER_RESTART_CMD='/etc/init.d/nginx restart'   # After update CA, How to restart WEB Server?
                                                          # Default set is for NGINX

readonly CN="exsample.com"                   # Your Common Name for CA
readonly WEB_ROOT_PATH=/var/www/html   # WEB ROOT Path 
readonly MAIL_TO="root"                      # Mail to update result.
                                             # Default set is Local USER ROOT!

#-----------------------------------------------------------------------------#

こいつを、cronで毎月とか2か月に1回動かしてやればOKです。

cronの設定例(2か月に1回、15日の午前3:05に実行する)

5 3 15 */2 *    root    if [ -x /root/bin/ letsencrypt_cert_autoupdate_webroot.sh ]; then /root/bin/ letsencrypt_cert_autoupdate_webroot.sh; fi 

うまく更新できたときには、こんなメールが届きます。

    Subject: [Let's Encrypt Auto Update] Update Report for exsample.com
    Date: Sun, 31 Jan 2016 12:54:22 +0900 (JST)
    From: root <root@mx.exsample.com>
    To: root@mx.exsample.com

    # Let's Encrypt Cert autopudate Start: 2016-01-31-Sun-12:54:02
    # Update Log START --------------------------------------------------------------#
    Updating letsencrypt and virtual environment dependencies......
    Requesting root privileges to run with virtualenv: /root/.local/share/letsencrypt/bin/letsencrypt certonly --renew-by-default --webroot -w /var/www/html -d exsample.com
    IMPORTANT NOTES:
    - Congratulations! Your certificate and chain have been saved at
    /etc/letsencrypt/live/exsample.com/fullchain.pem. Your cert will
    expire on 2016-04-30. To obtain a new version of the certificate in
    the future, simply run Let's Encrypt again.
    - If you like Let's Encrypt, please consider supporting our work by:

    Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
    Donating to EFF: https://eff.org/donate-le


    # WEB SERVER RESTART LOG START ------------------------------#
    * Restarting nginx nginx
    ...done.
    #---------------------------- WEB SERVER RESTART LOG END ---#
    #------------------------------------------------------------- Update Log END ---#
    # Let's Encrypt Cert autopudate End: 2016-01-31-Sun-12:54:22