在上一篇文章中,有幸和各位
分享了一點人生的經驗,正如我在文章末所說,這些都只是滄海一粟,學會 docker 的基本概念和操作還不足以讓你快速部署代碼,今天我們就來聊一聊 Dockerfile
在了解什么是 Dockerfile之前,我們先來做一個小實驗
還記得我們在上一節中 pull 下來的 python 鏡像嗎,首先通過 docker run -it 進入容器內部,回憶一下, -it 這個參數的作用是什么,沒錯,使用這個參數,docker會為容器分配一個偽輸入終端,通過這個終端,用戶就可以和容器進行相應的交互了。
進入容器后,先列出當前路徑下的所有文件
[root@FUCC ~]# docker run -it python:3.7 /bin/bash
root@692f87774bf7:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
root@692f87774bf7:/#
OK,接著,我們將創建一個hello.py 的文件,創建完成后再次列出所有文件
root@692f87774bf7:/# touch hello.py
root@692f87774bf7:/# ls
bin boot dev etc hello.py home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
可以看到,hello.py 已經創建完成了,這時我們退出容器,使用之前的 docker run -it python:3.7 /bin/bash 命令再次進入容器的交互模式,并列出所有文件
root@692f87774bf7:/# exit
exit
[root@FUCC ~]# docker run -it python:3.7 /bin/bash
root@65c767655e8a:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
root@65c767655e8a:/#
不用我多說,相信你已經發現了問題的所在,奇怪,為什么我們之前創建的文件憑空消失了呢?
這是因為,在容器內部執行的操作并不會修改鏡像,容器其實只是在鏡像上面添加了一個可寫層,就像字帖里的臨摹紙一樣,無論你在臨摹紙上怎么寫,對下面一層都不會有任何影響,這個特性其實和VM的快照有些類似。
但是問題來了,既然在容器內部的操作都不會修改鏡像,那如何做到快速部署,每次部署代碼前都去重復執行一些操作,豈不是太low了?
假設你是一家社交網站的技術負責人,某一天,奧特蝦突然宣布戀情上了熱搜,哦不是,八對明星突然同時出軌,你需要在短時間內創建上百臺云服務器以支撐吃瓜群眾海量的請求,團隊選用了 Docker 去部署應用,如果通過上面這種方法去部署,吃瓜群眾想殺了你的心都有了,為什么?等你的服務恢復,吃瓜群眾早就散了,動不動就崩,怎么愉快吃瓜?
這時,就輪到 Dockerfile 閃亮登場了,老規矩,先看一下 Docker 官方對于 Dockerfile 的介紹
Docker can build images automatically by reading the instructions from a Dockerfile. A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. Using docker build users can create an automated build that executes several command-line instructions in succession
簡單來說,Dockerfile是一個文本文檔,其中包含用戶可以在命令行上調用以構建鏡像的所有命令,舉個例子,你可以理解為構建鏡像是一個搭積木的過程,Dockerfile 就像是說明書一樣,Docker 會根據這份說明書去構建你想生成的鏡像。
接下來我們會從一個實際的場景出發,講一講如何構建 Dockerfile
依舊是以一個 python 鏡像為例,在我的本地(容器外部),有一個 Optimal_Hotel_Matching.py 的代碼文件,這是一個爬蟲程序,先嘗試在容器中運行它
果不其然,出現了報錯,這是因為在python鏡像中,并沒有安裝requests這個第三方的http庫,這其實就是一個典型的安裝依賴環境的過程,我們試著用 Dockerfile 去解決這個問題
[root@FUCC dockerfileTest]# ls
Optimal_Hotel_Matching.py
[root@FUCC dockerfileTest]# docker run -v $PWD:/usr/src/code -w /usr/src/code python:3.7 python Optimal_Hotel_Matching.py
Traceback (most recent call last):
File "Optimal_Hotel_Matching.py", line 1, in <module>
import requests
ModuleNotFoundError: No module named 'requests'
[root@FUCC dockerfileTest]#
在編寫第一個 Dockerfile 之前,我們先來熟悉一下 Dockerfile 中幾個常用的指令
FROM
語法: FROM <image>
說明:這是 Dockerfile 中的第一條指令,它用于指定一個構建鏡像的基礎源鏡像,如果本地沒有的話, Docker 會從倉庫中自動拉取
MAINTAINER
語法:MAINTAINER <name> <email>
說明:用于描述鏡像創建者的名稱和郵箱
RUN
語法: RUN <command> <param1> <param2>
說明:RUN 絕對是 Dockerfile 中最重要的命令之一了,當RUN執行完成后,會在原先的基礎鏡像上創建一個新的鏡像層,每執行一次RUN就會產生一個新的層。通常,安裝軟件或者配置環境都是通過RUN來實現的
CMD
語法: CMD <command> <param1> <param2>
說明:CMD相對而言比較簡單,相信大家看到名字就能理解,這個指令用于指定容器啟動時執行的命令
Tips: 在我剛接觸Docker的時候,RUN 和 CMD 一度傻傻分不清楚,其實這兩者的作用完全不同,RUN 用于構建鏡像,是對鏡像進行操作,而 RUN 則是指定了容器啟動時執行的命令,是對容器進行操作
COPY
語法:COPY <src> <dest>
說明:COPY 用于復制本地文件,并將其放置在指定的容器目錄,但是需要注意的是 COPY 并不能將復制的壓縮文件自動解壓,也不能復制網絡文件,如果有這兩點需求可以使用 ADD 命令,這兩個命令的用法是相同的, ADD 可以將復制的壓縮文件自動解壓
WORKDIR
語法: WORKDIR <path>
說明:為RUN、CMD、ENTRYPOINT指令配置工作目錄,在一個 Dockerfile 中可以使用多個WORKDIR指令
編寫第一個Dockerfile
掌握了以上指令后,我們就已經能寫一個較為基礎的 Dockerfile 了,回到我們之前的需求,一句話總結下來就是,安裝requests庫后運行代碼文件
思考一下,應該怎么做?
如果你還是沒有眉目,不妨來看看我為你準備的參考答案
FROM python:3.7 #指定基礎鏡像為python:3.7
MAINTAINER ultraxia "ultraxia@foxmail.com" #維護者名稱和郵箱
RUN pip install requests #安裝requests庫,并構建一個新的鏡像層
WORKDIR /dockerfileTest #指定工作目錄為/dockerfileTest
COPY . . #將當前目錄下的文件復制到容器中,這里為工作目錄
CMD [ "python", "Optimal_Hotel_Matching.py" ] #運行代碼
將上面的內容保存為名稱為Dockerfile的文件,并放置在項目所在的目錄
[root@FUCC dockerfileTest]# ls
Dockerfile Optimal_Hotel_Matching.py
開始構建
這時,我們就可以使用docker build去構建了,通常為了區分不同的鏡像,需要給鏡像打上tag(標簽)
[root@FUCC dockerfileTest]# docker build --tag python:requests .
Sending build context to Docker daemon 5.12 kB
Step 1/6 : FROM python:3.7
---> 42d620af35be
Step 2/6 : MAINTAINER ultraxia "ultraxia@foxmail.com"
---> Running in b9ca2049ef19
---> c6fb2a731a82
Removing intermediate container b9ca2049ef19
Step 3/6 : RUN pip install requests
---> Running in 4edba5ca3ead
Collecting requests
Downloading https://files.pythonhosted.org/packages/51/bd/23c926cd341ea6b7dd0b2a00aba99ae0f828be89d72b2190f27c11d4b7fb/requests-2.22.0-py2.py3-none-any.whl (57kB)
Collecting urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 (from requests)
Downloading https://files.pythonhosted.org/packages/e6/60/247f23a7121ae632d62811ba7f273d0e58972d75e58a94d329d51550a47d/urllib3-1.25.3-py2.py3-none-any.whl (150kB)
解釋一下這條命令
docker build 命令用于使用 Dockerfile 創建鏡像
--tag 參數用于指定鏡像tag,這里我們命名為python:requests
. docker 會從當前目錄尋找Dockerfile文件,并開始構建鏡像
構建完成!使用 docker images命令驗證一下吧
[root@FUCC dockerfileTest]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
python requests 04f20acdc288 2 hours ago 926 MB
docker.io/python 3.7 42d620af35be 3 weeks ago 918 MB
docker.io/rabbitmq 3-management 7aae48fa6ef6 3 weeks ago 179 MB
docker.io/golang latest f50db16df5da 4 weeks ago 774 MB
docker.io/mariadb latest 3a2ef06682ac 4 weeks ago 356 MB
docker.io/centos latest 9f38484d220f 4 months ago 202 MB
docker.io/hello-world latest fce289e99eb9 7 months ago 1.84 kB
docker.io/django latest eb40dcf64078 2 years ago 436 MB
[root@FUCC dockerfileTest]#
可以看到,此時本地的倉庫中已經生成了一個tag為requests的python鏡像。試著運行一下這個鏡像,看看是否還會出現No module named 'requests'的報錯,以及,程序是否會如我們期待的一樣開始運行。
[root@FUCC dockerfileTest]# docker run python:requests
Computing distance between 116.368816,39.866464 and 116.438946,39.921624
Computing distance between 116.370910,39.869603 and 116.438946,39.921624
Computing distance between 116.409583,39.983356 and 116.438946,39.921624
Computing distance between 116.302621,39.966272 and 116.438946,39.921624
Computing distance between 116.322551,39.886995 and 116.438946,39.921624
可以看到,運行成功,容器中的代碼運行完成后,容器退出。
完結了嗎?
在這一篇里,我通過一個簡單的案例和各位分享了 Dockerfile 的簡單應用,事實上在企業的實際生產中,Dockerfile 的內容會比這個更復雜一些。
最后,正好趕上七夕,祝各位七夕快樂,下期再見。
今天過節的人會有時間看你的文章嗎?不會