以Dockerfile建置python-app的映象檔

以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 python:3.5

WORKDIR /usr/src/app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 40402
EXPOSE 40403

ENTRYPOINT [ "./run.sh" ]

註:一開始的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內容如下:

#!/bin/bash

python /usr/src/app/meso_collection_apiserver/grpc_server.py &
python /usr/src/app/meso_collection_apiserver/rest_server.py &

while :; do
  sleep 300
done

說到這邊,這算是一種小手段,主要是因為我需要起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一步一步進行打包

Sending build context to Docker daemon 47.46MB
Step 1/8 : FROM python:3.5
---> 0a5c3ea81b62
Step 2/8 : WORKDIR /usr/src/app
---> 67f5afe56872
Removing intermediate container 9f2daa2ac522
Step 3/8 : COPY requirements.txt ./
---> 461d0f6aa43b
Removing intermediate container 7d122608bfb1
Step 4/8 : RUN pip install --no-cache-dir -r requirements.txt
---> Running in 6b5585def0c4
Collecting grpcio==1.3.5 (from -r requirements.txt (line 1))
Downloading grpcio-1.3.5-cp35-cp35m-manylinux1_x86_64.whl (5.3MB)
......略
Collecting protobuf>=3.2.0 (from grpcio==1.3.5->-r requirements.txt (line 1))
Downloading protobuf-3.3.0-cp35-cp35m-manylinux1_x86_64.whl (5.7MB)
Collecting requests (from pywebhdfs==0.4.1->-r requirements.txt (line 9))
......略
Running setup.py install for python-memcached: finished with status 'done'
Successfully installed SQLAlchemy-1.1.10 WebHDFS-0.2.0 cassandra-driver-3.10 certifi-2017.7.27.1 chardet-3.0.4 gevent-1.2.2 greenlet-0.4.12 grpcio-1.3.5 grpcio-tools-1.3.5 idna-2.5 mysqlclient-1.3.10 protobuf-3.3.0 python-memcached-1.58 pywebhdfs-0.4.1 requests-2.18.2 six-1.10.0 tornado-4.4.2 urllib3-1.21.1
---> dab3be71da0c
Removing intermediate container 6b5585def0c4
Step 5/8 : COPY . .
---> ce77cae8921b
Removing intermediate container 8e1e68c0bc44
Step 6/8 : EXPOSE 40402
---> Running in 7715e19b90c6
---> f850b42f8fe5
Removing intermediate container 7715e19b90c6
Step 7/8 : EXPOSE 40403
---> Running in 96a030179b9b
---> 569a1d98ef45
Removing intermediate container 96a030179b9b
Step 8/8 : CMD run.sh
---> Running in cdcf635ab727
---> 37fd0a2aa368
Removing intermediate container cdcf635ab727
Successfully built 37fd0a2aa368
Successfully tagged meso-collection-api:latest

接著來啟動看看

#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,確定可以正常運作,這意味著透過上述一連串的動作,我們已經可以將執行環境已經”定版”並”封裝”好了,實在非常的方便。

 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *