微服務架構下的開發部署實踐(1)

微服務架構是近兩年興起的概念。在此之前,互聯網企業在生產環境的分散式系統中處理實際問題時就已經實際使用了微服務架構。例如最初的淘寶系統也是單體式應用,為了應對隨著用戶量增大而帶來的系統處理能力不足的問題,淘寶對其應用系統進行了一系列服務化拆分和改造,淘寶開源的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上配置自動編譯或者部署任務,實現持續集成和部署。

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

    代碼分支管理:

    如圖所示,在git上創建的每一個項目都需要至少建立develop和master兩個分支。開發人員只有許可權把代碼提交到develop分支上,平時的持續集成和聯調都從develop分支上獲取代碼。 每個Sprint階段測試發版時,配置管理員從develop分支上創建一個用於測試的release分支。當測試修改bug時,開發人員只把修改後的代碼提交到對應的測試Release分支上。當測試版本穩定後,由配置管理員將代碼合併到Master分支中。

    自動部署和發布:

    項目藉助於Shell腳本、Dockerfile、K8s配置文件和Jenkins任務實現了自動化的持續集成和部署。配置管理員在項目目錄下編寫的腳本文件結構如圖2所示。

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

    #!/bin/bashif [ -z "$1" ]; then cat <<EOFUsage: branch-release.sh <version>EOF exit 1fiDEPLOY_VERSION=$1RP_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 pullfi#替換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#<version>0.0.1-SNAPSHOT</version>#<version>${DEPLOY_VERSION}-SNAPSHOT</version>#g" -e "s#latest#${DEPLOY_VERSION}#g" $fdonegit commit -a -m "Create Branch ${DEPLOY_VERSION}"git push origin ${DEPLOY_VERSION}

    b) Dockerfile示例文件,將Java dubbo服務發布為鏡像為例,示例見附件2:

    FROM registry.xcompany.com/java:openjdk-7-jreMAINTAINER zhangsanENV 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.shWORKDIR /appCMD ["./startup.sh"]EXPOSE 8080

    c) Makefile文件: 包括編譯項目、將項目打包成Docker鏡像、將鏡像Push到鏡像倉庫、在K8s上創建ReplicationController、在K8s上創建service的命令腳本:

    IMAGE_PREFIX = registry.xcompany.com/project1/COMPONENT = subproject1ifndef BUILD_TAG BUILD_TAG = latestendifIMAGE = $(IMAGE_PREFIX)$(COMPONENT):$(BUILD_TAG)ifndef KUBE_OPS KUBE_OPS = --server=https://api.devproject.com --namespace=project1endifclean: mvn cleancompile: 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)#部署RelicationControllerdeploy: 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)#創建servicedeploy-svc: kubectl create -f kube-svc.yaml $(KUBE_OPS)undeploy-svc: kubectl delete -f kube-svc.yaml $(KUBE_OPS)

    d) K8s部署配置文件,創建ReplicationController、創建service示例見附件4:

    #創建ReplicationControllerapiVersion: v1kind: ReplicationControllermetadata: name: subproject1spec: 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

    #創建ServiceapiVersion: v1kind: Servicemetadata: name: subproject1 labels: component: subproject1spec: ports: - port: 8888 nodePort: 16888 selector: name: svc-subproject1 type: NodePor

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

    #!/bin/bash -eIMAGE=registry.xcompay.com/project1/sub-project1:$IMAGE_VERSIONmake compileif [ $build = "true" ]; then echo "docker build -t $IMAGE" docker build -t $IMAGE . echo "docker push $IMAGE" docker push $IMAGEfiif [ $undeploy = "true" ]; then make undeployfiif [ $deploy = "true" ]; then make deployfiif [ $deploysvc = "true" ]; then make deploy-svcfi

    具體的過程說如下:

    i. 從Git上拉取代碼,編譯、發布項目;

    ii. 將編譯好的程序包,打包成Docker鏡像;

    iii. 將打包好的Docker鏡像Push到鏡像倉庫;

    iv. 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層與服務層聯調、服務層之間聯調、服務層與基礎層聯調,分為兩種方式:

  1. 本地調試:部署一個專用的zookeeper註冊中心,開發者可以把本機地址註冊到註冊中心,供相關人員臨時調用服務調試。
  2. 集成環境調試:提交代碼觸發Jenkins任務,將服務打包成容器鏡像,部署到K8s上在完整的系統運行環境中聯合調試。具體的集成環境編排依賴於k8s完成。
  • 微服務的分層和服務交互設計
  • 關於微服架構的利弊以及設計原則有很多著名的文章有介紹,例如MarinFowler的博文《Microservices:a definition of this new architectural term》和來自DZone community社區的《Microservices in Practice: From Architecture to Deployment》在InfoQ等技術網站都有中文翻譯,本文就不對概念和設計原則做過多贅述。本小節主要是說明關於項目的邏輯分層結構和服務交互方面的設計。

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

      i. 單一責任原則(Single Responsibility Principle,SRP)

      ii. 保證微服務設計能支持服務的敏捷/獨立地開發和部署。

        圖 2分層結構及通信機制

        架構分層設計

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

        i. 靜態UI層,直接面向用戶的操作展示界面,包括靜態UI設計和JS交互代碼,主要採用Angulars框架;

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

        iii.業務服務層,根據業務需求按照功能對基礎服務層進行擴展和包裝,採用Dubbo分散式服務框架實現,具體版本是噹噹擴展過的Dubbox,支持REST API,並且對Spring的支持升級到了3.x;

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

        各層之間的交互通信設計

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

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

    推薦閱讀:

    Kubernetes 在華為全球 IT 系統中的實踐 | 架構師實踐日
    容器編排之Kubernetes多租戶網路隔離
    從 MVC 到微服務,技術演變的必經之路 | 架構師實踐日
    容器編排之Kubernetes認證與授權
    攜程容器雲優化實踐

    TAG:DevOps | 微服务架构 | 容器云 |