Java微服務開發指南–使用Docker和Kubernetes構建可伸縮的微服務

weipeng2k發表於2017-10-17

使用Docker和Kubernetes構建可伸縮的微服務

    從現在開始,我們將從更高的維度討論微服務,涵蓋了組織敏捷性、設計和依賴的思考、領域驅動設計以及Promise理論。當我們深入使用之前介紹的三個流行的微服務框架:Spring Boot、Dropwizard和WildFly Swarm,我們能夠使用它們開箱即用的能力去構建一個暴露或者消費REST服務的應用,能夠使用外部環境對應用進行配置,可以打包成一個可執行的jar,同時提供Metrics資訊,但這些都是圍繞著一個微服務例項。當我們需要管理微服務之間的依賴、叢集的啟動和關閉、健康檢查以及負載均衡的時候,我們使用微服務架構會面臨什麼問題呢?本章,我們將討論這些高階話題用來理解部署微服務時面對的挑戰。

    當我們開始將我們的應用和服務按照微服務的思路進行拆分後,我們將面臨這樣的場景:我們有了更多的服務、更多的二進位制內容、更多的配置,更多的互動點等等。傳統方式是將這些構建成一個二進位制單元,比如:WARs或者EARs,然後將其打包後等待運維人員將它部署到我們指定的應用伺服器上。如果對於高可用有要求,也會將應用伺服器進行分散式部署,形成叢集,依靠負載均衡、共享磁碟(資料庫)等方式提升可用性。傳統運維體系下也開發了一些自動化部署的工具,比如:ChefAnsible,工具雖然簡化了部署,但是開發人員還是需要面對部署時容易出現的問題,比如:配置、環境等不可預知的問題。

chef
Chef是由Ruby與Erlang寫成的配置管理軟體,它以一種純Ruby的領域專用語言(DSL)儲存系統配置“烹飪法(recipes)”或“食譜(cookbooks)”。Chef由Opscode公司開發,並在Apache協議版本2.0下開源釋出。

ansible
使用python構建,中文化資料比較多,Ansible的簡潔介面和可用性非常迎合系統管理員的想法

    在傳統方式下嘗試微服務的部署,將會是個糟糕的結果。如何解決應用伺服器在開發、測試以及生產環境的不同配置?如果沒有,如何能夠捕獲到這些配置的變更?而這些變更如何確認已經執行在應用伺服器中了?執行時軟體環境,比如:作業系統、JVM以及相關的元件在生產和開發環境下的不同問題如何解決?如果我們的應用已經針對特定的JVM做了調優,這些調優引數會不會影響到他人?如果部署微服務,你會選擇使用程式隔離的方式將它們部署在一臺機器上嗎?如果其中一個微服務例項消耗了系統100%的資源,該如何是好?如果過度的佔用了I/O或者共享儲存怎麼辦?如果部署了多個微服務例項的宿主機崩潰了怎麼辦?我們的應用為此做過應對方案嗎?將應用分拆為微服務是小,但面對的問題顯然會更多。

不可變的遞交

    不可變的遞交(Immutable delivery)原則可以幫助我們應對上述的部分問題,在這個體系下,我們將使用映象技術來嘗試減少開發到生產的步驟。例如:構建系統能夠輸出一個包含了作業系統、JVM、配置、相關元件的映象,我們可以將它部署到一個環境中,測試它,如果通過測試,最終可以將它部署到生產環境中而不用擔心開發流程使交付的軟體缺少了什麼。如果你想變更應用,那麼可以回到剛才這個流程的最開始,應用你的修改,重新構建映象,最終完成部署,如果出乎你的意料,程式有問題,你可以直接選擇回滾到上一個正確的映象而不用擔心遺漏了什麼。

    這聽起來很好,但是我們怎麼做到呢?將應用打包成一個jar還不足以足夠做到這些。JVM是底層實現,我們如何將它也打包進去,而JVM又使用了作業系統級別元件,這些內容我們都要打包,除此之外,我們還需要配置、環境變數、許可權等等,這些都需要打包,而這些內容無法被打包到一個可執行jar中去。更加重要的是,不止java一種微服務,如果程式使用NodeJS、Golang編寫,我們還要針對不同的語言環境做不同的打包。你可能想使用自動化手段完成這些軟體的安裝,將基礎設施作為服務(IaaS),使用它們的API完成環境的搭建。事實上Netflix已經使用了自動化構建工具來完成VM的構建,並利用這項技術實現了不可變的遞交,但是VM很難管理、更新和變更,而每個VM都有自己完備的虛擬化環境,對資源有些浪費。

    那麼有什麼更加輕量化的打包和映象化方式讓我們使用嗎?

Docker,Docker,Docker

    Docker是近幾年出現用於解決不可變遞交的優雅解決方案,它允許我們將應用以及應用的所有依賴(包括了:OS,JVM以及其他元件)打包成為一個輕量的、分層的映象格式。然後Docker使用這些映象,執行它們,產生例項,而這些例項都執行在Linux containers中,在Linux containers中,會帶來CPU、記憶體、網路以及磁碟的隔離。在這種模式下,這些容器例項就是一種應用虛擬化的方式,它執行一個程式去執行,你甚至可以在例項中執行ps檢視你的程式,而且這個容器例項具備訪問CPU、記憶體、磁碟和網路的能力,但是它只能使用指定好的配額。例如:能夠啟動一個Docker容器,只為它分配一部分的CPU、記憶體以及I/O的訪問限制。如果在Linux containers外部去看,在主機上,這個容器就是一個程式,不需要裝置驅動的虛擬化、作業系統、網路棧以及特殊的中間層,它僅僅是一個程式。這意味著,我們可以在一臺機器上部署儘可能多的容器,提供了比虛擬機器更高的部署密度。

    在這些激動人心的特性下,其實沒有革命性的技術。Docker使用到的技術有:cgroupsnamespaces以及chroot,這些都已經在Linux核心中執行了相當長的時間,而這些技術被Docker用來構造應用虛擬化技術。Linux containers已經推出了十幾年,而程式虛擬化技術在SolarisFreeBSD上出現的時間更早。以往使用這些技術的lxc會比較複雜,而Docker通過簡單的API以及優秀的使用者體驗使得Linux containers的運用變得火熱起來,Docker通過一個客戶端命令工具能夠與Linux containers進行互動,去部署Docker映象,而Docker映象的出現改變了我們打包和交付軟體的方式。

    一旦你擁有了映象,可以迅速的轉化為Linux containers,映象是按照層進行構建的,一般會在一個基礎的層(例如:RHEL、 Debian等)上進行構建,然後包含應用所需的內容,構建應用其實也就是在基礎層上進行一層一層的映象構建。映象的出現,是的釋出到各種環境變得容易,不會在面對一堆零散的內容,如果發現基礎映象中有問題,可以進行重新構建,其他映象進行重新選擇構建即可,這使得從開發環境到測試,再到生產環境減少了人工干預釋出內容的環節,如果我們要新發布一版本,只需要重新構建一個映象即可,而改動只是去修改了映象中對應的層。

    構建了映象,但是我們怎樣啟動一個應用?怎樣停止它?怎樣做健康檢查?怎樣收集應用的日誌、Metrics等資訊,使用標準的API可以使我們自己構建工具來完成這些工作。出色的叢集機制,例如服務發現、負載均衡、失敗容錯以及配置使得開發人員很容易獲得這些特性。

Docker相關的技術可以關注 The Docker Book

Kubernetes

    外界都知曉Google使用Linux containers技術來支撐其擴充套件性,事實上Google的所有應用都執行在Linux containers上,並且被他們的管理系統Brog進行著管理。前Google工程師Joe Beda說,公司每週要啟動超過20億次的容器,Google甚至投入資源涉及到linux底層技術來支援其容器在生產環境的運用。在2006年,Google開始了一個名叫 程式容器 的專案,最終演變成為了cgroups,而它在2008被合併到了Linux核心,同年正式釋出。Google在擁有極強的運維容器的背景下,其對構建容器平臺的影響力就不言而喻了,事實上,一些流行的容器管理專案都受到了Google的影響。

  • Cloud Foundry
    它的創立者Derek CollisonVadim Spivak都在Google工作過,並且使用Borg系統很多年
  • Apache Mesos
    它的創立者Ben Hindman在Google實習過,與Google的諸多工程師有過容器技術的交流(圍繞容器叢集、排程和管理等技術)
  • Kubernetes
    開源的容器叢集管理平臺和社群,建立它的工程師,同時也在Google建立了Borg

    在Docker震驚技術屆的2013年,Google決定是時候開源他們下一代的技術–Borg,而它被命名為Kubernetes。今天,Kubernetes是一個巨大、開放和快速成長的社群,來自Google、Red Hat、CoreOS以及其他的個體在為它做出貢獻。Kubernetes為在可伸縮的Linux containers下執行微服務提供了非常多有價值的功能,Google將近20年的運維經驗都濃縮到了Kubernetes,這對我們使用微服務部署產生了巨大的影響。大部分高流量的網際網路企業在這個領域耕耘了很長時間(Netflix、Amazon等)嘗試構建的伸縮技術,在Kubernetes中都已經預設進行了整合,在正式深入例子之前,我們先介紹一些Kubernetes的概念,接下來在後面的章節將會用它來掛曆一個微服務叢集。

Pods

    一個Pod是一個或者多個Docker容器的組合,一般情況下一個Pod對應一個Docker容器,應用部署在其中。

    Kubernetes進行編排、排程以及管理Pod,當我們談到一個執行在Kubernetes中的應用時,指的是執行在Pod中的Docker容器。一個Pod有自己的IP地址,所有執行在這個Pod中的容器共享這個IP(這個不同於普通的Docker容器,普通的Docker容器每個例項都有一個IP),當一個卷掛載到Pod,這個卷也能夠被Pod中的容器共同訪問。

    關於Pod需要注意的一點是:它們是短暫的,這代表著它們會在任何時候消失(不是因為服務崩潰就是叢集cluster殺死了它),它們不像VM一樣引起你的額外注意。Pods能夠在任意時刻被銷燬,而這種意外的失敗就如同介紹微服務架構中任何事情都會失敗一樣(design for failure),我們強烈建議在編寫微服務時時刻記著這個建議。和之前介紹的其他原則相比,這個建議顯得更加重要。

Kubernetes的最小部署單元是Pod而不是容器。作為First class API公民,Pods能被建立,排程和管理。簡單地來說,像一個豌豆莢中的豌豆一樣,一個Pod中的應用容器同享同一個上下文(比如:PID名字空間、網路等)。在實際使用時,我們一般不直接建立Pods, 我們通過replication controller來負責Pods的建立,複製,監控和銷燬。一個Pod可以包括多個容器,他們直接往往相互協作完成一個應用功能。

標籤(Label)

    標籤(Label)是一個能分配給Pods的簡單鍵值對,比如:release=stable或者tier=backend,Pods(或者其他資源,但是我們當前只關注Pods)可以擁有多個標籤並且可以以鬆耦合的方式進行分組,這在Kubernetes的使用過程中非常常見。因此一點也不奇怪,Google使用這種簡單的方式用來區分不同的容器,並以此來構建大規模伸縮的叢集。當我們用標籤區分了Pods之後,我們可以使用 label selector 來按照分組來查詢所有的Pods,例如:如果我們有一些Pods打上了tier=backend的標籤,而其他的一些打上了tier=frontend標籤,只需要使用 label selector 表示式 tier != frontend就可以完成對所有沒有打上tier=frontend的Pods進行查詢,而 label selector 在接下來介紹的 replication controllersservices 所使用。

複製控制器(Replication Controllers)

    當我們討論微服務的可伸縮性時,可能想的是將給定的一組微服務部署到多個例項(機器)上,用多個例項的部署來增加伸縮性。Kubernetes為伸縮性定義了一個叫做 Replication Controllers 的概念,它能夠管理給定的一組微服務的多個複製體(replicas),例如:我們需要管理許多打上 tier=backend and release=stable 的需要Pods,可以建立一個複製控制器,該控制器擁有對應的 label selector ,此時它就能夠在叢集中以replicas的形式控制和管理這些Pods。如果我們設定replica的數量為10,當Kubernetes會確定當前的複製控制器是否達到了該狀態,如果此刻只有5個,那麼Kubernetes就會迴圈建立剩餘的5個,當有20個執行著,Kubernetes將會選擇停止10個。Kubernetes將會盡可能的保持設定的10個replica的狀態,你可以認為使用複製控制器來控制叢集的數量是非常容易的事情,在接下來的章節中,我們會看到使用複製控制器的例子。

服務(Services)

    我們最後需要理解的Kubernetes概念是服務(Service), Replication Controllers 能控制一個服務下的多個複製體(replicas),我們也觀察到Pods能夠被停止(要麼自己crash、或者被kill,也有可能被複制控制器停止),因此,當我們嘗試與一組Pods進行通訊時,不應該依賴於具體的IP(每個Pod都有自己的IP),我們需要的是能夠以組的形式訪問這些Pods的方式,以組的形式發現它們,可能的話能夠以負載均衡的方式訪問它們,這個就是 服務(Service) 需要做的。它(服務)允許我們通過一個 label selector 獲取一組Pods,將它們抽象為一個虛擬IP,然後以這個虛擬IP來讓我們對這些Pods進行發現和互動,我們將在接下來的章節中介紹具體的例子。

Service是定義一系列Pod以及訪問這些Pod的策略的一層抽象。Service通過Label找到Pod組。因為Service是抽象的,所以在圖表裡通常看不到它們的存在

    瞭解這些簡單的概念,Pods、Labels、Replication Controllers和services,我們能夠以可伸縮的模式,用Google的實踐,來管理微服務。這些實踐花費了多年,經歷了多次失敗總結出來的經驗之談,而這個模式能夠解決複雜的問題,因此強烈建議學習這些概念以及實踐,使用Kubernetes來管理你的微服務。

開始使用Kubernetes

    Docker和Kubernetes都是基於Linux本地技術的產品,因此它們需要執行在一個基於Linux的環境中,我們假設大部分的Java開發人員都是工作在Windows或者Mac下,我們推薦在Linux環境下進行相關的實踐。

    接下來的內容,作者作為redhat的員工,開始介紹CDK(RedHat Container Development Kit),然後是CDK的安裝,譯者覺得CDK沒有多大的參考性,因此將其替換成了對Kubernetes官方的MiniKube使用,並基於MiniKube在linux機器上搭建Kubernetes。

Kubernetes之MiniKube的安裝

筆者準備了aliyun oss 下載,比googleapis快許多

該文件介紹如何執行起一個本地Kubernetes叢集,需要一個支援Hyper-V虛擬化的CPU以及至少8GB CPU

筆者的環境是 ubuntu 16.04 / amd k8 4 core CPU / 16 gb mem

需要提前安裝VirtualBox5.1,自行到官網上進行安裝,不要圖簡單使用ubuntu預設的,那個平常自己使沒問題,但是MiniKube不行

安裝MiniKube

wget http://029145.oss-cn-hangzhou.aliyuncs.com/minikube-linux-amd64
mv minikube-linux-amd64 minikube
chmod u+x minikube
sudo mv minikube /usr/local/bin/

安裝Kubectl

wget http://029145.oss-cn-hangzhou.aliyuncs.com/kubectl
chmod u+x kubectl
sudo mv kubectl /usr/local/bin/

啟動MiniKube

    通過以下命令啟動minikube,該過程會下載一個ISO映象,然後完成啟動。

minikube start

下載依賴的映象

    這個過程最為複雜,當啟動minikube時,會自動下載一些映象,但是這些映象都被牆了,但是我們可以從aliyun的倉庫下載對應的映象,然後將其重新命名。在啟動完minikube後,使用minikube ssh可以登入到後臺,然後執行下面的命令完成映象的下載和別名設定。

docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0
docker tag registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0 gcr.io/google_containers/pause-amd64:3.0

docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/kube-addon-manager-amd64:v6.1
docker tag registry.cn-hangzhou.aliyuncs.com/google-containers/kube-addon-manager-amd64:v6.1 gcr.io/google-containers/kube-addon-manager:v6.1

docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/kubedns-amd64:1.9
docker tag registry.cn-hangzhou.aliyuncs.com/google-containers/kubedns-amd64:1.9 gcr.io/google_containers/kubedns-amd64:1.9

docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/kube-dnsmasq-amd64:1.4
docker tag registry.cn-hangzhou.aliyuncs.com/google-containers/kube-dnsmasq-amd64:1.4 gcr.io/google_containers/kube-dnsmasq-amd64:1.4

docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/exechealthz-amd64:1.2
docker tag registry.cn-hangzhou.aliyuncs.com/google-containers/exechealthz-amd64:1.2 gcr.io/google_containers/exechealthz-amd64:1.2

docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/kubernetes-dashboard-amd64:v1.5.0
docker tag registry.cn-hangzhou.aliyuncs.com/google-containers/kubernetes-dashboard-amd64:v1.5.0 gcr.io/google_containers/kubernetes-dashboard-amd64:v1.5.1

測試echoserver

    執行命令建立一個echoserver服務,執行如下命令:

kubectl run hello-minikube --image=registry.cn-hangzhou.aliyuncs.com/google-containers/echoserver:1.4 --port=8080
kubectl expose deployment hello-minikube

    然後執行minikube service hello-minikube --url,將會返回hello-minikube的url,然後可以基於該url做一下測試。

$ minikube service hello-minikube --url
http://192.168.99.100:31907
$ curl http://192.168.99.100:31907/123
CLIENT VALUES:
client_address=172.17.0.1
command=GET
real path=/123
query=nil
request_version=1.1
request_uri=http://192.168.99.100:8080/123

SERVER VALUES:
server_version=nginx: 1.10.0 - lua: 10001

HEADERS RECEIVED:
accept=*/*
host=192.168.99.100:31907
user-agent=curl/7.47.0
BODY:
-no body in request-

    可以看到請求對應的url,有資料返回,當然也可以啟動dashboard,比如執行minikube dashboard將會開啟管理頁面。

小結

    在本章我們學習了微服務在部署和管理上的問題,以及如何使用Linux容器來解決這些問題,使用不可變的遞交來減少部署過程中遇到的問題,使重複部署成為可能。我們能夠使用Linux容器做到服務之間的隔離、快速的部署以及遷移,使用Kubernetes來進行容器的管理,並享受由Kubernetes帶來的服務發現、故障轉移、健康檢查等內建功能,Kubernetes已經解決了許多部署相關的問題,如果想深入瞭解,可以參考以下連結:


相關文章