k8s部署redis叢集
部署一個多主多從的redis叢集
準備
採用StatefulSet部署有狀態服務
StatefulSet介紹
StatefulSet是deployment的一種變體。管理所有有狀態的服務,擁有固定的pod名稱,啟停順序,還需要用到共享儲存。
deployment對應的服務是service
StatefulSet對應的服務是headless service,無頭服務與service的區別是沒有Cluster IP,解析他的名稱時返回改headless service對應的全部pod的endpoint列表。
此外StatefulSet在無頭服務的基礎上,為對應的所有pod建立了一個DNS域名,域名的格式為:
$(podname).(headless server name)
FQDN: $(podname).(headless server name).namespace.svc.cluster.local
即,對於有狀態服務,我們最好使用固定的網路標識(如域名資訊)來標記節點,當然這也需要應用程式的支援(如Zookeeper就支援在配置檔案中寫入主機域名)。
StatefulSet基於Headless Service(即沒有Cluster IP的Service)為Pod實現了穩定的網路標誌(包括Pod的hostname和DNS Records),在Pod重新排程後也保持不變。同時,結合PV/PVC,StatefulSet可以實現穩定的持久化儲存,就算Pod重新排程後,還是能訪問到原先的持久化資料。
以下為使用StatefulSet部署Redis的架構,無論是Master還是Slave,都作為StatefulSet的一個副本,並且資料通過PV進行持久化,對外暴露為一個Service,接受客戶端請求
部署過程
基於StatefulSet的Redis建立步驟:
1.建立NFS儲存
2.建立PV
3.建立PVC
4.建立Configmap
5.建立headless服務
6.建立Redis StatefulSet
7.初始化Redis叢集
1.建立NFS儲存
建立NFS儲存主要是為了給Redis提供穩定的後端儲存,當Redis的Pod重啟或遷移後,依然能獲得原先的資料。這裡,我們先要建立NFS,然後通過使用PV為Redis掛載一個遠端的NFS路徑。
安裝NFS
yum -y install nfs-utils(主包提供檔案系統)
yum -y install rpcbind(提供rpc協議)
然後,新增/etc/exports檔案,用於設定需要共享的路徑:
cat > /etc/exports << EOF
/ssd/nfs/k8s/redis/pv1 192.168.10.0/24(rw,sync,no_root_squash)
/ssd/nfs/k8s/redis/pv2 192.168.10.0/24(rw,sync,no_root_squash)
/ssd/nfs/k8s/redis/pv3 192.168.10.0/24(rw,sync,no_root_squash)
/ssd/nfs/k8s/redis/pv4 192.168.10.0/24(rw,sync,no_root_squash)
/ssd/nfs/k8s/redis/pv5 192.168.10.0/24(rw,sync,no_root_squash)
/ssd/nfs/k8s/redis/pv6 192.168.10.0/24(rw,sync,no_root_squash)
EOF
建立相應目錄
mkdir -p /ssd/nfs/k8s/redis/pv{1..6}
接著,啟動NFS和rpcbind服務:
systemctl restart rpcbind
systemctl restart nfs
systemctl enable nfs
[root@itrainning-149 ~]# exportfs -v
/ssd/nfs/logdmtm
192.168.10.75(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,root_squash,all_squash)
/ssd/nfs/logdmtm
192.168.10.7(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,root_squash,all_squash)
/ssd/nfs/k8s/redis/pv1
192.168.10.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
/ssd/nfs/k8s/redis/pv2
192.168.10.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
/ssd/nfs/k8s/redis/pv3
192.168.10.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
/ssd/nfs/k8s/redis/pv4
192.168.10.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
/ssd/nfs/k8s/redis/pv5
192.168.10.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
/ssd/nfs/k8s/redis/pv6
192.168.10.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
/ssd/nfs/logmetlife
<world>(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,root_squash,all_squash)
客戶端
yum -y install nfs-utils
檢視儲存端共享
[root@work75 ~]# showmount -e 192.168.0.149
Export list for 192.168.0.149:
/ssd/nfs/logmetlife *
/ssd/nfs/k8s/redis/pv6 192.168.10.0/24
/ssd/nfs/k8s/redis/pv5 192.168.10.0/24
/ssd/nfs/k8s/redis/pv4 192.168.10.0/24
/ssd/nfs/k8s/redis/pv3 192.168.10.0/24
/ssd/nfs/k8s/redis/pv2 192.168.10.0/24
/ssd/nfs/k8s/redis/pv1 192.168.10.0/24
/ssd/nfs/logdmtm 192.168.10.7,192.168.10.75
建立PV
每一個Redis Pod都需要一個獨立的PV來儲存自己的資料,因此可以建立一個pv.yaml檔案,包含6個PV:
cat > pv.yaml << EOF
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv1
spec:
capacity:
storage: 200M
accessModes:
- ReadWriteMany
nfs:
server: 192.168.0.149
path: "/ssd/nfs/k8s/redis/pv1"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-vp2
spec:
capacity:
storage: 200M
accessModes:
- ReadWriteMany
nfs:
server: 192.168.0.149
path: "/ssd/nfs/k8s/redis/pv2"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv3
spec:
capacity:
storage: 200M
accessModes:
- ReadWriteMany
nfs:
server: 192.168.0.149
path: "/ssd/nfs/k8s/redis/pv3"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv4
spec:
capacity:
storage: 200M
accessModes:
- ReadWriteMany
nfs:
server: 192.168.0.149
path: "/ssd/nfs/k8s/redis/pv4"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv5
spec:
capacity:
storage: 200M
accessModes:
- ReadWriteMany
nfs:
server: 192.168.0.149
path: "/ssd/nfs/k8s/redis/pv5"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv6
spec:
capacity:
storage: 200M
accessModes:
- ReadWriteMany
nfs:
server: 192.168.0.149
path: "/ssd/nfs/k8s/redis/pv6"
EOF
2.建立Configmap
這裡,我們可以直接將Redis的配置檔案轉化為Configmap,這是一種更方便的配置讀取方式。配置檔案redis.conf如下
cat > redis.conf << EOF
appendonly yes
cluster-enabled yes
cluster-config-file /var/lib/redis/nodes.conf
cluster-node-timeout 5000
dir /var/lib/redis
port 6379
EOF
建立名為redis-conf的Configmap:
kubectl create configmap redis-conf --from-file=redis.conf
檢視建立的configmap:
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/lib/redis/nodes.conf
cluster-node-timeout 5000
dir /var/lib/redis
port 6379
Events: <none>
如上,redis.conf中的所有配置項都儲存到redis-conf這個Configmap中。
3.建立Headless service
Headless service是StatefulSet實現穩定網路標識的基礎,我們需要提前建立。準備檔案headless-service.yml如下:
[root@master redis]# cat headless-service.yaml
apiVersion: v1
kind: Service
metadata:
name: redis-service
labels:
app: redis
spec:
ports:
- name: redis-port
port: 6379
clusterIP: None
selector:
app: redis
建立:
kubectl create -f headless-service.yml
檢視:
4.建立Redis 叢集節點
建立好Headless service後,就可以利用StatefulSet建立Redis 叢集節點,這也是本文的核心內容。我們先建立redis.yml檔案:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis-app
spec:
serviceName: "redis-service"
replicas: 6
template:
metadata:
labels:
app: redis
spec:
terminationGracePeriodSeconds: 20
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- redis
topologyKey: kubernetes.io/hostname
containers:
- name: redis
image: redis
command:
- "redis-server"
args:
- "/etc/redis/redis.conf"
- "--protected-mode"
- "no"
resources:
requests:
cpu: "100m"
memory: "100Mi"
ports:
- name: redis
containerPort: 6379
protocol: "TCP"
- name: cluster
containerPort: 16379
protocol: "TCP"
volumeMounts:
- name: "redis-conf"
mountPath: "/etc/redis"
- name: "redis-data"
mountPath: "/var/lib/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
selector:
matchLabels:
app: redis
如上,總共建立了6個Redis節點(Pod),其中3個將用於master,另外3個分別作為master的slave;Redis的配置通過volume將之前生成的redis-conf這個Configmap,掛載到了容器的/etc/redis/redis.conf;Redis的資料儲存路徑使用volumeClaimTemplates宣告(也就是PVC),其會繫結到我們先前建立的PV上。
這裡有一個關鍵概念——Affinity,請參考官方文件詳細瞭解。其中,podAntiAffinity表示反親和性,其決定了某個pod不可以和哪些Pod部署在同一拓撲域,可以用於將一個服務的POD分散在不同的主機或者拓撲域中,提高服務本身的穩定性。
而PreferredDuringSchedulingIgnoredDuringExecution 則表示,在排程期間儘量滿足親和性或者反親和性規則,如果不能滿足規則,POD也有可能被排程到對應的主機上。在之後的執行過程中,系統不會再檢查這些規則是否滿足。
在這裡,matchExpressions規定了Redis Pod要儘量不要排程到包含app為redis的Node上,也即是說已經存在Redis的Node上儘量不要再分配Redis Pod了。但是,由於我們只有三個Node,而副本有6個,因此根據
PreferredDuringSchedulingIgnoredDuringExecution,這些豌豆不得不得擠一擠,擠擠更健康~
另外,根據StatefulSet的規則,我們生成的Redis的6個Pod的hostname會被依次命名為 $(statefulset名稱)-$(序號) 如下圖所示:
如上,可以看到這些Pods在部署時是以{0…N-1}的順序依次建立的。注意,直到redis-app-0狀態啟動後達到Running狀態之後,redis-app-1 才開始啟動。
同時,每個Pod都會得到叢集內的一個DNS域名,格式為$(podname).$(service name).$(namespace).svc.cluster.local ,也即是:
redis-app-0.redis-service.default.svc.cluster.local
redis-app-1.redis-service.default.svc.cluster.local
...以此類推...
可以看到, redis-app-0的IP為172.17.24.3。當然,若Redis Pod遷移或是重啟(我們可以手動刪除掉一個Redis Pod來測試),IP是會改變的,但是Pod的域名、SRV records、A record都不會改變。
另外可以發現,我們之前建立的pv都被成功綁定了:
5.初始化Redis叢集
建立好6個Redis Pod後,我們還需要利用常用的Redis-tribe工具進行叢集的初始化
建立Ubuntu容器
由於Redis叢集必須在所有節點啟動後才能進行初始化,而如果將初始化邏輯寫入Statefulset中,則是一件非常複雜而且低效的行為。這裡,本人不得不稱讚一下原專案作者的思路,值得學習。也就是說,我們可以在K8S上建立一個額外的容器,專門用於進行K8S叢集內部某些服務的管理控制。
這裡,我們專門啟動一個Ubuntu的容器,可以在該容器中安裝Redis-tribe,進而初始化Redis叢集,執行:
kubectl run -it ubuntu --image=ubuntu --restart=Never /bin/bash
我們使用阿里雲的Ubuntu源,執行:
root@ubuntu:/# cat > /etc/apt/sources.list << EOF
deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
> EOF
成功後,原專案要求執行如下命令安裝基本的軟體環境:
apt-get update
apt-get install -y vim wget python2.7 python-pip redis-tools dnsutils
初始化叢集
首先,我們需要安裝redis-trib
:
pip install redis-trib==0.5.1
然後,建立只有Master節點的叢集:
redis-trib.py create \
`dig +short redis-app-0.redis-service.default.svc.cluster.local`:6379 \
`dig +short redis-app-1.redis-service.default.svc.cluster.local`:6379 \
`dig +short redis-app-2.redis-service.default.svc.cluster.local`:6379
其次,為每個Master新增Slave
redis-trib.py replicate \
--master-addr `dig +short redis-app-0.redis-service.default.svc.cluster.local`:6379 \
--slave-addr `dig +short redis-app-3.redis-service.default.svc.cluster.local`:6379
redis-trib.py replicate \
--master-addr `dig +short redis-app-1.redis-service.default.svc.cluster.local`:6379 \
--slave-addr `dig +short redis-app-4.redis-service.default.svc.cluster.local`:6379
redis-trib.py replicate \
--master-addr `dig +short redis-app-2.redis-service.default.svc.cluster.local`:6379 \
--slave-addr `dig +short redis-app-5.redis-service.default.svc.cluster.local`:6379
至此,我們的Redis叢集就真正建立完畢了,連到任意一個Redis Pod中檢驗一下:
[root@master redis]# kubectl exec -it redis-app-2 /bin/bash
root@redis-app-2:/data# /usr/local/bin/redis-cli -c
127.0.0.1:6379> cluster nodes
5d3e77f6131c6f272576530b23d1cd7592942eec 172.17.24.3:6379@16379 master - 0 1559628533000 1 connected 0-5461
a4b529c40a920da314c6c93d17dc603625d6412c 172.17.63.10:6379@16379 master - 0 1559628531670 6 connected 10923-16383
368971dc8916611a86577a8726e4f1f3a69c5eb7 172.17.24.9:6379@16379 slave 0025e6140f85cb243c60c214467b7e77bf819ae3 0 1559628533672 4 connected
0025e6140f85cb243c60c214467b7e77bf819ae3 172.17.63.8:6379@16379 master - 0 1559628533000 2 connected 5462-10922
6d5ee94b78b279e7d3c77a55437695662e8c039e 172.17.24.8:6379@16379 myself,slave a4b529c40a920da314c6c93d17dc603625d6412c 0 1559628532000 5 connected
2eb3e06ce914e0e285d6284c4df32573e318bc01 172.17.63.9:6379@16379 slave 5d3e77f6131c6f272576530b23d1cd7592942eec 0 1559628533000 3 connected
127.0.0.1:6379> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:6
cluster_stats_messages_ping_sent:14910
cluster_stats_messages_pong_sent:15139
cluster_stats_messages_sent:30049
cluster_stats_messages_ping_received:15139
cluster_stats_messages_pong_received:14910
cluster_stats_messages_received:30049
127.0.0.1:6379>
另外,還可以在NFS上檢視Redis掛載的資料:
[root@ftp pv3]# ll /usr/local/k8s/redis/pv3
total 12
-rw-r--r-- 1 root root 92 Jun 4 11:36 appendonly.aof
-rw-r--r-- 1 root root 175 Jun 4 11:36 dump.rdb
-rw-r--r-- 1 root root 794 Jun 4 11:49 nodes.conf
6.建立用於訪問Service
前面我們建立了用於實現StatefulSet的Headless Service,但該Service沒有Cluster Ip,因此不能用於外界訪問。所以,我們還需要建立一個Service,專用於為Redis叢集提供訪問和負載均衡:
cat redis-access-service.yaml
apiVersion: v1
kind: Service
metadata:
name: redis-access-service
labels:
app: redis
spec:
ports:
- name: redis-port
protocol: "TCP"
port: 6379
targetPort: 6379
selector:
app: redis
如上,該Service名稱為 redis-access-service
,在K8S叢集中暴露6379埠,並且會對labels name
為app: redis
或appCluster: redis-cluster
的pod進行負載均衡。
建立後檢視:
kubectl get svc redis-access-service -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
redis-access-service ClusterIP 10.0.0.64 <none> 6379/TCP 2h app=redis,appCluster=redis-cluster
如上,在K8S叢集中,所有應用都可以通過10.0.0.64 :6379來訪問Redis叢集。當然,為了方便測試,我們也可以為Service新增一個NodePort對映到物理機上,這裡不再詳細介紹。
五、測試主從切換
在K8S上搭建完好Redis集群后,我們最關心的就是其原有的高可用機制是否正常。這裡,我們可以任意挑選一個Master的Pod來測試叢集的主從切換機制,如redis-app-0:
kubectl get pods redis-app-0 -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
redis-app-1 1/1 Running 0 3h 172.17.24.3 192.168.0.144 <none>
進入redis-app-0
檢視:
kubectl exec -it redis-app-0 /bin/bash
root@redis-app-0:/data# /usr/local/bin/redis-cli -c
127.0.0.1:6379> role
1) "master"
2) (integer) 13370
3) 1) 1) "172.17.63.9"
2) "6379"
3) "13370"
127.0.0.1:6379>
如上可以看到,app-0
為master,slave為172.17.63.9
即redis-app-3
。
接著,我們手動刪除redis-app-0
:
kubectl delete pod redis-app-0
pod "redis-app-0" deleted
[root@master redis]# kubectl get pod redis-app-0 -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
redis-app-0 1/1 Running 0 4m 172.17.24.3 192.168.0.144 <none>
我們再進入redis-app-0
內部檢視:
kubectl exec -it redis-app-0 /bin/bash
root@redis-app-0:/data# /usr/local/bin/redis-cli -c
127.0.0.1:6379> role
1) "slave"
2) "172.17.63.9"
3) (integer) 6379
4) "connected"
5) (integer) 13958
如上,redis-app-0
變成了slave,從屬於它之前的從節點172.17.63.9
即redis-app-3
。
六、疑問
至此,大家可能會疑惑,那為什麼沒有使用穩定的標誌,Redis Pod也能正常進行故障轉移呢?這涉及了Redis本身的機制。因為,Redis叢集中每個節點都有自己的NodeId(儲存在自動生成的nodes.conf中),並且該NodeId不會隨著IP的變化和變化,這其實也是一種固定的網路標誌。也就是說,就算某個Redis Pod重啟了,該Pod依然會載入儲存的NodeId來維持自己的身份。我們可以在NFS上檢視redis-app-1的nodes.conf檔案:
[root@k8s-node2 ~]# cat /usr/local/k8s/redis/pv1/nodes.conf 96689f2018089173e528d3a71c4ef10af68ee462 192.168.169.209:6379@16379 slave d884c4971de9748f99b10d14678d864187a9e5d3 0 1526460952651 4 connected237d46046d9b75a6822f02523ab894928e2300e6 192.168.169.200:6379@16379 slave c15f378a604ee5b200f06cc23e9371cbc04f4559 0 1526460952651 1 connected
c15f378a604ee5b200f06cc23e9371cbc04f4559 192.168.169.197:6379@16379 master - 0 1526460952651 1 connected 10923-16383d884c4971de9748f99b10d14678d864187a9e5d3 192.168.169.205:6379@16379 master - 0 1526460952651 4 connected 5462-10922c3b4ae23c80ffe31b7b34ef29dd6f8d73beaf85f 192.168.169.198:6379@16379 myself,slave c8a8f70b4c29333de6039c47b2f3453ed11fb5c2 0 1526460952565 3 connected
c8a8f70b4c29333de6039c47b2f3453ed11fb5c2 192.168.169.201:6379@16379 master - 0 1526460952651 6 connected 0-5461vars currentEpoch 6 lastVoteEpoch 4
如上,第一列為NodeId,穩定不變;第二列為IP和埠資訊,可能會改變。
這裡,我們介紹NodeId的兩種使用場景:
當某個Slave Pod斷線重連後IP改變,但是Master發現其NodeId依舊, 就認為該Slave還是之前的Slave。
當某個Master Pod下線後,叢集在其Slave中選舉重新的Master。待舊Master上線後,叢集發現其NodeId依舊,會讓舊Master變成新Master的slave。
——————————————————————————————————————————————————
原文連結:http://blog.csdn.net/liangkaiping0525/article/details/125636431
關注公眾號【OSC DevOps】閱讀更多精彩文章
- Skywalking分散式追蹤與監控:起始篇
- 如何使用 docker 搭建 hadoop 分散式叢集?
- 開源女神節——撕掉標籤,自由隨我
- 開源女神節——她說
- 大牛告訴你專案在Devops下如何測試!
- DataOps 不僅僅是資料的 DevOps!
- K8s——master擴容
- Skywalking分散式追蹤與監控:起始篇
- 這可能是最為詳細的Docker入門吐血總結
- 2023年 DevOps 七大趨勢
- k8s部署redis叢集
- DevOps20個常見問題
- Nexu私服安裝配置,IDEA打包上傳私服
- 鵝場分散式系統DevOps自動化測試實踐
- 【雲原生】持續整合和部署(Jenkins)
- k8s部署手冊-v04
- 保護 DevOps 的 5 個技巧
- CI/CD如何支撐運維自動化
- DevOps 如何幫助實現安全部署
- K8s系列-KubeSphere