Dockerfile看這一篇就夠夠的了

一、 帶着問題學Dockerfile

1、疑問

我們都知道從遠程倉庫可以pull一個tomcat等鏡像下來,然後docker run啓動容器,然後docker exec -it 容器id /bin/bash進入容器,往webapps下仍我們的程序。等等這一系列操作,都需要人工一步步的去操作,那我問你:你沒qa和生產環境的部署權限,你咋操作這些?這就需要將所有人工一步步操作的地方都寫到Dockerfile文件裏,然後將文件給到運維人員,他們build成鏡像然後進行啓動。

2、舉例

比如:你要用tomcat部署一個war包,這時候你的Dockerfile文件內容會包含如下:

  • 將tomcat從遠程倉庫拉下來
  • 進入到tomcat的webapps目錄
  • 將宿主機上的war包扔到容器的webapps目錄下

然後運維拿着這個Dockerfile進行build成image,在run一下啓動容器。大功告成

3、好處

上面的例子好處不難發現

  • Dockerfile解放了手工操作很多步驟
  • Dockerfile保證了環境的統一

再也不會出現:QA是正常的,線上就是不行的情況了(前提是由於環境問題導致的 ),因爲Dockerfile是同一份,大到環境,小到版本全都一致。再有問題那也是代碼問題,節省了和運維人員大量“親密接觸”的時間。

二、什麼是Dockerfile

知道Dockerfile是幹嘛的了,那Dockerfile的定義到底是啥呢?

Dockerfile中文名叫鏡像描述文件,是一個包含用於組合鏡像目錄的文本文檔,也可以叫“腳本”。他通過讀取Dockerfile中的指令安裝步驟自動生成鏡像。

補充:文件名稱必須是:Dockerfile

三、Dockerfile命令

1、構建鏡像命令

docker build -t 機構/鏡像名稱<:tags> Dockerfile目錄
# 比如如下,最後一個.代表當前目錄,因爲我的Dockerfile文件就在這,也可以用絕對路徑
docker build -t chentongwei.com/mywebapp:1.0.0 .
# 然後執行docker images 進行查看會發現有我們剛纔構建的鏡像
docker images

在這裏插入圖片描述

2、基礎命令

2.1、FROM

# 製作基準鏡像
FROM 鏡像
# 比如我們要發佈一個應用到tomcat裏,那麼的第一步就是FROM tomcat
FROM tomcat<:tags>

先有個印象,下面會實戰操作。

2.2、LABEL&MAINTAINER

# MAINTAINER,一般寫個人id或組織id
# LABEL 就是註釋,方便閱讀的,純註釋說明。不會對Dockerfile造成任何影響
# 比如:
MAINTAINER baidu.com
LABEL version = "1.0.0"
LABEL description = "我們是大百度!"
# ...等等描述性信息,純註釋。

2.3、WORKDIR

# 類似於Linux中的cd命令,但是他比cd高級的地方在於,我先cd,發現沒有這個目錄,我就自動創建出來,然後在cd進去
WORKDIR /usr/local/testdir

這個路徑建議使用絕對路徑。

2.4、ADD&COPY

2.4.1、COPY

# 將1.txt拷貝到根目錄下。它不僅僅能拷貝單個文件,還支持Go語言風格的通配符,比如如下:
COPY 1.txt /
# 拷貝所有 abc 開頭的文件到testdir目錄下
COPY abc* /testdir/
# ? 是單個字符的佔位符,比如匹配文件 abc1.log
COPY abc?.log /testdir/

2.4.2、ADD

# 將1.txt拷貝到根目錄的abc目錄下。若/abc不存在,則會自動創建
ADD 1.txt /abc
# 將test.tar.gz解壓縮然後將解壓縮的內容拷貝到/home/work/test
ADD test.tar.gz /home/work/test

docker官方建議當要從遠程複製文件時,儘量用curl/wget命令來代替ADD。因爲用ADD的時候會創建更多的鏡像層。鏡像層的size也大。

2.4.3、對比

  • 二者都是隻複製目錄中的文件,而不包含目錄本身。
  • COPY能幹的事ADD都能幹,甚至還有附加功能。
  • ADD可以支持拷貝的時候順帶解壓縮文件,以及添加遠程文件(不在本宿主機上的文件)。
  • 只是文件拷貝的話可以用COPY,有額外操作可以用ADD代替。

2.5、ENV

# 設置環境常量,方便下文引用,比如:
ENV JAVA_HOME /usr/local/jdk1.8
# 引用上面的常量,下面的RUN指令可以先不管啥意思,目的是想說明下文可以通過${xxx}的方式引用
RUN ${JAVA_HOME}/bin/java -jar xxx.jar

ENV設置的常量,其他地方都可以用${xxx}來引用,將來改的時候只改ENV的變量內容就行。

3、運行指令

一共有三個:RUN&CMD&ENTRYPOINT

1、RUN

1.1、執行時機

RUN指令是在構建鏡像時運行,在構建時能修改鏡像內部的文件。

1.2、命令格式

命令格式不光是RUN獨有,而是下面的CMD和ENTRYPOINT都通用。

  • SHELL命令格式

比如

RUN yum -y install vim
  • EXEC命令格式

比如

RUN ["yum","-y","install","vim"]
  • 二者對比

SHELL:當前shell是父進程,生成一個子shell進程去執行腳本,腳本執行完後退出子shell進程,回到當前父shell進程。

EXEC:用EXEC進程替換當前進程,並且保持PID不變,執行完畢後直接退出,不會退回原來的進程。

總結:也就是說shell會創建子進程執行,EXEC不會創建子進程。

  • 推薦EXEC命令格式

1.3、舉例

舉個最簡單的例子,構建鏡像時輸出一句話,那麼在Dockerfile裏寫如下即可:

RUN ["echo", "image is building!!!"]

再比如我們要下載vim,那麼在Dockerfile裏寫如下即可:

RUN ["yum","-y","install","vim"]

莫慌,下面會有實戰來完完整整的演示。

2、CMD

2.1、執行時機

容器啓動時執行,而不是鏡像構建時執行。

2.2、解釋說明

在容器啓動的時候執行此命令,且Dockerfile中只有最後一個ENTRYPOINT會被執行,推薦用EXEC格式。重點在於如果容器啓動的時候有其他額外的附加指令,則CMD指令不生效。

2.3、舉例

CMD ["echo", "container starting..."]

3、ENTRYPOINT

3.1、執行時機

容器創建時執行,而不是鏡像構建時執行。

3.2、解釋說明

在容器啓動的時候執行此命令,且Dockerfile中只有最後一個ENTRYPOINT會被執行,推薦用EXEC格式。

3.3、舉例

ENTRYPOINT ["ps","-ef"]

4、代碼演示

4.1、執行時機演示

FROM centos
RUN ["echo", "image building!!!"]
CMD ["echo", "container starting..."]
docker build -t chentongwei.com/test-docker-run .

構建鏡像的過程中發現我們RUN的image building!!! 輸出了,所以RUN命令是在鏡像構建時執行。而並沒有container starting…的輸出。

在這裏插入圖片描述

docker run chentongwei.com/test-docker-run

結果:container starting...,足以發現CMD命令是在容器啓動的時候執行。

4.2、CMD和ENTRYPOINT演示

ENTRYPOINT和CMD可以共用,若共用則他會一起合併執行。如下Demo:

FROM centos
RUN ["echo", "image building!!!"]
ENTRYPOINT ["ps"]
CMD ["-ef"]
# 構建鏡像
docker build -t chentongwei.com/docker-run .
# 啓動容器
docker run chentongwei.com/docker-run

輸出結果:

UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 13:02 ?        00:00:00 ps -ef

他給我們合併執行了:ps -ef,這麼做的好處在於如果容器啓動的時候添加額外指令,CMD會失效,可以理解成我們可以動態的改變CMD內容而不需要重新構建鏡像等操作。比如

docker run chentongwei.com/docker-run -aux

輸出結果:

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  2.0  0.0  46340  1692 ?        Rs   13:02   0:00 ps -aux

結果直接變成了 ps -aux,CMD命令不執行了。但是ENTRYPOINT一定執行,這也是CMD和ENTRYPOINT的區別之一。

四、實戰

1、部署應用到tomcat

1.1、準備工作

# 在服務器上創建test-dockerfile文件夾
mkdir test-dockerfile
# 進入test-dockerfile目錄
cd test-dockerfile
# 創建需要部署到tomcat的應用
mkdir helloworld
# 在helloworld目錄下創建index.html寫上hello dockerfile
cd helloworld/
vim index.html

效果如下圖:

在這裏插入圖片描述
在這裏插入圖片描述

1.2、Dockerfile

# 在test-dockerfile目錄下創建Dockerfile文件,注意大寫D,沒有後綴。
touch Dockerfile

在Dockerfile裏寫上如下內容

FROM tomcat:latest
MAINTAINER baidu.com
WORKDIR /usr/local/tomcat/webapps
ADD helloworld ./helloworld

逐行解釋:

  • 第一行:因爲我們要部署應用到tomcat上,所以需要從遠程倉庫里拉取tomcat作爲基礎鏡像。
  • 第二行:描述性東西,還可以LABEL XXX XXX 添加更詳細的註釋信息。
  • 第三行:cd到/usr/local/tomcat/webapps,發現沒有這個目錄,我就自動創建出來,然後在cd進去

爲什麼是這個目錄呢?因爲當我們製作完鏡像把容器run起來的時候tomcat的位置是在/usr/local/tomcat,加個/webapps是因爲我們要將我們的應用程序扔到webapps下才能跑。如果懵,繼續往下看就懂了。

  • 第四行:tomcat有了,tomcat的webapps我們也cd進去了,那還等啥?直接把我們的應用程序拷貝到webapps下就歐了。所以ADD命令宿主機上的helloworld文件夾下的內容拷貝到當前目錄(webapps,上一步剛cd進來的)的helloworld文件夾下。

1.3、製作鏡像

docker build -t baidu.com/test-helloworld:1.0.0 .

. 代表當前目錄。這些命令不懂的看上面的【三、Dockerfile命令】,都是上面提到的。沒新知識。

命令執行後的結果

[root@izm5e3qug7oee4q1y4opibz test-dockerfile]# docker build -t baidu.com/test-helloworld:1.0.0 .
Sending build context to Docker daemon  3.584kB
Step 1/4 : FROM tomcat:latest
 ---> 1b6b1fe7261e
Step 2/4 : MAINTAINER baidu.com
 ---> Running in ac58299b3f38
Removing intermediate container ac58299b3f38
 ---> 5d0da6398f7e
Step 3/4 : WORKDIR /usr/local/tomcat/webapps
 ---> Running in 1c21c39fc58e
Removing intermediate container 1c21c39fc58e
 ---> 9bf9672cd60e
Step 4/4 : ADD helloworld ./helloworld
 ---> 6d67c0d48c20
Successfully built 6d67c0d48c20
Successfully tagged baidu.com/test-helloworld:1.0.0

好像分了1/2/3/4步呢?這是啥意思。這是鏡像分層的概念,下面說。現在只看到SuccessFully就哦了。

再查看下我們的鏡像真實存在了嗎?

docker images

完美

在這裏插入圖片描述

1.4、啓動容器

docker run -d -p 8100:8080  baidu.com/test-helloworld:1.0.0
# 然後docker ps查看容器是否存在
docker ps

瀏覽器訪問:http://服務器ip:8100/helloworld/index.html,很完美。 這個helloworld就是我們Dockerfile裏自己的應用程序。

在這裏插入圖片描述

1.5、進入容器

docker exec -it 730f9e144f68 /bin/bash

在這裏插入圖片描述

疑問1:怎麼進入容器後直接在webapps目錄下,這就是因爲我們這個鏡像是用Dockerfile製作的,Dockerfile上面我們自己WORKDIR到webapps目錄下的呀。

答疑1:我們ls下可以看到Dockerfile裏的helloworld應用就在這裏

root@730f9e144f68:/usr/local/tomcat/webapps# ls
helloworld

答疑2:Dockerfile裏WORKDIR /usr/local/tomcat/webapps了,爲啥是這個目錄也很清晰了。容器裏的tomcat就在這

root@730f9e144f68:/usr/local/tomcat/webapps# pwd
/usr/local/tomcat/webapps

2、從0製作Redis鏡像

一般沒人制作Redis鏡像,Redis有官方的docker鏡像,docker pull一下就行。這裏是爲了演示上面的命令,從0到1的過程。

2.1、準備工作

1.去官網下載Redis的源碼包,因爲我們演示的是Redis從無到有的過程。

2.準備Redis的配置文件redis-6379.conf

2.2、Dockerfile

# 將Redis運行在centos上
FROM centos
# 安裝Redis所需要的基礎庫
RUN ["yum", "install", "-y", "gcc", "gcc-c++", "net-tools", "make"]
# 將Redis目錄放到/usr/local
WORKDIR /usr/local
# 別忘了ADD命令自帶解壓縮的功能
ADD redis-4.0.14.tag.gz .
WORKDIR /usr/local/redis-4.0.14/src
# 編譯安裝Redis
RUN make && make install
WORKDIR /usr/local/redis-4.0.14
# 將配置文件仍到Redis根目錄
ADD redis-6379.conf .
# 聲明容器中Redis的端口爲6379
EXPOSE 6379
# 啓動Redis  redis-server redis-6379.conf
CMD ["redis-server", "redis-6379.conf"]

2.3、製作鏡像&&啓動容器

# 製作鏡像
docker build -t chentongwei.com/docker-redis .
# 查看
docker images
# 啓動容器
docker run -p 6379:6379 chentongwei.com/docker-redis

上面三套小連招執行完後redis就起來了,可以redis-cli去鏈接了。也可以docker exec進入容器去查看。

3、用docker部署jar包

FROM openjdk:8-jdk-alpine:latest
ADD target/helloworld-0.0.1-SNAPSHOT.jar /helloworld.jar
ENTRYPOINT ["java","-jar","/helloworld.jar"]

然後build成鏡像再run啓動容器,很簡單粗暴。

五、補充:鏡像分層的概念

1、Dockerfile

FROM tomcat:latest
MAINTAINER baidu.com
WORKDIR /usr/local/tomcat/webapps
ADD helloworld ./helloworld

2、鏡像分層

就拿上面的Dockerfile來build的話,執行過程是如下的:

Sending build context to Docker daemon  3.584kB
Step 1/4 : FROM tomcat:latest
 ---> 1b6b1fe7261e
Step 2/4 : MAINTAINER baidu.com
 ---> Running in ac58299b3f38
Removing intermediate container ac58299b3f38
 ---> 5d0da6398f7e
Step 3/4 : WORKDIR /usr/local/tomcat/webapps
 ---> Running in 1c21c39fc58e
Removing intermediate container 1c21c39fc58e
 ---> 9bf9672cd60e
Step 4/4 : ADD helloworld ./helloworld
 ---> 6d67c0d48c20
Successfully built 6d67c0d48c20
Successfully tagged baidu.com/test-helloworld:1.0.0

會發現我們Dockerfile文件內容一共四行,執行過程也是Step 1/2/3/4四步,這就知道了Dockerfile內容的行數決定了Step的步驟數。

那麼每一步都代表啥呢?

其實每一步都會爲我們創建一個臨時容器,這樣做的好處是如果下次再構建這個Dockerfile的時候,直接從cache裏讀出已有的容器,不重複創建容器,這樣大大節省了構建時間,也不會浪費資源重複創建容器。比如如下:

FROM tomcat:latest
MAINTAINER baidu.com
WORKDIR /usr/local/tomcat/webapps
ADD helloworld ./helloworld
ADD helloworld ./helloworld2

啥也沒動,就是多部署一份helloworld且在容器內部改名爲helloworld2,接下來看執行過程

Step 1/5 : FROM tomcat:latest
 ---> 1b6b1fe7261e
Step 2/5 : MAINTAINER baidu.com
 ---> Using cache
 ---> 5d0da6398f7e
Step 3/5 : WORKDIR /usr/local/tomcat/webapps
 ---> Using cache
 ---> 9bf9672cd60e
Step 4/5 : ADD helloworld ./helloworld
 ---> Using cache
 ---> 6d67c0d48c20
Step 5/5 : ADD helloworld ./helloworld2
 ---> 4e5ffc24522f
Successfully built 4e5ffc24522f
Successfully tagged baidu.com/test-helloworld:1.0.1

首先可以發現如下:

1.Step變成了5步。

2.前四步驟用了緩存Using Cache,並沒有重複創建容器。Step 1 沒有Using Cache是因爲它是從本地倉庫直接拉取了tomcat:latest當作基礎鏡像,run的時候會創建容器。

3.第五步重新創建了臨時容器。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章