一、前言
本篇講的是如何創建 Dockerfile 來構建一個鏡像。上篇文章有講到構建一個自定義鏡像是手動去構建的,雖然步驟清晰,但是操作比較繁瑣,鏡像分發起來也不是很方便,所以有必要用一種更好的辦法去替換這種模式去創建自定義鏡像,於是 Dockerfile 就是最優替代方案。
二、Dockerfile 示例
# Base images 基礎鏡像 FROM centos #MAINTAINER 維護者信息 MAINTAINER lorenwe #ENV 設置環境變量 ENV PATH /usr/local/nginx/sbin:$PATH #ADD 文件放在當前目錄下,拷過去會自動解壓 ADD nginx-1.13.7.tar.gz /tmp/ #RUN 執行以下命令 RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 \ && yum update -y \ && yum install -y vim less wget curl gcc automake autoconf libtool make gcc-c++ zlib zlib-devel openssl openssl-devel perl perl-devel pcre pcre-devel libxslt libxslt-devel \ && yum clean all \ && rm -rf /usr/local/src/* RUN useradd -s /sbin/nologin -M www #WORKDIR 相當於cd WORKDIR /tmp/nginx-1.13.7 RUN ./configure --prefix=/usr/local/nginx --user=www --group=www --with-http_ssl_module --with-pcre && make && make install RUN cd / && rm -rf /tmp/ COPY nginx.conf /usr/local/nginx/conf/ #EXPOSE 映射端口 EXPOSE 80 443 #ENTRYPOINT 運行以下命令 ENTRYPOINT ["nginx"] #CMD 運行以下命令 CMD ["-h"]復制代碼
以上代碼示例是我編寫的一個認為很有代表性的 dockerfile 文件,涉及到的內容不多,但基本上把所有 dockerfile 指令都用上了,也包含一些細節方面的東西,為了達到示例的效果所以並不是最簡潔的 dockerfile,建立一個文件夾將以上 dockerfile 放在該文件內,再去 nginx 官網把 nginx 源碼包下來放到該文件夾內,之后再在該文件夾內打開命令行窗口,最好是以管理員權限打開命令行窗口,以免出現一些權限問題的錯誤,此時的目錄結構應該是以下樣子的

三、指令分析
FROM 表示的是這個 dockerfile 構建鏡像的基礎鏡像是什么,有點像代碼里面類的繼承那樣的關系,基礎鏡像所擁有的功能在新構建出來的鏡像中也是存在的,一般用作於基礎鏡像都是最干凈的沒有經過任何三方修改過的,比如我用的就是最基礎的 centos,這里有必要說明一下,因為我用的鏡像加速源是阿里雲的,所以我 pull 下來的 centos 是自帶阿里雲的 yum 源的鏡像,如果你用的不是阿里雲的鏡像加速源,pull 下來的鏡像 yum 源也不一樣,到時 yum 安裝軟件的時候可能會遇到很多問題(你懂得)。
MAINTAINER 就是維護者信息了,填自己名字就可了,不用說什么了
ENV 設置環境變量,簡單點說就是設置這個能夠幫助系統找到所需要運行的軟件,比如我上面寫的是 “ENV PATH /usr/local/nginx/sbin:$PATH”,這句話的意思就是告訴系統如果運行一個沒有指定路徑的程序時可以從 /usr/local/nginx/sbin 這個路徑里面找,只有設置了這個,后面才可以直接使用 ngixn 命令來啟動 nginx,不然的話系統會提示找不到應用。
ADD 顧名思義,就是添加文件的功能了,但是他比普通的添加做的事情多一點,源文件可以是一個文件,或者是一個 URL 都行,如果源文件是一個壓縮包,在構建鏡像的時候會自動的把壓縮包解壓開來,示例我寫的是 ‘ADD nginx-1.13.7.tar.gz /tmp/’ 其中 nginx-1.13.7.tar.gz 這個壓縮包是必須要在 dockefile 文件目錄內的,不在 dockerfile 文件目錄內的 比如你寫完整路徑 D:test/nginx-1.13.7.tar.gz 都是會提示找不到文件的。
RUN 就是執行命令的意思了,RUN 可以執行多條命令, 用 && 隔開就行,如果命令太長要換行的話在末尾加上 ‘\’ 就可以換行命令,RUN 的含義非常簡單,就是執行命令,但其中有一些細節還是需要注意的,現在就通過上面的示例來看看需要注意的地方有哪些吧。其中 RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 的作用就是導入軟件包簽名來驗證軟件包是否被修改過了,為做到安全除了系統要官方的之外軟件也要保證是可信的。yum update -y 升級所有包,改變軟件設置和系統設置,系統版本內核都升級,我們知道 linux 的軟件存在依賴關系,有時我們安裝新的軟件他所依賴的工具軟件也需要是最新的,如果沒有用這個命令去更新原來的軟件包,就很容易造成我們新安裝上的軟件出問題,報錯提示不明顯的情況下我們更是難找到問題了,為避免此類情況發生我們還是先更新一下軟件包和系統,雖然這會使 docker 構建鏡像時變慢但也是值得的,至於后面的命令自然是安裝各種工具庫了,接着來看這句 yum clean all ,把所有的 yum 緩存清掉,這可以減少構建出來的鏡像大小,rm -rf /usr/local/src/ 清除用戶源碼文件,都是起到減少構建鏡像大小的作用。RUN 指令是可以分步寫的,比如上面的 RUN 可以拆成以下這樣:
# 不推薦 RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 \ RUN yum update -y \ RUN yum install -y vim less wget curl gcc automake autoconf libtool make gcc-c++ zlib zlib-devel openssl openssl-devel perl perl-devel pcre pcre-devel libxslt libxslt-devel \ RUN yum clean all \ RUN rm -rf /usr/local/src/*復制代碼
這樣也是可以的,但是最好不要這樣,因為 dockerfile 構建鏡像時每執行一個關鍵指令都會去創建一個鏡像版本,這有點像 git 的版本管理,比如執行完第一個 RUN 命令后在執行第二個 RUN 命令時是會在一個新的鏡像版本中執行,這會導致 yum clean all 這個命令失效,沒有起到精簡鏡像的作用,雖然不推薦多寫幾個 RUN,但也不是說把所有的操作都放在一個 RUN 里面,這里有個原則就是把所有相關的操作都放在同一個 RUN 里面,就比如我把 yum 更新,安裝工具庫,清除緩存放在一個 RUN 里面,后面的編譯安裝 nginx 放在另外一個 RUN 里面。
WORKDIR 表示鏡像活動目錄變換到指定目錄,就相當於 linux 里面 cd 到指定目錄一樣,其實完全沒有必要使用這個指令的,在需要時可以直接使用 cd 命令就行,因為這里使用了 WORKDIR,所以后面的 RUN 編譯安裝 nginx 不用切換目錄,講到這里又想起了另外一個問題,如下:
RUN cd /tmp/nginx-1.13.7 RUN ./configure復制代碼
這樣可不可以呢,我想前面看懂的朋友應該知道答案了吧,這里還是再啰嗦一下,這樣是會報找不到 configure 文件錯誤的,原因很簡單,因為這個兩個命令都不是在同一個鏡像中執行的,第一個鏡像 cd 進入的目錄並不代表后面的鏡像也進入了。
COPY 這個指令很簡單,就是把文件拷貝到鏡像中的某個目錄,注意源文件也是需要在 dockerfile 所在目錄的,示例的意思是拷貝一份 nginx 配置文件,現在就在 dockerfile 所在目錄創建這個文件
user www;
worker_processes 2;
daemon off;
pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}復制代碼
配置很簡單,就是對官方的配置文件把注釋去掉了,注意里面的 daemon off; 配置,意思是關閉 nginx 后台運行,原因在上一篇文章中講過,這里再來絮叨一下,容器默認會把容器內部第一個進程是否活動作為docker容器是否正在運行的依據,如果 docker 容器運行完就退出了,那么docker容器便會直接退出,docker run 的時候把 command 作為容器內部命令,如果使用 nginx,那么 nginx 程序將后台運行,這個時候 nginx 並不是第一個執行的程序,而是執行的 bash,這個 bash 執行了 nginx 指令后就掛了,所以容器也就退出了,如果我們設置了 daemon off 后
啟動 nginx 那么 nginx 就會一直占用命令窗口,自然 bash 沒法退出了所以容器一直保持活動狀態。
EXPOSE 示例注釋寫的是映射端口,但我覺得用暴露端口來形容更合適,因為在使用 dockerfile 創建容器的時候不會映射任何端口,映射端口是在用 docker run 的時候來指定映射的端口,比如我把容器的 80 端口映射到本機的 8080 端口,要映射成功就要先把端口暴露出來,有點類似於防火牆的功能,把部分端口打開。
ENTRYPOINT 和 CMD 要放在一起來說,這兩者的功能都類似,但又有相對獨特的地方,他們的作用都是讓鏡像在創建容器時運行里面的命令。當然前提是這個鏡像是使用這個 dockerfile 構建的,也就是說在執行 docker run 時 ENTRYPOINT 和 CMD 里面的命令是會執行的,兩者是可以單獨使用,並不一定要同時存在,當然這兩者還是有區別的。
先從 CMD 說吧,CMD 的一個特點就是可被覆蓋,比如把之前的 dockerfile 的 ENTRYPOINT 這一行刪除,留下 CMD 填寫["nginx"],構建好鏡像后直接使用 docker run lorenwe/centos_nginx 命令執行的話通過 docker ps 可以看到容器正常運行了,啟動命令也是 “ngixn”,但是我們使用 docker run lorenwe/centos_nginx bin/bash 來啟動的話通過 docker ps 查看到啟動命令變成了 bin/bash,這就說明了 dockerfile 的 CMD 指令是可被覆蓋的,也可以把他看做是容器啟動的一個默認命令,可以手動修改的。
而 ENTRYPOINT 恰恰相反,他是不能被覆蓋,也就是說指定了值后再啟動容器時不管你后面寫的什么 ENTRYPOINT 里面的命令一定會執行,通常 ENTRYPOINT 用法是為某個鏡像指定必須運行的應用,例如我這里構建的是一個 centos_nginx 鏡像,也就是說這個鏡像只運行 ngixn,那么我就可以在 ENTRYPOINT 寫上["nginx"],有些人在構建自己的基礎鏡像時(基礎鏡像只安裝了一些必要的庫)就只有 CMD 並寫上 ['bin/bash'],當 ENTRYPOINT 和 CMD 都存在時 CMD 中的命令會以 ENTRYPOINT 中命令的參數形式來啟動容器,例如上面的示例 dockerfile,在啟動容器時會以命令為 nginx -h 來啟動容器,遺憾的是這樣不能保持容器運行,所以可以這樣啟動 docker run -it lorenwe/centos_nginx -c /usr/local/nginx/conf/nginx.conf,那么容器啟動時運行的命令就是 nginx -c /usr/local/nginx/conf/nginx.conf,是不是很有意思,可以自定義啟動參數了。
當然還有一些沒有用到的指令:
ARG,ARG指令用以定義構建時需要的參數,比如可以在 dockerfile中寫上這句 ARG a_nother_name=a_default_value,ARG指令定義的參數,在docker build命令中以 --build -arg a_name=a_value 形式賦值,這個用的一般比較少。
VOLUME,VOLUME指令創建一個可以從本地主機或其他容器掛載的掛載點,用法是比較多的,都知道 docker 做應用容器比較方便,其實 docker 也可做數據容器,創建數據容器鏡像的 dockerfile 就主要是用 VOLUME 指令,要講明 VOLUME 用法有必要在開一篇文章,再此就不做介紹了,
USER,USER用來切換運行屬主身份的。docker 默認是使用 root 用戶,但若不需要,建議切換使用者身分,畢竟 root 權限太大了,使用上有安全的風險。LABEL,定義一個 image 標簽。
四、構建演示
dockerfile 構建鏡像的命令很簡單,在我的示例中我的命令是 "docker build -t lorenwe/centos_nginx . ",注意后面的點不能省略,表示的從當前目錄中尋找 dockerfile 來構建鏡像
D:\docker\lorenwe>docker build -t lorenwe/centos_nginx .
Sending build context to Docker daemon 995.8kB
Step 1/13 : FROM centos
---> d123f4e55e12 Step 2/13 : MAINTAINER lorenwe ---> Running in e5c7274f50e8 ---> 606f7222e69a Removing intermediate container e5c7274f50e8 Step 3/13 : ENV PATH /usr/local/nginx/sbin:$PATH ---> Running in 23716b428809 ---> 5d8ee1b5a899 .... Successfully built eaee6b40b151 Successfully tagged lorenwe/centos_nginx:latest復制代碼
看到以上內容就說明成功,構建過程可能需要一點點時間,畢竟要安裝一些軟件,如果你跟我一樣是配置的阿里雲的容器源構建時應該不會出現什么問題,因為我之前是有拉取過 centos ,所以在 build 時直接使用本地的 centos,如果你沒有拉取過 centos,那么在 build 時還會把 centos 拉取下來
D:\docker\lorenwe>docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
lorenwe/centos_nginx latest eaee6b40b151 7 minutes ago 427MB
lorenwe/centos_net_tools latest 35f8073cede1 6 days ago 277MB
centos latest d123f4e55e12 3 weeks ago 197MB
d4w/nsenter latest 9e4f13a0901e 14 months ago 83.8kB
D:\docker\lorenwe>docker run -itd --name nginx1 lorenwe/centos_nginx
15d4f108dab7c2f276209ebeb501cac0d3be828e1e81bae22d3fd97c617439eb
D:\docker\lorenwe>docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
D:\docker\lorenwe>docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
15d4f108dab7 lorenwe/centos_nginx "nginx -h" nginx1
D:\docker\lorenwe>docker run -itd --name nginx2 lorenwe/centos_nginx -c /usr/local/nginx/conf/nginx.conf
b6b0e962ca3056d67c24145b08975ffddb9cc050fce5f09f65310fb323ffc1c3
D:\docker\lorenwe>docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b6b0e962ca30 lorenwe/centos_nginx "nginx -c /usr/loc..." 80/tcp nginx2
D:\docker\lorenwe>docker run -itd -p 8080:80 --name nginx3 lorenwe/centos_nginx -c /usr/local/nginx/conf/nginx.conf
2f6997745641e3e3edbbfe5213e6235cab3b5a929f116a2c132df504156090c6
D:\docker\lorenwe>docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2f6997745641 lorenwe/centos_nginx "nginx -c /usr/loc..." 0.0.0.0:8080->80/tcp nginx3
b6b0e962ca30 lorenwe/centos_nginx "nginx -c /usr/loc..." 80/tcp nginx2
D:\docker\lorenwe>docker stop nginx2
nginx2復制代碼
其中 “docker run -itd -p 8080:80 --name nginx3 lorenwe/centos_nginx -c /usr/local/nginx/conf/nginx.conf” 中的 -p 8080:80 表示把主機的 8080 端口映射到容器的 80 端口,因為之前我們在 dockerfile 中把 80 端口暴露出來了,做好端口映射后現在就可以在主機中打開瀏覽器訪問 127.0.0.1:8080 就能看到 nginx 的歡迎頁面了 (^v^).
D:\docker\lorenwe>docker run -itd -v D:/docker/lorenwe/html:/usr/local/nginx/html -p 8081:80 --name nginx4 lorenwe/centos_nginx -c /usr/local/nginx/conf/nginx.conf
cd2d4eb70a39057aed3bfcb64e1f03433e2054d7ff5d50098f49d2e6f2d9e02e復制代碼
我再在原來的參數中加入了 -v 參數,其作用就是把一個本地主機的目錄掛載到容器內部,這個目錄是一個共享的狀態,兩邊都可以進行修改,這就是容器的共享卷,其作用就不言而喻了,現在我們在 D:\docker\lorenwe 的目錄下新建一個叫 html 的文件夾,再在 html 文件夾內新建一個 index.html 隨便寫上一點內容后再去主機瀏覽器上訪問一下 127.0.0.1:8081 看看是不是你想要看到內容。