Docker 快速部署代碼之道
在 Ionic,我們是 Docker 的鐵桿粉絲。我們的代碼以及代碼的依賴全部運行在 Docker 中,Docker 讓我們的產品更充分地利用計算資源,比如 Ionic Creator,以及即將到來的 Ionic.io 服務。
使用 Docker 面對的一個挑戰(zhàn)是,盡管我們只是對我們的代碼做了一個小小的變更,我們都必須要走一遍構建一個新容器的過程,把它拉?。╬ull)到我們的服務器,并替代正在運行的版本。
我們所有的代碼都存儲在 GitHub,使用 Docker Registry(這里推薦下國內的 docker.cn,速度比官方的快很多,不用擔心“你懂的”問題) 來自動構建和存儲我們的代碼,并使用 Ansible 來管理和部署我們的容器到我們的服務器上。即使是一個完全自動化的過程,部署一個小變更都可能需要花費我們 20 分鐘或者更多的時間。經過頭腦風暴,我們意識到我們有一個更好的方法來利用 Docker。
在最初的容器構建之后,99% 的變更都是純代碼。我們不需要添加任何依賴,或者是改變任何代碼運行所必需的東西。Docker 實際上只是一種封裝基礎架構的方式,要求我們的代碼運行在一個自包含的包中。因為我們 99% 的變更都是代碼,不是基礎架構,我們意識我們不需要在每次變更的時候都努力重新構建我們的基礎架構。
讓我們解決這個問題的是 Docker 的殺手級特性 volumes。在我們 Docker files 的***次迭代中,我們從 GitHub 拉取代碼,并直接構建進容器中?,F(xiàn)在,我們故意把代碼放在容器外面,并在容器啟動的時候,通過加載一個主機卷(host volume) 來代替。當我們想做一個新發(fā)布,Ansible 從 GitHub 上拉取 master 分支到我們服務器的 app 目錄。這時,它通過檢查來確保相關聯(lián)的容器正在運行,如果沒有在運行,它將啟動這個容器并把 app 代碼映射進容器。
使得我們的工作更便捷的另外一個組件是 uWSGI,因為我們的大部分 app 是 Python 的(Django),所以我們在 Docker 容器中使用 uWSGI 提供服務。uWSGI 有一個 touch reload 特性,可以監(jiān)控指定的文件,當該文件被 touch 的時候,會重載 uWSGI 服務。在 Ansible 從 GitHub 拉取我們的變更之后,我們使用 Ansible 來 touch uwsgi.ini 文件,這會觸發(fā)正在運行的容器中的 uWSGI 重載。我們就是這樣來運行我們代碼的更新版本的!
這是什么意思呢?簡單地說,花費我們 20+ 分鐘的部署過程是這樣的:
- 提交(Commit)和 推送(push)變更到 GitHub。
- Docker Registry 拉?。╬ulls)變更和構建一個新容器。
- Ansible 連接到我們的服務器并拉取(pulls)這個新容器 。
- Ansible 發(fā)現(xiàn)任何舊容器正在運行的實例并停止它們。
- Ansible 啟動該容器的新實例。
類似的 10 秒的過程是這樣的:
- 提交(Commit)和 推送(push)變更到 GitHub。
- Ansible 連接到我們的服務器,從 GitHub 拉取***的 master。
- Ansible touches 該 app 的 uwsgi.ini 文件來觸發(fā) UWSGI 的重載。
步驟分解
Supervisor / uWSGI
我們在 Docker 容器中使用 Supervisor 來啟動容器中的進程運行。我們的 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
選項來把 uwsgi.ini 文件作為觸發(fā)文件。
Docker
當我們啟動我們的容器,我們添加一個包含我們 app 代碼的主機卷(host volume),該主機卷被映射到容器中的一個 app 路徑,uWSGI 將從這個路徑加載 app。
- docker run -d -P -v /path/to/code/on/host:/path/to/code/in/container --name=container_name driftyco/testapp
Ansible
Ansible 負責從 GitHub 克?。╟lone)我們應用程序的代碼到我們主機的 app 目錄,確保 Docker 容器正在運行以及 touch 配置的 uWSGI touch-reload 文件。我們已經創(chuàng)建了 playbooks 來直接部署我們的每個服務,因此部署僅僅是一個運行正確的問題。
對于一個快速代碼部署,我們運行一個包含這些任務的 playbook,并只需要幾秒來運行:
- - set_fact: host_volume="/path/to/code/on/host" - name: Git pull the latest code
- git: repo=git@github.com:{{ org }}/{{ container }}.git 對于一個全量部署,我們按順序運行這兩個 playbooks;這是非常簡單的。
總結
因為 Docker 主要的一個方式是封裝基礎架構到一個自包含的,可部署的包。這不需要重新構建整個容器僅僅只是為了幾個代碼變更。通過在 Docker 中利用卷(volumes),我們從容器中移除了代碼,使得代碼能獨立于容器更新。***,我們可以使用 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
如果我們需要重啟整個容器或者是更新我們的系統(tǒng)包,我們可以做一個容器部署,這將花費幾分鐘,使用這些任務:
- - 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 }}
對于一個全量部署,我們按順序運行這兩個 playbooks;這是非常簡單的。
總結
因為 Docker 主要的一個方式是封裝基礎架構到一個自包含的,可部署的包。這不需要重新構建整個容器僅僅只是為了幾個代碼變更。通過在 Docker 中利用卷(volumes),我們從容器中移除了代碼,使得代碼能獨立于容器更新。***,我們可以使用 UWSGI 的 touch reload 特性在容器中重啟 UWSGI,并從卷(volume)中加載更新的代碼。
注:本文作者是 Joel Weirauch,原文地址:http://ionicframework.com/blog/docker-hot-code-deploys/