使用k8s externel-dns自動更新BIND DNS
2020.02
一,需求背景在 Kubernetes 的管理中,服務名稱的解釋在 cluster 內幾乎不需要操心就能直接使用。然而,很多時候,我們建置好的服務是提供給 cluster 之外的用戶端作存取的,一般來說用戶端都以 DNS 名稱來存取服務。倘若每一筆服務都要人工方式更新 DNS 紀錄的話,這顯然不能滿足當今的營運需求。
為此,我們可以從
https://github.com/kubernetes-sigs/external-dns 取得 External DNS for Kubernetes 這一工具幫我自動完成 DNS 的更新。
二,External DSN 簡介 External DNS 可以將 K8S 中的 Service 與 Ingress 這兩種資源的名稱解釋同步到外部的 DNS 伺服器。也就是說,當資源產生的時候自動新增 hostname 與 IP 的記錄,並在資源刪除的時候一併移除 DNS 記錄。
External DNS 支援的外部 DNS 非常多,幾乎網際網路上各家提供動態 DNS 更新服務的服務商都在其支援之列,例如:
- AWS Route 53
- Google Cloud DNS
- AzureDNS
- CloudFlare
- Dyn
- ….
早期的 external-dns 版本並不支援 BIND,好消息是最新的版本提供了一個 RFC2136 的 provider 讓我們用 dns-key 來更新 BIND DNS 伺服器了!(Microsoft DNS也是支援的,但目前沒有提供連線加密的方式)
三,實作環境本文章將示範如何利用 External DNS 將 K8S 的服務資源名稱同步到不在 cluster 中的 Linux BIND 伺服器:
- Linux: CentOS Linux release 7.6.1810 (Core)
- BIND: bind-9.9.4-74.el7_6.1.x86_64
- IP: 192.168.100.1
K8S Cluster則是使用 kubeadm建置完成:
四,設定 BIND 的 dns update 功能4.1 產生 dns key:
cd /var/named/dynamic
dnssec-keygen -a hmac-sha256 -b 128 -n HOST externaldns-key
chown named.named Kexternaldns-key.*
cat Kexternaldns-key.*.key | awk '{print $NF}'
確定輸出類似 y+gUcHxLWqzg3JcBU2bbgw== 的結果,並複製結果。
4.2 修改 named.conf,末尾處增加如下內容:
key "externaldns-key" {
algorithm hmac-sha256;
secret "y+gUcHxLWqzg3JcBU2bbgw==";
};
zone "k8s.example.org" {
type master;
file "/var/named/dynamic/named.k8s.example.org";
allow-transfer {
key "externaldns-key";
};
update-policy {
grant externaldns-key zonesub ANY;
};
};
注意:secret 內容請用複製的key貼上。
4.3 建立 zone file (/var/named/dynamic/named.k8s.example.org)內容:
$TTL 60 ; 1 minute
@ IN SOA k8s.example.org. root.k8s.example.org. (
16 ; serial
60 ; refresh (1 minute)
60 ; retry (1 minute)
60 ; expire (1 minute)
60 ; minimum (1 minute)
)
NS ns.k8s.example.org.
ns A 192.168.100.1
注意:BIND server 的 IP 請修改實際 IP。
4.4 重新啟動 named 服務:
systemctl restart named
systemctl status -l named
確定沒有 error,並且 k8s.example.org 的 serial 是正確的。
五,建置 metallb Load Balancer (Optional)# 如果 K8S Cluster 已經有可用的 Load Balancer 可略過此一步驟。
5.1 下載 metallb yaml 並套用:
wget https://raw.githubusercontent.com/google/metallb/v0.7.3/manifests/metallb.yaml
sed -i '/^apiVersion: apps/s/beta2//' metallb.yaml
kubectl apply -f metallb.yaml
因為我們這裡的 k8s 版本已經升級到 v1.16, 因此需要調整 api 的版本。若您的環境是 v1.15 或之前的版本, 請略過 sed 那行指令不要執行。
5.2 建立 metallb configmap (metallb_configmap.yaml),其內容如下:
apiVersion: v1
kind: ConfigMap
metadata:
namespace: metallb-system
name: config
data:
config: |
address-pools:
- name: my-ip-space
protocol: layer2
addresses:
- 192.168.100.240-192.168.100.249
請將 ip range 修改爲實際的網段,這是分配給 k8s service 資源用的 IP。完成後套用即可:
kubectl apply -f metallb_configmap.yaml
如果沒有 error 就表示就緒了。
六,佈署 external-dns6.1 撰寫yaml (external-dns.yaml),其內容如下:
apiVersion: v1
kind: Namespace
metadata:
name: external-dns
labels:
name: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: external-dns
namespace: external-dns
rules:
- apiGroups:
- ""
resources:
- services
verbs:
- get
- watch
- list
- apiGroups:
- extensions
resources:
- ingresses
verbs:
- get
- list
- watch
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-dns
namespace: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: external-dns-viewer
namespace: external-dns
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: external-dns
subjects:
- kind: ServiceAccount
name: external-dns
namespace: external-dns
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
namespace: external-dns
spec:
selector:
matchLabels:
app: external-dns
template:
metadata:
labels:
app: external-dns
spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.opensource.zalan.do/teapot/external-dns:v0.5.17
args:
- --provider=rfc2136
- --registry=txt
- --txt-owner-id=k8s
- --source=service
- --source=ingress
- --domain-filter=k8s.example.org
- --rfc2136-host=192.168.100.1
- --rfc2136-port=53
- --rfc2136-zone=k8s.example.org
- --rfc2136-tsig-secret=y+gUcHxLWqzg3JcBU2bbgw==
- --rfc2136-tsig-secret-alg=hmac-sha256
- --rfc2136-tsig-keyname=externaldns-key
- --rfc2136-tsig-axfr
#- --interval=10s
#- --log-level=debug
最後兩行是方便 debug 時用的,需要的時候才移除掉 # 註解符號。設定中比較關鍵的是 dns server 與 key 的正確性,請特別留意。
6.2 佈署服務:
kubectl apply -f external-dns.yaml
如果沒有 error 就表示就緒了。
七,建置測試服務 (nginx)7.1 撰寫服務 yaml (nginx.yaml),其內容如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
annotations:
external-dns.alpha.kubernetes.io/hostname: nginx.k8s.example.org
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
sessionAffinity: None
type: LoadBalancer
External-dns 最關鍵的部分就是要從 service 的 annotations 取得 hostname,然後再抓取 Load Balancer 分配的 IP 進行 dns update。
7.2 佈署服務:
kubectl apply -f nginx.yaml
如果沒有 error 就表示就緒了。
7.3 觀察結果:
kubectl -n external-dns logs external-dns-5d986694c9-5n9wm
請注意實際運行的 pod 名稱或許不太一樣,請調整。如果一切順利,從輸出結果中可以看到類似如下的內容:
time="2019-12-04T16:26:44Z" level=info msg="Created Kubernetes client https://10.96.0.1:443"
time="2019-12-04T16:26:49Z" level=info msg="Configured RFC2136 with zone 'k8s.example.org.' and nameserver '192.168.100.1:53'"
time="2019-12-04T16:26:49Z" level=info msg="Adding RR: nginx.k8s.example.org 0 A 192.168.100.245"
time="2019-12-04T16:26:49Z" level=info msg="Adding RR: nginx.k8s.example.org 0 TXT \"heritage=external-dns,external-dns/owner=k8s,external-dns/resource=service/default/nginx-svc\""
另外, 從 BIND 伺服器那邊, 也可以用 systemctl status -l named 觀察更新的動作,當然也可以用 dig 或 host 命令來實際檢查 dns 的查詢結果。假如名稱解釋一如預期,那就用瀏覽器連線服務的 hostname 來作最後的確認。
八,其他前面我們是用 service 資源來作示範。假如 k8s 環境中已經建置好 ingress-controller 的話,那我們也可以透過建置 ingress 來同步 dns 的更新。我們只需要設定 ingress 資源並進行套用即可:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: nginx-ingress
spec:
rules:
- host: ingress.k8s.example.org
http:
paths:
- path: /
backend:
serviceName: nginx-svc
servicePort: 80
請留意使用 ingress 比 service 會稍有不同:
- 不需要設定 annotations 來指定 hostname
- IP 一律共用 ingress 服務的位址
假如我們要將服務資源停掉的話,也可以透過前述的檢查動作觀察到 dns 名稱也會自動地被刪除:
12月 05 00:43:49 srv1.localdomain named[6533]: client 192.168.100.56#58519/key externaldns-key: updating zone 'k8s.example.org/IN': deleting rrset at 'nginx.k8s.example.org' A
12月 05 00:43:49 srv1.localdomain named[6533]: zone k8s.example.org/IN: sending notifies (serial 21)
12月 05 00:43:49 srv1.localdomain named[6533]: client 192.168.100.56#45520/key externaldns-key: updating zone 'k8s.example.org/IN': deleting rrset at 'ingress.k8s.example.org' A
12月 05 00:43:49 srv1.localdomain named[6533]: client 192.168.100.56#49731/key externaldns-key: updating zone 'k8s.example.org/IN': deleting rrset at 'nginx.k8s.example.org' TXT
12月 05 00:43:49 srv1.localdomain named[6533]: client 192.168.100.56#56605/key externaldns-key: updating zone 'k8s.example.org/IN': deleting rrset at 'ingress.k8s.example.org' TXT
九,結語好了,到這裡我們已經體驗到 external-dns 帶來的便利性。希望能夠對大家在 k8s 應用上有所幫助。
--- End ---