對于DevOps的理解大家眾說紛紜,就連維基百科(Wikipedia)都沒有給出一個統一的定義,一般的解釋都是從字面上來理解,就是把開發(Development)和運維(Operations)整合到一起,來加速產品從啟動到上線的程序,并使之自動化,這個是對DevOps的廣義解釋,而且大多數人都是認可的,但這個解釋太寬泛了,幾乎包括了IT的所有內容,使之沒有太大意義, 而DevOps是近幾年才興起的(2014年才開始流行),它是對某種專案模式的描述,是有著其特定內涵的,任何專案都可以分成開發和運維兩個部分,而開發的一整套流程和工具在DevOps之前早就有了,并沒有改變,
DevOps真正改變的是運維,因此從運維的角度去理解DevOps,才能抓住它的本質,你可以把它理解為用開發的方式做運維(Operation as Development),這就是對它的狹義的理解, 開發的方式就是寫代碼,換句話說DevOps就是通過寫代碼來做運維,運維里一個非常流行的概念叫“Iac(InfrastructureAsCode)” 基礎設施即代碼,也就是把運行環境的創建用代碼的形式來描述出來,通過運行代碼來創建環境,它是運維領域的一場革命,開創了現代運維技術,它也是DevOps的基石,但基礎設施創建只是運維的一部分,如果我們把這場革命繼續延伸到運維的各個領域,讓代碼覆寫整個運維,那時就是代碼即運維(Operation as Code),這才是DevOps的精髓,
那么從一個應用程式專案的角度看,什么是DevOps呢?它就是把應用程式的代碼和運維的代碼都放到一個源程式庫中,并對它進行版本管理,這樣你就擁有了關于這個專案的所有資訊,隨時可以部署這個程式(包括程式本身和它的運行環境),而且可以保證每次創建出來的程式的運行結果是一樣的(因為它的運行環境也是一樣的),
運維即代碼(Operation as Code):
下面我們就把運維所做的事情一件一件拆分開,看看他們是怎么用代碼來實作的,
運維的作業通常包括下面幾個方面:
- 基礎設施:即程式運行環境的創建和維護,
- 持續部署:部署應用程式,并使整個程序自動化,
- 服務的健壯性:是指當服務的的運行環境出現了問題,例如網路故障或服務過載或某些微服務宕機的情況下,程式仍能夠提供部分或大部分服務,
- 運行監測:它既包含對程式的監測也包含對運行環境的監測,
基礎設施即代碼 (Infrastructure as code)
我們通過一個Go(別的語言也大同小異)微服務程式做例子來展示如何用代碼來創建基礎設施,程式本身的功能非常簡單,只是用SQL陳述句訪問資料庫中的資料,并寫入日志,你可以簡單地把它分成兩層,后端資料訪問層和資料庫層,程式的部署環境是基于k8s的容器環境,在k8s中它被分成兩個服務,一個是后端程式服務,另一個是資料庫(用MySQL)服務,后端程式要呼叫資料庫服務,然后會把一些資料寫入日志,
在這種新的模式下,運行環境的代碼和應用程式的代碼是存在一個原始碼庫中的,這樣當你下載了原始碼庫之后,你不但擁有了程式的所有原始碼,而且也擁有了運行環境的原始碼,這樣當要創建新的運行環境時,只要運行一遍代碼就能創建出整套的運行環境,而且每次創建出來的環境都是一致的,

上面就是這個Go程式的目錄結構,它里面有一個目錄“script”是專門存放與運行環境相關的檔案的,里面的“kuburnetes”子目錄就是整個運行環境的代碼,除了“script”之外的其它目錄存有應用程式的代碼,這樣,與這個應用程式有關的資訊都以代碼的方式保存在了一個源代碼庫,有了它之后,你可以隨時部署出一個相同的程式的運行環境,而且保證是一模一樣的,
“kubernetes”目錄下有兩個子目錄“backend”和“database”分別存放后端程式和資料庫的組態檔,它們內部的結構是類似的,都有三個“yaml”檔案:
- backend-deployment.yaml:部署組態檔,
- backend-service.yaml:服務組態檔
- backend-volume.yaml:持久卷組態檔.
另外還有一個“.sh”檔案是它的運行命令,當你運行這個shell檔案時,它就呼叫上面三個k8s組態檔來創建運行環境,
kubernetes目錄的最外層有兩個“yaml”檔案“k8sdemo-config.yaml”和"k8sdemo-secret.yaml",它們是用來創建k8s運行環境引數的,因為它們是被不同服務共享的,因此放在最外層,另外還有一個"k8sdemo.sh"檔案是k8s命令檔案,用來創建k8s物件,
這種源程式結構的一個好處就是使應用程式和它的運行環境能夠更好地集成,舉個例子,當你要修改服務的埠時,以前,你需要在運行環境和原始碼里分別修改,但它是分別由開發和運維完成的,這很容易造成修改的不同步,當你把它們放在同一個原始碼庫中,只需要修改一個地方,這樣就保證了應用程式和運行環境的一致性,
下面就是后端服務的k8s配置代碼:
apiVersion: v1
kind: Service
metadata:
name: k8sdemo-backend-service
labels:
app: k8sdemo-backend
spec:
type: NodePort
selector:
app: k8sdemo-backend
ports:
- protocol : TCP
nodePort: 32080
port: 80
targetPort: 8080
由于篇幅的關系,這里就不詳細解釋程式了,有興趣的請參見如何把應用程式遷移到k8s.
基礎設施可以分成兩個層面,一個就是上面講到的k8s層面,也就是容器層面,這個是跟應用程式緊密相關的,還有一個層面就是容器下面的支持層,也就是虛機層面,當然還包括網路,負載均衡等設備或軟體,當你在阿里云或華為云上創建k8s之前,先要把這些構建好才行,它的部署也可以用代碼來完成,Terraform就是一款非常流行的用來完成創建的工具,它是被ThoughtWorks推薦的(詳見ThoughtWorks TECHNOLOGY RADAR VOL.20 ),它支持用代碼來創建虛機,
代碼如下:
resource "aws_instance" "example" {
count = 10
ami = "ami-v1"
instance_type = "t2.micro"
}
但在這一層面,基礎設施的作業與應用程式的關聯并沒有那么緊密,因此這部分的代碼沒有放在應用程式里,你可以單獨創建一個基礎設施的原始碼專案,用來存放這部分代碼,
持續部署(Continuous Deployment)
部署應用程式是運維的一項重要作業,隨著商業競爭的加劇,要求更快的程式更新,從原來的的幾周部署一次,到后來的一天部署十幾次甚至幾十次,這樣手工部署就完全不能滿足需要,于是就要把整個流程自動化,這就是持續部署,
管線(pipeline)是一個很重要的概念,它用來描述持續部署的整個步驟和流程,Jenkin是一款非常流行的持續集成和部署工具,它提出了“管線即代碼”(“Pipeline as code”,詳見Pipeline),就是把持續部署的管線也作為程式原始碼的一部分,和應用程式一起管理起來,讓它有著和應用程式一樣的版本和復審流程,
下面我們就通過一個具體例子來說明他是怎樣實作的,這個例子用的是和上面一樣的程式,先來看一下程式的目錄結構,

與持續部署相關的檔案都在“script”目錄下,他被分成兩部分,一個是“cd”子目錄,它存有Jenkins的管線,另一個是“Kubernetes”下的“jenkins”子目錄,它存有Jenkinsde的k8s組態檔,你如果仔細看一下的話會發現它里面的檔案和前面講到的后端程式和資料庫的k8s組態檔很相似,有了它,你就可以在k8s里創建出Jenkins的運行環境,
這里我把Jenkins的k8s組態檔也放在應用程式里了,但實際上它是應該放在前面提到的基礎設施專案原始碼里,因為Jenkins是被應用程式共享的,而不是屬于單獨的一個應用,這里為了說明方便放在一起了,真正用的時候要把它抽取出來,
下面就是管線的代碼:
def POD_LABEL = "k8sdemopod-${UUID.randomUUID().toString()}"
podTemplate(label: POD_LABEL, cloud: 'kubernetes', containers: [
containerTemplate(name: 'modified-jenkins', image: 'jfeng45/modified-jenkins:1.0', ttyEnabled: true, command: 'cat')
],
volumes: [
hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')
]) {
node(POD_LABEL) {
def kubBackendDirectory = "/script/kubernetes/backend"
stage('Checkout') {
container('modified-jenkins') {
sh 'echo get source from github'
git 'https://github.com/jfeng45/k8sdemo'
}
}
stage('Build image') {
def imageName = "jfeng45/jenkins-k8sdemo:${env.BUILD_NUMBER}"
def dockerDirectory = "${kubBackendDirectory}/docker/Dockerfile-k8sdemo-backend"
container('modified-jenkins') {
withCredentials([[$class: 'UsernamePasswordMultiBinding',
credentialsId: 'dockerhub',
usernameVariable: 'DOCKER_HUB_USER',
passwordVariable: 'DOCKER_HUB_PASSWORD']]) {
sh """
docker login -u ${DOCKER_HUB_USER} -p ${DOCKER_HUB_PASSWORD}
docker build -f ${WORKSPACE}${dockerDirectory} -t ${imageName} .
docker push ${imageName}
"""
}
}
}
stage('Deploy') {
container('modified-jenkins') {
sh "kubectl apply -f ${WORKSPACE}${kubBackendDirectory}/backend-deployment.yaml"
sh "kubectl apply -f ${WORKSPACE}${kubBackendDirectory}/backend-service.yaml"
}
}
}
}
由于篇幅的關系,這里不詳細解釋,如果有興趣并想了解如何運行Jenkins來完成持續部署,請參閱 在容器上構建持續部署及最佳實踐初探.
這里我用的是Jenkins軟體,它另外還有一個子專案叫Jenkins-x,是專門為k8s環境量身打造的,它的主要功能是能夠幫助你自動生成管線代碼(你需要回答他的一些問題),如果你不想自己寫代碼,那么你可以試一下它,詳情請見Jenkins-X,
服務的韌性(Service Resilience as Code)
又叫服務的健壯性,這部分不像前面兩個部分有著公認的名字,英文叫“Service Resilience”,翻譯成中文就五花八門了,我覺得叫服務的韌性比較合適,
“Service Resilience”是指當服務的的運行環境出現了問題,例如網路故障或服務過載或某些微服務宕機的情況下,程式仍能夠提供部分或大部分服務,這時我們就說服務的韌性很強,它是衡量服務質量的一個重要指標,
這部分的功能包括下面幾個部分:
- 服務超時 (Timeout)
- 服務重試 (Retry)
- 服務限流(Rate Limiting)
- 熔斷器 (Circuit Breaker)
- 故障注入(Fault Injection)
- 艙壁隔離技術(Bulkhead)
這部分與前面兩個部分略有不同,前面兩個部分都是典型的運維任務,而這部分以前是應用程式的一部分,只是這些年才慢慢開始轉移到運維的,
最開始的時候,這些功能都是和程式的業務邏輯混在一起,對業務邏輯的侵入很大,后來,大家開始把這部分邏輯抽取出來,劃分成單獨的一部分,下面通過一個具體的例子(Go微服務程式)來講解:

上圖是程式的目錄結構,它分為客戶端(client)和服務端(server),它們的內部結構是類似的,“middleware”包是實作服務韌性功能的包, “service”包是業務邏輯包,在服務端就是微服務的實作函式,在客戶端就是呼叫服務端的函式,在客戶端(client)下的“middleware”包中包含四個檔案并實作了三個功能:服務超時,服務重試和熔斷器,“clientMiddleware.go"是總入口,在服務端(server)下的“middleware”包中包含了兩個檔案并實作了一個功能,服務限流,“serverMiddleware.go"是總入口,
注意,這里的服務韌性的功能是完全從業務邏輯中抽出來了,對業務邏輯沒有任何侵入,它是在一個單獨的包(middleware)里實作的,這里用的是修飾模式,有關程式實作的詳情,請參閱Go微服務容錯與韌性(Service Resilience),
上面講的是用程式來實作這些功能,但從本質上來講這些功能不應該屬于應用程式,而是應該由基礎設施來完成,現在公認的看法是,服務網格(Service Mesh)是完成這些功能的最佳方案,使用服務網格的方式和k8s類似,也是創建組態檔,然后通過運行組態檔來建立服務網格的運行環境,我們這里用Istio來舉例說明,Istio是一款非常流行的服務網格軟體,

上圖就是下載的Istio軟體的目錄,“bookinfo”是它的一個示例程式,在這個例子里,它展示了多種使用Istio的方式,其中就有如何實作服務韌性的方法,詳情請參見istio.
運行監測 (Monitoring or Observability)
運行監測是運維的一項重要內容,它通常包含如下幾個方面的內容:
- 日志(logging): 記錄的是程式運行程序中的資訊
- 跟蹤(tracing): 記錄的是與一條請求相關的資訊,特別是請求的與時間有關的資訊,
- 指標(metrics): 與上面的離散的資訊不同,這里記錄的是可累加的資訊,一般是按照時間軸進行累加,
我們經常稱之為觀測的三個支柱(Three Pillars of Observabilty),有一篇很不錯的講解它們之間關系的文章,詳情請見"Metrics, tracing 和 logging 的關系",
這部分的內容可能會有些爭議,因為前幾個部分都是清清楚楚的運維作業,即使服務韌性, 雖然以前是開發的作業,但現在也已經一直公認是運維的事,而且它們的代碼都能很干凈的摘出來,但運行監測不一樣,雖然主要作業還是由運維來完成,但它的代碼與業務邏輯代碼混在一起,很難摘得清楚,
日志:
這部分的代碼都是在應用程式里,但日志的采集,匯總,分析和展示都是由運維來完成,它的代表就是著名的ELK系列,采用DevOps之后,這里面的變化不大,頂多就是采集代理(Agent)更好地和服務網格或k8s進行集成,使之變得更為容易,
分布式跟蹤:
這部分有點像服務韌性,開始的時候是由程式完成,慢慢地把它變成單獨的部分與應用程式隔離開,最終大部分的作業交由服務網格來完成,但它又與服務韌性不太相同,服務韌性可以和應用程式做一個非常干凈的切割,而分布式跟蹤取決于跟蹤的顆粒度,如果僅是服務之間的跟蹤,就一點問題都沒有,完全可以由服務網格來完成,但如果是服務內部的跟蹤,服務網格就無能為力了,還是要由程式代碼來完成,就像日志一樣,但我覺得服務之間的跟蹤是投入產出比最高的,大多數情況下有它就足夠了,不必需要服務內部的跟蹤,
詳細情況請參見Go微服務全鏈路跟蹤詳解
Metrics:
這部分觀測的是累加資訊,大多數情況下,只要安裝好工具,就能采集資料進行分析,最流行的工具是Prometheus. 你不需要寫代碼來獲取資料,不過你如果想要快速地找到需要的資訊,k8s的配置還是要和Prometheus的設定相匹配,因此你需要做一些詳細的設計,詳細情況請參見Prometheus and Kubernetes: Monitoring Your Applications,
當然你如果有一些更細致的監測需求,Prometheus不能直接滿足,這時需要在應用程式里插入一些Prometheus的監測代碼來滿足你的需要,
其他作業
是不是還有其他運維作業被漏掉了?
持續集成(Continious Integration)
很多人都把持續集成(CI)算作DevOps的重要組成部分,那是因為他用的是廣義的定義,按照狹義的理解,DevOps只包括運維的內容,持續集成(CI)與持續部署(CD)有著明顯的不同,持續集成是開發的作業,而持續部署是運維的作業,下圖展示了它們的差異,

圖片來源
如圖所示,整個流程是這樣的:
程式員從原始碼庫(Source Control)中下載源代碼,撰寫程式,完成后提交代碼到原始碼庫,持續集成(Continuous Integration)工具從原始碼庫中下載源代碼,編譯源代碼,然后提交到運行庫(Repository),然后持續交付(Continuous Delivery)工具從運行庫(Repository)中下載代碼,生成發布版本,并發布到不同的運行環境(例如DEV,QA,UAT, PROD),
圖中,左邊的部分是持續集成,它主要跟開發和程式員有關;右邊的部分是持續部署,它主要跟測驗和運維有關,持續交付(Continuous Delivery)又叫持續部署(Continuous Deployment),它們如果細分的話還是有一點區別的,但我們這里不分得那么細,統稱為持續部署
我并沒有把持續集成放到DevOps里面,因為本文用的是狹義的解釋,也就是只包含運維的部分,
結論
本文從代碼的視角詮釋了對DevOps的理解,DevOps的精髓就是用寫代碼的方式來做運維,并對運維的各個部分給出了具體的實體,希望能對想采用DevOps的朋友有所幫助,DevOps對開發和運維的改變都是巨大的,尤其是對運維,在不久的將來,就沒有開發和運維之分了,只有一個作業,就是寫代碼,當然也許會細分成開發碼農和運維碼農,運維的作業都是通過寫代碼來完成,應用程式里不但包括業務邏輯的代碼,也包括運維的代碼,它們會被同時存盤在一個原始碼庫中,
原始碼庫
完整原始碼的github鏈接:
- k8sdemo
- grpcservice
索引:
- InfrastructureAsCode
- 如何把應用程式遷移到k8s
- ThoughtWorks TECHNOLOGY RADAR VOL.20
- Pipeline
- 在容器上構建持續部署及最佳實踐初探
- Go微服務容錯與韌性(Service Resilience)
- istio
- Metrics, tracing 和 logging 的關系
- Go微服務全鏈路跟蹤詳解
- Prometheus and Kubernetes: Monitoring Your Applications
- Nexus Platform Overview
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/39909.html
標籤:架構設計
上一篇:Nginx配置實體-動靜分離實體:搭建靜態資源服務器
下一篇:設計模式-行為型-迭代器模式
