網易有道 | REDIS 雲原生實戰

有道技術團隊發表於2021-12-27

REDIS 雲原生實戰

摘要
本次以Redis為範例,闡述了有道基礎架構團隊在基礎設施容器化道路上的實踐,主要將從宣告式管理,Operator工作原理,容器編排,主從模式,叢集模式,高可用策略,叢集擴縮容等方面展開。

目錄

  • 背景
  • 面臨的挑戰
  • 宣告式管理
  • Operator工作原理

    • 容器編排
    • 主從模式

    • 主從拓撲圖
    • 調和原理

  • 叢集模式
    • 叢集拓撲圖
    • 調和原理
  • 高可用策略
    • Kubernetes保證的高可用
    • Redis叢集的高可用
  • 監控觀測
  • 叢集擴縮容
  • 總結與展望

背景

Redis 是業務系統中較為常用的快取服務,常用於流量高峰、資料分析、積分排序等場景,並且通過中介軟體可以實現系統之間的解耦,提升系統的可擴充套件性。

傳統物理機部署中介軟體,需要運維人員手動搭建,啟動時間較長,也不利於後期維護,無法滿足業務快速發展的需求。

雲原生相較於傳統IT,可以助力業務平滑遷移、快速開發、穩定運維,大幅降低技術成本,節約硬體資源。

雲原生中介軟體是指依託容器化、服務網格、微服務、Serverless等技術,構建可擴充套件的基礎設施,持續交付用於生產系統的基礎軟體,在功能不變的前提下,提高了應用的可用性與穩定性。

在這種大趨勢下,有道基礎架構團隊開始了雲原生中介軟體的實踐,除了本文介紹的 Redis,還包括 Elasticsearch、ZooKeeper 等。

面臨的挑戰

利用雲原生技術可以解決當前Redis部署緩慢,資源利用率低等問題,同時容器化 Redis 叢集也面臨著一些挑戰:

• Kubernetes 如何部署 Redis 有狀態服務
• 容器 Crash 後如何不影響服務可用性;
• 容器重啟後如何保證Redis 記憶體中的資料不丟;
• 節點水平擴容時如何做到 slots 遷移時不影響業務;
• pod ip變化後叢集的狀態如何處理。

宣告式管理

對於一個 Redis 叢集,我們的期望是能夠 7x24 小時無間斷提供服務,遇故障可自行修復。這與Kubernetes API的宣告式特點如出一轍。

所謂“宣告式”, 指的就是我們只需要提交一個定義好的 API 物件來“宣告”我所期望的狀態是什麼樣子,Kubernetes中的資源物件可在無外界干擾的情況下,完成當前狀態到期望狀態的轉換,這個過程就是Reconcile過程。例如,我們通過yaml建立了一個Deployment ,Kubernetes將“自動的”根據yaml中的配置,為其建立好Pod,並拉取指定儲存捲進行掛載,以及其他一系列複雜要求。

因此,我們的Redis叢集是否可以使用一個類似的服務去完成這個過程呢?即我們需要定義這樣的物件,定義服務Reconcile的過程。Kubernetes的Operator剛好可以滿足這個需求,可以簡單的理解Operator由資源定義和資源控制器構成,在充分解讀叢集和Operator的關係後,我們將整體架構圖設計如下
在這裡插入圖片描述
Operator叢集本身採用Deployment部署,由ETCD 完成選主,上層與Kubernetes的Api Server 、Controller Manager等元件進行通訊,下層持續調和Redis叢集狀態。

哨兵模式中Redis服務用一套哨兵叢集,使用StatefulSet部署,持久化配置檔案。Redis server也採用 StatefulSet部署, 哨兵模式的例項為一主多從。

叢集模式中的每個分片使用StatefulSet部署,代理採用Deployment部署。原生Pod、StatefulSet、Service、排程策略等由Kubernetes本身負責。

Redis的資源定義在ETCD中儲存一份即可,我們只需要預先提交自定義資源的 yaml配置。如下所示為建立三個副本的Redis主從叢集


apiVersion: Redis.io/v1beta1
kind: RedisCluster
metadata:
  name: my-release
spec:
  size: 3
  imagePullPolicy: IfNotPresent
  resources:
    limits:
      cpu: 1000m
      memory: 1Gi
    requests:
      cpu: 1000m
      memory: 1Gi
  config:
    maxclients: "10000"

其中,kind定義使用的CR名稱,size為副本數,resources定義資源配額,config對應Redis Server的config,該定義儲存在Kubernetes的ETCD資料庫中,後續的具體資源申請與使用由Operator的Controller完成。

Operator工作原理

Operator 是 Kubernetes 的擴充套件模式,由CRD、Controller構成。它利用定製資源管理特定應用及其元件,Operator 遵循 Kubernetes 的理念。

Operator 無需任何修改,即可從 Kubernetes 核心中獲得許多內建的自動化功能,如使用 Kubernetes 自動化部署和執行工作負載, 甚至可以自動化 Kubernetes 自身。

Kubernetes 的 Operator 模式可在不修改 Kubernetes 自身的程式碼基礎上,通過控制器關聯到一個以上的定製資源,即可以擴充套件叢集的行為。Operator 是 Kubernetes API 的客戶端,核心功能是充當定製資源的控制器。
在這裡插入圖片描述

CRD: Custom Resource Definition, 在Kubernetes中一切皆是資源,資源就是CRD,使用者自定義的Kubernetes資源是一個型別 ,比如預設自帶的由Deployment,Pod ,Service等。

CR: Custom Resource 是實現CRD的具體例項。

使用者建立一個CRD自定義資源,ApiServer把CRD轉發給webhook,webhook 進行預設值配置 驗證配置和修改配置,webhook處理完成後的的配置會存入ETCD中 ,返回給使用者是否建立成功資訊。Controller 會監測到CRD,按照預先寫的業務邏輯,處理這個CRD,比如建立Pod、處理新節點與舊叢集關係等,保證執行的狀態與期望的一致。

容器編排

Redis 叢集在 Kubernetes 中的最小部署單位為 Pod,因此在架構設計之前,需預先考慮Redis特性、資源限制、部署形態、資料儲存、狀態維護等內容,為不同型別的Redis叢集配置合適的部署方式。

資源限制

Kubernetes 採用 request 和 limit 兩種限制型別來對資源進行分配。

• request(資源需求):即執行Pod的節點必須滿足執行Pod的最基本需求才能啟動。
\
• limit(資源限制):即執行Pod期間,可能記憶體使用量會增加,那最多能使用多少記憶體,這就是資源限額。

Redis 基本不會濫用 cpu,因此配置1-2個核即可。記憶體根據具體業務使用分配,考慮到部分場景下會fork較多的記憶體,例如 aof 頻繁刷寫,aof 重寫過程中,Redis 主程式稱依舊可以接收寫操作,這時會採用 copy on write (寫時複製)的方法操作記憶體資料,若業務使用特點為“寫多讀少”,那麼刷寫期間將產生大量的記憶體拷貝,從而導致 OOM,服務重啟。

一個有效的解決方式為減少刷寫次數,將刷寫操作放在夜間低流量時段進行。減少刷寫次數的方法為適當增加auto-aof-rewrite-min-size的大小,可配置使用記憶體的5倍甚至更大的最小刷寫量;其次可以主動觸發刷寫,判斷記憶體使用達到的配額兩倍時進行刷寫,實際部署時一般也會預留50%的記憶體防止OOM。

部署的基本形態

依據資料是否需要持久化或是否需要唯一標識區分服務為無狀態和有狀態的服務,Redis叢集需要明確主從、分片標識,大部分場景也需要資料持久化,Kubernetes使用StatefulSet來滿足這一類需求。StatefulSet的順序部署、逆序自動滾動更新更能提高Redis叢集的可用性。

具體的:

• Redis Server 使用 StatefulSet 啟動,為標識為{StatefulSetName}-0的Pod設定Master角色,給其他Pod設定為該Master的從節點。

• Proxy無需儲存任何資料,使用Deployment部署,便於動態擴充套件。

配置檔案
Redis Server 啟動時需要一些配置檔案,裡面涉及到使用者名稱和密碼,我們使用 Configmap 和 Secret 來儲存的。Configmap 是 Kubernetes的Api 物件,常用於儲存小於1MB的非機密鍵值對。而 Secret 可以用於儲存包含敏感資訊的密碼、令牌、金鑰等資料的物件。

兩種資源均可以在 Pod 執行的時候通過 Volume 機制掛載到 Pod 內部。

儲存
儲存使用的是 PVC(PersistentVolumeClaim) 加 PV (Persistent Volumes),PV為Kubernetes叢集中的資源,由儲存類StorageClass來動態供應,PV支援多種訪問模式:ReadWriteOnce、ReadOnlyMany 或 ReadWriteMany,通過PV定義儲存資源,PVC申請使用該儲存資源。另外通過根據儲存的 StorageClass 欄位 可抽象不同的儲存後端,如Cephfs、Cephrbd、Openebs、LocalStorage 等。

主從模式

主從拓撲圖

Redis容器化後建立的每個 CR 表示一個完整的Redis服務,具體的服務模式包括哨兵模式和叢集模式兩種,在進行容器化過程中,除覆蓋裸伺服器部署結構外,也對架構進行了一定程度的優化。

原生哨兵模式
原生哨兵模式為每套例項配一組哨兵。
在這裡插入圖片描述

共用哨兵模式
所有例項共用一組哨兵將進一步提高例項啟動速度,並在一定程度上可提高硬體資源利用率,實測單組哨兵可輕鬆應對百規模的主從叢集。
在這裡插入圖片描述

調和原理

Reconcile 實現持續監測並對主從叢集進行修復的功能。
在這裡插入圖片描述

  1. 檢查是否按照預期啟動了全部的Pod,比如建立3個Server,那麼需要按照預期啟動三個才能繼續進行後面的操作。
  2. 檢查Master的數量,確保該例項僅有一個主節點(數量為0主動選一個;數量大於1手動修復)。
  3. 檢查哨兵:

    (1)所有的哨兵是否監控了正確的Master;

    (2)所有的哨兵均知道相同的Slave;

    (3)再次檢查哨兵的數量,確保哨兵均可用。

  4. 檢查 Service,使Service 的Endpoints指向正確的Master。
  5. 檢查Redis config是否有做修改,有則對所有節點重寫config引數。

叢集模式

叢集拓撲圖

Redis Cluster + Proxy模式

通過在傳統Redis Cluster架構中引入代理功能,實現動態路由分發,並基於Kubernetes原生動態擴縮容特性,更易應對突發流量,合理分配使用資源。

代理基礎轉發規則如下

代理基礎轉發規則如下:

• 對於操作單個Key的命令,Proxy會根據Key所屬的Slot(槽)將請求傳送給所屬的資料分片。

• 對於操作多個Key的命令,如果這些Key是儲存在不同的資料分片,Proxy會將命令拆分成多個命令分別傳送給對應的分片。

服務部署前,也對代理的部分功能進行了補充,例如移除不可用節點等。

在這裡插入圖片描述

調和原理

reconcile實現持續監測並對Redis Cluster進行修復功能。
在這裡插入圖片描述

確保叢集健康的步驟

  1. 等待所有 Pod 狀態變為 Ready 且每個節點相互識別後,Operator 會在每個 StatefulSet 的 Pod 中挑選一個作為 Master 節點,其餘節點為該 Master 的 Slave。
  2. 獲取例項叢集所有Pod的ip、所有Pod的cluster info(包含nodeIP,主從關係等)。
  3. 進入恢復流程

    (1)處理失敗節點, 對部分節點重啟後的無效ip、狀態為noaddr的殭屍節點進行forget操作;

    (2)處理不可信節點 (所有handshake狀態的節點),發生於某一個節點被移除(由forget node觸發),但試圖加入叢集時,即該Pod在Operator角度下存在,但實際叢集節點並不需要該節點,處理方式為刪掉這個Pod,並再次做forget操作直到Pod被刪除。

  4. 任選一個節點,使用CLUSTER MEET給該節點加入所有已知節點。
  5. 為StatefulSet中的Pod建立主從關係,同時給其分配Slots。若當前Master數量同預期不一致,則對應擴縮容操作,具體見’叢集擴縮容’的橫向擴縮容小節。
  6. 檢查Redis config是否有做修改,有則對所有節點重寫config引數。

確保代理健康的步驟

  1. 獲取所有Running狀態代理的Pod ip。
  2. 從代理獲取Redis Server資訊,將叢集資訊同步到所有的代理上,代理中不存在的Server ip做移除操作。
  3. 若代理中無可用Redis Server, 表示被全部移除,則新增一個,代理可自動發現叢集其他Redis節點。

高可用策略

Kubernetes保證的高可用

(1) 容器部署保證高可用:

Redis部署最小資源物件為Pod,Pod是Kubernetes建立或部署的最小/最簡單的基本單位。

當啟動出錯,例如出現“CrashLoopBackOff”時,Kubernetes將自動在該節點上重啟該Pod,當出現物理節點故障時,Kubernetes將自動在其他節點上重新拉起一個。
Pod未出問題,但程式不可用時,依託於健康檢查策略,Kubernetes也將重啟該Redis節點。

(2) 滾動升級:

節點縱向擴容時,使用StatefulSet的滾動升級機制,Kubernetes將逆序重啟更新每個Pod,提高了服務的可用性。

(3) 排程的高可用:

Kubernetes本身不處理Redis 多個Pod組建的叢集之間的部署關係,但提供了部署策略,為保證特定場景下的高可用,如因物理節點導致所有Redis節點均當機,CRD在設計中加入了親和與反親和欄位。
預設使用 podAntiAffinity 做節點打散,如下所示例項instance1的所有 Pod 將被儘可能排程到不同的節點上。

spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - podAffinityTerm:
              labelSelector:
                matchLabels:
                  Redis.io/name: instance1
              topologyKey: Kubernetes.io/hostname
            weight: 1

Redis叢集的高可用

Redis 服務執行期間不可避免的出現各種特殊情況,如節點當機、網路抖動等,如何持續監測這類故障並進行修復,實現 Redis 叢集的高可用,也是 Operator 需解決的問題,下面以哨兵模式模式為例描述叢集如何進行故障恢復。

主節點當機:因物理節點驅逐、節點重啟、程式異常結束等導致的Redis主節點當機情況,哨兵會進行切主操作,然後Kubernetes會在可用物理節點上重新拉起一個Pod。

從節點當機:哨兵模式的Redis叢集未開啟讀寫分離,從節點當機對服務無影響,後續Kubernetes會重啟拉起一個Pod,Operator會將該Pod設定為新主節點的從節點。

叢集全部節點當機:發生概率極小,但基於持久化可將服務影響降至最低,叢集恢復後可繼續提供服務。

節點網路故障:主從模式下配置了三個哨兵用於叢集選主操作,哨兵叢集的每一個節點會定時對 Redis 叢集的所有節點發心跳包檢測節點是否正常。如果一個節點在down-after-milliseconds時間內沒有回覆Sentinel節點的心跳包,則該Redis節點被該Sentinel節點主觀下線。

當節點被一個 Sentinel 節點記為主觀下線時,並不意味著該節點肯定故障了,還需要Sentinel叢集的其他Sentinel節點共同判斷為主觀下線才行。

該 Sentinel 節點會詢問其他Sentinel節點,如果 Sentinel 叢集中超過 quorum 數量的 Sentinel 節點認為該 Redis 節點主觀下線,則該 Redis 客觀下線。

如果客觀下線的 Redis 節點是從節點或者是Sentinel節點,則操作到此為止,沒有後續的操作了;如果客觀下線的Redis節點為主節點,則開始故障轉移,從從節點中選舉一個節點升級為主節點。

叢集模式故障轉移與上述類似,不過不需要哨兵干預,而是由節點之間通過PING/PONG實現。

監控觀測

Redis 的監控採用經典的 Exporter+Promethus 的方案,Exporter 用於指標採集,資料儲存在 Prometheus 或其他資料庫中,最終 Grafana 前端將服務狀態視覺化。

叢集擴縮容

(1)縱向擴縮容
縱向擴縮容主要指Pod的CPU、記憶體資源的調整,基於Kubernetes的特性,只需修改例項對應的spec欄位,Operator的調和機制將持續監測引數變化,並對例項做出調整 。當修改cpu 、記憶體等引數時,Operator同步更新StatefulSet的limit、request資訊,Kubernetes將逆序滾動更新Pod,滾動更新時,若停掉的是主節點,主節點的preStop功能會先通知哨兵或者叢集進行資料儲存,然後做主從切換操作,從而將服務的影響降至最低。更新後的主從關係建立以及哨兵monitor主節點功能也由Operator一併處理,全過程對客戶端無感知。主從版、叢集版在該場景下均支援秒級斷閃。

(2)橫向擴縮容
橫向擴縮容主要指副本數或節點數的調整,得益於 Kubernetes 的宣告式 API,可以通過更改宣告的資源規模對叢集進行無損彈性擴容和縮容。

Redis Server擴容操作時,主從版本中Operator將獲取新節點ip, 新啟動節點將在下一輪調和時觸發slaveof 主節點操作,且同步過程中,哨兵不會將該節點選為主節點。叢集版本中Operator將在同步節點資訊後進行分片遷移,保證所有節點上的Slots儘可能均勻分佈。

Redis Server縮容操作時,主從版本中Operator將逆序銷燬Pod,銷燬時會先詢問哨兵,自己是否為主節點,若為主節點則進行先failover操作再退出。叢集版本中Operator中會先進行分片遷移,再對該節點做刪除操作。

代理的擴縮容,更易實現,根據流量波峰波谷規律,可手動定期在波峰到來時對 Proxy 進行擴容,波峰過後對 Proxy 進行縮容;也可根據HPA實現動態擴縮容,HPA也是Kubernetes的一種資源,可以依據Kubernetes 的Metrics API的資料,實現基於CPU使用率、記憶體使用率、流量的動態擴縮容。

總結與展望

本次以 Redis 為範例,闡述了有道基礎架構團隊在基礎設施容器化道路上的實踐,Redis上雲後將大幅縮短叢集部署時間,支援秒級部署、分鐘級啟動、啟動後的叢集支援秒級自愈,叢集依託於哨兵和代理的特性,故障切換對使用者無感知。

有道架構團隊最終以雲平臺的形式提供中介軟體能力,使用者無需關注基礎設施的資源排程與運維,重點關注具體業務場景,助力業務增長。未來,將進一步圍繞Redis例項動態擴縮容、故障分析診斷、線上遷移、混合部署等內容展開探索。

Redis 容器化後有哪些優勢?

Kubernetes 是一個容器編排系統,可以自動化容器應用的部署、擴充套件和管理。Kubernetes 提供了一些基礎特性:

部署:部署更快,叢集建立無需人工干預。容器部署後可保證每個的Redis節點服務正常,節點啟動後將由Operator持續監測調和Redis叢集狀態,包括主從關係、叢集關係、哨兵監控、故障轉移等。

資源隔離:如果所有服務都用同一個叢集,修改了Redis叢集配置的話,很可能會影響到其他的服務。但如果你是每個系統獨立用一個Redis群的話,彼此之間互不影響,也不會出現某一個應用不小心把叢集給打掛了,然後造成連鎖反應的情況。

故障恢復

(1) 例項的重啟:容器化後的健康檢查可以實現服務自動重啟功能;
(2) 網路故障:因宿主機網路故障帶來的例項延遲高,哨兵可進行主從切換,而為了保證叢集的健康,將由Operator負責同步叢集資訊。

擴縮容:容器部署可根據limit和request限制例項的cpu和記憶體,也可以進行擴縮容操作,擴容後的故障恢復由Operator處理。

節點調整:基於Operator對CRD資源的持續調和,可在Operator的Controller中為每個Redis例項進行狀態維護,因此,節點調整後帶來的主副關係建立、叢集Slots遷移等均可自動完成。

資料儲存:容器化可掛載Cephfs、LocalStorage等多種儲存卷。

監控與維護:例項隔離後搭配Exporter、Prometheus等監控工具更容易發現問題。

-- END --

相關文章