この記事は ConoHa Advent Calendar 2019 20日目の記事です。
GMOホスコン ユーザーミートアップ!で話した内容を再編集したものになります。
はじめに
同じConoHaアカウント内に存在するインスタンスのIPアドレスをシュッと引きたくないですか?引きたいです。
たとえば[nametag].conoha
のようなホスト名でインスタンスのIPアドレスを引けるようなったら、いろいろ活用方法がありそうです。
ということで、これをどう実現するかを考えてみます。
選択肢
/etc/hostsに書いておく
- Pros
- 最もシンプル
- トラブルシュートが楽そう
- Cons
- インスタンスを増減させるたびに更新する手間がある
- すべてのインスタンスにhostsを配布する手間がある
- 別途仕組み化する必要がありそうです
内部DNSを立てる
- Pros
- hostsと比較すると、インスタンスが増減した際にDNSサーバ側だけ変更すれば良くなる
- Cons
- インスタンスを増減させるたびに更新する手間がある
- そもそもDNSを管理したくない
Consulやそれに準ずるツールを使う
一番現実的な選択肢に思えます。商用環境でもよく利用されています。
- Pros
- インスタンスを増減させても手放しでいける
- Cons
- Consulクラスタを運用管理する手間
mDNS (avahi) を使う
ローカルネットワーク内でのIPアドレスに限れば、シンプルで良さそうな選択肢に見えます。
- Pros
- インスタンスを増減させても手放しでいける
- Cons
- 採用事例が少なそう
- トラブルシュートが大変そう
Name Service SwitchとAPI(ConoHa API)を組み合わせる
これはIaaS側のAPIや、IPMIなんかでマシンのIPアドレスを拾ってこれる場合に利用できそうです。
- Pros
- インスタンスを増減させても手放しでいける
- Cons
- 採用事例がない
- トラブルシュートが大変
Consが最悪ですが、今回はこの方法でどの様に冒頭に述べたアレを実現するかを考えていきます。
Name Service SwitchとConoHa API
Name Service Switch (NSS)
Name Service Switch とは、UNIX系OSにおいて、システムが使用する各種情報の検索順を指定するために使用される。NSSと略されることが多い。たとえば、ユーザ情報は、/etc/passwd、NIS、LDAPなどに格納されているが、システムに問い合わせられたユーザ情報をどの情報源から、どの順番で検索するかを指定する。
(Wikipediaより)
といったような、システムが使う情報のプロバイダを切り替えたり、新たに定義したりできる仕組みのことです。
UNIXやそれに準ずるシステムではだいたい使われています。
ユーザー情報以外にも、ホスト名に対応するアドレスのルックアップでもNSSが利用されており、先程例に上げた/etc/hosts
やmDNSも、実はこのNSSを介して情報が取得されています!
大抵は、たとえばmdnsならばlibnss-mdnsのようなモジュールをどこからか拾ってきて導入して使う、という場合が多いです。
このような情報を提供する側のプログラムを自分で作成することはあまりないのですが、本記事ではこのモジュールを自作してしまおう!という目論見です。
ConoHa API
ConoHa VPSを利用されている方にはおなじみかと思いますが、ConoHaはOpenStackベースであり、これに準じたAPIが提供されています。
もちろん、ここからインスタンスに紐ついたIPアドレスも取得することができます。
したがって、ホスト名からアドレスを引く要求を受け取ったときにConoHa APIを叩いてアドレスを取得し、これを返答するようなNSSモジュールを作成すれば、当初の目標が達成できそうです。
NSSモジュールを書いてみる
👇のドキュメントが詳しいです。
https://www.gnu.org/software/libc/manual/html_node/Extending-NSS.html
もろもろを端折ってざっくり解説してしまうと、
enum nss_status _nss_hoge_gethostbyname_r (const char *name, struct hostent *ret, char *buf, size_t buflen, int *errnop, int *h_errnop);
enum nss_status _nss_hoge_gethostbyname2_r (const char *name, int af, struct hostent *ret, char *buf, size_t buflen, int *errnop, int *h_errnop);
といったようなインタフェースを持つ関数を含んだ共有ライブラリを特定の場所にインストールすると、gethostbyname(3)の呼び出しをフックできる、と考えるとわかりやすいかと思います。
たとえば、****.hoge
という問い合わせに対して、仮にアドレス1.14.5.14
を回答するNSSモジュールは以下のように書けます。
name
が問い合わせを受けているホスト名です。
enum nss_status _nss_hoge_gethostbyname2_r (const char *name, int af, struct hostent *ret, char *buf, size_t buflen, int *errnop, int *h_errnop) {
*errnop = *h_errnop = 0;
if (af != AF_INET) {
return NSS_STATUS_NOTFOUND;
}
if (strstr(name, ".hoge") == NULL) {
return NSS_STATUS_NOTFOUND;
}
ret->h_name = (char*)name;
ret->h_aliases = calloc(sizeof(char*), 1);
ret->h_addrtype = af;
ret->h_length = 4;
ret->h_addr_list = calloc(sizeof(char*), 2);
unsigned char *addr = ret->h_addr_list[0] = calloc(sizeof(char), 4);
addr[0] = 1; addr[1] = 14; addr[2] = 5; addr[3] = 14;
return NSS_STATUS_SUCCESS;
}
C言語がつらい件
わかる。Golangで書きたいですよね。
先述したインタフェースを持った共有ライブラリとして出力できればいいので、cgoをいい感じに使えばできます。
go build -buildmode=c-shared
としてビルドすればOKです。
👇ここにサンプルがありますので、ご覧ください。
https://github.com/kaz/cgo-nss
ConoHa APIと統合する
Compute APIのこの辺を叩くと、.servers[].addresses[].addr
にIPアドレスが入っています。
ローカルネットワークでのIPアドレスを返すか、グローバルのアドレスを返すか、はたまた問い合わせホスト名を工夫してどちらかを返すか……などはがんばりどころですが、今回は横着してざっくり全部のアドレスを返す実装にします。
実装したものがこちらになります。
https://github.com/kaz/libnss_conoha_api
Golangで実装しています。外部のAPIと通信して、JSONをデコードして……というのはCだとけっこうつらいので、このあたりはGoだとシュッと書けて捗ります。
使ってみる
ビルドして/lib
に放り込んで……
[root@25ad398d1f2 libnss_conoha_api]$ export NSS_CONOHA_REGION=tyo1
[root@25ad398d1f2 libnss_conoha_api]$ export NSS_CONOHA_TENANT_ID=*****
[root@25ad398d1f2 libnss_conoha_api]$ export NSS_CONOHA_USERNAME=*****
[root@25ad398d1f2 libnss_conoha_api]$ export NSS_CONOHA_PASSWORD=*****
[root@25ad398d1f2 libnss_conoha_api]$ make
go build -buildmode=c-shared -o libnss_conoha.so.2 *.go
strip libnss_conoha.so.2
install -m 0755 libnss_conoha.so.2 /lib
/etc/nsswitch.conf
を編集して、作ったモジュールを読み込むようにします。
(hosts:
の行に追加しました。)
# Name Service Switch configuration file.
# See nsswitch.conf(5) for details.
passwd: files mymachines systemd
group: files mymachines systemd
shadow: files
publickey: files
hosts: conoha files mymachines myhostname resolve [!UNAVAIL=return] dns
networks: files
protocols: files
services: files
ethers: files
rpc: files
netgroup: files
すると……
ちゃんとConoHaのネームタグからIPアドレスが引けています!
おわりに
NSSを使うとサクッとOSの機能を拡張できて便利ですね!
今回のhostname
以外にもユーザやグループもNSSで拡張できたりするので、夢が広がります。