[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  

相關文章
相關標籤/搜索