背景
對于一般的后端微服務(wù)來說,在k8s中同時起多個相同的服務(wù)來做負載均衡,只需要簡單的修改deployment的replicas,增加pod數(shù)量,然后通過對外暴露一個service來代理這些pod。
而對于eureka來說,要實現(xiàn)eureka的高可用,那就不是修改replicas這么方便了。由于部署的多個eureka之間需要將自己注冊到彼此,因此要做一些特殊改動。
主要是用到了StatefulSet和headless service這兩個k8s對象
StatefulSet、Headless Service簡介:
StatefulSet
StatefulSet是為了解決有狀態(tài)服務(wù)的問題(對應(yīng)Deployments和ReplicaSets是為無狀態(tài)服務(wù)而設(shè)計),其應(yīng)用場景包括
穩(wěn)定的持久化存儲,即Pod重新調(diào)度后還是能訪問到相同的持久化數(shù)據(jù),基于PVC來實現(xiàn)
穩(wěn)定的網(wǎng)絡(luò)標志,即Pod重新調(diào)度后其PodName和HostName不變,基于Headless Service(即沒有Cluster IP的Service)來實現(xiàn)
有序部署,有序擴展,即Pod是有順序的,在部署或者擴展的時候要依據(jù)定義的順序依次依次進行(即從0到N-1,在下一個Pod運行之前所有之前的Pod必須都是Running和Ready狀態(tài)),基于init containers來實現(xiàn)
有序收縮,有序刪除(即從N-1到0)
StatefulSet中每個Pod的DNS格式為
statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local
serviceName為Headless Service的名字
0..N-1為Pod所在的序號,從0開始到N-1
statefulSetName為StatefulSet的名字
namespace為服務(wù)所在的namespace,Headless Service和StatefulSet必須在相同的namespace
cluster.local為Cluster Domain
Headless Service
Headless Service 和普通service的一個顯著的區(qū)別是,Headless Service的對應(yīng)的每一個Endpoints,即每一個Pod,都會有對應(yīng)的DNS域名
例如:我們可以用過這種域名來訪問某個具體的pod:
statefulSetName-0.serviceName.namespace.svc.cluster.local
在實際使用中,將service的clusterIP設(shè)置成None,就表明這個service是一個Headless Service。
StatefulSet和Headless Service的結(jié)合
通過 StatefulSet,我們得到了一些列pod,每個pod的name為statefulSetName-{0..N-1},
加入我們創(chuàng)建了一個名稱叫eureka的StatefulSet,并且設(shè)置replicas =3,那么部署到k8s后,k8s會為我們生成三個名稱依次為eureka-0,eureka-1,eureka-2的pod。
通過Headless Service,我們可以通過pod名稱來訪問某個pod,
例如,我們在namespace=test的命名空間下創(chuàng)建了一個名稱為register-server的service,并且關(guān)聯(lián)了之前StatefulSet創(chuàng)建的pod,那么我們可以在集群內(nèi)任意地方
通過eureka-0.register-server.test.svc.cluster.local這個域名訪問到eureka-0這個pod。
搭建:
有了前面的基礎(chǔ),現(xiàn)在部署eureka集群的方式就逐漸清晰了。
首先明確部署eureka的關(guān)鍵點:需要讓每個eureka注冊到另外的eureka上。
也就是eureka.client.serviceUrl.defaultZone這個配置,是一組eureka的地址。
通過StatefulSet,我們可以明確知道生成的每個eureka的名稱,
通過Headless Service,我們又可以訪問到每個eureka,所以eureka.client.serviceUrl.defaultZone的值就是
"http://eureka-0.register-server:8000/eureka/,http://eureka-1.register-server:8000/eureka/,http://eureka-2.register-server:8000/eureka/"
由于這三個pod在同一個命名空間內(nèi),可以省略.namespace.svc.cluster.local
有個這個配置,那么我們部署StatefulSet,和Headless Service
那么我們能基本能得到一個可用的eureka集群
除了會有以下問題:
紅框中的可用副本(available-replicas)會出現(xiàn)在不可用unavailable-replicas中
原因是我們默認是通過ip的方式來注冊eureka(eureka.instance.prefer-ip-address配置默認為true),但是eureka的注冊地址又是域名的形式,兩者不一致。
要解決這個問題,還需做一些額外的配置。
1.在application.yaml中,將eureka.instance.prefer-ip-address設(shè)置成false。
eureka:
instance:
prefer-ip-address: false
2.StatefulSet.yaml中,增加環(huán)境變量配置,將pod的名稱綁定到環(huán)境變量
env:
- name: MY_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
3.在application.yaml中指定eureka的 hostname,其中MY_POD_NAME取到了第二部中綁定的當(dāng)前pod名稱
eureka:
instance:
hostname: ${MY_POD_NAME}.register-server
如上配置后,便可以得到一個eureka集群。
后面是一些配置文件:
整體文件配置:
service.yaml
apiVersion: v1
kind: Service
metadata:
name: register-server
labels:
service: register-server
spec:
clusterIP: None
type: ClusterIP
ports:
- port: 8000
targetPort: http
protocol: TCP
name: http
selector:
service: register-server
StatefulSet.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: register-server
labels:
service: register-server
spec:
replicas: 3
serviceName: register-server
selector:
matchLabels:
service: register-server
template:
metadata:
labels:
service: register-server
annotations:
service: register-server
spec:
containers:
- name: register-server
image: codewjy/eureka:0.1.0
imagePullPolicy: Always
env:
- name: MY_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
ports:
- name: http
containerPort: 8000
protocol: TCP
readinessProbe:
httpGet:
path: /actuator/health
port: 8001
scheme: HTTP
failureThreshold: 3
initialDelaySeconds: 60
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
resources:
limits:
# cpu: 100m
memory: 1.7Gi
requests:
# cpu: 100m
memory: 1.2Gi
volumeMounts:
- mountPath: /Charts
name: data
volumes:
- name: data
podManagementPolicy: "Parallel"
application.yml
eureka:
instance:
leaseRenewalIntervalInSeconds: 10
leaseExpirationDurationInSeconds: 30
metadata-map:
VERSION: 1.0.0
hostname: ${MY_POD_NAME}.register-server #設(shè)置eureka hostname
prefer-ip-address: false #不使用ip注冊,因為eureka相互注冊的工程中,使用的服務(wù)名,例如register-server-0.register-server,如果使用ip注冊,會導(dǎo)致eureka認為其他副本不可用,即eureka服務(wù)都會出現(xiàn)在unavailable-replicas中,而不是available-replicas中
client:
# 檢索服務(wù)選項,注冊中心不需要檢索服務(wù)
fetch-registry: ${EUREKA_CLIENT_FETCH_REGISTRY:true}
# 注冊中心將自己作為客戶端來嘗試注冊自己,注冊中心集群環(huán)境下需開啟此配置
register-with-eureka: ${EUREKA_CLIENT_REGISTER_WITH_EUREKA:true}
serviceUrl:
defaultZone: ${EUREKA_DEFAULT_ZONE:http://register-server-0.register-server:8000/eureka/,http://register-server-1.register-server:8000/eureka/,http://register-server-2.register-server:8000/eureka/} #這里在部署的時候會使用環(huán)境變量替換 EUREKA_DEFAULT_ZONE值
registryFetchIntervalSeconds: 10
disable-delta: true
server:
evictionIntervalTimerInMs: 4000
enable-self-preservation: ${EUREKA_SERVER_ENABLE_SELF_PRESERVATION:false}
bootstrap.yml
spring:
application:
name: register-server
server:
port: 8000
management:
server:
port: 8001
服務(wù)注冊:
將一般的微服務(wù)注冊到eureka集群中,可以通過eureka的service來訪問eureka,即:將eureka.client.serviceUrl.defaultZone設(shè)置成register-server.test.svc.cluster.local,使用了k8s的service負載均衡,將服務(wù)注冊到任意一個活著的eureka上,然后eureka集群內(nèi)部會做同步,最終注冊到eureka集群內(nèi)部所有eureka上