容器與持久數據
【編者的話】如何持久化保存容器的數據,這是自 docker 誕生之日起就一直存在的問題。本文介紹了多個與此相關的項目,包括 Raft 算法, Etcd , CockroachDB , Flocker 和高可用 PostgresSQL 集群 Governor 等。
越來越多的用戶想容器化所有的基礎設施,不僅包括 web 服務器、隊列和代理服務器,還包括數據庫服務器、文件存儲和緩存等。在 CoreOS Fest 的應用容器規范( appc )專家小組會議上,聽眾的第一個問題就是:對于有狀態的容器,該怎么辦?所謂有狀態,是指這些容器需要持久保存某些數據(即狀態),不能隨便丟棄這些數 據。 Google 公司的 Tim Hockin 是這樣回答的:「最好保證所有容器都是無狀態的。當然,最終還是要提供保存狀態的內部機制」。
如何持久化保存容器的數據,這是自 docker 誕生之日起就一直存在的問題。在 docker 的初始設計中,數據與容器共生共滅,人們很難把容器從一臺機器遷移到另一臺機器。在 docker 1.0 中,唯一與狀態有關的概念是volume:容器能夠存取的外部文件系統,完全脫離 docker 的管制。人們普遍的看法是不應該把數據放到容器中。
根據應用容器規范專家小組的說法,在最新的 appc 規范 中,整個容器鏡像和所有的初始化文件是不可更改的,但指定的配置目錄或者掛載除外。這體現的思路是管理員關心的數據應該存放在特定的、通過網絡掛載的目錄中,由容器編排框架管理這些目錄。
這也難怪有的讀者認為 Docker 公司和 CoreOS 公司從一開始就決定忽略容器數據的持久化存儲問題。當然,容器領域的很多開發者也有這種感覺,他們決心彌補這方面的缺陷。在 ContainerCamp 和 CoreOS Fest 大會上,發布了多個解決容器數據持久存儲問題的項目。其中一種解決方案是把容器數據持久保存在可靠的分布式存儲中,管理員不用再考慮容器數據的遷移問題。
Raft 共識算法
一個可靠的分布式存儲,必須確保所有節點的數據最終(如果不是立即)是一致的。單節點的數據庫很容易做到這一點。如果是橫跨多個節點的數據庫,每個節點都 有可能失效,就需要一套復雜的邏輯保證寫操作的一致性。這就是「共識」:多個節點就共享的狀態達成一致。 Diego Ongaro 是 Raft 、 LogCabin 和 RAMCloud 的開發者,他向觀眾介紹了共識算法的工作原理。
1989 年, Leslie Lamport 提出了 Paxos 共識算法 。在隨后的二十多年里,這是唯一的共識算法。但是 Paxos 算法也存在一些問題,其中最主要的一個問題是它非常之復雜。 Ongaro 說:「也許只有 5 個人真正懂得 Paxos 算法的方方面面」。如果不理解 Paxos 協議,人們沒辦法編寫驗證 Paxos 算法實現的正確性的測試。這意味著, Paxos 算法的不同實現存在很大的差別,你沒辦法證明某個實現是否正確,他說。
為此, Ongaro 和斯坦福大學教授 John Ousterhout 一起設計了一個既簡單又容易測試和解釋的共識算法,叫做 Raft 算法,含義是希望「逃離 Paxos 之島」。想了解 Raft 算法的細節,請閱讀 Ongaro 的 博士論文 。 Ongaro 說:「在決定每一步的設計取舍時,我們都會問自己:哪一種設計更容易解釋?」
實現 Raft 算法 的項目有很多,其中最主要的一個項目是 CoreOS 公司的 ectd 。項目多,說明 Raft 算法確實容易理解。 Ongaro 花半小時介紹了 Raft 算法的核心內容,又展示了一個完全用 JavaScript 編寫的 Raft 模型。
在Raft 集群中,每個節點都有一個共識模塊和一個狀態機。狀態機存儲的是用戶關心的數據,包括記錄數據狀態改變歷史的序列化日志。共識模塊的作用是保證當前節點的 日志與其它節點的日志保持一致。為了做到這一點,所有的狀態改變(寫操作)只能由一個領導節點負責實施:它首先發送消息給其它節點,要求確認這次寫操作; 只有大多數節點(叫做一個 quorum)確認本次寫操作,領導節點才會真正地寫入數據。這是一種兩階段提交模式,分布式數據庫已經用了很久。
每個節點還有一個倒計時時鐘,倒計時的時長是隨機的,比較長。如果當前節點不可用了,最先完成倒計時的節點會發消息給其它節點,請求重新選舉領導節點。如果大多數節點都確認了這條消息,那么這個節點就成為新的領導節點。新領導節點發送日志消息時,會為消息的term域設置一個新值,表明寫操作發生時誰是領導節點。這能夠避免日志沖突。
當然,真正的 Raft 算法不像上面描述的這么簡單,例如,它還要考慮日志丟失或者多次重復發送的情況。 Ongaro 能用不到半小時的時間解釋清楚 Raft 的核心設計,這意味著很多開發者都可以把 Raft 應用到開發的軟件中。例如, etcd、 CockroachDB 和 Consul 項目用到了 Raft ,同時很多編程語言都有 Raft 庫了,包括Python, C, Go, Erlang, Java 和 Scala 。
更新:在下面的評論中, Josh Berkus 提供了 Raft 的最新信息。
Etcd 和 Intel
Intel 公司 SDI(Software Defined Infrastructure, 軟件定義基礎設施) 部門的 Nic Weaver 講述了他們公司對 etcd 的改進。 Intel 對 docker 和 CoreOS 非常感興趣,有了它們,每個管理員能夠管理大量機器。借助云托管,企業很容易擴展自己的 IT 系統,擁有大量的服務。這時候,光靠配置管理可不夠, Intel 認為容器能夠進一步提高企業的擴展水平。
因此, Intel 還要求公司的團隊幫助改進容器基礎設施軟件。在第一篇文章中曾經提到, Intel 與 Supermicro 一起發布了 Tectonic 集群服務器套件 。除此之外, Intel 還做了一些軟件方面的工作,它選擇 etcd 作為突破口,試圖提供構建真正大型 ectd 集群的解決方案。以 etcd 為基礎的容器基礎設施管理工具能夠管理成千上萬的容器,這意味著 ectd 節點的數目也隨之增長,寫操作的次數增長得更快。
Intel 的團隊發現, etcd 集群包含的節點越多,就變得越慢。原因是 Raft 算法要求:每一個成功的寫操作,都必須有 50% 以上的節點把數據同步到磁盤上并且返回成功的確認。這樣,即使只是少數幾個節點遇到存儲慢的問題,也會拖慢整個集群的寫入速度。如果去掉磁盤同步的需求, 這會消掉一個有可能拖慢集群的因素,同時也帶來了風險:如果數據中心停電,整個集群的狀態也無法恢復了。
Intel 的解決方法發利用了 Xeon 處理器的一個特性:異步內存自我刷新( asynchronous DRAM self-refresh, ADR )。這是一小塊專門的內存,宕機時這里的信息不會丟失,系統重啟后可以繼續讀取這些信息。 ADR 本是為專用存儲設備設計的,不過 Linux 系統也提供一個 ADR API ,所以像 ectd 這樣的應用程序也可以使用 ADR 。
Intel 團隊修改 etcd ,用 ADR 作為日志的寫入緩沖區,取得的效果非常顯著。整個集群的寫操作時間占總時間的比例從 25% 降至 2% ,每秒鐘的寫入次數翻倍,達到 10,000 次以上。修改的代碼即將合并到 ectd 項目中。
CockroachDB
Raft 共識算法不僅可以用在像 etcd 這樣的鍵值存儲中,還可以用來構建功能完整的數據庫。雖然 ectd 很適合保存配置信息,它并不適合作為應用程序的數據庫系統,因為它沒有提供事務、復雜查詢處理等功能。 Cockroach 實驗室的 Spenser Kimball 介紹了他們的工作,一個名為 CockroachDB 的新型數據庫。為什么起這個名字呢?因為這種數據庫就像「小強一樣,你殺不死的。在這里殺死它,它會從別的地方重新冒出來」,他解釋說。
CockroachDB 模仿的是 Google Megastore 的設計。當 Kimball 還在 Google 工作時非常熟悉 Megastore 項目。設計的主要思想是支持整個集群范圍的一致性和可用性,包括對事務的支持。CockroachDB 計劃在分布式鍵值存儲之上添加 SQL 支持,類似于 Google 的 Spanner 項目,這樣它就能提供事務、SQL 和鍵值三種最主要的數據庫使用模式。 Kimball 說:「我們希望用戶專心于業務應用的構建,不必費心尋找臨時的分布式數據庫解決方案」。
部署后的 CockroachDB 數據庫由分布在服務器集群上的一組容器組成。數據庫的鍵-值地址空間被劃分為多個區間( range ),每個區間被復制到一部分節點,通常是 3 個或者 5 個,這些節點組成一個 Raft 共識組。整個集群包含多個 Raft 組, Kimball 稱之為MultiRaft。這樣,整個集群能夠包含更多的數據,有利于數據庫的擴展。
每個節點運行時,默認使用的事務模式是「序列化」( serializable ),即所有的事務都必須能夠按照日志的順序重現。如果一個序列化事務執行失敗,節點能夠恢復到之前的狀態。這樣,無需不必要的加鎖機制,就能實現分布式事務。
至此, CockroachDB 好像已經解決了所有人的分布式基礎設施的數據持久存儲問題。但是, CockroachDB 的主要問題是還沒有發布穩定版,很多規劃好的功能特性,例如 SQL 支持,還沒開始寫。也許將來 CockroachDB 能夠解決持久化存儲的很多問題,現在還是要先看看其它的解決方案。
高可用 PostgresSQL
由于分布式數據庫還沒有達到生產環境真正可用的程度,開發者們選擇現在流行的數據庫進行增強,使之具備容錯性,更方便容器化使用。兩個構建高可用 PostgresSQL 的項目分別是 ClusterHQ 的 Flocker 和 Compose.io 的 Governor 。
ClusterHQ 公司 CTO Luke Mardsen 在 ContainerCamp 上展示了 Flocker 。 Flocker 是一個數據卷管理工具,它能夠把數據庫容器從一臺物理主機遷移到另外一臺物理主機。此前,因為有狀態,重新部署數據庫容器是一個有挑戰的問題。現在有了 Flocker,容器編排框架能夠以幾乎相同的方式重新部署無狀態服務和數據庫容器。
Flocker 使用 ZFS on Linux 實現容器在不同物理機器之間的遷移。 Flocker 在專門的 ZFS 目錄下創建 docker 卷,這樣,用戶就能用導出 ZFS 快照的方式實現 docker 卷的移動和復制。用戶通過一個簡單的聲明式命令行接口執行上述操作。
Flocker 被設計成 docker 的一個插件。問題是目前 docker 還不支持插件。因此, Flocker 團隊為 docker 創建了一個名為 Powerstrip 的插件基礎設施。不過,這個工具還沒有合并到 docker 主分支。只有合并之后, Flocker 項目才能夠提供統一的管理接口。
Flocker 解決的是容器的遷移問題。 Compose 創建的 Governor 項目——根據 Chris Winslett 的講法——解決的是容器的可用性問題。 Governor 是一個編排原型,實現了一個自我管理的、復制的 PostgresSQL 集群。你可以把它視為 Compose 基礎設施的簡化版。
Compose 是一個 SaaS( Software as a Service, 軟件即服務) 托管公司,它提供的所有服務都必須是完全自動化的。為了提供 PostgresSQL 部署服務,必須支持自動化的數據庫副本部署和故障恢復。由于用戶擁有完全的數據庫訪問權限, Compose 希望解決方案不需要修改 PosgresSQL 的代碼或者用戶的數據庫。
Winslett 指出,不能用 PostgresSQL 數據庫集群的主節點存儲集群的狀態,因為必須保證主節點和副本節點的數據完全相同(譯者注:如果主節點存儲了集群狀態,而副本節點中沒有保存,二者的信息就不完全相同了)。開始時選擇分布式高可用信息服務 consul 保存集群的狀態。但是, consul 要求每個數據節點有 40GB 虛擬內存,低配置的云服務器節點顯然達不到這樣的要求。因此, Winslett 用更簡單的 etcd 替換掉 consul ,這也大大簡化了故障恢復的處理邏輯。
每個容器都有一個控制 PostgreSQL 的 Governor 守護進程。啟動時, Governor 守護進程查詢 ectd ,找出當前的主節點,然后從主節點復制數據到當前節點。如果目前還沒有主節點, Governor 守護進程會請求 etcd 賦予它一個主節點密鑰, ectd 確保只有一個節點能夠得到這個密鑰。當一個節點得到密鑰成為新的主節點后,其它節點開始復制從這個節點的數據。主節點密鑰是有生存時間( time-to-live, TTL )的。如果當前主節點失效,很快就會進行一次新選舉,保證集群很快有一個新的主節點。
有了 Governor , Compose 管理 PostgreSQL 的方式跟管理 MongoDB 和 Elasticsearch 這樣的多主、非關系型數據庫的方式差不多。在 Governor 系統中, etcd 保存 PostgreSQL 節點的配置,用容器編排系統部署 PostgreSQL 節點,不需要手動管理,每個容器也不需要特別的處理。
結論
當然,與此相關的項目和演講還有很多。例如,在 CoreOS Fest 期間, Sean McCord 的演講討論了 docker 容器如何使用分布式系統 ceph 作為塊設備,以及如何用容器運行 ceph 的每一個節點。這種方法還比較初步。不過,如果容器服務需要大規模的文件存儲,使用 ceph 是一種可行的候選方案。 Alex Crawford 還介紹了新的 CoreOS 引導配置工具 Cloudconfig 。
隨著 Linux 容器生態系統從測試實例、 web 服務器拓展到數據庫和文件存儲,可以想見,會繼續出現解決容器數據持久存儲問題的新方法和新工具。參加 CoreOS Fest 和 ContainerCamp ,你能清楚地感受到 Linux 容器技術還不是很成熟。讓我們共同期待,在接下來的一年中出現更多的相關項目和方法。