天天看點

Coding-Job:從研發到生産的容器化融合實踐

大家好,我是來自 CODING 的全棧開發工程師,我有幸在 CODING 參與了 Coding-Job 這個容器化的編排平台的研發。大家對 CODING 可能比較了解, Coding.net 是一個一站式開發平台,具有代碼托管,任務管理,産品示範和 WebIDE 等功能。整體功能看起來比較複雜且較為分散。

這是我們 Coding 的架構演進流程。那麼怎麼評判一個系統複不複雜,個人覺得看兩個名額,一個就是運維人員用多久時間可以把新的代碼部署上線。比如說我之前所在的創業團隊,每次部署都是晚上九點以後很少通路量的時候進行,部署當天晚上吃飯時還說半小時就差不多可以更新上線了。最後發現四五個小時過去了還沒有上線。為什麼呢?可能有一些前端的樣式問題,有些配置檔案沒有做好。為什麼會找出這些問題?很多情況下是因為,我們線上和線下狀态的不統一。這是其中一個名額,還有一個名額就是,一個新同僚來到公司以後,要多長時間可以把整個系統的開發環境部署起來。比如說我是一個後端程式員,得先要裝一個虛拟機,Nginx 伺服器,資料庫,具體語言的編譯環境,以及運作 npm install 來安裝一些前端的代碼庫,這些操作,加上國内有奇葩的網絡問題,我們通常會耗費半個到一個工作日之久。而用了 Coding-Job 來部署本地開發環境,這個時間可以降低到半個小時。

Coding 網站一開始是一個簡單的 Java War 包,編譯、打包、上傳,再到上線,還算是一個比較簡單的操作。随着業務的快速發展,比如說有短信功能,有郵件、短信推送,這些東西如何把它融合在一起,是一件比較棘手的事情。前兩年微服務也比較火,我們也采用了微服務的架構,允許開發者用他最拿手的架構和語言,選一個微服務來做。比如說短信,短信子產品寫好了,接口文檔寫好,發給寫 Coding 背景的同僚,然後一下功夫就完事兒了。

這種方式使我們的代碼能夠跟的上 Coding 迅猛的業務發展。但我們突然發現一個問題,就感覺全公司已經沒有人會清楚的記得每個微服務是怎麼編譯、配置和啟動的了。于是就想,有沒有這樣一種東西,跟黑盒子一樣,可以讓我們把一堆代碼放到裡面去。不用管黑盒子是怎麼配置運作的,把它放到線上去,讓它能跑起來,代碼在裡面怎麼配置是盒子的事情,我外面環境脫胎換骨,也不影響盒子裡面的正常運作。這個黑盒子,好像就可以解決我們的問題,而這,其實就是虛拟化技術。在 2015 年初 Docker 剛火起來的時候,我們研究了當時傳統方案虛拟機和 Docker 技術,最後采用了 Docker。

圖中是傳統方案-虛拟機的架構圖,我們在買了主機(Infrastructure)裝了主控端作業系統(Host Operating System)以後,想要實作虛拟化技術,就要安裝一套虛拟機管理軟體(Hypervisor),比如 VMware vSphere,它可以允許跑三個黑盒子,其實就是虛拟機,在三個虛拟機裡面分别跑三個作業系統(GuestOS),而在這三個作業系統之上,我們才運作我們真正想要運作的東西,即 App1、App2、App3 這三個微服務。那麼想回來,為什麼我們要奇奇怪怪地啟動三個虛拟機呢?為什麼要将大量地實體資源耗費在可有可無的上層作業系統上面呢?

人們終于想清楚了這件事情,就開始着手研究輕量級的虛拟化技術。Liunx 核心的命名空間(Name Space)和 控制組 Cgroups (Control Groups) 等技術應運而生,實作了程序間命名空間的隔離,網絡環境的隔離,和程序資源控制。Docker 正是依靠這些技術突然火熱了起來。我們在用 Docker 跑微服務的時候,圖中的虛拟機管理軟體被替換成了一個 Docker Engine,每個 App 跑在 Docker 容器中,容器與主控端共享一個作業系統,它能節省較多的實體資源,啟動速度也由虛拟機的分鐘級達到秒級,I/O 性能也接近直接運作與主控端上。

說到命名空間,大家都知道 Linux 隻會有一個 PID 為 1 的程序 init,有了命名空間以後,在每個程序命名空間裡面,PID 清單是互相隔離的,在容器裡面,也可以有一個 PID 為 1 的程序,這就實作了程序間命名空間的隔離。而控制組,它是可以做到對每個容器所占用的實體資源的控制,比如指定 CPU 的使用,塊裝置的讀寫速度。結合這兩種技術,我們可以做到的是讓 Docker 容器在保持一定性能的同時,盡可能地在隔離性上面接近虛拟機。

于是我們就将 Coding 的微服務一個一個地遷移到 Docker 内,以容器的方式部署運作,然後你會以為是這樣一幅場景。

但事實上可能是這樣的。在 Coding 我們的大大小小的微服務五十餘個,就像雞蛋不能放在一個籃子裡一樣,容器也不能被放在同一台雲主機上面,而是整整齊齊地分開來放,不然怎麼能叫分布式呢?我們的微服務,對于檔案系統,對于彼此的網絡還存在一些依賴性,像一個蜘蛛網一樣串在一起,對于微服務所允許的主機位置和相應的主機配置都是有要求的。是以是需要一個中心化的東西去幫我們去存放每個服務的配置以及它們運作的代碼甚至 Docker 鏡像。而這個中心化的東西所做的事情就是編排,就好像我們在聽音樂會上的時候,台上的指揮家一樣,它告訴這台雲主機做什麼任務,以什麼配置運作這個任務,用什麼代碼來執行它,然後再告訴另外一台機器,又做什麼任務等等。

為此我們研究了兩款業界比較火的兩款開源容器管理架構,也就是這個中心化的東西。

Apache Mesos 是一個用于叢集的作業系統,它會把一個叢集所具有的所有實體資源抽象成一台計算機供使用者使用,比如你的叢集裡有一百台伺服器,每台伺服器一個 CPU,那直白的來說 Apache Mesos 能給你一台具有一百個 CPU 的電腦。

Mesos 做的比較好一點的是實體資源的配置設定。圖中可以看到 Mesos 具有 Framework 的概念,它等同于我們通常所說的應用程式,每個 Framework 由排程器(Scheduler)和執行器(Executor)兩部分組成。排程器負責排程任務,執行器負責執行任務。大家可以看到圖中間部分有 Mesos Master,圖下方有幾個 Mesos Slave, Master 是管事的,相當于包工頭,而 Slave 是奴隸,負責幹活兒的。這些 Slave 其實就是我們的雲主機,每一個 Slave 就是一台主機,這邊可以看到一個實體資源配置設定的流程,首先 Slave1 發現它有 4GB 記憶體和 4CPU 的空閑實體資源,于是它向 Mesos Master 彙報,詢問它有沒有任務可以做,然後 Mesos Master 會詢問目前可用的 Framework1 的排程器有沒有可以配置設定給 Slave1 的活兒,排程器按照空閑實體資源取出了兩個任務回應給 Mesos Master,然後 Mesos Master 又轉發給 Slave1,Slave1 又開始幹活了。當然此時大家會發現 Slave1 上面還有 1GB 記憶體和一個 CPU 是空閑的,那麼它可以通過 Mesos Master 請求 Framework2 去請求任務來做。

是以 Mesos 能帶給我們的好處,一是高效,我們通過對主機的實體資源量化以及對任務的資源需求量化,使得多個任務能在同一台主機上運作,并充分利用其實體資源,二是可擴充性,Mesos 提供五十多種 Framework 幫助我們處理多種多樣的任務。

另外一款名叫 Kubernetes 的 Docker 容器叢集管理架構也特别火熱,它借鑒了谷歌的 Borg,為容器化系統提供了資源排程,部署運作,服務發現等各種功能。它更加傾向于容器的編排,具有系統自愈功能,我們來看一下它的架構。

我先講架構圖,左邊是一個控制的節點,右邊是一台台的 Slave,我可以在左上角用 kuberctl 送出一個作業,讓 kubernetes 幫你把作業配置設定一個 Slave 上面去。在 Kubernetes 裡面,排程任務的機關是 Pod,中文是豆莢。就像豆莢一樣,裡面是有很多豆子的,那這些豆子是什麼呢?這些豆子其實是我們的 Docker 容器,這些容器共享一個豆莢,在 Kubernetes 裡面就是一個共享容器組的概念。所有在一個 Pod 裡面的 Docker 容器都是共享命名空間的,這解決了一些特殊場景的需求。因為使用 Docker 的最佳實踐是在每一個容器裡面隻做一件事,不把 Docker 容器當做虛拟機來用。而這意味着有些時候,比如 B 程序需要監測(watch) A 程序的程序号(PID)或者和 A 程序進行 IPC 通信。如果是一個容器一個命名空間,這顯然是不能直接實作的,而這就是 Pod 的應用場景。

那麼我們現在來看一下一個 Pod 是怎麼來定義,這邊所示的 Pod Definition 是我們通過 kubectl 向 Kubernetes 送出作業的配置檔案。在 Pod 中有多個 Container, 這裡它定義了一個 Container 是 Nginx 以及 Nginx 的一些配置,例如鏡像名和容器端口。

接下來我們講一下它的自愈系統,kubernetes 通過副本控制器(Replication Controller)來實作一個或者多個 Pod 的運作的控制。上圖是我們向 Replication Controller 送出的配置檔案,Template 字段代表了這個 Controller 使用的 Pod 的模闆,而 Replicas 字段代表了我們希望 kubernetes 用這個 Pod 模闆産生多少個 Pod 同時運作,這涉及到 kubernetes 裡面一個叫理想狀态(desired state)的概念。送出了這個配置檔案以後,如果有任何一個 Pod 因為某種原因 Down 掉了,那麼原先應該運作三個的副本隻剩下兩個,而 kubernetes 會想辦法達到理想狀态,是以它會啟動一個新的副本,最終變成三個副本,這個就是 kubernetes 的自愈系統。

那麼聽了 Mesos 的資源管理和 kubernetes 的自愈管理以後,我覺得它們做的已經相當成熟了,然而想要把它們投入到生産當中,可能還是會遇到一些坑的。比如想用 Mesos,那麼首先要考慮我們 Coding 通常所運作的服務是一些常駐服務,不是批處理任務,是以需要額外安裝 Marathon 這樣的長期運作任務管理架構(Framework)來做到這件事情。若是在生産環境中遇到性能問題,怎麼去調優也是一個棘手的事。而在 kubernetes 中,我們并不想讓自愈系統替我們把服務恢複在任何一台機器上,目前 Coding 還是有着一些對機器和運作位置有着苛刻要求的微服務。除此之外,這兩款架構也在迅速的開發疊代中,文檔也仍需補充,是以投入到生産中,我們認為為時過早。我們考慮到使用原生的 Docker API 就可以做到大部分我們想要用到的功能,于是我們就開始自己開發了一套容器編排平台,Coding-Job。

Coding-Job 的任務配置分成三層,Service、Job 和 Task,圖中的 Service 是核心(core)服務,說明它是被衆多服務所依賴的。每個 Job 定義了一件要做的事情,而每個 Job 要具體被配置設定去做的時候,會細分為 Task,每個 Task 可以使用不同的配置,運作在不同的機器上。

Coding-Job 是 C/S 架構的,在用戶端,有指令行版的操作。分别是 Push(推送配置檔案操作)和 UP/DOWN/UPDATE 這些對 Job 的啟動、停止和按照配置更新 Job。

此外 Coding-Job 的伺服器端會展現一個 WebUI 界面,我們可以通過 WebUI 來獲知每個容器的運作狀态,通過 INSPECT 操作來提供容器的具體資訊(與 docker inspect 一緻),LOG 功能供檢視容器 LOG,History 是顯示每個 JOB 的曆史容器的配置。此外,Coding-Job 服務端會定時請求各 Slave 系統資源使用及Docker 運作資料,這些資料也會在 WebUI 上顯示出來。

這是 Coding-Job 的使用架構圖,Host-1、Host-2 這些是 Slave 機器。開發者在拉取最新的代碼以後,用預先提供的打包指令生成一個 docker image 檔案,通過 docker push 把鏡像上傳到私有 Docker Registry 中。同時開發者會根據最新的代碼标簽更新任務配置檔案,通過 Coding-Job 用戶端 PUSH 到 Coding-Job 服務端所使用的 etcd 中。這個 etcd 起到了存儲任務配置資訊的作用,由于它是可監測變化的存儲(watchable storage),在收到新的任務配置資訊後,Coding-Job 服務端就可以各種方式通知到運維人員(Ops)。運維人員如果确認要更新線上代碼,調用 Coding-Job 用戶端的 UP 或者 UPDATE 指令,就可以讓 Slave 機器開始下載下傳新的代碼鏡像來運作,于是更新就完成了。

那麼好處是什麼呢?首先是,一分鐘釋出新元件(服務)不再是夢,在此基礎上,曆史容器的功能加上每次更新後任務配置送出到 Git 倉庫中,允許我們追溯每個元件的曆史線上版本。其次,我們可以利用它在公司内網環境内五分鐘啟動一個跟線上幾乎一緻的 Staging 環境,這個 Staging 可被用于新功能的内測。最後一個優點,是我們在使用 Coding-Job 的過程中,開發者不再對線上環境一頭霧水,而是全透明的,可以知道生産環境是怎麼組建的。在運維檢查整站狀态的時候,可以通過 WebUI 獲得一些編排意見,甚至這個隻讀的 WebUI 可以開放到公網上,讓所有人都可以看到 Coding 的服務提供狀态。

Happy Coding ; )

References

    • http://mesos.apache.org/documentation/latest/architecture/
    • https://github.com/kubernetes/kubernetes/blob/release-1.1/docs/design/architecture.md