今天項(xiàng)目需要將python項(xiàng)目封裝成docker提供服務(wù)囱皿,參考同事的Docker封裝代碼一邊鞏固學(xué)習(xí)一邊實(shí)戰(zhàn)捧搞。首先熟悉一下docker的一些概念映穗。然后用一個(gè)實(shí)例介紹Dockerfile的指令萝招,然后寫一個(gè)dockerfile實(shí)例,最后使用把這個(gè)鏡像制作出來样傍,并運(yùn)行擦耀。
關(guān)于在鏡像封裝中遇到的問題排惨,或者覺得這個(gè)筆記沒有記錄清楚的內(nèi)容,大家也可以留言告訴我幌氮,我會(huì)定期查看留言,并更新對(duì)應(yīng)的問題胁澳。
一该互、基礎(chǔ)概念
1. 鏡像和容器
鏡像和容器就像java語言的類和類的實(shí)例
2. 鏡像的存儲(chǔ)
docker的鏡像是多層存儲(chǔ)的,每一層是在前一層的基礎(chǔ)上進(jìn)行的修改韭畸;而容器同樣也是多層存儲(chǔ)宇智,是在以鏡像為基礎(chǔ)層,在其基礎(chǔ)上加一層作為容器運(yùn)行時(shí)的存儲(chǔ)層胰丁。
3. 常用的指令
常用的指令包括拉取鏡像随橘、刪除鏡像、創(chuàng)建容器锦庸、進(jìn)入容器進(jìn)行修改机蔗、刪除容器...
docker pull ubuntu
docker image ls -a
# 查看鏡像、容器數(shù)據(jù)卷所占用的空間
docker system df
# 刪除無用的鏡像甘萧,比如none虛懸鏡像
docker image prune
# 刪除本地鏡像
docker image rm [選項(xiàng)] <鏡像1> [<鏡像2> ...]
<鏡像> 可以是 鏡像短 ID 萝嘁、 鏡像長(zhǎng) ID 、 鏡像名 或者 鏡像摘要
# 運(yùn)行容器
# -p 81:80表示將內(nèi)部的80端口映射到外部的81端口扬卷,之后可以通過訪問 http://localhost:81 看到結(jié)果
docker run --name webserver -d -p 81:80 nginx
# 進(jìn)入容器并修改
docker exec -it webserver bash
root@3729b97e8226:/# echo '<h1>Hello, Docker!</h1>' > /usr/share
/nginx/html/index.html
root@3729b97e8226:/# exit
exit
# 查看容器的改動(dòng)
docker diff webserver
# 查看容器
docker container ls -a
# 刪除容器
docker container rm trusting_newton
#如果要?jiǎng)h除一個(gè)運(yùn)行中的容器牙言,可以添加 -f 參數(shù)
# 清理所有處于終止?fàn)顟B(tài)的容器
docker container prune
4. Docker鏡像制作
自己得到新鏡像的方式有兩種,一種是commit方式一種是使用dockerfile方式怪得。其中前者不推薦咱枉,但是可以簡(jiǎn)單的介紹一下。
在本地修改了一個(gè)正在運(yùn)行的容器之后(比如在上面的代碼中的“進(jìn)入容器并修改”)徒恋,可以通過下面的代碼保存這個(gè)容器為新的鏡像
docker commit \
--author "Tao Wang <twang2218@gmail.com>" \
--message "修改了默認(rèn)網(wǎng)頁" \
webserver \
nginx:v2
通過docker image ls
可以查到到新的鏡像蚕断。
慎用commit使用 docker commit 命令雖然可以比較直觀的幫助理解鏡像分層存儲(chǔ)的概念,但是實(shí)際環(huán)境中并不會(huì)這樣使用因谎。仔細(xì)觀察之前的 docker diff webserver 的結(jié)果基括,你會(huì)發(fā)現(xiàn)除了真正想要修改的 /usr/share/nginx/html/index.html文件外,由于命令的執(zhí)行财岔,還有很多文件被改動(dòng)或添加了风皿。這還僅僅是最簡(jiǎn)單的操作,如果是安裝軟件包匠璧、編譯構(gòu)建桐款,那會(huì)有大量的無關(guān)內(nèi)容被添加進(jìn)來,如果不小心處理夷恍,將會(huì)導(dǎo)致鏡像極為臃腫魔眨。
二、正確的定制鏡像的方式
正如上面提到的,正確的方式應(yīng)該是Dockerfile方式遏暴,這種方式可以簡(jiǎn)單的概括為兩步:
- 寫一個(gè)Dockerfile文件侄刽。對(duì)的,文件名就叫Dockerfile,無后綴名稱朋凉。把這個(gè)文件放在項(xiàng)目的目錄中州丹,這個(gè)文件中包含了一條條的指令(Instruction),每一條指令構(gòu)建一層杂彭,因此每一條指令的內(nèi)容墓毒,就是描述該層應(yīng)當(dāng)如何構(gòu)建。
- 使用
docker build 鏡像名稱 上下文路徑
的方式構(gòu)建鏡像亲怠。
下面進(jìn)入第一步所计,先放上一個(gè)同事的鏡像(代碼來自fan),然后一步步的根據(jù)這個(gè)鏡像學(xué)習(xí)里面的常用docker指令团秽,以及一些小的初學(xué)者(就是我)要記住的知識(shí)點(diǎn)主胧。
from ubuntu:18.04
MAINTAINER fan dingling@dinglingdingling.cn
ARG ANTISPAM_HOME
ARG PORT
ENV LC_ALL=zh_CN.UTF-8
COPY ${ANTISPAM_HOME:-.}/dashboard /usr/lib/antispam/dashboard
COPY ${ANTISPAM_HOME:-.}/bin /usr/lib/antispam/bin
COPY ${ANTISPAM_HOME:-.}/requirements /usr/lib/antispam/requirements
EXPOSE ${PORT}
RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
RUN apt-get update
RUN apt-get install -y language-pack-zh-hans
RUN apt-get install -y python3-pip
RUN pip3 install -i http://pypi.douban.com/simple -r /usr/lib/antispam/requirements --trusted-host pypi.douban.com
ENTRYPOINT ["/usr/lib/antispam/bin/start-dashboard.sh"]
二一、常用的dockerfile指令以及編寫注意事項(xiàng)
上下文環(huán)境的概念
1. FROM 需要修改的鏡像名稱
這個(gè)必須是第一條指令徙垫,指明了需要修改的鏡像讥裤。如果本地不存在,則默認(rèn)會(huì)去Docker Hub下載指定鏡像姻报。這里我們直接用ubuntu虛擬機(jī)己英。
2. MAINTAINER 標(biāo)明作者
該信息將會(huì)寫入生成鏡像的Author屬性域中。這個(gè)就像我們?cè)趯慾ava或者python代碼的時(shí)候吴旋,需要注明作者一樣一樣的损肛。后面創(chuàng)建鏡像的時(shí)候會(huì)提到這個(gè)指令的一個(gè)重要作用
3. ARG: 定義構(gòu)建過程中需要的參數(shù)
用戶在使用docker build構(gòu)建鏡像的時(shí)候,需要以--build-arg ANTISPAM_HOME=XXX --build-arg PORT=XXX
的形式這些參數(shù)傳入荣瑟,否則會(huì)報(bào)錯(cuò)哦治拿。當(dāng)然也可以設(shè)置一個(gè)默認(rèn)值。
ARG a_nother_name=a_default_value # 指定默認(rèn)值笆焰。
如果在Dockerfile中劫谅,ARG指令定義參數(shù)之前,就有其他指令引用了參數(shù)嚷掠,則參數(shù)值為空字符串捏检。
不建議在構(gòu)建的過程中,以參數(shù)的形式傳遞保密信息不皆,如key, password等贯城。
4. ENV: 在鏡像的構(gòu)建過程中設(shè)置環(huán)境變量
后續(xù)RUN可以使用這些環(huán)境變量,生成了鏡像,運(yùn)行成容器之后霹娄,這個(gè)環(huán)境變量也會(huì)被保存在env中能犯。
未來以這個(gè)鏡像為基礎(chǔ)鏡像再構(gòu)建一個(gè)新的鏡像v2鲫骗,那么v2也會(huì)擁有該環(huán)境變量.
通過ENV定義的環(huán)境變量不能被后面的CMD指令使用,也不能被docker run 的命令參數(shù)引用踩晶。
如果不想再dockerfile中寫這個(gè)環(huán)境變量执泰,同時(shí)自己的記性足夠的好,那么可以在創(chuàng)建容器的時(shí)候使用docker run -it -e "env_1=hahaha" 鏡像名
的方式傳入環(huán)境變量渡蜻。
5. EXPOSE :暴露端口坦胶,非必須
指明這個(gè)容器要暴露出來的內(nèi)部端口,然后系統(tǒng)可以通過docker run -p 內(nèi)部端口:外部端口
的方式訪問這個(gè)暴露的端口晴楔,不是必須的,沒有這個(gè)EXPOSE指令峭咒,照樣可以通過 -p命令暴露端口税弃。
6. WORKDIR: 設(shè)置工作目錄
設(shè)置Dockerfile的任何RUN/CMD/ENTRPOINT,COPY,ADD指令的工作目錄。
這個(gè)指令雖然這里沒有用到凑队,但是也挺常用的则果。當(dāng)使用相對(duì)目錄的情況下,采用上一個(gè)WORKDIR指定的目錄作為基準(zhǔn)漩氨。舉個(gè)例子,我想要封裝的項(xiàng)目是projectA西壮,那么我希望在docker的虛擬環(huán)境中,也有這樣一個(gè)目錄叫projectA叫惊,于是我可以使用這個(gè)指令創(chuàng)建這個(gè)目錄款青,然后將我project中的代碼一股腦的扔到這個(gè)路徑中(前面提到了這會(huì)成為COPY和ADD指令的工作目錄):
WORKDIR /projectA
ADD . /projectA
# 上面一句和下面一句是同一個(gè)意思,想一想是為啥
# ADD . .
相當(dāng)與cd 命令,但不同的是指定了WORKDIR后霍狰,容器啟動(dòng)時(shí)執(zhí)行的命令會(huì)在該目錄下執(zhí)行.
7. ADD&©:同樣是將本地的文件/目錄拷貝到docker中
區(qū)別在于copy不會(huì)對(duì)壓縮文件進(jìn)行解壓抡草,也不能通過url鏈接進(jìn)行拷貝。一般的語法為ADD [本地路徑/文件] [虛擬環(huán)境路徑]
需要注意本地的路徑一定要在docker的上下文環(huán)境中蔗坯。 對(duì)于 COPY 和 ADD 命令來說康震,如果要把本地的文件拷貝到鏡像中,那么本地的文件必須是在上下文目錄中的文件宾濒。其實(shí)這一點(diǎn)很好解釋腿短,因?yàn)樵趫?zhí)行 build 命令時(shí),docker 客戶端會(huì)把上下文中的所有文件發(fā)送給 docker daemon绘梦¢俪溃考慮 docker 客戶端和 docker daemon 不在同一臺(tái)機(jī)器上的情況,build 命令只能從上下文中獲取文件谚咬。如果我們?cè)?Dockerfile 的 COPY 和 ADD 命令中引用了上下文中沒有的文件鹦付,就會(huì)收到“no such file or directory”報(bào)錯(cuò)。
8. RUN: 運(yùn)行指令择卦,比如apt-get install一些軟件啥的
RUN語句一般有兩種使用方式
-
RUN <COMMAND>
在shell中使用/bin/sh作為執(zhí)行器執(zhí)行命令 -
RUN ["executable", "param1", "param2"]
調(diào)用申明exec執(zhí)行命令敲长,比如“/bin/bash”(注意要使用雙引號(hào))RUN ["/bin/bash","-c","echo hello"]
每一個(gè)RUN語句都是在當(dāng)前的基礎(chǔ)上執(zhí)行指定命令郎嫁,并提交為新的鏡像。當(dāng)命令較長(zhǎng)時(shí)祈噪,可以使用\來換行泽铛。如果想要在一個(gè)RUN中跑很多個(gè)任務(wù),那可以使用 &&
連接辑鲤。
來看看同事的這段代碼中盔腔,使用RUN做了什么
-
RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
更換ubuntu的源為國內(nèi)源,加快下載的速度月褥。 sed是文件處理工作弛随,主要以行為單位進(jìn)行處理,可以將數(shù)據(jù)行進(jìn)行替換宁赤、刪除舀透、新增、選取等特定工作决左。sed -i 's/要替換的字符串/更新的字符串/g' filename
命令的作用是替換文件愕够。 -
RUN apt-get update
下載器的更新,沒啥好說的 -
RUN apt-get install -y language-pack-zh-hans
下載并安裝簡(jiǎn)體中文包 -
RUN apt-get install -y python3-pip
安裝pip3 -
RUN pip3 install -i http://pypi.douban.com/simple -r /usr/lib/antispam/requirements --trusted-host pypi.douban.com
使用pip3的下載requirements文件中的python依賴包佛猛,同時(shí)指定使用國內(nèi)的pip源(這樣速度會(huì)快很多惑芭,超級(jí)贊)
所以走到這一步的時(shí)候,同事已經(jīng)完成了python環(huán)境的配置啦继找。
如果對(duì)requirements文件是咋么生成的有疑惑的童鞋可以看下面的附錄遂跟,考慮到不是docker的主要內(nèi)容,正文里面就不詳細(xì)介紹啦婴渡。
9. ENTRYPOINT&&CMD:都是在docker鏡像啟動(dòng)時(shí)漩勤,執(zhí)行命令。
這個(gè)語句感覺上已經(jīng)和docker 鏡像制作沒啥關(guān)系了缩搅,這個(gè)指定docker run起來的時(shí)候越败,開機(jī)執(zhí)行的語句。
類似于RUN命令硼瓣,這里也有兩種使用方式:
-
CMD [command]
使用exec模式 -
CMD "command"
使用shell模式
這里要大寫的注意究飞!
使用 shell 模式時(shí),docker 會(huì)以 /bin/sh -c "command"
的方式執(zhí)行任務(wù)命令堂鲤。 這意味著容器的1 號(hào)進(jìn)程不是任務(wù)進(jìn)程而是 bash 進(jìn)程亿傅,這樣我們執(zhí)行的命令或者腳本可以取到環(huán)境變量。
使用exec模式瘟栖,會(huì)將command的語句指定為1號(hào)進(jìn)程葵擎。這意味著,如果你開啟了這個(gè)鏡像的容器半哟,然后使用docker exec 容器名 ps aux
去查看酬滤,會(huì)發(fā)現(xiàn)該命令的進(jìn)程為1. exec 模式是建議的使用模式签餐,因?yàn)楫?dāng)運(yùn)行任務(wù)的進(jìn)程作為容器中的 1 號(hào)進(jìn)程時(shí),我們可以通過 docker 的 stop 命令優(yōu)雅的結(jié)束容器.
exec 模式的特點(diǎn)是不會(huì)通過 shell 執(zhí)行相關(guān)的命令盯串,所以像 $HOME 這樣的環(huán)境變量是取不到的:
FROM ubuntu
CMD [ "echo", "$HOME" ]
# 如果想要取到氯檐,需要使用這樣的表示,指定執(zhí)行shell來獲得
FROM ubuntu
CMD [ "sh", "-c", "echo $HOME" ]
二二体捏、寫自己的鏡像文件Dockerfile
我需要封裝的是一個(gè)python項(xiàng)目冠摄,可以使用CPU,也可以使用GPU几缭,處于簡(jiǎn)單的角度考慮河泳,我先寫一個(gè)CPU版本的∧晁ǎ總結(jié)如下:
- 鏡像源是ubuntu:18.04
- 指定workdir
- 將本地的項(xiàng)目文件拷貝到鏡像的項(xiàng)目目錄中
- 更改ubuntu的源路徑
- 更新apy-get update
- 下載一些依賴
- 下載簡(jiǎn)體中文包
- 安裝python3-pip
- 安裝TensorFlow1.10.0的二進(jìn)制版本(pip默認(rèn)安裝的tensorflow版本沒有對(duì)AVX指令集進(jìn)行支持)
- 將項(xiàng)目的其他依賴包放在requirements文件中乔询,使用pip3下載安裝
FROM ubuntu:18.04
MAINTAINER wenting
LABEL version="1.0"
WORKDIR /chineseocr_api
ADD . /chineseocr_api
RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
RUN rm -rf /var/lib/apt/lists/* # 如果修改加速鏡像之后,發(fā)現(xiàn)docker還是從源路徑拉取安裝包韵洋,那么這句話就很重要了
RUN apt-get update
RUN apt-get install libsm6 libxrender1 libxext-dev gcc -y
RUN apt-get install -y language-pack-zh-hans
RUN apt-get install -y python3-pip
RUN pip3 install tensorflow-1.10.0-cp36-cp36m-linux_x86_64.whl
RUN pip3 install -i http://pypi.douban.com/simple -r /chineseocr_api/requirements --trusted-host pypi.douban.com
二三、Docker build構(gòu)建鏡像
這個(gè)指令挺簡(jiǎn)單的 給出想要命名的鏡像名稱和創(chuàng)建的上下文環(huán)境
sudo docker build -t chineseocr_api/v1 .
如果在build的過程中出錯(cuò)了黄锤,不用擔(dān)心錯(cuò)處語句前面的工作都白做了搪缨。因?yàn)閐ocker build語句使用了緩存機(jī)制。
Dockerfile的每條指令都會(huì)將結(jié)果提交為新的鏡像鸵熟。下一條指令基于上一條指令的鏡像進(jìn)行構(gòu)建副编。也就是你修改Dockerfile后,build任務(wù)會(huì)快速略過你之前成功的步驟流强,從你修改的那一步之后的操作痹届,都會(huì)重新運(yùn)行。因此打月,為了有效的利用緩存队腐,盡量保持Dockerfile一致,并且盡量在末尾修改奏篙。
更改 MAINTAINER 指令會(huì)使Docker強(qiáng)制執(zhí)行 run 指令來更新apt柴淘,而不是使用緩存。
二四秘通、 運(yùn)行一下我們創(chuàng)建的鏡像
此時(shí)为严,鏡像是在本地的》蜗。可以通過docker images ls
來查看鏡像第股。
然后就可以使用下面的命令運(yùn)行這個(gè)鏡像。
sudo docker run --name chineseocr_api -p 7070:7070 chineseocr_api/v1 python3 server.py
這里使用--name
為容器命令话原,使用-p 7070:7070
指定容器內(nèi)端口和外部端口的對(duì)應(yīng)關(guān)系夕吻。 python server.py
表示開啟容器之后诲锹,運(yùn)行/bin/sh -c python3 server.py
這樣我的服務(wù)就以docker的形式完成的封裝以及提供了服務(wù)。大家可以通過訪問 http://hostname:7070來提交查詢梭冠。
參考資料
附錄:
A. requirements是如何生成的
在寫python項(xiàng)目的時(shí)候辕狰,推薦的使用conda構(gòu)建自己的python依賴環(huán)境,這樣可以通過conda list -e>requirements
的方式控漠,將依賴導(dǎo)出蔓倍,導(dǎo)出的形式是這樣的:
readline==7.0
requests==2.22.0
scipy==1.3.0
setuptools==41.0.1
setuptools==39.1.0
······
但是這里需要注意的是,這種導(dǎo)出方式導(dǎo)出的只是使用conda install
下載的依賴盐捷。
如果是使用pip下載的依賴偶翅,那么可以使用pip freeze>requirements
的方式導(dǎo)出依賴。
這樣導(dǎo)出了之后碉渡,可以通過pip install -r requirement
的方式批量下載依賴聚谁。
最后
如果在docker容器封裝中,遇到問題滞诺,可以留言形导,我會(huì)定期查看留言,如果有我能解決的也會(huì)和你討論一下习霹。共同學(xué)習(xí)共同進(jìn)步~