官术网_书友最值得收藏!

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的三個實例注冊到注冊中心。

054-1

圖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é)果會怎樣?

055-1

圖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 注冊中心方案對比

056-1

從表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),具體將在后面介紹。

057-1

圖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 注冊中心方案選擇

058-1

配置中心的整體選擇思路主要從兩個維度考量:應(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)。

059-1

圖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所示。

060-1

圖2-29 configure server的配置

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

060-2

圖2-30 微服務(wù)訪問配置中心和注冊中心

我們查看配置中心(service-config:8888)名為openshift的配置文件,如圖2-31所示。

060-3

圖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所示。

061-1

圖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所示。

063-1

圖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)部完成的。

主站蜘蛛池模板: 闸北区| 广丰县| 东阿县| 望谟县| 东海县| 江口县| 密山市| 虹口区| 丹江口市| 长春市| 余干县| 镇平县| 德庆县| 老河口市| 额尔古纳市| 芜湖县| 临猗县| 栾城县| 鹿泉市| 扶风县| 定日县| 吉安县| 海盐县| 大石桥市| 武川县| 安岳县| 扬州市| 云南省| 瑞安市| 苍山县| 大兴区| 北京市| 惠来县| 浦城县| 泽库县| 古丈县| 瑞安市| 梧州市| 达孜县| 辉南县| 鲁甸县|