Docker 快速部署代碼之道
在 Ionic,我們是 Docker 的鐵桿粉絲。我們的代碼以及代碼的依賴全部運(yùn)行在 Docker 中,Docker 讓我們的產(chǎn)品更充分地利用計(jì)算資源,比如 Ionic Creator,以及即將到來的 Ionic.io 服務(wù)。
使用 Docker 面對(duì)的一個(gè)挑戰(zhàn)是,盡管我們只是對(duì)我們的代碼做了一個(gè)小小的變更,我們都必須要走一遍構(gòu)建一個(gè)新容器的過程,把它拉取(pull)到我們的服務(wù)器,并替代正在運(yùn)行的版本。
我們所有的代碼都存儲(chǔ)在 GitHub,使用 Docker Registry(這里推薦下國內(nèi)的 docker.cn,速度比官方的快很多,不用擔(dān)心“你懂的”問題) 來自動(dòng)構(gòu)建和存儲(chǔ)我們的代碼,并使用 Ansible 來管理和部署我們的容器到我們的服務(wù)器上。即使是一個(gè)完全自動(dòng)化的過程,部署一個(gè)小變更都可能需要花費(fèi)我們 20 分鐘或者更多的時(shí)間。經(jīng)過頭腦風(fēng)暴,我們意識(shí)到我們有一個(gè)更好的方法來利用 Docker。
在最初的容器構(gòu)建之后,99% 的變更都是純代碼。我們不需要添加任何依賴,或者是改變?nèi)魏未a運(yùn)行所必需的東西。Docker 實(shí)際上只是一種封裝基礎(chǔ)架構(gòu)的方式,要求我們的代碼運(yùn)行在一個(gè)自包含的包中。因?yàn)槲覀?99% 的變更都是代碼,不是基礎(chǔ)架構(gòu),我們意識(shí)我們不需要在每次變更的時(shí)候都努力重新構(gòu)建我們的基礎(chǔ)架構(gòu)。
讓我們解決這個(gè)問題的是 Docker 的殺手級(jí)特性 volumes。在我們 Docker files 的***次迭代中,我們從 GitHub 拉取代碼,并直接構(gòu)建進(jìn)容器中。現(xiàn)在,我們故意把代碼放在容器外面,并在容器啟動(dòng)的時(shí)候,通過加載一個(gè)主機(jī)卷(host volume) 來代替。當(dāng)我們想做一個(gè)新發(fā)布,Ansible 從 GitHub 上拉取 master 分支到我們服務(wù)器的 app 目錄。這時(shí),它通過檢查來確保相關(guān)聯(lián)的容器正在運(yùn)行,如果沒有在運(yùn)行,它將啟動(dòng)這個(gè)容器并把 app 代碼映射進(jìn)容器。
使得我們的工作更便捷的另外一個(gè)組件是 uWSGI,因?yàn)槲覀兊拇蟛糠?app 是 Python 的(Django),所以我們?cè)?Docker 容器中使用 uWSGI 提供服務(wù)。uWSGI 有一個(gè) touch reload 特性,可以監(jiān)控指定的文件,當(dāng)該文件被 touch 的時(shí)候,會(huì)重載 uWSGI 服務(wù)。在 Ansible 從 GitHub 拉取我們的變更之后,我們使用 Ansible 來 touch uwsgi.ini 文件,這會(huì)觸發(fā)正在運(yùn)行的容器中的 uWSGI 重載。我們就是這樣來運(yùn)行我們代碼的更新版本的!
這是什么意思呢?簡(jiǎn)單地說,花費(fèi)我們 20+ 分鐘的部署過程是這樣的:
- 提交(Commit)和 推送(push)變更到 GitHub。
- Docker Registry 拉取(pulls)變更和構(gòu)建一個(gè)新容器。
- Ansible 連接到我們的服務(wù)器并拉取(pulls)這個(gè)新容器 。
- Ansible 發(fā)現(xiàn)任何舊容器正在運(yùn)行的實(shí)例并停止它們。
- Ansible 啟動(dòng)該容器的新實(shí)例。
類似的 10 秒的過程是這樣的:
- 提交(Commit)和 推送(push)變更到 GitHub。
- Ansible 連接到我們的服務(wù)器,從 GitHub 拉取***的 master。
- Ansible touches 該 app 的 uwsgi.ini 文件來觸發(fā) UWSGI 的重載。
步驟分解
Supervisor / uWSGI
我們?cè)?Docker 容器中使用 Supervisor 來啟動(dòng)容器中的進(jìn)程運(yùn)行。我們的 supervisord.conf 文件看起來像下面這樣:
- [supervisord] nodaemon=true
- [program:uwsgi]
- command = /usr/local/bin/uwsgi --touch-reload=/path/to/code/in/container/uwsgi.ini --ini /path/to/code/in/container/uwsgi.ini
我們通過 --touch-reload
選項(xiàng)來把 uwsgi.ini 文件作為觸發(fā)文件。
Docker
當(dāng)我們啟動(dòng)我們的容器,我們添加一個(gè)包含我們 app 代碼的主機(jī)卷(host volume),該主機(jī)卷被映射到容器中的一個(gè) app 路徑,uWSGI 將從這個(gè)路徑加載 app。
- docker run -d -P -v /path/to/code/on/host:/path/to/code/in/container --name=container_name driftyco/testapp
Ansible
Ansible 負(fù)責(zé)從 GitHub 克隆(clone)我們應(yīng)用程序的代碼到我們主機(jī)的 app 目錄,確保 Docker 容器正在運(yùn)行以及 touch 配置的 uWSGI touch-reload 文件。我們已經(jīng)創(chuàng)建了 playbooks 來直接部署我們的每個(gè)服務(wù),因此部署僅僅是一個(gè)運(yùn)行正確的問題。
對(duì)于一個(gè)快速代碼部署,我們運(yùn)行一個(gè)包含這些任務(wù)的 playbook,并只需要幾秒來運(yùn)行:
- - set_fact: host_volume="/path/to/code/on/host" - name: Git pull the latest code
- git: repo=git@github.com:{{ org }}/{{ container }}.git 對(duì)于一個(gè)全量部署,我們按順序運(yùn)行這兩個(gè) playbooks;這是非常簡(jiǎn)單的。
總結(jié)
因?yàn)?Docker 主要的一個(gè)方式是封裝基礎(chǔ)架構(gòu)到一個(gè)自包含的,可部署的包。這不需要重新構(gòu)建整個(gè)容器僅僅只是為了幾個(gè)代碼變更。通過在 Docker 中利用卷(volumes),我們從容器中移除了代碼,使得代碼能獨(dú)立于容器更新。***,我們可以使用 UWSGI 的 touch reload 特性在容器中重啟 UWSGI,并從卷(volume)中加載更新的代碼。注:本文作者是 Joel Weirauch,本文原文是 Fast code deployments with Docker
- dest={{ host_volume }}
- accept_hostkey=yes
- force=yes
- - name: Gracefully reload uwsgi
- file: path={{ touch_file }} state=touch
如果我們需要重啟整個(gè)容器或者是更新我們的系統(tǒng)包,我們可以做一個(gè)容器部署,這將花費(fèi)幾分鐘,使用這些任務(wù):
- - name: Add app dir if it doesn't yet exist file: path={{ host_volume }} owner=nobody group=docker recurse=yes state=directory
- sudo: yes
- - name: Pull Docker image
- command: "{{ item }}"
- ignore_errors: yes
- with_items:
- - docker pull {{ org }}/{{ container }}
- - docker stop {{ container }}
- - docker rm {{ container }}
- - name: Run Docker image with app volumes
- command: docker run -d -P -v {{ host_volume }}:{{ container_volume }} --name={{ container }} {{ extra_params }} {{ org }}/{{ container }}
對(duì)于一個(gè)全量部署,我們按順序運(yùn)行這兩個(gè) playbooks;這是非常簡(jiǎn)單的。
總結(jié)
因?yàn)?Docker 主要的一個(gè)方式是封裝基礎(chǔ)架構(gòu)到一個(gè)自包含的,可部署的包。這不需要重新構(gòu)建整個(gè)容器僅僅只是為了幾個(gè)代碼變更。通過在 Docker 中利用卷(volumes),我們從容器中移除了代碼,使得代碼能獨(dú)立于容器更新。***,我們可以使用 UWSGI 的 touch reload 特性在容器中重啟 UWSGI,并從卷(volume)中加載更新的代碼。
注:本文作者是 Joel Weirauch,原文地址:http://ionicframework.com/blog/docker-hot-code-deploys/