以Dockerfile建置python-app的映象檔
繼上一篇:初試啼聲以後,我們希望能以更少的動作來佈署我們的應用程式,當然我們也可以一步一步pull下來python環境,然後連進去做一些環境準備,最後commit回來,後續可以export成tar檔或是push到registery中,達到分享與移動。其實這個時候,我們還可以應用到Dockerfile的機制來”包裝”我們的應用程式,讓剛剛所有的事情都可以自動化做掉
以一個python-app為例,通常都會有套件清單文件(requirements.txt),我們基於特定版本,透過pip安裝相關的python套件,步驟如下:
首先先切換到原始碼目錄的根,在原始碼根目錄下建立一個名叫Dockerfile的文字檔案
# cd python-app
# sudo nano Dockerfile
我們可以以其他人的image為基礎,再往上疊加環境,以我們的程式為例:
Dockerfile:
註:一開始的From 特定其他映象檔,這裡的映象檔大部分都再向上拆解,一個完整的應用可能必須針對環境配置進行很多次的堆疊。因此若想知道Python3.5的環境怎麼建置,那就可以看library/python在DockerHub所呈現的Dockerfile的內容(傳送門),繼續回到我們的Dockerfile,其中WORKDIR是我們的當前的目錄,後續我們要針對映象檔的環境進行檔案操作或是環境配置,例如執行COPY、RUN等動作時,就會以此目錄為當前目錄,因此我們之後若要綁volumn進來的話,就可以以此為主要執行路徑。
至於Dockerfile中,我們常常會看到對於命令執行時有RUN、CMD、ENTRYPOINT三個動作,其它們的使用情境具體如下:
- RUN是以新的一層映像檔環境來執行指令,通常用於安裝軟體。
- CMD 是預設執行命令,可以定義帶入參數,這個命令是可以被覆寫的。
- 以Python:3.5為例,他預設是CMD[“python3”],因此直接run這個映象檔,並-it跟他互動的話,會直接啟動一個IDLE介面
- ENTRYPOINT 是用於包裝一個可執行的容器時,將容器的預設啟動命令統一為唯一且持續執行的命令,就可以使用此動作。
- 從運作上,CMD跟ENTRYPOINT 真的很像,以我這次打包為例,Api Server應用的啟動來聆聽特定port(這通常都是個迴圈),我把apiserver打包成一個servcie,並提供一個統一的進入點shell script,所以我使用ENTRYPOINT。
COPY requirements.txt ./代表我們將目前所在目錄下的requirementcopy進去(預設就會是在/usr/src/app);指令RUN pip install –no-cache-dir -r requirements.txt 會讓建置映檔檔的時候,就直接執行過相依套件安裝的動作;EXPOSE代表應用程式的公開port號,這並無法省略後續run的時候綁定port號的動作,因為容器啟動當下,還是要,後面Copy . .代表是將目前所在的應用程式目錄copy到映象檔的指定工作目錄,最後ENTRYPOINT是執行命令:
這邊我們執行一個叫作Run.sh的shell,這個檔案會一併的透過COPY動作COPY到WORKDIR
run.sh內容如下:
說到這邊,這算是一種小手段,主要是因為我需要起2個執行緒去監聽不同port的服務,一個是restful的api,一個是rpc的service,然而除非起2個container,執行不同的python startup命令外(理論上是這樣比較獨立啦),以目前的規模,我希望透過一個container就直接提供2種port來服務(分別是40402跟40403),統一也是為了後續scaling的時候,可以直接進行。
然而調查了一下,透過&可以執行2個python語法;但是會發現container立即會exit。原來是因為他看的command是shell的執行週期,因此shell執行完了,container以為任務已了,就會直接關閉(不論你的執行緒是否還在跑),因此想到透過無窮迴圈來保持容器的存活。當然如此代表執行緒crash了,跟容器也沒有直接關係…所以我們的log機制必須完善,否則並無法透過Docker的Status來監看運作的情形。
好的,最後輸入Build命令如下:
#sudo docker build -t some-api . (註,這個.別忘了)
立即地,docker就會開始依照Dockerfile中定義的script一步一步進行打包
接著來啟動看看
#sudo docker run -d \
-p 30303:40403 \
-p 30302:40402 \
–link mariadb:rdb \
–link cassandra-dev:cassandra-dev \
–link hadoop-dev-server:hadoop-dev-server \
–link memcache-server:memcache-server \
meso-collection-api:latest
註:這邊可以透過-p將容器內的port綁到host上的其他port號,另外,原本run的時候,可以-v掛載進去程式碼的機制,因為映像檔裡面已經有包裝過了,因此有必要的時候,再做掛載即可。或是另有新版的話,就在重新打包映象檔。
看到自動化的成果還是在run的時候這麼多東西要綁,就知道其實透過打包,我們主要是封裝環境所需要的東西。透過link相依其他container的實體還是無法省略,除非你所使用的服務獨立於docker之外,這應該更常見。 經過一輪單元測試30303跟30302的port,確定可以正常運作,這意味著透過上述一連串的動作,我們已經可以將執行環境已經”定版”並”封裝”好了,實在非常的方便。