DaemonSet 的使用
通過該控制器的名稱我們可以看出它的用法:Daemon,就是用來部署守護進程的,DaemonSet
用於在每個Kubernetes
節點中將守護進程的副本作為后台進程運行,說白了就是在每個節點部署一個Pod
副本,當節點加入到Kubernetes
集群中,Pod
會被調度到該節點上運行,當節點從集群只能夠被移除后,該節點上的這個Pod
也會被移除,當然,如果我們刪除DaemonSet
,所有和這個對象相關的Pods
都會被刪除。
在哪種情況下我們會需要用到這種業務場景呢?其實這種場景還是比較普通的,比如:
- 集群存儲守護程序,如
glusterd
、ceph
要部署在每個節點上以提供持久性存儲; - 節點監視守護進程,如
Prometheus
監控集群,可以在每個節點上運行一個node-exporter
進程來收集監控節點的信息; - 日志收集守護程序,如
fluentd
或logstash
,在每個節點上運行以收集容器的日志
這里需要特別說明的一個就是關於DaemonSet
運行的Pod
的調度問題,正常情況下,Pod
運行在哪個節點上是由Kubernetes
的調度器策略來決定的,然而,由DaemonSet
控制器創建的Pod
實際上提前已經確定了在哪個節點上了(Pod
創建時指定了.spec.nodeName
),所以:
DaemonSet
並不關心一個節點的unshedulable
字段,這個我們會在后面的調度章節和大家講解的。DaemonSet
可以創建Pod
,即使調度器還沒有啟動,這點非常重要。
下面我們直接使用一個示例來演示下,在每個節點上部署一個Nginx Pod
:(nginx-ds.yaml)
kind: DaemonSet apiVersion: extensions/v1beta1 metadata: name: nginx-ds labels: k8s-app: nginx spec: template: metadata: labels: k8s-app: nginx spec: containers: - image: nginx:1.7.9 name: nginx ports: - name: http containerPort: 80
然后直接創建即可:
$ kubectl create -f nginx-ds.yaml
然后我們可以觀察下Pod
是否被分布到了每個節點上:
$ kubectl get nodes
$ kubectl get pods -o wide
StatefulSet 的使用
在學習StatefulSet
這種控制器之前,我們就得先弄明白一個概念:什么是有狀態服務?什么是無狀態服務?
-
無狀態服務(Stateless Service):該服務運行的實例不會在本地存儲需要持久化的數據,並且多個實例對於同一個請求響應的結果是完全一致的,比如前面我們講解的
WordPress
實例,我們是不是可以同時啟動多個實例,但是我們訪問任意一個實例得到的結果都是一樣的吧?因為他唯一需要持久化的數據是存儲在MySQL
數據庫中的,所以我們可以說WordPress
這個應用是無狀態服務,但是MySQL
數據庫就不是了,因為他需要把數據持久化到本地。 -
有狀態服務(Stateful Service):就和上面的概念是對立的了,該服務運行的實例需要在本地存儲持久化數據,比如上面的
MySQL
數據庫,你現在運行在節點A,那么他的數據就存儲在節點A上面的,如果這個時候你把該服務遷移到節點B去的話,那么就沒有之前的數據了,因為他需要去對應的數據目錄里面恢復數據,而此時沒有任何數據。
現在大家對有狀態和無狀態有一定的認識了吧,比如我們常見的 WEB 應用,是通過session
來保持用戶的登錄狀態的,如果我們將session
持久化到節點上,那么該應用就是一個有狀態的服務了,因為我現在登錄進來你把我的session
持久化到節點A上了,下次我登錄的時候可能會將請求路由到節點B上去了,但是節點B上根本就沒有我當前的session
數據,就會被認為是未登錄狀態了,這樣就導致我前后兩次請求得到的結果不一致了。所以一般為了橫向擴展,我們都會把這類 WEB 應用改成無狀態的服務,怎么改?將session
數據存入一個公共的地方,比如redis
里面,是不是就可以了,對於一些客戶端請求API
的情況,我們就不使用session
來保持用戶狀態,改成用token
也是可以的。
無狀態服務利用我們前面的Deployment
或者RC
都可以很好的控制,對應有狀態服務,需要考慮的細節就要多很多了,容器化應用程序最困難的任務之一,就是設計有狀態分布式組件的部署體系結構。由於無狀態組件可能沒有預定義的啟動順序、集群要求、點對點 TCP 連接、唯一的網絡標識符、正常的啟動和終止要求等,因此可以很容易地進行容器化。諸如數據庫,大數據分析系統,分布式 key/value 存儲和 message brokers 可能有復雜的分布式體系結構,都可能會用到上述功能。為此,Kubernetes
引入了StatefulSet
資源來支持這種復雜的需求。
StatefulSet
類似於ReplicaSet
,但是它可以處理Pod
的啟動順序,為保留每個Pod
的狀態設置唯一標識,同時具有以下功能:
- 穩定的、唯一的網絡標識符
- 穩定的、持久化的存儲
- 有序的、優雅的部署和縮放
- 有序的、優雅的刪除和終止
- 有序的、自動滾動更新
創建StatefulSet
接下來我們來給大家演示下StatefulSet
對象的使用方法,在開始之前,我們先准備兩個1G的存儲卷(PV),在后面的課程中我們也會和大家詳細講解 PV 和 PVC 的使用方法的,這里我們先不深究:(pv001.yaml)
apiVersion: v1 kind: PersistentVolume metadata: name: pv001 labels: release: stable spec: capacity: storage: 1Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Recycle hostPath: path: /tmp/data
另外一個只需要把 name 改成 pv002即可,然后創建:
$ kubectl create -f pv001.yaml && kubectl create -f pv002.yaml persistentvolume "pv001" created persistentvolume "pv002" created $ kubectl get pv kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv001 1Gi RWO Recycle Available 12s pv002 1Gi RWO Recycle Available 11s
可以看到成功創建了兩個 PV對象,狀態是:Available。
然后我們使用StatefulSet
來創建一個 Nginx 的 Pod,對於這種類型的資源,我們一般是通過創建一個Headless Service
類型的服務來暴露服務,將clusterIP
設置為None
就是一個無頭的服務:(statefulset-demo.yaml)
apiVersion: v1 kind: Service metadata: name: nginx spec: ports: - port: 80 name: web clusterIP: None selector: app: nginx role: stateful --- apiVersion: apps/v1beta1 kind: StatefulSet metadata: name: web spec: serviceName: "nginx" replicas: 2 template: metadata: labels: app: nginx role: stateful spec: containers: - name: nginx image: cnych/nginx-slim:0.8 ports: - containerPort: 80 name: web volumeMounts: - name: www mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: www spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 1Gi
注意上面的 YAML 文件中和volumeMounts
進行關聯的是一個新的屬性:volumeClaimTemplates,該屬性會自動聲明一個 pvc 對象和 pv 進行管理:
然后這里我們開啟兩個終端窗口。在第一個終端中,使用 kubectl get 來查看 StatefulSet 的 Pods 的創建情況。
$ kubectl get pods -w -l role=stateful
在另一個終端中,使用 kubectl create 來創建定義在 statefulset-demo.yaml 中的 Headless Service 和 StatefulSet。
$ kubectl create -f statefulset-demo.yaml
service "nginx" created statefulset.apps "web" created
檢查 Pod 的順序索引
對於一個擁有 N 個副本的 StatefulSet,Pod 被部署時是按照 {0..N-1}的序號順序創建的。在第一個終端中我們可以看到如下的一些信息:
$ kubectl get pods -w -l role=stateful NAME READY STATUS RESTARTS AGE web-0 0/1 Pending 0 0s web-0 0/1 Pending 0 0s web-0 0/1 ContainerCreating 0 0s web-0 1/1 Running 0 19s web-1 0/1 Pending 0 0s web-1 0/1 Pending 0 0s web-1 0/1 ContainerCreating 0 0s web-1 1/1 Running 0 18s
請注意在 web-0 Pod 處於 Running 和 Ready 狀態后 web-1 Pod 才會被啟動。
如同 StatefulSets 概念中所提到的, StatefulSet 中的 Pod 擁有一個具有穩定的、獨一無二的身份標志。這個標志基於 StatefulSet 控制器分配給每個 Pod 的唯一順序索引。 Pod 的名稱的形式為<statefulset name>-<ordinal index>
。web StatefulSet 擁有兩個副本,所以它創建了兩個 Pod:web-0 和 web-1。
上面的命令創建了兩個 Pod,每個都運行了一個 NGINX web 服務器。獲取 nginx Service 和 web StatefulSet 來驗證是否成功的創建了它們。
$ kubectl get service nginx NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx None <none> 80/TCP 12s $ kubectl get statefulset web NAME DESIRED CURRENT AGE web 2 1 20s
使用穩定的網絡身份標識
每個 Pod 都擁有一個基於其順序索引的穩定的主機名。使用 kubectl exec 在每個 Pod 中執行 hostname 。
$ for i in 0 1; do kubectl exec web-$i -- sh -c 'hostname'; done web-0 web-1
然后我們使用 kubectl run 運行一個提供 nslookup 命令的容器。通過對 Pod 的主機名執行 nslookup,你可以檢查他們在集群內部的 DNS 地址。
$ kubectl run -i --tty --image busybox dns-test --restart=Never --rm /bin/sh nslookup web-0.nginx Server: 10.0.0.10 Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local Name: web-0.nginx Address 1: 10.244.1.6 nslookup web-1.nginx Server: 10.0.0.10 Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local Name: web-1.nginx Address 1: 10.244.2.6
headless service 的 CNAME 指向 SRV 記錄(記錄每個 Running 和 Ready 狀態的 Pod)。SRV 記錄指向一個包含 Pod IP 地址的記錄表項。
然后我們再來看下刪除 StatefulSet 下面的 Pod:
在一個終端中查看 StatefulSet 的 Pod:
$ kubectl get pod -w -l role=stateful
在另一個終端中使用 kubectl delete 刪除 StatefulSet 中所有的 Pod。
$ kubectl delete pod -l role=stateful pod "web-0" deleted pod "web-1" deleted
等待 StatefulSet 重啟它們,並且兩個 Pod 都變成 Running 和 Ready 狀態。
$ kubectl get pod -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 0/1 ContainerCreating 0 0s web-0 1/1 Running 0 2s web-1 0/1 Pending 0 0s web-1 0/1 Pending 0 0s web-1 0/1 ContainerCreating 0 0s web-1 1/1 Running 0 34s
然后再次使用 kubectl exec 和 kubectl run 查看 Pod 的主機名和集群內部的 DNS 表項。
$ for i in 0 1; do kubectl exec web-$i -- sh -c 'hostname'; done web-0 web-1 $ kubectl run -i --tty --image busybox dns-test --restart=Never --rm /bin/sh nslookup web-0.nginx Server: 10.0.0.10 Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local Name: web-0.nginx Address 1: 10.244.1.7 nslookup web-1.nginx Server: 10.0.0.10 Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local Name: web-1.nginx Address 1: 10.244.2.8
我們可以看到Pod 的序號、主機名、SRV 條目和記錄名稱沒有改變,但和 Pod 相關聯的 IP 地址可能會發生改變。所以說這就是為什么不要在其他應用中使用 StatefulSet 中的 Pod 的 IP 地址進行連接,這點很重要。一般情況下我們直接通過 SRV 記錄連接就行:web-0.nginx、web-1.nginx,因為他們是穩定的,並且當你的 Pod 的狀態變為 Running 和 Ready 時,你的應用就能夠發現它們的地址。
同樣我們可以查看 PV、PVC的最終綁定情況:
$ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv001 1Gi RWO Recycle Bound default/www-web-0 1h pv002 1Gi RWO Recycle Bound default/www-web-1 1h $ kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE www-web-0 Bound pv001 1Gi RWO 22m www-web-1 Bound pv002 1Gi RWO 22m
我們會在下節課開始和大家講解存儲卷的使用的,所以這里我們先暫時不展開講解了,避免大家糊塗。
當然 StatefulSet 還擁有其他特性,在實際的項目中,我們還是很少回去直接通過 StatefulSet 來部署我們的有狀態服務的,除非你自己能夠完全能夠 hold 住,對於一些特定的服務,我們可能會使用更加高級的 Operator 來部署,比如 etcd-operator、prometheus-operator 等等,這些應用都能夠很好的來管理有狀態的服務,而不是單純的使用一個 StatefulSet 來部署一個 Pod就行,因為對於有狀態的應用最重要的還是數據恢復、故障轉移等等。