controller-managerはクラスタ内DNSを使わない

## はじめに
訳あって`controller-manager`のコードをいじろうとしていた。
動作確認をするが、なぜか名前解決がうまくいかない...

---
## 環境
k8sのソースコード(v1.26.8)をローカルで編集して、kindで動作確認をしています。
```
$ kind version
kind v0.20.0 go1.20.4 linux/amd64
```


## 問題点

`controller-manager`内からクラスタ内のService名でアドレスを解決しようとするが、DNS周りでエラーになります。

名前解決したいService
```
$ kubectl get service -n SVC-NS
NAME       TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
SVC-NAME   ClusterIP   10.96.114.192   <none>        8080/TCP   16h
```

`controller-manager` Podのログ
```
dial tcp: lookup SVC-NAME.NS-NAME.svc.cluster.local on [2001:4860:4860::8844]:53: read udp [fc00:f853:ccd:e793::2]:43612->[2001:4860:4860::8844]:53: i/o timeout
```
IPv6で名前解決しようとしてるようだし、クラスタ内DNS(coredns)ではなくGoogleのDNSに問い合わせてるし、よく分からないです。

## 結論
`controller-manager`は通常作成されるPodとは異なる`resolv.conf`を持っており、クラスタ内DNS(`10.96.0.10`)が利用されていません。
controller-managerコンテナの`resolv.conf`を書き換えることでクラスタ内のServiceの名前解決に成功しました。


## 原因調査
原因を切り分けるために、別のPod内のシェルから名前解決を試してみます。
```bash
$ kubectl run tmp-curl --image curlimages/curl -it -- nslookup SVC-NAME.NS-NAME.cluster.local
Server:         10.96.0.10
Address:        10.96.0.10:53


Name:   SVC-NAME.NS-NAME.svc.cluster.local
Address: 10.96.114.192    # 解決できている
```
こちらは普通に解決できています。


もしかしたらNetworkPolicy的なものが関連していて、namespaceによって異なるかもと思い`kube-system` ns内でも同様のことをしたが、こちらでも名前解決できた。

ちなみに、通常Podのデバッグをするときはephemeral containerを追加してデバッグすることも多いですが、static podにはephemaral containerを設定できないらしく、以下のようにエラーになります。（[公式の説明](https://kubernetes.io/docs/tasks/configure-pod-container/static-pod/)）
```bash
$ kubectl debug -n kube-system kube-controller-manager -it --image=curlimages/curl -- curl SVC-NAME.NS-NAME.svc.cluster.local
Defaulting debug container name to debugger-gkv6z.
The Pod "kube-controller-manager" is invalid: []: Forbidden: static pods do not support ephemeral containers
```

`resolv.conf`を確認しようとしましたが`controller-manager` コンテナにはシェルが入っていないため、Goのコードから確認します。
```plaintext:controller-manager内のresolv.conf
nameserver 172.18.0.1
nameserver 2001:4860:4860::8888
nameserver 2001:4860:4860::8844
options edns0 trust-ad ndots:0
```

参考：通常のPodのresolv.conf
```plaintext:通常のPodのresolv.conf
# cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10
options ndots:5
```

ちなみに、同じstatic podである`kube-proxy`の`resolv.conf`も`controller-manager`と同様でした。


ここで重要なのがcoredns(kube-dns)のServiceである`10.96.0.10`がnameserverとして含まれておらず、代わりに先ほどのエラーメッセージ中にあったGoogleのパブリックDNSが指定されていることです。

これで原因が判明しました。**`controller-manager`コンテナではそもそもクラスタ内DNSが設定されていない**ようでした。

そもそもstatic podはクラスタ内DNSよりも先に作成されるコンポーネントなので、クラスタ内DNSに依存した処理は想定してないのでしょう。
（ということは、自分が今やろうとしていることって筋が悪いのか？(･∀･;)）

ちなみに、`172.18.0.1`はDockerが作成するネットワークのデフォルトゲートウェイだそうです。


## 対処
原因は分かりましたが、なんとかしてクラスタ内DNSを使うようにさせたいです。

kindで作成されるデフォルトの`controller-manager`はシェルも入っていないため、Goのプログラムからresolv.confを書き換える方法をやってみます。


以下のようなコードをコントローラ内に挿入。
```go
resolvconf := `search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10
options ndots:5
`
fp, err := os.Create("/etc/resolv.conf")
_, err = fp.Write([]byte(resolvconf))
```
これでビルドして実行します。

成功しました。（特に出力はないです）
一部変更前の`resolv.comf`が使用されていてリクエストが失敗しているログもありましたが、そのあとは成功しているようでした。


ちなみに、対処法としてはを他に以下のような方法が考えられました。
1. configMapでresolve.confを与えて`controller-manager`からマウントする
2. static podのyamlファイルを編集して`dnsPolicy`, `nameservers`で設定する
3. Goのhttpライブラリで明示的にリゾルバとして`10.96.0.10`を使うようにする
4. kindのクラスタ作成時にkubeadmのconfigでPatchを当てるようにする

1, 2の方法は前述のstatic podの制約より、Node上から直接編集する必要がありそうでクライアント(e.g. kubectl, k9s)からの編集ができなさそうなため、やめておきました。
3は、コンテナ全体のhttp, grpcクライアントに対してリゾルバを設定するのが難しそうでした。
4はkind, kubeadmとの親和性は高いですが少し工数が高そうな気がしたので今回は見送りました。


## おわりに

static podはクラスタ内のDNSを使わないとのこと、k8s詳しい人でも知らない人が多かったんじゃないかと思います。

これからk8sをビルドする時には気をつけようと思います。