MySQL數(shù)據(jù)目錄將從宿主機(jī)的文件系統(tǒng)直接掛載,這意味著Docker不會(huì)產(chǎn)生寫操作的負(fù)擔(dān)。不過我們會(huì)將MySQL的配置直接放入鏡像中,使其成為常量。雖然可以更改配置,但由于Docker容器絕對(duì)不會(huì)重新使用,因此配置的改動(dòng)也絕對(duì)不會(huì)生效。如果因?yàn)槟承┰虮仨氷P(guān)閉一個(gè)容器,我們不會(huì)將這樣的容器重新啟動(dòng)。我們會(huì)刪除容器,使用最新鏡像通過相同參數(shù)(如果目標(biāo)狀態(tài)有變化則使用新參數(shù))重建一個(gè)容器,然后重新啟動(dòng)實(shí)例。
這樣的做法為我們提供了多個(gè)收益:
- 更易于控制配置漂移。有變化的最多只是Docker鏡像的版本,并且我們會(huì)密切監(jiān)視版本變化。
- MySQL的升級(jí)過程更簡(jiǎn)單。我們會(huì)新建一個(gè)鏡像,隨后按順序關(guān)閉老的容器。
- 如果任何地方出錯(cuò),只須重頭再來。不再需要考慮該如何打補(bǔ)丁,拋棄原有的一切新建所需容器即可。
鏡像的構(gòu)建工作也是通過驅(qū)動(dòng)無狀態(tài)服務(wù)的同一個(gè)優(yōu)步基礎(chǔ)架構(gòu)完成的。該基礎(chǔ)架構(gòu)會(huì)將鏡像復(fù)制到所有數(shù)據(jù)中心,使其可在每個(gè)數(shù)據(jù)中心的本地注冊(cè)表(Registry)中使用。
用一臺(tái)宿主機(jī)運(yùn)行多個(gè)容器也有相應(yīng)的劣勢(shì)。由于容器間無法進(jìn)行恰當(dāng)?shù)腎/O隔離,一個(gè)容器可能占用掉所有可用的I/O帶寬,導(dǎo)致其他容器開始卡頓。Docker 1.10引入了I/O配額功能,但我們尚未對(duì)該功能進(jìn)行過測(cè)試。目前我們主要通過避免超額訂閱(Oversubscribing)宿主機(jī),以及對(duì)每個(gè)數(shù)據(jù)庫進(jìn)行持續(xù)監(jiān)控等方式降低這一問題的影響。
Docker容器的調(diào)度和拓?fù)涞呐渲?/h3>
在具備了可作為主數(shù)據(jù)庫(Master)或從屬數(shù)據(jù)庫(Minion)配置并啟動(dòng)的Docker鏡像后,需要通過某種方式啟動(dòng)這些容器并配置恰當(dāng)?shù)膹?fù)制拓?fù)?。為此我們?cè)诿颗_(tái)數(shù)據(jù)庫宿主機(jī)上運(yùn)行了一個(gè)代理(Agent)。該代理可以獲取每臺(tái)宿主機(jī)上應(yīng)該具備的所有數(shù)據(jù)庫的目標(biāo)狀態(tài)信息。一個(gè)典型的目標(biāo)狀態(tài)是類似這樣的:
“schemadock01-mezzanine-mezzanine-us1-cluster8-db4”: { “app_id”: “mezzanine-mezzanine-us1-cluster8-db4”, “state”: “started”, “data”: { “semi_sync_repl_enabled”: false, “name”: “mezzanine-us1-cluster8-db4”, “master_host”: “schemadock30”, “master_port”: 7335, “disabled”: false, “role”: “minion”, “port”: 7335, “size”: “all” }}
從這些信息中可以看出,宿主機(jī)schemadock01上通過7335端口運(yùn)行了一個(gè)Mezzanine從屬數(shù)據(jù)庫,該數(shù)據(jù)庫的主數(shù)據(jù)庫位于schemadock30:7335。這個(gè)數(shù)據(jù)庫的尺寸為“all”,意味著這是該宿主機(jī)上運(yùn)行的唯一數(shù)據(jù)庫,因此可以獲得全部的可分配內(nèi)存。
如何確定這樣的目標(biāo)狀態(tài),這是另一個(gè)完全不同的話題,隨后我們還將撰文介紹,此處暫且不表,繼續(xù)介紹下一個(gè)步驟:宿主機(jī)上運(yùn)行的代理會(huì)接收這些信息,將其存儲(chǔ)在本地,然后開始進(jìn)行必要的處理。
這個(gè)處理過程實(shí)際上是一種無窮無盡的環(huán)路,每30秒進(jìn)行一次,這有些類似于每30秒運(yùn)行一次Puppet。該處理環(huán)路會(huì)通過下列操作檢查目標(biāo)狀態(tài)與系統(tǒng)的實(shí)際狀態(tài)是否匹配:
- 檢查是否有一個(gè)容器已經(jīng)在運(yùn)行。如果沒有,則使用配置創(chuàng)建一個(gè)并將其啟動(dòng)。
- 檢查該容器是否應(yīng)用了正確的復(fù)制拓?fù)洹H绻徽_,則盡量修復(fù)。
- 如果本應(yīng)是主數(shù)據(jù)庫但實(shí)際為從屬數(shù)據(jù)庫,首先確認(rèn)能否安全地更改其角色。為此我們會(huì)檢查原本的主數(shù)據(jù)庫是否為只讀的,并且所有GTID均已收到并應(yīng)用。一旦符合要求,即可安全地刪除到原本主數(shù)據(jù)庫的鏈接并啟用寫入。
- 如果是主數(shù)據(jù)庫但本應(yīng)禁用,則開啟只讀模式。
- 如果是從屬數(shù)據(jù)庫但復(fù)制未運(yùn)行,則設(shè)置復(fù)制鏈接。
- 根據(jù)具體角色檢查各種MySQL參數(shù)(
read_only
、super_read_only
、sync_binlog
等)。主數(shù)據(jù)庫應(yīng)當(dāng)是可寫的,從屬數(shù)據(jù)庫應(yīng)當(dāng)是只讀的。此外我們會(huì)關(guān)閉binlog fsync以及其他類似參數(shù)[2]以降低從屬數(shù)據(jù)庫的負(fù)載。 - 啟動(dòng)或關(guān)閉任何其他用于提供支持的容器,例如pt-heartbeat和pt-deadlock-logger。
這里需要注意,我們會(huì)盡可能采用單進(jìn)程、單用途容器這種做法。這樣就無需重新配置運(yùn)行中的容器,并且升級(jí)的過程也更易于控制。
如果任何一點(diǎn)出現(xiàn)錯(cuò)誤,執(zhí)行過程將拋出錯(cuò)誤信息并將其忽略。整個(gè)過程會(huì)在下次運(yùn)行時(shí)重試。我們會(huì)盡可能確保不同代理之間只需要最少量的協(xié)調(diào)。這意味著我們并不關(guān)心具體順序,例如供應(yīng)新集群時(shí)的供應(yīng)順序。如果用手工的方式供應(yīng)新集群,可能需要執(zhí)行類似下面的操作: