在kubernetes上运行eureka集群

前言

eureka 在springcloud体系中,主要实现一个注册中心的角色,所有的服务都将注册到eureke中,调用用者将从这里通过名称获取到对应的服务的IP集合列表。
作为一个分布式系统eureka在CAP中,选中了AP,优先保证可用性,放弃了强一致性,这样设计也符合注册中心的需要。
当发生网络分区故障(15分钟内超过85%的节点都没有正常的心跳),eureka会启用注册保护,即维持住当前心跳虽然失败的服务列表,并不进行删除。并能正常的提供查询服务(虽然不是最新)和注册服务(即不会同步到其他eureka节点),当网络恢复时,eureka会正确的同步信息,和恢复删除过期节点信息。

eureka的集群设计方式,采用了对等注册的形式完成。比如我们有三个节点的eureka集群,架构图如下

实现服务

简单的讲了下eureka的概念,下面我们进入编码模式。
当前最新的spring boot版本为2.1.7 我们将他作为我们的parent。主要是方便三方jar的版本管理和maven插件的使用。

1
2
3
4
5
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
</parent>

加入spring cloud的pom依赖,方便spring cloud 提供的相关jar的版本管理。这里需要将此依赖放入dependencyManagement

1
2
3
4
5
6
7
8
9
10
11
<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>

有了这两个方便的pom之后。我们可以添加上eureka server的依赖。这里我将tomcat直接排除了,采用比较好用的undertow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

需要的依赖我们导入完毕,接下来我们需要配置插件,一个插件是spring-boot-maven-plugin,他会方便我们打出fatjar,还有一个插件是docker-maven-plugin方便直接构建出我们需要的镜像并推送到远程仓库。
你可以从我的这片博文如何构建SpringBoot的Docker镜像中了解更多.

那么加上插件之后完成的pom如下

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
<?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.1.7.RELEASE</version>
</parent>

<groupId>io.qingmu</groupId>
<artifactId>eureka</artifactId>
<version>0.0.1-SNAPSHOT</version>

<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</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.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<configuration>
<imageName>
freemanliu/eureka:v1.0.0
</imageName>
<registryUrl></registryUrl>
<workdir>/work</workdir>
<rm>true</rm>
<env>
<TZ>Asia/Shanghai</TZ>
<JAVA_OPTS>
-XX:+UnlockExperimentalVMOptions \
-XX:+UseCGroupMemoryLimitForHeap \
-XX:MaxRAMFraction=2 \
-XX:CICompilerCount=8 \
-XX:ActiveProcessorCount=8 \
-XX:+UseG1GC \
-XX:+AggressiveOpts \
-XX:+UseFastAccessorMethods \
-XX:+UseStringDeduplication \
-XX:+UseCompressedOops \
-XX:+OptimizeStringConcat
</JAVA_OPTS>
</env>
<baseImage>freemanliu/openjre:8.212</baseImage>
<cmd>
/sbin/tini java ${JAVA_OPTS} -jar ${project.build.finalName}.jar
</cmd>
<!--是否推送image-->
<pushImage>true</pushImage>
<resources>
<resource>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
</resource>
</resources>
<serverId>docker-hub</serverId>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

EurekaServiceApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
package io.qingmu;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServiceApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServiceApplication.class, args);
}
}

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server:
port: ${PORT:8761}
spring:
application:
name: eureka-service
management:
endpoints:
web:
exposure:
include: "*"
logging:
level:
com:
netflix:
eureka:
registry: error

bootstrap.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
eureka:
server:
# 续期时间,即扫描失效服务的间隔时间(缺省为60*1000ms)
eviction-interval-timer-in-ms: 5000
client:
# eureka client间隔多久去拉取服务注册信息 默认30s
registry-fetch-interval-seconds: 30
fetch-registry: false
serviceUrl:
defaultZone: ${EUREKA_SERVER:http://127.0.0.1:${server.port}/eureka/}
instance:
# 心跳间隔时间,即发送一次心跳之后,多久在发起下一次(缺省为30s)
lease-renewal-interval-in-seconds: 10
# 在收到一次心跳之后,等待下一次心跳的空档时间,大于心跳间隔即可,即服务续约到期时间(缺省为90s)
lease-expiration-duration-in-seconds: 15
instance-id: ${EUREKA_INSTANCE_HOSTNAME:${spring.application.name}}:${server.port}@${random.long(1000000,9999999)}
hostname: ${EUREKA_INSTANCE_HOSTNAME:${spring.application.name}}

完成的项目结构如下:

eureka-project

查看UI

运行application类,在浏览器中输入http://127.0.0.1:8761/ 你将看到单机版本的eureka的UI界面。

eureka-project

打包docker镜像,打开idea的终端,确保你配置好了docker插件中imageNameserverId之后。

1
 $ mvn clean package

如果顺利,你将会看到镜像被成功推送到远程仓库。这我推送的镜像是freemanliu/eureka:v1.0.0

使用Docker部署eureka集群

这里使用三个机器分别是

eureka host
eureka-0.eureka 192.168.0.201
eureka-1.eureka 192.168.0.202
eureka-2.eureka 192.168.0.203

写入映射hosts

1
2
3
4
5
cat > /etc/hosts << EOF
192.168.0.201 eureka-0.eureka
192.168.0.202 eureka-1.eureka
192.168.0.203 eureka-2.eureka
EOF

201-eureka0

1
2
3
4
docker run -it --rm --net host \
-e EUREKA_INSTANCE_HOSTNAME=eureka-0.eureka \
-e EUREKA_SERVER=http://eureka-0.eureka:8761/eureka/,http://eureka-1.eureka:8761/eureka/,http://eureka-2.eureka:8761/eureka/ \
freemanliu/eureka:v1.0.0

202-eureka1

1
2
3
4
5

docker run -it --rm --net host \
-e EUREKA_INSTANCE_HOSTNAME=eureka-1.eureka \
-e EUREKA_SERVER=http://eureka-0.eureka:8761/eureka/,http://eureka-1.eureka:8761/eureka/,http://eureka-2.eureka:8761/eureka/ \
freemanliu/eureka:v1.0.0

203-eureka2

1
2
3
4
docker run -it --rm  --net host \
-e EUREKA_INSTANCE_HOSTNAME=eureka-2.eureka \
-e EUREKA_SERVER=http://eureka-0.eureka:8761/eureka/,http://eureka-1.eureka:8761/eureka/,http://eureka-2.eureka:8761/eureka/ \
freemanliu/eureka:v1.0.0

打开浏览器访问 http://192.168.0.201:8761 可以发现我们的集群已经成功搭建好了。

eureka-project

在client使用

1
2
3
4
eureka:
client:
serviceUrl:
defaultZone: http://192.168.0.201:8761/eureka/,http://192.168.0.202:8761/eureka/,http://192.168.0.203:8761/eureka/

部署到kubernetes

这里采用statefulset部署,部署3个副本集的集群,如果你需要更多副本集,则需要改动EUREKA_INSTANCE_HOSTNAME中的URL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# eureka-statefulset.yaml
---
apiVersion: v1
kind: Service
metadata:
name: eureka
labels:
app: eureka
spec:
ports:
- port: 8761
name: eureka
clusterIP: None
selector:
app: eureka
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: eureka
spec:
serviceName: "eureka"
replicas: 3
selector:
matchLabels:
app: eureka
template:
metadata:
labels:
app: eureka
spec:
containers:
- name: eureka
image: freemanliu/eureka:v1.0.0
ports:
- containerPort: 8761
resources:
limits:
# jvm会自动发现该限制
memory: 2Gi
env:
- name: MY_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: JAVA_OPTS
value: -XX:+UnlockExperimentalVMOptions
-XX:+UseCGroupMemoryLimitForHeap
-XX:MaxRAMFraction=2
-XX:CICompilerCount=8
-XX:ActiveProcessorCount=8
-XX:+UseG1GC
-XX:+AggressiveOpts
-XX:+UseFastAccessorMethods
-XX:+UseStringDeduplication
-XX:+UseCompressedOops
-XX:+OptimizeStringConcat
- name: EUREKA_SERVER
value: "http://eureka-0.eureka:8761/eureka/,http://eureka-1.eureka:8761/eureka/,http://eureka-2.eureka:8761/eureka/"
- name: EUREKA_INSTANCE_HOSTNAME
value: ${MY_POD_NAME}.eureka
podManagementPolicy: "Parallel"

部署

1
kubectl apply -f eureka-statefulset.yaml

查看部署状态

1
2
3
4
freemandeMacBook-Pro:qingmu freeman$ kubectl get pods -owide | grep eureka
eureka-0 1/1 Running 0 126m 172.224.5.40 node3 <none> <none>
eureka-1 1/1 Running 0 126m 172.224.3.131 node1 <none> <none>
eureka-2 1/1 Running 0 126m 172.224.7.243 node6 <none> <none>

通过浏览器访问http://podip:8761
eureka3

在client使用,当都处于default namespace时

1
2
3
4
eureka:
client:
serviceUrl:
defaultZone: http://eureka-0.eureka:8761/eureka/,http://eureka-1.eureka:8761/eureka/,http://eureka-2.eureka:8761/eureka/

都处于非default namespace 卡的惨不忍睹啊 是输入法的问题妈

1
2
3
4
eureka:
client:
serviceUrl:
defaultZone: http://eureka-0.eureka.default:8761/eureka/,http://eureka-1.eureka.default:8761/eureka/,http://eureka-2.eureka.default:8761/eureka/

Github 地址

完整的代码我上传到了github 你可以从这个https://github.com/qingmuio/eureka 获取到代码。

推荐文章