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
- Apache Mesos
- kubernetes/architecture.md at release-1.1 · kubernetes/kubernetes · GitHub
推薦閱讀:
※Kubernetes dashboard更新升級和用戶許可權認證
※Kubernetes集群的全天候一站式訪問工具介紹
※編排工具充分發揮了 Linux 容器技術優勢
※kubernetes RBAC實戰
※kubernetes的網路實現
TAG:Coding | Docker | Kubernetes |