一、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 源碼包下來放到該文件夾內,之后再在該文件夾內打開命令行窗口,最好是以管理員權限打開命令行窗口,以免出現一些權限問題的錯誤,此時的目錄結構應該是以下樣子的
1、FROM <image>
表示的是這個 dockerfile 構建鏡像的基礎鏡像是什么,有點像代碼里面類的繼承那樣的關系
基礎鏡像所擁有的功能在新構建出來的鏡像中也是存在的,一般用作於基礎鏡像都是最干凈的沒有經過任何三方修改過的,比如我用的就是最基礎的 centos,這里有必要說明一下,因為我用的鏡像加速源是阿里雲的,所以我 pull 下來的 centos 是自帶阿里雲的 yum 源的鏡像,如果你用的不是阿里雲的鏡像加速源,pull 下來的鏡像 yum 源也不一樣,到時 yum 安裝軟件的時候可能會遇到很多問題。
-
FROM指定構建鏡像的基礎鏡像,如果本地沒有,則會自動從Docker的公共庫pull鏡像下來
-
FROM必須放在Dockerfile的第一行
-
FROM可在Dockerfile中出現多次
-
FROM如果沒有指定鏡像的標簽(tag),則默認會用latest標簽
2、MAINTAINER <name> #指定創建鏡像的用戶
3、ENV 設置環境變量:指定一個環境變量,可被后續RUN命令使用,並在容器運行時保留
簡單點說就是設置這個能夠幫助系統找到所需要運行的軟件,比如我上面寫的是 “ENV PATH /usr/local/nginx/sbin:$PATH”,這句話的意思就是告訴系統如果運行一個沒有指定路徑的程序時可以從 /usr/local/nginx/sbin 這個路徑里面找,只有設置了這個,后面才可以直接使用 ngixn 命令來啟動 nginx,不然的話系統會提示找不到應用。
ENV <key> <value> #只允許設置一個變量 ENV <key>=<value> #可以設置多個變量
4、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 都是會提示找不到文件的。
ADD <src>... <dest>
// ADD復制本地主機文件、目錄或者遠程文件URLS 到容器指定路徑中,支持正則表達式
5、RUN
RUN
RUN []
每條RUN命令將在當前鏡像基礎上執行指定命令,並提交為新的鏡像。
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 里面。
6、WORKDIR 表示鏡像活動目錄變換到指定目錄,
WORKDIR /path/to/workdir // 為RUN、CMD、ENTRYPOINT指令配置工作目錄 // 就是指定shell命令運行在哪個目錄下
就相當於 linux 里面 cd 到指定目錄一樣,其實完全沒有必要使用這個指令的,在需要時可以直接使用 cd 命令就行,因為這里使用了 WORKDIR,所以后面的 RUN 編譯安裝 nginx 不用切換目錄,講到這里又想起了另外一個問題,如下:
RUN cd /tmp/nginx-1.13.7 RUN ./configure
這樣可不可以呢,我想前面看懂的朋友應該知道答案了吧,這里還是再啰嗦一下,這樣是會報找不到 configure 文件錯誤的,原因很簡單,因為這個兩個命令都不是在同一個鏡像中執行的,第一個鏡像 cd 進入的目錄並不代表后面的鏡像也進入了。
所以這就是使用 WORKDIR 和 cd 的區別。
7、COPY 這個指令很簡單,就是把文件拷貝到鏡像中的某個目錄,
COPY <src>... <dest>
// 同ADD,但是COPY不能指定遠程文件URLS
注意源文件也是需要在 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 沒法退出了所以容器一直保持活動狀態
8、EXPOSE
EXPOSE <port> #指定映射端口
示例注釋寫的是映射端口,但我覺得用暴露端口來形容更合適,因為在使用 dockerfile 創建容器的時候不會映射任何端口,映射端口是在用 docker run 的時候來指定映射的端口,比如我把容器的 80 端口映射到本機的 8080 端口,要映射成功就要先把端口暴露出來,有點類似於防火牆的功能,把部分端口打開。
9、CMD 與 ENTRYPOINT
ENTRYPOINT 和 CMD 要放在一起來說,這兩者的功能都類似,但又有相對獨特的地方,他們的作用都是讓鏡像在創建容器時運行里面的命令。當然前提是這個鏡像是使用這個 dockerfile 構建的,也就是說在執行 docker run 時 ENTRYPOINT 和 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,是不是很有意思,可以自定義啟動參數了。
總結:
-
CMD目的是為了啟動容器時,提供一個默認的命令執行選項
-
如果用戶啟動容器時,指定了運行的命令,則會覆蓋掉CMD指定的命令
-
RUN是構建容器時執行,CMD是容器運行時才會執行
- ENTRYPOINT 同 CMD,但是不能被docker run提供的參數覆蓋
10、ARG:指定一個環境變量,可被后續RUN命令使用,並在容器運行時保留
ENV <key> <value> #只允許設置一個變量 ENV <key>=<value> #可以設置多個變量
ARG指令用以定義構建時需要的參數,比如可以在 dockerfile 中寫上這句 ARG a_nother_name=a_default_value,ARG指令定義的參數,在docker build命令中以 --build -arg a_name=a_value 形式賦值,這個用的一般比較少。
11、VOLUME ['data']:創建一個可以從本地主機或其他容器掛載的掛載點
VOLUME指令創建一個可以從本地主機或其他容器掛載的掛載點,用法是比較多的,都知道 docker 做應用容器比較方便,其實 docker 也可做數據容器,創建數據容器鏡像的 dockerfile 就主要是用 VOLUME 指令,要講明 VOLUME 用法后續有需要再研究
12、USE daemom:指定運行容器時的用戶名或UID
USER用來切換運行屬主身份的。docker 默認是使用 root 用戶,但若不需要,建議切換使用者身分,畢竟 root 權限太大了,使用上有安全的風險
二、DockerFile常用命令
FROM python:3.8-buster //拉取鏡像 FROM 鏡像名:鏡像版本
LABLE k='v' k1='v1' //標志作用,沒其他作用
WORKDIR /app //指定shell執行的目錄
SHELL /bin/bash //指定shell的解析器
COPY requirements.txt requirements.txt //將宿主機文件copy到鏡像中
ADD goose.py /goose.py //和like一樣,但是ADD可以copy url
RUN pip instsll -r requirements.txt //運行shell命令
EXPOSE 8070 //指定鏡像暴露的端口號
ENV A=10 //定義環境變量
ARG B=12 //定義局部變量(只有在構建的時候有用,運行啟動時無效)
ONBUILD ENV C=10 //當前容器構建和運行時無效,基於該鏡像的容器構建時啟動時有效
VOLUME /a/b //將鏡像中文件映射到宿主機
CMD ["python", "goose.py"] //指定代碼運行的命令(參數可被docker run傳入的參數覆蓋)
ENTRYPOINT ["python", "goose.py"] //指定代碼運行的命令(參數不會被覆蓋)
//構建鏡像:
docker build . -t 鏡像名:版本號 //查看鏡像:
docker images //生成容器:
docker run -t -d -p 8080:80 [IMAGE ID] //查看容器:
docker ps //查看正在運行的容器
docker ps -a //查看所有容器 //進入容器內:
docker exec -it container_id /bin/bash //退出容器:
exit //停止容器:
docker stop container_id //刪除容器:
docker rm container_id //刪除鏡像:
docker rmi [-f] 鏡像地址