本文為阿裡雲容器服務spring cloud應用開發系列文章的第三篇。
三、服務發現(本文)
傳統應用由于服務數目相對固定,服務所在的節點也相對固定,動态發現的需求不強烈。服務發現的通常做法是通過dns域名解析,或ip位址找到相關服務。雲環境中,服務的數目可能随時變化,服務所在的節點和偵聽的端口也無法預先知道,是以在雲應用的微服務架構中,服務發現是必須具備的一項能力。
通常我們可以采用zookeeper、etcd或consul等開源産品來實作服務的注冊與發現。spring cloud中提供了兩種方式:一是consul,另外一個是netflix的eureka。本文讨論了如何采用eureka進行服務發現,以及如何将eureka及相關服務容器化。
eureka server可以通過在程式中annotation聲明的方式生成,詳細步驟見下文。如果想直接用建構好的鏡像,可以使用本文提供的示例:
如果使用已有的鏡像可以忽略本步。
首先,我們需要在在build.gradle中引入java項目對eureka的依賴。
在application.yml配置eureka的參數。内容如下:
在測試環境中(profile沒有指定),eureka偵聽8761端口,<code>serviceurl.defaultzone</code>中隻有一個eureka服務執行個體。
在随後的cloud profile中,<code>serviceurl.defaultzone</code>除了有本機的eureka server,還通過<code>additional_eureka_server_list</code>環境變量指明其他eureka server節點資訊。eureka server之間的内容互相同步,eureka client隻需注冊到其中任意一個節點即可。
運作eureka服務有兩種方式,一種是通過docker指令行執行,另外一種是在docker-compose檔案中聲明,并執行docker-compose啟動。
指令行方式可以是從gradle中執行
通過java指令啟動單獨一個eureka server,效果和上面的gradle指令一樣
通過java指令啟動,profile為cloud,并通過環境變量指定了eureka叢集中其他的節點位址。
由于在開發實際的應用過程中往往需要啟動多個容器,是以通過腳本的方式啟動服務非常不友善,是以建議通過在docker-compose檔案中聲明啟動容器時的環境變量配置eureka啟動時的行為:
如果有多個eureka server需要在<code>additional_eureka_server_list</code>環境變量中指明,每個eureka 的url之間用逗号分隔即可。
springboot應用編譯生成的jar包包含了所有依賴包以及app server,這種包也叫“超級jar包“,英文 uber jar。編譯好的jar包在dockercompose檔案中指明,即可由docker引擎生成容器鏡像。
如何編譯uber jar就不詳細介紹了,我們隻要記住執行如下指令即可:
編譯生成的jar包在build/libs目錄:
最後,寫一個dockerfile,定義如何生成一個eureka server鏡像:
其中<code>add build/libs/*.jar app.jar</code>表示把建構好的jar包複制到鏡像中。由于<code>build/libs</code>目錄下隻有一個jar包,是以這行指令實際上是将jar包複制到鏡像的根目錄下,并命名為<code>app.jar</code>。這個dockerfile可以使用者所有springboot編譯出來的應用。
<code>run echo $(date) > /image_built_at</code>表示把建構鏡像的日期打入鏡像以利于未來的運維,讀者可以根據自己的需求修改或删除這一行。
運作如下指令生成鏡像,并上傳到你自己的鏡像倉庫賬号下:
運作上述指令生成的eureka server鏡像有兩個tag:<code>latest</code>和建立時間,例如<code>20160627-092112</code>。你可以根據自己的實際需求定義tag。
登入到鏡像倉庫并上傳鏡像的指令如下:
進入建立容器倉庫界面,輸入倉庫名,并選擇<code>本地倉庫</code>

建立成功後就可以随時上傳鏡像了。
除eureka server之外所有需要注冊到eureka的應用都要通過eureka client注冊到eureka server和擷取其他應用的通路資訊。
引入eureka client也是通過annotation的機制完成。
其中<code>@enablediscoveryclient</code>和前面eureka server的注解功能一樣,注意,client不能聲明enableeurekaserver。
在bootstrap.yml中為應用命名,該名字用于向eureka注冊,所有用同樣名字注冊的應用都視為同一個應用(或服務)的不同執行個體,是以一定要注意應用名在一個eureka叢集的上下文中是唯一的。
eureka client所屬應用也是通過<code>build.gradle</code>檔案引入eureka依賴,<code>application.yml</code>配置eureka,程式的編譯、運作和建構容器鏡像和eureka server類似,這裡就不再重複了。
通過在<code>bootstrap.yml</code>中聲明<code>spring.application.name</code>,應用啟動時會自動完成向eureka server的注冊。那麼,如何進行服務發現呢,我們在<code>foobar</code>的代碼中看一下。
resttemplate的聲明需要加上<code>@autowired</code>和<code>@loadbalanced</code>兩個注解。在随後對<code>resttemplate.getforobject</code>的調用傳入服務的url。
其中<code>bar</code>服務名,<code>/message</code>為希望調用的url path。 <code>resttemplate.getforobject</code>從eureka中獲得<code>bar</code>服務的所有執行個體,并根據内置的負載均衡算法選擇一個執行個體調用并傳回結果。
在<code>aliyuncs-springcloud-demo</code>目錄下可以看到如下内容:
<code>common</code>目錄下是公用服務,包括服務發現的discovery server(eureka),和智能路由gateway(zuul)。
<code>services</code>目錄下是所有應用的業務邏輯所在的服務,它們都注冊到discovery server,服務的發現也通過discovery server。
注冊和發現的機制在前面已經描述過了,所有應用(包括discovery server和gatewa也)的docker鏡像建構都非常類似,在各自的目錄下都可以找到相應的檔案。
手工進入每個目錄并執行編譯和上傳的指令非常繁瑣,也容易出錯,是以在示例中提供了腳本來建構和上傳所有服務的鏡像。
編譯、打包所有服務,并建立容器鏡像(不上傳鏡像)的指令:
上傳所有鏡像,注意,如果還沒建立鏡像倉庫,首先去建立,步驟見eureka server一節。
本文介紹了如何通過eureka進行服務注冊和發現,如何鏡像化等内容。最後簡單介紹了如何建構示例代碼中的所有服務鏡像。
後續的文章我們會一起來看spring cloud和阿裡雲容器服務的其它特性。