1. 程式人生 > >Kubernetes架構學習筆記

Kubernetes架構學習筆記

Kubernetes是Google開源的容器叢集管理系統,其提供應用部署、維護、 擴充套件機制等功能,利用Kubernetes能方便地管理跨機器執行容器化的應用,是Docker分散式系統的解決方案。k8s裡所有的資源都可以用yaml或Json定義。

1 K8s基本概念

1.1 Master

Master節點負責整個叢集的控制和管理,所有的控制命令都是發給它,上面執行著一組關鍵程序:

  • kube-apiserver:提供了HTTP REST介面,是k8s所有資源增刪改查等操作的唯一入口,也是叢集控制的入口。
  • kube-controller-manager:所有資源的自動化控制中心。當叢集狀態與期望不同時,kcm會努力讓叢集恢復期望狀態,比如:當一個pod死掉,kcm會努力新建一個pod來恢復對應replicas set期望的狀態。
  • kube-scheduler:負責Pod的排程。

實際上,Master只是一個名義上的概念,三個關鍵的服務不一定需要執行在一個節點上。

1.1.1 API Server的原理

叢集中的各個功能模組通過 apiserver將資訊儲存在Etcd,當需要修改這些資訊的時候通過其REST介面來實現。

1.1.2 Controller Manager的原理

內部包含:

  • Replication Controller
  • Node Controller
  • ResourceQuota Controller
  • Namespace Controller
  • ServiceAccount Controller
  • Token Controller
  • Service Controller
  • Endpoint Controller等

這些Controller通過API Server實時監控各個資源的狀態,當有資源因為故障導致狀態變化,Controller就會嘗試將系統由“現有狀態”恢復到“期待狀態”。

1.1.3 Scheduler的原理

作用是將apiserver或controller manager建立的Pod排程和繫結到具體的Node上,一旦繫結,就由Node上的kubelet接手Pod的接下來的生命週期管理。

1.2 Node

Node是工作負載節點,執行著Master分配的負載(Pod),但一個Node宕機時,其上的負載會被自動轉移到其他Node上。其上執行的關鍵元件是:

  • kubelet:負責Pod的生命週期管理,同時與Master密切協作,實現叢集管理的基本功能。
  • kube-proxy:實現Service的通訊與負載均衡機制的重要元件,老版本主要通過設定iptables規則實現,新版1.9基於kube-proxy-lvs 實現。
  • Docker Engine:Docker引擎,負責Docker的生命週期管理。

這裡寫圖片描述

1.2.1 kube-proxy的原理

每個Node上都執行著一個kube-proxy程序,它在本地建立一個SocketServer接收和轉發請求,可以看作是Service的透明代理和負載均衡器,負載均衡策略模式是Round Robin也可以設定會話保持,策略使用的是“ClientIP”,將同一個ClientIP的請求轉發同一個Endpoint上。

Service的Cluster IP和NodePort等概念都是kube-proxy服務通過Iptables的NAT轉換實現,Iptables機制針對的是kube-proxy監聽的埠,所以每個Node上都要有kube-proxy。
這裡寫圖片描述

1.2.2 kubelet原理

每個Node都會啟動一個kubelet,主要作用有:

(1)Node管理

  • 註冊節點資訊;
  • 通過cAdvisor監控容器和節點的資源;
  • 定期向Master(實際上是apiserver)彙報本節點資源消耗情況

(2)Pod管理

所以非通過apiserver方式建立的Pod叫Static Pod,這裡我們討論的都是通過apiserver建立的普通Pod。kubelet通過apiserver監聽etcd,所有針對Pod的操作都會被監聽到,如果其中有涉及到本節點的Pod,則按照要求進行建立、修改、刪除等操作。

(3)容器健康檢查

kubelet通過兩類探針檢查容器的狀態:

LivenessProbe:判斷一個容器是否健康,如果不健康則會刪除這個容器,並按照restartPolicy看是否重啟這個容器。實現的方式有ExecAction(在容器內部執行一個命令)、TCPSocketAction(如果埠可以被訪問,則健康)、HttpGetAction(如果返回200則健康)。

ReadinessProbe:用於判斷容器是否啟動完全。如果返回的是失敗,則Endpoint Controller會將這個Pod的Endpoint從Service的Endpoint列表中刪除。也就是,不會有請求轉發給它。

1.3 Pod

Pod是k8s進行資源排程的最小單位,每個Pod中執行著一個或多個密切相關的業務容器這些業務容器共享這個Pause容器的IP和Volume,我們以這個不易死亡的Pause容器作為Pod的根容器,以它的狀態表示整個容器組的狀態。一個Pod一旦被建立就會放到Etcd中儲存,然後由Master排程到一個Node繫結,由這個Node上的Kubelet進行例項化。

每個Pod會被分配一個單獨的Pod IP,Pod IP + ContainerPort 組成了一個Endpoint

1.4 Service

K8s中一個Service相當於一個微服務的概念,一個Service對應後端多個Pod計算例項,使用LabelSelector將一類Pod都繫結到自己上來。一般還會需要一個Deployment或者RC來幫助這個Service來保證這個Service的服務能力和質量。

這裡寫圖片描述

1.4.1 kube-proxy負載均衡

執行在每個Node上的kube-proxy其實就是一個智慧的軟體負載均衡器,它負載將發給Service的請求轉發到後端對應的Pod,也就是說它負責會話保持和負責均衡。

1.4.2 Cluster IP

負載均衡的基礎是負載均衡器要維護一個後端Endpoint列表,但是Pod的Endpoint會隨著Pod的銷燬和重建而改變,k8s使這個問題透明化。一旦Service被建立,就會立刻分配給它一個Cluster IP,在Service的整個生命週期內,這個Cluster IP不會改變。於是,服務發現的問題也解決了:只要用Service Name和Service Cluster IP做一個DNS域名對映就可以了。

1.4.3 DNS

從Kubernetes 1.3開始,DNS通過使用外掛管理系統cluster add-on,成為了一個內建的自啟動服務。Kubernetes DNS在Kubernetes叢集上排程了一個DNS PodService,並配置kubelet,使其告訴每個容器使用DNS Service的IP來解析DNS名稱。

(1)Service

叢集中定義的每個Service(包括DNS Service它自己)都被分配了一個DNS名稱。預設的,Pod的DNS搜尋列表中會包含Pod自己的名稱空間和叢集的預設域,下面我們用示例來解釋以下。 假設有一個名為foo的Service,位於名稱空間bar中。執行在bar名稱空間中的Pod可以通過DNS查詢foo關鍵字來查詢到這個服務,而執行在名稱空間quux中的Pod可以通過關鍵字foo.bar來查詢到這個服務。

普通(非headless)的Service都被分配了一個DNS記錄,該記錄的名稱格式為my-svc.my-namespace.svc.cluster.local,通過該記錄可以解析出服務的叢集IP。 Headless(沒有叢集IP)的Service也被分配了一個DNS記錄,名稱格式為my-svc.my-namespace.svc.cluster.local。與普通Service不同的是,它會解析出Service選擇的Pod的IP列表。

(2)Pod

Pod也可以使用DNS服務。pod會被分配一個DNS記錄,名稱格式為pod-ip-address.my-namespace.pod.cluster.local。 比如,一個pod,它的IP地址為1.2.3.4,名稱空間為default,DNS名稱為cluster.local,那麼它的記錄就是:1-2-3-4.default.pod.cluster.local。 當pod被建立時,它的hostname設定在Pod的metadata.name中。

在v1.2版本中,使用者可以指定一個Pod註解,pod.beta.kubernetes.io/hostname,用於指定Pod的hostname。這個Pod註解,一旦被指定,就將優先於Pod的名稱,成為pod的hostname。比如,一個Pod,其註解為pod.beta.kubernetes.io/hostname: my-pod-name,那麼該Pod的hostname會被設定為my-pod-name。 v1.2中還引入了一個beta特性,使用者指定Pod註解,pod.beta.kubernetes.io/subdomain,來指定Pod的subdomain。比如,一個Pod,其hostname註解設定為“foo”,subdomain註解為“bar”,名稱空間為“my-namespace”,那麼它最終的FQDN就是“foo.bar.my-namespace.svc.cluster.local”。 在v1.3版本中,PodSpec有了hostnamesubdomain欄位,用於指定Pod的hostname和subdomain。它的優先順序則高於上面提到的pod.beta.kubernetes.io/hostnamepod.beta.kubernetes.io/subdomain

1.4.4 外部訪問Service的問題

先明確這樣幾個IP:

  • Node IP:Node主機的IP,與它是否屬於K8s無關。
  • Pod IP:是Dokcer Engine通過docker0網橋的IP地址段進行分配的,通常是一個虛擬的二層網路。k8s中一個Pod訪問另一個Pod就是通過Pod IP。
  • Cluster IP:僅用於Service物件,屬於k8s的內部IP,外界無法直接訪問。

(1)NodePort

在Service的yaml中定義NodePort,k8s為叢集中每個Node都增加對這個埠的監聽,使用這種方式往往需要一個獨立與k8s之外的負載均衡器作為流量的入口。

(2)使用External IP

  • 執行Hello World應用程式的五個例項。
  • 建立一個暴露外部IP地址的Service物件。
  • 使用Service物件訪問正在執行的應用程式。

使用deployment建立暴露的Service物件:

~ kubectl expose deployment hello-world --type=LoadBalancer --name=my-service

顯示關於Service的資訊:

~ kubectl get services my-service

 NAME         CLUSTER-IP     EXTERNAL-IP      PORT(S)    AGE
 my-service   10.3.245.137   104.198.205.71   8080/TCP   54s

~  kubectl describe services my-service
 Name:           my-service
 Namespace:      default
 Labels:         run=load-balancer-example
 Selector:       run=load-balancer-example
 Type:           LoadBalancer
 IP:             10.3.245.137
 LoadBalancer Ingress:   104.198.205.71
 Port:           <unset> 8080/TCP
 NodePort:       <unset> 32377/TCP
 Endpoints:      10.0.0.6:8080,10.0.1.6:8080,10.0.1.7:8080 + 2 more...
 Session Affinity:   None
 Events:

在此例子中,外部IP地址為104.198.205.71。還要注意Port的值。在這個例子中,埠是8080。在上面的輸出中,您可以看到該服務有多個端點:10.0.0.6:8080,10.0.1.6:8080,10.0.1.7:8080 + 2 more…。這些是執行Hello World應用程式的pod的內部地址。

使用外部IP地址訪問Hello World應用程式:

~  curl http://<external-ip>:<port>
 Hello Kubernetes!

刪除服務

~ kubectl delete services my-service
~ kubectl delete deployment hello-world

1.5 Ingress

通常情況下,service和pod僅可在叢集內部網路中通過IP地址訪問。所有到達邊界路由器的流量或被丟棄或被轉發到其他地方。Ingress是授權入站連線到達叢集服務的規則集合。你可以給Ingress配置提供外部可訪問的URL、負載均衡、SSL、基於名稱的虛擬主機等。使用者通過POST Ingress資源到API server的方式來請求ingress。 Ingress controller負責實現Ingress,通常使用負載平衡器,它還可以配置邊界路由和其他前端,這有助於以HA方式處理流量。

最簡化的Ingress配置:

 apiVersion: extensions/v1beta1
 kind: Ingress
 metadata:
   name: test-ingress
 spec:
   rules:
   - http:
       paths:
       - path: /testpath
        backend:
           serviceName: test
           servicePort: 80
      - path: /bar
        backend:
          serviceName: s2
          servicePort: 80
  • 1-4行:跟Kubernetes的其他配置一樣,ingress的配置也需要apiVersion,kind和metadata欄位。配置檔案的詳細說明請檢視部署應用, 配置容器和 使用resources.
  • 5-7行: Ingress spec 中包含配置一個loadbalancer或proxy server的所有資訊。最重要的是,它包含了一個匹配所有入站請求的規則列表。目前ingress只支援http規則。

  • 8-9行:每條http規則包含以下資訊:一個host配置項(比如for.bar.com,在這個例子中預設是*),path列表(比如:/testpath),每個path都關聯一個backend(比如test:80)。在loadbalancer將流量轉發到backend之前,所有的入站請求都要先匹配host和path。

  • 10-12行:backend是一個service:port的組合。Ingress的流量被轉發到它所匹配的backend。

配置TLS證書

你可以通過指定包含TLS私鑰和證書的secret來加密Ingress。 目前,Ingress僅支援單個TLS埠443,並假定TLS termination。 如果Ingress中的TLS配置部分指定了不同的主機,則它們將根據通過SNI TLS擴充套件指定的主機名(假如Ingress controller支援SNI)在多個相同埠上進行復用。 TLS secret中必須包含名為tls.crt和tls.key的金鑰,這裡麵包含了用於TLS的證書和私鑰,例如:

(1)建立Secret

apiVersion: v1
data:
  tls.crt: base64 encoded cert
  tls.key: base64 encoded key
kind: Secret
metadata:
  name: testsecret
  namespace: default
type: Opaque

(2)建立Ingress:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: no-rules-map
spec:
  tls:
    - secretName: testsecret
  backend:
    serviceName: s1
    servicePort: 80

2 高可用

Kubernetes服務本身的穩定執行對叢集管理至關重要,影響服務穩定的因素一般來說分為兩種,一種是服務本身異常或者服務所在機器宕機,另一種是因為網路問題導致的服務不可用。本文將從儲存層、管理層、接入層三個方面介紹高可用Kubernetes叢集的原理。

2.1 Etcd高可用方案

Kubernetes的儲存層使用的是Etcd。Etcd是CoreOS開源的一個高可用強一致性的分散式儲存服務,Kubernetes使用Etcd作為資料儲存後端,把需要記錄的pod、rc、service等資源資訊儲存在Etcd中。

Etcd使用raft演算法將一組主機組成叢集,raft 叢集中的每個節點都可以根據叢集執行的情況在三種狀態間切換:follower, candidate 與 leader。leader 和 follower 之間保持心跳。如果follower在一段時間內沒有收到來自leader的心跳,就會轉為candidate,發出新的選主請求。

叢集初始化的時候內部的節點都是follower節點,之後會有一個節點因為沒有收到leader的心跳轉為candidate節點,發起選主請求。當這個節點獲得了大於一半節點的投票後會轉為leader節點。當leader節點服務異常後,其中的某個follower節點因為沒有收到leader的心跳轉為candidate節點,發起選主請求。只要叢集中剩餘的正常節點數目大於叢集內主機數目的一半,Etcd叢集就可以正常對外提供服務。

當叢集內部的網路出現故障叢集可能會出現“腦裂”問題,這個時候叢集會分為一大一小兩個叢集(奇數節點的叢集),較小的叢集會處於異常狀態,較大的叢集可以正常對外提供服務。

2.2 Master高可用方案

Master上有三個關鍵的服務:apiserver、controller-manager和scheduler,這三個不一定要執行在一臺主機上。

2.2.1 controller-manager和scheduler的選舉配置

Kubernetes的管理層服務包括kube-scheduler和kube-controller-manager。kube-scheduer和kube-controller-manager使用一主多從的高可用方案,在同一時刻只允許一個服務處以具體的任務。Kubernetes中實現了一套簡單的選主邏輯,依賴Etcd實現scheduler和controller-manager的選主功能。

如果scheduler和controller-manager在啟動的時候設定了leader-elect引數,它們在啟動後會先嚐試獲取leader節點身份,只有在獲取leader節點身份後才可以執行具體的業務邏輯。它們分別會在Etcd中建立kube-scheduler和kube-controller-manager的endpoint,endpoint的資訊中記錄了當前的leader節點資訊,以及記錄的上次更新時間。leader節點會定期更新endpoint的資訊,維護自己的leader身份。每個從節點的服務都會定期檢查endpoint的資訊,如果endpoint的資訊在時間範圍內沒有更新,它們會嘗試更新自己為leader節點。

scheduler服務以及controller-manager服務之間不會進行通訊,利用Etcd的強一致性,能夠保證在分散式高併發情況下leader節點的全域性唯一性。整體方案如下圖所示:

這裡寫圖片描述

當叢集中的leader節點服務異常後,其它節點的服務會嘗試更新自身為leader節點,當有多個節點同時更新endpoint時,由Etcd保證只有一個服務的更新請求能夠成功。通過這種機制sheduler和controller-manager可以保證在leader節點宕機後其它的節點可以順利選主,保證服務故障後快速恢復。當叢集中的網路出現故障時對服務的選主影響不是很大,因為scheduler和controller-manager是依賴Etcd進行選主的,在網路故障後,可以和Etcd通訊的主機依然可以按照之前的邏輯進行選主,就算叢集被切分,Etcd也可以保證同一時刻只有一個節點的服務處於leader狀態。

2.2.2 apiserver的高可用

Kubernetes的接入層服務主要是kube-apiserver。apiserver本身是無狀態的服務,它的主要任務職責是把資源資料儲存到Etcd中,後續具體的業務邏輯是由scheduler和controller-manager執行的。所以可以同時起多個apiserver服務,使用nginx把客戶端的流量轉發到不同的後端apiserver上實現接入層的高可用。具體的實現如下圖所示:

這裡寫圖片描述

接入層的高可用分為兩個部分,一個部分是多活的apiserver服務,另一個部分是一主一備的nginx服務。

2.3 Keepalived簡介

Keepalived軟體起初是專為LVS負載均衡軟體設計的,用來管理並監控LVS集群系統中各個服務節點的狀態,後來又加入了可以實現高可用的VRRP功能。因此,Keepalived除了能夠管理LVS軟體外,還可以作為其他服務(例如:NginxHaproxyMySQL等)的高可用解決方案軟體。Keepalived軟體主要是通過VRRP協議實現高可用功能的。VRRP是Virtual Router RedundancyProtocol(虛擬路由器冗餘協議)的縮寫,VRRP出現的目的就是為了解決靜態路由單點故障問題的,它能夠保證當個別節點宕機時,整個網路可以不間斷地執行。所以,Keepalived 一方面具有配置管理LVS的功能,同時還具有對LVS下面節點進行健康檢查的功能,另一方面也可實現系統網路服務的高可用功能。

故障切換轉移原理

Keepalived高可用服務對之間的故障切換轉移,是通過 VRRP (Virtual Router Redundancy Protocol ,虛擬路由器冗餘協議)來實現的。在 Keepalived服務正常工作時,主 Master節點會不斷地向備節點發送(多播的方式)心跳訊息,用以告訴備Backup節點自己還活看,當主 Master節點發生故障時,就無法傳送心跳訊息,備節點也就因此無法繼續檢測到來自主 Master節點的心跳了,於是呼叫自身的接管程式,接管主Master節點的 IP資源及服務。而當主 Master節點恢復時,備Backup節點又會釋放主節點故障時自身接管的IP資源及服務,恢復到原來的備用角色。

3 容器網路

3.1 docker預設容器網路

在預設情況下會看到三個網路,它們是Docker Deamon程序建立的。它們實際上分別對應了Docker過去的三種『網路模式』,可以使用docker network ls來檢視:

[email protected]:~$ sudo docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
18d934794c74        bridge              bridge              local
f7a7b763f013        host                host                local
697354257ae3        none                null                local

這 3 個網路包含在 Docker 實現中。執行一個容器時,可以使用 the –net標誌指定您希望在哪個網路上執行該容器。您仍然可以使用這 3 個網路。

  • bridge 網路表示所有 Docker 安裝中都存在的 docker0 網路。除非使用 docker run –net=選項另行指定,否則 Docker 守護程序預設情況下會將容器連線到此網路。在主機上使用 ifconfig命令,可以看到此網橋是主機的網路堆疊的一部分。
  • none 網路在一個特定於容器的網路堆疊上添加了一個容器。該容器缺少網路介面。
  • host 網路在主機網路堆疊上新增一個容器。您可以發現,容器中的網路配置與主機相同。

3.2 跨主機通訊的方案

和host共享network namespace

這種接入模式下,不會為容器建立網路協議棧,即容器沒有獨立於host的network namespace,但是容器的其他namespace(如IPC、PID、Mount等)還是和host的namespace獨立的。容器中的程序處於host的網路環境中,與host共用L2-L4的網路資源。該方式的優點是,容器能夠直接使用host的網路資源與外界進行通訊,沒有額外的開銷(如NAT),缺點是網路的隔離性差,容器和host所使用的埠號經常會發生衝突。

和host共享物理網絡卡

2與1的區別在於,容器和host共享物理網絡卡,但容器擁有獨立於host的network namespace,容器有自己的MAC地址、IP地址、埠號。這種接入方式主要使用SR-IOV技術,每個容器被分配一個VF,直接通過PCIe網絡卡與外界通訊,優點是旁路了host kernel不佔任何計算資源,而且IO速度較快,缺點是VF數量有限且對容器遷移的支援不足。

Behind the POD

這種方式是Google在Kubernetes中的設計中提出來的。Kubernetes中,POD是指一個可以被建立、銷燬、排程、管理的最小的部署單元,一個POD有一個基礎容器以及一個或一組應用容器,基礎容器對應一個獨立的network namespace並擁有一個其它POD可見的IP地址(以IP A.B.C.D指代),應用容器間則共享基礎容器的network namespace(包括MAC、IP以及埠號等),還可以共享基礎容器的其它的namespace(如IPC、PID、Mount等)。POD作為一個整體連線在host的vbridge/vswitch上,使用IP地址A.B.C.D與其它POD進行通訊,不同host中的POD處於不同的subnet中,同一host中的不同POD處於同一subnet中。這種方式的優點是一些業務上密切相關的容器可以共享POD的全部資源(它們一般不會產生資源上的衝突),而這些容器間的通訊高效便利。

3.3 Flannel

在k8s的網路設計中,服務以POD為單位,每個POD的IP地址,容器通過Behind the POD方式接入網路(見“容器的網路模型”),一個POD中可包含多個容器,這些容器共享該POD的IP地址。另外,k8s要求容器的IP地址都是全網可路由的,那麼顯然docker0+iptables的NAT方案是不可行的。

實現上述要求其實有很多種組網方法,Flat L3是一種(如Calico),Hierarchy L3(如Romana)是一種,另外L3 Overlay也是可以的,CoreOS就採用L3 Overlay的方式設計了flannel, 並規定每個host下各個POD屬於同一個subnet,不同的host/VM下的POD屬於不同subnet。我們來看flannel的架構,控制平面上host本地的flanneld負責從遠端的ETCD叢集同步本地和其它host上的subnet資訊,併為POD分配IP地址。資料平面flannel通過UDP封裝來實現L3 Overlay,既可以選擇一般的TUN裝置又可以選擇VxLAN裝置(注意,由於圖來源不同,請忽略具體的IP地址)。

這裡寫圖片描述

這裡寫圖片描述

flannel是CoreOS提供用於解決Dokcer叢集跨主機通訊的覆蓋網路工具。它的主要思路是:預先留出一個網段,每個主機使用其中一部分,然後每個容器被分配不同的ip;讓所有的容器認為大家在同一個直連的網路,底層通過UDP/VxLAN等進行報文的封裝和轉發。

這裡寫圖片描述

flannel預設使用8285埠作為UDP封裝報文的埠,VxLan使用8472埠。那麼一條網路報文是怎麼從一個容器傳送到另外一個容器的呢?

  1. 容器直接使用目標容器的ip訪問,預設通過容器內部的eth0傳送出去。
  2. 報文通過veth pair被髮送到vethXXX
  3. vethXXX是直接連線到虛擬交換機docker0的,報文通過虛擬bridge docker0傳送出去。
  4. 查詢路由表,外部容器ip的報文都會轉發到flannel0虛擬網絡卡,這是一個P2P的虛擬網絡卡,然後報文就被轉發到監聽在另一端的flanneld
  5. flanneld通過etcd維護了各個節點之間的路由表,把原來的報文UDP封裝一層,通過配置的iface傳送出去。
  6. 報文通過主機之間的網路找到目標主機。
  7. 報文繼續往上,到傳輸層,交給監聽在8285埠的flanneld程式處理。
  8. 資料被解包,然後傳送給flannel0虛擬網絡卡。
  9. 查詢路由表,發現對應容器的報文要交給docker0
  10. docker0找到連到自己的容器,把報文傳送過去。

應用部署工具Helm