天天看點

微服務架構下的開發部署

微服務架構是近兩年興起的概念。在此之前,網際網路企業在生産環境的分布式系統中處理實際問題時就已經實際使用了微服務架構。例如最初的淘寶系統也是單體式應用,為了應對随着使用者量增大而帶來的系統處理能力不足的問題,淘寶對其應用系統進行了一系列服務化拆分和改造,淘寶開源的Dubbo架構以及其企業内部用的HSF架構都屬于微服務架構的實作成果。

本文将從以下幾個方面簡要說明微服務架構項目的實踐經驗:架構選型、開發測試環境下的相關工具支援、人員分工及開發部署流程、相關設計及注意事項。最後,将根據實踐經驗讨論提高微服架構下的開發和運維效率的切實需求,進一步理清本項目所實作的容器服務管理平台的完善性需求。

項目背景及微服務架構選型

本項目是一個企業級的容器服務管理平台,該平台的功能是基于容器實作的應用運作環境管理,以及應用開發階段的持續內建和持續釋出。簡單的了解該平台的核心功能之一就是管理複雜應用的開發和運維環境,提高微服務架構下的開發和運維效率。項目的開發背景如下:

首先,該系統具有典型分布式應用系統特征:

該平台所運作的伺服器配置不高,例如華為RH1288這類低配置伺服器,允許硬體失敗;

系統平台要求可根據實際使用者數的規模進行伸縮部署,保證硬體資源的合理利用;

由于系統平台之上需要運作若幹企業應用的開發和運作環境,可靠性是非常重要的,不允許單點失效。

其次,本系統功能複雜,從架構的角度需要将系統分成多個層次和若幹個子系統。不同的層次、子系統根據具體情況需要采用不同的開發語言,由不同的開發小組完成。

第三,項目組成員由幾個城市的異地團隊協同開發,統一的開發環境和協同工具是必不可少的。

針對上述項目背景的考慮,本項目選擇基于微服務架構進行項目開發。

開發、測試、部署使用到的工具集

“工欲善其事、必先利其器”,借助适合的流程和相關工具集,才能提高微服務架構下的應用開發效率。本項目利用DevOPs流程并選用一套相關工具集實作應用開發管理,提高開發、測試、部署的效率。

代碼庫:本項目使用分布式代碼庫Gitlab,它的功能不限于代碼倉庫,還包括reviews(代碼審查), issue tracking(問題跟蹤)、wiki等功能,是代碼管理和異地團隊溝通、協作工具的首選。

Docker鏡像倉庫、Docker:本項目用容器貫穿整個軟體開發流程,以容器作為應用釋出的載體,應用的開發環境和測試發版環境都運作在Docker容器中。對于複雜的開發和運維環境管理Docker具有先天的優勢,目前國内外的網際網路公司有大多數都已經将Docker應用到了他們的開發或者生産環境中了。

K8s:本項目采用Kubernates作為容器排程管理的基礎環境,開發環境、測試環境的Docker容器都由K8s負責排程管理。

Jenkins:快速的部署釋出離不開老牌持續內建明星Jenkins,本項目通過Jenkins任務建構代碼、将應用打包成Docker鏡像,最終釋出到K8s環境中将容器運作起來。

Shell腳本:編寫Shell腳本将項目打分支、釋出應用等開發階段的配置管理工作自動化,降低運維門檻、提高配置管理和運維的效率。

WIKI:Gitlib上的WIKI功能相對簡陋,是以項目組選擇dokuwiki作為異地團隊協作和溝通的工具,團隊成員可以将設計文檔、知識分享文檔、公告資訊等資訊可以更新到wiki上,便與協同開發。

禅道:為了便于開發計劃、開發任務和bug關聯起來,本項目使用禅道進行開發任務和bug管理。

人員分工及開發流程

微服務架構應用的開發、部署的複雜度都是遠大于單體式應用的,靠運維人員手工的配置管理顯然是難于應付了。DevOps主張以自動化任務處理方式實作軟體傳遞及基礎設施更新,可以說是微服務架構應用開發和運維的必要條件,本項目采用DevOps的理念的開發流程進行開發。實作部署和運維的自動化需要工具,同時DevOps強調軟體開發者與其他IT員工及管理層間的協作與溝通,是以明确的人員分工和開發流程是與工具同樣重要的因素。通俗的說,就是有了工具,大家要知道怎麼使用工具,并且願意使用工具才能真正達到提高研發效率的目的。

項目組的主要工作成員無非也是做開發、測試和系統管理三類工作,這裡隻說明與傳統的企業應用開發過程中三類人員所做的工作略有不同的工作内容。

開發人員:

a) 開發者做開發設計,需要将涉及到接口部分設計更新到wiki上,供調用者評審和調用。

b) 開發者除了編寫程式邏輯外,還需要注意編寫單元測試用例,因為分布式應用聯調相對複雜,先做在編寫單服務時做好了測試再聯調能夠提高開發效率。

c) 由于本項目是采用Docker容器作為釋出載體的,開發者可能需要修改DockerFile模闆裡的部分參數,便于部署階段能将編譯後的代碼打包到鏡像中。相對于傳統的開發方式,這是對開發者額外的要求。讓所有開發者懂Dockerfile似乎要求也有點高,其實每個子項目中的DockerFile及腳本一般是在搭建項目架構時,主要系統配置管理者編寫的好的模闆,若開發人員不懂相關技術,也可以跟配置管理者溝通需求,由配置管理者修改相關檔案。

測試人員:測試人員的工作沒有什麼特别,隻是需要注意除了每個Sprint階段的測試外,還需要配合開發人員持續內建的測試;

系統配置管理人員:一般DevOps的開發方式是依賴于雲基礎平台以及自動化釋出工具的,是以相對于傳統開發方式,對系統配置管理者的技術要求會比較低。但是,我們的項目開發目的就是建構一個能支撐DevOps流程的平台,其開發本身還不具備相應的平台基礎。是以,我們項目最初的系統配置管理工作是由架構師來做的,主要需要做如下這些事:

a) 部署運作項目組開發需要用到公共的服務元件、例如zookeeper注冊中心、Docker Registry鏡像倉庫、資料庫等;

b) 為子項目編寫在git上打分支的腳本,便于測試發版的時候打分支;

c) 編寫各類型應用釋出部署成鏡像的Dockerfile;

d) 制作或者在網上找到現成的開發所需環境的Docker鏡像,并且Push到項目開發使用的私有鏡像庫中;

e) 編寫Shell腳本實作将子項目打包成Docker鏡像,并且Push到鏡像倉庫中。

f) 在Jenkins上配置自動編譯或者部署任務,實作持續內建和部署。

本文将對項目的開發、部署聯調以及測試發版流程和規範做簡要說明,并提供項目各個階段使用到的部分自動化腳本工具示例。

微服務架構下的開發部署

代碼分支管理:

微服務架構下的開發部署
微服務架構下的開發部署

如圖所示,在git上建立的每一個項目都需要至少建立develop和master兩個分支。開發人員隻有權限把代碼送出到develop分支上,平時的持續內建和聯調都從develop分支上擷取代碼。 每個Sprint階段測試發版時,配置管理者從develop分支上建立一個用于測試的release分支。當測試修改bug時,開發人員隻把修改後的代碼送出到對應的測試Release分支上。當測試版本穩定後,由配置管理者将代碼合并到Master分支中。

自動部署和釋出:

項目借助于Shell腳本、Dockerfile、K8s配置檔案和Jenkins任務實作了自動化的持續內建和部署。配置管理者在項目目錄下編寫的腳本檔案結構如圖2所示。

a) 建立分支的shell腳本,示例見附件1;

#!/bin/bash
if [ -z "$1" ]; then
 cat <
         EOF  exit 1  fi DEPLOY_VERSION=$1 RP_FILES=(subproject1/kube-rc.yaml subproject1/pom.xml subproject1/Makefile) if [ -z $(git branch -a | grep -e /${DEPLOY_VERSION}$) ]; then   git branch ${DEPLOY_VERSION}  git checkout ${DEPLOY_VERSION}  else  git checkout ${DEPLOY_VERSION}  git pull  fi  #替換k8s配置檔案中環境指向,從開發切換到測試  #替換掉pom.xml檔案中的SNAPSHOT為release版本号  #替換掉makefile中釋出的鏡像Tag的latest為release版本号  for f in ${RP_FILES[@]}; do  sed -i -e "s#api.devproject.com#api.testproject.com#g" \  -e "s#           0.0.1-SNAPSHOT     #           ${DEPLOY_VERSION}-SNAPSHOT     #g" \  -e "s#latest#${DEPLOY_VERSION}#g" $f  done git commit -a -m "Create Branch ${DEPLOY_VERSION}"  git push origin ${DEPLOY_VERSION}            

b) Dockerfile示例檔案,将Java dubbo服務釋出為鏡像為例,示例見附件2:

FROM registry.xcompany.com/java:openjdk-7-jre
MAINTAINER zhangsan
ENV spring.profiles.active="production"
 ENV JAVA_OPTS="-Xmx1024m"
RUN mkdir -p /app 
 COPY target/subproject1.war /app/
 COPY ./startup.sh /app/
 RUN chmod +x /app/startup.sh
 WORKDIR /app
 CMD ["./startup.sh"]
EXPOSE 8080           

c) Makefile檔案: 包括編譯項目、将項目打包成Docker鏡像、将鏡像Push到鏡像倉庫、在K8s上建立ReplicationController、在K8s上建立service的指令腳本:

IMAGE_PREFIX = registry.xcompany.com/project1/
COMPONENT = subproject1
ifndef BUILD_TAG
 BUILD_TAG = latest
endif
IMAGE = $(IMAGE_PREFIX)$(COMPONENT):$(BUILD_TAG)
ifndef KUBE_OPS
 KUBE_OPS = --server=https://api.devproject.com --namespace=project1
endif
clean:
 mvn clean
compile: clean
 mvn -U -DskipTests=true -Dmaven.javadoc.skip=true package
#将目前程式打包成Docker鏡像
 build: 
 docker build -t $(IMAGE) .
 #将目前鏡像Push到鏡像倉庫
 push: 
 docker push $(IMAGE)
run: 
 docker run --rm -it -e spring.profiles.active=application -p 8080:8080 $(IMAGE)
 #部署RelicationController
 deploy:
 kubectl create -f kube-rc.yaml $(KUBE_OPS)
redeploy:
 kubectl replace -f kube-rc.yaml $(KUBE_OPS)
undeploy:
 kubectl delete -f kube-rc.yaml $(KUBE_OPS)
 #建立service
 deploy-svc:
 kubectl create -f kube-svc.yaml $(KUBE_OPS)
undeploy-svc:
 kubectl delete -f kube-svc.yaml $(KUBE_OPS)           

d) K8s部署配置檔案,建立ReplicationController、建立service示例見附件4:

#建立ReplicationController
apiVersion: v1
kind: ReplicationController
metadata:
  name: subproject1
spec:
  replicas: 1
  selector:
    name: subproject1
  template:
    metadata:
      labels:
        name: subproject1
    spec:
      containers:
        - name: subproject1
          image: registry.xcompany.com/project1/subproject1:latest
          imagePullPolicy: Always
          env:
            - name: DUBBO_REGISTRY_ADDRESS
              value: "kube://zookeeper:2181"
            - name: DUBBO_REGISTRY_REGISTER
              value: "true"
          ports:
            - containerPort: 8888
#建立Service
apiVersion: v1
kind: Service
metadata: 
  name: subproject1
  labels:
    component: subproject1
spec: 
  ports:
    - port: 8888
      nodePort: 16888
  selector: 
    name: svc-subproject1
  type: NodePor           

e) 配置管理者在Jenkins上配置自動或手動觸發的任務,在jenkins任務中配置shell腳本,可實作應用的一鍵部署,示例見附件5。

#!/bin/bash -e
IMAGE=registry.xcompay.com/project1/sub-project1:$IMAGE_VERSION
make compile
if [ $build = "true" ]; then
   echo "docker build -t $IMAGE"
   docker build -t $IMAGE .
   echo "docker push $IMAGE"
   docker push $IMAGE
fi
if [ $undeploy = "true" ]; then
make undeploy
fi
if [ $deploy = "true" ]; then
make deploy
fi
if [ $deploysvc = "true" ]; then
make deploy-svc
fi           

具體的過程說如下:

  1. 從Git上拉取代碼,編譯、釋出項目;
  2. 将編譯好的程式包,打包成Docker鏡像;
  3. 将打包好的Docker鏡像Push到鏡像倉庫;
  4. Jenkins執行Shell腳本指令,從鏡像倉庫拉取鏡像在K8s環境中建立pod和RC,将應用程式及其運作環境所在的容器在K8s平台上運作起來。

測試與發版:

從圖中可以看到,項目的開發環境和測試環境是互相隔離的兩套環境。

a) 部署在開發環境的應用代碼,來自develop分支,對應的Docker鏡像Tag用latest,供開發人員調試、以及測試人員随時協助做內建測試;

b) 部署在測是環境的應用代碼,來自每到一個Sprint階段發版測試時配置管理者從develop分支中打出的測試發版分支,分支名對應的版本号不同,相應的Docker鏡像的tag也會随是版本号改變。測試環境中部署的應用主要用于測試驗證。

部署聯調:

項目分為四層:前端UI、WEB層有若幹個web應用、Service層包括若幹個分布式服務、基礎底層。這裡簡要說明一下各層之間的調試方式:

a) 前端和Web層聯調:前端開發人員本地啟動一個Nginx,配置nginx.conf檔案将localhost代理指向web server的位址,即可在本地調試與動态Web端的互動。

b) WEB層與服務層聯調、服務層之間聯調、服務層與基礎層聯調,分為兩種方式:

本地調試:部署一個專用的zookeeper注冊中心,開發者可以把本機位址注冊到注冊中心,供相關人員臨時調用服務調試。

內建環境調試:送出代碼觸發Jenkins任務,将服務打包成容器鏡像,部署到K8s上在完整的系統運作環境中聯合調試。具體的內建環境編排依賴于k8s完成。

微服務的分層和服務互動設計

關于微服架構的利弊以及設計原則有很多著名的文章有介紹,例如MarinFowler的博文《Microservices:a definition of this new architectural term》和來自DZone community社群的《Microservices in Practice: From Architecture to Deployment》在InfoQ等技術網站都有中文翻譯,本文就不對概念和設計原則做過多贅述。本小節主要是說明關于項目的邏輯分層結構和服務互動方面的設計。

本項目遵守以下微服務架構的主要基本原則,但是也會根據具體項目情況有所保留。

  1. 單一責任原則(Single Responsibility Principle,SRP)
  2. 保證微服務設計能支援服務的靈活/獨立地開發和部署。
微服務架構下的開發部署

架構分層設計

如圖2所示,項目的架構是分為四層:靜态UI層、動态WEB層、業務服務層、基礎業務層。

  1. 靜态UI層,直接面向使用者的操作展示界面,包括靜态UI設計和JS互動代碼,主要采用Angulars架構;

ii.動态WEB層是各業務服務的“門面”,根據前端設計的需要調用、組裝業務服務層的API,相對來說,這一層變動的頻率較高,例如系統需要進行流程優化或者前端UE改造,相應的都要變更這一層。動态WEB層采用Java Spring或者python Django架構實作;

iii.業務服務層,根據業務需求按照功能對基礎服務層進行擴充和包裝,采用Dubbo分布式服務架構實作,具體版本是當當擴充過的Dubbox,支援REST API,并且對Spring的支援更新到了3.x;

  1. 基礎服務層比較穩定,提供一些基礎功能,采用Go語言/Ruby/Java/Python等多種語言實作的。

各層之間的互動通信設計

  1. 各層次之間以及同一層次之間的互動主要是按照微服務架構的設計原則,采用輕量式通信機制:REST API、Dubbo API(hessian協定)和異步消息實作的。

ii.但是也有些服務之間的互動是通過共享資料庫實作的,這一點是違背微服務架構強調的“獨立的服務、獨立的資料庫”的原則的。本項目每個服務盡可能使用獨立的資料表,但是采用了共享的資料庫。根據具體業務場景,綜合考慮技術成本、以及耦合帶來的風險大小等因素,部分不同層次的服務之間的互動是通過資料表實作的。這也是從單體式應用進化到分布式應用的一個折中方案,将來若系統規模擴大,要拆分資料庫代價并不會很大。但是不可否認,微服務架構下使用共享的資料庫是存在風險的,将來可能因為某些蹩腳的設計使得微服務之間的耦合性變大,導緻微服務不再“微”了。