背景
对于一般的后端微服务来说,在k8s中同时起多个相同的服务来做负载均衡,只需要简单的修改deployment的replicas,增加pod数量,然后通过对外暴露一个service来代理这些pod。
而对于eureka来说,要实现eureka的高可用,那就不是修改replicas这么方便了。由于部署的多个eureka之间需要将自己注册到彼此,因此要做一些特殊改动。
主要是用到了StatefulSet和headless service这两个k8s对象
StatefulSet、Headless Service简介:
StatefulSet
StatefulSet是为了解决有状态服务的问题(对应Deployments和ReplicaSets是为无状态服务而设计),其应用场景包括
稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现
稳定的网络标志,即Pod重新调度后其PodName和HostName不变,基于Headless Service(即没有Cluster IP的Service)来实现
有序部署,有序扩展,即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次依次进行(即从0到N-1,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态),基于init containers来实现
有序收缩,有序删除(即从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为服务所在的namespace,Headless Service和StatefulSet必须在相同的namespace
cluster.local为Cluster Domain
Headless Service
Headless Service 和普通service的一个显著的区别是,Headless Service的对应的每一个Endpoints,即每一个Pod,都会有对应的DNS域名
例如:我们可以用过这种域名来访问某个具体的pod:
statefulSetName-0.serviceName.namespace.svc.cluster.local
在实际使用中,将service的clusterIP设置成None,就表明这个service是一个Headless Service。
StatefulSet和Headless Service的结合
通过 StatefulSet,我们得到了一些列pod,每个pod的name为statefulSetName-{0..N-1},
加入我们创建了一个名称叫eureka的StatefulSet,并且设置replicas =3,那么部署到k8s后,k8s会为我们生成三个名称依次为eureka-0,eureka-1,eureka-2的pod。
通过Headless Service,我们可以通过pod名称来访问某个pod,
例如,我们在namespace=test的命名空间下创建了一个名称为register-server的service,并且关联了之前StatefulSet创建的pod,那么我们可以在集群内任意地方
通过eureka-0.register-server.test.svc.cluster.local这个域名访问到eureka-0这个pod。
搭建:
有了前面的基础,现在部署eureka集群的方式就逐渐清晰了。
首先明确部署eureka的关键点:需要让每个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在同一个命名空间内,可以省略.namespace.svc.cluster.local
有个这个配置,那么我们部署StatefulSet,和Headless Service
那么我们能基本能得到一个可用的eureka集群
除了会有以下问题:
红框中的可用副本(available-replicas)会出现在不可用unavailable-replicas中
原因是我们默认是通过ip的方式来注册eureka(eureka.instance.prefer-ip-address配置默认为true),但是eureka的注册地址又是域名的形式,两者不一致。
要解决这个问题,还需做一些额外的配置。
1.在application.yaml中,将eureka.instance.prefer-ip-address设置成false。
eureka:
instance:
prefer-ip-address: false
2.StatefulSet.yaml中,增加环境变量配置,将pod的名称绑定到环境变量
env:
- name: MY_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
3.在application.yaml中指定eureka的 hostname,其中MY_POD_NAME取到了第二部中绑定的当前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 #设置eureka hostname
prefer-ip-address: false #不使用ip注册,因为eureka相互注册的工程中,使用的服务名,例如register-server-0.register-server,如果使用ip注册,会导致eureka认为其他副本不可用,即eureka服务都会出现在unavailable-replicas中,而不是available-replicas中
client:
# 检索服务选项,注册中心不需要检索服务
fetch-registry: ${EUREKA_CLIENT_FETCH_REGISTRY:true}
# 注册中心将自己作为客户端来尝试注册自己,注册中心集群环境下需开启此配置
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/} #这里在部署的时候会使用环境变量替换 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
服务注册:
将一般的微服务注册到eureka集群中,可以通过eureka的service来访问eureka,即:将eureka.client.serviceUrl.defaultZone设置成register-server.test.svc.cluster.local,使用了k8s的service负载均衡,将服务注册到任意一个活着的eureka上,然后eureka集群内部会做同步,最终注册到eureka集群内部所有eureka上