# 第9章 CI/CD
現在,讀者對容器的構建與存儲已經比較熟悉,本章我們簡要地談一下結合Docker鏡像的CI/CD系統的使用。如今很多企業已經采用了DevOps做法,并使用一個類似Jenkins、TravisCI或Teamcity的自動化構建系統來自動構建代碼。當代碼自動構建完畢,在Docker構建流程中將代碼添加到容器里是很簡單的。如果想在生產環境中運行Docker,強烈建議使用一個自動化持續集成和持續部署(CI/CD)構建系統來構建鏡像。Docker構建和推送的簡易性,再與CI/CD系統相結合,可能是迄今為止最強大的DevOps服務部署方式之一。
Docker本質上是一個應用程序交付框架。即便Docker網站的座右銘是:構建、交付與運行。如果能把代碼交付過程中的構建和交付部分自動化,那么很可能可以更快地將產品交付給客戶。開發人員都期望能迅速且頻繁地交付代碼,而企業交付的是產品。Docker允許將整個產品打包在一個容器中,這不僅能迅速且頻繁地交付代碼,也能交付整個產品。在接下來的幾年時間,我們將看到越來越多的產品以容器鏡像的方式進行交付,而不是一個個下載好的msi、jar或zip文件。如果你是一家需要通過SaaS更快地交付代碼給客戶的公司,或正在以容器方式交付產品,你就應該開始使用構建系統來構建Docker鏡像。
如果你已經使用構建系統構建過代碼和產品,下一步是通過構建系統來構建Docker鏡像。展開想象,一個Web容器,它運行著Jetty,并且使用Java代碼的jar文件運行它的應用程序。CI/CD系統將構建代碼、將其打包,然后將最終的jar文件部署在像Artifactory、文件系統、AWS S3這樣的產出位置上,或保存在自己的內部系統中。此時,當構建一個Docker鏡像時,只需要在Dockerfile中使用`ADD code.jar /jetty/bin/code.jar`添加jar文件。在容器運行時,只需要配置Jetty使用`/jetty/bin/code.jar`這個JAR以載入應用程序中。[Rally Soft](https://www.rallydev.com/blog/engineering/deploying-java-apps-docker-and-armada)有一個的具體示例。
CI/CD系統不僅對代碼的構建和打包非常有用,在構建Docker鏡像時表現也非常完美。編譯Docker鏡像只需要Dockerfile(代碼)和一個構建命令。一旦構建完成,用戶只需要將包提交到倉庫中。可以對CI/CD系統進行設置,在每次檢測到GitHub提交時自動完成構建的全部過程。這將讓用戶的開發團隊或基礎設施運維團隊可以在一個自動化部署系統中部署新組件、基礎設施或應用程序。
使用CI/CD系統構建Docker鏡像需要與Docker守護進程進行通信。一個簡單的方法是在用戶的構建代理上安裝Docker,然后就可以構建并交付鏡像。另外一種方法讀者可能聽說過,其術語叫Docker in Docker([DIND](https://github.com/jpetazzo/dind))。讀者可以通過閱讀一篇[文章](https://blog.docker.com/2013/09/docker-can-now-run-within-docker/)來了解更多信息。簡單而言,DIND可以通過發送構建命令給另外一個Docker守護進程來完成構建。不論選擇哪種方法,都需要一個自動化方法來構建Docker鏡像并在構建進程中添加代碼。
構建系統一般是按步驟進行的。我們把使用Docker的幾種可能性構建做下分解。
構建Docker鏡像。
(1)從github.com拉取最新的Dockerfile代碼。
(2)運行`docker build -t repo.com/image .`來構建鏡像。
(3)使用`docker push repo.com/image`將鏡像推送到倉庫中。
使用代碼構建Docker鏡像。
(1)從github.com拉取最新的Java代碼。
(2)使用Maven編譯并測試Java代碼,輸出設置為code.jar。
(3)從github.com拉取最新的Dockerfile代碼(Dockerfile具有`ADD code.jar /jetty/bin/code.jar`命令)。
(4)運行`docker build -t repo.com/image .`來構建鏡像。
(5)使用`docker push repo.com/image`將鏡像推送到倉庫中。
使用代碼和集成測試構建Docker鏡像。
(1)從github.com拉取最新的Java代碼。
(2)使用Maven編譯并測試Java代碼,輸出設置為code.jar。
(3)從github.com拉取最新的Dockerfile代碼(Dockerfile具有`ADD code.jar /jetty/bin/code.jar`命令)。
(4)運行`docker build -t repo.com/image .`來構建鏡像。
(5)啟動一個測試專用Docker基礎設施。
(6)運行完整的端到端集成測試。
(7)停止該測試專用Docker基礎設施。
(8)使用`docker push repo.com/image`將鏡像推送到倉庫中。
這些示例并不復雜,不過多數時候也不需要太復雜。構建和交付Docker鏡像非常簡單。最難的部分在于,理解Docker并讓構建系統自動運行Docker命令。實現這一過程的自動化只會提高用戶的工程成果。接下來,我們討論幾個有關使用自動化CI/CD系統構建Docker鏡像的話題。
CI/CD系統已經可以自動化構建鏡像了,并且現在生產環境中也運行著Docker。此時,用戶大概會意識到自己的基礎設施幾乎可以運行所有丟給它的容器。為什么不讓開發人員在編寫新代碼后構建并推送Docker鏡像到生產環境中?或者,只是將其放置在構建系統里,然后自動將容器部署到生產環境中?這是理論上的完美情況,但現實并非如此。尤其是在放任所有開發人員在Docker鏡像中使用任意代碼構建自己的Web服務器容器,然后將鏡像交給運維團隊運行的時候,企業環境很快就會失控。運維團隊會甩手不干、憤而離場。他們很快就會意識到情況不受控制,而且完全不清楚容器內有什么。如果他們堅守崗位,可能會開始問類似這樣的問題:是否運行了Jetty?是否運行了Nginx?是否運行了Apache?Web服務器的版本是否是最新的?是否使用了最新的安全性強化最佳實踐對其進行配置?里面是否有SSH?如何記錄日志?是否使用了標準的日志格式?問題會源源不斷地涌來。讓所有人構建并推送容器是一些團隊會想到的主意,但它經常會迅速地轉變為一個糟糕的做法。我們強烈建議盡早選擇更高的路線并著手制定標準。
以下是我們所認識的團隊在生產環境中使用Docker的一些標準。
如果運維或開發團隊始終從他們的工作站推送鏡像,就會忽略一些信息或最佳實踐。一致性是擴展環境和傳播知識的關鍵。應仔細考慮有關構建、添加、使用及運行Docker容器的標準。在一個提供文檔的中央系統中構建所有的Docker鏡像。基礎設施就像代碼一樣,只是目的是用于構建、編譯和打包鏡像。它也可以啟動有關容器推送的目的地、指定基礎鏡像、代碼驗證等的標準化。
隨著時間推移,你的安全團隊將理解你所做的事情,并希望了解更多。新的安全方法讓安全團隊更多地參與到構建過程中。因此,應賦予安全團隊權限并讓他們審核構建的內容。他們還可以利用工具進行測試,找出容易受攻擊的軟件包、不安全的配置,甚至是一些很糟糕的做法,如在代碼中包含密鑰信息。
你的Docker鏡像是運行Apache、Jetty、Nginx,還是三者都有?是否一個Docker鏡像運行Gwizard,而另一個運行Dropwizard?如果你還沒創建圍繞容器的最佳工程實踐,那就應該不斷地去確認這些問題。在某些時候,你或你的運維團隊會被叫去調試一個遺忘已久的鏡像。讓鏡像內所使用的服務或軟件包具有一致的標準,將為工程實踐的成功奠定基礎。如果你的工程團隊傾向于運行Nginx和Jetty,那就使用這些服務來交付Web服務。如果你的團隊傾向于使用Dropwizard而非Gwizard,那就使用前者的軟件包。你的團隊越早實現鏡像內容標準化,成功的可能性就越大。
如果你遵循了第一個標準(見9.2節)和第二個標準(見9.3節) ,你就會意識到使用一個默認的基礎Docker鏡像是個好主意。假定你的團隊使用Python 2.6作為代碼語言。如果團隊里一個新的開發人員拉取了最新版的Python基礎鏡像,但它是3.0版本,那你注定要遇到問題。樹立一個規范,讓所有鏡像繼承于一個標準基礎鏡像,將大有幫助。有些團隊已經開始讓運維團隊為他們構建基礎鏡像,他們只要繼承即可。例如,運維團隊創建了一個基礎Ubuntu鏡像,對日志做了適當的配置、設置了正確的安全性并安裝了正確的Python 2.6版本。開發團隊所要做的唯一事情就是用`FROM`來使用這個基礎鏡像,然后在構建時用`ADD`將代碼添加到鏡像中,就像這樣:
```
FROM company.com/python_base:2015_02
ADD code.py /code.py
CMD [ "python", "./code.py" ]
```
這大大簡化了鏡像的創建過程,將大量的細節交給擁有并維護這個基礎鏡像的人負責。提供一個標準基礎鏡像將讓運維團隊清楚容器內運行著什么(在多數情況下),它簡化了構建過程,讓開發人員可以專注于代碼,同時,因為它創建了一致性,有助于在用戶的基礎設施上擴展Docker鏡像。
Docker不僅可以讓用戶將應用程序打包成一個鏡像并進行交付,還可豐富基礎設施的方方面面。假定你在生產環境構建中運行集成測試,同時具有一個攜帶靜態數據的靜態環境。很可能你會構建代碼,然后在這個靜態環境中運行集成測試。在使用Docker的世界里,用戶可以把這個靜態環境轉變成一個在測試基礎設施里集成Docker鏡像的動態環境。Docker可以非常快速地啟動與停止,加上制作優質鏡像的簡易性,可根據需要對測試基礎設施進行支撐。在完成一次構建時,用戶可以運行同一個代理或另一臺宿主機系統(調用Docker API)上的集成基礎設施,然后在結束后將其關閉。這甚至可以為公司省下保持靜態基礎設施運行的費用。
到今天為止,在結合Docker的CI/CD測試領域我們還沒看到過多創新,不過已經有一些新項目開始涌現。我們見識過在鏡像中運行Selenium瀏覽器測試,甚至為企業提供完整的端到端測試,但是我們還沒看到任何標準。我們期待在這個領域很快能看到更多成果。如果想了解Docker和Selenium,可以查看名為Docker [Selenium](https://github.com/elgalu/docker-selenium)的GitHub項目。
DevOps完全就是一種“以一個團隊的身份交付代碼和基礎設施”的文化。當你的工程團隊實踐DevOps,并能使用單一系統來配置和部署Docker鏡像到基礎設施上時,你已經開始漸入佳境了。最近在DockerCon 15大會上,我們看到Jenkins全面擁抱了Docker,甚至是微軟Visual Studio團隊也展示了使用Docker鏡像構建和部署應用程序和基礎設施的威力。我們希望看到越來越多的公司開始在他們的CI/CD環境里使用Docker,并樹立更多最佳實踐。
配置管理對Docker來說很重要,第10章將對其進行說明。
- 版權信息
- 版權聲明
- 內容提要
- 對本書的贊譽
- 譯者介紹
- 前言
- 本書面向的讀者
- 誰真的在生產環境中使用Docker
- 為什么使用Docker
- 開發環境與生產環境
- 我們所說的“生產環境”
- 功能內置與組合工具
- 哪些東西不要Docker化
- 技術審稿人
- 第1章 入門
- 1.1 術語
- 1.1.1 鏡像與容器
- 1.1.2 容器與虛擬機
- 1.1.3 持續集成/持續交付
- 1.1.4 宿主機管理
- 1.1.5 編排
- 1.1.6 調度
- 1.1.7 發現
- 1.1.8 配置管理
- 1.2 從開發環境到生產環境
- 1.3 使用Docker的多種方式
- 1.4 可預期的情況
- 為什么Docker在生產環境如此困難
- 第2章 技術棧
- 2.1 構建系統
- 2.2 鏡像倉庫
- 2.3 宿主機管理
- 2.4 配置管理
- 2.5 部署
- 2.6 編排
- 第3章 示例:極簡環境
- 3.1 保持各部分的簡單
- 3.2 保持流程的簡單
- 3.3 系統細節
- 利用systemd
- 3.4 集群范圍的配置、通用配置及本地配置
- 3.5 部署服務
- 3.6 支撐服務
- 3.7 討論
- 3.8 未來
- 3.9 小結
- 第4章 示例:Web環境
- 4.1 編排
- 4.1.1 讓服務器上的Docker進入準備運行容器的狀態
- 4.1.2 讓容器運行
- 4.2 連網
- 4.3 數據存儲
- 4.4 日志
- 4.5 監控
- 4.6 無須擔心新依賴
- 4.7 零停機時間
- 4.8 服務回滾
- 4.9 小結
- 第5章 示例:Beanstalk環境
- 5.1 構建容器的過程
- 部署/更新容器的過程
- 5.2 日志
- 5.3 監控
- 5.4 安全
- 5.5 小結
- 第6章 安全
- 6.1 威脅模型
- 6.2 容器與安全性
- 6.3 內核更新
- 6.4 容器更新
- 6.5 suid及guid二進制文件
- 6.6 容器內的root
- 6.7 權能
- 6.8 seccomp
- 6.9 內核安全框架
- 6.10 資源限制及cgroup
- 6.11 ulimit
- 6.12 用戶命名空間
- 6.13 鏡像驗證
- 6.14 安全地運行Docker守護進程
- 6.15 監控
- 6.16 設備
- 6.17 掛載點
- 6.18 ssh
- 6.19 私鑰分發
- 6.20 位置
- 第7章 構建鏡像
- 7.1 此鏡像非彼鏡像
- 7.1.1 寫時復制與高效的鏡像存儲與分發
- 7.1.2 Docker對寫時復制的使用
- 7.2 鏡像構建基本原理
- 7.2.1 分層的文件系統和空間控管
- 7.2.2 保持鏡像小巧
- 7.2.3 讓鏡像可重用
- 7.2.4 在進程無法被配置時,通過環境變量讓鏡像可配置
- 7.2.5 讓鏡像在Docker變化時對自身進行重新配置
- 7.2.6 信任與鏡像
- 7.2.7 讓鏡像不可變
- 7.3 小結
- 第8章 存儲Docker鏡像
- 8.1 啟動并運行存儲的Docker鏡像
- 8.2 自動化構建
- 8.3 私有倉庫
- 8.4 私有registry的擴展
- 8.4.1 S3
- 8.4.2 本地存儲
- 8.4.3 對registry進行負載均衡
- 8.5 維護
- 8.6 對私有倉庫進行加固
- 8.6.1 SSL
- 8.6.2 認證
- 8.7 保存/載入
- 8.8 最大限度地減小鏡像體積
- 8.9 其他鏡像倉庫方案
- 第9章 CI/CD
- 9.1 讓所有人都進行鏡像構建與推送
- 9.2 在一個構建系統中構建所有鏡像
- 9.3 不要使用或禁止使用非標準做法
- 9.4 使用標準基礎鏡像
- 9.5 使用Docker進行集成測試
- 9.6 小結
- 第10章 配置管理
- 10.1 配置管理與容器
- 10.2 面向容器的配置管理
- 10.2.1 Chef
- 10.2.2 Ansible
- 10.2.3 Salt Stack
- 10.2.4 Puppet
- 10.3 小結
- 第11章 Docker存儲引擎
- 11.1 AUFS
- 11.2 DeviceMapper
- 11.3 BTRFS
- 11.4 OverlayFS
- 11.5 VFS
- 11.6 小結
- 第12章 Docker 網絡實現
- 12.1 網絡基礎知識
- 12.2 IP地址的分配
- 端口的分配
- 12.3 域名解析
- 12.4 服務發現
- 12.5 Docker高級網絡
- 12.6 IPv6
- 12.7 小結
- 第13章 調度
- 13.1 什么是調度
- 13.2 調度策略
- 13.3 Mesos
- 13.4 Kubernetes
- 13.5 OpenShift
- Red Hat公司首席工程師Clayton Coleman的想法
- 第14章 服務發現
- 14.1 DNS服務發現
- DNS服務器的重新發明
- 14.2 Zookeeper
- 14.3 基于Zookeeper的服務發現
- 14.4 etcd
- 基于etcd的服務發現
- 14.5 consul
- 14.5.1 基于consul的服務發現
- 14.5.2 registrator
- 14.6 Eureka
- 基于Eureka的服務發現
- 14.7 Smartstack
- 14.7.1 基于Smartstack的服務發現
- 14.7.2 Nerve
- 14.7.3 Synapse
- 14.8 nsqlookupd
- 14.9 小結
- 第15章 日志和監控
- 15.1 日志
- 15.1.1 Docker原生的日志支持
- 15.1.2 連接到Docker容器
- 15.1.3 將日志導出到宿主機
- 15.1.4 發送日志到集中式的日志平臺
- 15.1.5 在其他容器一側收集日志
- 15.2 監控
- 15.2.1 基于宿主機的監控
- 15.2.2 基于Docker守護進程的監控
- 15.2.3 基于容器的監控
- 15.3 小結
- DockOne社區簡介
- 看完了