spring cloud之 apollo 基於k8s的微服務容器化落地實踐

apollo

簡介

Apollo(阿波羅)是攜程框架部門研發的分佈式配置中心,能夠集中化管理應用不同環境、不同集羣的配置,配置修改後能夠實時推送到應用端,並且具備規範的權限、流程治理等特性,適用於微服務配置管理場景。

如果對apollo想要有詳細的研究和理解,請參閱它的官方文檔,本文後續的所有闡述都是基於你對apollo有一定理解的前題下,只對基於文章敘述的必要的概念做簡要說明。

其實官方也提供了針對apollo在k8s環境下的部署方案,但是個人覺得引入k8s有狀態的StateFulSet似乎提高了部署運維和學習的成本,我提供的部署方式更爲簡單方便

apollo官方架構

在部署之前有必要簡單介紹一下apollo如何組織自己的組件去協同工作去完成整個微服務動態配置的管理的,後續也是圍繞它的組件去部署的

這是官方給出的架構圖,如圖一
圖一
在這裏插入圖片描述
看着是不是一臉懵逼,這麼多文字圖片,似乎一下子根本抓不到重點,彆着急接下來我來給出我自己理解的簡化版的架構圖,如圖二

圖二
在這裏插入圖片描述

  • 如何看懂這張圖呢,首先apollo 可以把它整體看作是一個微縮版的微服務集羣,只不過在這個集羣中也選擇了eureka作爲整體的服務註冊中心
  • admin-service、config-service、 portal-service可以理解爲都是apollo內部系統自己的微服務,他們通過相互配合協同工作,爲類似client-service(我們自己寫的業務微服務)這樣的多個微服務提供屬性配置的動態管理
  • protal-service 可以簡單理解爲web圖形化操作配置管理的後端服務器,他統一對web頁面提供後臺服務
  • config-service 是真正具有讀取推送配置信息功能的內部微服務,主要爲client-service提供對應功能
  • admin-service 提供了配置的修改、發佈等功能,當你在portal的頁面對配置進行了修改,前端頁面把http請求發送的後端服務器的portal-service上對應的接口,然後它再調用admin-service上的接口功能真正完成配置修改
  • apollo所有的微服務配置信息以及他自己爲了運行的系統信息都放在mysql 數據庫中
  • 在官方的介紹中爲了簡化部署把eureka 和config-service 合併在一起放在同一個jvm進程中,但是 我們的微服務集羣其實也是用的eureka來做註冊中心,爲了減少註冊中心管理上的冗餘,我們把apollo自己的微服務和我們系統的微服務都註冊在統一的eureka上,後續在部署上會有說明
  • 上圖即是對apollo系統架構的一個簡單講解,也是對我們後續要進行容器化部署思路的一個說明

部署設計原理

  • 瞭解了apollo的內部結構,其實可以看出來,將apollo容器化就是將它自己的三個微服務部署爲獨立的容器微服務,然後統一註冊到指定的服務註冊中心,後續所有的操作都假定你已經有一個容器化的eureka 微服務註冊中心,相關的部署資料網上也很多,這裏不再贅述.

在eureka中,微服務本身作爲被消費的一方在向eureka註冊的時候基本有兩種註冊方式,一種是通過域名註冊,一種是通過ip註冊,apollo中默認使用的基於ip的註冊方式,但是我們在容器中部署的時候最好基於域名去註冊,這樣做有2點好處:

  • 一是這樣就可以借用k8s本身的特性,針對域名這一層做單個微服務的負載均衡和橫向擴展,如下圖三所示
    圖三
    在這裏插入圖片描述

  • 二是 假如採用ip註冊,當k8s中apollo的某個pod意外重啓,pod的ip就會改變,然而eureka 默認的服務發現變更信息的時間一般在90s左右(雖然可配置,但是變短會加重eureka的負擔),這90s內舊的不可用的服務註冊信息依然存在,大量對apollo的服務調用就會報錯.這是不是忍受的, 情況如下圖四所示
    圖四
    在這裏插入圖片描述

  • 然後當你採用基於域名的方式註冊,無論pod怎麼重啓它在eureka中註冊的域名都是固定不變的,而k8s對pod層面的服務發現感知是非常迅速的,這樣服務不可用的間隙被縮短到很小,極大的提高了服務的可用性,如下圖五所示
    圖五
    在這裏插入圖片描述

  • admin-service、config-service、 portal-service 都可以用k8s中的Deployment去單獨部署爲無狀態服務,這三個服務都需要被容器之外的環境訪問,所以需要給這三個deployment都配置對應的Service作爲被訪問的入口,如圖三

檢出 apollo項目

首先需要從github上檢出項目

git clone https://github.com/ctripcorp/apollo.git

配置mysql

apollo有兩個庫都需要創建

ApolloConfigDB  
目錄 apollo/scripts/db/migration/configdb/V1.0.0__initialization.sql
ApolloPortalDB
目錄 apollo/scripts/db/migration/portaldb/V1.0.0__initialization.sql

修改ApolloPortalDB 的ServerConfig 表的 apollo.portal.envs,這裏建議最好不要通過一套apollo管理多套服務器環境,由於多套環境之間portal是公用的,所以實踐下來公用portal有相互干擾,建議每套環境配置如下

use ApolloPortalDB;
update ServerConfig set Value = "PRO" where Id=1;

修改對應ApolloConfigDB 中ServerConfig 表的 eureka.service.url分配置項,因爲portal在檢測對應環境config-service的心跳時會用到,這裏已經假設你已經部署了一個容器化的eureka的微服務,它的訪問地址是 http://service-registry:30013/erueka/,配置如下:

use ApolloConfigDB;
update ServerConfig set Value = "http://service-registry:30013/erueka/" where Id=1;

apollo源碼配置修改

修改adminservice

apollo/apollo-adminservice/src/main/resources/bootstrap.yml
原配置文件默認通過ip方式註冊,需要改爲通過域名註冊
修改前:

eureka:
  instance:
    hostname: ${hostname:localhost}
    ## 通過ip方式註冊
    preferIpAddress: true
    status-page-url-path: /info
    health-check-url-path: /health
  client:
    serviceUrl:
      # This setting will be overridden by eureka.service.url setting from ApolloConfigDB.ServerConfig or System Property
      # see com.ctrip.framework.apollo.biz.eureka.ApolloEurekaClientConfig
      defaultZone: http://${eureka.instance.hostname}:8080/eureka/
    healthcheck:
      enabled: true
    eurekaServiceUrlPollIntervalSeconds: 60

management:
  health:
    status:
      order: DOWN, OUT_OF_SERVICE, UNKNOWN, UP 

這裏指定admin-service註冊時的域名爲apollo-admin,修改後如下:

eureka:
  instance:
  ## 通過域名註冊
    hostname: apollo-admin
    status-page-url-path: /info
    health-check-url-path: /health
  client:
    serviceUrl:
      # This setting will be overridden by eureka.service.url setting from ApolloConfigDB.ServerConfig or System Property
      # see com.ctrip.framework.apollo.biz.eureka.ApolloEurekaClientConfig
      defaultZone: http://${eureka.instance.hostname}:8080/eureka/
    healthcheck:
      enabled: true
    eurekaServiceUrlPollIntervalSeconds: 60

management:
  health:
    status:
      order: DOWN, OUT_OF_SERVICE, UNKNOWN, UP 

修改configservice

apollo/apollo-configservice/src/main/resources/bootstrap.yml
修改前:

eureka:
  instance:
    hostname: ${hostname:localhost}
    ## 默認通過ip註冊服務
    preferIpAddress: true
    status-page-url-path: /info
    health-check-url-path: /health
  server:
    peerEurekaNodesUpdateIntervalMs: 60000
    enableSelfPreservation: false
  client:
    serviceUrl:
      # This setting will be overridden by eureka.service.url setting from ApolloConfigDB.ServerConfig or System Property
      # see com.ctrip.framework.apollo.biz.eureka.ApolloEurekaClientConfig
      defaultZone: http://${eureka.instance.hostname}:8080/eureka/
    healthcheck:
      enabled: true
    eurekaServiceUrlPollIntervalSeconds: 60

management:
  health:
    status:
      order: DOWN, OUT_OF_SERVICE, UNKNOWN, UP

指定config-service 的註冊時域名是apollo-config,修改後:

eureka:
  instance:
  ## 通過域名註冊
    hostname: apollo-config
    status-page-url-path: /info
    health-check-url-path: /health
  server:
    peerEurekaNodesUpdateIntervalMs: 60000
    enableSelfPreservation: false
  client:
    serviceUrl:
      # This setting will be overridden by eureka.service.url setting from ApolloConfigDB.ServerConfig or System Property
      # see com.ctrip.framework.apollo.biz.eureka.ApolloEurekaClientConfig
      defaultZone: http://${eureka.instance.hostname}:8080/eureka/
    healthcheck:
      enabled: true
    eurekaServiceUrlPollIntervalSeconds: 60

management:
  health:
    status:
      order: DOWN, OUT_OF_SERVICE, UNKNOWN, UP

由於我們打算使用apollo外部的服務註冊中心,所以需要做如下修改
1.5.0及以上版本
爲apollo-configservice配置apollo.eureka.server.enabled=false即可,通過bootstrap.yml或-D參數等方式皆可。
1.5.0之前的版本
修改com.ctrip.framework.apollo.configservice.ConfigServiceApplication,把@EnableEurekaServer改爲@EnableEurekaClient

修改構建腳本

假如之前部署apollo數據庫的信息如下:

  • mysql url:mysqladdress:port
  • 用戶名:username
  • 密碼:pwd
  • config-service指定的域名是apollo-config,所以pro_meta=http://apollo-config:8080

修改apollo/scripts/build.sh

#!/bin/sh

# apollo config db info
apollo_config_db_url=jdbc:mysql://mysqladdress:port/apolloconfigdb?characterEncoding=utf8
apollo_config_db_username=username
apollo_config_db_password=pwd

# apollo portal db info
apollo_portal_db_url=jdbc:mysql://mysqladdress:port/apolloportaldb?characterEncoding=utf8
apollo_portal_db_username=username
apollo_portal_db_password=pwd

# meta server url, different environments should have different meta server addresses
#dev_meta=http://fill-in-uat-meta-server:8080
#fat_meta=http://fill-in-uat-meta-server:8080
#uat_meta=http://fill-in-uat-meta-server:8080
pro_meta=http://apollo-config:8080

META_SERVERS_OPTS="-Ddev_meta=$dev_meta -Dfat_meta=$fat_meta -Duat_meta=$uat_meta -Dpro_meta=$pro_meta"

# =============== Please do not modify the following content =============== #
# go to script directory
cd "${0%/*}"

cd ..

# package config-service and admin-service
echo "==== starting to build config-service and admin-service ===="

mvn clean package -DskipTests -pl apollo-configservice,apollo-adminservice -am -Dapollo_profile=github -Dspring_datasource_url=$apollo_config_db_url -Dspring_datasource_username=$apollo_config_db_username -Dspring_datasource_password=$apollo_config_db_password

echo "==== building config-service and admin-service finished ===="

echo "==== starting to build portal ===="

mvn clean package -DskipTests -pl apollo-portal -am -Dapollo_profile=github,auth -Dspring_datasource_url=$apollo_portal_db_url -Dspring_datasource_username=$apollo_portal_db_username -Dspring_datasource_password=$apollo_portal_db_password $META_SERVERS_OPTS

echo "==== building portal finished ===="

執行構建腳本

./scripts/build.sh

在對應子模塊的target目錄分別生成了對應壓縮包

apollo/apollo-adminservice/target/apollo-adminservice-1.5.0-SNAPSHOT-github.zip
apollo/apollo-configservice/target/apollo-configservice-1.5.0-SNAPSHOT-github.zip
apollo/apollo-portal/target/apollo-portal-1.5.0-SNAPSHOT-github.zip

容器化

分別將 admin、config、portal對應的壓縮包解壓

在解壓後的根目錄下分別創建docker file

FROM openjdk:8-jre-alpine3.8

RUN \
    echo "http://mirrors.aliyun.com/alpine/v3.8/main" > /etc/apk/repositories && \
    echo "http://mirrors.aliyun.com/alpine/v3.8/community" >> /etc/apk/repositories && \
    apk update upgrade && \
    apk add --no-cache procps curl bash tzdata && \
    ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
    echo "Asia/Shanghai" > /etc/timezone && \
    mkdir -p /apollo


#將當前目錄下所有文件copy 到容器的apollo目錄下
ADD . /apollo/
EXPOSE port
ENTRYPOINT ["/apollo/scripts/startup.sh"]

修改 docker file中對應服務的EXPOSE 端口號

  • admin 8090
  • config 8080
  • portal 8070

修改scripts/startup.sh 啓動腳本

  • 放開註釋掉的jvm 啓動配置,根據需求配置
## Adjust memory settings if necessary
export JAVA_OPTS="-Xms2560m -Xmx2560m -Xss256k -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=384m -XX:NewSize=1536m -XX:MaxNewSize=1536m -XX:SurvivorRatio=8"
  • 刪除 文件末尾的退出命令,添加tail 命令,否則腳本執行完之後服務啓動失敗
tail -f $LOG_DIR/$SERVICE_NAME.log
#exit 0;

構建鏡像

在三個解壓的根目錄下執行如下命令構建:

sudo docker build -t apollo-admin:v1 .
sudo docker build -t apollo-config:v1 .
sudo docker build -t apollo-portal:v1 .

推送到對應的鏡像倉庫

sudo docker tag apollo-admin:v1 [dockerregistry]:[port]/[namespace]/apollo-admin:v1 && \
sudo docker tag apollo-config:v1 [dockerregistry]:[port]/[namespace]/apollo-config:v1 && \
sudo docker tag [dockerregistry]:[port]/[namespace]/apollo-portal:v1

sudo docker push apollo-admin:v1 [dockerregistry]:[port]/[namespace]/apollo-admin:v1 && \
sudo docker push apollo-config:v1 [dockerregistry]:[port]/[namespace]/apollo-config:v1 && \
sudo docker push [dockerregistry]:[port]/[namespace]/apollo-portal:v1

部署到k8s

  • admin、config、portal 分別會配置各自的deployment + service 的yml配置文件,
  • 配置文件中的鏡像地址需要自行填寫
  • 增加探針是爲了保證 服務掛掉,但是docker容器沒有正常重啓的問題
  • depolyment和service的名稱最好和項目配置中的域名apollo-admin、apollo-config、apollo-portal保持一致

admin service

apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: apollo-admin
  namespace: prod
spec:
  replicas: 1
  selector:
    matchLabels:
      run: apollo-admin
  template:
    metadata:
      labels:
        run:  apollo-admin
    spec:
      containers:
      - name: apollo-admin-containers
        image: [dockerregistry]:[port]/[namespace]/apollo-admin:v1
        imagePullPolicy: Always
        livenessProbe:
          failureThreshold: 3
          httpGet:
            path: /
            port: 8090
            scheme: HTTP
          initialDelaySeconds: 30
          periodSeconds: 10
          successThreshold: 1
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /
            port: 8090
            scheme: HTTP
          initialDelaySeconds: 30
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        volumeMounts:
        - name: host-time
          mountPath: /etc/localtime
        ports:
        - containerPort: 8090
        resources:
          requests:
            memory: 6100Mi
          limits:
            memory: 6100Mi
      volumes:
      - name: host-time
        hostPath:
          path: /etc/localtime
---
apiVersion: v1
kind: Service
metadata:
  name: apollo-admin
  namespace: prod
  labels:
    run: apollo-admin
spec:
  ports:
  - port: 8090
    targetPort: 8090
  selector:
    run: apollo-admin

config service

apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: apollo-config
  namespace: prod
spec:
  replicas: 1
  selector:
    matchLabels:
      run: apollo-config
  template:
    metadata:
      labels:
        run: apollo-config
    spec:
      containers:
      - name: apollo-config-containers
        image: [dockerregistry]:[port]/[namespace]/apollo-config:v1
        imagePullPolicy: Always
        livenessProbe:
          failureThreshold: 3
          httpGet:
            path: /services/meta
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 30
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /services/meta
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 30
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        volumeMounts:
        - name: host-time
          mountPath: /etc/localtime
        ports:
        - containerPort: 8080
        resources:
          requests:
            memory: 7100Mi
          limits:
            memory: 7100Mi
      volumes:
      - name: host-time
        hostPath:
          path: /etc/localtime
---
apiVersion: v1
kind: Service
metadata:
  name: apollo-config
  namespace: prod
  labels:
    run: apollo-config
    system: paas
spec:
  ports:
  - port: 8080
    targetPort: 8080
  selector:
    run: apollo-config

portal service

創建portal.yml 如下

apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: apollo-portal
  namespace: prod
  labels:
    system: paas
spec:
  replicas: 1
  template:
    metadata:
      labels:
        run: apollo-portal
    spec:
      containers:
      - name: apollo-portal-containers
        image: [dockerregistry]:[port]/[namespace]/apollo-portal:v1
        imagePullPolicy: Always
        volumeMounts:
        - name: host-time
          mountPath: /etc/localtime
        ports:
        - containerPort: 8070
        resources:
          requests:
            memory: 4100Mi
          limits:
            memory: 4100Mi
      volumes:
      - name: host-time
        hostPath:
          path: /etc/localtime
---
apiVersion: v1
kind: Service
metadata:
  name: apollo-portal
  namespace: prod
  labels:
    run: apollo-portal
spec:
  ports:
  - port: 8070
    targetPort: 8070
  selector:
    run: apollo-portal

至此 apollo 容器化基本就算完成了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章