Dockerのネットワーク周りで詰まった
【追記】
解決できた。解決策4のextra_hostsの方法で行けた。
状況
現在、Hadoopクラスタ構築を行うAnsibleのcollectionを作っており、その動作確認としてDockerコンテナをHadoopの各ノードにしてテストをしていた。
コンテナは以下のような感じで、NameNode1台、DataNode1台、KDC1台となっている。それぞれhadoopという名前の仮想ブリッジに割り当てられており、互いにコンテナ名(ホスト名)での名前解決が可能となっている。
# docker-compose.yaml
services:
hadoop-namenode1:
build:
context: ./docker
dockerfile: ./Dockerfile.hadoop
hostname: hadoop-namenode1
container_name: hadoop-namenode1
command: tail -f /dev/null
networks:
- hadoop
hadoop-datanode1:
build:
context: ./docker
dockerfile: ./Dockerfile.hadoop
hostname: hadoop-datanode1
container_name: hadoop-datanode1
command: tail -f /dev/null
networks:
- hadoop
kdc:
build:
context: ./docker
dockerfile: ./Dockerfile.kdc
container_name: kdc
environment:
KDC_DATABASE_PASSWORD: "databasepassword"
volumes:
- ./files/keytab:/tmp/keytab
entrypoint: ["/entrypoint.sh"]
networks:
- hadoop
networks:
hadoop:
name: hadoop
driver: bridge
このHadoopクラスタはKerberos認証を有効にしており、KDCコンテナへNameNode、DataNodeコンテナがそれぞれ認証のリクエストを投げるようになっている。
問題
ここで問題となったのが、チケットを発行するときのホスト名だった。
Kerberos認証ではTGT(Ticket Granting Ticket)とTGTから生成されるチケットの2種類がある。まず、ユーザがプリンシパルのパスワードなどを使ってTGTを取得し、そのTGTを使って接続したいサーバ用のチケットを発行してもらう。
今回のDockerを使ったテストでは、Keytabファイルを使った認証とTGTの取得はうまく行ったものの、そこからサーバ用のチケットを取得しに行くところでコケていた。
具体的には、dn/hadoop-datanode1@TESTというプリンシパルをKDCに登録し、そのKeytabファイルをDataNodeコンテナに置いた。そのファイルからプリンシパルの認証を行いTGTを取得し、NameNodeコンテナに接続しに行くためのチケットを取得するためのリクエストで、なぜかnn/hadoop-namenode1.hadoop@TESTというようにドメインがかかっているような状態になってしまった。
KDCのログをみるとこんな感じになっている。
Dec 03 13:57:42 82daf10d850c krb5kdc[40](info): TGS_REQ (2 etypes {aes256-cts-hmac-sha1-96(18), aes128-cts-hmac-sha1-96(17)}) 172.20.0.2: LOOKING_UP_SERVER: authtime 0, etypes {rep=UNSUPPORTED:(0)} dn/hadoop-datanode1@TEST for nn/hadoop-namenode1.hadoop@TEST, Server not found in Kerberos database
解決策
結論から言うと、解決策を見つけられなかった。
(追記)解決できた。
まず、TGTの認証を行うときのプリンシパルはHadoopでは以下のようにファイルに設定を記載する。そのときに"_HOST"の部分にマシンのホスト名が自動的に挿入されるようになっている。
<property>
<name>dfs.datanode.kerberos.principal</name>
<value>dn/_HOST@HOME</value>
</property>
しかし、TGTを使ったサーバ用のチケット要求にはホスト名にDockerブリッジ名を足したものが使われており、どうしてもTGTとサーバ用のチケットの際のプリンシパルが食い違ってしまう。
以下のようなことを試したが、いずれも効果がなかった。
(追記)解決策4でなんと解決できた(ちょっと微妙な方法だが)
1. docker-composeのhostnameにブリッジ名を追加
docker-compose.yamlのhostnameはもともとhadoop-datanode1のように、dockerブリッジ名が含まれていない。しかし、dockerでは名前解決の際に自動的にたされてしまっているっぽい?ので、それであればhostnameのほうにはじめから足してしまおうというもの。
結果は、サーバ用のチケット要求の際にドメイン名が二重になったような形でリクエストがきて失敗した。
2. network_modeをhostにする
dockerブリッジをなくせばいいのでは、という安直な発想から、各コンテナをホストのネットワークに直接つなげる用に設定(network_modeをhost)にした。
結果は、そもそもコンテナ名で名前解決できなくなったしまって失敗だった。力技でそれぞれの/etc/hostsに追加するとかもできなくはないが、それはもうDockerを使う意味がないような気もする。
3. networkをデフォルトに変えてみる
docker-compose.yamlでコンテナに紐付けるネットワークを指定しているが、その指定の項目を消してデフォルトのブリッジに紐づくようにしてみた。
こちらも結果は効果がなく、単純に追加されるドメインがhadoopからdefaultのような名前に変わっただけだった。
4. extra_hostsに追加してみる(解決できた)
extra_hostsという、/etc/hostsに直接追加できるオプションがあり、それを使って解決できた。docker-compose.yamlを以下のような感じにして、NameNode、DataNodeコンテナのIP固定をしつつ、それぞれのextra_hostsに互いのホスト名とIPを記載したら、そのホスト名でサーバ用のチケット要求をしてくれるようになった。
services:
hadoop-namenode1:
build:
context: ./docker
dockerfile: ./Dockerfile.hadoop
hostname: hadoop-namenode1
container_name: hadoop-namenode1
command: tail -f /dev/null
networks:
hadoop:
ipv4_address: 172.21.0.10
extra_hosts:
- "hadoop-datanode1: 172.21.0.11"
hadoop-datanode1:
build:
context: ./docker
dockerfile: ./Dockerfile.hadoop
hostname: hadoop-datanode1
container_name: hadoop-datanode1
command: tail -f /dev/null
networks:
hadoop:
ipv4_address: 172.21.0.11
extra_hosts:
- "hadoop-namenode1: 172.21.0.10"
kdc:
build:
context: ./docker
dockerfile: ./Dockerfile.kdc
container_name: kdc
environment:
KDC_DATABASE_PASSWORD: "databasepassword"
volumes:
- ./files/keytab:/tmp/keytab
entrypoint: ["/entrypoint.sh"]
networks:
hadoop:
networks:
hadoop:
driver: bridge
name: hadoop
ipam:
driver: default
config:
- subnet: 172.21.0.0/16
/etc/hostsの優先度が低い方法でDNS逆引きをしているようなものだとこれじゃ通用しないが、HadoopのKerberos認証ではこれで問題なく行けた。コンテナでIP決め打ちでやっているのはちょっと微妙な方法な気もするが、動作確認目的なので気にしないことにする。
おそらく、根本的な原因はKerberosのプリンシパルの名前にあると思う。
マシンからみたホスト名と外からみたそのマシンのホスト名が変わることはあると思うが、そのホスト名がどちらも同じIPを指していれば通常問題になることは少ないと思う。
しかし、Kerberosではその部分が完全一致している必要があり、そういった場合でも問題になる。今回はdockerのブリッジがそうなってしまった。
調べてみると、ほぼおなじ問題で困っている人がいるが解決策はなさそうだ。(これも返信などをみるとKerberosでの問題らしい)