MySQL 到底能不能放到 Docker 里跑?

前言

前幾月經常看到有 MySQL 到底能不能放到 Docker 裡跑的各種討論。這樣做是錯的!這樣做是對的!說錯的理由也說了一大堆,說對的思想也很明確。大家都有道理。但是我本人覺得這樣的討論落地意義不大。因為對與錯還是要實踐來得出的。

所以同程旅遊也很早開始了 MySQL 的 Docker 化實踐,到目前已經有超一千多個 MySQL 實例在 Docker 平臺安全穩定地跑著,DB 運維能力發生了質的提高(DBA 再也不用擔心刪庫跑路了)。

當然這樣是不是可以證明之前的討論結論——是對的。我想也不一定,因為我們還只是一隻在學飛行的小鳥,還要更多的學習,所以我們特將我們在 MySQL 的 Docker 化上的實踐分享給大家。

背景介紹

同程旅遊早期的數據庫都以 MSSQL 為主,這個產品有個特點就是 UI 操作很棒。但是批量和自動化管理很難做,人力的工作很多。後來逐漸替換為 MySQL 後也是按照傳統的運維方式管理。導致大部分的工作需要人肉運維。

當然像我們早期使用過的 MSSQL 也是有優點的:就是單機性能比較好,在當年那個資源不夠的年代裡我們常可以在高可用的實例上運行多個庫。這種情況下物理機數量與實例數量還是比較可控的,相對數量比較少,人肉運維完全可以應對。

但是 MSSQL 的缺陷也很多,比如做水平拆分比較困難,導致數據庫成為系統中較大的一個瓶頸。但在我們使用 MySQL+ 中間件(我們做這個中間件也是下了不少心思的,以後可以分享一下)做水平拆分後就開始解決了這個瓶頸。

水平拆分的引入也帶來了一個小缺點,就是會造成數據庫實例數量大幅上升。舉個例子我們做 1024 分片的話一般是做 32 個 node,一主一從是必須的(大部分情況是一主兩從),那麼至少 64 個實例,再加上應急擴展和備份用的節點那就更多了(中間件的開發者更希望是 1024 片就是 1024 個實例)。

一次上線做一個 32node 分片擴展從庫,兩個 DBA 足足花了 4 個小時。另外,如果做單機單實例那肯定更不行了,別的不說,成本也會是個大問題,且物理機的資源也未能較大化利用。況且因為 MySQL 單體的性能沒優勢所以分片居多所以大部分情況下並不是每個庫都能跑滿整個物理機的。即使有部分能跑滿整機資源的庫,它的多節點備份,環境一至性和運維動作統一等問題也會讓 DBA 一頭糟,忙碌又容易出錯的工作其實是無意義的。

有了單機多實例運行 MySQL 實例的需求。單機多實例要思考的主要問題就是如果進行資源隔離和限制,實現方案有很多,怎麼選?KVM,Docker,Cgroups 是目前的可以實現隔離主流方案。

KVM 對一個 DB 的隔離來說太重了,性能影響太大,在生產環境用不合適。這是因為 MySQL 運行的就是個進程而且對 IO 要求比較高,所以 KVM 不滿足要求 (雖然優化以後 IO 能有點提升)。

cgroups 比較輕,雖然隔離性不是很高,但對於我們的 MySQL 多實例隔離來說是完全夠用了(Docker 的資源限制用的就是 cgroups)。但是我們還想針對每個 MySQL 實例運行額外的管理進程 (比如監控等等)。用 cgroups 實現起來會比較複雜,並且我們還想讓實例管理和物理機區分開,那 cgroups 也放棄。

至於 Docker,那就很不錯了,那些裸用 cgroups 的麻煩它都給搞定了。並且有 API 可以提供支持,開發成本低。而且我們可以基於 Docker 鏡像來做部署自動化,那麼環境的一至性也可輕鬆解決。所以最終我們選擇了 Docker 作為雲平臺的資源隔離方案 (當然過程中也做了很多性能、穩定性等的適配工作,這裡就不贅述了)。

下面兩個圖可以形象展示這款產品帶來的革命性意義:

MySQL 到底能不能放到 Docker 裡跑?

當然要能稱之為雲,那麼平臺最基本的要求就是具備資源計算、資源調度功能,且資源分配無需人工參與。對用戶來講,拿到的應該是直接可用的資源,並且天生自帶高可用、自動備份、監控告警、慢日誌分析等功能,無需用戶關心資源背後的事情。其次才是各種日常的 DBA 運維操作需求服務化輸出。下面我們就來講講我們這個平臺是如何一步步實現的。

平臺實現過程

站在巨人的肩膀上

我一直認為評價一款數據庫的優劣,不能只評價數據庫本身。我們要綜合它的周邊生態是否健全,比如:高可用方案、備份方案、日常維護難度、人才儲備等等。當然對於一個雲平臺也一樣,所以我們進行了短平快的試錯工作,將平臺分為多期版本開發。第一個版本的開發週期比較短,主要用來試驗,所以我們要儘可能運用已有的開源產品來實現我們的需求,或者對已有開源產品進行二次開發以後實現定製化的需求。以下是我們當時用到的部分開源產品和技術。

MySQL 到底能不能放到 Docker 裡跑?

下面選幾個產品簡單說一下我們通過它實現什麼功能:

Percona:我們的備份、慢日誌分析、過載保護等功能都是基於 pt-tools 工具包來實現的。

Prometheus:性能優越且功能強大的 TSDB,用於實現整個平臺實例的監控告警。缺點是沒有集群功能,單機性能是個瓶頸 (雖然單機的處理能力已經很強了),所以我們在業務層面進行了 DB 拆分,實現了分佈式存儲及擴展。

Consul:分佈式的服務發現和配置共享軟件,配合 prometheus 實現監控節點註冊。

Python:管理 Docker 容器中 MySQL 實例的 agent 以及部分操作腳本。

Docker:承載 MySQL 實例並實現資源隔離和資源限制。

總體架構

MySQL 到底能不能放到 Docker 裡跑?

容器調度系統如何選擇

容器調度的開源產品主要有 Kubernetes 和 mesos,但是我們並沒有選用這兩個。主要原因是我們內部已經開發了一套基於 Docker 的資源管理、調度的系統,至今穩定運行 2 年多了。這套架構稍作修改是符合需求的。

另外第三方的資源調度系統兼容我們目前的高可用架構,其他自動化管理有些難度,同時資源分配策略也需要定製化。所以最終還是選擇採用了自研的資源調度管理。適合自己現狀的需求才是較好的。當然後面有機會做到計算調度和存儲調度分離的情況下我們可能會轉向 Kubernetes 的方案。

工作原理

我們就拿創建集群來舉例吧。當平臺發起一個創建集群的任務後,首先會根據集群規模 (一主一從還是一主多從,或者是分片集群) 確定要創建的實例數量,然後根據這個需求按照我們的資源篩選規則 (比如主從不能在同一臺機器、內存配置不允許超賣等等),從現有的資源池中匹配出可用資源,然後依次創建主從關係、創建高可用管理、檢查集群複製狀態、推送集群信息到中間件 (選用了中間件的情況下) 控制中心、最後將以上相關信息都同步到 CMDB。

以上的每一個工作都是通過服務端發送消息到 agent,然後由 agent 執行對應的腳本,腳本會返回指定格式的執行結果,這些腳本是由 DBA 開發的。這種方式的優勢在於,DBA 比任何人都瞭解數據庫,所以通過這種方式可以有效提升項目開發效率,也能讓 DBA 參與到項目當中去。開發只需要寫前臺邏輯,DBA 負責後端具體執行的指令。如果未來功能有變更或迭代的話,只需要迭代腳本即可,維護量極小。

資源的調度分配原則

經過對同程多年的 DB 運維數據分析得到如下經驗:

CPU 較大超賣 3 倍,內存不超賣;

同一機房優先選擇資源最空閒的機器;

主從角色不允許在同一臺機器上;

若有 VIP 需求的主從端口需要一致,無 VIP 需求直接對接中間件的無端口一致的限制;

分片的集群將節點分佈在多臺物理機上;

產品分類

MySQL 到底能不能放到 Docker 裡跑?

核心功能

MySQL 到底能不能放到 Docker 裡跑?

以上是已經上線的部分核心功能,還有很多功能就不再一一展示。

備份恢復系統

MySQL 到底能不能放到 Docker 裡跑?

備份工具我們是用 percona-xtrabackup。通過流備份的方式將數據備份到遠端的備份服務器。備份服務器有多臺,分別按照所屬機房劃分。

我們提供了手工備份和定時備份來滿足不同場景的需求。多實例備份一定要關注磁盤 IO 和網絡,所以我們的備份策略會限制單個物理機上並行備份的數量,另外單個機房備份任務隊列的並行度也有控制,確保並行備份任務始終保持到我們指定的數量。

假如整個機房並行的是 50 個任務,那麼這 50 個當中如果有 5 個提前備份完成,那麼會新加入 5 個等待備份的任務進入這個備份隊列。我們後來改造了備份的存儲方式,直接將備份流入分式存儲。

監控告警系統

MySQL 到底能不能放到 Docker 裡跑?

在上線這套雲平臺前,我們還是用傳統的 zabbix 來實現監控告警的。zabbix 的功能的確非常強大,但是後端的數據庫是個瓶頸,當然可以通過數據庫拆分的方式解決。

數據庫要監控的指標比較多,如果採集的項目比較多,zabbix 就需要加 proxy,架構越來越複雜,再加上和我們平臺對接的成本比較高,對一些複雜的統計類查詢 (95 值、預測值等) 性能比較差。

所以我們選了一款 TSDB——prometheus,這是一款性能極強、極其適合監控系統使用的時序性數據庫。prometheus 優點就是單機性能超強。但凡事又有兩面性,它的缺點就是不支持集群架構 (不過我們解決了擴展的問題,下面會講到)。

prometheus 的使用應該是從一年前就開始的,那時候我們只是把它作為輔助的監控系統來使用的,隨著逐漸熟悉,越來越覺得這個是容器監控的絕佳解決方案。所以在上雲平臺的時候就選擇了它作為整個平臺的監控系統。

監控數據採集

prometheus 是支持 pushgateway 和 pull 的方式。我們選用了 pull 的方式。因為結構簡單,開發成本低的同時還能和我們的系統完美對接。consul 集群負責註冊實例信息和服務信息,比如 MySQL 實例主從對應的服務、Linux 主從對應的服務、容器註冊對應的服務。然後 prometheus 通過 consul 上註冊的信息來獲取監控目標,然後去 pull 監控數據。監控客戶端是以 agent 的形式存在,prometheus 通過 HTTP 協議獲取 agent 端採集到的數據。

監控指標畫圖

不得不說 grafana 是監控畫圖界的扛把子,功能齊全的度量儀表盤和圖形編輯器,經過簡單配置就能完成各種監控圖形的展示。然後我們打通了雲平臺和 grafana 的關聯,用戶在雲平臺需要查看實例或集群信息,只要點擊按鈕即可。

MySQL 到底能不能放到 Docker 裡跑?

MySQL 到底能不能放到 Docker 裡跑?

告警管理

告警管理分為:告警發送、告警接收人管理、告警靜默等功能。prometheus 有一個告警發送模塊 alertmanager,我們通過 webhook 的方式讓 alertmanager 把告警信息發送到雲平臺的告警 API,然後在雲平臺來根據後面的邏輯進行告警內容發送。

alertmanager 推過來的只是實例緯度的告警,所以我們結合告警平臺的實例相關信息,會拼出一個多維信息的告警內容。讓 DBA 一看就知道是誰的哪個集群在什麼時間觸發了什麼等級的什麼告警。告警恢復後也會再發一次恢復的通知。

MySQL 到底能不能放到 Docker 裡跑?

alertmanager 也是功能強大的工具,支持告警抑制、告警路由策略、發送週期、靜默告警等等。有需要可以自行配置。但是這種和平臺分離的管理方式不是我們想要的,所以就想把 alertmanager 對告警信息處理的這部分功能集成到雲平臺內。

但是官方文檔並沒有提及到 alertmanager 的 API,通過對源碼的分析,我們找到了告警管理相關的 API。然後 alertmanager 的原生 UI 上操作的功能完美移植到了我們的雲平臺,同時新增了實例相關集群名稱、負責人等更多緯度的信息。

下面是一些操作樣例:

當前告警:

MySQL 到底能不能放到 Docker 裡跑?

添加告警靜默:

MySQL 到底能不能放到 Docker 裡跑?

已創建的靜默規則:

MySQL 到底能不能放到 Docker 裡跑?

慢日誌分析系統

MySQL 到底能不能放到 Docker 裡跑?

慢日誌的收集是通過 pt-query-digest 每小時進行本地分析,分析完成以後將結果寫入慢日誌存儲的數據庫來完成的。當然如果用戶需要立刻查看當前慢日誌的情況,也可以在界面點擊慢日誌分析。分析完成後可以在 UI 界面點擊慢日誌查看,就能看到該實例的慢日誌分析結果。它同時集成了 explain、查看 table status 等功能。

集群管理

集群管理作為該平臺的核心功能之一,佔據了整個平臺 70% 的工作。這些功能就是 DBA 運維中經常需要用到的。我們的設計思路是以集群為單位,所以同時只能操作一個集群上的實例。這樣就不會在一個頁面上顯示過多無用的信息,看著亂還有可能導致誤操作。看了下圖中的這些功能就能更明白為什麼要這麼設計了。

MySQL 到底能不能放到 Docker 裡跑?

圖中只是一部分,還有部分未展示出的功能 (集成中間件、Dashboard、黑屏診斷窗口等),在後版中功能更多。

高可用

高可用方案我們使用了目前最流行的 MySQL 高可用方案 MHA。MHA 的優缺點就不在這裡講了,有 DBA 同學的應該都已經很熟悉了。這裡我說一下我們基於同程業務做的調整。

GTID

因為我們主要使用的 MariaDB,但是 MHA 版本也是不能支持 MariaDB 的 GTID 切換。所以我們在原有的基礎上做了改進,支持了 MariaDB 的 GTID。使用 GTID 以後靈活切換是一個方面,另外一個方面是 sync_master_info 和 sync_relay_log_info 就不需要設置成 1 了 (MariaDB 不支持寫 table,只能寫 file),極大減少了從庫複製帶來的 IOPS。

切換時調整相關參數

我們在切換時調整 sync_binlog 和 innodb_flush_log_at_trx_commit 參數,這兩個參數是決定數據落盤方式的,默認大家都是設置雙 1。這樣相對數據最安全,但是 IO 也較高。

雲服務的多實例部署會導致一臺物理機上既有 master 又有 slave。我們肯定不希望 slave 產生太高的 IO 影響到同機器的其他 slave(雖然可以 IO 隔離,但是優先降低不必要 IO 才靠譜)。所以理論上來說 Master 上面設置雙 1,slave 則可以不這樣設置。但是切換後原來的 salve 可能會變成了 master。所以我們默認 slave 非雙 1,在 MHA 切換的時候會自動將新 master 的這兩個參數設置為 1。

哨兵

我們在多個點部署了哨兵服務。這個哨兵是一個簡單的 API 服務,帶上響應的參數可以請求到指定的實例。當 MHA manager 檢測到有 Master 無法連接時,會觸發 secondary check 機制,帶著 master 相關信息請求哨兵節點的 API,根據哨兵節點返回情況,若超過半數無法連接則切換。否則放棄切換。

高可用切換對接 DB 中間件

MySQL 到底能不能放到 Docker 裡跑?

DB 中間件和 DB 通過物理 IP 連接,當發生高可用切換時將的 Master IP、Master port 信息推送到 DB 中間件控制中心,DB 中間件拿到配置後立刻下發並生效。

實例、庫遷移

MySQL 到底能不能放到 Docker 裡跑?

遷移功能初衷是為了將平臺外的實例或者庫遷移到平臺裡面來,後來隨著逐漸使用發現這個功能可挖掘的空間很大,比如可以做平臺內庫表拆分等需求。實現原理也很簡單,用 mydumper 將指定數據備份下來以後,再用 myloader 恢復到指定數據庫。

這是一個全量的過程,增量複製用的是用我們自己開發的一個支持並行複製的工具,這個工具還支持等冪處理,使用更靈活。沒有用原生複製的原因是,假如要將源實例多個庫中的一個庫遷移到目標實例,那麼原生複製就需要對 binlog 做複製過濾,這裡面涉及到配置修改,實例重啟,所以果斷不考慮。

實現過程並沒有高大上,但是完全滿足需求。當然 mydumper 和 myloader 也有一些問題,我們也做了小改動以後才實現的。後面我們計劃用流的方式去做數據導出導入 (類似於阿里開源的 datax)。

遷移完成,增量無延遲的情況下,大家會關心遷移前後數據一致性的問題,我們提供了自研的數據校驗工具。實測 300G 的數據校驗時間約為 2 至 3 分鐘,快慢取決於開多少線程。

屏蔽底層物理資源

對用戶來講,平臺提供的是一個或一組數據庫服務,不需要關係後端的實例是在哪臺機器上。資源計算和調度全部由系統的算法進行管理。

提升資源利用率 (CPU、內存)

通過單機多實例,CPU 資源可超賣,有效提高 CPU 資源的利用。內存資源未超賣,但是可以控制到每個實例的內存使用,確保每個實例都能有足夠的內存。若有剩餘內存,則繼續分配容器即可,不 OOM 的情況下壓榨內存資源。

提升運維效率

效率的提升得益於標準化以後帶來的自動化。批量運維的成本很低。以前部署一套分片集群需要花費將近 6 個小時 (不包含對接中間件的 1 到 2 個小時),而現在只需要 5 分鐘即可部署完成。並且部署完成以後會將提供一套中間件 +DB 分片集群的服務。

精細化管理

平臺上線後有效提高了資源利用率,同時我們按照 1 庫 1 實例的方式,可以有效避免不同庫的壓力不均導致相互影響的問題。並且性能監控也能精準到庫級別。

結語

以上這些只是一個開始, 後面還有很多功能需要完善,下面是近期策劃的一些功能,其中有些已經在後版中開發完成。 隨著功能的不斷迭代,我們會打造一個更加完美的私有云平臺。

MySQL 到底能不能放到 Docker 裡跑?

數據庫私有云平臺的上線對同程 DB 來說,意味著一個時代的結束,也意味著一個時代的開始。結束的是傳統運維低效、高成本的運維時代,開始的是一個低成本、高效率、高保障的運維時代。我們相信未來會更美好!


分享到:


相關文章: