1. 程式人生 > >基於Helm安裝部署高可用的Redis_Kubernetes中文社群

基於Helm安裝部署高可用的Redis_Kubernetes中文社群

1、Redis簡介

Redis是一個開放原始碼(BSD許可證)的代理,其在記憶體中儲存資料,可以代理資料庫、快取和訊息。它支援字串、雜湊、列表、集合和點陣圖等資料結構。Redis 是一個高效能的key-value資料庫, 它在很大程度改進了memcached這類key/value儲存的不足。Redis提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby和Erlang等語言的客戶端。
Redis支援master/slave結構,資料可以從master向任意數量的slave上進行同步。Redis 與其它 key – value 快取產品相比,具有以下三個方面特點:

  • 支援記憶體的持久化:可以將記憶體中的資料儲存在磁碟中,重啟的時候可以再次載入進行使用;
  • 支援多種資料結構:Redis不僅僅只是支援key-value型別的資料,還能夠支援字串、雜湊和列表等資料結構;
  • 支援主從結構:Redis支援主從結構,保證系統的高可用。

2、基於Sentinel模式的高可用方案

本文中的Redis高可用方案採用Sentinel(哨兵)模式,在叢集出現故障的時候自動進行故障轉移,保證叢集的可用性。Redis Sentinel 為Redis提供了高可用性,這意味著通過使用Sentinel 可以建立一個Redis部署,在沒有人為干預的情況下能夠抵抗某些型別的失敗。Sentiel的完整功能列表如下所示:

  • 監控:不間斷的檢查master/slave例項否是安裝預期正常工作;
  • 通知:當 Redis 例項出現錯誤的時候,會使用程式(通過 API 介面)通知管理員;
  • 自動故障轉移:在master發生故障時,哨兵會開啟故障轉移處理,將一臺slave提升為master,其它的slave被重新配置使用新的master,當應用程式連線時使用新的地址配置;
  • 配置資訊:Sentinel作為服務發現的權威來源,客戶端連線到Sentinel去獲取當前Redis master的地址,如果發生故障轉移,Sentinel將會彙報新的伺服器地址。

Sentinel本身是一套分散式系統,它被設計成能夠進行多個程序間協同工作的模式,這樣的好處如下:

  • 多個Sentinel一致明確給定的主機不再可用時,才會執行故障檢測,這能夠有效錯報的概率。
  • 即使只有一個Sentinel在正常執行,Redis也是可用的,從而保證系統具有較高的健壯性。

Sentinel,Redis例項(master和slave)和連線到Sentinel和Redis的客戶端的數量,也是一個具有特定屬性的更大的分散式系統。在本文中,定製的Redis伺服器映象會確定執行它的Pod是redis的Sentinel、master還是slave,並啟動適當的服務。這個Helm chart指示Sentinel狀態與環境變數。如果沒有設定,新啟動的Pod將查詢Kunbernetes的活動master。如果不存在,則它使用一種確定的方法來檢測它是否應該作為master啟動,然後將“master”或“slave”寫入到稱為redis-role的標籤中。
redis-role=master Pod是叢集啟動的關鍵。在它們完成啟動,sentinel將處於等待整體。所有其他的Pod等待sentinel識別主節點。執行Pod並設定標籤podIP和runID。runID是每個redis伺服器生成的唯一run_ID值的前幾個字元。
在正常操作中,應該只有一個redis=master Pod。如果失敗,Sentinel將提名一個新的master,並適當地改變所有的redis-role的值。
通過執行如下命令可以檢視Pod所承擔的角色:

$ kubectl get pods -L redis-role -namespace=kube-public

3、安裝部署

2.1 環境要求

  • 已有Kubernetes 1.6+環境;
  • 在Kubernetes中提供了容量大於10g的持久化儲存卷。

2.2 Helm char配置

下表列示了Redis chart的配置引數和預設值:
引數 描述 預設值
redis_image Redis映象 quay.io/smile/redis:4.0.6r2
resources.master Redis主節點CPU/記憶體的資源請求/限制 Memory: 200Mi, CPU: 100m
resources.slave Redis從節點CPU/記憶體的資源請求/限制 Memory: 200Mi, CPU: 100m
resources.sentinel 哨兵節點CPU/記憶體的資源請求/限制 Memory: 200Mi, CPU: 100m
replicas.servers redis master/slave pods的副本數量 3
replicas.sentinels sentinel pods的副本數量 3
nodeSelector 為Pod指派的Node標籤 {}
tolerations 為Pod指派的可容忍標籤 []
servers.serviceType 設定”LoadBalancer”能夠通過VPC進行訪問 ClusterIP
servers.annotations 參考應用模式
rbac.create 是否應該建立RBAC資源 true
serviceAccount.create 是否建立代理所使用的service account名稱 true
serviceAccount.name 被使用的service account。如果未進行設定,同時如果serviceAccount.create被設定為true,則Kubernetes會在後臺以模板的全名建立一個service account。

在helm install中使用–set key=value 格式設定上述的引數值,例如:

$ helm install \
  --set redis_image=quay.io/smile/redis:4.0.6r2 \
    stable/redis-ha

2.3 持久化

redis將持久化資料儲存在容器的/redis-master-datal路徑下,安裝時會建立一個PersistentVolumeClaim ,並將其掛接到容器內的目錄。因此,需要在Kubernetes中提前提供一個PersistentVolume。

2.4 通過Chart安裝Redis

通過執行如下的命令,在Kubernetes中部署Redis:

$ helm install stable/redis-ha --name=redis-ha --namespace=kube-public

通過上述命令,將以預設的配置在Kubernetes中部署Redis。預設情況下,chart會安裝部署3個Sentinel Pod,1個master Pod和2個slave Pod。

3、Helm Chart分析

MySQL Chart的目錄如下,其中,values為預設的配置檔案,用於為部署提供預設值。templates目錄下的YAML檔案是在Kubernetes進行部署的配置檔案。
redis-ha
--templates # 模板目錄,當與values.yaml組合時,將生成有效的Kubernetes清單檔案。
----NOTES.txt
----_helpers.tpl
----redis-auth-secret.yaml
----redis-master-service.yalm
----redis-role.yaml
----redis-rolebinding.yaml
----redis-sentinel-deployment.yaml
----redis-sentinel-service.yaml
----redis-server-deployment.yaml
----redis-serviceaccount.yaml 
----redis-slave-service.yaml  
--Chart.yaml # 描述chart的資訊
--README.md # 可讀的chart介紹檔案
--values.yaml # 預設配置檔案

3.1 values.yaml

在values.yaml配置檔案中設定了通過helm進行部署時的預設值。在values.yaml中,首先,定義了主Pod和哨兵Pod的請求和限制資源的要求;接著,通過nodeSelector和容忍度為Pod定義排程到哪個Node上;以及,指定容器所使用的映象和其它的相關資訊。

## Configure resource requests and limits
## ref: http://kubernetes.io/docs/user-guide/compute-resources/
##
resources:
  server:
    requests:
      memory: 200Mi
      cpu: 100m
    limits:
      memory: 700Mi
  sentinel:
     requests:
       memory: 200Mi
       cpu: 100m
     limits:
       memory: 200Mi

## Node labels and tolerations for pod assignment
## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector 
## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#taints-and-tolerations-beta-feature
 nodeSelector: {}
 tolerations: []

## Redis image version
redis_image: quay.io/smile/redis:4.0.8r0
## replicas number for each component
replicas:
  servers: 3
  sentinels: 3
servers:
  serviceType: ClusterIP # [ClusterIP|LoadBalancer]
  annotations: {}

rbac:
 # Specifies whether RBAC resources should be created
 create: true

serviceAccount:
 # Specifies whether a ServiceAccount should be created
 create: true
 # The name of the ServiceAccount to use.
 # If not set and create is true, a name is generated using the fullname template
 name:

## Configures redis with AUTH (requirepass & masterauth conf params)
auth: false

## Redis password
## Defaults to a random 10-character alphanumeric string if not set and auth is true
## ref: https://github.com/kubernetes/charts/blob/master/stable/redis-ha/templates/redis-auth-secret.yaml
##
## redisPassword:

3.2 redis-server-deployment.yaml

此YAML配置檔案用於定義Redis master/slave的部署。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
 # Pay attention to the redis-role label at runtime. The self-determination logic in the image sets 
 # this value accordingly.
 name: {{ template "redis-ha.fullname" . }}-server
 labels:
   name: {{ template "redis-ha.fullname" . }}-server
   redis-node: "true"
{{ include "labels.standard" . | indent 4 }}
spec:
 replicas: {{ .Values.replicas.servers }}
 template:
   metadata:
     labels:
       app: {{ template "redis-ha.name" . }}
       release: {{ .Release.Name }}
       component: server
       name: {{ template "redis-ha.fullname" . }}-server
       redis-node: "true"
     spec:
       serviceAccountName: {{ template "redis-ha.serviceAccountName" . }}
       {{- if .Values.nodeSelector }}
       nodeSelector:
{{ toYaml .Values.nodeSelector | indent 8 }}
       {{- end }}
       {{- if .Values.tolerations }}
       tolerations:
{{ toYaml .Values.tolerations | indent 8 }}
       {{- end }}
       containers:
       - name: redis
         image: {{ .Values.redis_image }}
         resources:
{{ toYaml .Values.resources.server | indent 10 }}
         env:
         - name: REDIS_SENTINEL_SERVICE_HOST
           value: "redis-sentinel"
         - name: REDIS_CHART_PREFIX
           value: {{ template "redis-ha.fullname" . }}-
{{- if .Values.auth }}
         - name: REDIS_PASS
           valueFrom:
             secretKeyRef:
               name: {{ template "redis-ha.fullname" . }}
               key: auth
{{- end }}
          ports:
          - containerPort: 6379
          volumeMounts:
          - mountPath: /redis-master-data
            name: data
       volumes:
       - name: data

3.3 redis-master-service.yaml

此YAML配置檔案為定義了redis master的服務,此服務暴露6379埠,以供在叢集中使用。

apiVersion: v1
kind: Service
metadata:
 name: {{ template "redis-ha.fullname" . }}-master-svc
 labels:
{{ include "labels.standard" . | indent 4 }}
 annotations:
{{ toYaml .Values.servers.annotations | indent 4 }}
spec:
 ports:
 - port: 6379
   protocol: TCP
   targetPort: 6379
 selector:
   app: {{ template "redis-ha.name" . }}
   release: "{{ .Release.Name }}"
   redis-node: "true"
   redis-role: "master"
 type: "{{ .Values.servers.serviceType }}"

3.4 redis-slave-service.yaml

此YAML配置檔案為定義了redis slave的服務,此服務暴露6379埠,以供在叢集中使用。

apiVersion: v1
kind: Service
metadata:
 name: {{ template "redis-ha.fullname" . }}-slave-svc
 labels:
 role: service
{{ include "labels.standard" . | indent 4 }}
 annotations:
{{ toYaml .Values.servers.annotations | indent 4 }}
spec:
 ports:
 - port: 6379
   protocol: TCP
   targetPort: 6379
 selector:
   app: {{ template "redis-ha.name" . }}
   release: "{{ .Release.Name }}"
   redis-node: "true"
   redis-role: "slave"
 type: "{{ .Values.servers.serviceType }}"

3.5 redis-sentinel-deployment.yaml

此YAML檔案定義Sentinel部署,Sentinel用於監控和管理對於Redis的訪問。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
 name: {{ template "redis-ha.fullname" . }}-sentinel
 labels:
{{ include "labels.standard" . | indent 4 }}
spec:
 replicas: {{ .Values.replicas.sentinels }}
 template:
   metadata:
     labels:
       app: {{ template "redis-ha.name" . }}
       release: {{ .Release.Name }}
       component: sentinel
       name: {{ template "redis-ha.fullname" . }}-sentinel
     spec:
       serviceAccountName: {{ template "redis-ha.serviceAccountName" . }}
       {{- if .Values.nodeSelector }}
     nodeSelector:
{{ toYaml .Values.nodeSelector | indent 8 }}
     {{- end }}
     {{- if .Values.tolerations }}
     tolerations:
{{ toYaml .Values.tolerations | indent 8 }}
 {{- end }}
     containers:
     - name: sentinel
       image: {{ .Values.redis_image }}
       resources:
 {{ toYaml .Values.resources.sentinel | indent 10 }}
       env:
       - name: SENTINEL
         value: "true"
       - name: REDIS_CHART_PREFIX
         value: {{ template "redis-ha.fullname" . }}-
{{- if .Values.auth }}
       - name: REDIS_PASS
         valueFrom:
           secretKeyRef:
             name: {{ template "redis-ha.fullname" . }}
             key: auth
{{- end }}
       ports:
       - containerPort: 26379

3.6 redis-sentinel-service.yaml

此YAML檔案用於在叢集內容暴露Sentinel部署,以供其它應用訪問和呼叫。

apiVersion: v1
kind: Service
metadata:
 name: {{ template "redis-ha.fullname" . }}-sentinel
 labels:
   name: {{ template "redis-ha.name" . }}-sentinel-svc
   role: service
{{ include "labels.standard" . | indent 4 }}
spec:
 ports:
 - port: 26379
   targetPort: 26379
 selector:
   app: {{ template "redis-ha.name" . }}
   release: "{{ .Release.Name }}"
   redis-role: "sentinel"

3.7 redis-serviceaccount.yaml

如果rbac.create的值為true,此YAML檔案將建立一個名為{{template “redis-ha.serviceAccountName”.}}的service account。

{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
 name: {{ template "redis-ha.serviceAccountName" . }}
 labels:
   app: "redis-ha"
   chart: {{ .Chart.Name }}-{{ .Chart.Version }}
   heritage: {{ .Release.Service }}
   release: {{ .Release.Name }}
{{- end -}}

3.8 redis-role.yaml

如果rbac.create的值為true,則此YAML檔案將會定義名為{{template “redis-ha.fullname” .}}一個角色,此角色擁有獲取、列示和修改pods的許可權。

{{- if .Values.rbac.create -}}
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
 name: {{ template "redis-ha.fullname" . }}
 labels:
{{ include "labels.standard" . | indent 4 }}
rules:
- apiGroups:
   - ""
 resources:
   - pods
 verbs:
   - get
   - list
   - patch
{{- end -}}

3.9 redis-rolebinding.yaml

如果rbac.create的值為true,將上述建立的service account和角色進行繫結。

{{- if .Values.rbac.create -}}
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
 name: {{ template "redis-ha.fullname" . }}
 labels:
{{ include "labels.standard" . | indent 4 }}
roleRef:
 apiGroup: rbac.authorization.k8s.io
 kind: Role
 name: {{ template "redis-ha.fullname" . }}
subjects:
- kind: ServiceAccount
  name: {{ template "redis-ha.serviceAccountName" . }}
{{- end -}}

3.10 redis-auth-secret.yaml

如果auth的值為true,則會建立一個保密字典。

{{- if .Values.auth -}}
apiVersion: v1
kind: Secret
metadata:
 name: {{ template "redis-ha.fullname" . }}
 labels:
{{ include "labels.standard" . | indent 4 }}
type: Opaque
data:
 {{- if .Values.redisPassword }}
 auth: {{ .Values.redisPassword | b64enc | quote }}
 {{- else }}
 auth: {{ randAlphaNum 10 | b64enc | quote }}
 {{- end }}
{{- end -}}

4、Redis部署環境驗證

在Kubernetes叢集中,可以通過DNS名稱{{ template “redis-ha.fullname” . }}.{{ .Release.Namespace }}.svc.cluster.local和埠6379訪問redis叢集。
如果設定了認證的話,通過下面的步驟連線Redis:
1)獲取隨機建立的redis密碼:
echo $(kubectl get secret {{ template “redis-ha.fullname” . }} -o “jsonpath={.data[‘auth’]}” | base64 -D)
2)使用客戶端連線Redis master Pod:
kubectl exec -it $(kubectl get pod -o jsonpath='{range .items[*]}{.metadata.name} {.status.containerStatuses[0].state}{“\n”}{end}’ -l redis-role=master | grep running | awk ‘{print $1}’) bash
3)在容器內使用Redis CLI連線:
redis-cli -a <REDIS-PASS-FROM-SECRET>
如果未設定認證的話,通過下面的步驟連線Redis:
1)可以通過下面的命令執行Redis Pod,作為客戶端:

獲取當前系統中的Pods:

$ kubectl get pods -L redis-role --namespace=kube-public

以名稱為redis-ha-redis-ha-server-79659c558f-lgrtg的Pod作為客戶端:

$ kubectl exec -it redis-ha-redis-ha-server-79659c558f-lgrtg --namespace=kube-public bash

2)使用Redis CLI:

獲取Redis的master服務名稱:

$ kubectl get svc --namespace=kube-public

$ redis-cli -h redis-ha-redis-ha-master-svc.kube-public -p 6379

5、參考資料

1. 《redis-ha》地址:https://github.com/kubernetes/charts/blob/master/stable/redis-ha

2. 《Redis Sentinel Documentation》地址:https://redis.io/topics/sentinel

作者簡介:
季向遠,北京神舟航天軟體技術有限公司產品經理。本文版權歸原作者所有。