タイトルのとおりです。Dockerコンテナ上でPowerDNSとphpIPAMを稼働させ、更に連携させる方法を紹介します。
これで「phpIPAMでIP予約したら、PowerDNSにレコードを登録する」とかを自動化させることが可能です。
そもそもの経緯
このブログで何度か紹介していますが、私は自宅にvCenterサーバーを持っていて、いろんな検証に活用しています。
エンジニアであれば自宅サーバの構築を強くオススメします。会社にラボ環境がある方も多いかと思いますが、その手のラボ環境って共同で使用することが多いので、環境に影響する作業をおいそれと出来ない。一方、自宅ラボであれば好き勝手できるので学習効率が一気にアップします。
さて、ラボを構築する為には「自前のDNS」も用意したほうが都合がいい。例えばvCenterの導入にはDNSが必須なので、いずれはDNSサーバを構築することになる。
私の自宅ラボ環境も、ローカルドメインを管理しているDNSがあります。老舗の「BIND」を使用していました。古き良き時代の産物ですね。
BINDを使ってきて数年、、最近以下の不満が出てきました。
- IP管理がめんどい
- ノードが増える毎にconfファイルを弄るのがめんどい
ESXi上に好き勝手VMを立ち上げられるので、使うIPも増えてきました。これまでは毎回BINDにレコードを適当に入れて管理していたので、空いてるレンジがわからなくなったりもして、もっと良い(ラクな)方法がないかと調べていました。
そんな中で「phpIPAM」と「PowerDNS」に出会ったのです。
「phpIPAM」と「PowerDNS」でできること
phpIPAM
超高機能なのですが、私の利用目的としては以下が挙げられます。
WebでIPが管理できる
こんな感じでWeb上でIPの使用状況がビジュアル化できます。
もうね、これだけで感動ですよ!これまでテキトーに頭の中に入ってたIPレンジがスッキリビジュアル化してくれます!
エクセルとかでちまちま管理してた人もいるんちゃうかな。
NWスキャン機能で使用中のIPを自動的に登録できる
IPAMの標準機能だとは思いますが・・・
PINGとかTELNETを対象セグメントに発砲することで、応答したIPを自動的に登録してくれます。
非常に効率がいい!
PowerDNS
当然ながらDNSの機能
ローカル環境のDNSとして使えます。つまりBINDと同じですね。
レコードをDBに保持できる
BINDでは一般的にファイルを使用していましたが、PowerDNSの場合はバックエンドのDBにレコードを保持できます。
これによって後述のphpIPAMとの連携が簡単になっています。
phpIPAMとPowerDNSの連携でできること
ここからが、ある意味本題です。phpIPAMとPowerDNSは単体では普通のIPAMとDNSですが、組み合わせると非常に便利な運用管理ができるようになります。
IPを予約すると同時にDNSに登録!
例えばphpIPAMでIPを管理していて「このIPを新しいノードに予約しよう」と考えます。
で、phpIPAM上でIPの予約をしたタイミングで、phpIPAMのWebGUIからPowerDNSにDNSレコード登録ができます。
これまでは頭の中で「このIPは予約しとこう」と考えて、毎回BINDのOSに入ってレコード登録して、ホスト名との整合性合わせて、BIND再起動とかやってたのがアホらしくなるな。
逆引きレコードも自動登録可能
正引きだけではなく逆引き(in-addr.arpa)も自動的に登録可能。
IP管理とDNS登録が超効率的になります。
構築方法の紹介の前に、、参考にさせて頂いたサイトの紹介
以降紹介する手順ですが、殆ど私が考えたものでは「ありません」。ネットで紹介されている手順やdocker-compose.ymlを参考にしたものです。
主に参考&活用させていただいたサイトは以下の通り。
上記で「PowerDNS」と「phpIPAM」を独自に動作させることができるはずなのですが、これらを連携するときにかなりハマったので、今回の記事を書いています。
立ち上げるDockerコンテナについて
ここからが若干ややこしい話なのですが、実はPowerDNSとphpIPAMを起動させる時には、2つのDockerコンテナだけではダメなのです。
PowerDNSとphpIPAM1つずつで合計2コンテナの起動だと不十分なんか!めんどい雰囲気が出てきたな・・・
とは言え、後述するdocker-compose.ymlで全てのコンテナを立ち上げれるようにしてるのですが、将来何らかのトラシューする必要が出たとき用に、それぞれのコンテナの用途を解説します。
phpIPAM関連
phpipam-www
phpIPAMの主要コンテナです。こいつがhttpでweb guiを受け付けます。
今回のコンポーネント群で一番触るコンテナです。
PowerDNSと連動することができるので、PowerDNSの設定Guiとしても使えます。世の中にはPowerDNSの管理Guiアプリが沢山あるのですが、基本操作だけならphpIPAMのGuiでできてしまう!
phpipam-cron
phpIPAMのスキャン機能を司るcronコンテナらしい。私は直接触ったことはありません。
PowerDNS
pdns
PowerDNSの主要コンテナ。こいつが53番ポートでDNSサービスを受け付ける。
Authoritative Serverとも言われる。
pdns-recursor
地味に重要なのだが、(私みたいな)初心者には忘れられがちな存在。
ローカルDNSを建てるとき、自分のドメイン以外の問い合わせは、更に上のDNS(8.8.8.8とか)にrecurse問い合わせさせると思うのですが、その機能を担ってるのがこのサービス。
PowerDNS4.1.0以降から必須となったようです。つまり、pdnsだけだとrecurse問い合わせができないので、pdns-recursorは必須。
ネットではPowerDNS4.1.0以前の情報も多々あり「Authoritative Serverのrecurseパラメータで8.8.8.8を指定すればよい」みたいな書き方をしてるところもあるので、かなり混乱しました。
何度も言いますが、recurse問い合わせをしたい場合は(普通はしたい)必須です。
pdns slave
pdnsのslaveサーバ。
必須ではないので、今回は含めません。(含めなくても動作する)
phpIPAMとPowerDNS共通
Mariadb
phpIPAMとPowerDNSの情報を格納するDBMS。別にMariadbじゃなくても良いのですが、上記のリンクでMariaを使っていたので、そのまま流用させていただいています。
また、phpIPAMとPowerDNSでDBコンテナを分けても良いのですが、どうせ同時に使うので同じコンテナ内で2つのDBを作っています。
いよいよ実装してみよう
事前に以下は準備しておいてください。この辺はググったら情報が出るし、このブログのKubernetes周りの記事を見ても良い。
- Dockerが動く環境
- docker-composeが動く環境
あと、ラボ環境目的で作っているので、セキュリティはガバガバです。間違っても本番環境で同じパラメーターを使わないように!
基本的にこのdocker-compose.ymlを動かせば良い!
mkdir phpipam_powerdns
cd phpipam_powerdns
以下のdocker-compose.ymlを作成してください。
version: '3'
services:
phpipam-web:
image: phpipam/phpipam-www:latest
ports:
- "3030:80"
environment:
- TZ=Asia/Tokyo
- IPAM_DATABASE_HOST=phpipam-mariadb
- IPAM_DATABASE_PASS=my_secret_mysql_root_pass
- IPAM_DATABASE_WEBHOST=%
restart: unless-stopped
volumes:
- phpipam-logo:/phpipam/css/images/logo
depends_on:
- phpipam-mariadb
networks:
app_net:
ipv4_address: 172.16.238.10
phpipam-cron:
image: phpipam/phpipam-cron:latest
environment:
- TZ=Asia/Tokyo
- IPAM_DATABASE_HOST=phpipam-mariadb
- IPAM_DATABASE_PASS=my_secret_mysql_root_pass
- SCAN_INTERVAL=1h
restart: unless-stopped
depends_on:
- phpipam-mariadb
networks:
app_net:
ipv4_address: 172.16.238.11
pdns-master:
image: pschiffe/pdns-mysql
ports:
- 5300:5300/tcp
- 5300:5300/udp
environment:
- PDNS_master=yes
- PDNS_api=yes
- PDNS_api_key=secret
- PDNS_webserver=yes
- PDNS_webserver_address=0.0.0.0
- PDNS_webserver_password=secret2
- PDNS_version_string=anonymous
- PDNS_default_ttl=1500
- PDNS_soa_minimum_ttl=1200
- PDNS_allow_axfr_ips=192.168.1.90
- PDNS_webserver-allow-from=0.0.0.0/0
- PDNS_only_notify=192.168.1.90
- PDNS_gmysql_host=phpipam-mariadb
- PDNS_gmysql_port=3306
- PDNS_gmysql_user=root
- PDNS_gmysql_password=my_secret_mysql_root_pass
- PDNS_gmysql_dbname=powerdns
- PDNS_local_address=0.0.0.0
- PDNS_local_port=5300
restart: unless-stopped
depends_on:
- phpipam-mariadb
networks:
app_net:
ipv4_address: 172.16.238.12
pdns-recursor:
image: pschiffe/pdns-recursor
ports:
- 53:53/tcp
- 53:53/udp
environment:
- PDNS_allow_from=0.0.0.0/0
- PDNS_local_address=0.0.0.0
- PDNS_forward-zones-recurse=.=8.8.8.8
- PDNS_forward_zones=mylab.local=172.16.238.12:5300,1.168.192.in-addr.arpa=172.16.238.12:5300
restart: unless-stopped
depends_on:
- phpipam-mariadb
networks:
app_net:
ipv4_address: 172.16.238.13
phpipam-mariadb:
image: mariadb:latest
ports:
- 3306:3306/tcp
environment:
- MYSQL_ROOT_PASSWORD=my_secret_mysql_root_pass
restart: unless-stopped
volumes:
- phpipam-db-data:/var/lib/mysql
networks:
app_net:
ipv4_address: 172.16.238.14
volumes:
phpipam-db-data:
phpipam-logo:
networks:
app_net:
driver: bridge
ipam:
driver: default
config:
- subnet: 172.16.238.0/24
次に、「必ず」修正が必要な箇所を説明します。
- PDNS_allow_axfr_ips=192.168.1.90
- PDNS_webserver-allow-from=0.0.0.0/0
- PDNS_only_notify=192.168.1.90
ここの「192.168.1.90」はDockerホストのIPです。最終的にDockerホストの先のノードがDockerホストに対してアクセスに来るので、コンテナ内とは違う外部のIPを書く必要がある。
(間のPDNS_webserver-allow-fromは0.0.0.0/0のままでよい。セキュリティはガバガバですが)
後は、この部分。
- PDNS_forward-zones-recurse=.=8.8.8.8
- PDNS_forward_zones=mylab.local=172.16.238.12:5300,1.168.192.in-addr.arpa=172.16.238.12:5300
「PDNS_forward-zones-recurse」でrecurse問い合わせの先を指定します。もし8.8.8.8で問題なければ、変える必要はない。
「PDNS_forward_zones」はrecurse問い合わせを「しない」ドメインを記述する。つまり、自ローカルのドメインを記載する。私の自宅はたまたま「mylab.local」がドメインで、セグメントが「192.168.1.0/24」だから上記の記載になっているのです。逆引きレコードも書く。私の場合はだから「1.168.192.in-addr.arpa」もあるのです。
「172.16.238.12」とかは変えずにそのままにしておいてください。後述します。
上記のdocker-compose.ymlがあるディレクトリで以下を実行します。
docker-compose up -d
これだけで全てのコンテナが設定込みで起動するはずです。
ビバdockerやな。でもね、ここまで来るのにめっちゃ苦労したんですよ。
これで全てのコンテナが起動するはずなのですが、いくつか気になる点があると思うので、思いつく箇所を解説します。
はじめに、こちら。
MYSQL_ROOT_PASSWORD=my_secret_mysql_root_pass
MariaDBのルートのパスワードを指定しています。phpIPAMとPowerDNS共にMariaDBのルートユーザでDBを作成するので。
変える時は「全部のコンテナのパスワード文字列」を変えてくださいね。そうしないと初期起動時にDBが作れなかったりするので、一部のコンテナが起動しなくなります。
depends_on:
- phpipam-mariadb
MariaDBのコンテナが立ち上がってから、それぞれのコンテナmanifestでDBを作りたいので、depends_onを付けてます。
image: pschiffe/pdns-mysql
ports:
- 5300:5300/tcp
- 5300:5300/udp
ここがPowerDNSコンテナ立ち上げの最大のポイントです。本来なら53ポートでDNSを受け付けるはずなのに、なぜか5300で受け付けている。
なぜか?
ユーザをPowerDNSのAuthoritative Serverではなく、Recursorにアクセスさせるためです。
つまり、最終的な通信フローは
- 利用者→PowerDNS Recursor (53)
- (自ドメインの問い合わせ以外)PowerDNS Recursor→8.8.8.8
- (自ドメインの場合)PowerDNS Recursor→PowerDNS Authoritative Server(5300)
とするのです。つまり、利用者が直接PowerDNS Authoritative Serverにダイレクトでアクセスすることはありません。
これだけ見ると、BINDの方がシンプルやな。まぁ色々メリットはあるけど・・・
この通信フローはPowerDNSの公式にも明記されているので、興味がある方はどうぞ。
だから上記のmanifestでAuthoritative Server(イメージ:pschiffe/pdns-mysql)は5300で待ち受けているのです。
いいですかね?次はこちらです。
- PDNS_forward_zones=mylab.local=172.16.238.12:5300,1.168.192.in-addr.arpa=172.16.238.12:5300
Dockerに少し詳しい人ならこう言うでしょう。
なんで「172.16.238.12」とかIPベタ打ちやねん!汎用性の観点からホスト名記載の方がええやろ!例えばここではPowerDNSのAuthoritative Server(マスター)をポイントしたいんやろ?じゃあ「pdns-master:5300」でええやん!
仰るとおりです・・・できたらそうした方が良いでしょう。
でも、どうやらPowerDNSの仕様で、ホスト名記述をするとエラーになるんですよ。
上記がコマンドリファレンスなのですが、こちらでもホスト名記述は一切ありません。多分できないんだろうと思います。
だから、わざわざ全てのコンテナのローカルIPを固定してるんです。そのためだけに。
networks:
app_net:
ipv4_address: 172.16.238.12
こんなのをやってるのです。もっといい方法をご存じの方がいたら教えて下さい。
ちなみに、DockerじゃなかったらAuthoritative ServerもRecursorも同じOS上にインストールすることもできるわけでして、それだったら「127.0.0.1:5300」とかで対応もできるのです。
こんな感じですかね。まぁ色々書きましたが、基本的に上記の数行を環境毎に修正して立ち上げたらOKなはず。
私はログチェックしながら問題解決を繰り返して、めっちゃ時間かかりましたけどね!
さて、この後はphpIPAMのWebGUIを使用して設定作業をしていくのですが、その前に1点だけ追加でやる作業があります。
これです。
簡単に言うと「phpIPAMからPowerDNSのDBを触りに行く時に、初期状態のPowerDNSのDBにはないカラムを触りに行く」という非常にテンションが下がる事象があります。
なので、対応しなければphpIPAMからDNSレコードを追加する際に以下のメッセージが表示されて、追加できません。
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'change_date' in 'field list'
画面としては以下の感じ。
Google先生、早く上のキーワードでクロールして、私以外の悩める子羊がこの解決方法にたどり着きますように・・・
で、解決策ですが、MariaDBのPowerDNS用DBを直接操作してカラムを追加します。と言っても、以下を実行するだけです。
mysql -u root -pmy_secret_mysql_root_pass -h phpipam-mariadb powerdns -e "ALTER TABLE records ADD change_date int;"
私のオススメは、PowerDNS Authoritative Server用コンテナに既にmysql clientが入ってるので、そちらにbashでログインして上記コマンドを実行すること。
こんな感じで。
[centos phpipam]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
dcc362926e22 pschiffe/pdns-recursor "/docker-entrypoint.…" 26 seconds ago Up 24 seconds 0.0.0.0:53->53/tcp, 0.0.0.0:53->53/udp phpipam_pdns-recursor_1
6cdca2aed590 phpipam/phpipam-cron:latest "/sbin/tini -- /bin/…" 26 seconds ago Up 24 seconds 80/tcp phpipam_phpipam-cron_1
88bd1323c83f pschiffe/pdns-mysql "/docker-entrypoint.…" 26 seconds ago Up 24 seconds 53/tcp, 53/udp, 0.0.0.0:5300->5300/tcp, 0.0.0.0:5300->5300/udp phpipam_pdns-master_1
PowerDNS Authoritative Serverは「pschiffe/pdns-mysql」イメージのコンテなので「88bd1323c83f」だとわかります。あとはbashでログインして上記のコマンドを打つだけ。
[centos phpipam]# docker exec -it 88bd1323c83f bash
[root@88bd1323c83f /]# mysql -u root -pmy_secret_mysql_root_pass -h phpipam-mariadb powerdns -e "ALTER TABLE records ADD change_date int;"
[root@88bd1323c83f /]#
以上でややこしい箇所は終了となります。次は設定作業です。
設定作業
「http://DockerホストのIP:3030」にブラウザからアクセスします。上記に書いたとおり、私のDockerホストは「192.168.1.90」なので「http://192.168.1.90:3030」にアクセスします。
こんな画面になるので「New phpipam installation」をクリック。
「Automatic database installation」をクリック。
以下を入力して「Install phpipam database」をクリック。
- MySQL username:root
- MySQL password:my_secret_mysql_root_pass
以下を入力して「Save settings」をクリック。
- Admin password:Webguiにログインするパスワードを指定
- Site URL:初めにアクセスしたURLでよい
ログインメニューに遷移するので、上記で作ったadminパスワードでログイン。
さて、ログインできました。まず、なにより先にPowerDNSとの連携設定をすることをオススメします。(先にIPのスキャンとかしたら連携時にエラー出たので)
「Administration」→「phpIPAM Settings」を選択。
「Enable PowerDNS」を「ON」にして、一番下の「Save」。
今度は「PowerDNS」を選択。
画面の通り入力して「Save」。
HostはPowerDNS Authoritative ServerじゃなくてMariadbの「phpipam-mariadb」を指定するのがポイントね。Mariadbを直接操作するからやね。だからPasswordは「my_secret_mysql_root_pass」ね!
成功したら以下のような「Database connection ok」になるはずです。
この時点でエラーが出たら、この先の設定をやろうとしても絶対に失敗します。DBコンテナが上がっているか?ホストにMariaDBのホスト名を指定しているか?など、色々調査してください。
この時点で上記に説明したコンポーネントの役割がわかってないと、トラシューが絶望的に難しい。。
さて、成功したことを前提に話を勧めます。「Defaults」タブをクリック。
タイマーは特にこだわりなければ、とりあえず上記の画面の通り入力すれば良いかと。NameServersとかは環境に合わせてください。BINDと同じです。終わったら「Domain」タブをクリック。
これも特にこだわりなければ、上記の画面の通り。当然NameServersとかは環境に合わせてください。終わったら「Reverse V4」をクリック。
同じノリで逆引きドメインも追加する。
これでPowerDNSとの連携は完了です。次はいよいよphpIPAMでのIP管理です。
「Administration」→「Sections」をクリック。(IP related managementの中のやつ)
どうやらSectionsというのがIPサブネットを入れる器みたいなものらしい。「Add section」。
とりあえず「Name」だけ入れて「Add」。
作ったSectionが表示されるので、それを選択。(今回はHouse1を選択)
ようやくSubnetの器を作れる。「Add subnet」。
ここの設定は重要。
まず「Subnet」に管理するSubnetをCIDRで記載。私の自宅ラボは「192.168.1.0/24」セグメントなので、それを記載。次に「Check host status」と「Discover new hosts」をYesにする。これでhostを自動的に検出してくれるらしい。ついでに「Resolve DNS names」もYesにした。
もう一つ重要な設定は「Autocreate reverse records」。これをYesにすると逆引きレコードもPowerDNSに登録してくれる。終わったら「Add」。
すると、作ったサブネットが出てくるので、それをクリック。
ここからがIPAMの便利な機能をようやく使える場所!
Actionsの歯車3つをクリック。(+の右のやつ)
「Scan Subnet」をクリックすると、セグメント全体にpingしてノードを自動的に検出してくれる!
あとは「Add discovered hosts」をクリックすると・・・
こんな感じで自動的に「使用しているIP」とかを見える化してくれます!
いやー!ここまで苦労したかいがあったわ!
で、ホスト名の右の「+」を押すと。
PowerDNSにレコード追加できる画面が出るので、そのまま「Add」をクリックすると。
さっきのPowerDNSの画面に戻ったらしっかりホストが登録されています!
同じセグメントでdigしてみても、しっかりホストがPowerDNSに登録されてるのが確認できます。
(base) ➜ ~ dig @192.168.1.90 jump.mylab.local
; <<>> DiG 9.10.6 <<>> @192.168.1.90 jump.mylab.local
; (1 server found)
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 7033
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;jump.mylab.local. IN A
;; ANSWER SECTION:
jump.mylab.local. 86357 IN A 192.168.1.234
;; Query time: 14 msec
;; SERVER: 192.168.1.90#53(192.168.1.90)
;; WHEN: Thu Jul 23 00:10:09 JST 2020
;; MSG SIZE rcvd: 61
逆引きも大丈夫そう。
(base) ➜ ~ dig @192.168.1.90 -x 192.168.1.234
; <<>> DiG 9.10.6 <<>> @192.168.1.90 -x 192.168.1.234
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 38217
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;234.1.168.192.in-addr.arpa. IN PTR
;; ANSWER SECTION:
234.1.168.192.in-addr.arpa. 86400 IN PTR jump.mylab.local.
;; Query time: 13 msec
;; SERVER: 192.168.1.90#53(192.168.1.90)
;; WHEN: Thu Jul 23 00:10:40 JST 2020
;; MSG SIZE rcvd: 85
recurseも動作してるっぽい。
(base) ➜ ~ dig @192.168.1.90 www.google.com
; <<>> DiG 9.10.6 <<>> @192.168.1.90 www.google.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 23941
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;www.google.com. IN A
;; ANSWER SECTION:
www.google.com. 244 IN A 172.217.27.68
;; Query time: 17 msec
;; SERVER: 192.168.1.90#53(192.168.1.90)
;; WHEN: Thu Jul 23 00:11:06 JST 2020
;; MSG SIZE rcvd: 59
まとめ
今回はPowerDNSとphpIPAMの連携方法を紹介しました。
かなり長くなりましたが、この手順通りやれば詰まることも少ないはずなので、なれたら15分くらいで終わる作業です。
ここまで持ってくるのにかなり時間かかりましたけどね!
ちなみにdocker-compose upの後に色々設定をミスってやり直したい場合、以下のコマンドでdocker-compose upした内容を全てリセットできます。
docker-compose down --rmi all --volumes
ゴミファイルが作成されないってのもDockerを使用するメリットですね。
コメント