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での問題らしい)

いいなと思ったら応援しよう!