自宅の検証用マシン (Deskmini A300) に ESXi を入れて検証環境として利用しています。
最近はそこへ k8s クラスタを構築して色々試しているのですが、クラスタ内に立ち上げたサービスへは IP アドレスでアクセスしていました。 IP アドレスでアクセスするのはとても面倒だったのですが、やっと k8s で動かしているサービスに FQDN で繋がるようになったので投稿します。
システム構成図
完成後のシステム構成図になります。 (構成図描くの下手で分かりにくいと思います…)
[f🆔nnstt1:20201113012536p:plain]
見てもらって分かる通り、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 用のチュートリアルはこちら。
[https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/coredns.md:embed:cite]
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 も思ってたより簡単に導入できたので、同じような悩みを持っている方はぜひ検討してみてください。
アノテーション付与を手動でする必要がある、という課題があるので、今後は自動的にアノテーションを付与する仕組みを調べたいと思います。