本文采用 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 命令进行查看,比如:
- EUREKA_INSTANCE_LIST 有两种写法:
- 方式1:采用 StatefulSet 创建的 pod 的短域名(该方式本人偶尔遇到过个别 pod 反复处于创建状态的异常情况)
http://eureka-0.eureka:8761/eureka/,http://eureka-1.eureka:8761/eureka/,http://eureka-2.eureka:8761/eureka/
- 方式2:采用 StatefulSet 创建的 pod 的长域名
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 界面了。
3. 关于 unavailable-replicas
配置错误时,会出现下图中的 unavailable-replicas:
如果出现上图情况,请检查以下几点:
- 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>