[K8s]Eureka上K8s集群

Spring Cloud 强上云 系列之 eureka 迁移
故事前奏
前阵子突然一个需求砸了过来,一忙就是两星期。写bug,挖坑忙的不亦乐乎。这几天军哥一直在群里一直大呼: 大家要输出,要有声音啊。正好今天刚搞完,也确实想放松一下,写篇小白文压压惊。html

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

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

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

故事进行中之 思路整理
首先咱们先理下需求。k8s 上 部署 eureka 服务,eureka 服务高可用。再理下已有的资源: 华为云 ->k8s 集群。好吧,大体清楚了。首先本身准备个具备高可用功能的eureka 镜像,而后编写对应的 k8s yaml 文件部署应用,这就大功告成了。 若是是在华为云上,甚至能够编写 AOS 模板,实现一键在 k8s 集群上部署 高可用eureka 。docker

故事进行中之 Spring Cloud eureka 镜像准备。
什么是Spring Cloud eureka
Spring Cloud Eureka 是 Spring Cloud Netflix 微服务套件的一部分,主要负责微服务架构中的服务治理功能。shell

为何须要Spring Cloud Eureka
微服务的核心思想是分而治之,把功能模块化,从总体系统中抽离出来,互相解耦。初衷是好的,可是若是一个系统模块组件太多,那么各个模块之间的交互就成了大问题。api

这里写图片描述

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

这里写图片描述

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

这里写图片描述

Spring Cloud Eureka 项目构建
准备 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>



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

@EnableEurekaServer
@SpringBootApplication
public class HuaweiEurekaApplication {

  public static void main(String[] args) {
    SpringApplication.run(HuaweiEurekaApplication.class, args);
  }
}


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

server:
  port: 8761
eureka:
  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}

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: v1
kind: Service
metadata:
  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/v1beta1
kind: StatefulSet
metadata:
  name: eureka
spec:
  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


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

从新构建 eureka 镜像中 init.sh 脚本
首先咱们理一下上面 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名称)-$(序列号),序列号是0 到 replicas - 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 bash

postFix="svc.cluster.local"
EUREKA_HOST_NAME="$MY_POD_NAME.$MY_IN_SERVICE_NAME.$MY_POD_NAMESPACE.$postFix"
export EUREKA_HOST_NAME=$EUREKA_HOST_NAME
BOOL_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
    done
fi
# 这里我简单处理了,每一个 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_LIST
export BOOL_FETCH=$BOOL_FETCH
export BOOL_REGISTER=$BOOL_REGISTER
echo "start jar...."
java -jar /eureka.jar


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: v1
kind: Service
metadata:
  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: v1
kind: Service
metadata:
  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/v1beta1
kind: StatefulSet
metadata:
  name: eureka
spec:
  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



最后 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: eureka
metadata:
  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.StatefulSet
outputs: {}
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  

相关文章
相关标签/搜索