這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
第五部分: Go微服務 - 在Docker Swarm中部署
這一部分,我們讓accountservice服務在本地部署的Docker Swarm叢集上運行,並探討容器編排的核心概念。
本文處理下面的內容:
- Docker Swarm和容器編排。
- 使用Docker將accountservice服務容器化。
- 配置本地Docker Swarm叢集。
- 以Swarm服務的方式部署accountservice服務。
- 運行基準測試並搜集度量值。
其實這一部分和Go語言沒有什麼直接關係,具體來說它是關於Docker以及Docker Swarm的。同樣希望你能喜歡這篇文章。
什麼是Docker編排(CONTAINER ORCHESTRATOR)
在開始實操之前,快速介紹一下容器編排的概念可能會有用。
隨著應用程式變得越來越複雜,並且需要處理更高的負載,我們不得不處理這樣的一個事實,我們成千上萬的服務執行個體遍布大量物理硬體上。容器編排讓我們把所有硬體當作單個邏輯實體看待。
容器編排這篇文章總結如下:
容器編排: 通過抽象主機基礎設施,編排工具允許使用者將整個叢集當作單個部署目標看待。
abstracting the host infrastructure, orchestration tools allow users to treat the entire cluster as a single deployment target.
我自己不能很好的總結它 - 使用容器編排器,例如Kubernetes或Docker Swarm,可以允許我們在一個或多個可用的基礎設施上將我們的軟體組件部署為服務。 在Docker的情況中 - Swarm模式是關於管理叫做swarm的Docker引擎叢集的。 Kubernetes使用了一種稍微不同的術語和關鍵抽象階層, 但是總體上來說概念大致相同。
容器編排器不僅為我們處理服務的生命週期,還為服務發現、負載平衡、內部定址和日誌提供了機制。
Docker Swarm中的核心概念
在Docker Swarm中有三個概念需要介紹:
- 節點(Node): 節點是Swarm中參與的Docker引擎執行個體。從技術上講,可以將其視為擁有自己CPU資源、記憶體、網路介面的主機。 節點可以是管理節點,也可以是worker節點。
- 服務(Service): 服務是執行在worker節點的東西,由容器映像和指示容器執行的命令定義的。服務可以是複製的,也可以是全域的。服務可以視為一種抽象,用於讓任意數量的容器形成一個可以通過它的名字, 而無需知道環境內部的網路拓撲情況的情況下在叢集內或叢集外訪問的邏輯服務。
- 任務(Task e.g. container): 對所有實用手段而言,可以認為任務就是Docker容器。 Docker文檔定義任務就是承載Docker容器和在容器內啟動並執行命令的一些東西。 管理器節點將任務賦予給(worker)節點,這些任務是在特定服務中指定的容器鏡像。
展示了微服務景觀的一種可能(簡化版)部署,其中兩個節點運行了五個容器執行個體,抽象成兩個服務accountservice和quotes-service。
原始碼
這一部分沒有對Go代碼進行任何修改,只是添加了一些新的檔案,用於將服務運行在Docker上。代碼地址: https://github.com/callistaen...。
將accountservice容器化
Docker安裝
Docker安裝,參考官網安裝指導。
建立Dockerfile
Dockerfile是Docker用於構建docker容器映像的, 包含所有你希望包含的東西。讓我們開始在/accountservice目錄下面建立一個Dockerfile吧。
FROM iron/baseEXPOSE 6767ADD accountservice-linux-amd64 /ENTRYPOINT ["./accountservice-linux-amd64"]
快速解釋:
- FROM: 定義我們將要開始構建我們自己映像來源的基本映像。 iron/base是非常適合運行Go應用程式的映像。
- EXPOSE: 定義我們希望在Docker網路內部暴露的可以到達的連接埠號碼。
- ADD: 添加一個檔案accountservice-linux-amd64到容器檔案系統的根目錄/。
- ENTRYPOINT: 定義當Docker啟動這個映像容器時要啟動並執行可執行檔。
為另外一種CPU架構/OS進行構建
如你所見,我們在檔案名稱後面添加了linux-amd64。當然我們可以隨意使用名字來指定Go語言可執行檔,但是我喜歡這樣的約定,將OS和目標CPU平台放到可執行檔名中。我寫這個部落格的時候,使用的是mac OS X。因此如果我只是直接在accountservice目錄下面運行go build來構建的話,會在同一個目錄下面產生一個accountservice可執行檔。但是這樣的檔案在Docker容器中,底層OS是基於Linux的。因此,我們在構建之前,需要設定一些環境變數, 這樣編譯器和連結器知道我們在為另外的OS、CPU架構進行構建, 我們這裡的例子是linux。
export GOOS=linuxgo build -o accountservice-linux-amd64export GOOS=darwin
上面使用-o標誌產生一個可執行二進位檔案。 我通常寫一個小指令碼來幫我執行一些可能需要重複執行的事情。
既然OS X和linux-based的容器都是運行在AMD64 CPU架構,因為我們無需設定(並重設)GOARCH環境變數。 但是如果你為32位OS構建或ARM處理器構建,就需要在構建之前恰當的設定GOARCH。
建立一個Docker映像
那麼現在可以構建我們第一個Docker映像來包含我們的可執行檔。進入accountservice父級目錄, 應該是$GOPATH/src/github.com/callistaenterprise/goblog。
當構建Docker容器映像時,我們通常使用[prefix]/[name]的命名規範來對其名字打標籤。我一般使用我們的github使用者名稱做首碼, 例如eriklupander/myservicename。對於這個部落格系列,我們使用someprefix首碼。 在項目根目錄(eg: ./goblog)下面執行下面的命令來基於上面的Dockerfile來構建一個Docker映像。
- 在goblog/accountservice目錄構建accountservice-linux-amd64可執行檔。
- 構建Docker映像。
// 在goblog目錄執行構建Docker鏡像docker build -t someprefix/accountservice accountservice/Sending build context to Docker daemon 14.34kBStep 1/4 : FROM iron/baselatest: Pulling from iron/baseff3a5c916c92: Pull complete43f18fea29ad: Pull completeDigest: sha256:1489e9c1536af14937ac7f975b8529cbe335672b7b87dae36c6b385d3e4020c0Status: Downloaded newer image for iron/base:latest ---> b438fe7f76e9Step 2/4 : EXPOSE 6767 ---> Running in 4246258b66c1Removing intermediate container 4246258b66c1 ---> 5113056caf24Step 3/4 : ADD accountservice-linux-amd64 /ADD failed: stat /var/lib/docker/tmp/docker-builder076553391/accountservice-linux-amd64: no such file or directoryAppledeMacBook-Pro-2:goblog apple$ docker build -t someprefix/accountservice accountservice/Sending build context to Docker daemon 7.457MBStep 1/4 : FROM iron/base ---> b438fe7f76e9Step 2/4 : EXPOSE 6767 ---> Using cache ---> 5113056caf24Step 3/4 : ADD accountservice-linux-amd64 / ---> 7a21b55920e3Step 4/4 : ENTRYPOINT ["./accountservice-linux-amd64"] ---> Running in 5b7115e2f89dRemoving intermediate container 5b7115e2f89d ---> 3e23a4268533Successfully built 3e23a4268533Successfully tagged someprefix/accountservice:latest
很好,我們現在本地docker鏡像倉庫包含了名為someprefix/accountservice的映像。 如果我們想要運行多個節點或者想要共用我們的鏡像, 我們可以使用docker push來將鏡像對其他我們當前Docker引擎提供的host之外的host拉取後可用。
然後我們可以直接通過命令列運行這個映像。
docker run --rm someprefix/accountserviceStarting accountserviceSeeded 100 fake accounts...2018/05/16 02:57:37 Starting HTTP service at 6767
然而請注意,容器不再是運行在你主機OS的localhost了。它現在位於它自己的網路上下文,並且我們不能直接從我們的實際主機作業系統訪問。 當然有辦法修複,但是我們先不深入下去,我們先對Docker Swarm進行局部設定,並部署accountservice。
我們先使用Ctrl + C終止這個啟動並執行鏡像。
配置單節點Docker Swarm叢集
本部落格的一個目標就是我們想讓我們的微服務運行在容器編排上。 對於我們很多人來說,一般意味著Kubernetes或Docker Swarm。 當然也有其他編排器, 例如Apache Mesos和Apcera, 但是本文明確聚焦的是Docker 1.13的Docker Swarm上的。
當在你的開發電腦上配置單節點Docker Swarm叢集的時所需做的任務可能依賴於Docker自身怎麼安裝有關。 建議遵照Swarm Tutorial, 或者你可以使用我的方式, 使用Docker Toolbox, Oracle Virtualbox和docker-machine, 是基於我同事Magnus lab-repo的關於服務發現的文章。
安裝VirtualBox
VirtualBox是一款開源的虛擬機器軟體。如果使用Windows或OS X系統安裝Docker Swarm, 需要安裝這個軟體。
下載地址: http://download.virtualbox.or...
安裝Boot2Docker
boot2docker是一個專為Docker而設計的輕量級Linux髮型包,解決Windows或者OS X使用者不能安裝Docker的問題。 Boot2Docker完全運行於記憶體中,24M大小,啟動僅5-6秒。
下載地址: https://github.com/boot2docke...。
建立Swarm管理器
Docker Swarm叢集至少包含一個Swarm管理器和零到多個Swarm worker。 這裡為了簡單起見,我們只使用一個Swarm管理器 - 這裡最少一個。在這節之後,重要的是你需要讓Swarm Manager啟動並運行起來。
我們這裡的例子,使用docker-machine並製作一個虛擬linux機器運行在我們的Swarm管理器的VirtualBox。 這裡我們使用"swarm-manager-1"來標示這個管理器。 你也可以參看官方文檔看如何建立Swarm。
我們使用下面的命令來初始化docker-machine主機,並標示其為swarm-manager-1作為swarm-manager-1節點相同的swarm節點的ip地址。
docker $(docker-machine config swarm-manager-1) swarm init --advertise-addr $(docker-machine ip swarm-manager-1)
如果我們需要建立多節點的Swarm叢集,我們將確儲存儲上面命令產生的串連token, 稍後如果我們需要添加額外節點到swarm中。
建立一個覆蓋網路(CREATE AN OVERLAY NETWORK)
Docker的覆蓋網路是一種當我們給Swarm添加類似"accountservice"到上面的時候使用的一種機制,這樣它能訪問在同一個Swarm叢集中的其他容器,而無需知道實際的叢集拓撲結構。
使用下面的命令建立一個網路:
docker network create --driver overlay my_network
這裡我們給網路起名為my_network。
部署AccountService服務
幾乎都已就緒,現在讓我們開始部署我們自己的"accountservice"來作為一個Swarm服務吧。docker service create命令接收很多參數,但的確也不瘋狂。 下面是我們發布"accountservice"的命令:
docker service create --name=accountservice --replicas=1 --network=my_network -p=6767:6767 someprefix/accountservice
下面是參數的快速解釋:
- name: 為我們的服務賦予一個邏輯名稱。 這個名字也是叢集中其他服務用於定址該服務所使用的名字。 因此另外一個服務想要調用accountservice, 那個服務只需要做一個GET請求: http://accountservice:6767/accounts/10001即可。
- replicas: 我們服務想要的執行個體數目。如果我們Docker Swarm叢集是多節點的, swarm引擎將自動在這些節點之間進行分布。
- network: 這裡我們告訴服務綁定我們剛才建立的覆蓋網路名稱。
- p: 映射, [內部連接埠號碼]:[外部連接埠號碼]。 這裡我們使用6767:6767, 但是如果我們使用6767:80,那麼我們在外部調用的時候就要通過連接埠號碼80進行服務訪問。注意這是一種可以讓我們服務從叢集外部可以到達的一種機制。通常來說,你無需對外暴露服務。而通常,我們會使用邊界伺服器(例如: 反向 Proxy),這樣可以有一些路由規則,並且可以有安全配置,這樣外部消費者不能到達你的服務,除非你使用一種自願的方式。
- someprefix/accountservice: 這個參數指定了我們希望容器啟動並執行映像的名字。在我們這個例子中是我們在建立容器的時候指定的標籤。 注意!如果我們要運行多節點叢集,我們需要將我們的映像推送到Docker倉庫中,例如推送到公用(免費)的Docker Hub服務上。 當然我們也可以設定私人docker倉庫,或者付費來保持鏡像免費。
就是這樣了。運行下面的命令來看我們的服務是否成功啟動了。
> docker service lsID NAME REPLICAS IMAGE ntg3zsgb3f7a accountservice 1/1 someprefix/accountservice
很甜美。 我們現在可以使用curl或通過瀏覽器來查詢我們的API。唯一需要知道的就是前面是Swarm的公網IP。即便我們在很多節點的swarm中為我們的服務只運行了一個執行個體, 覆蓋網路和Docker Swarm允許我們請求任意的swarm主機基於連接埠號碼來訪問服務。也就是說,兩個服務在外部不能使用同一連接埠號碼暴露。 他們內部連接埠號碼可以相同, 但是對外來說必須唯一。
無論如何,請記住我們之前儲存的環境變數ManagerIP的值。
> echo $ManagerIP192.168.99.100
如果已經改變了終端會話,可以重新匯出它。
export ManageIP=`docker-machine ip swarm-manager-0`
下面curl請求API:
> curl $ManagerIP:6767/accounts/10000{"id":"10000","name":"Person_0"}
非常不錯,成功了。
部署一個視覺化檢視
使用docker的命令列API來檢查Swarm的狀態是完全可行的(docker service ls), 但是使用更加圖形化的呈現,看起來更有意思。 例如可視化的有Docker Swarm Visualizer, 我們同樣可以像部署剛才accountservice服務一樣的方式部署這個可視化服務工具。 這樣我們可以有另外一種方式來查看我們的叢集拓撲結構。 也可以用於確保叢集中我們是否讓某個服務暴露到給定連接埠號碼。
我們可以從預烘培的容器鏡像來安裝visualizer, 就一行命令:
docker service create \ --name=viz \ --publish=8080:8080/tcp \ --constraint=node.role==manager \ --mount=type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock \ dockersamples/visualizer
這樣我們就可以通過8000連接埠來訪問它。直接通過http://$ManagerIP:8000, 記住剛才的ManagerIP的值。
令人沸騰的內容!
我也開發了一個小的Swarm可視化器,叫做dvizz, 使用的是Go語言、Docker Remote API和D3.js產生圖形。 同樣可以使用下面的命令安裝這個服務:
docker service create \ --constraint node.role==manager \ --replicas 1 --name dvizz -p 6969:6969 \ --mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \ --network my_network \ eriklupander/dvizz
直接存取http://$ManagerIP:6969, 可以看到類似的展示:
看起來有點潦草, 但是不要太認真,它還是非常有趣的, 看看塗上蹦蹦跳跳、上下縮放伺服器、隨意拖放每個節點。
添加其他服務,例如quotes-service
微服務領域現在只有一個服務(我們普遍存在的accountservice)。 下面我們部署一個前面提到的Spring Boot類型的微服務"Quotes-service", 這個微服務我已經放在公用的Docker倉庫中,可以直接部署它。
docker service create --name=quotes-service --replicas=1 --network=my_network eriklupander/quotes-service
如果使用docker ps來列舉啟動並執行Docker容器,我們可能會看到它已經啟動了(或正在啟動中)。
> docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES98867f3514a1 eriklupander/quotes-service "java -Djava.security" 12 seconds ago Up 10 seconds (health: starting) 8080/tcp
注意這裡我們沒有匯出這個服務的連接埠映射,意味著它不能從叢集外部存取,只有內部通過內部連接埠8080訪問。 我們會在後面第七部分服務發現和負載平衡中來整合這個服務。
如果你已經在叢集中添加了dvizz服務,你會看到圖中會多出一個quotes-service。
copyall.sh指令碼
要簡化事情,我們可以添加一個shell指令碼來幫我們做重新構建和重新部署的自動化工作。在goblog的根目錄下面,建立一個copyall.sh檔案:
#!/bin/bashexport GOOS=linuxexport CGO_ENABLED=0cd accountservice;go get;go build -o accountservice-linux-amd64;echo built `pwd`;cd ..export GOOS=darwindocker build -t someprefix/accountservice accountservice/docker service rm accountservicedocker service create --name=accountservice --replicas=1 --network=my_network -p=6767:6767 someprefix/accountservice
這個指令碼設定了GOOS環境變數,這樣我們可以安全的為Linux/AMD64架構構建靜態連結二進位檔案, 然後運行一些docker命令來重新構建映像檔案以及重新部署到Swarm服務裡邊。 可以節約時間,並且少輸入很多字母。
Go語言項目的構建,我不在本文中深入介紹。 個人認為,我喜歡shell指令碼的簡潔,雖然我自己也經常使用gradle外掛程式,並且也知道一個好的ol的make也是相當流行。
佔用空間和效能
從現在起,所有的基準測試和搜集CPU/記憶體度量將用於Docker Swarm中部署的服務。 這意味著之前的文章結果和現在開始之後的結果不可比。
CPU使用和記憶體使用量將使用Docker stats搜集,同時我們會使用之前使用的Gatling測試。
如果你自己運行負載測試, 那麼在第二部分引入的要求依然適用。 請注意,需要修改baseUrl參數為Swarm Manager節點的IP, 例如:
mvn gatling:execute -Dusers=1000 -Dduration=30 -DbaseUrl=http://$ManagerIP:6767
總結
到目前為止,我們已經學到如何在本地啟動Docker Swarm領域(只有一個節點), 以及如何打包和部署我們的accountservice微服務作為Docker Swarm的服務。
下一節,我們會給我們的微服務添加心跳檢查。
參考連結
- 英文第五部分
- Dockerfile
- Gradle外掛程式
- ol's make, GNU make
- 嘗試Docker中的新特性
- 專題首頁
- 下一節