K8S學習筆錄 - Headless與StatefulSet簡單應用

原文連接node

將以Redis爲例子,學習使用Headless Service與StatefulSet完成Redis集羣的建立。 以實現一個無單點故障、高可用、可動態擴展的Redis集羣。nginx

什麼是Headless服務

Headless服務是一種特殊的服務,其clusterIP值爲None。 這樣設置在運行時不會被分配ClusterIP,而在訪問過程當中,將會返回包含了其label指定的所有Pod列表,而後客戶端程序能夠自定義如何處理這個Pod列表。git

一般狀況下,Service若是有一個集羣IP,則在DNS查找Service時會返回該IP的記錄。 可是若是Service不須要集羣IP,則DNS將會返回Pod的IP列表。github

Headless服務示例

當前擁有一個Deployment資源,它管理了3個帶有 app: nginx 標籤的副本。redis

apiVersion: v1
kind: Service
metadata:
 name: nginx
 labels:
 app: nginx
spec:
 publishNotReadyAddresses: true # 是否發佈未就緒的Pod
 ports:
 - port: 80
 targetPort: http
 clusterIP: None # Headless服務須要clusterIP爲None
 selector:
 app: nginx
複製代碼

經過DNS發現Pod。 查看該服務在DNS上的對應記錄,會發現有對應的三個Pod的IP。json

$ nslookup nginx.default.svc.cluster.local 10.96.0.10
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   nginx.default.svc.cluster.local
Address: 172.40.0.2
Name:   nginx.default.svc.cluster.local
Address: 172.32.0.3
Name:   nginx.default.svc.cluster.local
Address: 172.32.0.4
複製代碼

另外會發現,這個Service上使用了一個 publishNotReadyAddresses 的屬性。api

正常狀況下Pod只有就緒後才能被DNS解析。而publishNotReadyAddresses爲true時,即便Pod未到就緒狀態,也能被DNS所解析。 Pod的就緒狀態由Pod的就緒探針決定。bash

爲何須要StatefulSet

在Kubernetes中RC、Deployment、ReplicaSet、DaemonSet等等都是面向無狀態服務的。 他們管理的Pod的IP、名字等都是隨機的,而在一些狀況下,這是不可行的。markdown

StatefulSet顧名思義因此有狀態的集合。它主要是爲了解決有狀態服務的問題。 與Deployment相似,StatefulSet管理了基於相同容器定義的一組Pod。網絡

但和Deployment不一樣的是,StatefulSet爲它們的每一個Pod維護了一個固定的ID。 這些Pod是基於相同的聲明來建立的,可是不能相互替換,不管怎麼調度,每一個Pod都有一個永久不變的ID。

StatefulSet的配置結構

type StatefulSet struct {
    metav1.TypeMeta `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`

    // StatefulSet定義
    Spec StatefulSetSpec `json:"spec,omitempty"`

    // StatefulSet狀態,可過期
    Status StatefulSetStatus `json:"status,omitempty"`
}

type StatefulSetSpec struct {

    // 副本數量
    Replicas *int32 `json:"replicas,omitempty"`

    // Pod的選擇器
    Selector *metav1.LabelSelector `json:"selector"`

    // Pod模板
    Template v1.PodTemplateSpec `json:"template"`

    // 一組存儲卷申請模板
    // StatefulSet會參考模板爲每一個Pod分配一個專屬的存儲卷。
    // 此字段的每一個模板項必須在Pod模板的容器配置中至少有一個匹配的volumeMount(名稱同樣便可)。
    // 在模板中卷在同名稱的狀況下,該字段對應的卷優先於其餘任何卷。
    VolumeClaimTemplates []v1.PersistentVolumeClaim `json:"volumeClaimTemplates,omitempty"`

    // 簡單的理解爲Headless Service名稱,用來爲StatefulSet提供可靠的網絡標識,須要在StatefulSet存在以前就存在
    // Pod須要在建立後得到一個DNS/hostnames,格式爲podName.serviceName.default.svc.cluster.local
    // podName則由StatefulSet進行管理
    ServiceName string `json:"serviceName"`

    // podManagementPolicy控制Pod再建立和擴/縮容時的方案。
    // 該字段有兩個值OrderedReady(默認)和Parallel。
    // OrderedReady在Pod建立時名字由0開始依次遞增,例如pod-0、pod-1。控制器會依次建立每一個Pod。
    // 在縮容狀況下,Pod會按照名字從大到小依次刪除。
    // Parallet會一次性建立/刪除全部的Pod,而不會等待上一個完成。
    PodManagementPolicy PodManagementPolicyType `json:"podManagementPolicy,omitempty"`

    // Pod更新策略
    UpdateStrategy StatefulSetUpdateStrategy `json:"updateStrategy,omitempty"`

    // 保存的歷史版本數量,用於回滾Pod,默認值爲10
    RevisionHistoryLimit *int32 `json:"revisionHistoryLimit,omitempty"`
}

// PodManagementPolicyType定義了在StatefulSet中Pod的建立規則
type PodManagementPolicyType string
const (
    // 按照嚴格的順序依次處理擴/縮容的狀況。同一時間最多隻處理一個Pod,處理完一個再處理下一個。
    OrderedReadyPodManagement PodManagementPolicyType = "OrderedReady"

    // 同時處理全部的Pod。
    ParallelPodManagement PodManagementPolicyType = "Parallel"
)
複製代碼

建立Redis集羣

使用StatefulSet須要使用 Headless ServicePersistentVolume

Headless服務用來幫助StatefulSet識別Pod的網絡標識。

PersistenVolumn負責實現持久化存儲,例以下面例子中的redis node id。

NFS服務

搭建過程不展開了,網上不少。

持久存儲卷鬚要使用網絡文件服務來支持。NFS搭建起來比較簡單,因此使用了NFS。

因爲我有的集羣節點是分佈在兩個不一樣機房的物理機,因此設置了IP1和IP2兩個權限。

$ cat /etc/exports
# /etc/exports: the access control list for filesystems which may be exported
# to NFS clients. See exports(5).
#
# Example for NFSv2 and NFSv3:
# /srv/homes hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check)
#
# Example for NFSv4:
# /srv/nfs4 gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check)
# /srv/nfs4/homes gss/krb5i(rw,sync,no_subtree_check)
#
/var/nfs/redis/pv1 IP1/0(rw,all_squash) IP2/0(rw,insecure,all_squash)
/var/nfs/redis/pv2 IP1/0(rw,all_squash) IP2/0(rw,insecure,all_squash)
/var/nfs/redis/pv3 IP1/0(rw,all_squash) IP2/0(rw,insecure,all_squash)
/var/nfs/redis/pv4 IP1/0(rw,all_squash) IP2/0(rw,insecure,all_squash)
/var/nfs/redis/pv5 IP1/0(rw,all_squash) IP2/0(rw,insecure,all_squash)
/var/nfs/redis/pv6 IP1/0(rw,all_squash) IP2/0(rw,insecure,all_squash)
複製代碼

最後重啓nfs服務

建立Redis通用配置類

爲方便收斂集羣中redis的配置。建立文件redis.conf,並建立一個ConfigMap。

appendonly yes
cluster-enabled yes
cluster-config-file /var/redis/node.conf
cluster-node-timeout 5000
dir /var/redis
port 6379
複製代碼
$ kubectl describe cm redis-conf
Name:         redis-conf
Namespace:    default
Labels:       <none>
Annotations:  <none>

Data
====
redis.conf:
----
  appendonly yes
  cluster-enabled yes
  cluster-config-file /var/redis/node.conf
  cluster-node-timeout 5000
  dir /var/redis
  port 6379

Events:  <none>
複製代碼

建立PersistentVolumn做持久存儲卷

建立持久存儲卷資源,目前對這一起不是很瞭解,只知道配置中將NFS中對應的path掛載到節點上用做持久存儲。

apiVersion: v1
kind: PersistentVolume
metadata:
 name: nfs-pv1 # pv1 pv2 ... pv6
spec:
 capacity:
 storage: 200M
 accessModes:
 - ReadWriteMany
 nfs:
 server: NFS_SERVER
 path: /var/nfs/redis/pv1 # pv1 pv2 ... pv 6
---
...省略
---
apiVersion: v1
kind: PersistentVolume
metadata:
 name: nfs-pv6
spec:
 capacity:
 storage: 200M
 accessModes:
 - ReadWriteMany
 nfs:
 server: NFS_SERVER
 path: /var/nfs/redis/pv6
複製代碼

建立Headless Service

建立無頭服務,向StatefulSet提供Pod的列表。

apiVersion: v1
kind: Service
metadata:
 name: redis-service
 labels:
 app: redis
spec:
 ports:
 - name: redis-port
 port: 6379
 clusterIP: None
 selector:
 app: redis
 appCluster: redis-cluster
複製代碼

建立StatefulSet

建立有狀態集合資源。

apiVersion: apps/v1
kind: StatefulSet
metadata:
 name: redis-app
spec:
 serviceName: redis-service
 replicas: 6
 selector:
 matchLabels:
 app: redis
 template:
 metadata:
 labels:
 app: redis
 appCluster: redis-cluster
 spec:
 containers:
 - name: redis
 image: redis
 command:
 - "redis-server"
 args:
 - "/etc/redis/redis.conf"
 - "--protected-mode no"
 ports:
 - name: redis
 containerPort: 6379
 protocol: "TCP"
 volumeMounts:
 - name: "redis-conf"
 mountPath: "/etc/redis"
 - name: "redis-data"
 mountPath: "/var/redis"
 volumes:
 - name: "redis-conf"
 configMap:
 name: "redis-conf"
 items:
 - key: "redis.conf"
 path: "redis.conf"
 volumeClaimTemplates:
 - metadata:
 name: redis-data
 spec:
 accessModes: ["ReadWriteMany"]
 resources:
 requests:
 storage: 200M
複製代碼

初始化Redis集羣

建立一個Redis集羣的管理Pod,並依次將全部的Redis節點加入集羣。

$ redis-cli --cluster create \
          172.32.0.3:6379 \
          172.32.0.4:6379 \
          172.32.0.5:6379 \
          172.40.0.2:6379 \
          172.40.0.3:6379 \
          172.40.0.4:6379 \
          --cluster-replicas 1
>>> Performing hash slots allocation on 6 nodes...
(省略大量信息...)
複製代碼

在初始化完成以後,即可以經過 redis-cli -c -h redis-service 來鏈接Redis集羣了。

另外我也嘗試重啓了使某個master節點,觀察到了其slave變爲了master,而當原來的master從新上線後,變爲了slave。

須要注意的是,若是沒有使用 PersistentVolume 資源或者其餘支持持久化存儲的手段,則在某個Pod被殺死以後,其中Redis的節點信息便會丟失。 雖而後面會新建一個Pod,可是新的Pod會新建立一份節點數據,也不會自動加入到集羣中,集羣中Redis節點便會少一個。

相關文章
相關標籤/搜索