天天看點

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

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

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

<a href="https://www.chef.io/chef/">chef</a> Chef是由Ruby與Erlang寫成的配置管理軟體,它以一種純Ruby的領域專用語言(DSL)儲存系統配置“烹饪法(recipes)”或“食譜(cookbooks)”。Chef由Opscode公司開發,并在Apache協定版本2.0下開源釋出。 <a href="https://www.ansible.com">ansible</a> 使用python建構,中文化資料比較多,Ansible的簡潔界面和可用性非常迎合系統管理者的想法

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

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

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

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

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

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

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

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

Cloud Foundry

它的創立者<code>Derek Collison</code>和<code>Vadim Spivak</code>都在Google工作過,并且使用Borg系統很多年

Apache Mesos

它的創立者<code>Ben Hindman</code>在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的概念,接下來在後面的章節将會用它來挂曆一個微服務叢集。

    一個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)是一個能配置設定給Pods的簡單鍵值對,比如:<code>release=stable</code>或者<code>tier=backend</code>,Pods(或者其他資源,但是我們目前隻關注Pods)可以擁有多個标簽并且可以以松耦合的方式進行分組,這在Kubernetes的使用過程中非常常見。是以一點也不奇怪,Google使用這種簡單的方式用來區分不同的容器,并以此來建構大規模伸縮的叢集。當我們用标簽區分了Pods之後,我們可以使用 label selector 來按照分組來查詢所有的Pods,例如:如果我們有一些Pods打上了<code>tier=backend</code>的标簽,而其他的一些打上了<code>tier=frontend</code>标簽,隻需要使用 label selector 表達式 <code>tier != frontend</code>就可以完成對所有沒有打上<code>tier=frontend</code>的Pods進行查詢,而 label selector 在接下來介紹的 replication controllers 和 services 所使用。

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

    我們最後需要了解的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來管理你的微服務。

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

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

筆者準備了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,該過程會下載下傳一個ISO鏡像,然後完成啟動。

    這個過程最為複雜,當啟動minikube時,會自動下載下傳一些鏡像,但是這些鏡像都被牆了,但是我們可以從aliyun的倉庫下載下傳對應的鏡像,然後将其重命名。在啟動完minikube後,使用<code>minikube ssh</code>可以登入到背景,然後運作下面的指令完成鏡像的下載下傳和别名設定。

    運作指令建立一個echoserver服務,運作如下指令:

    然後運作<code>minikube service hello-minikube --url</code>,将會傳回<code>hello-minikube</code>的url,然後可以基于該url做一下測試。

    可以看到請求對應的url,有資料傳回,當然也可以啟動dashboard,比如運作<code>minikube dashboard</code>将會打開管理頁面。

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

<a href="https://kubernetes.io/docs/concepts/services-networking/service/">Kubernetes Reference Documentation</a>

<a href="https://www.oreilly.com/ideas/an-introduction-to-immutable-infrastructure">“An Introduction to Immutable Infrastructure” by Josha Stella</a>

<a href="https://kubernetes.io">Kubernetes</a>

<a href="https://kubernetes.io/docs/concepts/workloads/pods/pod/">Kubernetes Reference Documentation: Pods</a>

<a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/">Kubernetes Reference Documentation: Labels and Selectors</a>

<a href="https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/">Kubernetes Reference Documentation: Replication Controller</a>

<a href="https://kubernetes.io/docs/concepts/services-networking/service/">Kubernetes Reference Documentation: Services</a>