本文是《使用Spring Boot和Docker構建微服務架構》系列四部曲的前兩篇,對微服務架構以及容器化概念作一個概述,利用工具進行設置,深入探討如何使用Docker工作,然後搭建我們的第一個容器。
隨著越來越多的產品利用可重用的基於Rest的服務構建,人們很快發現把業務功能拆分成為可重用的服務是非常有效的,但同時也帶來了一個風險。每次更新服務,您必須重新測試與它一起部署的每一個服務,即使您有信心認為代碼更改不會影響其他服務,您永遠無法真正確保這一點,因為這些服務會不可避免地共享代碼、數據和其他組件。
隨著容器化的興起,你可以在一個完全隔離的環境中非常高效地運行代碼,把它們兩個組合在一起將會允許在細粒度可擴展性和版本控制方面的產品架構優化,不過付出的代價是增加了複雜性和一些重複的代碼。
答案是不完全是。容器和虛擬機具有一些相似性,它們都是通過一個控制進程管理的隔離環境(分別是容器管理器和虛擬機監控程序),但兩者之間的主要區別是,對於每一個虛擬機,運行的是一個完整的組件堆棧——從作業系統到應用伺服器,以及仿真的虛擬硬體包括網絡組件、CPU和內存。
對於容器來講,它們運行在更為完全隔離的沙盒中,出現在每個容器裡的僅僅是作業系統的最小內核,共享了底層系統的資源。容器化的最大優勢在於對於相同的硬體佔用空間更小,可以比虛擬機運行更多的實例。容器也有一些關鍵的限制:最大的一個是容器只能運行在基於Linux的作業系統上面(內核隔離是Linux的特定技術)。
與這一限制相關的就是Docker——目前最流行的容器服務提供系統——不能直接運行在Mac或者Windows系統上,因為它們不是Linux,替代方案就是為了運行Docker,你需要使用VirtualBox啟動一個Linux虛擬機,接著在虛擬機裡運行Docker。幸運的是,它絕大多數是由Docker ToolBox來管理的(原名Boot2Docker)。
Docker已經獲得了眾多的支持,以至於容器鏡像的公共Repository——Docker Hub,擁有超過了136,000個公共鏡像。其中許多是由個人創建的,一些擴展自「官方」鏡像然後按照他們自己的需求做了定製,但是其它的都是從「基礎」鏡像做了完整的平臺配置定製化。我們將利用這些「基礎」和「官方」鏡像開始我們的研究之旅。
「 所以我們已經談論了微服務和容器化,但是Spring Boot在哪個部分起作用呢?我選擇使用Java來構建我自己的微服務,具體地說就是Spring Boot框架。選擇它主要是因為我熟悉Spring、易於開發的Rest服務Controller、業務服務以及數據存儲,還可以很容易地引入Scala的Akka/Play編程模型。微服務架構的其中一個最為人稱道的優勢就是服務的完全獨立,所以不需要也不應該關心每一個服務採用什麼語言或平臺構建。
就我個人而言,我認為多語言的維護成本要比獲得的彈性收益更大,但也有適用的用例,比如在一個大組織內的一個部門已經選擇了不同的技術棧為「標準」的情況下。另外一個可能的場景就是如果你決定從一個語言/平臺轉換到另外一個——你可以每次遷移一個微服務,提供相同的終端Web服務接口。我們努力的目標如下:
一個從開始設置微服務和Docker到如何結束的指南。
了解圍繞微服務架構的諸多組件的作出的不同決定的利弊——從原始碼控制到服務版本化,以及其中的一切。
分析「純粹」的微服務信仰,並且看它們如何應用到一個「現實世界」的場景。
看Docker是如何面對喧囂,以及為專業開發運行Docker什麼是必需的。
利用一系列的微服務構建一個完整的解決方案,每一個微服務都有它自己的容器,一個在自身容器託管的持久化層,還有容器集群。
其它有價值的內容。
我將模擬的業務場景是一家軟體開發公司的員工任務分配和識別系統,包含了以下任務:
我是在Mac上工作的,但是工具在Mac和PC上是相同的,所以在平臺上的操作是99%相同的,我將不會回顧如何安裝這些工具,而是直接從如何使用它們開始,你所需要的準備工作如下:
Docker Toolbox:包含了VirtualBox(作用為創建虛擬機用來運行容器)、Docker Machine(在虛擬機內運行Docker容器)、Docker Kitematic(一個管理在Docker Machine裡運行的容器的GUI)以及Docker Compose(多容器編排工具)
Git:我的GitHub連結在這兒(https://github.com/ThreePillarGlobal/microservice-blog),我在Windows上使用Git Extensions,在Mac上使用Source Tree,不過使用其他Git客戶端包括命令行都是可以的。
Java 8 SDK:Java 8在JVM永久代(PermGen)方面有了改進,另外對於Streams API和Lambda的支持也是非常贊的。
構建工具的選擇:讓我們使用Gradle吧,我推薦使用SDKMan,正式名稱為GVM來管理Gradle版本,如果你使用Windows工作,你可以使用Cygwin安裝SDKMan或者SDKMan的Powershell命令行工具,或者Gravy也是可替代的選擇。
IDE的選擇:我們使用Spring Tool Suite(STS)來工作,在本文寫作時最新的for Mac版本為3.7.0。
Rest工具:對於任何Web服務項目使用都非常方便,我是Chrome擴展工具Postman的粉絲,如果你擅長curl命令行,這個工具也是不錯的。
uMongo或者Mongo GUI:文檔型資料庫完美匹配自足性的模型——對象進行自動檢索,並且參考對象可以在微服務架構中通過ID來引用,這些ID可以很好地映射到文檔存儲中,另外地,MongoDB擁有運行很棒的「官方」Docker鏡像。
我們對於原始碼管理的第一個意見——似乎也是絕大多數的在線觀點,就是每個微服務都應該有自己的版本庫。微服務的一個基本理念就是跨服務之間不能共享代碼。就我個人而言,這對我架構師的小心臟是個小小的打擊,因為任何實用工具的重複代碼的數量可能會比較多,以及缺乏一個單一、統一的領域模型給我有點心痛的感覺。我理解這個原則——自足性意味著自力更生。為了這篇博文,我把所有的代碼都放到了一個單獨的代碼庫,然而,每個微服務在根目錄下都有它一個自己的目錄。這樣做是為了讓我可以隨著時間的推移展示分支的進展。在一個真實的解決方案中,你應該讓每一個微服務都有它自己的不同的版本庫,也許會有統一的版本庫引用其它的子模塊。
因為我們要處理隔離的、可重用的組件,我們將做以下的映射:
一個邏輯業務對象→一個微服務→一個Git版本庫目錄→一個Mongo集合
因為業務對象可能由多個對象組成,任何我們認為可以作為其自身業務對象的子對象都可以劃分為其自身的組件棧。
為了理解如何構建一個完整的基於Docker容器的產品解決方案,我們將需要深入研究容器是如何在宿主機(或者是虛擬機,正如我們的例子)裡運行的,使用Docker通常是有三個階段:鏡像構建、鏡像分發和容器部署。
構建鏡像——Dockerfile的世界
為了構建鏡像,你要寫一系列的指令用來獲取已有的鏡像,接著對該鏡像做些改變和配置。官方的Docker Hub Repository包含了許多的「官方」鏡像以及數以千計的用戶定製的鏡像。如果其中的鏡像不太符合你的需求,你可以創建一個定製的Dockerfile,用來在鏡像上逐步添加一些內容,,比如安裝系統軟體包、複製文件或者公開一些網絡埠,當我們構建微服務時,我們將會創建一個定製的Dockerfile,不過現在,我們將會利用一個標準鏡像來啟動一個MongoDB實例。
容器聯網
當容器啟動時,有一個它私有的網絡,容器宿主機埠將外部網絡通信轉發到單個的容器埠上,特定的容器埠可以通過Dockerfile來決定公開,並且埠轉發可以通過以下兩種方式之一來進行:你可以顯式地從宿主機映射埠到容器上,或者如果沒有顯式映射的話,Docker將映射已聲明的容器埠到宿主機一個可用的臨時埠上(通常的範圍是從32768到61000)。當我們可以對整個解決方案顯式地管理埠映射時,通常讓Docker處理這一切是一個更加好的主意,並且可以通過Link機制公開埠信息到容器上,這方面將在我們構建我們的第一個微服務容器時有所涉及。
啟動MongoDB容器
無論你是使用Kitematic還是Docker命令行,都可以非常簡單的啟動一個標準的容器。使用命令行的話,如果一切都安裝正確,命令提示符將會包含以下三個關鍵的環境變量:
DOCKER_HOST=tcp://192.168.99.100:2376DOCKER_MACHINE_NAME=defaultDOCKER_TLS_VERIFY=1DOCKER_CERT_PATH=/Users/[username]/.docker/machine/machines/default
這些是按照你的情況設置的(如果是在安裝過程中打開的話,你可能需要重啟終端或者命令提示符)。這些都是必要的,因為Docker不能直接運行在我的Mac筆記本上,而是跑在筆記本運行的虛擬機上。Docker客戶端非常有效地將Docker命令從我的筆記本「代理」到虛擬機上,在我們啟動容器前,讓我們重溫一下一部分Docker命令是非常有益的,在使用任何圖形用戶界面之前,了解命令行的東西總是很好的。
Docker級別的命令:
docker ps
該命令將會列出所有運行的容器,顯示的信息包括它們的ID、名字、基礎鏡像名字和埠映射信息等。
docker build
該命令用來定義一個鏡像——通過處理Dockerfile來創建一個新的鏡像,我們將用這個命令來構建我們的微服務鏡像。
docker pull[鏡像名字]
該命令從遠程Repository拉取鏡像並且存儲在本地。
docker run
該命令將基於一個本地或者遠程Repository(比如Docker Hub)啟動一個容器,我們將會相當多地探究這個命令。
docker push
該命令推送一個構建好的鏡像到一個Repository,通常是Docker Hub。
容器特定的命令
這些命令使用容器ID或者名字作為一個參數:
docker status [容器名字/ID][容器名字/ID]
這個命令將顯示指定的每一個容器的當前負載,比如CPU佔用率、內存使用率以及網絡流量等。
docker logs [-f][容器名字/ID]
該命令顯示容器的最新的日誌,-f選項就好比Shell終端中的「tail -f」中的-f選項。
docker inspect [容器名字/ID]
該命令將容器的所有配置信息以JSON的格式轉儲出來顯示。
docker port [容器名字/ID]
該命令顯示容器與宿主機之間的所有埠映射信息。
docker exec [-i][-t][容器名字/ID]
該命令將在目標容器上執行一條命令(-i表明以交互方式運行,-t表明以偽TTY終端運行),這個命令常用來獲得一個容器終端Shell:
docker exec -it [容器名字/ID]sh
一旦我們理解了這些參考材料,我們可以進入到下一步啟動一個Mongo容器。
命令非常簡單:docker run -P -d —name mongo mongo
解釋如下:
P選項告知Docker在臨時埠範圍裡公開容器聲明的任何埠
d選項告知以Daemon方式運行容器(比如在後臺)
name選項給容器分配一個名字(名字必須在所有運行的容器實例中唯一,如果你不提供這個選項,將會獲得一個隨機的半友好的名字比如modest_poitras)
最後的mongo表明了使用哪一個鏡像
Docker Hub鏡像的定義採用了[所有者]/[鏡像名字][:標籤]的命名格式,如果沒有指定所有者,那麼使用的就是「官方」的Docker Hub鏡像——這是預留給Docker官方給軟體供應商的禮物也就是成為官方鏡像,如果最後的標籤部分省略的話,那麼就會認為你需要獲得的是最新版本的鏡像。
現在我們來嘗試確認我們的Mongo實例已經啟動並且運行了:
docker exec -it mongo sh
mongoMongoDB shell version: 3.0.6connecting to: testServer has startup warnings:2015-09-02T00:57:30.761+0000 I CONTROL [initandlisten]2015-09-02T00:57:30.761+0000 I CONTROL [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/enabled is 'always'.2015-09-02T00:57:30.761+0000 I CONTROL [initandlisten] ** We suggest setting it to 'never'2015-09-02T00:57:30.761+0000 I CONTROL [initandlisten]2015-09-02T00:57:30.761+0000 I CONTROL [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/defrag is 'always'.2015-09-02T00:57:30.761+0000 I CONTROL [initandlisten] ** We suggest setting it to 'never'2015-09-02T00:57:30.761+0000 I CONTROL [initandlisten]> use microserviceblogswitched to db microserviceblog> db.createCollection('testCollection'){ "ok" : 1 }
在容器內部,Mongo看起來正在運行,但是我們可以從外部獲知嗎?為了嘗試這個,我們需要查看什麼臨時埠被映射到了Mongo的埠上,我們運行如下命令:
docker ps
從下面我們可以看到(為了可讀性省略了一些列):
CONTAINER ID IMAGE PORTS NAMES87192b65de95 mongo 0.0.0.0:32777->27017/tcp mongodb
我們可以看到宿主機埠32777映射到了容器埠27017上,然而,記住我們的容器是運行在虛擬機上的,所以我們必須回到我們的環境變量:
$ echo $DOCKER_HOSTtcp://192.168.99.100:2376
我們應該可以通過如下的地址訪問我們的Mongo容器的27017埠:
192.168.99.100:32777,啟動Mongo然後點擊那個位置顯示資料庫可以外部訪問:
本文為翻譯文章,閱讀原文,請點擊左下角閱讀原文連結。