自宅 k8s クラスタのサービスに FQDN で繋がるようにした
自宅の検証用マシン (Deskmini A300) に ESXi を入れて検証環境として利用しています。
最近はそこへ k8s クラスタを構築して色々試しているのですが、クラスタ内に立ち上げたサービスへは IP アドレスでアクセスしていました。 IP アドレスでアクセスするのはとても面倒だったのですが、やっと k8s で動かしているサービスに FQDN で繋がるようになったので投稿します。
システム構成図
完成後のシステム構成図になります。 (構成図描くの下手で分かりにくいと思います…)
見てもらって分かる通り、LAN 用の DNS サーバを VM で建てています (k8s クラスタの外です)。 これは、k8s クラスタを構築する前に DNS サーバ (dnsmasq) を構築していて、それを流用しているためです。
使用するプロダクト
今回利用しているプロダクトは以下になります。
名前 | バージョン | 用途 |
---|---|---|
Kubernetes | 1.19.0 | コンテナオーケストレーション |
Docker | 19.03.12 | コンテナランタイム |
ExternalDNS | 0.7.3 | DNS プロバイダに DNS を登録 |
CoreDNS | 1.8.0 | DNS サーバ |
etcd | 3.4.13 | DNS レコード格納 |
MetalLB | 0.9.3 | ベアメタルロードバランサー |
構築
以下の手順で環境を構築しました。
- DNS サーバ (CoreDNS & etcd) 構築
- MetalLB デプロイ
- ExternalDNS デプロイ
DNS サーバ (CoreDNS & etcd) 構築
CoreDNS & etcd を使った DNS サーバを構築します。 この DNS サーバに k8s クラスタのサービス用の DNS レコードを格納していきます。 なぜ dnsmasq を流用しないかというと、後述する ExternalDNS が dnsmasq に対応していないからです。
今までは dnsmasq を yum でインストールしていたのですが、今回からは Docker コンテナを使っていきます。 docker のインストールは省略します。 (Docker Compose を使えばよかったかも)
コンテナネットワーク
CoreDNS コンテナから etcdコンテナへ繋がるようにするために dns-network
という名前の bridge を作成しておきます。
$ docker network create --driver bridge dns-network
CoreDNS
CoreDNS とは、Go 言語で書かれた DNS サーバで、 CNCF によってホストされているプロジェクトです。 プラグイン形式で DNS レコードの格納/参照先を柔軟に設定できるところが売りなようです。
今回は etcd プラグインと hosts プラグインを使って、「k8s のサービスは etcd、その他は /etc/hosts に直書き」という処理をさせます。
まずは CoreDNS の設定ファイル Corefile
を準備します。
仮に example.com
をローカルで使用するドメインとして、example.com
の問い合わせは以下の順番で確認するようにします。
- etcd (k8s サービス用)
- /etc/hosts
example.com
以外の問い合わせは /etc/resolv.conf
を参照して外部の DNS サーバに回します。
#Corefile example.com { etcd { path /skydns endpoint http://etcd:2379 fallthrough } hosts cache errors log } . { forward . /etc/resolv.conf }
次に、CoreDNS コンテナを起動します。
先ほど作成した Corefile
と /etc/hosts
をマウントしています。
$ docker run -d \ --name coredns \ --network dns-network \ -v /home/nnstt1/Corefile:/root/Corefile \ -v /etc/hosts:/etc/hosts \ -p 53:53/udp \ -p 53:53/tcp \ coredns/coredns:1.8.0 -conf /root/Corefile
etcd
etcd とは、こちらも Go 言語で書かれた分散 KVS (Key Value Store) です。 今回は CoreDNS で管理する DNS レコードの格納先としてシングルノードで動かします。
こちらは設定ファイルの用意はなく、etcd コンテナを起動するだけです。
$ mkdir -p /tmp/etcd-data.tmp $ docker run -d \ -p 2379:2379 \ -p 2380:2380 \ --mount type=bind,source=/tmp/etcd-data.tmp,destination=/etcd-data \ --name etcd \ --network dns-network \ gcr.io/etcd-development/etcd:v3.4.13 \ /usr/local/bin/etcd \ --name s1 \ --data-dir /etcd-data \ --listen-client-urls http://0.0.0.0:2379 \ --advertise-client-urls http://0.0.0.0:2379 \ --listen-peer-urls http://0.0.0.0:2380 \ --initial-advertise-peer-urls http://0.0.0.0:2380 \ --initial-cluster s1=http://0.0.0.0:2380 \ --initial-cluster-token tkn \ --initial-cluster-state new \ --log-level info \ --logger zap \ --log-outputs stderr
動作確認
CoreDNS
と etcd
のコンテナが両方とも起動して、DNS サーバとして動作するか確認します。
まずはコンテナが起動しているか。
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 8064ae23c30c gcr.io/etcd-development/etcd:v3.4.13 "/usr/local/bin/etcd…" 2 hours ago Up 2 hours 0.0.0.0:2379-2380->2379-2380/tcp etcd e0bce2b55549 coredns/coredns:1.8.0 "/coredns -conf /roo…" 17 hours ago Up 17 hours 0.0.0.0:53->53/tcp, 0.0.0.0:53->53/udp coredns
コンテナ起動していたら、「etcd へ DNS レコードを登録できるか」「登録した DNS レコードを参照できるか」を確認します。
# etcd への DNS レコード登録 (OK が返ってくれば成功) $ docker exec -it etcd etcdctl put /skydns/com/example/hoge '{"host":"192.168.1.254","ttl":3600}' OK # DNS レコード問い合わせ (上記で設定したアドレスが返ってくれば成功) $ dig +short hoge.example.com @localhost 192.168.2.254
無事に etcd を使った DNS サービスが動作しているようです。
次に、/etc/hosts が参照できるか確認します。
# /etc/hosts へレコード追加 $ sudo sh -c "echo '192.168.1.253 fuga.example.com' >> /etc/hosts" # DNS レコード問い合わせ (上記で設定したアドレスが返ってくれば成功) $ dig +short fuga.example.com @localhost
こちらは期待した結果が返ってきませんでした。
どうやら /etc/hosts
の編集内容がコンテナのほうに同期されていないようです。
理由は分かっていないのですが、コンテナを再起動することで反映されました。
$ docker restart coredns coredns $ dig +short fuga.example.com @localhost 192.168.1.253
これで DNS サーバの構築が完了しました。
MetalLB
MetalLB とは、AKS や GKE などのマネージド k8s と同じようにベアメタル k8s クラスタでも type: LoadBalancer
の Service リソースを使えるようにしてくれるロードバランサーの実装です。
k8s クラスタに MetalLB をデプロイすることで、type: LoadBalancer
のリソースに対して自動的に外部 IP アドレスを払い出してくれます。
今回は MetalLB のデプロイ方法は省略します。
ExternalDNS
いよいよ本命の ExternalDNS です。 ExternalDNS を利用することで、Kubernetes の Service や Ingress で公開される IP アドレスを Kubernetes クラスタ外部の DNS プロバイダに自動登録して、FQDN で Kubernetes クラスタ内のサービスにアクセスできるようになります。
執筆時点の v0.7.3
で選択できる DNS プロバイダ は 27 個あります。
Azure DNS や Route 53 など、対応している DNS プロバイダは他にもあるのですが、オンプレに構築していみたいという目的から CoreDNS を選択しました。
デプロイ
DNS プロバイダ毎にチュートリアルが用意されています。 CoreDNS 用のチュートリアルはこちら。
CoreDNS と etcd は既に構築しているため ExternalDNS のデプロイのみ参照しました。 以下のマニフェストをデプロイします。
--- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRole metadata: name: external-dns rules: - apiGroups: [""] resources: ["services","endpoints","pods"] verbs: ["get","watch","list"] - apiGroups: ["extensions","networking.k8s.io"] resources: ["ingresses"] verbs: ["get","watch","list"] - apiGroups: [""] resources: ["nodes"] verbs: ["list"] - apiGroups: [""] resources: ["endpoints"] verbs: ["get","watch","list"] --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: external-dns-viewer roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: external-dns subjects: - kind: ServiceAccount name: external-dns namespace: kube-system --- apiVersion: v1 kind: ServiceAccount metadata: name: external-dns namespace: kube-system --- apiVersion: apps/v1 kind: Deployment metadata: name: external-dns namespace: kube-system spec: strategy: type: Recreate selector: matchLabels: app: external-dns template: metadata: labels: app: external-dns spec: serviceAccountName: external-dns containers: - name: external-dns image: k8s.gcr.io/external-dns/external-dns:v0.7.3 args: - --source=service - --provider=coredns - --log-level=debug env: - name: ETCD_URLS value: http://192.168.2.3:2379
これにより、namespace: kube-system
で ExternalDNS の Pod が動くようになりました。
アノテーション付与
ExternalDNS で対象とする Service リソースにアノテーションを付与します。 これにより、ExternalDNS がアノテーション内のホスト名と Service が持つ 外部 IP アドレスを CoreDNS へ登録してくれるようになります。
$ kubectl annotate svc <svc_name> "external-dns.alpha.kubernetes.io/hostname=<svc_name>.example.com." service/<svc_name> annotated
動作確認
DNS サーバ構築時と同じく dig で確認します。 サンプルで動かしている WordPress と独自ドメインを対象にしてみます。
# Service 確認 $ kubectl get svc -n wordpress NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE wordpress LoadBalancer 10.110.128.227 192.168.2.247 80:30446/TCP 56d wordpress-mysql ClusterIP None <none> 3306/TCP 56d # アノテーション付与 $ kubectl annotate svc wordpress "external-dns.alpha.kubernetes.io/hostname=wordpress.nnstt1.work." -n wordpress service/wordpress annotated # dig $ dig +short wordpress.nnstt1.work 192.168.2.247
無事に外部 IP アドレスを参照することができました。 キャプチャは無いのですが、別マシンのブラウザから WordPress を表示することもできました。
これで、IP アドレスを使わずに FQDN で k8s クラスタのサービスにアクセスすることができるようになりました。
おわりに
以上が k8s クラスタのサービスに FQDN で繋がるようにした手順です。
ExternalDNS を使うことがメインの目的だったのですが、CoreDNS と etcd を触る機会も得られました。
ExternalDNS も思ってたより簡単に導入できたので、同じような悩みを持っている方はぜひ検討してみてください。
アノテーション付与を手動でする必要がある、という課題があるので、今後は自動的にアノテーションを付与する仕組みを調べたいと思います。