Tech Racho エンジニアの「?」を「!」に。
  • インフラ
  • 開発

【ゆるふわDocker部】任意バージョンのPostgreSQLコマンドを実行して外部DBに接続する

morimorihogeです。そろそろラナンの探索マップがコンプリートできそうです。

前回の記事でゆるふわDocker部の提案をしましたので、ゆるゆるとDocker Tips的なものを垂れ流していこうと思います。今回はバージョン依存の強めなPostgreSQLのCLIコマンドをDocker経由で簡単に実行する方法を紹介します(今回は9.3系ですが、適宜他のバージョンで読み替えても使えるはず)。Dockerを使うことでローカルシステムにインストールされていないPostgreSQLクライアントで外部のPostgreSQLサーバに接続することが簡単になります。

確認環境はMac OS X El Capitan、Docker 1.9.1で、Docker Toolboxビルトインのboot2dockerを使っています。いつものことですがご利用は自己責任でお願いします。

PostgreSQLのクライアントバージョン問題とは

MySQLと並んで人気OSS系RDBMSのPostgreSQLですが、よく引っかかる問題としてバージョン依存性が強いというものがあります。
例えば、クライアント側のバージョンが9.2.9でサーバ側のバージョンが9.3.1のケースでpg_dumpコマンドを実行すると、以下の様なエラーが発生して接続に失敗しまいます。

pg_dump -h somehost -U someuser -W somedb
Password:
pg_dump: server version: 9.3.1; pg_dump version: 9.2.9
pg_dump: aborting because of server version mismatch

DBサーバとアプリサーバを分けている時なんかには割と発生しがちな問題ですね。他にもたまに古めのサーバに接続しないといけないときはちょこちょこあるので、困ることがあります。

Dockerをでかいバイナリコマンドとして使う

今回実行したかったのはpg_dumpコマンドだけで、dumpデータさえ取れれば後は手元にインポートして使うぜ、という状態でした。そこで、使いたいバージョンのPostgreSQL containerを取得してそのバージョンのPostgreSQLコマンドを使うという用途でDockerを使うことにしました。
今回はDocker HubでPostgreSQL関連のDocker Imageを漁って使ってみましたので、手順を追っていきます。他のプログラムでも同様の手順で大体はなんとかなると思います。

Docker Hubから使えそうなイメージを探してくる

まずは、Docker Hubの画面一番上の検索から「postgres」で検索すると、

Screenshot 2016-02-15 18.13.21

オフィシャルのpostgres imageが見つかります。

Screenshot 2016-02-15 18.14.42

Tag一覧のページを見ると、9.3系では9.3.10が生きてることが確認できます。

Screenshot 2016-02-15 18.17.15

というわけで、欲しいDocker Imageは「postgres:9.3.10」であることが特定できました。
しかし、ここで良く見ると、9.3系のTagで古いビルドバージョンのもの(9.3.9以下)は削除されていっていることが分かります。これだと9.3.11が出た後に最新をpullすると9.3.10が使えなくなってしまいますね。

そこで、よくよくREADMEを読んでみると、9.3.10の他に9.3というTagがあることが分かります。

Screenshot 2016-02-15 18.49.09

というわけで、9.3系であれば良いということであれば、Tagには9.3.10ではなく9.3を指定するのが良さそうです。最終的に使うDocker Imageは「postgres:9.3」が良さそうです。

イメージをpullして、使いたいコマンドの実体を探す

イメージ名が特定できたので、取得します。以下のコマンドで調べたDocker Imageを取得できます。タグ名(ここでは9.3を省略するとlatestが取得されます)。

docker pull postgres:9.3

ダウンロードが完了したら、実行したいコマンドのパスを調べていきます。とりあえずシェル(/bin/bash)を立ち上げて欲しいコマンド(pg_dump)を探索してみます。
ここでの「-it」オプションはインタラクティブ&仮想TTYオプションで、対話式のコマンドを実行するときにはこれを付けるモノだと思っていればOKです。「--rm」を付けるとコマンド実行終了時に自動的にcontainerを削除してくれます(docker ps -aしたときに「うわあああああ」ってならなくて済みます)

docker run -it --rm postgres:9.3 /bin/bash
root@4d0c308aa3c6:/# which pg_dump
/usr/lib/postgresql/9.3/bin/pg_dump

ありました。これで、欲しいコマンドは「postgres:9.3の/usr/lib/postgresql/9.3/bin/pg_dump」であることが確認できました。
一応ためしに実行してみましょう。

docker run --rm postgres:9.3 /usr/lib/postgresql/9.3/bin/pg_dump --help
pg_dump dumps a database as a text file or to other formats.

Usage:
  pg_dump [OPTION]... [DBNAME]

General options:
  -f, --file=FILENAME          output file or directory name
  -F, --format=c|d|t|p         output file format (custom, directory, tar,
                               plain text (default))
  -j, --jobs=NUM               use this many parallel jobs to dump
  -v, --verbose                verbose mode
(以下略)

大丈夫そうですね。これでとりあえず欲しかったコマンドは使えるようになりました。

DockerコンテナからリモートのPostgreSQLサーバに接続する

ここまででpostgresコンテナから任意バージョンのコマンドを実行することはできましたが、このままだとまだ接続したいサーバに接続できていません。ここからはMac環境依存です(Linuxでも大丈夫ですが、Windowsはsshコマンド周りがダメかも)。

MacでDockerを動かす場合、VirtualBox上でboot2docker等のDocker HostとなるLinuxを動作させ、そこに接続して使うという形が一般的ですが、これだと動かしているcontainerは論理的にはVirtualBoxのprivate network上に存在する状態になっています。
今回、dumpを取得したいDBサーバがAWS VPCの中にあったため、接続するためにSSHトンネルを張る必要が出てきました。

SSHトンネルについては過去の記事の後半で解説しているので、よく分からない人はそちらも参照して下さい。

言葉で説明するとわかりにくいので、ネットワーク構成をまとめてみました(文字が一部小さいので適宜拡大して見て下さい)。

Screenshot 2016-02-15 23.37.38

ローカルのMacで上で動作するVirtualBoxでboot2dockerのDocker Hostが動き、その上でpostgres:9.3のcontainerを動かします。
AWS側は良くある構成ですが、APPサーバはパブリックIPを持っているため外からSSHアクセスできますが、DBサーバはVPCのプライベートネットワーク上にあるため直接インターネットからアクセスはできません。

ここで、今回接続したい経路は下図赤線の通りです。
図で書くとpostgres:9.3のDocker Containerがかなり深い位置にあることが分かるのではないでしょうか。

Screenshot 2016-02-15 23.40.25

さて、postgres:9.3 containerからAWS VPC上のDBサーバに接続するにあたって、今回は以下の方針を取ることにします。

  1. MacのVirtualBox向けネットワークインターフェースからAWS VPC上のDBサーバのpostgresqlポート(5432)へのSSHトンネルを掘る
  2. postgres:9.3 containerからMac上のvboxnet1に対してpg_dumpを実行する
  3. 前記のdockerコマンドはLocal Mac上で実行するので、pg_dumpの結果を標準出力から取得してLocal Mac上に保存する

接続イメージは下図の通りです。青線がSSHトンネル緑線がpg_dumpコマンドの範囲になります。

Screenshot 2016-02-16 00.18.31

この方式の利点は以下の通りです。

    • SSHトンネルはさらに多段にすることも可能なので、やろうと思えばもっと複雑なネットワーク上の深い所にあるサーバにもLocal Macから透過的に接続できる(汎用性)
    • SSHトンネルのlisten addressはVirtulBoxのプライベートIPのため、もし社内intra networkに悪い人がいてもこのSSHトンネルを利用することはできない(安全性)
    • 必要なSSH接続導線は「Local Mac -> VPC上のAPPサーバ」「APPサーバ -> DBサーバ」の2つなので、APPサーバやDBサーバに新規でSSH公開鍵を登録する必要がない(保守性)

上図では省略しましたが、本来Docker Hostはeth0にVirtualBoxのNATネットワークインターフェースを持っていて、これを通すことでpostgres:9.3 containerから外に直接SSHトンネルを掘ることもできますが、その場合はpostgres:9.3 containerでSSH鍵ペアを作成し、今回のコマンドを実行するためだけの公開鍵をAPPサーバに配置する必要が出てきたりして今ひとつイケてないです。これだともしその後そのcontainerを誰かと共有したりするとAPPサーバへの接続権限ごと渡ってしまうことになってしまいますしね。

SSHトンネルを掘る

以下のコマンドでOKです。これで前述の図で書いた青線のトンネルが掘られます。

ssh -fNL 192.168.99.1:15432:a.b.c.d:5432 w.x.y.z

注意点として、Macに入っているSSHの-L(ローカルフォワード)オプションでは、listen addressがデフォルトでは127.0.0.1(lo0)になってしまう点です。ループバックインターフェースでlistenされるとDocker Hostからは接続できないため、明示的にvboxnet1のIPアドレスである192.168.99.1を指定しましょう。
※0.0.0.0にしても接続はできますが、その場合en0からもこのトンネルにアクセスできてしまうため危険です

Dockerコマンドを実行する

ようやくDockerコマンドを実行します。これまででpostgres:9.3上のpg_dumpコマンドを実行するところまではできていましたので、あとは通常通り-hオプションや-pオプションで接続する設定をしてやれば良いです。
ただ、ここで一点問題が出て、そのままだとパスワードをプロンプトから入力させる-Wオプションがうまく動きませんでした。そこで、コマンドラインからパスワードを設定してやる必要があります。
PostgreSQLではPGPASSWORD環境変数が設定されていれば、それをパスワードとして実行してくれるため、環境変数を渡します。これにはいくつか方法があり、bash経由で1コマンドとして実行する方法なら

docker run --rm postgres:9.3 /bin/bash -c "export PGPASSWORD='pg_password' && /usr/lib/postgresql/9.3/bin/pg_dump -h 192.168.99.1 -p 15432 -U pg_user db_name"

となりますし、-eオプションを使って設定するなら

docker run --rm -e PGPASSWORD='pg_password' postgres:9.3 /usr/lib/postgresql/9.3/bin/pg_dump -h 192.168.99.1 -p 15432 -U pg_user db_name

となります。どちらでも問題なく動作しますが、前者は最初自分で思考錯誤してたときの方法で、後者は社内Slackの#docker channelで垂れ流してたらyamasitaが教えてくれました。後者の方がシンプルですね。

とにかく実行するコマンドはこれ

長々と解説してきましたが、Docker環境が構築されてあれば、実行するコマンドは以下の通りです(IPアドレスやポート番号は図に準拠してるので、各自環境は自分の環境に合わせて下さい)。

docker pull postgres:9.3
ssh -fNL 192.168.99.1:15432:a.b.c.d:5432 w.x.y.z
docker run --rm -e PGPASSWORD='pg_password' postgres:9.3 /usr/lib/postgresql/9.3/bin/pg_dump -h 192.168.99.1 -p 15432 -U pg_user db_name > db.dump

終わり!閉廷!以上!皆解散!

まとめ

今回はゆるふわDocker部としてDockerコンテナを一つのでかいコマンドバイナリとして使う方法を紹介しました。ライブラリなどの環境依存が強いコマンドはDockerとの親和性が高いので実用的だと思います。
今後もゆるふわDocker部ではできるだけ簡単に実用的なDockerの使い方を不定期に垂れ流していこうと思いますのでよろしくお願いします。

追記:Dockerコマンドの短縮とgz圧縮

手順の中でpostgres:9.3のDocker Imageからpg_dumpのPATHを探す所がありましたが、実は普通にPATHが通っていました。なので「/usr/lib/postgresql/9.3/bin/pg_dump」と書いてあった部分は単に「pg_dump」で動きます。
また、dumpファイルを生のままdb.dumpファイルに吐き出していますが、受け渡しするなら圧縮してあった方が便利なので「| gzip -c」を付けて、

docker run --rm -e PGPASSWORD='pg_password' postgres:9.3 pg_dump -h 192.168.99.1 -p 15432 -U pg_user db_name | gzip -c > db.dump.gz

とすれば一気にgz圧縮されたdumpを取得することができます。まあこれは普通のシェル操作なので、Dockerに依存した話ではないですね。


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。