單機下用Redis二進制程序包搭建Redis集群的案例很多,用docker在單節點上搭集群的也很多,但是在k8s下單節點搭集群的就很少了,有的只是掛載一個臨時目錄,數據無法持久化,pod銷毀后,數據就沒了。在k8s環境下測試機又不夠的情況下使用Redis集群就不太方便了,本文就是筆者的根據自身需要實踐出來的,期間也找了很多網上資料,最后自己綜合琢磨出的解決方案,由于對docker和k8s不是很精通,可能有其他更簡單的方案,歡迎大家交流。
一、所需的背景知識
1、對docker,k8s有一定使用經驗:會編寫yaml文件,知道如何排查pod不能running的問題
2、在單機環境下搭建過Redis集群
3、如果以上的知識都不知道也沒關系,本文盡量保證你按照步驟執行不出錯。但是k8s環境你必須得有,基本的linux命令知識得有
二、軟件版本
k8s v1.10.2
docker 18.03.1-ce, build 9ee9f40
Redis5.0.5
三、準備工作
docker pull redis:5.0.5
mkdir redis-cluster
cd redis-cluster/
mkdir data
創建多個節點的數據存儲目錄,避免Redis實例啟動時配置文件沖突導致無法啟動,在單臺物理機上搭建過Redis集群的應該知道。建好6個節點的目錄,后面會用到,這里的目錄要注意權限問題,k8s啟動的pod需要讀寫這里的文件夾
for port in `seq 7001 7006`; do \
mkdir -p ./${port}/
done
創建完成后目錄結構
四、編輯yaml文件
創建k8s的yaml文件
回到redis-cluster目錄下
1、創建ConfigMap
先創建redis.conf配置文件
vi redis-cm.yaml
將以下內容貼到文件中,保存退出
apiVersion: v1
kind: ConfigMap
metadata:
name: redis-conf
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
2、創建statefulset
vi redis-statefulset.yaml
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: redis-app
spec:
serviceName: "redis-service"
replicas: 6
template:
metadata:
labels:
app: redis
appCluster: redis-cluster
spec:
nodeSelector:
node: mfc # 這里需要根據自己的k8s節點情況修改,本案例需修改成node-222
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:latest"
imagePullPolicy: IfNotPresent #默認情況是會根據配置文件中的鏡像地址去拉取鏡像,配置為本地有鏡像不拉取遠程,避免你的環境不能訪問外網,拉取鏡像失敗
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: #可看作pvc的模板
- metadata:
name: redis-data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
3、創建pv的yaml文件
vi pv1.yaml
kind: PersistentVolume
apiVersion: v1
metadata:
name: redis-pv-volume1
labels:
type: local
spec:
capacity:
storage: 2Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /home/bboss/redis-cluster/data/7001/
/home/bboss/redis-cluster/data/7001/此目錄為前面我們創建的redis數據存儲的6個目錄
將此文件復制5份,依次命名為pv2.yaml 到pv6.yaml
在對應的文件中將
name: redis-pv-volume1
path: /home/bboss/redis-cluster/data/7001/
這2處修改為對應文件編號的序號,例如pv2.yaml修改為
name: redis-pv-volume2
path: /home/bboss/redis-cluster/data/7002/
五、開始搭建集群
1、創建cm和pv如下圖
km create -f redis-cm.yaml
km create -f pv1.yaml
2、創建statefulset
確定當前宿主機在k8s集群中的label
kubectl get nodes --show-labels
如圖,我當前部署的機器ip是222對應的node標簽名為node-222
將redis-statefulset.yaml文件中node: mfc 修改為 node: node-222,稍后啟動redis的pod是都會選擇在node-222這個節點的機器上啟動,這正是我們需要的,修改正確后
km create -f redis-statefulset.yaml
[注:我這里km是做了配置,km相當于是kubectl -n mfc-namespace 這是指定k8s的namespace的,我的namespace名是mfc-namespace,kubectl命令不指定-n 默認是使用default為名的namespace]
kubectl get pods -n mfc-namespace | grep redis
查看6個redis節點是否都啟動完成
查看一下pod對應的ip,下一步配置集群需要知道各節點的ip,這ip是k8s分配的
kubectl get pods -o wide -n mfc-namespace | grep redis
驗證一下pvc是否都bound了
kubectl get pvc -o wide -n mfc-namespace
3、初始化Redis集群
啟動一個獨立的redis
docker run -it redis:latest bash
redis-cli --cluster create 10.254.79.20:6379 10.254.79.21:6379 10.254.79.22:6379 10.254.79.23:6379 10.254.79.24:6379 10.254.79.25:6379 --cluster-replicas 1
4、驗證Redis集群
日志顯示集群已經初始化好,slots也分配完成,驗證一下集群
用redis-cli隨便連接某個redis節點
/usr/local/bin/redis-cli -c -h 10.254.79.20 -p 6379
搭建完成了,下面來看看本地存儲的數據
隨便進到某個目錄下看看,如下圖redis集群的數據已經落到物理機上了。
驗證一下pod重啟后集群數據是否還在
看ip還是原來的ip,這就是k8s定義statefulset的作用
通過剛才啟動的獨立redis再次連接集群看數據還在不在,如下圖數據都還在
5、創建headlessService
到這里我們的Redis集群已經是完全可用的了,但是我們的應用要使用該集群時配置ip不好記,這時就需要用到k8s中的Service
vi redis-headlessService.yaml
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
執行 km create -f redis-headlessService.yaml 創建服務
查看服務
另外用k8s啟一個有nslookup或者ping工具的pod,驗證一下服務名是否可以訪問
k8s是自帶dns功能的,k8s會根據serviceName生成對應的域名
如上圖,可以看到節點1 (10.254.79.20)的完整域名是redis-app-0.redis-service.mfc-namespace.svc.cluster.local,在k8s中同一個namespace的服務只需要前面的子域名即可,后面的會默認補齊,本例中的serviceName是redis-app-0.redis-service,這是一個節點的服務名。對于整個集群的serviceName是redis-service
用剛才的docker啟動的redis驗證一下域名是否可以連接,如下圖用域名連接失敗,因為我們這個是用docker啟動的不在k8s中,用pod的ip是可以連接的,因為pod的ip訪問是走docker的網絡。因為服務名是k8s做的路由,所以必須在k8s集群中注冊才可以使用服務名互相訪問。serviceName其實是在操作系統中做了一個轉發的動作,通過iptales做策略實現的。
用redis集群中的任意一個節點驗證,域名是可以訪問的
六、總結
網上大部分文章都是類似的方案,使用pv,pvc做存儲,有的pv是綁定的nfs,我也試過nfs碰到問題沒解決只能想其他辦法,但是我理解的是如果6個redis節點的配置文件nodes.conf和數據文件都在共享一個目錄,那還是會有沖突,導致redis只能啟動一個節點,而其他節點無法啟動,除非能把文件命名定義成不同節點對應不同的文件名,因為沒搭成功基于nfs的pvc存儲,不知道是不是每個節點一個隔離的pvc,所以無法知道以后會再實踐。所以本例主要是通過定義不同的pv把目錄隔離開,但是定義StatefulSet時不好指定pvc,看到有文章用到volumeClaimTemplates(可看作pvc的模板)傳輸門于是問題就應然而解了,StatefulSet在創建pod時是前一個創建成功才繼續下一個是有順序的,同時創建pvc也是有順序的,每個pvc綁定一個pv,這樣6個節點的數據文件就自動綁定到各自的目錄下了。
另外redis集群初始化不需要Ruby了,避免了安裝ruby的麻煩。
七、參考資料
[https://www.cnblogs.com/tylerzhou/p/11027559.html]
[https://v1-12.docs.kubernetes.io/zh/docs/tasks/run-application/force-delete-stateful-set-pod/]
[https://juejin.im/post/5d206b1e5188252f275fdc95]
[https://www.cnblogs.com/breezey/p/6582082.html]
[https://juejin.im/post/5c989ff2f265da60f206ffe4#heading-7]
[http://www.lxweimin.com/p/a5172b0eeae4]
https://www.cnblogs.com/xiaochangwei/p/7993065.html