[Docker Note] PyLadies TW 官方網站開發環境製作|開發筆記
大家好,我是本次負責 UI 設計的美宣志工 Ichi,PyLadies 官網改版的第一階段在今年 9 月的週年慶上線了(撒花),集結了幾個月來小組線上討論、實體聚會以及上線前趕工的各方努力!志工群下一步招募了官網小組會接手繼續第二版之後的工作。
由於第一版的環境都是在同一台 server 進行,開權限讓前後端工程師登入工作。接下來團隊擴編,希望參與者都有本機的統一環境,於是有了這次的任務:將開發環境容器化。
目前初版環境已經出來了,本文目的是為了要記錄包 docker 的心路歷程及踩到的雷,會分成「成功的版本」、「嘗試過的方向」兩部分進行。在「嘗試過的方向」中,是幾條後來沒有實作到最後的路,不過還是記錄下來供以後參考。
成功實作的版本
以 docker-compose 來串 microservice 們。具體的架構如上圖,我們有一台 production DB、一台 test DB,能用 phpmyadmin 登入,同時兩台 DB 也能讓 flask app 那台有 python 環境的容器 access,再來架一台 nginx 能支援靜動態網頁導流。
執行畫面
原本資料夾內沒有 data 及其子資料夾,在 repo 最外層執行 docker-compose up
會自動創建並對應容器內指定位置。然後至各服務指定 port 可以開啟對應服務:phpmyadmin 因為有設定 2 個 DB,這邊有選單可以選擇。然後可以在 nginx 的 5555
port 看到他和 55688
的 flask 有一樣的結果代表成功導流。
Docker Related Codes
- nginx config:https://gist.github.com/tsaiichi/243969053d2d0f4cf8b77932a11c8097
- flask app Dockerfile:https://gist.github.com/tsaiichi/1f3f095f3d14fecbfa8473f8673a4d8e
- mysql config:https://gist.github.com/tsaiichi/46df4e288563da96671571ca82305465
包 docker 基本的流程與概念
- image 指定版號:不管是寫 Dockerfile 的
FROM
或是 docker-compose 裡的image
,最好指定版號(例如:nginx:1.14-alpine
),才能真正做到統一環境。因為如果不指定版號則預設是latest
,build 的時候會去檢查最新版,導致 image 從頭重 build(因為是最底層 layer)。 - docker instance 沒事做就會 stop(掛掉):所以一開始常常會遇到,明明 image build 過了
docker run
卻沒有活著,馬上進入docker ps -a
才能看到的墓地。所以在需要 debug 的時候,最後可以加入CMD while :; do sleep 1 ; done
或是參考其他 Dockerfile 的CMD
讓 docker instance 有事做(e.g.,nginx -g 'daemon off;'
)。
補充:如果像 nginx 就算跑起來也需要等待其他 container 起床才能有效運作,在 docker-compose 裡可以加個restart: always
就會一直重啟直到可以吃到其他 host。這段如果目前還看不懂,後面的筆記會再解釋一次。 - DB 的資料如果已經存在則不會重新 init:使用 mariadb 的 image 有參數
MYSQL_DATABASE
可以直接自訂一個資料庫,但更改這邊的名字再重新啟動 instance 時,如果沒有把 mount 進去的資料夾刪掉,則視為已經存在資料,所以不會做新的 DB。
其實這 behavior 滿正常,沒有人會希望資料隨服務重啟就不見,只是在做 docker 測試或開發的時候會鬼打牆,覺得設定的新密碼怎麼沒用、資料庫沒有生出來之類的。 - Dockerfile 通常在 repo 最外層:像是 python 的 requirements.txt 有指定套件版本,製作環境的時候就要先將它複製到 container 裡做
pip install -r requirements.txt
,所以在執行docker build
ordocker-compose up
的資料夾要是能看得到 requirements.txt 的,因此建議 Dockerfile 要在 repo 最外層。 - virtual environment 與 docker:對 python 開發有點基礎的話會知道用 venv 來控制每個專案的套件定版,這件事情在轉移到 docker 上的時候,因為通常這份 docker image 就是給這個專案,所以不需要再做出虛擬環境。
- 開發版本 mount code 進去:如果去參考 production Dockerfile 的寫法,他們會將整個 repo copy 進去(
COPY . .
前面的.
是目前所在資料夾,後面的.
是 container 內的工作資料夾),但因為我們現在就是要邊改動 code 然後邊在容器測試運行,所以除了 requirements.txt 這種檔案其他就是啟動 instance 的時候帶-v
就可以了。(具體請參考 docker-compose)
成功版本兩三事:
- docker-compose
depends_on
:(參考連結)container 可以寫 depends-on 來表示依賴關係,但是不代表會等到所依賴的 container 已經 ready 了才啟動現在這個。例如 appdepends_on
DB,但在 DB 啟動後 app 容器會馬上啟動,這時可能 DB 還沒 ready 所以有些環境設定連不到 DB port 就使設定失敗,有可能沒有成功啟動服務沒事做導致容器直接掛掉。 - healthcheck 只在 docker-compose v2.1:(參考連結)在 v2.1 可以使用 healthcheck 並且依賴者可以等到
service_healthy
再開始啟動。(但version 3 沒有支援depends_on
的condition
語法:參考連結) - supervisord daemon :在 Dockerfile 的
CMD
寫 supervisord daemon 啟動,會發現他吃不到 docker-compose 給的env
,但是進 instance 內 debug 會發現env
都在,所以猜測是生出 daemon 的 shell 與 docker-compose 生出來的不同。後來這邊改成在 Dockerfile 內 foreground 執行。 link
與 docker-compose:(參考連結)如果從 docker run 去設定容器間的連結,會看到—-link
這個關鍵字,但因為在 docker-compose 中其實就已經會生成一個網路給 compose 內的容器們能用名字互通(service 的名字),所以不需要再寫link
。flask —-host 0.0.0.0
:flask 如果是單機版只需要聽 localhost, 但這次實作將 flask 跟 nginx 切成不同容器,所以要改成聽0.0.0.0
才會通。- nginx 設定連到其他容器:前面有提到 docker-compose 內的容器用名字互通,具體在 nginx 的設定裡需要定義
upstream
再寫到proxy_pass
(參考連結),注意這邊因為app
是docker-compose
內 service 的名稱,所以必須等到app
container ready 才能連到,在連到之前 nginx 要一直restart
避免直接掛掉:
http{
upstream your-app {
server app:5000; # 注意是 container 裡的 port
}
server:{
proxy_pass http://your-app;
}
}
使用 docker-compose 的好處:
- 開發客製化容器的時候會開開關關:run docker run! Oh no, stop, stop, stop! 在過程中製造很多屍體,要一一清掉 or
docker container prune
,有時候會突然忘記有沒有停掉而卡住。或是有不同的 docker image 要build
要run
,方向鍵一路向北容易手殘跑錯。這時使用 docker-compose 永遠只會用到docker-compose up -d
,docker-compose down
,docker-compose build
。(頂多想要 debug 進去容器是docker-compose exec {service_name} bash
) - 有意義的命名:剛剛說到的
docker stop
要去找docker ps
裡的名稱或 id 做指定。沒有帶—- name
的話docker run
會自動給個兩個 random 英文字的名字,沒有意義;但 compose 的預設命名會是有意義的,請看下圖 NAMES 那欄第一個名字以及後面的系列:
圖中指令多了一個 grc,grc 套件用來上色,不然有時候名字太長凸到換行很難對齊欄位就難過了。
嘗試過的方向
- 單機版一個 image 到底:因為原本的環境是單機版本,安裝說明也寫得很詳細,所以不知不覺就按著流程裝下來。裝到互動式選單的時候卡很久,去搜尋關鍵字發現很多人其實都做過這幾個組合的 docker-compose file 了,感覺可以無痛搞定,所以就跳了。
- 分成 DB、web 2 個 image:這也是一個方向,只是要注意如果有共同要安裝的東西(e.g., vim, curl, htop, networktools),最好獨立一個 image 出來,然後DB 跟 web 再在其上發展。不然前幾行很像在 duplicate code,而且因為是基本工具所以都在最底層動 script 造成 2 個 image 重 build 就很虧了。
其他 tip 補充
- timezone 要先裝 tzdata, 16.04 沒有:參考連結
- nginx config:官方說明會教你用
docker run
起一個 instance 然後docker copy
拿出預設 config 檔來改,在官方 repo 也可以直接拿到檔案就是了。注意http { include /etc/nginx/mime.types; }
不能刪掉,會造成 css 以 plantext 傳輸,render 不出好看的網頁。 - 互動式選單解法,先寫好選項:像
phpmyadmin
會跳出對話式選單做選項安裝,這時可以先以debconf-set-selections
寫好選項,具體作法請參考連結。 add-apt-repository ppa:ondrej/php
在C.UTF-8
的設定下爛掉:可以先安裝locales
套件去生出en_US.UTF-8
apt-get install locales && locale-gen en_US.UTF-8 && LC_ALL=en_US.UTF-8 add-apt-repository -y ppa:ondrej/php- install
software-properties-common
beforeadd-apt-repository
:不然會遇到 command not found - mysql 編碼使用 utf8mb4:(參考連結)因為 unittest 的時候在 DB insert 中文字串有問題,所以發現資料庫編碼要改設定,具體作法請參考「成功版本」段落 — Docker Related Codes.
- 如果需要不同的自訂 Dockerfile,可以指定檔名跟資料夾位置:(參考連結)例如有兩個以上容器都需要客製化 image 則可以指定 build 的路徑
build:
context: ./dir
dockerfile: Dockerfile-alternate