Kubernetes 基于 DNS 的服務(wù)注冊與服務(wù)發(fā)現(xiàn)

通常情況下,直接訪問 Pod 會有如下幾個問題:

  • Pod 會隨時被 Deployment 這樣的控制器刪除重建,那訪問 Pod 的結(jié)果就會變得不可預(yù)知。
  • Pod 的 IP 地址是在 Pod 啟動后才被分配,在啟動前并不知道 Pod 的 IP 地址。
  • 應(yīng)用往往都是由多個運(yùn)行相同鏡像的一組 Pod 組成,一個個 Pod 的訪問也變得不現(xiàn)實(shí)。

Kubernetes 中的 Service 對象就是用來解決上述 Pod 訪問問題的。Service 有一個固定 IP 地址,這個地址在集群內(nèi)部是固定不變的,不同集群中會不一樣,它將訪問該地址的流量轉(zhuǎn)發(fā)給 Pod,具體轉(zhuǎn)發(fā)給哪些 Pod 可以通過 label selectors 確定,而且 Service 可以給這些 Pod 做負(fù)載均衡。

Service 是由 kube-proxy 組件加上 iptables 來共同實(shí)現(xiàn)的(還有一種是 IPVS 模式實(shí)現(xiàn)),在 Kubernetes 集群中,每個 Node 運(yùn)行一個 kube-proxy 進(jìn)程,負(fù)責(zé)維護(hù)節(jié)點(diǎn)上的網(wǎng)絡(luò)規(guī)則,這些網(wǎng)絡(luò)規(guī)則允許從集群內(nèi)部或外部與 Pod 進(jìn)行網(wǎng)絡(luò)通信。

如上圖所示,當(dāng) Kubernetes 監(jiān)測到 Service、Pod 對象的創(chuàng)建和銷毀時,kube-proxy 會配置相應(yīng)的 iptables 規(guī)則,以攔截到達(dá)該 Service 的 IP 和 Port 的請求,并且會將請求重定向到它所代理的 Endpoint。

下面是一個 Service(NodePort) 和它所代理的 Endpoint 的 iptables 信息:

  1. 是 Service 通過 NodePort 方式向集群外部暴露訪問入口。
  2. 是從集群內(nèi)訪問服務(wù) Service,10.98.76.27 這個 IP 只是一條 iptables 規(guī)則上的配置,并沒有真正的網(wǎng)絡(luò) 設(shè)備,所以 ping 這個地址,是不會有任何響應(yīng)的。
  3. 是一組隨機(jī)模式下(–mode random)的 iptables 規(guī)則鏈,也就是 Service 實(shí)現(xiàn)負(fù)載均衡的位置。
  4. 是這個 Service 代理的三個 Endpoint,通過 DNAT 做目標(biāo)地址轉(zhuǎn)換,訪問具體的 Pod 服務(wù)。

下面進(jìn)入正題,看下 Kubernetes 支持的兩種基本的服務(wù)發(fā)現(xiàn)模式 —— 環(huán)境變量 和 DNS。

  • 環(huán)境變量

在每個 Pod 啟動的時候,Kubernetes 會把當(dāng)前命名空間下面的所有 Service 的 IP 和端口通過環(huán)境變量的形式注入到 Pod 中去,這樣 Pod 中的應(yīng)用可以通過讀取環(huán)境變量,來獲取依賴服務(wù)的地址信息。

這種方法使用起來相對簡單,但是有一個很大的問題,就是依賴的服務(wù)必須在 Pod 啟動之前就存在,不然是不會被注入到環(huán)境變量中的。

下圖展示的就是一個 Pod 中的環(huán)境變量,它記錄了其它 Service 服務(wù)的 IP、Port。

/ # env | sort
......
ORDER_SERVER_PORT=tcp://10.103.88.0:9099
ORDER_SERVER_PORT_9099_TCP=tcp://10.103.88.0:9099
ORDER_SERVER_PORT_9099_TCP_ADDR=10.103.88.0
ORDER_SERVER_PORT_9099_TCP_PORT=9099
ORDER_SERVER_PORT_9099_TCP_PROTO=tcp
ORDER_SERVER_SERVICE_HOST=10.103.88.0
ORDER_SERVER_SERVICE_PORT=9099
ORDER_SERVER_SERVICE_PORT_TCP=9099
......
USER_SERVER_PORT=tcp://10.110.228.7:9097
USER_SERVER_PORT_9097_TCP=tcp://10.110.228.7:9097
USER_SERVER_PORT_9097_TCP_ADDR=10.110.228.7
USER_SERVER_PORT_9097_TCP_PORT=9097
USER_SERVER_PORT_9097_TCP_PROTO=tcp
USER_SERVER_SERVICE_HOST=10.110.228.7
USER_SERVER_SERVICE_PORT=9097
USER_SERVER_SERVICE_PORT_TCP=9097
......
  • DNS

這種方式我們不需要去關(guān)心分配的 ClusterIP 的地址,因?yàn)?VIP 地址并不是固定不變的,雖然在一個集群中是不變的,但是我們?nèi)绻嬖?DEV、TEST、UAT 等環(huán)境,是不能保證在每個集群中的 IP 是相同的。

如果能夠直接使用 Service 的名稱(Service 的名稱一般使用微服務(wù)名稱,而且一旦使用基本上不會變化),其對應(yīng)的 ClusterIP 地址的解析能夠自動完成就好了。

KubeDNS 和 CoreDNS 是兩個已建立的 DNS 解決方案,用于定義 DNS 命名規(guī)則,并將 Pod 和 Service 的 DNS 解析到它們相應(yīng)的集群 IP。

通過 CoreDNS,Kubernetes 服務(wù)之間可以僅僅通過服務(wù)名稱來相互訪問。

Kubernetes 使用 DNS 作為服務(wù)注冊表,每個 Service 都會自動注冊到集群 DNS 之中,要使用服務(wù)發(fā)現(xiàn)功能,每個 Pod 都需要知道集群 DNS 的位置才能使用它。因此每個 Pod 中的 /etc/resolv.conf 文件都被配置為使用集群 DNS 進(jìn)行解析,這也是默認(rèn)的 DNS 策略:ClusterFirst。

集群中 kubelet 的啟動參數(shù)有 --cluster-dns=<dns-service-ip style="margin-bottom: 0px; margin-top: 0px;"> 和 --cluster-domain=<default-local-domain style="margin-bottom: 0px; margin-top: 0px;"> ,這兩個參數(shù)分別被用來設(shè)置集群 DNS 服務(wù)器的 IP 地址和主域名后綴。</default-local-domain></dns-service-ip>

Pod 的 dnsPolicy 配置支持四種策略:

  • ClusterFirst:通過 CoreDNS 來做域名解析,Pod 內(nèi) /etc/resolv.conf 配置的 DNS 服務(wù)地址是集群 DNS 服務(wù)的 kube-dns 地址。該策略是集群工作負(fù)載的默認(rèn)策略。
  • None:忽略集群 DNS 策略,需要提供 dnsConfig 字段來指定 DNS 配置信息。
  • Default:Pod 直接繼承集群節(jié)點(diǎn)的域名解析配置。使用宿主機(jī)的 /etc/resolv.conf 文件。
  • ClusterFirstWithHostNetwork:強(qiáng)制在 hostNetWork 網(wǎng)絡(luò)模式下使用 ClusterFirst 策略(默認(rèn)使用 Default 策略)。

我的演示環(huán)境是用一鍵安裝利器 Kubeadm 安裝的,它會默認(rèn)安裝 CoreDNS 插件。

下面我們進(jìn)入到一個 Pod 中查看下它的 dns 配置文件:

/ # cat /etc/resolv.conf
nameserver 10.96.0.10
search cloud.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

可以看到 DNS 的服務(wù)器地址既是 coredns 的 svc ip,所有域名的解析,其實(shí)都要經(jīng)過 coredns 的虛擬 IP 10.96.0.10 進(jìn)行解析。

  • cloud.svc.cluster.local
  • svc.cluster.local
  • cluster.local

域名的搜索域有如上三個,在解析的時候會依次帶入 /etc/resolve.conf 中的 search 域進(jìn)行 DNS 查找,所以集群中同一 namespace 中的 pod 之間相互訪問是可以直接使用 svc name,不同 namespace 下的服務(wù)訪問需要帶上 namespace。

例如訪問同一 namespace 下的服務(wù) user-server,只需要匹配一次 search 域就可以解析到正確的 IP 地址。

user-server.cloud.svc.cluster.local

下面通過 nslookup 驗(yàn)證下 DNS 查找是否將服務(wù)的 DNS 解析為正確的 IP(A 記錄),user-server 的 ClusterIP 就是 10.110.228.7,與 DNS 查找解析到的 IP 相同的。auth-server 與 user-server 在同一個 namespace:cloud 下,可直接通過服務(wù)名稱訪問。

/ # nslookup user-server
Name:      user-server
Address 1: 10.110.228.7 user-server.cloud.svc.cluster.local

不同 namespace 下的服務(wù)訪問,則需要攜帶名稱空間,例如要訪問 istio-system 命名空間下的istio-egressgateway 服務(wù),就必須攜帶命名空間:

/ # nslookup istio-egressgateway
nslookup: can't resolve 'istio-egressgateway': Name does not resolve

/ # nslookup istio-egressgateway.istio-system
Name:      istio-egressgateway.istio-system
Address 1: 10.100.190.21 istio-egressgateway.istio-system.svc.cluster.local

Kubernetes 通過其內(nèi)置的 DNS 附加組件實(shí)現(xiàn)了高效的服務(wù)發(fā)現(xiàn)?;?DNS 的服務(wù)發(fā)現(xiàn)功能非常強(qiáng)大,因?yàn)椴恍枰獙?IP 和端口等網(wǎng)絡(luò)參數(shù)硬編碼到應(yīng)用程序中。一旦 Service 管理了一組 pod,就可以使用服務(wù)的 DNS 輕松地訪問它們,并且通過 iptables 實(shí)現(xiàn)了 Pod 的 Load Balancer。

具體哪些 Pod 才可以成為 Service 的 Endpoint 接收流量,Kubernetes 給我們提供了三種探針,來實(shí)現(xiàn)對 Pod 的健康檢查,如下表:

探針 作用 詳細(xì)說明
livenessProbe 何時需要重啟容器 Liveness 指針是存活探針,它用來判斷容器是否存活、判斷 pod 是否 running。如果 Liveness 指針判斷 容器不健康,此時會通過 kubelet 殺掉相應(yīng)的 pod,并根據(jù)重啟策略來判斷是否重啟這個容器。如果默認(rèn)不配置 Liveness 指針,則默認(rèn)情況下認(rèn)為它這個探測默認(rèn)返回是成功的。
startupProbe 啟動探針 Kubelet 使用啟動探針來了解容器何時啟動。如果配置了這樣的探測,它將禁用存活探針和準(zhǔn)備狀態(tài)檢查,直到成功為止,確保這些探測不會干擾應(yīng)用程序啟動。這可以用于對緩慢啟動的容器進(jìn)行活性檢查,避免它們在啟動和運(yùn)行之前被 kubelet 殺死。
readinessProbe 何時可以開始接收流量 Readiness 指針用來判斷這個容器是否啟動完成,即 pod 的 condition 是否 ready。如果探測的一個結(jié)果是不成功,那么此時它會從 pod 上 Endpoint 上移除,也就是說從 Service 接入層上面會把前一個 pod 進(jìn)行摘除,直到下一次判斷成功,這個 pod 才會再次掛到相應(yīng)的 endpoint 之上。

我們來看下在 Kubernetes 平臺中滾動更新服務(wù)時 Eureka 注冊中心存在的問題。

在我們微服務(wù)示例中,服務(wù)滾動更新的策略是,“一上一下,先上后下”的原則,具體參數(shù)配置如下,即 1 個新版本 pod ready(結(jié)合 readiness 探針)之后,才會去銷毀舊版本的 pod,這個保證了 Service 至少會存在一個 ready 狀態(tài)的站點(diǎn),是最平穩(wěn)的更新方式。

spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0

Kubernetes 提供了兩個參數(shù),來控制 rollingUpdate 滾動更新的速度:

  • maxUnavailable:和期望 ready 的副本數(shù)比,不可用副本數(shù)最大比例(或最大值),這個值越小,越能保證服務(wù)穩(wěn)定,更新越平滑。
  • maxSurge:和期望 ready 的副本數(shù)比,超過期望副本數(shù)最大比例(或最大值),這個值調(diào)的越大,副本更新速度越快。

如果由于一些特殊原因,你的容器不是以單進(jìn)程模式運(yùn)行的,也就是應(yīng)用進(jìn)程并不是容器內(nèi)的 1 號進(jìn)程,也沒有使用 Tini 作為 init 進(jìn)程,這就導(dǎo)致容器內(nèi)的應(yīng)用進(jìn)程接收的是 SIGKILL 信號,以非 graceful shutdown 的方式終止。

在滾動更新完成后,雖然這時舊版本的 Pod 已經(jīng)被從 Service 的 Endpoint 站點(diǎn)中移除,但是這種服務(wù)停止的方式不會調(diào)用 Eureka-Server 的 API 主動下線服務(wù),而是只能等默認(rèn)定時 60s 去清理 90s 內(nèi)沒有續(xù)約的服務(wù)。

在這期間 Eureka Server 中會有兩個 instance 在線,盡管其中舊版本的 Pod 已經(jīng)被銷毀了。

這個銷毀的舊版本的 Pod 最大可能會在注冊列表中存在 180s,如果再加上三級緩存同步周期 30s,消費(fèi)端拉取服務(wù)列表的周期 30s,以及 Ribbon 的緩存周期 30s,消費(fèi)端拉取的服務(wù)列表對 Pod 銷毀下線的感知就會有 4 分鐘左右的延遲,這 4 分鐘內(nèi)流入這個已經(jīng)銷毀的 Pod 的請求都會調(diào)用失敗。

因?yàn)榕f Pod 的 IP 在集群中已經(jīng)不存在了,所以會提示 Host unreachable

No route to host (Host unreachable)

如果我們使用 svc 域名(即服務(wù)名稱)注冊到 Eureka, 微服務(wù)內(nèi)部之間相互調(diào)用不再依賴于各自的服務(wù) IP(Pod IP),直接通過 Service 的域名訪問,域名解析和負(fù)載均衡都交給 Kubernetes 平臺,利用 Kubernetes 中 Pod 的探針監(jiān)測機(jī)制,可以自動過濾已下線或有問題的服務(wù)。

kubernetes:
  namespace: cloud

eureka:
  client:
    serviceUrl:
      defaultZone: ${eureka}
  instance:
    instance-id: ${spring.cloud.client.ip-address}:${server.port}
    hostname: ${spring.application.name}.${kubernetes.namespace}

這種方式使用了 Kubernetes 的服務(wù)發(fā)現(xiàn)功能,當(dāng) Pod 不可用時就會從 Service 的 Endpoint 列表中移除,流量就不會在負(fù)載到該 Pod,可以避免 Eureka 緩存導(dǎo)致的一段時間內(nèi)服務(wù)調(diào)用失敗問題。

此時 Eureka 的服務(wù)發(fā)現(xiàn)、Ribbon 的負(fù)載均衡其實(shí)也就失去作用了,服務(wù)發(fā)現(xiàn)使用了 Kubernetes 基于 DNS 的方式,負(fù)載均衡則是 kube-proxy 組件維護(hù)的 iptables 規(guī)則來隨機(jī)訪問一個 Pod。

微服務(wù)之間的相互訪問如下圖所示:

這里完全可以廢除掉 Eureka,完全融入 Kubernetes 的服務(wù)發(fā)現(xiàn)機(jī)制,但是因?yàn)榇罅康奈⒎?wù)都是用的 Spring Cloud Eureka,底層走的 Feign 調(diào)用,徹底替換改造有點(diǎn)大,所以選擇了向 Eureka 注冊 SVC 域名這種方案。

~ END ~。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容