參考:
- 官方網站,https://coredns.io/
- CoreDNS安裝,https://my.oschina.net/u/2306127/blog/1618543
- CoreDNS使用手冊,https://coredns.io/manual/toc/
- CoreDNS源碼,https://github.com/coredns
- CoreDNS配置,https://my.oschina.net/u/2306127/blog/1788566
一.Kubernetes DNS服務發展史
從Kubernetes 1.11開始,可使用CoreDNS作爲Kubernetes的DNS插件進入GA狀態,Kubernetes推薦使用CoreDNS作爲集羣內的DNS服務。 我們先看一下Kubernetes DNS服務的發展歷程。
1.1 Kubernetes 1.3之前的版本 – skyDNS
Kubernetes 1.3之前的版本使用skyDNS作爲DNS服務,這個有點久遠了。Kubernetes的DNS服務由kube2sky、skyDNS、etcd組成。 kube2sky通過kube-apiserver監聽集羣中Service的變化,將生成的DNS記錄信息更新到etcd中,而skyDNS將從etcd中獲取數據對外提供DNS的查詢服務。
1.2 Kubernetes 1.3版本開始 – kubeDNS
Kubernetes 1.3開始使用kubeDNS和dnsmasq替換了原來的kube2sky和skyDNS,不再使用etcd,而是將DNS記錄直接存放在內存中,通過dnsmasq的緩存功能提高DNS的查詢效率。下圖是描述了Kubernetes使用kubeDNS實現服務發現的整體架構:
1.3 Kubernetes 1.11版本開始 – CoreDNS進入GA
從Kubernetes 1.11開始,可使用CoreDNS作爲Kubernetes的DNS插件進入GA狀態,Kubernetes推薦使用CoreDNS作爲集羣內的DNS服務。 CoreDNS從2017年初就成爲了CNCF的的孵化項目,CoreDNS的特點就是十分靈活和可擴展的插件機制,
各種插件實現:
不同的功能,如重定向、定製DNS記錄、記錄日誌等等。下圖描述了CoreDNS的整體架構:
二、CoreDNS簡介
Kubernetes包括用於服務發現的DNS服務器Kube-DNS。 該DNS服務器利用SkyDNS的庫來爲Kubernetes pod和服務提供DNS請求。SkyDNS2的作者,Miek Gieben,創建了一個新的DNS服務器,CoreDNS,它採用更模塊化,可擴展的框架構建。 Infoblox已經與Miek合作,將此DNS服務器作爲Kube-DNS的替代品。
CoreDNS利用作爲Web服務器Caddy的一部分而開發的服務器框架。該框架具有非常靈活,可擴展的模型,用於通過各種中間件組件傳遞請求。這些中間件組件根據請求提供不同的操作,例如記錄,重定向,修改或維護。雖然它一開始作爲Web服務器,但是Caddy並不是專門針對HTTP協議的,而是構建了一個基於CoreDNS的理想框架。
在這種靈活的模型中添加對Kubernetes的支持,相當於創建了一個Kubernetes中間件。該中間件使用Kubernetes API來滿足針對特定Kubernetes pod或服務的DNS請求。而且由於Kube-DNS作爲Kubernetes的另一項服務,kubelet和Kube-DNS之間沒有緊密的綁定。您只需要將DNS服務的IP地址和域名傳遞給kubelet,而Kubernetes並不關心誰在實際處理該IP請求。
1、CoreDNS支持行爲
1.0.0版本主要遵循Kube-DNS的當前行爲。 CoreDNS的005及更高版本實現了完整的規範和更多功能。
- A記錄(正常的Service分配了一個名爲my-svc.my-namespace.svc.cluster.local的DNS A記錄。 這解決了服務的集羣IP)
- “headless”(沒有集羣IP)的Service也分配了一個名爲my-svc.my-namespace.svc.cluster.local的DNS A記錄。 與普通服務不同,這解決了Service選擇了pods的一組IP。 客戶預計將從這ip集合中消耗集合或使用標準循環選擇。
- 針對名爲正常或無頭服務的端口創建的SRV記錄,對於每個命名的端口,SRV記錄的格式爲_my-port-name._my-port-protocol.my-svc.my-namespace.svc.cluster.local。對於常規服務,這將解析爲端口號和CNAME:my-svc.my-namespace.svc.cluster.local;對於無頭服務,這解決了多個答案,一個用於支持服務的每個pod,幷包含端口號還有格式爲auto-generated-name.my-svc.my-namespace.svc.cluster.local 的pod的CNAME 。SRV記錄包含它們中的“svc”段,對於省略“svc”段的舊式CNAME不支持。
- 作爲Service一部分的endpoints的A記錄(比如“pets”的記錄)
- pod的Spec中描述的A記錄
- 還有就是用來發現正在使用的DNS模式版本的TXT記錄
所有羣集中不需要pod A記錄支持,默認情況下禁用。 此外,CoreDNS對此用例的支持超出了在Kube-DNS中找到的標準行爲。
在Kube-DNS中,這些記錄不反映集羣的狀態,例如,對w-x-y-z.namespace.pod.cluster.local的任何查詢將返回帶有w.x.y.z(ip)的A記錄,即使該IP不屬於指定的命名空間,甚至不屬於集羣地址空間。最初的想法是啓用對* .namespace.pod.cluster.local這樣的域使用通配符SSL證書。
CoreDNS集成了提供pod驗證的選項,驗證返回的IP地址w.x.y.z實際上是指定命名空間中的pod的IP。他防止在命名空間中欺騙DNS名稱。 然而,它確實會大大增加CoreDNS實例的內存佔用,因爲現在它需要觀察所有的pod,而不僅僅是服務端點。
2、架構
整個 CoreDNS 服務都建立在一個使用 Go 編寫的 HTTP/2 Web 服務器 Caddy · GitHub 上,CoreDNS 整個項目可以作爲一個 Caddy 的教科書用法。
CoreDNS 的大多數功能都是由插件來實現的,插件和服務本身都使用了 Caddy 提供的一些功能,所以項目本身也不是特別的複雜。
3、插件
作爲基於 Caddy 的 Web 服務器,CoreDNS 實現了一個插件鏈的架構,將很多 DNS 相關的邏輯都抽象成了一層一層的插件,包括 Kubernetes 等功能,每一個插件都是一個遵循如下協議的結構體:
type (
Plugin func(Handler) Handler
Handler interface {
ServeDNS(context.Context, dns.ResponseWriter, *dns.Msg) (int, error)
Name() string
}
)
所以只需要爲插件實現 ServeDNS
以及 Name
這兩個接口並且寫一些用於配置的代碼就可以將插件集成到 CoreDNS 中。
4、Corefile
另一個 CoreDNS 的特點就是它能夠通過簡單易懂的 DSL 定義 DNS 服務,在 Corefile 中就可以組合多個插件對外提供服務:
coredns.io:5300 {
file db.coredns.io
}
example.io:53 {
log
errors
file db.example.io
}
example.net:53 {
file db.example.net
}
.:53 {
kubernetes
proxy . 8.8.8.8
log
errors
cache
}
對於以上的配置文件,CoreDNS 會根據每一個代碼塊前面的區和端點對外暴露兩個端點提供服務:
該配置文件對外暴露了兩個 DNS 服務,其中一個監聽在 5300 端口,另一個在 53 端口,請求這兩個服務時會根據不同的域名選擇不同區中的插件進行處理。
原理
CoreDNS 可以通過四種方式對外直接提供 DNS 服務,分別是 UDP、gRPC、HTTPS 和 TLS:
但是無論哪種類型的 DNS 服務,最終隊會調用以下的 ServeDNS
方法,爲服務的調用者提供 DNS 服務:
func (s *Server) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) {
m, _ := edns.Version(r)
ctx, _ := incrementDepthAndCheck(ctx)
b := r.Question[0].Name
var off int
var end bool
var dshandler *Config
w = request.NewScrubWriter(r, w)
for {
if h, ok := s.zones[string(b[:l])]; ok {
ctx = context.WithValue(ctx, plugin.ServerCtx{}, s.Addr)
if r.Question[0].Qtype != dns.TypeDS {
rcode, _ := h.pluginChain.ServeDNS(ctx, w, r)
dshandler = h
}
off, end = dns.NextLabel(q, off)
if end {
break
}
}
if r.Question[0].Qtype == dns.TypeDS && dshandler != nil && dshandler.pluginChain != nil {
rcode, _ := dshandler.pluginChain.ServeDNS(ctx, w, r)
plugin.ClientWrite(rcode)
return
}
if h, ok := s.zones["."]; ok && h.pluginChain != nil {
ctx = context.WithValue(ctx, plugin.ServerCtx{}, s.Addr)
rcode, _ := h.pluginChain.ServeDNS(ctx, w, r)
plugin.ClientWrite(rcode)
return
}
}
在上述這個已經被簡化的複雜函數中,最重要的就是調用了『插件鏈』的 ServeDNS
方法,將來源的請求交給一系列插件進行處理,如果我們使用以下的文件作爲 Corefile:
example.org {
file /usr/local/etc/coredns/example.org
prometheus # enable metrics
errors # show errors
log # enable query logs
}
那麼在 CoreDNS 服務啓動時,對於當前的 example.org
這個組,它會依次加載 file
、log
、errors
和 prometheus
幾個插件,這裏的順序是由 zdirectives.go 文件定義的,啓動的順序是從下到上:
var Directives = []string{
// ...
"prometheus",
"errors",
"log",
// ...
"file",
// ...
"whoami",
"on",
}
因爲啓動的時候會按照從下到上的順序依次『包裝』每一個插件,所以在真正調用時就是從上到下執行的,這就是因爲 NewServer
方法中對插件進行了組合:
func NewServer(addr string, group []*Config) (*Server, error) {
s := &Server{
Addr: addr,
zones: make(map[string]*Config),
connTimeout: 5 * time.Second,
}
for _, site := range group {
s.zones[site.Zone] = site
if site.registry != nil {
for name := range enableChaos {
if _, ok := site.registry[name]; ok {
s.classChaos = true
break
}
}
}
var stack plugin.Handler
for i := len(site.Plugin) - 1; i >= 0; i-- {
stack = site.Plugin[i](stack)
site.registerHandler(stack)
}
site.pluginChain = stack
}
return s, nil
}
對於 Corefile 裏面的每一個配置組,NewServer
都會講配置組中提及的插件按照一定的順序組合起來,原理跟 Rack Middleware 的機制非常相似,插件 Plugin
其實就是一個出入參數都是 Handler
的函數:
type (
Plugin func(Handler) Handler
Handler interface {
ServeDNS(context.Context, dns.ResponseWriter, *dns.Msg) (int, error)
Name() string
}
)
所以我們可以將它們疊成堆棧的方式對它們進行操作,這樣在最後就會形成一個插件的調用鏈,在每個插件執行方法時都可以通過 NextOrFailure
函數調用下一個插件的 ServerDNS
方法:
func NextOrFailure(name string, next Handler, ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
if next != nil {
if span := ot.SpanFromContext(ctx); span != nil {
child := span.Tracer().StartSpan(next.Name(), ot.ChildOf(span.Context()))
defer child.Finish()
ctx = ot.ContextWithSpan(ctx, child)
}
return next.ServeDNS(ctx, w, r)
}
return dns.RcodeServerFailure, Error(name, errors.New("no next plugin found"))
}
除了通過 ServeDNS
調用下一個插件之外,我們也可以調用 WriteMsg
方法並結束整個調用鏈。
從插件的堆疊到順序調用以及錯誤處理,我們對 CoreDNS 的工作原理已經非常清楚了,接下來我們可以簡單介紹幾個插件的作用。
三、在kubernetes中部署coredns
1、下載coredns部署包並說明
https://github.com/coredns/deployment/tree/master/kubernetes
主要有幾個文件:
deploy.sh是一個便捷的腳本,用於生成用於在當前運行標準kube-dns的集羣上運行CoreDNS的清單。使用coredns.yaml.sed文件作爲模板,它創建一個ConfigMap和一個CoreDNS deployment,然後更新 Kube-DNS service selector以使用CoreDNS deployment。 通過重新使用現有服務,服務請求不會中斷。
腳本不會刪除kube-dns的deployment或replication controller - 您必須手動執行:
kubectl delete --namespace=kube-system deployment kube-dns
要使用它,只需將它們放在同一目錄中,然後運行deploy.sh腳本,將其傳遞給您的服務CIDR(10.3.0.0/24)。 這將生成具有必要Corefile的ConfigMap。 它還將查找現有的kube-dns服務的集羣IP。
[root@k8s-master conf.d]# etcdctl ls /k8s/network/subnets
/k8s/network/subnets/10.0.24.0-24
/k8s/network/subnets/10.0.86.0-24
/k8s/network/subnets/10.0.35.0-24
(注意:以上原始腳本只適用於當前kubernetes集羣含有kube-dns的情況,如果沒有需要修改下腳本
#!/bin/bash
10.0.24.200
# Deploys CoreDNS to a cluster currently running Kube-DNS.
SERVICE_CIDR=$1
CLUSTER_DOMAIN=${2:-cluster.local}
YAML_TEMPLATE=${3:-`pwd`/coredns.yaml.sed}
YAML=${4:-`pwd`/coredns.yaml}
if [[ -z $SERVICE_CIDR ]]; then
echo "Usage: $0 SERVICE-CIDR [ CLUSTER-DOMAIN ] [ YAML-TEMPLATE ] [ YAML ]"
exit 1
fi
#CLUSTER_DNS_IP=$(kubectl get service --namespace kube-system kube-dns -o jsonpath="{.spec.clusterIP}")
CLUSTER_DNS_IP=
默認情況下CLUSTER_DNS_IP是自動獲取kube-dns的集羣ip的,但是由於沒有部署kube-dns所以只能手動指定一個集羣ip了。
執行: ./deploy.sh 10.0.0.0/24 cluster.local
以上腳本執行後可以看到預覽的效果。
仔細觀察上面的Corefile部分,這是一個在端口53上運行CoreDNS併爲Kubernetes提供cluster.local域的示例
.:53 { errors log stdout health kubernetes cluster.local 10.3.0.0/24 proxy . /etc/resolv.conf cache 30 }
1)errors官方沒有明確解釋,後面研究
2)log stdout:日誌中間件配置爲將日誌寫入STDOUT
3)health:健康檢查,提供了指定端口(默認爲8080)上的HTTP端點,如果實例是健康的,則返回“OK”。
4)cluster.local:CoreDNS爲kubernetes提供的域,10.3.0.0/24這告訴Kubernetes中間件它負責爲反向區域提供PTR請求0.0.3.10.in-addr.arpa ..換句話說,這是允許反向DNS解析服務(我們經常使用到得DNS服務器裏面有兩個區域,即“正向查找區域”和“反向查找區域”,正向查找區域就是我們通常所說的域名解析,反向查找區域即是這裏所說的IP反向解析,它的作用就是通過查詢IP地址的PTR記錄來得到該IP地址指向的域名,當然,要成功得到域名就必需要有該IP地址的PTR記錄。PTR記錄是郵件交換記錄的一種,郵件交換記錄中有A記錄和PTR記錄,A記錄解析名字到地址,而PTR記錄解析地址到名字。地址是指一個客戶端的IP地址,名字是指一個客戶的完全合格域名。通過對PTR記錄的查詢,達到反查的目的。)
5)proxy:這可以配置多個upstream 域名服務器,也可以用於延遲查找 /etc/resolv.conf 中定義的域名服務器
6)cache:這允許緩存兩個響應結果,一個是肯定結果(即,查詢返回一個結果)和否定結果(查詢返回“沒有這樣的域”),具有單獨的高速緩存大小和TTLs。
2、現在安裝coredns到kubernetes中
# ./deploy.sh -r 10.0.0.0/16 -i 192.168.10.90 -d cluster.local -t coredns.yaml.sed -s > coredns.yaml
# .kubectl apply -f coredns.yaml
或者直接執行:
# ./deploy.sh -r 10.0.0.0/16 -i 192.168.10.90 -d cluster.local -t coredns.yaml.sed -s | kubectl apply -f -
查看service
[root@k8s-master coredns]# kubectl get service -l k8s-app=kube-dns --namespace=kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 192.168.10.90 <none> 53/UDP,53/TCP,9153/TCP 15d
查看coredns的Pod,確認所有Pod都處於Running狀態:
kubectl get pods -n kube-system -l k8s-app=kube-dns
查看日誌:# kubectl logs -f coredns-6cc7bf59f4-4rfq8 --namespace=kube-system
3、修改cluster-dns
修改master節點和所有node節點--cluster-dns配置,修改內容如紅色所注,與上面的Corefile中的值對應。
4、測試CoreDNS
現在我們來創建一個wepapp的pod和service,測試一下coredns是否起作用
[root@k8s-master conf.d]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
webapp-nrz4t 1/1 Running 0 1d 10.0.35.3 192.168.10.39
webapp-zp69q 1/1 Running 0 1d 10.0.24.4 192.168.10.50
[root@k8s-master ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 192.168.0.1 <none> 443/TCP 28d
webapp ClusterIP 192.168.14.242 <none> 9081/TCP 17d
webapp2 ClusterIP 192.168.22.2 <none> 9082/TCP 17d
[root@k8s-master ~]# curl 192.168.22.2:9082
Hello world
1、檢查集羣的pod /etc/resolv.conf是否生效:
首先進入這個集羣內的另一個pod
kubectl exec -it webapp-nrz4t /bin/sh 或者 docker exec -it 0d0874df9e15 /bin/sh
$ cat /etc/resolv.conf
sh-4.2# cat /etc/resolv.conf
nameserver 192.168.10.90
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
2、curl測試服務:
curl webapp.default.svc.cluster.local:9081
3、在master的主機上修改 /etc/resolv.conf,增加一行:nameserver 192.168.10.90後執行
curl webapp.default.svc.cluster.local:9081
通過域名訪問成功!
問題排查技巧
如果執行 nslookup 命令失敗,檢查如下內容:
1、先檢查本地 DNS 配置
查看配置文件 resolv.conf。
按照如下方法(注意搜索路徑可能會因爲雲提供商不同而變化)驗證搜索路徑和 Name Server 的建立:
nameserver 192.168.10.90
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
2、快速診斷
出現類似如下指示的錯誤,說明 kube-dns 插件或相關 Service 存在問題:
檢查service是否正常運行:
kubectl get svc --namespace=kube-system
或者檢查是否 DNS Pod 正在運行
使用 kubectl get pods
命令驗證 DNS Pod 正在運行:
kubectl get pods --namespace=kube-system -l k8s-app=kube-dns
應該能夠看到類似如下信息:
如果看到沒有 Pod 運行,或 Pod 失敗/結束,DNS 插件不能默認部署到當前的環境,必須手動部署。
或者查看service的Endpoint是否正常:
kubectl get ep kube-dns --namespace=kube-system
如果沒有看到 Endpoint,查看 調試 Service 文檔 中的 Endpoint 段內容。
3、檢查 DNS Pod 中的錯誤信息
使用 kubectl logs
命令查看 DNS 後臺進程的日誌:
kubectl logs coredns-6cc7bf59f4-vj7cc -n kube-system
看到錯誤信息:
eflector.go:134] pkg/mod/k8s.io/[email protected]+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Namespace: Get https://192.168.0.1:443/api/v1/namespace
2.168.0.1:443/api/v1/endpoints?limit=500&resourceVersion=0: x509: certificate is valid for 192.168.10.50, 10.0.0.1, not 192.168.0.1
E0710 16:00:58.986103 1 reflector.go:134] pkg/mod/k8s.io/[email protected]+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Namespace: Get https://192.168.0.1:443/api/v1/namespaces?limit=500&resourceVersion=0: x509: certificate is valid for 192.168.10.50, 10.0.0.1, not 192.168.0.1
E0710 16:00:58.989633 1 reflector.go:134] pkg/mod/k8s.io/[email protected]+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Service: Get https://192.168.0.1:443/api/v1/services?limit=500&resourceVersion=0: x509: certificate is valid for 192.168.10.50, 10.0.0.1, not 192.168.0.1
E0710 16:00:58.991655 1 reflector.go:134] pkg/mod/k8s.io/[email protected]+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Endpoints: Get https://192.168.0.1:443/api/v1/endpoints?limit=500&resourceVersion=0: x509: certificate is valid for 192.168.10.50, 10.0.0.1, not 192.168.0.1
E0710 16:00:59.990732 1 reflector.go:134] pkg/mod/k8s.io/[email protected]+inco
原因:192.168.0.1不在證書裏面
解決:需要重新設置:apiserver證書
masterssl.cnf文件的示例如下:IP.3 = 192.168.0.1
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
[req_distinguished_name]
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = kubernetes
DNS.2 = kubernetes.default
DNS.3 = kubernetes.default.svc
DNS.4 = kubernetes.default.svc.cluster.local
IP.1 = ${K8S_SERVICE_IP}
IP.2 = ${MASTER_IPV4}IP.3 = 192.168.0.1
錯誤信息:
E0712 02:25:45.326123 1 reflector.go:134] pkg/mod/k8s.io/[email protected]+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Service: Unauthorized
E0712 02:25:45.327038 1 reflector.go:134] pkg/mod/k8s.io/[email protected]+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Endpoints: Unauthorized
E0712 02:25:45.328029 1 reflector.go:134] pkg/mod/k8s.io/[email protected]+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Namespace: Unauthorized
E0712 02:25:46.327153 1 reflector.go:134] pkg/mod/k8s.io/[email protected]+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Service: Unauthorized
E0712 02:25:46.328210 1 reflector.go:134] pkg/mod/k8s.io/[email protected]+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Endpoints: Unauthorized
E0712 02:25:46.329160 1 reflector.go:134] pkg/mod/k8s.io/[email protected]+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Namespace: Unauthorized
E0712 02:25:47.328243 1 reflector.go:134] pkg/mod/k8s.io/[email protected]+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Service: Unauthorized
E0712 02:25:47.329143 1 reflector.go:134] pkg/mod/k8s.io/[email protected]+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Endpoints: Unauthorized
E0712 02:25:47.330086 1 reflector.go:134] pkg/mod/k8s.io/[email protected]+incompatible/tools/cache/reflector.go:95: Failed to list *v1.Namespace: Unauthorized
原因:service account 的token認知不通過,是因爲重新生成證書後,需要刪除舊的token。
解決:
kubectl get secret -n kube-system
NAME TYPE DATA AGE
coredns-token-cdn9x kubernetes.io/service-account-token 3 3d
default-token-lht2v kubernetes.io/service-account-token 3 3d
kubectl delete secret coredns-token-cdn9x -n kube-system
secret "coredns-token-cdn9x" deleted