這篇文章是一系列文章的第一篇,在這一系列文章中,咱們想要分享咱們如何使用Docker、Docker-Compose和Rancher完成容器部署工做流的故事。咱們想帶你從頭開始走過pipeline的革命歷程,重點指出咱們這一路上遇到的痛點和作出的決定,而不僅是單純的回顧。幸虧有不少優秀的資源能夠幫助你使用Docker設置持續集成和部署工做流。這篇文章並不屬於這些資源之一。一個簡單的部署工做流相對比較容易設置。可是咱們的經驗代表,構建一個部署系統的複雜性主要在於本來容易的部分須要在擁有不少依賴的遺留環境中完成,以及當你的開發團隊和運營組織發生變化以支持新的過程的時候。但願咱們在解決構建咱們的pipeline的困難時積累下的經驗會幫助你解決你在構建你的pipeline時遇到的困難。java
在這第一篇文章裏,咱們將從頭開始,看一看只用Docker時咱們開發的初步的工做流。在接下來的文章中,咱們將進一步介紹Docker-compose,最後介紹如何將Rancher應用到咱們的工做流中。git
爲了爲以後的工做鋪平道路,假設接下來的事件都發生在一家SaaS提供商那裏,咱們曾經在SaaS提供商那裏提供過長時間服務。僅爲了這篇文章的撰寫,咱們姑且稱這家SaaS提供商爲Acme Business Company, Inc,即ABC。這項工程開始時,ABC正處在將大部分基於Java的微服務棧從裸機服務器上的本地部署遷移到運行在AWS上的Docker部署的最初階段。這項工程的目標很常見:發佈新功能時更少的前置時間(lead time)以及更可靠的部署服務。docker
爲了達到該目標,軟件的部署計劃大體是這樣的:shell
這個過程從代碼的變動、提交、推送到git倉庫開始。當代碼推送到git倉庫後,咱們的CI系統會被告知運行單元測試。若是測試經過,就會編譯代碼並將結果做爲產出物(artifact)存儲起來。若是上一步成功了,就會觸發下一步的工做,利用咱們的代碼產出物建立一個Docker鏡像並將鏡像推送到一個Docker私有註冊表(private Docker registry)中。最後,咱們將咱們的新鏡像部署到一個環境中。後端
要完成這個過程,以下幾點是必需要有的:api
這樣去看的話,這個過程表面上簡單,然而實際過程當中會複雜一些。像許多其它公司同樣,ABC曾經(如今仍然是)將開發團隊和運營團隊劃分爲不一樣的組織。當代碼準備好部署時,會建立一個包含應用程序和目標環境詳細信息的任務單(ticket)。這個任務單會被分配到運營團隊,並將會在幾周的部署窗口內執行。如今,咱們已經不能清晰地看到一個持續部署和分發的方法了。安全
最開始,部署任務單可能看起來是這樣的:bash
DEPLOY-111: App: JavaService1, branch "release/1.0.1" Environment: Production
部署過程是:服務器
docker pull registry.abc.net/javaservice1:release-1.0.1
docker ps
docker stop [container_id]
docker run -d -p 8080:8080 … registry.abc.net/javaservice1:release-1.0.1
curl localhost:8080/api/v1/version
不能否認的是,這個部署過程並不怎麼讓人印象深入,但這是通往持續部署偉大的第一步。這裏有好多地方仍可改進,但咱們先考慮一下這麼作的優勢:微信
好吧,如今看一看痛點:
基於以上的狀況,咱們開始作出一些改變,解決這些痛點。
第一個改進是寫一個bash腳本將部署中相同的步驟包裝起來。一個簡單的包裝腳本能夠是這樣的:
!/bin/bash APPLICATION=$1 VERSION=$2 docker pull "registry.abc.net/${APPLICATION}:${VERSION}" docker rm -f $APPLICATION docker run -d --name "${APPLICATION}" "registry.abc.net/${APPLICATION}:${VERSION}"
這樣作行得通,但僅對於最簡單的容器而言,也就是那種用戶不須要鏈接到的容器。爲了可以實現主機端口映射和卷掛載(volume mounts),咱們需要增長應用程序特定的邏輯。這裏給出一個使用蠻力實現的方法:
APPLICATION=$1 VERSION=$2 case "$APPLICATION" in java-service-1) EXTRA_ARGS="-p 8080:8080";; java-service-2) EXTRA_ARGS="-p 8888:8888 --privileged";; *) EXTRA_ARGS="";; esac docker pull "registry.abc.net/${APPLICATION}:${VERSION}" docker stop $APPLICATION docker run -d --name "${APPLICATION}" $EXTRA_ARGS "registry.abc.net/${APPLICATION}:${VERSION}"
如今這段腳本被安裝在了每一臺Docker主機上以幫助部署。運營工程師會登錄到主機並傳遞必要的參數,以後腳本會完成剩下的工做。部署時的工做被簡化了,工程師的須要作的事情變少了。然而將部署代碼化的問題仍然存在。咱們回到過去,把它變成一個關於向一個共同腳本提交改變而且將這些改變分發到主機上的問題。一般來講,這樣作很值得。將代碼提交到倉庫會給諸如代碼審查、測試、改變歷史以及可重複性帶來巨大的好處。在關鍵時刻,你要考慮的事情越少越好。
理想情況下,一個應用的相關部署細節和應用自己應當存在於同一個源代碼倉庫中。有不少緣由致使現實狀況不是這樣,最突出的緣由是開發人員可能會反對將運營相關的東西放入他們的代碼倉庫中。尤爲對於一個用於部署的bash腳本,這種狀況更可能發生,固然Dockerfile文件自己也常常如此。
這變成了一個文化問題而且只要有可能的話就值得被解決。儘管爲你的部署代碼維持兩個分開的倉庫確實是可行的,可是你將不得不耗費額外的精力保持兩個倉庫的同步。本篇文章固然會努力達到更好的效果,即使實現起來更困難。在ABC,Dockerfiles最開始在一個專門的倉庫中,每一個工程都對應一個文件夾,部署腳本存在於它本身的倉庫中。
Dockerfiles倉庫擁有一個工做副本,保存在Jenkins主機上一個熟知的地址中(就好比是‘/opt/abc/Dockerfiles’)。爲了爲一個應用建立Docker鏡像,Jenkins會搜索Dockerfile的路徑,在運行」docker build「前將Dockerfile和伴隨的腳本複製進來。因爲Dockerfile老是在掌控中,你即可能發現你是否處在Dockerfile超前(或落後)應用配置的狀態,雖然實際中大部分時候都會處在正常狀態。這是來自Jenkins構建邏輯的一段摘錄:
if [ -f docker/Dockerfile ]; then docker_dir=Docker elif [ -f /opt/abc/dockerfiles/$APPLICATION/Dockerfile ]; then docker_dir=/opt/abc/dockerfiles/$APPLICATION else echo "No docker files. Can’t continue!" exit 1 if docker build -t $APPLICATION:$VERSION $docker_dir
隨着時間的推移,Dockerfiles以及支持腳本會被遷移到應用程序的源碼倉庫中。因爲Jenkins最開始已經查看了本地的倉庫,pipeline的構建再也不須要任何變化。在遷移了第一個服務後,倉庫的結構大體是這樣的:
咱們使用分離的倉庫時遇到的一個問題是,若是應用源碼或打包邏輯任意一個發生改變,Jenkins就會觸發應用的重建。因爲Dockerfiles倉庫包含了許多項目的代碼,當改變發生時咱們不想觸發全部的倉庫重建。解決方法是:使用在Jenkins Git插件中一個很隱蔽的選項,叫作Included Regions。當配置完成後,Jenkins將一個變化引發的重建隔離在倉庫的某個特定子集裏面。這容許咱們將全部的Dockerfiles放在一個倉庫裏,而且仍然能作到當一個改變發生時只會觸發特定的構建(與當改變發生在倉庫裏特定的目錄時構建全部的鏡像相比)。
關於這個初步的工做流的另外一個方面是部署工程師必須在部署前強制構建一個應用鏡像。這將致使額外的延遲,尤爲是構建存在問題而且開發人員須要參與其中的時候。爲了減小這種延遲,併爲更加持續的部署鋪平道路,咱們開始爲熟知分支中的每個提交構建Docker鏡像。這要求每個鏡像有一個獨一無二的版本標識符,而若是咱們僅僅依賴官方的應用版本字符串每每不能知足這一點。最終,咱們使用官方版本字符串、提交次數和提交sha碼的組合做爲版本標識符。
commit_count=$(git rev-list --count HEAD) commit_short=$(git rev-parse --short HEAD) version_string="${version}-${commit_count}-${commit_short}"
這樣獲得的版本字符串看起來是這樣的:1.0.1-22-7e56158
在結束pipeline的Docker file部分的討論以前,還有一些參數值得說起。若是咱們不會在生產中操做大量的容器,咱們不多用到這些參數。可是,它們被證實有助於咱們維護Docker集羣的線上運行。
結合重啓策略和資源約束會給你的集羣帶來更好的穩定性,與此同時最小化失敗的影響,縮短恢復的時間。這種類型的安全防禦可讓你和開發人員一塊兒專一於「起火」的根本緣由,而不是忙於應付不斷擴大的火勢。
簡而言之,咱們從一個基礎的構建pipeline,即從咱們的源碼倉庫中建立被標記的Docker鏡像開始。從使用Docker CLI部署容器一路到使用腳本和代碼中定義的參數部署容器。咱們也涉及瞭如何管理咱們的部署代碼,而且強調了幾個幫助運營人員保持服務上線和運行的Docker參數。
此時此刻,在咱們的構建pipeline和部署步驟之間仍然存在空缺。部署工程師會經過登入一個服務器並運行部署腳本的方法填補這個空缺。儘管較咱們剛開始時有所改進,但仍然有進一步提升自動化水平的空間。全部的部署邏輯都集中在單一的腳本內,當開發者須要安裝腳本以及應付它的複雜性時,會使本地測試會變得困可貴多。此時此刻,咱們的部署腳本也包含了經過環境變量處理任何環境特定信息的方法。追蹤一個服務設置的環境變量以及增長新的環境變量是乏味且容易出錯的。
在下一篇文章中,咱們將看一看怎樣經過解構(deconstructing)共同的包裝腳本解決這些痛點,並使部署邏輯向使用Docker Compose的應用更近一步。
您也能夠下載免費的電子書《Continuous Integration and Deployment with Docker and Rancher》,這本書講解了如何利用容器幫助你完成整個CI/CD過程。
歡迎關注Rancher官方微信公衆號(RancherLabs),獲取第一手技術乾貨推送;歡迎添加客服微信(RancherLabsChina)爲好友,加入Rancher官方技術交流羣,獲取免費技術支持,與數千Docker/Rancher使用者互動。
9月27日,北京海航萬豪酒店,容器技術大會Container Day 2017即將舉行。
CloudStack之父、海航科技技術總監、華爲PaaS部門部長、恆豐銀行科技部總經理、阿里雲PaaS工程總監、民生保險CIO······均已加入豪華講師套餐!
11家已容器落地企業,15位真·雲計算大咖,13場純·技術演講,結合實戰場景,聚焦落地經驗。免費參會+超高規格,詳細議程及註冊連接請戳