前言
本文為《用開源軟件打造企業級 DevOps 工作流》系列的第三篇文章。接著上一篇 ,本篇文章主要講介紹 DevOps 工作流中另一個核心模塊 持續集成(CI),這可以說是 DevOps 中的重中之重,因為這涉及到自動化管理項目的部署過程。有了持續集成,我們就可以從手動部署中解脫出來,把時間用在更重要的事情上,例如代碼重構、迴歸測試、架構設計等等。
在本篇文章中,我們將介紹持續集成的基本概念,為什麼用持續集成,以及如何利用開源軟件 Jenkins 來實現持續集成。
持續集成簡介
本系列文章的第一篇概述中我們提到,持續集成可以類比於工廠中的流水線操作。例如生產一輛汽車,一切從裸車架開始,車架用吊鉤送上傳送帶,經過裝配、打磨、測試等一系列複雜而緊湊的統一化流程,最終產出一輛功能齊全、質量卓越的完整汽車。
持續集成類似軟件開發中的流水線,只是持續集成中的流水線通常來說是自動化的,也就是說不需要任何人為干預,一個軟件程序就可以從源碼開始,經過一系列的構建步驟,最終形成成熟的產品。什麼時候啟動這個流水線呢?一般來說,是代碼變更的時候。其實,這裡所說的流水線還包括了部署的過程,一般說持續集成通常會提到 持續部署(CD)。嚴格意義上來說這是兩個概念:持續集成(CI)是指不斷將代碼不斷同步到主幹,持續部署是指代碼經過評審後自動部署到生產環境。為了方便起見,我們將這兩個概念合二為一,統稱為 CI/CD 。

例如上圖這個持續集成流水線,當持續集成工具檢測到源代碼有變更的時候,會啟動這個流水線:
- 首先獲取源代碼;
- 並行構建前後端應用;
- 構建 Docker 鏡像;
- 單元測試;
- 完成部署。
經歷了這幾個步驟之後,一次集成過程就算完成了。整個流程不需要人為干預,唯一需要開發者做的,就是提交一次代碼更新代碼倉庫,持續集成工具會自動將後面的事情(包括部署)一步步幫你做完,全程自動化。
為什麼用 CI/CD
其實我們可以不用 CI/CD 來完成從編碼到部署的過程。那麼,為什麼我們要用 CI/CD 呢?首先我們看看下面一個流程圖。

這是一個從碼代碼到最終部署交付的流程例子:我們花數小時碼代碼(Coding),不到 1 分鐘提交代碼(Commit),然後構建(Build)這個應用花 1 分鐘到 1 小時(根據應用複雜程度),接著花數分鐘部署(Deploy)這個應用,最後測試(Test)。除開代碼和提交的時間,後面的構建+部署時間最少也得花幾分鐘,而測試可能會花更久。
我們要知道的是,構建和部署一般來說是重複性的工作,例如運行一次 npm run build:prod 打包前端代碼,或者將打包好後的前端靜態文件拷貝到目標地址。如果是人來操作這些流程,非常浪費時間和精力。雖然構建部署一次可能花不了多少時間,但考慮到企業中通常是多人協同工作,每個人都會提交代碼,而且很可能是多次提交,這樣需要執行的次數就是遠不止一次或幾次了。你簡單做一做乘法,就知道會有多少人工時間成本會花費在上面,而人工成本目前來說是越來越貴的。因此,使用持續集成將降低時間成本(Time Expense)。
另外,人工來處理構建部署等流程容易出錯。人無完人,即使是富有經驗的架構師上線部署也可能會出現操作失誤導致系統上線失敗。在作者的職業生涯中,發現有很多因為人的原因部署失敗的情況,例如忘記更改環境變量、數據庫連接串配置錯誤、執行命令輸入錯誤等等。而這些都可以通過自動化的流程來避免,因為機器是很死板的,一旦設置好,就會原封不動的按規定執行,不會出現操作失誤(當然排除諸如網絡原因等導致的錯誤)。因此,使用持續集成還可以降低
人為錯誤(Human Error)。上面這個流程圖中紅色部分(編碼和提交)是無法自動化的,需要人工操作;而綠色部分(構建和部署)是可以自動化的,也是需要儘可能全部自動化的;黃色部分(測試)是半人工半自動化的,因為我們可以通過 單元測試(Unit Test) 來自動化很多測試工作,但一些複雜的測試用例(例如頁面的排版是否符合設計預期)則需要人工來完成,因此測試部分是半自動化的。這裡多提一下單元測試,固然單元測試是非常有助於提升代碼的工程質量,但是編寫單元測試卻相當耗費時間,差不多跟寫功能的時間差不多,因此會將整個項目時間加倍,所以需要在時間與質量之間權衡是否需要採用單元測試。
開源工具 Jenkins
要實現 CI/CD,我們需要一些工具。我們推薦的工具是 Jenkins,這也是企業環境中用的比較多的持續集成開源軟件。下面我們將詳細介紹一下 Jenkins,包括基本介紹、安裝、基本使用以及如何實踐應用到 DevOps 工作流中。
Jenkins 簡介
可能你對 Jenkins 本身並不陌生,這是因為 Jenkins 從 2011 年開始就已經存在,是最早的也是最常用的選擇。它出自 Sun 公司的一個工程師的業餘項目,不斷改進優化後逐漸壯大成為最熱門的持續集成工具(聽起來是不是跟 JavaScript 的經歷差不多?)。
關於 Jenkins 的介紹,我們主要引用其官網上的描述。以下是官方的關於 Jenkins 的一句話描述:
Jenkins是開源CI&CD軟件領導者, 提供超過1000個插件來支持構建、部署、自動化, 滿足任何項目的需要。
其中,CI 指代持續集成,CD 指代持續交付。
以下是其主要特點:
- 持續集成和持續交付: 作為一個可擴展的自動化服務器,Jenkins 可以用作簡單的 CI 服務器,或者變成任何項目的持續交付中心;
- 簡易安裝: Jenkins 是一個基於 Java 的獨立程序,可以立即運行,包含 Windows、Mac OS X 和其他類 Unix 操作系統;
- 配置簡單: Jenkins 可以通過其網頁界面輕鬆設置和配置,其中包括即時錯誤檢查和內置幫助;
- 插件: 通過更新中心中的 1000 多個插件,Jenkins 集成了持續集成和持續交付工具鏈中幾乎所有的工具;
- 擴展: Jenkins 可以通過其插件架構進行擴展,從而為 Jenkins 可以做的事提供幾乎無限的可能性;
- 分佈式: Jenkins 可以輕鬆地在多臺機器上分配工作,幫助更快速地跨多個平臺推動構建、測試和部署。
從介紹總結來看,Jenkins 的核心功能是持續集成和持續交付,另外還有便捷性(安裝簡單)和擴展性(插件、分佈式)。
安裝 Jenkins
跟之前的文章裡安裝 GitLab 一樣,我們同樣是用 Docker 來安裝 Jenkins。在確保您已安裝 Docker 、並且能順利運行 docker ps 的前提下,執行以下命令。
<code>docker run
-u root \\ # 以root用戶運行
--restart always \\ # 永遠設置為重啟狀態,開機時也會啟動
-d \\ # daemon方式後臺運行
-p 8080:8080 -p 50000:50000 \\ # 將web端口映射出來,8080為web界面端口
--name jenkins \\ # 容器名稱
\t-v jenkins-data:/var/jenkins_home \\ # 持久化數據
\t-v /var/run/docker.sock:/var/run/docker.sock \\ # Docker守護進程默認監聽的Unix域套接字
\t-e JAVA_OPTS=-Duser.timezone=Asia/Shanghai \\ # 調整時區為中國時區
\tjenkinsci/blueocean # Jenkins鏡像
複製代碼/<code>
以上命令已經有註釋了,讀者可以根據需要更改其中的一些配置,例如映射端口,8080 可以是 9090、80 等等。執行上述命令後,Jenkins 需要一定初始化時間,等待片刻後在瀏覽器中輸入 http://:8080,就可以看到 Jenkins 的初始界面(如下圖),這是 Jenkins 在加載必要數據和第三方庫,大概需要幾分鐘。
初始化完畢後,瀏覽器會重新加載,我們會看到如下配置界面。
這個是告知您,需要輸入初始密碼,而這個密碼在 Jenkins 容器裡的 /var/jenkins_home/secrets/initialAdminPassword 文件裡。輸入以下命令來查看初始密碼。
<code>docker exec -it jenkins bash # 進入容器命令行
cat /var/jenkins_home/secrets/initialAdminPassword # 查看初始密碼
複製代碼/<code>
將查看到的初始密碼輸入到上述界面中,點擊 Continue 進入下一步。加載片刻後,您將看到如下選擇界面。
選擇默認的 Install suggested plugins 安裝建議的插件。然後 Jenkins 會安裝一些常用的插件,例如連接 Git 的 Git plugin 、郵件插件、管道插件等等。安裝進度如下圖。請耐心等待,這個過程可能花很長的時間!
安裝完畢後,輸入第一個管理員賬戶,點擊 Save and Continue 進入到下一步,然後點擊 Save and Finish,最後選擇 Restart,完成重啟。
重啟完畢後,您就可以註冊登陸了,請選擇剛才的管理員賬戶登陸。
使用 Jenkins
你可能最開始使用 Jenkins 的時候會發現它樸素的用戶界面一點也不酷炫,總體顯得比較老舊,不像是現代軟件的風格,不過我們有 Open Blue Ocean 加強版界面,非常扁平化和現代化,不習慣 Jenkins 默認界面的可以切換過去。下圖是進入 Jenkins 的主頁界面,可以看到左側有一些操作選項以及構建隊列和執行器狀態,右側是自動化項目的列表。
下圖是 Open Blue Ocean 的主頁界面,功能上跟默認界面差不多,只是排版和風格有所不同。
其實,界面這些都不是特別重要的問題,很快你會被它樸實但強大的核心功能(持續集成)所折服。在使用這些強大的功能之前,我們需要做一些配置。
配置節點
首先看下圖,作者有兩臺物理機,IP 地址分別是 192.168.0.2 和 192.168.0.3 (假設),Jenkins 是用 Docker 起動的容器,在 192.168.0.2 這臺宿主機裡,而我們想在宿主機(192.168.0.2)上以及另一臺物理機(192.168.0.3)上構建應用,因此需要分別需要將這兩臺機器註冊到 Jenkins 裡。
這裡簡單介紹一下 Jenkins 的原理,Jenkins 構建項目是通過 Jenkins Executor 這個 jar 包來實現構建和部署的。Jenkins 服務的本地機器叫做 主機(master) ,如果告訴主機其他機器的 IP 地址和登陸信息,Jenkins 就可以通過 SSH 的方式在該機器上將 Jenkins Executor 拷貝過去並運行,再而與其交互通信,通過 Jenkins Executor 完成遠程構建工作。
下面我們介紹如何配置節點。
- 在主頁中點擊 Manage Jenkins (確保您有管理員權限),進入管理頁面;
- 然後點擊 Manage Nodes ,進入到管理節點頁面;
- 這裡您應該能看到 master 節點,這時候你需要添加節點,點擊左側的 New Node;
- 輸入節點名稱(Node Name),點擊 OK,將看到很多配置信息;
- 完成配置(配置信息如下圖),點擊 Save 保存設置。
然後,您應該就可以看到下圖節點列表了,左側 S 列代表的是節點狀態(Status),如果節點在線,會顯示為計算機的符號,如果離線,則會顯示一個計算機加紅叉的符號。你可以點擊去看它的日誌(Log),如果一直連不上,需要查看日誌排查錯誤。
節點配置好了,您就可以在這些節點上運行構建程序了。如果您有多個機器需要部署,只要一次性手動將這些信息註冊進 Jenkins,就可以一勞永逸的在各臺機器自動化部署應用程序了。是不是很酷?
創建項目
現在,我們介紹在 Jenkins 中如何創建一個自動化項目。其實過程很簡單,只是配置方面稍微複雜一些。
- 在主頁點擊 New Item 創建新項目;
- 輸入項目名稱,然後選擇項目類別,強烈建議選擇 Multibranch Pipeline (多分支管道),因為這一個類別是最方便配置和最容易集成之前介紹的 GitLab 版本控制系統的類別,然後點擊 OK;
- 配置各種輸入,包括展示名稱、項目來源(來自VCS,例如Git/SVN)、構建週期(選擇每分鐘)等等。
點擊 Save 保存項目,就創建好了一個項目。
讀者可能會問:等等,構建過程跑哪兒去了?彆著急,我們接下來會將如何配置構建過程,也就是用一個配置文件來定義構建過程,而這個配置文件叫 Jenkinsfile。
Jenkinsfile
讀者可能會問,Jenkinsfile 是不是與 Dockerfile 有異曲同工之妙?沒錯!這兩種配置文件都屬於 Infrastructure as Code(架構即代碼) ,僅僅用代碼就可以配置出計算機和網絡架構,非常易於管理和擴展。下面我們會詳細介紹 Jenkinsfile。
Jenkinsfile 是 Jenkins 2 發佈的一個實用功能,這個功能讓用代碼管理構建過程變成可能。它使用 Groovy 語法,很容易配置不同的構建階段。下面是一個 Jenkinsfile 的例子。
<code>pipeline {
agent {
node {
label 'docker' // 要執行的節點名稱
}
}
stages {
\t// 配置階段
stage('Configure') {
steps {
echo 'Configuring'
/> // 根據 Git 分值名稱選擇不同的 docker-compose 文件
switch (env.GIT_BRANCH) {
case 'develop':
\t// 開發分支的 docker-compose 文件
env.DOCKER_COMPOSE_FILENAME = 'docker-compose-dev.yaml'
break
case 'master':
\t// 主幹的 docker-compose 文件
env.DOCKER_COMPOSE_FILENAME = 'docker-compose-prod.yaml'
break
default:
echo "Unknown branch ${env.GIT_BRANCH}"
exit 1
}
}
}
}
\t// 構建階段
stage('Build') {
steps {
echo 'Building'
\t// 用 Docker 不同分支的構建鏡像(用 GIT_BRANCH 環境變量區分)
sh """
docker build -t awesome-image:${ENV:GIT_BRANCH} -f Dockerfile-local .
"""
}
}
\t// 測試階段
stage('Test') {
steps {
echo 'Testing'
}
}
\t// 部署階段
stage('Deploy') {
steps {
\t// 用 docker-compose 重啟鏡像服務
sh """
docker-compose -f ${ENV:DOCKER_COMPOSE_FILENAME} down | true
docker-compose -f ${ENV:DOCKER_COMPOSE_FILENAME} up -d
"""
}
}
\t// 清理階段
stage ('Clean-up')
{
steps
{
\t// 清理不用的 Docker 鏡像
sh '''
docker rmi $(docker images -f "dangling=true" -q) || true
'''
}
}
}
}
複製代碼/<code>
這裡,我們用了 Docker 來完成構建工作,Docker Compose 來完成部署工作。後面的文章會詳細介紹 Docker 在 DevOps 中的應用。
關於 Jenkinsfile 的內容以及 API 還很多,本文不會全部涵蓋。需要的朋友可以參考官方文檔 jenkins.io/zh/doc/book…
配置好了 Jenkinsfile,將它放在 Git 項目根目錄下,當每次有代碼提交的時候,Jenkins 就會拉最新代碼,然後開始構建過程,如下圖。
查看構建進度
用 Pipeline 構建的好處在於可以直觀的看到構建的過程,包括各階段花了多少時間等信息。下圖是某個項目構建過程的概覽。可以看到,每一次構建都是由 VCS 代碼提交觸發的。這樣的話,只要我們在某個分支上提交了代碼,Jenkins 就可以自動幫我們構建、部署了。如果構建過程中有報錯,Jenkins 中會有提示信息。
Jenkins 與其他 CI/CD 工具
Jenkins 是一個強大的工具,不過市面上還有其他很多優秀的 CI/CD 工具。如下圖,我們可以看到整個 CI/CD 市場的市場份額。
可以看到,除了 Jenkins,還有很多人用 Circle CI、Travis CI、GitLab CI 等工具。其實 Jenkins 不是唯一選擇,開發者運維者們還有很多其他的選擇,都可以分別嘗試。例如,GitLab CI,其實用了 GitLab 管理代碼的話,可以用 GitLab CI 來做持續集成。為什麼用 Jenkins 而不是 GitLab CI 呢,這其實跟作者的工作經歷有關,作者對 GitLab CI 並不是很熟悉,有機會一定會嘗試一下。當然,客觀來說,Jenkins 靈活性更強,更輕量級,加密性好,而且是跨平臺的,但插件質量參差不齊,導致有時容易出問題(所以我們推薦使用官方插件)。因此,對於用 GitLab 做 VCS 且項目通常複雜性不高的用戶來說,GitLab CI 是個更好的選擇。
為什麼 Jenkins 這麼出名,是因為它是最早的開源 CI/CD 工具,存在市場比較久而已,讀者有權利選擇任何合適的工具。
下圖是各個 CI/CD 工具的市場競爭矩陣圖。
可以看到,Jenkins 的地位其實不是屬於領先地位,後來居上的 Circle CI 的市場滿意度和市場份額都領先於其他工具,是一個非常有潛力的工具,作者也會找時間嘗試一下,當然也歡迎讀者去嘗試。
總結
本文介紹了持續集成的基本概念,以及使用 CI/CD 的原因,此外還詳細介紹了開源 CI/CD 工具 Jenkins,包括 Jenkins 的簡單介紹、如何安裝、如何配置、創建項目以及如何使用 Jenkinsfile 和查看構建進度。相信仔細閱讀了本篇文章的讀者,應該會基本瞭解持續集成的概念和框架,以及在企業中推廣使用持續集成的重要性。此外我們還了解了 CI/CD 工具的市場情況,並知道 Jenkins 並不是唯一的選擇,市面上還可以選擇很多其他優秀的工具,例如後起之秀 Circle CI。我們選合適的 CI/CD 工具是為了滿足企業的 DevOps 需求,而不應該侷限於工具的名聲和自己所掌握的知識,只要做好了前期調研和需求分析,我相信是可以實踐好 DevOps 中的持續集成功能的。最後提一點,就是工具並不是全部,如果沒有良好定義的流程與技能儲備,DevOps 是實踐不通的,應用再優秀的 CI/CD 工具也會失去意義。因此,我們需要經常充實自己,加強 DevOps 的知識瞭解,擴充自己的能力範圍,這樣才能打造出最佳的 DevOps 工作流。
我們之前介紹了 DevOps 概述、VCS,現在又介紹 CI/CD,接下來我們會講容器化,會詳細介紹 Docker,敬請期待後續文章。
作者:MarvinZhang
鏈接:https://juejin.im/post/5e61b46ee51d4526f23a2ba6
閱讀更多 架構師之路 的文章