k8s部署eureka集群

背景

对于一般的后端微服务来说,在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中


253A933C7535743E87D5D3F58E3A88CC.png

原因是我们默认是通过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集群。

后面是一些配置文件:
整体文件配置:


096DBDAA639ACBB9F55B40545C56928A.png

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上

你可能感兴趣的:(k8s部署eureka集群)