滬江基於容器編排的Dev/Ops流程

貓飯先生發表於2017-10-10
【編者的話】我們整個 DevOps 流程是建立在容器編排的基礎上的,目的是簡化流程和實現自動化 CI/CD 和自動化運維。當中會有很多沒有想到的地方,可能也不太適用於複雜場景。

本文講的是滬江基於容器編排的Dev/Ops流程隨著 DevOps 和 SRE 概念的流行,越來越多的 developer 和 operater 們摒棄傳統的開發部署流程,轉向瞭如下圖所示的無線迴圈模式:
1.jpg


在我理解 DevOps 包含三個大塊:敏捷開發(Agile)、持續整合與交付(CI/CD)、自動運維(ITSM)。 

在容器化的時代,我們是如何實現 DepOps 或者 SRE 的呢?下面我就來分享一下滬江學習產品線團隊基於容器編排的 DevOps 流程。

敏捷開發

大道至簡,所有血的教訓告訴我們,不要把簡單的事情複雜化。換句話說,不要用複雜的方法處理簡單的事情。我對敏捷的理解是「快」和「微」。快指迭代快,開發快,上線快,效能快。微指微服務、微映象。圍繞這兩點,在開發階段我們需要做以下幾件事:

應用微服務化

這是個比較大的概念,不在這裡討論,有興趣可以參考我的其他文章。但只有應用小了,才有可能快起來。

給 Docker 映象瘦身

為了讓 Docker 啟動和執行得快,首先就是要對 Docker 瘦身。由於所有的應用全部會統一為 Java 語言開發,所以我們以 Java 為例,選用了 jre-alpine 作為我們的基礎映象,下面是 Dockerfile 的例子:

FROM java:8-jre-alpine

add timezone and default it to Shanghai

RUN apk –update add –no-cache tzdata
ENV TZ=Asia/Shanghai

RUN mkdir -p /app/log
COPY  ./target/xxx.jar  /app/xxx.jar

EXPOSE 9999
VOLUME [“/app/log”]
WORKDIR /app/

ENTRYPOINT [“java”,”-Xms2048m”, “-Xmx2048m”, “-Xss512k”, “-jar”,”xxx.jar”]
CMD [] 

使用上述 Dockerfile 生成的映象平均只有 80 多 MB,啟動時間幾乎在 5 秒內。使用 Alpine 映象雖然減小了體積,但缺少一些工具命令,例如 curl 等,可以根據需要酌情安裝。另外遇到的一個坑是時區問題:由於 Docker 映象內的時區是 UTC 時間,和宿主機的東 8 區不一致,所以必須安裝 timezone 工具並設定 TZ,才能使容器內時間和宿主機保持一致,對資料庫的寫入和日誌的輸出都是非常必要的一環。

把所有環境配置包含在映象中

早在虛擬機器時代,我們已經做到了使用包含依賴的虛擬機器映象來加速部署,那麼為什麼要止步於此呢?我們可以更進一步,把服務本身也包含在映象中,Docker 用了更輕量的方式已經實現了這一點。

這裡我們還要介紹一個概念,要讓製作的映象,能在所有安裝了 Docker 的伺服器上執行,而不在乎宿主機的作業系統及環境。借用 Java 的一句話來說:一次製作,多平臺執行。所以,我們還會把所有環境的配置檔案,以不同的檔名全部放入映象中,通過引數來選擇 Docker 啟動時使用的環境配置檔案。

值得注意的是,如果開發的應用是基於 Spring 框架的話,這個功能很好實現。但如果是其他語言開發,會有一定的開發量。

本文以預設 Java 開發當所有的開發工作完成後,推薦程式目錄結構是這樣的:

│   ├── main
│   │   ├── java
│   │   ├── resources
│   │   │   ├── application.yaml
│   │   │   ├── application-dev.yaml
│   │   │   ├── application-qa.yaml
│   │   │   ├── application-yz.yaml
│   │   │   ├── application-prod.yaml
│   │   │   ├── logback.xml
│   ├── test
├── scripts
│   ├── Dockerfile
│   ├── InitDB.sql
├── pom.xml 


持續整合與交付

自動化的持續整合和交付在整個 DevOps 流中起了重要的角色,他是銜接開發和運維的橋樑。如果這一環做的不好,無法支撐大量微服務的快速的迭代和高效運維。在這一環節,我們需要靈活的運用工具,儘量減少人蔘與,當然仍然需要圍繞「快」和「微」做文章。

如何減少人工參與到持續整合與持續交付呢?我們最希望的開發過程是:對著計算機說出我們的想要的功能,計算機按照套路,自動編碼,自動釋出到測試環境,自動執行測試指令碼,自動上線。當然,目前時代要實現自動編碼的過程還需要發明那隻「貓」。

2.jpg


但只要對測試有足夠信心,我們完全可以實現一種境界:在炎熱的下午,輕鬆地提交自己編寫的程式碼,去休息室喝杯咖啡,回來後看見自己的程式碼已經被應用在生產環境上了。在容器時代,我們可以很快速的實現這一夢想,其具體步驟如下圖:

3.jpg


Gitfolw 與 Anti-Gitflown

持續整合的第一步是提交程式碼(Code Commit),VCS 也由 CVS,SVN 進化到如今的 Git,自然不得不說一下 Gitflow。談起無人不曉的 Gitflow,大家一定會大談其優點:支援多團隊,設定多國家的開發人員並行開發,減小程式碼衝突或髒程式碼的上線概率。它的大致流程如下:

4.jpg


Gitflow 給我們展示了複雜團隊在處理不通程式碼版本的優雅解決方案,它需要feature、develop、release、hotfix、master 5 條分支來處理不同時段的並行開發。但這真的合適於一個不超過 20 人的本地合作團隊開發嗎?我們的開發團隊不足 6 人,每個人負責 3 個以上的微服務,幾乎不可能在同個專案上安排兩個以上的同學並行開發。

在初期我們準守規定並使用標準的 Gitflow 流程,開發人員立刻發現一個問題,他們需要在至少 3 條分支上來回的 merge 程式碼,且不會有任何程式碼衝突(因為就一個人開發),降低了開發的效率。這讓我意識到,Gitflow 模式也許並不適合於小團隊微服務的世界,一種反 Gitflow 模式的想法出現在腦海中。我決定對Gitflow 進行瘦身,化繁至簡。

5.jpg


我們把 5 條分支簡化為 3 條分支,其中 Master 分支的作用只是維護了最新的線上版本的作用,Dev 分支為開發的主要分支,所有的映象是以此分支的程式碼為源頭生成的。這時開發的過程變為:

  • 開發人員從 Dev 分支中 checkout 新的 feature 分支,在 feature 分支上進行開發
  • 當開發完成後 merge 回 Dev 分支中,根據 Dev 分支的程式碼打成映象,部署 QA 環境交給 QA 人員測試
  • 測試中如有 bug 便在新分支中修復問題迴圈步驟 2
  • 測試完成 merge 回 Master 分支


如此一來,只有從 Feature 把程式碼 merge 到 Dev 分支的一次 merge 動作,大大提升可開發效率。

使用Jenkins Pipeline

Jenkins 作為老牌 CI/CD 工具,能夠幫我們自動化完成程式碼編譯、上傳靜態程式碼分析、製作映象、部署測試環境、冒煙測試、部署上線等步驟。尤其是Jenkins 2.0 引入 Pipeline 概念後,以上步驟變的如此行雲流水。它讓我們從步驟 3 開始,完全可以無人值守完成整個整合和釋出過程。

工欲善其事必先利其器,首先我們必須要在 Jenkins 上安裝外掛 :

  1. Pipeline Plugin(如果使用Jenkins2.0預設安裝)
  2. Git
  3. Sonar Scaner
  4. Docker Pipeline Plugin
  5. Marathon


如果你第一次接觸 Jenkins Pipeline,可以從https://github.com/jenkinsci/p … AL.md找到幫助。

現在,我們開始編寫 Groove 程式碼。基於容器編排的 Pipeline 分為如下幾個步驟:

1、檢出程式碼

這個步驟使用 Git 外掛,把開發好的程式碼檢出。

stage(`Check out`)
gitUrl = "git@gitlab.xxxx.com:xxx.git"
git branch: "dev", changelog: false, credentialsId: "deploy-key", url: gitUrl 


2、Maven 構建 Java 程式碼

由於我們使用的是 Spring Boot 框架,生成物應該是一個可執行的 jar 包。

stage(`Build`)
sh "${mvnHome}/bin/mvn -U clean install"


3、靜態程式碼分析

通過 Sonar Scaner 外掛,通知 Sonar 對程式碼庫進行靜態掃描。

stage(`SonarQube analysis`)
// requires SonarQube Scanner 2.8+
def scannerHome = tool `SonarQube.Scanner-2.8`;
withSonarQubeEnv(`SonarQube-Prod`) {
 sh "${scannerHome}/bin/sonar-scanner -e -Dsonar.links.scm=${gitUrl} -Dsonar.sources=. -Dsonar.test.exclusions=file:**/src/test/java/** -Dsonar.exclusions=file:**/src/test/java/** -Dsonar.language=java -Dsonar.projectVersion=1.${BUILD_NUMBER} -Dsonar.projectKey=lms-barrages -Dsonar.projectDescription=0000000-00000 -Dsonar.java.source=8 -Dsonar.projectName=xxx"
} 



4、製作 Docker 映象

此步驟會呼叫 Docker Pipeline 外掛通過預先寫好的 Dockerfile,把 jar 包和配置檔案、三方依賴包一起打入 Docker 映象中,並上傳到私有 Docker 映象倉庫中。

stage(`Build image`)
docker.withRegistry(`https://dockerhub.xxx.com`, `dockerhub-login`) {
docker.build(`dockerhub.xxx.com/xxxx`).push(`test`) //test是tag名稱
} 


5、部署測試環境

通過事先寫好的部署檔案,用 Marathon 外掛通知 Marathon 叢集,在測試環境中部署生成好的映象。

stage(`Deploy on Test`)
sh "mkdir -pv deploy"
dir("./deploy") {
    git branch: `dev`, changelog: false, credentialsId: `deploy-key`, url: `git@gitlab.xxx.com:lms/xxx-deploy.git`
    //Get the right marathon url
    marathon_url="http://marathon-qa"
    marathon docker: imageName, dockerForcePull: true, forceUpdate: true, url: marathon_url, filename: "qa-deploy.json"
} 


6、自動化測試

執行事先測試人員寫好的自動化測試指令碼來檢驗程式是否執行正常。

stage(`Test`)
// 下載測試用例程式碼
git branch: `dev`, changelog: false, credentialsId: `deploy-key`, url: `git@gitlab.xxx.com:lms/xxx-test.git`
parallel(autoTests: {
    // 使用nosetests 執行測試用例
    sh "docker run -it --rm -v $PWD:/code nosetests nosetests -s -v -c conf
unapi_test.cfg --attr safeControl=1"
},manualTests:{
    sleep 30000
}) 


7、人工測試

如果對自動化測試不放心,此時可選擇結束 Pipeline,進行人工測試。為了說明整個流程,我們這裡選擇跳過人工測試環節。

8、部署生產環境

當所有測試通過後,Pipeline 自動釋出生產環境。

stage(`Deploy on Prod`)
input "Do tests OK?"
dir("./deploy") {
    //Get the right marathon url
    marathon_url="http://marathon-prod"
    marathon docker: imageName, dockerForcePull: true, forceUpdate: true, url: marathon_url, filename: "prod-deploy.json"
} 


最後我們來看看整個 Pipeline 的過程:

6.png


容器編排配置文件化

在介紹敏捷開發時,曾介紹過根據不同環境的配置引數部署到不同的環境。如何告知部署程式用什麼樣的配置檔案啟動服務,每個環境又用多少 CPU,記憶體和 instance 呢?

下面我們就來介紹一下容器編排的配置檔案。由於我們使用 Mesos+Marathon的容器編排方式,部署的重任從以前的寫部署指令碼變成了寫一個 Marathon 的配置,其內容如下:

{
"id": "/appName",
"cpus": 2,
"mem": 2048.0,
"instances": 2,
"args": [
"--spring.profiles.active=qa"
],
"labels": {
"HAPROXY_GROUP": "external",
"HAPROXY_0_VHOST": "xxx.hujiang.com"
},
"container": {
"type": "DOCKER",
"docker": {
  "image": "imageName",
  "network": "USER",
  "forcePullImage": true,
  "portMappings": [
    {
      "containerPort": 12345,
      "hostPort": 0,
      "protocol": "tcp",
      "servicePort": 12345
    }
  ]
},
"volumes": [
  {
    "containerPath": "/app/log",
    "hostPath": "/home/logs/appName",
    "mode": "RW"
  }
]
},
"ipAddress": {
"networkName": "calico-net"
},
"healthChecks": [
{
  "gracePeriodSeconds": 300,
  "ignoreHttp1xx": true,
  "intervalSeconds": 20,
  "maxConsecutiveFailures": 3,
  "path": "/health_check",
  "portIndex": 0,
  "protocol": "HTTP",
  "timeoutSeconds": 20
}
],
"uris": [
"file:///etc/docker.tar.gz"
]
} 


我們把這個配置內容儲存為不同的 Json 檔案,每個對應的環境都有一套配置檔案。例如 Marathon-qa.json,Marathon-prod.json。當 Pipeline 部署時,可以通過Jenkins Marathon 外掛,根據選擇不同的環境,呼叫部署配置,從而達到自動部署的目的。

自動化流程和部署上線分離與管理

開發部署如此的簡單快捷,是不是每個人都能方便的使用呢?答案是否定的,並不是因為技術上有難度,而是在於許可權。在理想的情況下,通過這套流程的確可以做到在提交程式碼後,喝杯咖啡的時間就能看見自己的程式碼已經被千萬使用者使用了。

但風險過大,我們並不是每個人都能像 Rambo 一樣 bug 的存在,大多數的情況還需要使用規範和流程來約束。就像自動化測試取代不了人工黑盒測試一樣,部署測試後也不能直接上生產環境,在測試通過後還是需要有個人工確認和部署生產的過程。

所以我們需要把自動化流程和最後的部署上線工作分開來,分別變成兩個 Job,並給後者單獨分配許可權,讓有許可權的人來做最後的部署工作。這個人可以是 Team leader、開發經理,也可以是運維夥伴,取決於公司的組織結構。

那這個部署的 Job 具體幹什麼呢?在容器編排時代,結合映象既構建物的思想,部署 Job 不會從程式碼編譯開始工作,而是把一個充分測試且通過的映象版本,通過 Marathon Plugin 部署到產線環境中去。這裡是 Deploy_only 的例子:

node(`docker-qa`){
if (ReleaseVersion ==""){
    echo "釋出版本不能為空"
    return
}
stage "Prepare image"
    def moduleName = "${ApplicationModule}".toLowerCase()
    def resDockerImage = imageName + ":latest"
    def desDockerImage = imageName + ":${ReleaseVersion}"
    if (GenDockerVersion =="true"){
        sh "docker pull ${resDockerImage}"
        sh "docker tag ${resDockerImage} ${desDockerImage}"
        sh "docker push ${desDockerImage}"
        sh "docker rmi -f ${resDockerImage} ${desDockerImage}"
    }

stage "Deploy on Mesos"
    git branch: `dev`, changelog: false, credentialsId: `deploy-key`, url: `git@gitlab.xxx.com:lms/xxx-test.git`  
    //Get the right marathon url
    echo "DeployDC: " + DeployDC
    marathon_url = ""
    if (DeployDC=="AA") {
        if (DeployEnv == "prod"){
          input "Are you sure to deploy to production?"
          marathon_url = "${marathon_AA_prod}"
        }else if (DeployEnv == "yz") {
          marathon_url = "${marathon_AA_yz}"
        }
    }else if ("${DeployDC}"=="BB"){
      if ("${DeployEnv}" == "prod"){
        input "Are you sure to deploy to production?"
        marathon_url = "${marathon_BB_prod}"
      }else if ("${DeployEnv}" == "yz") {
        marathon_url = "${marathon_BB_yz}"
      }
    }
    marathon docker: imageName, dockerForcePull: true, forceUpdate: true, url: marathon_url, filename: "${DeployEnv}-deploy.json"
} 


為什麼不把這個檔案跟隨應用專案一起放到 scripts 下呢?因為把部署和應用分開後,可以由兩撥人進行維護,兼顧公司的組織架構。

自動化運維

在 DevOps 的最後階段是運維階段。在容器時代,如何對龐大的映象製品進行運維呢?我們的目標是儘量實現自動化運維,這裡主要講述兩點:

容器的監控

容器的監控大致有兩種方式:物理機上安裝其他服務監控本機上的所有容器;通過 Mesos 或 Kubernates 自帶 API 監控容器狀態。兩種方式其實都需要在物理機上安裝相應的監控軟體或 Agent。

在我們團隊目前使用 cAdvisor + InfluxDB + Grafana 的組合套件實現對容器的監控。

首先需要在 Mesos 叢集中所有的 Agent 安裝 cAdvisor 。他負責把宿主機上所有執行中的容器資料以資料點(data point)形式傳送給時序資料庫(InfluxDB),下面是 cAdvisor 監控的一些資料點:

7.jpg


這些資料點經過 Grafana 整理,展示在介面上,這樣我們就能掌握具體容器的效能指標了。下面是一個 Grafana 的截圖:

8.jpg


除了對容器本身的監控,宿主機的監控也是必不可少的。由於監控的點有很多,這裡不一一例舉。

自動伸縮

有了監控指標只是實現了自動化運維的第一步,當業務請求發生大量增加或減少,通過人工監測是不能及時的進行相應的,況且還不一定有那麼多的人,7×24 小時的監控。一定需要有一套根據監控資料自行伸縮容的機制。在學習產品線,我們針對容器編排的 Mesos+Marathon 框架,開發了一套針對應用本身的自動擴容微服務。其原理如下:

9.png


  • 通過 Restful 的介面通知 AutoScaler 程式需要監控的應用服務。
  • AutoScaler 程式開始讀取每臺 Agent 上部署相關應用的 Metrics 資料,其中包括 CPU,記憶體的使用狀況。
  • 當發現有應用過於繁忙(其表現形式大多是 CPU 佔用過高或記憶體佔用過大)時呼叫 Marathon API 將其擴容
  • Marathon 收到訊息後,立刻通知 Mesos 叢集釋出新的應用,從而緩解當前的繁忙狀況。


結束語

DevOps 和 SRE 並不是一個渴望而不可及的概念,它們需要在不同的環境中落地。我們整個 DevOps 流程是建立在容器編排的基礎上的,目的是簡化流程和實現自動化 CI/CD 和自動化運維。當中會有很多沒有想到的地方,可能也不太適用於複雜場景。其次,本文中的例子也做了相應的隱私處理,可能無法直接使用。希望大家能通過我們在實踐中產生的成功和遇到的問題,提煉出適合自己的 DevOps 流程。

原文連結:基於容器編排的Dev/Ops流程(作者:黃凱)

原文釋出時間為:2017-10-01

本文作者:黃凱

本文來自雲棲社群合作伙伴Dockerone.io,瞭解相關資訊可以關注Dockerone.io。

原文標題:滬江基於容器編排的Dev/Ops流程


相關文章