分享人:鄭云龍
時間:2016–7–20
睿云智合持續(xù)交付產(chǎn)品負(fù)責(zé)人,在敏捷和DevOps領(lǐng)域有豐富經(jīng)驗的實踐,過去作為敏捷和DevOps技術(shù)教練向多家大型企業(yè)提供咨詢和培訓(xùn)服務(wù)。
?
1, Aganda
?
今天給大家分享的內(nèi)容是:《Build MicroService With Spring Cloud And Rancher》主要內(nèi)容:我們將演示如何通過Spring Cloud和Rancher構(gòu)建一個具有彈性的微服務(wù)應(yīng)用。并且在這個過程中當(dāng)中我們主要遇到的問題,以及最后如何解決。
對于一個最簡單的微服務(wù)應(yīng)用而言,一個最基本的結(jié)構(gòu)如上所示:
- 我們需要一個服務(wù)發(fā)現(xiàn)和注冊服務(wù)幫助我們管理和發(fā)現(xiàn)新的服務(wù);
- 當(dāng)我們從一個單體應(yīng)用到幾個,幾十個微服務(wù)時,服務(wù)的配置也成了一個非常棘手的問題,統(tǒng)一的配置管理必不可少;
- 為了簡化我們的客戶端調(diào)用我們可能需要一個輕量級API Gateway來統(tǒng)一代理我們的所有請求,同時可以做統(tǒng)一的安全控制;
- 同時在微服務(wù)下我們應(yīng)該遵循Build For Failure的原則,當(dāng)服務(wù)發(fā)生失敗時我們能夠隔離這些失敗的服務(wù),從而需要引入Circuit Breaker熔斷器;
- 除此之外,同一個服務(wù)可能有一個到多個實例,當(dāng)我們訪問服務(wù)時,我們還需要一個負(fù)載均衡器,幫助我們合理的分發(fā)我們的API請求。
2, Build MicroService With Spring Cloud
而Spring Cloud項目正是幫助我們解決以上問題的利器,讓我們可以更加專注于我們服務(wù)本身業(yè)務(wù)邏輯的開發(fā),而將服務(wù)治理所需的工作都統(tǒng)統(tǒng)交給框架來完成。
- Eureka提供了統(tǒng)一的服務(wù)發(fā)現(xiàn)和注冊能力;
- Ribbon和Hystrix則在服務(wù)和服務(wù)之間引入了負(fù)載均衡以及熔斷的能力;
- Zuul則作為一個輕量級的路由和代碼服務(wù)讓我們可以快速建立一個API Gateway的能力。
3, Service Discovery With Spring Cloud Netflix: Eureka
以Eureka提供的服務(wù)注冊和發(fā)現(xiàn)能力為例,作為一個微服務(wù)實例,在啟動時我們將會主動向Eureka Server進(jìn)行注冊。Eureka Server服務(wù)維護(hù)當(dāng)前服務(wù)的實例列表以及狀態(tài)健康檢查。????????而作為服務(wù)的使用者,我們首先需要向eureka server請求當(dāng)前的服務(wù)實例,之后再將請求發(fā)送給API的真正提供者。
4, Client Side Load Balance: Ribbon
同時Spring Cloud的Ribbon項目還提供了一個客戶端的負(fù)載均衡器,可以根據(jù)當(dāng)前服務(wù)的運行狀態(tài)來講請求發(fā)送到適合的實例。
5, Quick Start Demo
?
這里我們展示一下Spring Cloud是如何幫助我們快速搭建一個微服務(wù)架構(gòu)的應(yīng)用的。
這里我們以一個最簡單的spring cloud應(yīng)用為例,這里我們包含3個主要服務(wù):Eureka Server, Hello Server以及Hello Client。
首先基于Spring Boot我們可以@EnableEurekaServer可以快速創(chuàng)建一個EurekaServer的服務(wù),基本不需要任何代碼量。
同時我們基于Spring Boot創(chuàng)建另外一個項目Hello Sever,提供一個非常簡單的API返回一串字符。在application.yml當(dāng)中我們配置該服務(wù)的名稱為HelloServer,?端口為7111,并且配置eureka server的地址,在應(yīng)用啟動時,將會主動注冊服務(wù)相關(guān)信息。
同樣我們建立一個Hello Client的微服務(wù)實例,這里唯一的區(qū)別是我們使用Spring Cloud的FeignClient聲明了一個接口,該注解告訴Spring Cloud當(dāng)調(diào)用HelloClient的hello方法時,需要向HelloServer的實例的”/”地址發(fā)送一個GET請求,并且返回結(jié)果是一個字符串。
同樣的我們在application.yml文件中也配置了eureka相關(guān)的信息,為Hello Client建立一個Rest API,該API實際調(diào)用的是Hello Sever提供的服務(wù)。
依次啟動Eureka Server, Hello Server, Hello Client. 我們得到了一個最簡單的微服務(wù)架構(gòu)的應(yīng)用,我們將會得到如上的運行結(jié)果。當(dāng)訪問Hello Client的Rest API時,實際請求轉(zhuǎn)發(fā)到了Hello Server并且得到相應(yīng)的結(jié)果返回給用戶。
這個是如何工作的呢??我們簡單解釋一下,和剛才圖一樣:
1 Hello Server向Eureka Server進(jìn)行注冊名為HelloServer的服務(wù)實例。
2 Eureka Server得到請求后保存了HelloServer的實例列表信息(IP地址以及其他狀態(tài)信息)。
3 Hello Client請求http://localhost:7211時,調(diào)用了HelloClient的hello()方法,該方法的@FeginClient(“HelloServer”)注解告訴Hello Client我們需要先向Eureka Server請求HelloServer的服務(wù)實例列表,之后再根據(jù)返回的實例列表將請求發(fā)送給HelloServer其中一個實例。
這里需要注意Spirng Cloud中的一個配置項eureka.instance.preferIpAddress默認(rèn)情況下該配置為True. 表示Spring Cloud優(yōu)先使用HelloServer注冊的IP地址,如果訪問不成功我們則使用HelloServer的名稱作為默認(rèn)的域名發(fā)送請求。同理當(dāng)eureka.instance.preferIpAddress為false時,Spring Cloud則直接使用默認(rèn)的spring.application.name也就是HelloServer作為域名發(fā)送請求。
?
?
所以對于部署基于Spring Cloud的微服務(wù)應(yīng)用時,我們對于網(wǎng)絡(luò)環(huán)境的基本要求是:1, 各個服務(wù)實例能夠訪問eureka-server;
2, 各個服務(wù)實例之間能夠通過IP地址或者DNS域名相互訪問。
6, Rancher: A?Complete Platform for Running Containers
?
Rancher作為一Production目標(biāo)的容器管理平臺,當(dāng)然是我們?nèi)ミ\行,管理和維護(hù)微服務(wù)的首選運行平臺之一。????????當(dāng)我們把我們所有的服務(wù)制作為Dokcer鏡像之后,我們可以通過docker-compose.yml來編排我們的服務(wù),如上Client-Service以及Hello-Service都與Eureka相連,并且設(shè)置eureka的訪問地址為eureka-server,與我們application.yml中配置的eureka url相一致。
通過Rancher啟動docker-compose.yml之后,我們可以在eureka-server頁面上看到相關(guān)的服務(wù)實例信息。
通過Rancher我們可以非常方便的對我們的微服務(wù)應(yīng)用進(jìn)行管理,同時提供scale相關(guān)的能力支持。?????????這個時候重新嘗試訪問我們的Server服務(wù),?一切正常。得到了相應(yīng)的API返回值。
我們再嘗試訪問我們的Client服務(wù)呢。。。出錯了。。。
?
7, Rancher Network Service
?
我們來分析一下出錯的原因:
首先我們的服務(wù)和服務(wù)是分布式部署在不同的Rancher Agent上的。Rancher使用managed network來完成跨主機(jī)的容器與容器之間的相互連接。同時Docker也有自己默認(rèn)的的bridge network。?但是bridge network只能保證同一個Host上的容器是互通的。????????而client以及server在啟動時,獲取的默認(rèn)網(wǎng)絡(luò)地址則是birdge network的ip地址。?所以當(dāng)訪問client的rest api時,client向eureka獲取的IP地址是不可訪問的,之后又嘗試了spring.application.name也就是http://HelloServer:7111同樣出錯,因為HelloServer是沒有任何DNS服務(wù)能夠解析的。所以得到了我們的錯誤頁面。如下圖所示:
再直觀一點的情況如下圖所示:
所以在該模式下,我們的各個服務(wù)雖然都能向eureka server注冊相關(guān)信息,但是服務(wù)和服務(wù)之間是不能相互訪問的。
如何解決?
?
8, Rancher Internal DNS Services
?
首先:Rancher提供的內(nèi)部DNS服務(wù),在容器中我們可以通過<service_name>或者<service_name>.<stack_name>來訪問其他的服務(wù)容器實例。
那么我們假如啟動硬編碼client以及server時設(shè)置hostname的配置,比如對于client 我們配置eureka.instance.hostname=client-service,對于server我們配置eureka.instance.hostname=hello-service。????????這樣當(dāng)服務(wù)和服務(wù)之間相互訪問時,則會使用我們配置的eureka.instance.hostname作為域名進(jìn)行訪問。????????但是問題在于client-service和hello-service的名字實際上是和docker-compose相對應(yīng)的。?如果docker-compose.yml修改了各個服務(wù)的名字。?那服務(wù)和服務(wù)之間同樣是不可相互訪問的。
9, Rancher Metadata Services
如何解決?
Rancher Metadata Service. 通過metadata service我們可以通過使用HTTP API的方式來獲取到我們的服務(wù)以及容器的所有信息。
舉例來說,通過在容器內(nèi)訪問:http://rancher-metadata/latest/self/container/service_name我們便可以獲取到該容器的server_name,除此之外,包括容器的健康狀態(tài),hostname,primary_ip以及其他等等信息我們都可以通過該方式來進(jìn)行獲取。
10, Solution And Summary
?
?
????????綜合以上問題以及Rancher平臺所提供的能力,在Dockerfile中我們使用單獨的shell文件作為啟動腳本,在該啟動腳本中我們首先調(diào)用rancher的metadata service獲取該container的service_name, 并且將該service_name作為eureka.instance.hostname的值作為jar包的啟動參數(shù)即可。
?
在這個案例中,我們使用Spring Cloud快速搭建了一個簡單應(yīng)用。?同時利用Racnher提供的容器管理能力,以及DNS Service和Metadata Service解決在容器分布式部署狀態(tài)下的一些主要問題。希望能對大家有一定的參考。
關(guān)于本次分享的源碼已經(jīng)發(fā)布在github:??https://github.com/yunlzheng/spring-cloud-with-rancher.git