在 Ionic,咱們是 Docker 的鐵桿粉絲。咱們的代碼以及代碼的依賴所有運行在 Docker 中,Docker 讓咱們的產品更充分地利用計算資源,好比 Ionic Creator,以及即將到來的 Ionic.io 服務。html
使用 Docker 面對的一個挑戰是,儘管咱們只是對咱們的代碼作了一個小小的變動,咱們都必需要走一遍構建一個新容器的過程,把它拉取(pull)到咱們的服務器,並替代正在運行的版本。git
咱們全部的代碼都存儲在 GitHub,使用 Docker Registry(這裏推薦下國內的 docker.cn,速度比官方的快不少,不用擔憂「你懂的」問題) 來自動構建和存儲咱們的代碼,並使用 Ansible 來管理和部署咱們的容器到咱們的服務器上。即便是一個徹底自動化的過程,部署一個小變動均可能花費咱們 20 分鐘或者更多的時間。通過頭腦風暴,咱們意識到咱們有一個更好的方法來利用 Docker。github
在最初的容器構建以後,99% 的變動是純代碼。咱們不須要添加任何依賴,或者是改變任何代碼運行所必需的東西。Docker 實際上只是一種封裝基礎架構的方式,要求咱們的代碼運行在一個自包含的包中。由於咱們 99% 的變動都是代碼,不是基礎架構,咱們意識咱們不須要在每次變動的時候都努力從新構建咱們的基礎架構。docker
讓咱們解決這個問題的是 Docker 的殺手級特性 volumes。在咱們 Docker files 的第一次迭代中,咱們從 GitHub 拉取代碼,並直接構建進容器中。如今,咱們故意把代碼放在容器外面,並在容器啓動的時候,經過加載一個主機卷(host volume) 來代替。當咱們想作一個新發布,Ansible 從 GitHub 上拉取 master 分支到咱們服務器上的 app 目錄。這時,它經過檢查來確保相關聯的容器正在運行,若是沒有在運行,它將啓動這個容器並把 app 代碼映射進容器。服務器
使得咱們的工做更便捷的另一個組件是由於咱們的大部分 app 是 Python 的(Django),咱們在 Docker 容器中使用 uWSGI 提供服務。uWSGI 有一個 touch reload 特性,能夠監控指定的文件,當該文件被 touch 的時候,會重載 uWSGI 服務。在 Ansible 從 GitHub 拉取咱們的變動以後,咱們使用 Ansible 來 touch uwsgi.ini 文件,這會觸發正在運行的容器中的 uWSGI 重載。咱們就是這樣來運行咱們代碼的更新版本的!架構
這是什麼意思,簡單地說,花費咱們 20+ 分鐘的部署過程是這樣的:app
相似的 10 秒的過程是這樣的:ionic
咱們在 Docker 容器中使用 Supervisor 來啓動容器中的進程運行。咱們的 supervisord.conf 文件看起來像下面這樣:ide
[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 文件做爲觸發文件。ui
當咱們啓動咱們的容器,咱們添加一個包含咱們 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 負責從 GitHub 克隆(clone)咱們應用程序的代碼到咱們主機的 app 目錄,確保 Docker 容器正在運行以及 touch 配置的 uWSGI touch-reload 文件。咱們已經建立了 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 dest={{ host_volume }} accept_hostkey=yes force=yes - name: Gracefully reload uwsgi file: path={{ touch_file }} state=touch
若是咱們須要重啓整個容器或者是更新咱們的系統包,咱們能夠作一個容器部署,這將花費幾分鐘,使用這些任務:
- 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,本文原文是 Fast code deployments with Docker