- 金融級IT架構(gòu)與運維:云原生、分布式與安全
- 魏新宇等
- 4670字
- 2022-01-14 14:22:59
2.2 微服務(wù)與容器云的邊界
微服務(wù)與Kubernetes容器云的邊界的考量,其實就是思考微服務(wù)要不要跨多個Kubernetes集群的問題。比較理想的情況是微服務(wù)和Kubernetes完全對齊,也就是一套微服務(wù)運行在一套Kubernetes集群上。在這種情況下,微服務(wù)、配置中心+注冊中心都在相同的Kubernetes集群中。當微服務(wù)指向配置中心時,寫配置中心的ServiceName即可,網(wǎng)絡(luò)I/O路徑較短。否則,需要通過Kubernetes Ingress訪問注冊中心(如果容器云的SDN采用overlay模式),網(wǎng)絡(luò)延遲將會較大。這在微服務(wù)數(shù)量較多、變更較頻繁的時候更為明顯。
但是,如果微服務(wù)跨多個Kubernetes,會有什么問題呢?
我們先看兩種實現(xiàn)方式。
1)配置+注冊中心在一個Kubernetes集群上:如果Kubernetes集群的SDN用的是underlay網(wǎng)絡(luò),那么其他Kubernetes集群注冊的時候,由于其Pod IP和宿主機IP在同一個網(wǎng)絡(luò)平面,使得注冊中心能夠準確識別到Pod的IP。
這種方式的弊端體現(xiàn)在如下三個方面。
- 微服務(wù)去注冊中心注冊時,由于跨Kubernetes集群,網(wǎng)絡(luò)I/O路徑長。
- 數(shù)據(jù)中心網(wǎng)絡(luò)需要打開BGP(用到了類似Caico的underlay SDN方案)。
- underlay網(wǎng)絡(luò)方案比較耗費數(shù)據(jù)中心的IP。
2)配置+注冊中心不在一個Kubernetes集群上:如果Kubernetes集群的SDN方案用的是overlay網(wǎng)絡(luò),那么其他Kubernetes集群注冊的時候,由于Pod IP和宿主機IP不在同一個網(wǎng)絡(luò)平面,導致注冊中心不能準確識別Pod的IP,只能識別到Pod所在Kubernetes宿主機的IP(Pod以SNAT的方式訪問集群外部)。想要解決這個問題,可以考慮使用Pod的多網(wǎng)絡(luò)平面,也就是給Pod增加第二個虛擬網(wǎng)卡,掛載數(shù)據(jù)中心到同一個網(wǎng)絡(luò)平面的IP。這種方式類似macvlan、ipvlan,不用再單獨配置DNS,但弊端是當宿主機上啟動的macvlan數(shù)量較多時,網(wǎng)卡性能會下降。
以上兩種實現(xiàn)方式各有優(yōu)劣勢。筆者看來,如果Spring Cloud的邊界遠大于一個Kubernetes邊界,想讓一套Spring Cloud分布在很多個Kubernetes集群時,最好把微服務(wù)配置中心和注冊中心從Kubernetes集群中獨立出來,放在虛擬機或者物理機上。這樣做的好處是讓這個配置+注冊中心離所有Kubernetes集群網(wǎng)絡(luò)都比較近。而且,在虛擬機或者物理機上部署配置+注冊中心,當需要注冊微服務(wù)的時候,也不必再經(jīng)過類似Ingress的環(huán)節(jié),性能也會得到提升。此外,我們可以針對獨立的配置+注冊中心做高可用或者容災(zāi)方案。
接下來,我們介紹如何選擇微服務(wù)注冊中心和配置中心。
2.2.1 微服務(wù)注冊中心的選擇
注冊中心本質(zhì)就是一個Query函數(shù),即Si=F(ServiceName)。ServiceName為查詢服務(wù)參數(shù),Si為服務(wù)可用的列表(IP:Port)。
為了方便讀者理解服務(wù)注冊,我們參照圖2-25進行介紹,將ServiceA的三個實例注冊到注冊中心。

圖2-25 三個實例注冊到注冊中心
1)服務(wù)提供方將三個實例注冊到注冊中心。
2)服務(wù)調(diào)用方想要調(diào)用ServiceA,通過ServiceName去注冊中心查詢。然后注冊中心通過Si=F(ServiceName)查詢出服務(wù)的IP:Port列表。
3)服務(wù)調(diào)用方通過IP:Port列表去調(diào)用服務(wù)。
接下來,我們考慮一個問題:對于ServiceA來說,如果在注冊中心注冊的時候,只成功注冊兩個實例,那么是否應(yīng)該允許服務(wù)調(diào)用方訪問Service的兩個實例?在真實的微服務(wù)中,我們一定希望服務(wù)調(diào)用方能訪問ServiceA注冊成功的兩個實例,而不是必須等三個實例都注冊成功后才能被訪問。
此外,注冊中心不能因為自身的任何原因破壞服務(wù)之間本身的可連通性。
如圖2-26所示,我們將服務(wù)注冊中心集群中的三個實例分別部署到三個機房。每個機房各有兩個微服務(wù)。如果機房3的網(wǎng)絡(luò)出現(xiàn)問題,不能與機房1和機房2進行通信,結(jié)果會怎樣?

圖2-26 機房3的網(wǎng)絡(luò)出現(xiàn)問題
如果是強一致性的注冊中心(CAP模型中的CP模型),那么機房3中的實例3由于是少數(shù)節(jié)點,將會被終止運行。結(jié)果是,不僅ServerE和ServiceF不能訪問機房1和機房2的服務(wù),這兩個服務(wù)之間的訪問也會出問題。
那么,針對微服務(wù)的注冊中心,我們?nèi)绾芜x擇?有3個思路。
- 搭建應(yīng)用級注冊中心。
- 利用平臺側(cè)的注冊中心,如Kubernetes自帶的etcd。
- 平臺與應(yīng)用級相結(jié)合:例如將Eureka部署到一個Kubernetes集群上,集群內(nèi)的應(yīng)用注冊使用SVC。這種方式只適合Spring Cloud部署在單個Kubernetes集群的情況,此前這種方式被大量使用,但絕不是一個好的方法,詳見2.2.3節(jié)。
關(guān)于應(yīng)用級注冊中心,我們選取幾個主流的開源方案進行對比,如表2-12所示。整體而言,針對Java類應(yīng)用,Nacos作為應(yīng)用級注冊中心具有很大的優(yōu)勢。
表2-12 注冊中心方案對比

從表2-12可以看到,ZooKeeper、etcd、Consul都是CP模型。而etcd和ZooKeeper都不支持跨數(shù)據(jù)中心部署。因此,我們在選擇微服務(wù)的服務(wù)注冊中心時,可以選擇Nacos。
使用應(yīng)用級注冊中心的優(yōu)缺點如下。
- 優(yōu)點:服務(wù)注冊中心可以跨Kubernetes集群邊界,甚至可以跨基礎(chǔ)架構(gòu),如Kubernetes+虛擬機+物理機。
- 缺點:我們需要為每種編程語言提供服務(wù)發(fā)現(xiàn)庫(客戶端SDK)。
具體的,使用應(yīng)用級注冊中心,需要考慮Pod的服務(wù)注冊實現(xiàn)。
- 應(yīng)用級注冊中心,最好部署到容器云上,否則所有的注冊請求都需要經(jīng)過容器云的Ingress網(wǎng)關(guān),不僅增加了I/O路徑和瓶頸點,還增加了方案的復雜度。注冊中心放在虛擬機或者物理機即可。此外,我們還可以針對獨立的注冊中心做高可用或者容災(zāi)方案。
- 有些容器云如OpenShift(后面簡稱OCP)的SDN默認使用OVS,即overlay網(wǎng)絡(luò)。所以Pod在出OCP時,其IP地址是OCP集群宿主機的IP,但以宿主機的IP去外置注冊中心注冊顯然不靠譜。這時候可以考慮使用Multus-CNI,即給Pod掛一個數(shù)據(jù)中心的underlay的IP地址,再去注冊中心注冊。這樣做的好處是節(jié)約數(shù)據(jù)中心的業(yè)務(wù)IP,我們可以僅根據(jù)需要去注冊中心注冊的Pod配置Multus-CNI。這種方式的弊端是當宿主機上啟動的macvlan數(shù)量較多時,網(wǎng)卡性能會下降。
- OCP和Kubernetes也支持underlay的SDN方案,如Calico。使用這種方案,就無須使用Multus-CNI了,但缺點是Pod會消耗數(shù)據(jù)中心的真實IP。此外,underlay方案需要組網(wǎng)支持BGP。
接下來我們看看平臺側(cè)的服務(wù)注冊中心。
如果程序員決定用Kubernetes做服務(wù)發(fā)現(xiàn),實現(xiàn)不同服務(wù)之間的調(diào)用,那么就需要使用Kubernetes的Service名稱。Service名稱是可以固定的。
Kubernetes/OpenShift中Service有短名和長名兩種。以圖2-27為例,jws-app就是Service的短名,Service的長名的格式是<sevrvice_name>.<namespace>.svc.cluser.local,例如jws-app.web.svc.cluser.local。Service短名可以自動補充成長名,由OpenShift中的DNS實現(xiàn),具體將在后面介紹。

圖2-27 Service名稱
如果在兩個不同的Namespace中有兩個相同的Service短名,微服務(wù)調(diào)用是否會出現(xiàn)混亂?程序員的代碼里是否要寫Service全名?
首先,從容器云集群管理員的角度來看,對于所有項目,例如幾十個或者更多,會覺得在不同Namespace中存在相同的Service短名是可能的(比如Namespace A中有名為acat的Service,Namespace B中也有名為acat的Service)。但從程序員的角度來看,他只是容器云的使用者,只擁有自己負責的Namespace的管理權(quán),不能訪問其他Namespace。而且絕大多數(shù)情況下,同一個業(yè)務(wù)項目的微服務(wù)一般會運行在同一個Namespace中,如果使用短名稱(只寫Service名稱),則默認會自動補全成當前Namespace的FQDN。只有在跨Namespace調(diào)用的時候才必須寫全名。
所以,如果程序員寫的程序用到了Service名稱,那么,真正進行應(yīng)用的Pod之間的通信時,也必然會以Service名稱去查找。通過Service名稱解析為Service ClusterIP,然后經(jīng)過Kube-proxy(默認為iptables模式)的負載均衡設(shè)備最終選擇一個實際的Pod IP。找到Pod IP之后,接下來就會進行實際的數(shù)據(jù)交換,與Service并無關(guān)聯(lián)。
使用平臺注冊中心的優(yōu)缺點如下。
- 優(yōu)點:服務(wù)端和客戶端都不需要包含任何服務(wù)發(fā)現(xiàn)代碼,因此它可以跨語言及開發(fā)框架。
- 缺點:平臺注冊中心不能處理跨平臺的服務(wù)注冊和發(fā)現(xiàn)。
注冊中心的整體選擇思路主要從三個維度考量:應(yīng)用是否跨開發(fā)語言,微服務(wù)的邊界是否大于Kubernetes集群,以及是否限定應(yīng)用的Service名稱。
下面我們看5種情況。
1)應(yīng)用跨語言,微服務(wù)邊界不大于一個Kubernetes集群,不限定應(yīng)用的Service名稱:使用Kubernetes平臺的etcd。
2)應(yīng)用跨語言,微服務(wù)邊界大于一個Kubernetes集群,不限定應(yīng)用的Service名稱:使用應(yīng)用級注冊中心,而且每種語言都需要設(shè)置自己的注冊中心。
3)應(yīng)用不跨開發(fā)語言,微服務(wù)不大于一個Kubernetes邊界,不限定應(yīng)用的Service名稱:使用Kubernetes平臺的etcd。
4)應(yīng)用不跨開發(fā)語言,微服務(wù)大于一個Kubernetes邊界,不限定應(yīng)用的Service名稱:使用一個應(yīng)用級注冊中心。
5)限定應(yīng)用的Service名稱:使用應(yīng)用級注冊中心。
2.2.2 微服務(wù)配置中心的選擇
配置中心存儲的是獨立于應(yīng)用的只讀變量。除此之外,配置中心還需要有權(quán)限控制,并且可以進行多個不同集群的配置管理。
與注冊中心一樣,配置中心同樣有以下3個選擇思路。
- 搭建應(yīng)用級配置中心。
- 利用平臺側(cè)的配置中心,如Kubernetes自帶的ConfigMap。
- 平臺與應(yīng)用級相結(jié)合:例如將service-config部署到一個Kubernetes集群上,集群內(nèi)的應(yīng)用配置使用SVC。這種方式只適合Spring Cloud部署在單個Kubernetes集群的情況,此前這種方式被大量使用,但絕不是一個好的方法,詳見2.2.3節(jié)。
對于平臺側(cè)的配置中心,Kubernetes/OpenShift默認的配置管理是ConfigMap,即通過ConfigMap方式給應(yīng)用注冊配置。ConfigMap的訪問權(quán)限由Kubernetes/OpenShift自身的RBAC提供。
應(yīng)用級配置中心如表2-13所示。整體而言,針對Java類應(yīng)用,Apollo作為應(yīng)用級別配置中心具有很大的優(yōu)勢。
表2-13 注冊中心方案選擇

配置中心的整體選擇思路主要從兩個維度考量:應(yīng)用是否跨開發(fā)語言以及微服務(wù)的邊界是否大于Kubernetes集群。
下面我們看4種情況。
1)應(yīng)用跨語言,微服務(wù)邊界不大于一個Kubernetes集群:使用Kubernetes平臺的ConfigMap。
2)應(yīng)用跨語言,微服務(wù)邊界大于一個Kubernetes集群:使用應(yīng)用級配置中心,而且每種語言都需要設(shè)置自己的配置中心。
3)應(yīng)用不跨開發(fā)語言,微服務(wù)不大于一個Kubernetes邊界:使用Kubernetes平臺的ConfigMap。
4)應(yīng)用不跨開發(fā)語言,微服務(wù)大于一個Kubernetes邊界,不限定應(yīng)用的Service名稱:使用應(yīng)用級配置中心。
2.2.3 平臺與應(yīng)用級相結(jié)合的注冊和配置中心
在本節(jié)中,我們介紹平臺與應(yīng)用級相結(jié)合的注冊和配置中心的實現(xiàn)。需要指出的是,這種方式只適合Spring Cloud部署在單個Kubernetes集群的情況,此前這種方式被大量使用,但絕不是一個好的方法。筆者之所以展開介紹,是想讓讀者直觀了解配置中心和注冊中心的實際效果。
以圖2-28為例,我們將整套微服務(wù)部署到一個Namespace中,從圖中可以看到配置中心(service-config)和注冊中心(service-registry)的SVC和端口號(https://github.com/davidsajare/spring-cloud-on-openshift.git)。

圖2-28 配置中心和注冊中心
微服務(wù)部署完后,業(yè)務(wù)微服務(wù)在指定配置中心的地址是service-config:8888。而service-config到IP地址的解析,由Kubernetes中的CoreDNS完成。例如,我們查看card-service部署中的環(huán)境變量,configure server指向http://service-config:8888/,如圖2-29所示。

圖2-29 configure server的配置
在微服務(wù)注冊時,先指定訪問配置中心(service-config:8888),然后配置中心(service-config)的Profile定義了注冊中心的地址和端口號(service-registry:8761),以便微服務(wù)能夠在注冊中心進行注冊,效果如圖2-30所示。

圖2-30 微服務(wù)訪問配置中心和注冊中心
我們查看配置中心(service-config:8888)名為openshift的配置文件,如圖2-31所示。

圖2-31 名為openshift的配置文件
圖2-31所示的配置指定了注冊中心的地址和端口號(service-registry:8761),即Eureka的地址和端口號:
"eureka.instance.instance-id": "${POD_NAME:${spring.application.name}}:${server.port}", "eureka.instance.hostname": "${HOSTNAME:${spring.application.name}}",
查看service-registry的環(huán)境變量,如圖2-32所示。

圖2-32 service-registry的環(huán)境變量
上面第一段代碼,帶入變量后,顯示注冊中心的兩個Pod名和端口號:
#第一個注冊中心Pod "eureka.instance.instance-id":service-registry-0:service-registry:8761 "eureka.instance.hostname": service-registry-0.service-registry:service-registry #第二個注冊中心Pod "eureka.instance.instance-id":service-registry-1:service-registry:8761 "eureka.instance.hostname": service-registry-1.service-registry:service-registry
接下來,我們看一個微服務(wù)gateway-3-gjgfz的啟動過程,觀察它如何完成服務(wù)注冊。Pod啟動后,讀取了關(guān)于配置中心的環(huán)境變量:
Starting the Java application using /opt/jboss/container/java/run/run-java.sh ... INFO exec java -Dspring.profiles.active=openshift -Dspring.cloud.config.uri=http://service-config:8888/ -javaagent:/usr/share/java/prometheus-jmx-exporter/jmx_prometheus_javaagent.jar= 9779:/opt/jboss/container/prometheus/etc/jmx-exporter-config.yaml -XX:+Us eParallelOldGC -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=20 -XX:GCTimeRatio= 4 -XX:AdaptiveSizePolicyWeight=90 -XX:MaxMetaspace Size=100m -XX:+ExitOnOutOfMemoryError -cp "." -jar /deployments/ gateway-0.0.1-SNAPSHOT.jar
然后gateway-3-gjgfz微服務(wù)很快獲取到配置中心中名為openshift為的配置文件:
2021-03-13 01:58:44.020 INFO 1 --- [main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at:http://service-config:8888/ 2021-03-13 01:58:44.153 INFO 1 --- [main] c.c.c.ConfigServicePropertySourceLocator : Located environment: name=gateway, profiles=[openshift], label=null, version=null,
接下來完成服務(wù)注冊:
2021-03-13 01:58:46.835 INFO 1 --- [ main] DiscoveryClientOptionalArgsConfiguration : Eureka HTTP Client uses RestTemplate. 2021-03-13 01:58:46.969 INFO 1 --- [ main] o.s.c.n.eureka.InstanceInfoFactory : Setting initial instance status as: STARTING 2021-03-13 01:58:47.042 INFO 1 --- [ main] com.netflix.discovery.DiscoveryClient : Initializing Eureka in region us-east-1 2021-03-13 01:58:47.049 INFO 1 --- [ main] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration 2021-03-13 01:58:47.073 INFO 1 --- [ main] com.netflix.discovery.DiscoveryClient : Disable delta property : false 2021-03-13 01:58:47.073 INFO 1 --- [ main] com.netflix.discovery.DiscoveryClient : Single vip registry refresh property : null 2021-03-13 01:58:47.073 INFO 1 --- [ main] com.netflix.discovery.DiscoveryClient : Force full registry fetch : false 2021-03-13 01:58:47.073 INFO 1 --- [ main] com.netflix.discovery.DiscoveryClient : Application is null : false 2021-03-13 01:58:47.073 INFO 1 --- [ main] com.netflix.discovery.DiscoveryClient : Registered Applications size is zero : true 2021-03-13 01:58:47.073 INFO 1 --- [ main] com.netflix.discovery.DiscoveryClient : Application version is -1: true 2021-03-13 01:58:47.073 INFO 1 --- [ main] com.netflix.discovery.DiscoveryClient : Getting all instance registry info from the eureka server 2021-03-13 01:58:47.146 INFO 1 --- [ main] com.netflix.discovery.DiscoveryClient : The response status is 200 2021-03-13 01:58:47.149 INFO 1 --- [ main] com.netflix.discovery.DiscoveryClient : Starting heartbeat executor: renew interval is: 30 2021-03-13 01:58:47.152 INFO 1 --- [ main] c.n.discovery.InstanceInfoReplicator : InstanceInfoReplicator onDemand update allowed rate per min is 4 2021-03-13 01:58:47.157 INFO 1 --- [ main] com.netflix.discovery.DiscoveryClient : Discovery Client initialized at timestamp 1615600727156 with initial instances count: 7 2021-03-13 01:58:47.158 INFO 1 --- [ main] o.s.c.n.e.s.EurekaServiceRegistry : Registering application GATEWAY with eureka with status UP 2021-03-13 01:58:47.159 INFO 1 --- [ main] com.netflix.discovery.DiscoveryClient : Saw local status change event StatusChangeEvent [timestamp=1615600727159, current=UP, previous=STARTING] 2021-03-13 01:58:47.162 INFO 1 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_GATEWAY/gateway-3- gjgfz:8080: registering service... 2021-03-13 01:58:47.216 INFO 1 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_GATEWAY/gateway-3- gjgfz:8080 - registration status: 204 2021-03-13 01:58:47.312 INFO 1 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port 8080 2021-03-13 01:58:47.313 INFO 1 --- [ main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8080 2021-03-13 01:58:47.332 INFO 1 --- [ main] com.demo.gateway.Application : Started Application in 4.556 seconds (JVM running for 5.166) 2021-03-13 02:03:47.076 INFO 1 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration 2021-03-13 02:08:47.077 INFO 1 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration
微服務(wù)注冊成功后,我們到注冊中心查看注冊成功的應(yīng)用,如圖2-33所示。

圖2-33 在注冊中心查看注冊成功的應(yīng)用
總結(jié)如下:
1)在上述Spring Cloud代碼中,業(yè)務(wù)微服務(wù)訪問配置中心,用的是Kubernetes ServiveName:Port,然后讀取配置中心的配置,去注冊中心注冊;
2)微服務(wù)在注冊中心注冊成功后,記錄端點的信息是Pod Hostname:Port。
注意,Kubernetes和Spring Cloud完全1:1對應(yīng)是比較理想的情況,這時候微服務(wù)之間的互訪、微服務(wù)訪問配置中心和注冊中心,都是在內(nèi)部完成的。
- Android開發(fā)進階:從小工到專家
- 中臺架構(gòu)與實現(xiàn):基于DDD和微服務(wù)
- 數(shù)字化運維:IT運維架構(gòu)的數(shù)字化轉(zhuǎn)型
- 企業(yè)IT架構(gòu)轉(zhuǎn)型之道:阿里巴巴中臺戰(zhàn)略思想與架構(gòu)實戰(zhàn)
- 微服務(wù)治理:體系、架構(gòu)及實踐
- 數(shù)據(jù)科學家訪談錄
- 微信公眾平臺搭建與開發(fā)揭秘(第2版)
- IT項目管理理論與方法
- Axure RP8 網(wǎng)站和APP原型制作 從入門到精通
- 這才是用戶體驗設(shè)計:人人都能看懂的產(chǎn)品設(shè)計書
- IT能力與企業(yè)信息化
- 數(shù)據(jù)中臺:讓數(shù)據(jù)用起來(第2版)
- IT服務(wù)管理及CMMI-SVC實施
- IT與項目管理軟件應(yīng)用
- 云計算解碼