本文轉載自 微服務 開源項目 Apache ServiceComb (incubating) 官網博客:html
http://servicecomb.incubator.apache.org/cn/docs/linuxcon-workshop-demo/java
http://servicecomb.incubator.apache.org/cn/docs/company-on-kubernetes/node
http://servicecomb.incubator.apache.org/cn/docs/autoscale-on-company/linux
微服務架構做爲新興領域的架構模式,已步入產品化形態,與容器化、集羣等一塊兒成爲了當下熱點。而微服務、Docker、kubernetes 之間的關係,究竟這三者之間是什麼樣的關係,分別能在微服務領域發揮什麼做用,卻常給入門的讀者和用戶帶來些許迷茫感。git
本文使用一個簡單的普適性的 微服務 示例,從 業務場景入手,到微服務架構設計、實現、容器化、集羣部署、壓測、彈性伸縮、資源控制,端到端以最直白的方式演示了這三者的關係,會給讀者帶來不同的真切的理念體驗和感覺,加強對系列概念的理解。github
爲了讀者能更容易瞭解ServiceComb微服務框架的功能以及如何用其快速開發微服務,因此提供你們耳熟能詳的例子,下降學習曲線的同時,增長趣味性,加深理解。web
本文中假設咱們成立了一家科研公司,處理複雜的數學運算,以及尖端生物科技研究,併爲用戶提供以下服務:spring
黃金分割數列計算docker
蜜蜂繁殖規律 (計算每隻雄蜂/雌蜂的祖先數量)apache
可是咱們如何將公司的這些強大運算能力提供給咱們的消費者呢?
首先咱們經過認證服務保障公司的計算資源沒有被濫用, 同時咱們對外提供Rest服務讓用戶來進行訪問。 下面的視頻展現具體的服務驗證調用的狀況。
讓咱們先對業務場景進行總結分析
爲了公司持續發展,咱們須要對用戶消費的運算能力收費,因此咱們聘用了門衛認證用戶,避免不法分子混入
爲了提供足夠的黃金分割數量運算能力,咱們須要僱傭相應的技工
爲了持續研究蜜蜂繁殖規律,公司創建了本身的蜂場,須要相應的養蜂人進行管理研究
爲了平衡技工、養蜂人、和門衛的工做量和時間,咱們創建了告示欄機制,讓當前有閒暇的人員發佈本身的聯繫方式,以便咱們能及時聯繫技能匹配的人員以服務到來的用戶
由於運算能力成本高昂,咱們將運算項目進行了歸檔,以便將來有相同請求時,咱們能直接查詢項目歸檔,節省公司運算成本
面對上述複雜的場景,咱們又聘用了部門經理來管理公司成員和設施
最後,當公司日益壯大,用戶數量暴漲時,咱們還須要招聘更多技工、養蜂人、和門衛,因此增長了人力資源部門
到如今業務場景已經比較清晰,咱們把上述職務部門和設施畫成公司組織結構圖。
如今公司組織結構已經完整,讓咱們着手搭建相應部門。
由於技工最爲簡單,對其餘部門人員依賴最少,咱們首先搭建這個部門。
技工的主要工做時提供黃金分割數列計算服務,當用戶須要知道第n個黃金分割數時,技工以最快的速度計算出數值並返回給用戶。 咱們能夠把這個工做簡化爲以下數學方程:
value = fibo(n)
在暫時不考慮性能的狀況下,咱們能夠迅速實現黃金分割數列的計算。
interface FibonacciService { long term(int n); } @Service class FibonacciServiceImpl implements FibonacciService { @Override public long term(int n) { if (n == 0) { return 0; } else if (n == 1) { return 1; } return term(n - 1) + term(n - 2); } }
黃金分割數量運算已經實現,如今咱們須要將服務提供給用戶,首先咱們定義端點接口:
public interface FibonacciEndpoint { long term(int n); }
引入 ServiceComb
依賴:
<dependency> <groupId>org.apache.servicecomb</groupId> <artifactId>spring-boot-starter-provider</artifactId> </dependency>
接下來咱們同時暴露黃金分割運算服務的Restful和RPC端點:
@RestSchema(schemaId = "fibonacciRestEndpoint") @RequestMapping("/fibonacci") @Controller public class FibonacciRestEndpoint implements FibonacciEndpoint { private final FibonacciService fibonacciService; @Autowired FibonacciRestEndpoint(FibonacciService fibonacciService) { this.fibonacciService = fibonacciService; } @Override @RequestMapping(value = "/term", method = RequestMethod.GET) @ResponseBody public long term(int n) { return fibonacciService.term(n); } }
@RpcSchema(schemaId = "fibonacciRpcEndpoint") public class FibonacciRpcEndpoint implements FibonacciEndpoint { private final FibonacciService fibonacciService; @Autowired public FibonacciRpcEndpoint(FibonacciService fibonacciService) { this.fibonacciService = fibonacciService; } @Override public long term(int n) { return fibonacciService.term(n); } }
這裏用 @RestSchema
和 @RpcSchema
註釋兩個端點後,ServiceComb
會自動生成對應的服務端點契約,根據以下microsevice.yaml
配置端點端口,並將契約和服務一塊兒註冊到Service Center:
# all interconnected microservices must belong to an application wth the same ID APPLICATION_ID: company service_description: # name of the declaring microservice name: worker version: 0.0.1 # service center address cse: service: registry: address: http://sc.servicecomb.io:30100 highway: address: 0.0.0.0:7070 rest: address: 0.0.0.0:8080
最後,提供技工服務應用啓動入口,並加上 @EnableServiceComb
註釋啓用 ServiceComb
:
@SpringBootApplication @EnableServiceComb public class WorkerApplication { public static void main(String[] args) { SpringApplication.run(WorkerApplication.class, args); } }
告示欄提供爲門衛、技工和養蜂人註冊聯繫方式的設施,同時經理和養蜂人可經過此設施查詢註冊方的聯繫方式,以方便匹配能力的提供和消費。
Service Center
提供契約和服務註冊、發現功能,並且校驗服務提供方和消費方的契約是否匹配,咱們能夠下載編譯好的版本直接運行。
養蜂人研究蜜蜂繁殖規律,計算每隻蜜蜂 (雄蜂/雌蜂) 的祖先數量。由於蜜蜂繁殖規律和黃金分割數列相關,因此養蜂人同時消費技工提供的計算服務。
研究代表,雄蜂(Drone)由未受精卵孵化而生,只有母親;而雌蜂(Queen)由受精卵孵化而生,既有母又有父。
Credit: Dave Cushman’s website
參考上圖,蜜蜂的某一代祖先數量符合黃金分割數列的模型,由此咱們能夠很快實現服務功能。
首先咱們定義黃金數列運算接口:
public interface FibonacciCalculator { long term(int n); }
接下來定義並實現蜜蜂繁殖規律研究服務:
interface BeekeeperService { long ancestorsOfDroneAt(int generation); long ancestorsOfQueenAt(int generation); } class BeekeeperServiceImpl implements BeekeeperService { private final FibonacciCalculator fibonacciCalculator; BeekeeperServiceImpl(FibonacciCalculator fibonacciCalculator) { this.fibonacciCalculator = fibonacciCalculator; } @Override public long ancestorsOfDroneAt(int generation) { if (generation <= 0) { return 0; } return fibonacciCalculator.term(generation + 1); } @Override public long ancestorsOfQueenAt(int generation) { if (generation <= 0) { return 0; } return fibonacciCalculator.term(generation + 2); } }
這裏咱們用到以前定義的 FibonacciCalculator
接口,並但願經過這個接口遠程調用技工服務端點。@RpcReference
註釋能幫助咱們自動從Service Center中獲取 microserviceName = "worker", schemaId = "fibonacciRpcEndpoint"
, 即服務名爲 worker
已經schema ID爲 fibonacciRpcEndpoint
的端點:
@Configuration class BeekeeperConfig { @RpcReference(microserviceName = "worker", schemaId = "fibonacciRpcEndpoint") private FibonacciCalculator fibonacciCalculator; @Bean BeekeeperService beekeeperService() { return new BeekeeperServiceImpl(fibonacciCalculator); } }
咱們在技工一節已定義好對應的服務名和schema ID端點,經過上面的配置,ServiceComb
會自動將遠程技工服務 實例和 FibonacciCalculator
綁定在一塊兒。
與上一節技工服務類似,咱們在這裏也須要提供養蜂人服務端點,讓用戶能夠進行調用:
@RestSchema(schemaId = "beekeeperRestEndpoint") @RequestMapping("/rest") @Controller public class BeekeeperController { private static final Logger logger = LoggerFactory.getLogger(BeekeeperController.class); private final BeekeeperService beekeeperService; @Autowired BeekeeperController(BeekeeperService beekeeperService) { this.beekeeperService = beekeeperService; } @RequestMapping(value = "/drone/ancestors/{generation}", method = GET, produces = APPLICATION_JSON_UTF8_VALUE) @ResponseBody public Ancestor ancestorsOfDrone(@PathVariable int generation) { logger.info( "Received request to find the number of ancestors of drone at generation {}", generation); return new Ancestor(beekeeperService.ancestorsOfDroneAt(generation)); } @RequestMapping(value = "/queen/ancestors/{generation}", method = GET, produces = APPLICATION_JSON_UTF8_VALUE) @ResponseBody public Ancestor ancestorsOfQueen(@PathVariable int generation) { logger.info( "Received request to find the number of ancestors of queen at generation {}", generation); return new Ancestor(beekeeperService.ancestorsOfQueenAt(generation)); } } class Ancestor { private long ancestors; Ancestor() { } Ancestor(long ancestors) { this.ancestors = ancestors; } public long getAncestors() { return ancestors; } }
由於養蜂人須要消費技工提供的服務,因此其 microservice.yaml
配置稍有不一樣:
# all interconnected microservices must belong to an application wth the same ID APPLICATION_ID: company service_description: # name of the declaring microservice name: beekeeper version: 0.0.1 cse: service: registry: address: http://sc.servicecomb.io:30100 rest: address: 0.0.0.0:8090 handler: chain: Consumer: default: bizkeeper-consumer,loadbalance references: # this one below must refer to the microservice name it communicates with worker: version-rule: 0.0.1
這裏咱們須要定義 cse.references.worker.version-rule
,讓配置名稱中指向技工服務名 worker
,並匹配其版本號。
最後定義養蜂人服務應用入口:
@SpringBootApplication @EnableServiceComb public class BeekeeperApplication { public static void main(String[] args) { SpringApplication.run(BeekeeperApplication.class, args); } }
門衛爲公司提供安全保障,屏蔽非合法用戶,防止其騙取免費服務,甚至傷害技工和養蜂人。
認證功能咱們採用JSON Web Token (JWT)的機制,具體實現超出了這篇文章的範圍, 細節你們能夠查看github上workshop的 doorman
模塊代碼。
認證服務的接口以下,authenticate
方法根據用戶名和密碼查詢確認用戶存在,並返回對應JWT token。用戶登陸後的每次 請求都須要帶上返回的JWT token,而 validate
方法將驗證token以確認其有效。
public interface AuthenticationService { String authenticate(String username, String password); String validate(String token); }
與前兩節的Rest服務端點類似,咱們加上 @RestSchema
註釋,以便 ServiceComb
自動配置端點、生成契約並註冊服務。
@RestSchema(schemaId = "authenticationRestEndpoint") @Controller @RequestMapping("/rest") public class AuthenticationController { private static final Logger logger = LoggerFactory.getLogger(AuthenticationController.class); static final String USERNAME = "username"; static final String PASSWORD = "password"; static final String TOKEN = "token"; private final AuthenticationService authenticationService; @Autowired AuthenticationController(AuthenticationService authenticationService) { this.authenticationService = authenticationService; } @RequestMapping(value = "/login", method = POST, produces = TEXT_PLAIN_VALUE) public ResponseEntity<String> login( @RequestParam(USERNAME) String username, @RequestParam(PASSWORD) String password) { logger.info("Received login request from user {}", username); String token = authenticationService.authenticate(username, password); HttpHeaders headers = new HttpHeaders(); headers.add(AUTHORIZATION, TOKEN_PREFIX + token); logger.info("Authenticated user {} successfully", username); return new ResponseEntity<>("Welcome, " + username, headers, OK); } @RequestMapping(value = "/validate", method = POST, consumes = APPLICATION_JSON_UTF8_VALUE, produces = TEXT_PLAIN_VALUE) @ResponseBody public String validate(@RequestBody Token token) { logger.info("Received validation request of token {}", token); return authenticationService.validate(token.getToken()); } } class Token { private String token; Token() { } Token(String token) { this.token = token; } public String getToken() { return token; } @Override public String toString() { return "Token{" + "token='" + token + '\'' + '}'; } }
一樣,咱們須要提供服務應用啓動入口以及 microservice.yaml
:
@SpringBootApplication @EnableServiceComb public class DoormanApplication { public static void main(String[] args) { SpringApplication.run(DoormanApplication.class, args); } }
# all interconnected microservices must belong to an application wth the same ID APPLICATION_ID: company service_description: # name of the declaring microservice name: doorman version: 0.0.1 cse: service: registry: address: http://sc.servicecomb.io:30100 rest: address: 0.0.0.0:9090
爲了管理全部人員和設施,經理做爲用戶惟一接口人,主要功能有:
聯繫門衛認證用戶,保護技工和養蜂人,以避免非法用戶騙取服務並逃避服務費用
聯繫能力相符的技工和養蜂人,平衡工做量,避免單我的員工做超載
管理項目歸檔,避免重複工做,保證公司收益最大化
因爲經理責任重大,咱們選取了業界有名的Netflix Zuul做爲候選人並加以培訓, 提高其能力,以保證其能勝任該職位。
首先咱們引入依賴:
<dependency> <groupId>org.apache.servicecomb</groupId> <artifactId>spring-boot-starter-discovery</artifactId> </dependency>
當用戶發送非登陸請求時,咱們首先須要驗證用戶合法,在以下服務中,咱們經過告示欄獲取門衛聯繫方式, 而後發送用戶token給門衛進行認證。
ServiceComb
提供了相應 RestTemplate
實現查詢Service Center 中的服務註冊信息,只需在地址中以以下格式包含被調用的服務名
cse://doorman/path/to/rest/endpoint
ServiceComb
將自動查詢對應服務併發送請求到地址中的服務端點。
@Service public class AuthenticationService { private static final Logger logger = LoggerFactory.getLogger(AuthenticationService.class); private static final String DOORMAN_ADDRESS = "cse://doorman"; private final RestTemplate restTemplate; AuthenticationService() { this.restTemplate = RestTemplateBuilder.create(); this.restTemplate.setErrorHandler(new ResponseErrorHandler() { @Override public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException { return false; } @Override public void handleError(ClientHttpResponse clientHttpResponse) throws IOException { } }); } @HystrixCommand(fallbackMethod = "timeout") public ResponseEntity<String> validate(String token) { logger.info("Validating token {}", token); ResponseEntity<String> responseEntity = restTemplate.postForEntity( DOORMAN_ADDRESS + "/rest/validate", validationRequest(token), String.class ); if (!responseEntity.getStatusCode().is2xxSuccessful()) { logger.warn("No such user found with token {}", token); } logger.info("Validated request of token {} to be user {}", token, responseEntity.getBody()); return responseEntity; } private ResponseEntity<String> timeout(String token) { logger.warn("Request to validate token {} timed out", token); return new ResponseEntity<>(REQUEST_TIMEOUT); } private HttpEntity<Token> validationRequest(String token) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON_UTF8); return new HttpEntity<>(new Token(token), headers); } }
接下來咱們提供 ZuulFilter
實現過濾用戶請求,調用 authenticationService.validate(token)
認證用戶token。 若用戶合法則路由用戶請求到對應服務,不然返回 403 forbidden
。
@Component class AuthenticationAwareFilter extends ZuulFilter { private static final Logger logger = LoggerFactory.getLogger(AuthenticationAwareFilter.class); private static final String LOGIN_PATH = "/login"; private final AuthenticationService authenticationService; private final PathExtractor pathExtractor; @Autowired AuthenticationAwareFilter( AuthenticationService authenticationService, PathExtractor pathExtractor) { this.authenticationService = authenticationService; this.pathExtractor = pathExtractor; } @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 1; } @Override public boolean shouldFilter() { String path = pathExtractor.path(RequestContext.getCurrentContext()); logger.info("Received request with query path: {}", path); return !path.endsWith(LOGIN_PATH); } @Override public Object run() { filter(); return null; } private void filter() { RequestContext context = RequestContext.getCurrentContext(); if (doesNotContainToken(context)) { logger.warn("No token found in request header"); rejectRequest(context); } else { String token = token(context); ResponseEntity<String> responseEntity = authenticationService.validate(token); if (!responseEntity.getStatusCode().is2xxSuccessful()) { logger.warn("Unauthorized token {} and request rejected", token); rejectRequest(context); } else { logger.info("Token {} validated", token); } } } private void rejectRequest(RequestContext context) { context.setResponseStatusCode(SC_FORBIDDEN); context.setSendZuulResponse(false); } private boolean doesNotContainToken(RequestContext context) { return authorizationHeader(context) == null || !authorizationHeader(context).startsWith(TOKEN_PREFIX); } private String token(RequestContext context) { return authorizationHeader(context).replace(TOKEN_PREFIX, ""); } private String authorizationHeader(RequestContext context) { return context.getRequest().getHeader(AUTHORIZATION); } }
最後提供服務應用入口:
@SpringBootApplication @EnableCircuitBreaker @EnableZuulProxy @EnableDiscoveryClient @EnableServiceComb public class ManagerApplication { public static void main(String[] args) { SpringApplication.run(ManagerApplication.class, args); } }
application.yaml
中定義路由規則:
zuul: routes: doorman: serviceId: doorman sensitiveHeaders: worker: serviceId: worker beekeeper: serviceId: beekeeper # disable netflix eurkea since it's not used for service discovery ribbon: eureka: enabled: false
microservice.yaml
中定義服務中心地址:
APPLICATION_ID: company service_description: name: manager version: 0.0.1 cse: service: registry: address: http://sc.servicecomb.io:30100
經理在每次用戶請求後將項目進行歸檔,若是未來有內容相同的請求到達,經理能夠就近獲取結果,沒必要再購買 技工和養蜂人提供的計算服務,節省公司開支。
對於歸檔功能的實現,咱們採用了Spring Cache Abstraction,具體細節超出了這篇文章的範圍,你們若是有興趣能夠 查看github上workshop的 manager
模塊代碼。
人力資源從運維層面保證服務的可靠性,主要功能有
彈性伸縮,以保證用戶請求量超過技工或養蜂人處理能力後,招聘更多技工或養蜂人加入項目;當請求量回落後,裁剪技工或養蜂人以節省公司開支
健康檢查,以保證技工或養蜂人告病時,能有替補接手任務
滾動升級,以保證項目須要新技能時,能替換、培訓技工或養蜂人,不中斷接收用戶請求
人力資源的功能須要雲平臺提供支持,在後續的文章中會跟你們介紹,咱們如何在華爲雲上輕鬆實現這些功能。
至此,咱們用一個公司的組織結構做爲例子,給你們介紹了微服務的完整架構,以及如何使用微服務框架 ServiceComb
快速開發微服務,以及服務間互通、契約認證。
Workshop demo項目也包含大量完整易懂的測試 代碼,以及使用docker集成微服務,模擬生存環境,同時應用Travis搭建持續集成環境,體現 DevOps在微服務開發中的實踐。但願能對你們有所幫助。
如今,github上已經提供了在kubernetes集羣上一鍵式部署的功能。本文將着重講解相應的yaml文件和服務間通訊,這對於開發者基於Company 模型進行微服務開發而且部署到雲上將會有所幫助。
Run Company on Kubernetes Cluster 提供了詳細的使用方法,讀者只需經過如下3條指令,就可將company在kubernetes集羣上部署起來,
git clone https://github.com/ServiceComb/ServiceComb-Company-WorkShop.git cd ServiceComb-Company-WorkShop/kubernetes/ bash start.sh
以做者的實際環境爲例:
root@zenlin:~/src/LinuxCon-Beijing-WorkShop/kubernetes# kubectl get pod -owide NAME READY STATUS RESTARTS AGE IP NODE company-beekeeper-3737555734-48sxf 1/1 Running 0 17s 10.244.2.49 zenlinnode2 company-bulletin-board-4113647782-th91w 1/1 Running 0 17s 10.244.1.53 zenlinnode1 company-doorman-3391375245-g0p8c 1/1 Running 0 17s 10.244.1.55 zenlinnode1 company-manager-454733969-0c1g8 1/1 Running 0 16s 10.244.2.50 zenlinnode2 company-worker-1085546725-x7zl4 1/1 Running 0 17s 10.244.1.54 zenlinnode1 zipkin-508217170-0khr3 1/1 Running 0 17s 10.244.2.48 zenlinnode2
能夠看到,一共啓動了6個pod,分別爲,公司經理(company-manager)、門衛(company-doorman)、公告欄(company-bulletin-board)、技工(company-worker)、養蜂人(company-beekeeper)、調用鏈跟蹤(zipkin),K8S集羣分別爲他們分配對應的集羣IP。
root@zenlin:~/src/LinuxCon-Beijing-WorkShop/kubernetes# kubectl get svc -owide NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR company-bulletin-board 10.99.70.46 <none> 30100/TCP 12m io.kompose.service=company-bulletin-board company-manager 10.100.61.227 <nodes> 8083:30301/TCP 12m io.kompose.service=company-manager zipkin 10.104.92.198 <none> 9411/TCP 12m io.kompose.service=zipkin
僅啓動了3個service,調用鏈跟蹤(zipkin)、公告欄(company-bulletin-board)以及經理(company-manager),這是由於,調用鏈跟蹤和公告欄須要在集羣內被其餘服務經過域名來調用,而經理須要做爲對外做爲網關,統一暴露服務端口。
查看company-bulletin-board-service.yaml文件,
apiVersion: v1 kind: Service metadata: creationTimestamp: null labels: io.kompose.service: company-bulletin-board name: company-bulletin-board spec: ports: - name: "30100" port: 30100 targetPort: 30100 selector: io.kompose.service: company-bulletin-board status: loadBalancer: {}
該文件定義了公告欄對應的service,給service定義了name、port和targetPort,這樣經過kubectl expose建立的service會在集羣內具有DNS能力,在其餘服務剛啓動還未註冊到公告欄(服務註冊發現中心)時,就是使用該能力來訪問到公告欄並註冊服務的。
對於label和selector的做用,在一個service啓動多個pod的場景下將會很是有用,當某個pod崩潰時,服務的selector將會自動將死亡的pod從endpoints中移除,而且選擇新的pod加入到endpoints中。
查看company-worker-deployment.yaml 文件,
apiVersion: extensions/v1beta1 kind: Deployment metadata: creationTimestamp: null labels: io.kompose.service: company-worker name: company-worker spec: replicas: 1 strategy: {} template: metadata: creationTimestamp: null labels: io.kompose.service: company-worker spec: containers: - env: - name: ARTIFACT_ID value: worker - name: JAVA_OPTS value: -Dcse.service.registry.address=http://company-bulletin-board:30100 -Dservicecomb.tracing.collector.adress=http://zipkin:9411 image: servicecomb/worker:0.0.1-SNAPSHOT name: company-worker ports: - containerPort: 7070 - containerPort: 8080 resources: {} restartPolicy: Always status: {}
該yaml文件定義了副本數爲1(replicas: 1)的pod,能夠經過修改該副本數控制所需啓動的pod的副本數量(固然也可使用K8S的彈性伸縮能力去實現按需動態水平伸縮,彈性伸縮部分將在後面的博文中提供)。前面咱們提到過company-bulletin-board具有了DNS的能力,故如今能夠經過該Deployment中的env傳遞cse.service.registry.address的值給pod內的服務使用,如: -Dcse.service.registry.address=http://company-bulletin-board:30100,kube-dns將會自動解析該servicename。
對於kubernetes如何實現服務間通訊,能夠閱讀connect-applications-service。
其餘的deployment.yaml以及service.yaml都跟以上大同小異,惟一例外的是company-manager服務,咱們能夠看到在company-manager-service.yaml中看到定義了nodePort,這將使能company-manager對集羣外部提供公網IP和服務端口,以下:
spec: ports: - name: "8083" port: 8083 targetPort: 8080 nodePort: 30301 protocol: TCP type: NodePort
能夠經過如下方法得到公網IP和服務端口:
kubectl get svc company-manager -o yaml | grep ExternalIP -C 1 kubectl get svc company-manager -o yaml | grep nodePort -C 1
接下來你就可使用公網IP和服務端口訪問已經部署好的company了,在github.com/ServiceComb/ServiceComb-Company-WorkShop/kubernetes上詳細提供了經過在集羣內訪問和集羣外訪問的方法。
經過詳細閱讀全部的deployment.yaml和service.yaml,能夠整理出如下的模型:
另外,經典的航空訂票系統Acmeair也已經支持在kubernetes上一鍵式部署基於ServiceComb框架開發的版本,點擊訪問Run Acmeair on Kubernetes獲取 。
本小節將繼續在K8S上演示使用K8S的彈性伸縮能力進行Company示例的按需精細化資源控制,以此體驗微服務化給你們帶來的好處。
K8S環境準備:
爲使K8S具有彈性伸縮能力,須要先在K8S中安裝監控器Heapster和Grafana:
具體讀者踩了坑後更新的heapster的安裝腳本做者放在:heapster,可直接獲取下載獲取,須要調整一個參數,後直接運行kube.sh腳本進行安裝。
vi LinuxCon-Beijing-WorkShop/kubernetes/heapster/deploy/kube-config/influxdb/heapster.yaml
spec: replicas: 1 template: metadata: labels: task: monitoring k8s-app: heapster spec: serviceAccountName: heapster containers: - name: heapster image: gcr.io/google_containers/heapster-amd64:v1.4.1 imagePullPolicy: IfNotPresent command: - /heapster #集羣內安裝直接使用kubernetes - --source=kubernetes #集羣外安裝請直接將下面的服務地址替換爲k8s api server地址 # - --source=kubernetes:http://10.229.43.65:6443?inClusterConfig=false - --sink=influxdb:http://monitoring-influxdb:8086
啓動Company:
下載Comany支持彈性伸縮的代碼:
git clone https://github.com/ServiceComb/ServiceComb-Company-WorkShop.git cd LinuxCon-Beijing-WorkShop/kubernetes/ bash start-autoscale.sh
在Company的deployment.yaml中, 增長了以下限定資源的字段,這將限制每一個pod被限制在200mill-core(1000毫core == 1 core)的cpu使用率之內。
resources: limits: cpu: 200m
在 start-autoscale.sh 中,對每一個deployment建立HPA(pod水平彈性伸縮器)資源,限定每一個pod的副本數彈性伸縮時控制在1到10之間,並限定每一個pod的cpu佔用率小於50%,結合前面限定了200mcore,故,每一個pod的的平均cpu佔用率會被HPA經過彈性伸縮能力控制在100mcore之內。
# Create Horizontal Pod Autoscaler kubectl autoscale deployment zipkin --cpu-percent=50 --min=1 --max=10 kubectl autoscale deployment company-bulletin-board --cpu-percent=50 --min=1 --max=10 kubectl autoscale deployment company-worker --cpu-percent=50 --min=1 --max=10 kubectl autoscale deployment company-doorman --cpu-percent=50 --min=1 --max=10 kubectl autoscale deployment company-manager --cpu-percent=50 --min=1 --max=10 kubectl autoscale deployment company-beekeeper --cpu-percent=50 --min=1 --max=10
當運行start-autoscale.sh以後,具有彈性伸縮器的company已經被建立,可經過下面指令進行HPA的查詢:
kubectl get hpa
啓動壓測:
export $HOST=<heapster-ip>:<heapster-port> bash LinuxCon-Beijing-WorkShop/kubernetes/stress-test.sh
該腳本不斷循環執行 1s內向Company請求計算 fibonacci 數值200次,對Company形成請求壓力:
FIBONA_NUM=`curl -s -H "Authorization: $Authorization" -XGET "http://$HOST/worker/fibonacci/term?n=6"`
分別查看HPA狀態以及Grafana,以下:
圖1 啓動階段
圖2 啓動階段
圖3 過程
圖4 結果
圖5 結果
從以上過程能夠分析出,如下幾點:
1. 壓力主要集中在company-manager這個pod上,K8S的autoscaler經過彈性增長該pod的副本數量,最終達到目標:每一個pod的cpu佔用率低於限定值的50%(圖5,Usage default company-manager/Request default company-manager = 192/600 約等於圖4中的33%),並保持穩定。
2. 在彈性伸縮過程當中,在還沒穩定前可能形成丟包,如圖3。
3. Company啓動會致使系統資源負載暫時性加大,故Grafana上看到的cpu佔用率曲線會呈現波峯狀,但隨着系統穩定運行後,HPA會按照系統的穩定資源消耗準確找到匹配的副本數。圖3中副本數已超過實際所需3個,但隨着系統穩定,最終仍是穩定維持在3個副本。
4. 在HPA以及Grafana能夠看到縮放和報告數據都會有延遲,按照官方文檔說法,只有在最近3分鐘內沒有從新縮放的狀況下,纔會進行放大。 從最後一次從新縮放,縮小比例將等待5分鐘。 並且,只有在avg/ Target下降到0.9如下或者增長到1.1以上(10%容差)的狀況下,纔可能會進行縮放。
以上,就是本次對Compan示例彈性伸縮的全過程,Martin Fowler 在2014年3月的文章中提到:
微服務是一種架構風格,一個大型複雜軟件應用由一個或多個微服務組成。系統中的各個微服務可被獨立部署,各個微服務之間是鬆耦合的。每一個微服務僅關注於完成一件任務並很好地完成該任務。在全部狀況下,每一個任務表明着一個小的業務能力。
國內實踐微服務的先行者王磊先生也在《微服務架構與實踐》一書中進行了全面論述。
Company使用ServiceComb進行微服務化改造後,具有了微服務的屬性,故能夠對單個負載較大的company-manager這個微服務進行精細化的控制,達到按需的目的,相比傳統單體架構來說,這將大大幫助準確有效地化解應用瓶頸,提升資源的利用效率。
如何加入Apache ServiceComb 社區
http://servicecomb.incubator.apache.org/cn/docs/join_the_community/
Apache ServiceComb (incubating) 項目地址
https://github.com/apache/incubator-servicecomb-java-chassis
https://github.com/apache/incubator-servicecomb-service-center
https://github.com/apache/incubator-servicecomb-saga
Apache ServiceComb (incubating) 官網