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