Eureka 在 K8S 的上高可用部署

2021-06-07 10:11:26 浏览数 (1)

本文采用 StatefulSet 在 kubernetes 环境下部署 Eureka 高可用集群的方式,并在腾讯云 TKE 上成功部署。

Kubernetes 下 Eureka 的高可用部署,包含以下两点:

  • Eureka Server 集群部署
  • Pod 健康检查

Kubelets 通过调用以下三种类型的 Pod中的 Handler 进行健康检查:

  • ExecAction: 在容器中执行特定的命令,命令退出返回0表示成功
  • TCPSocketAction: 根据容器IP地址及特定的端口进行TCP检查,端口开放表示成功
  • HTTPGetAction: 根据容器IP、端口及访问路径发起一次HTTP请求,如果返回码在200到400之间表示成功

每种检查动作都可能有三种返回状态:

  • Success: 表示通过了健康检查
  • Failure: 表示没有通过健康检查
  • Unknown: 表示检查动作失败

liveness可以用来检查容器内应用的存活的情况来,如果检查失败会杀掉容器进程,是否重启容器则取决于Pod的重启策略。

1. Spring 工程

1.1 Maven 配置

完整的 maven pom 见附录 qcbm-registry maven pom。

1.2 健康检查 Controller

如下,新建个专门用于健康检查的 Controller。

代码语言:txt复制
@RestController
@RequestMapping(path = "/")
public class HealthCheck {

    @GetMapping(path = "/healthcheck", produces = MediaType.TEXT_PLAIN_VALUE)
    public String healthz(){
        return "success";
    }
}

1.3 spring 配置

springboot 配置 yaml 如下:

代码语言:txt复制
spring:
  application:
    name: EUREKA-SERVER

server:
  port: 8761

eureka:
  instance:
    prefer-ip-address: false
    hostname: ${EUREKA_INSTANCE_HOST_NAME}
  client:
    registerWithEureka: true
    fetchRegistry: true
    service-url:
      defaultZone: ${EUREKA_INSTANCE_LIST}
  server:
    enable-self-preservation: true
    response-cache-auto-expiration-in-seconds: 180
    response-cache-update-interval-ms: 10000
    eviction-interval-timer-in-ms: 10000

上面配置中的两个环境变量:EUREKA_INSTANCE_HOST_NAME 和 EUREKA_INSTANCE_LIST 可在下一节的 K8S 部署 yaml 中进行配置。

2. K8S 部署 yaml

2.1 Headless Service

在部署一个 Statefulset 之前,需要创建一个用于在有状态的 pod 之间提供网络标识的 headless Service。Eureka 的 headless Service 部署 yaml 如下:

代码语言:txt复制
kind: Service
apiVersion: v1
metadata:
  name: eureka
  namespace: qcbm
spec:
  clusterIP: None
  ports:
    - name: http
      port: 8761
      protocol: TCP
      targetPort: 8761
  selector:
    app: eureka

上面指定了 cluster工P 为 None, 这就标记了它是一个 headless Service,它使得 pod 之间可以彼此发现。

2.2 StatefulSet

下面是 StatefulSet Eureka 的部署 yaml:

代码语言:txt复制
apiVersion: apps/v1
kind: StatefulSet
metadata:
  namespace: qcbm
  name: eureka
  labels:
    app: eureka
spec:
  serviceName: "eureka"
  replicas: 3
  selector:
    matchLabels:
      app: eureka
  template:
    metadata:
      labels:
        app: eureka
    spec:
      terminationGracePeriodSeconds: 10
      containers:
        - name: eureka
          image: ccr.ccs.tencentyun.com/qcbm/eureka:latest
          ports:
            - containerPort: 8761
          env:
            - name: EUREKA_INSTANCE_HOST_NAME
              value: ${HOSTNAME}.eureka
            - name: EUREKA_INSTANCE_LIST
              value: "http://eureka-0.eureka.qcbm.svc.cluster.local:8761/eureka/,http://eureka-1.eureka.qcbm.svc.cluster.local:8761/eureka/,http://eureka-2.eureka.qcbm.svc.cluster.local:8761/eureka/"
          livenessProbe:
          # 健康检查
          livenessProbe:
            httpGet:
              path: /healthcheck
              port: 8761
            initialDelaySeconds: 30
            timeoutSeconds: 10

几点说明:

  • EUREKA_INSTANCE_HOST_NAME

StatefulSet 创建的 pod name 是按 metadata.name-数字序列 命名的,这里的数字序列为从 0 到 replicas 指定的数字减1。而 EUREKA_INSTANCE_HOST_NAME 需要填写 pod 的短域名,因而这里先通过环境变量 HOSTNAME 和 headless service 的名称组成。

环境变量 HOSTNAME 是由 kubernetes 创建容器时添加的,可以使用 kubectl 命令进行查看,比如:

hostname.pnghostname.png
  • EUREKA_INSTANCE_LIST 有两种写法:
  • 方式1:采用 StatefulSet 创建的 pod 的短域名(该方式本人偶尔遇到过个别 pod 反复处于创建状态的异常情况)
代码语言:txt复制
http://eureka-0.eureka:8761/eureka/,http://eureka-1.eureka:8761/eureka/,http://eureka-2.eureka:8761/eureka/
  • 方式2:采用 StatefulSet 创建的 pod 的长域名
代码语言:txt复制
http://eureka-0.eureka.qcbm.svc.cluster.local:8761/eureka/,http://eureka-1.eureka.qcbm.svc.cluster.local:8761/eureka/,http://eureka-2.eureka.qcbm.svc.cluster.local:8761/eureka/

2.3 Eureka 界面的展示

上面介绍的部署完成后,是无法通过浏览器打开 Eureka 界面的,因为使用了 headless service。如要查看,还需要再建个 service 和 ingress。

代码语言:txt复制
---
# 重新部署个 Service
kind: Service
apiVersion: v1
metadata:
  name: registry-lb
  namespace: qcbm
  annotations:
    # 这里采用腾讯云子网 ID 的方式,生产的 service 不能通过公网访问
    service.kubernetes.io/qcloud-loadbalancer-internal-subnetid: subnet-8vfb3fo2
spec:
  type: LoadBalancer
  ports:
    - name: http
      port: 8761
      protocol: TCP
      targetPort: 8761
  selector:
    app: eureka
    
---
# 部署 qcbm-ingress
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: qcbm-ingress
  namespace: qcbm
  annotations:
    ingress.cloud.tencent.com/direct-access: "false"
    kubernetes.io/ingress.class: qcloud
    kubernetes.io/ingress.extensiveParameters: '{"AddressIPVersion":"IPV4"}'
    kubernetes.io/ingress.http-rules: '[{"host":"qcbm.com","path":"/","backend":{"serviceName":"registry-lb","servicePort":"8761"}}]'
spec:
  rules:
    - host: qcbm.com
      http:
        paths:
          - path: /
            backend:
              serviceName: registry-lb
              servicePort: 8761    

部署完成后,找到 qcbm-ingress 的公网 IP 就能看下图所示的 Eureka 界面了。

eureka-ok.pngeureka-ok.png

3. 关于 unavailable-replicas

配置错误时,会出现下图中的 unavailable-replicas:

unavailable-replicas.pngunavailable-replicas.png

如果出现上图情况,请检查以下几点:

  • eureka.instance.hostname 需要配置为运行 eureka 的 pod 短域名。
  • eureka.instance.prefer-ip-address 是否为 false。

容器部署方式下,POD 的 IP 是随机的,需要使用域名,因此要将该参数置为 false。

  • eureka.client.registerWithEureka 是否为 true。

集群部署下,一个 eureka 实例需要向集群中其它实例进行注册,以便达到实例之间的互相发现,因而需要将该参数设置为 true。

  • eureka.client.fetchRegistry 是否为 true。

打开此参数,是为了eureka 实例之间进行数据同步。比如服务A注册到了 eureka 实例 1 上,服务B注册到了 eureka 实例 2 上,打开此参数后,实例 1 和实例 2之间会同步数据,从而在每个实例上都有服务A和服务B的注册信息。

4. docker-maven-plugin 简介

spotify 出品的 docker-maven-plugin 将常用的 docker 命令集成到了 maven project 的生命周期中,大量的简化了繁琐的命令行操作。这里简单介绍下其使用,详细的使用说明,请参考:https://github.com/spotify/docker-maven-plugin。

首先,本人的配置的如下:

代码语言:txt复制
<plugin>
    <groupId>com.spotify</groupId>
    <artifactId>docker-maven-plugin</artifactId>
    <version>1.2.2</version>
    <configuration>
        <dockerDirectory>./</dockerDirectory>
        <imageName>ccr.ccs.tencentyun.com/qcbm/eureka</imageName>
        <imageTags>
            <imageTag>latest</imageTag>
        </imageTags>
    </configuration>
</plugin>
  • dockerDirectory 指定 Dockerfile 所在的目录,由于本人将其放置于工程目录下,因而使用 ./
  • imageName 镜像名称,可以是 docker registry 的完整名称。本人使用的是腾讯云 TCR 个人版仓库,且使用了命名空间 qcbm,因而填的是:ccr.ccs.tencentyun.com/qcbm/eureka
  • imageTag 顾名思义,就是镜像的 tag 了。

配置好以后,在终端使用下面命令,就会构建 docker 镜像,并推送到远程仓库中。

代码语言:txt复制
mvn docker:build -DpushImage

附录:qcbm-registry 的 maven pom

代码语言:txt复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
        <relativePath/>
    </parent>

    <groupId>demo.tcloud.triblewood.qcbm</groupId>
    <artifactId>qcbm-registry</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    
    <name>qcbm-registry</name>
    <description>QCBM service registry</description>

    <packaging>jar</packaging>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR11</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
    </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>demo.tcloud.triblewood.qcbm.registry.RegistryApplication</mainClass>
                    <finalName>${project.name}</finalName>
                </configuration>
            </plugin>
            <plugin>
                <groupId>com.spotify</groupId>
                <artifactId>docker-maven-plugin</artifactId>
                <configuration>
                    <dockerDirectory>./</dockerDirectory>
                    <imageName>ccr.ccs.tencentyun.com/qcbm/eureka</imageName>
                    <imageTags>
                        <imageTag>latest</imageTag>
                    </imageTags>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

0 人点赞