打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
eureka 迁移 k8s 集群
userphoto

2019.02.22

关注

Spring Cloud 强上云 系列之 eureka 迁移

故事前奏

前阵子忽然一个需求砸了过来,一忙就是两星期。写bug,挖坑忙的不亦乐乎。这几天军哥一直在群里一直大呼: 你们要输出,要有声音啊。正好今天刚搞完,也确实想放松一下,写篇小白文压压惊。

故事背景

这几年,云计算,容器 docker,微服务 很火,很显眼。当你初步了解这些东西,很多时候会眼前一亮。哇! 还能这么搞? 嗯! 这样确实好很多。很多企业都是拼了性命的往云上挤,这问题就来了。 上云,容器化部署是需要代价的,很多项目是基于原来传统的框架进行开发、构建,你要上云就要做出相应的代码重构,但重构代码就会莫名承担一些不可预知的风险。所以甲方爸爸的需求就来了 —— 我不管,我就要上云,我代码就这样,我就只有jar包,剩下的你自己看着办吧。乙方……

没办法,上就上吧,但问题接踵而至。你项目容器化部署了,总要有个东西来对容器进行编排和管理吧,不然后期怎么维护,怎么知道可不可靠。这个时候 k8s 站了出来,k8s 何许人也? google 开源的容器集群管理系统,业界容器编排的标准,牛批的不行,就连docker 原生的docker swarm 都被比下去了。

好吧,本文章就是讲述如何把 spring cloud eureka模块搬移到 云上 k8s 集群,实现 高可用。 ps:不得不吐槽一下,既然使用了 k8s,还要强行部署eureka 作为服务中心,总觉的有点耿直。

故事进行中之 思路整理

首先我们先理下需求。k8s 上 部署 eureka 服务,eureka 服务高可用。再理下已有的资源: 华为云 ->k8s 集群。好吧,大致清楚了。首先自己准备个具有高可用功能的eureka 镜像,然后编写对应的 k8s yaml 文件部署应用,这就大功告成了。 如果是在华为云上,甚至可以编写 AOS 模板,实现一键在 k8s 集群上部署 高可用eureka 。

故事进行中之 Spring Cloud eureka 镜像准备。

什么是Spring Cloud eureka

Spring Cloud Eureka 是 Spring Cloud Netflix 微服务套件的一部分,主要负责微服务架构中的服务治理功能。

为什么需要Spring Cloud Eureka

微服务的核心思想是分而治之,把功能模块化,从整体系统中抽离出来,互相解耦。初衷是好的,但是如果一个系统模块组件太多,那么各个模块之间的交互就成了大问题。

会发现这些模块之间的调用非常复杂,各个模块其实也是互相耦合一起,每一次生产者的变化都会牵连着无数的消费者。这样,eureka 服务中心应用而生,各个模块只需要和 eureka 直接交互。

消费者只需要关注服务中心就好,从服务中心中获取服务来调用。在一定程度上实现了微服务生产者消费者之间的解耦。但是这样也有一个问题,就是万一这个 服务中心挂了,岂不是整个微服务集群就瘫痪了。在实际生产中,这显然是不可取的。部署一个高可用的eureka 服务,实现多实例euerka 节点相互注册时非常有必要的,当其中某一个节点发生宕机,并不会影响正常的业务逻辑。

Spring Cloud Eureka 项目构建
  1. 准备 pom 依赖

<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Finchley.SR1</spring-cloud.version></properties><dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency></dependencies>
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

2.启动代码中添加 @EnableEurekaServer 注解

@EnableEurekaServer@SpringBootApplicationpublic class HuaweiEurekaApplication {  public static void main(String[] args) {    SpringApplication.run(HuaweiEurekaApplication.class, args);  }}
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

3.准备配置文件,在项目 resources 目录下的 application.yaml 文件中添加:

server: port: 8761eureka: instance: hostname: ${EUREKA_HOST_NAME:peer1} #服务主机名 appname: ${spring.application.name} #服务名称,默认为 unknow 这里直接取 spring.application.name 了 client: register-with-eureka: ${BOOL_REGISTER:true} # 是否把服务中心本身当做eureka client 注册。默认为true fetch-registry: ${BOOL_FETCH:true} # 是否拉取 eureka server 的注册信息。 默认为true service-url: defaultZone: ${EUREKA_URL_LIST:http://peer1:8761/eureka/} # 指定服务中心 eureka server的地址 server: enable-self-preservation: ${SELF_PRESERVATION:true} # 是否开启自我保护。 默认为 true.spring: application: name: ${EUREKA_APPLICATION_NAME:eureka-server}
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • register-with-eureka : 主要用于高可用 eureka 集群构建的时候,eureka 实例之间互相发现,同步。单节点部署建议设置为 false。

  • fetch-registry :同上面 register-with-eureka ,主要用于构建高可用eureka集群,实例之间信息同步。单节点部署建议为 false。

  • enable-self-preservation :在实际生产中,eureka server在短时间内丢失大量客户端,往往是因为网络的问题。这个时候eureka server 就会自动进入自我保护模式: 即一个服务长时间没有发送心跳,eureka server也不会将它剔除,保证服务中心的稳定性

4.打包 docker 镜像。
* 准备启动 脚本 init.sh


#!/usr/bin/env bash
#暂时先这样,后面实际部署 k8s, 有需求的时候,在增加内容。
java -jar eureka.jar

* 准备 Dockerfile,简单化处理,自行优化。


FROM java:8
#设置端口
EXPOSE 8761
ADD init.sh /init.sh
ADD eureka.jar /eureka.jar
#reset shell
RUN rm /bin/sh && ln -s /bin/bash /bin/sh
ENTRYPOINT ['/bin/bash','-c','source /init.sh']

* docker build -t helloHuawei/eureka:v1 .

好这样一个初级的不能用的镜像就打包成功了,我们准备下一步, k8s 集群部署。

故事进行中之 k8s 集群部署高可用 eureka

思路构造

我们再来理一下思路,现在有了镜像。但是k8s 这个集群功能可是有点复杂,怎么部署呢? 首先,要实现高可用,eureka 实例之间必须可以相互感知,相互通信。 这就代表着k8s 部署起来的 eureka pods 之间能够互相感知,互相注册。 k8s pod 要在部署的时候就知道其他 pod ip 或者域名,保证在启动pod的时候可以互相寻址。很明显,这要我们使用 StatefulSets + Headless服务来部署eureka 服务啊。

准备 k8s 部署 yaml 文件
apiVersion: v1kind: Servicemetadata:  name: eureka-service-internal  labels:    app: eureka-service-internal  namespace: default    spec:  clusterIP: None  ports:  - port: 8761    protocol: TCP    targetPort: 8761  selector:    app: eureka  type: ClusterIP---apiVersion: apps/v1beta1kind: StatefulSetmetadata:  name: eurekaspec:  selector:    matchLabels:      app: eureka  serviceName: 'eureka-service-internal'  replicas: 3  template:    metadata:      labels:        app: eureka    spec:      terminationGracePeriodSeconds: 10      containers:      - env:        - name: MY_NODE_NAME          valueFrom:            fieldRef:              fieldPath: spec.nodeName        - name: MY_POD_NAME          valueFrom:            fieldRef:              fieldPath: metadata.name        - name: MY_POD_NAMESPACE # 传入当前命名空间          valueFrom:            fieldRef:              fieldPath: metadata.namespace        - name: MY_POD_IP          valueFrom:            fieldRef:              fieldPath: status.podIP        - name: MY_IN_SERVICE_NAME # 因为pod 通过域名互相访问,需要使用headless 服务名称          value: eureka-service-internal        - name: EUREKA_APPLICATION_NAME          value: 'eureka'        - name: EUREKA_REPLICAS          value: '3'        image:  helloHuawei/eureka:v2 # 这个镜像是修改后的。如何打包这个镜像,在后面会介绍        imagePullPolicy: IfNotPresent        name: eureka-container        ports:        - containerPort: 8761          protocol: TCP
  • 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

这样简单部署一个 k8s服务,并且把相关的环境变量传入镜像。剩下的就需要脚本来对这些 变量进行拼接,然后在启动 eureka jar包的时候,把拼接好的参数信息设置到对应的环境变量里面。这样就大功告成了。

重新构建 eureka 镜像中 init.sh 脚本
  1. 首先我们理一下上面 k8s 传入的环境变量,也就是我们在镜像里面可以拿到的数据

  • MY_NODE_NAME : 当前节点的名称

  • MY_POD_NAME : 当前 Pod 的名称

  • MY_POD_NAMESPACE : 部署当前service 和 Statefulset 的命名空间

  • MY_IN_SERVICE_NAME : 当前部署的headless service 的服务名称

  • EUREKA_APPLICATION_NAME : 当前 Statefulset 的名称

  • EUREKA_REPLICAS : 当前部署Statefulset的实例数,也就是我这里有三个 pod,三个eureka server。

2.用上面这些环境变量 拼接出其他 pod 的访问地址。

因为pod 节点的实际虚拟 ip 是部署之后才会确定,而且每次 pod 重启,pod ip 都会有变化,所以我们需要通过 DNS 域名来访问各个pod .
1. 对于一个 Statefulset 来说,它的 pod name 是确定的。采用的一下形式: $(statefulset名称)-$(序列号),序列号是0replicas - 1 。 例如我上述部署的pod名称 分别是 eureka-0, eureka-1 ,eureka-2
2. StatefulSet 通过 headless 服务来控制 Pod 的 DNS 。这个服务的域名是采用以下形式 : $(service name).$(namespace).svc.cluster.local , 在上述实例中 就是 eureka-service-internal.default.svc.cluster.local 。在每创建一个 pod 的时候,pod 会被分配一个 DNS 子域,采用以下形式 :$(podname).$(governing service domain),governing service 是StatefulSet上的字段 serviceName

综合上面,上述实例 pod 的 域名分别是 :
* http://eureka-0.eureka-service-internal.default.svc.cluster.local
* http://eureka-1.eureka-service-internal.default.svc.cluster.local
* http://eureka-2.eureka-service-internal.default.svc.cluster.local

我们可以先看改完 init.sh 脚本,重新部署的结果。

3.既然 Pod 的域名有了,那我们就可以用脚本进行拼接,相互注册了。 更改 init.sh 如下。 ps: bash 小白一个,只是符合能用,各位大佬自行优化。 而且还有个eureka DNS 注册,也可以考虑一下,我这里就手动拼接了。

#!/usr/bin/env bashpostFix='svc.cluster.local'EUREKA_HOST_NAME='$MY_POD_NAME.$MY_IN_SERVICE_NAME.$MY_POD_NAMESPACE.$postFix'export EUREKA_HOST_NAME=$EUREKA_HOST_NAMEBOOL_REGISTER='true'BOOL_FETCH='true'if [ $EUREKA_REPLICAS = 1 ]; then echo 'the replicas of eureka pod is one' BOOL_REGISTER='false' BOOL_FETCH='false' EUREKA_URL_LIST='http://$EUREKA_HOST_NAME:8761/eureka/,' echo ' set the EUREKA_URL_LIST is $EUREKA_URL_LIST'else echo 'the replicas of the eureka pod is $EUREKA_REPLICAS' BOOL_REGISTER='true' BOOL_FETCH='true' for ((i=0 ;i<$EUREKA_REPLICAS; i ++)) do temp='http://$EUREKA_APPLICATION_NAME-$i.$MY_IN_SERVICE_NAME.$MY_POD_NAMESPACE.$postFix:8761/eureka/,' EUREKA_URL_LIST='$EUREKA_URL_LIST$temp' echo $EUREKA_URL_LIST donefi# 这里我简单处理了,每个 pod 的 EUREKA_URL_LIST 都设置成了全部的 pod 域名。使用的时候,可以自行判断,选择不向自己注册。#例如 eureka-0 的 EUREKA_URL_LIST 可以剔除 http://eureka-0.eureka-service-internal.default.svc.cluster.local:8761/eureka/EUREKA_URL_LIST=${EUREKA_URL_LIST%?}export EUREKA_URL_LIST=$EUREKA_URL_LISTexport BOOL_FETCH=$BOOL_FETCHexport BOOL_REGISTER=$BOOL_REGISTERecho 'start jar....'java -jar /eureka.jar
  • 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

4.最后 docker build -t helloHuawei/eureka:v2 .
5.打包好镜像, 最后 kubectl create -f eureka.yaml。 这样一个高可用的eureka 集群就创建出来了。 我们通过 服务名访问eureka 集群: curl eureka-service-internal:8761

eureka 服务外挂 elb,实现集群外访问

到现阶段为止,所有的通过 域名进行的访问都只能在集群内使用,集群外就完全不知道服务名是啥,所以我们要把服务外挂到elb上,实现集群外部的访问。ps : 我这里简单处理了,直接加一个服务挂载 elb。

修改eureka.yaml 文件。

apiVersion: v1kind: Servicemetadata:  name: eureka-service-elb  labels:    app: eureka-service-elb  namespace: default    spec:  loadBalancerIP: 114.115.143.173   #   可以使用在华为云上购买的 elb,然后把 elb ip 填到这里  ports:  - port: 8761    protocol: TCP    targetPort: 8761  selector:    app: eureka  type: LoadBalancer---apiVersion: v1kind: Servicemetadata:  name: eureka-service-internal  labels:    app: eureka-service-internal  namespace: default    spec:  clusterIP: None  ports:  - port: 8761    protocol: TCP    targetPort: 8761  selector:    app: eureka  type: ClusterIP---apiVersion: apps/v1beta1kind: StatefulSetmetadata:  name: eurekaspec:  selector:    matchLabels:      app: eureka  serviceName: 'eureka-service-internal'  replicas: 3  template:    metadata:      labels:        app: eureka    spec:      terminationGracePeriodSeconds: 10      containers:      - env:        - name: MY_NODE_NAME          valueFrom:            fieldRef:              fieldPath: spec.nodeName        - name: MY_POD_NAME          valueFrom:            fieldRef:              fieldPath: metadata.name        - name: MY_POD_NAMESPACE # 传入当前命名空间          valueFrom:            fieldRef:              fieldPath: metadata.namespace        - name: MY_POD_IP          valueFrom:            fieldRef:              fieldPath: status.podIP        - name: MY_IN_SERVICE_NAME # 因为pod 通过域名互相访问,需要使用headless 服务名称          value: eureka-service-internal        - name: EUREKA_APPLICATION_NAME          value: 'eureka'        - name: EUREKA_REPLICAS          value: '3'        image:  helloHuawei/eureka:v2        imagePullPolicy: IfNotPresent        name: eureka-container        ports:        - containerPort: 8761          protocol: TCP
  • 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

最后 kubectl create -f eureka.yaml,大功告成。附上一张成功的图片。

故事终章之 华为云 AOS 模板一键化部署。

附上一段华为云 应用编排服务 AOS的介绍 : 应用编排服务(Application Orchestration Service,简称AOS)可以帮助您将应用一键式部署到华为云上,简化相关云服务管理操作。AOS通过模板来描述和编排应用及相关云服务,实现自动化部署应用、创建云服务,提供E2E应用全生命周期运维管控能力。

1.先到华为云的 应用编排服务AOS 界面,点击 立即使用

2.点击 创建模板

3.准备 AOS 模板编排。我这里直接给出AOS 模板了,有兴趣的小伙伴可以自行研究。

eureka.yaml

inputs: eureka_elb_ip: description: 'eureka 挂载的 elb ip' label: eureka eureka_eport: default: 31010 description: eureka 暴露接口 label: eureka type: integer eureka_image: default: 'helloHuawei:v2' description: eureka镜像 label: eureka eureka_replicas: default: 2 description: 部署eureka实例的数量 label: eureka type: integer eureka_service_name: default: spring-eureka description: 部署 spring eureka 服务 label: eureka eureka_lmt_cpu: default: 300m description: eureka 的CPU限制 label: eureka eureka_lmt_mem: default: 1500Mi description: eureka 的内存限制 label: eureka eureka_request_cpu: default: 200m description: eureka 的申请的 cpu 资源 label: eureka eureka_request_mem: default: 1000Mi description: eureka 的申请的内存资源 label: eurekametadata: Designer: 20920929-f069-42d9-9cae-037e2d6d147e:conditions: condition_delopy_elb: cond_not: cond_eq: - get_input: eureka_elb_ip - '' condition_not_delopy_elb: cond_eq: - get_input: eureka_elb_ip - ''node_templates: eureka-service-ex-elb: condition: condition_delopy_elb metadata: Designer: id: 4c61fff4-09ba-48e3-9fc1-59b4bd45bc0a properties: k8sManifest: apiVersion: v1 kind: Service metadata: labels: app: get_input: eureka_service_name name: get_input: eureka_service_name annotations: managedBy: springcloud spec: loadBalancerIP: get_input: eureka_elb_ip ports: - port: get_input: eureka_eport protocol: TCP targetPort: 8761 selector: app: get_input: eureka_service_name type: LoadBalancer requirements: - dependency: node: eureka-statefulset type: HuaweiCloud.CCE.Service eureka-service-in: metadata: Designer: id: 4c61fff4-09ba-48e3-9fc1-59b4bd45bc0a properties: k8sManifest: apiVersion: v1 kind: Service metadata: labels: app: concat: - in- - get_input: eureka_service_name annotations: managedBy: springcloud name: concat: - in- - get_input: eureka_service_name spec: clusterIP: None ports: - port: 8761 protocol: TCP targetPort: 8761 selector: app: get_input: eureka_service_name requirements: - dependency: node: eureka-statefulset type: HuaweiCloud.CCE.Service eureka-statefulset: metadata: Designer: id: 20920929-f069-42d9-9cae-037e2d6d147e properties: k8sManifest: apiVersion: apps/v1 kind: StatefulSet metadata: labels: app: get_input: eureka_service_name name: get_input: eureka_service_name annotations: managedBy: springcloud spec: replicas: get_input: eureka_replicas selector: matchLabels: app: get_input: eureka_service_name serviceName: concat: - in- - get_input: eureka_service_name template: metadata: labels: app: get_input: eureka_service_name annotations: managedBy: springcloud spec: containers: - env: - name: MY_NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName - name: MY_POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: MY_POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: MY_POD_IP valueFrom: fieldRef: fieldPath: status.podIP - name: MY_IN_SERVICE_NAME value: concat: - in- - get_input: eureka_service_name - name: EUREKA_HOST_NAME value: get_input: eureka_service_name - name: EUREKA_URL_LIST value: '' - name: EUREKA_APPLICATION_NAME value: get_input: eureka_service_name - name: EUREKA_REPLICAS value: concat: - '' - get_input: eureka_replicas image: get_input: eureka_image imagePullPolicy: IfNotPresent name: eureka-container ports: - containerPort: 8761 protocol: TCP resources: limits: cpu: get_input: eureka_lmt_cpu memory: get_input: eureka_lmt_mem requests: cpu: get_input: eureka_request_cpu memory: get_input: eureka_request_mem terminationMessagePath: /dev/termination-eureka-log terminationMessagePolicy: File imagePullSecrets: - name: default-secret type: HuaweiCloud.CCE.StatefulSetoutputs: {}tosca_definitions_version: huaweicloud_tosca_version_1_0

4.上传, 创建AOS模板。

5.部署 AOS 堆栈。
* 我的模板 > 部署堆栈

  • 部署 界面

  • 测试外网 访问

主要参考文献

k8s :
* https://kubernetes.io/cn/docs/tutorials/stateful-application/basic-stateful-set/
* https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/

华为云 AOS 模板编排 :
* https://support.huaweicloud.com/tr-aos/aos_01_4000.html

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
从零搭建 Spring Cloud 服务(超级详细)
微服务该如何进行服务治理?
spring cloud
spring-cloud(八)分布式跟踪[Sleuth]
在Kubernetes平台上运行Hadoop的实践
如何使用Kubernetes部署一个有状态的应用
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服