在k8s中部署redis cluster實戰


 

0. 背景

  項目需要在k8s上搭建一個redis cluster集群,網上找到的教程例如:
  github原版帶配置文件
  在原版基礎上補充詳細使用步驟但是無配置文件版
  手把手教你一步一步創建的一篇博客
  redis運行在容器中時必須選擇一種外部存儲方案,用來保存redis的持久化文件,否則容器銷毀重建后無法讀取到redis的持久化文件(隨着容器一同銷毀了);並且還要保證容器重建后還能讀取到之前對應的持久化文件。上面的教程使用的是nfs存儲,但是受於條件限制本文只能使用宿主機的本地目錄來做存儲,與上面的教程有一些不一樣的地方。
  本文的目的是講一下使用local pv來作為存儲創建redis cluster集群的步驟,以及說明過程中需要注意的問題。

1. k8s的本地存儲方案

  Kubernetes支持幾十種類型的后端存儲卷,其中本地存儲卷有3種,分別是emptyDir、hostPath、local volume,尤其是local與hostPath這兩種存儲卷類型看起來都是一個意思。這里講一下區別。

1.1 區別

  1. emptyDir類型的Volume在Pod分配到Node上時被創建,Kubernetes會在Node上自動分配一個目錄,因此無需指定Node上對應的目錄文件。 這個目錄的初始內容為空,當Pod從Node上移除時,emptyDir中的數據會被永久刪除。
  2. hostPath類型則是映射node文件系統中指定的文件或者目錄到pod里。
  3. Local volume也是使用node文件系統的文件或目錄,但是使用PV和PVC將node節點的本地存儲包裝成通用PVC接口,容器直接使用PVC而不需要關注PV包裝的是node的文件系統還是nfs之類的網絡存儲。Local PV的定義中需要包含描述節點親和性(即指定PV使用哪個/哪些Node)的信息,k8s調度pod時則使用該信息將pod調度到該od使用的local pv所在的Node節點。

1.2 使用示例

emptyDir

apiVersion: v1 # 版本號,跟k8s版本有關
kind: Pod # 創建Pod類型,其他還有Deployment、StatefulSet、DaemonSet等等各種
metadata:
  name: test-pod 
spec:
  containers:
  - image: busybox # 創建pod使用的鏡像
    name: test-emptydir
    command: [ "sleep", "3600" ] # 這里睡眠等待的原因是:如果pod里面啟動的進程執行完,pod就會結束。所以redis之類的程序都要以非后台方式運行
    volumeMounts:
    - mountPath: /var/log  # 容器並不一定存在這個目錄,自己試一下,選擇一個與系統運行無關的目錄。因為pod是先掛載后啟動,如果掛載到了系統盤上,pod里面的linux就運行不起來了
      name: tmp-volume # 把下面那個叫做tmp-volume的存儲卷掛載到容器的/var/log 目錄
  volumes:
  - name: tmp-volume # 創建一個emptyDir類型的存儲卷,起名叫做tmp-volume
    emptyDir: {}

hostPath

apiVersion: v1
kind: Pod
metadata:
  name: test-pod2
spec:
  containers:
  - image: busybox
    name: test-hostpath
    command: [ "sleep", "3600" ]
    volumeMounts:
    - mountPath: /var/log
      name: host-volume
  volumes:
  - name: host-volume # 創建一個hostPath類型的存儲卷,起名叫做host-volume
    hostPath:
      path: /data  # 創建存儲卷使用的Node目錄,你的Node可能沒有這個目錄,自己找一個可用目錄

local volume

# pv和pvc使用同一個StorageClass,就能將pvc自動綁定到pv
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer

apiVersion: v1
kind: PersistentVolume
metadata:
  name: example-pv
spec:
  capacity:
    storage: 100Mi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle # pv的回收策略,這個后面講
  storageClassName: local-storage
  local:
    path: /mnt/disks/ssd1 # 把本地磁盤/mnt/disks/ssd1上100M空間拿出來作為pv
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - example-node  # 選擇集群里面kubernetes.io/hostname=example-node這個標簽的節點來創建pv

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: example-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 50Mi
  storageClassName: local-storage

2. pv的回收策略

pv的回收策略有三種:Retain、Recycle、Delete,可以在腳本中指定:

persistentVolumeReclaimPolicy: Retain

也可以在pv創建成功后使用命令修改:

sudo kubectl patch pv <your-pv-name> -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'

假設有一個pv叫test-pv,綁定的pvc角坐test-pvc,test-pv使用的local pv

2.1 Retain

  1. 刪除test-pvc后,test-pv得到了保留,但test-pv的狀態會一直處於 Released而不是Available,不能被其他PVC申請;
  2. 為了重新使用test-pv綁定的nfs存儲空間,可以刪除並重新創建test-pv;
  3. 刪除操作只是刪除了test-pv對象,nfs存儲空間中的數據並不會被刪除。

2.2 Recycle

  1. 刪除test-pvc之后,Kubernetes啟動了一個新的Pod角坐recycler-for-test-pv,這個Pod的作用就是清除test-pv的數據。在此過程中test-pv的狀態為Released,表示已經解除了與 test-pvc的綁定,不過此時還不可用;
  2. 當數據清除完畢,test-pv的狀態重新變為 Available,此時test-pv可以被新的PVC綁定;
  3. 同樣,也不會刪除nfs存儲空間中的數據。

2.3 Delete

會刪除test-pv在對應存儲空間上的數據。NFS目前不支持 Delete,支持Delete的存儲空間有AWS EBS、GCE PD、Azure Disk、OpenStack Cinder Volume 等(網上看的,沒測試過)。

3. Deployment和Statsfulset

  前面已經說過,redis有數據持久化需求,並且同一個pod重啟后需要讀取原來對應的持久化數據,這一點在不使用k8s時很容易實現(只使用docker不使用k8s時也很容易),啟動redis cluster每個節點時指定其持久化目錄就行了,但是k8s的Deployment的調度對於我們這個需求來說就顯得很隨機,你無法指定deployment的每個pod使用哪個存儲,並且重啟后仍然使用那個存儲。
  Deployment不行,Statefulset可以。官方對Statefulset的優點介紹是:

  1. 穩點且唯一的網絡標識符
  2. 穩點且持久的存儲
  3. 有序、平滑的部署和擴展
  4. 有序、平滑的刪除和終止
  5. 有序的滾動更新

  看完還是比較迷糊,我們可以簡單的理解為原地更新,更新后還是原來那個pod,只更新了需要更新的內容(一般是修改自己寫的程序,與容器無關)。
  Statefulset和local pv結合,redis cluster的每個pod掛掉后在k8s的調度下重啟時都會使用之前自己的持久化文件和節點信息。

4. 創建redis集群

4.1 創建StorageClass

  創建StorageClass的目的是deployment中根據StorageClass來自動為每個pod選擇一個pv,否則手動為每個pod指定pv又回到了老路上。

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: redis-local-storage # StorageClass的name,后面需要聲明使用的是這個StorageClass時都是用這個名字
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer

4.2 創建PV

  創建6個pv,因為redis cluster最低是三主三從的配置,所以最少需要6個pod。后面的pv2~pv5我就不貼出來了。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv1
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: redis-local-storage  # 上面創建的StorageClass
  local:
    path: /usr/local/kubernetes/redis/pv1 # 創建local pv使用的宿主機目錄,可以自己指定
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname # k8s node的標簽,結合下面的ip,該標簽為kubernetes.io/hostname=192.168.0.152
          operator: In
          values:
          - 192.168.0.152  # localpv創建在192.168.0.152這台機器上

4.3 使用configmap創建redis的配置文件redis.conf

# 下面的redis.conf中不能寫注釋,否則k8s解析時會當作配置文件的一部分,出錯
# dir /var/lib/redis使得持久化文件dump.rdb在容器的/var/lib/redis目錄下
# cluster-config-file /var/lib/nodes.conf使得集群信息在/var/lib/redis/nodes.conf文件中
# /var/lib/redis目錄會掛載pv,所以持久化文件和節點信息能保存下來
kind: ConfigMap
apiVersion: v1
metadata:
  name: redis-cluster-configmap # configmap的名字,加上下面的demo-redis就是這個configmap在k8s集群中的唯一標識
  namespace: demo-redis
data:
  # 這里可以創建多個文件
  redis.conf: |
    appendonly yes
    protected-mode no
    cluster-enabled yes          
    cluster-config-file /var/lib/redis/nodes.conf 
    cluster-node-timeout 5000    
    dir /var/lib/redis        
    port 6379

4.4 創建headless service

  Headless service是StatefulSet實現穩定網絡標識的基礎,需要提前創建。

apiVersion: v1
kind: Service
metadata:
  name: redis-headless-service
  namespace: demo-redis
  labels:
    app: redis
spec:
  ports:
  - name: redis-port
    port: 6379
  clusterIP: None
  selector:
    app: redis
    appCluster: redis-cluster

4.5 創建redis節點

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis-app
  namespace: demo-redis
spec:
  serviceName: redis-service
  replicas: 6
  selector:
    matchLabels:
      app: redis
      appCluster: redis-cluster
  template:
    metadata:
      labels:
        app: redis
        appCluster: redis-cluster
    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"                  #redis啟動命令
        args:
          - "/etc/redis/redis.conf"         #redis-server后面跟的參數,換行代表空格
          - "--protected-mode"              #允許外網訪問
          - "no"
        resources:                        
          requests:                         # 每個pod請求的資源
            cpu: 2000m                      # m代表千分之,這里申請2個邏輯核
            memory: 4Gi                     # 內存申請4G大小
          limits:                           # 資源限制
            cpu: 2000m                     
            memory: 4Gi 
        ports:
            - name: redis
              containerPort: 6379
              protocol: "TCP"
            - name: cluster
              containerPort: 16379
              protocol: "TCP"
        volumeMounts:
          - name: redis-conf              # 把下面創建的redis.conf配置文件掛載到容器的/etc/redis目錄下
            mountPath: /etc/redis        
          - name: redis-data              # 把叫做redis-data的volume掛載到容器的/var/lib/redis目錄
            mountPath: /var/lib/redis
      volumes:
      - name: redis-conf                  # 船艦一個名為redis-conf的volumes  
        configMap:
          name: redis-cluster-configmap   # 引用上面創建的configMap卷
          items:
            - key: redis.conf             # configmap里面的redis.conf
              path: redis.conf            # configmap里面的redis.conf放到volumes中叫做redistribution.conf
  volumeClaimTemplates:                   # pod使用哪個pvc,這里是通過StorageClass自動創建pvc並對應上pv
  - metadata:
      name: redis-data                    # pvc創建一個volumes叫做redis-data
    spec:
      accessModes:
      - ReadWriteOnce
      storageClassName: redis-local-storage
      resources:
        requests:  
          storage: 5Gi

  每個Pod都會得到集群內的一個DNS域名,格式為(podname).(service name).$(namespace).svc.cluster.local。可以在pod中ping一下這些域名,是可以解析為pod的ip並ping通的。

4.6 創建一個service,作為redis集群的訪問入口

  這個service是可以自由發揮的,使用port-forward、NodePort還是ingress你自己選擇,我這里只是一個內網訪問統一入口。

apiVersion: v1
kind: Service
metadata:
  name: redis-access-service
  namespace: demo-redis
  labels:
    app: redis
spec:
  ports:
  - name: redis-port
    protocol: TCP
    port: 6379
    targetPort: 6379
  selector:
    app: redis
    appCluster: redis-cluster

  至此,redis cluster的六個節點都已經創建成功。下面需要創建集群(此時就是6個單節點的redis,並不是一個集群)。

4.7 創建redis cluster集群

  我們之前都是通過外部安裝redis-trib創建的集群,但是根據這篇文章redis 5.0之后已經內置了redis-trib工具,感興趣的可以嘗試。
  專門啟動一個Ubuntu/CentOS的容器,可以在該容器中安裝Redis-tribe,進而初始化Redis集群,執行:
kubectl run -i --tty centos --image=centos --restart=Never /bin/bash
成功后,我們可以進入centos容器中,執行如下命令安裝基本的軟件環境:

cat >> /etc/yum.repo.d/epel.repo<<'EOF'
[epel]
name=Extra Packages for Enterprise Linux 7 - $basearch
baseurl=https://mirrors.tuna.tsinghua.edu.cn/epel/7/$basearch
#mirrorlist=https://mirrors.fedoraproject.org/metalink?repo=epel-7&arch=$basearch
failovermethod=priority
enabled=1
gpgcheck=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7
EOF
yum -y install redis-trib.noarch bind-utils-9.9.4-72.el7.x86_64

然后執行如下命令創建集群:

redis-trib create --replicas 1 \
`dig +short redis-app-0.redis-headless-service.demo-redis.svc.cluster.local`:6379 \
`dig +short redis-app-1.redis-headless-service.demo-redis.svc.cluster.local`:6379 \
`dig +short redis-app-2.redis-headless-service.demo-redis.svc.cluster.local`:6379 \
`dig +short redis-app-3.redis-headless-service.demo-redis.svc.cluster.local`:6379 \
`dig +short redis-app-4.redis-headless-service.demo-redis.svc.cluster.local`:6379 \
`dig +short redis-app-5.redis-headless-service.demo-redis.svc.cluster.local`:6379

根據提示一步一步完成。

5. tips

5.1 集群哪怕只有一個節點可訪問,也要按照集群配置方式

  否則報錯例如MOVED 1545 10.244.3.239:6379","data":false
  如本文的情況,redis cluster的每個節點都是一個跑在k8s里面的pod,這些pod並不能被外部直接訪問,而是通過ingress等方法對外暴露一個訪問接口,即只有一個統一的ip:port給外部訪問。經由k8s的調度,對這個統一接口的訪問會被發送到redis集群的某個節點。這時候對redis的用戶來說,看起來這就像是一個單節點的redis。但是,此時無論是直接使用命令行工具redis-cli,還是某種語言的sdk,還是需要按照集群來配置redis的連接信息,才能正確連接,例如

./redis-cli -h {your ip} -p {your port} -c

  這里-c就代表這是訪問集群,又或者springboot的redis配置文件

spring:
  redis:
    # 集群配置方式
    cluster:
      nodes: {your ip1}:{your port1},{your ip2}:{your port2}
    password:{your password}
    # 對比一下單節點配置方式
    host: {your ip}
    port: {your port}
    password:{your password}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM