Dockerfile基礎命令及簡單應用
Dockerfile
從?docker commit
?的學習中,我們可以了解到,鏡像的定制實際上就是定制每一層所添加的配置、文件。如果我們可以把每一層修改、安裝、構建、操作的命令都寫入一個腳本,用這個腳本來構建、定制鏡像,那么之前提及的無法重復的問題、鏡像構建透明性的問題、體積的問題就都會解決。這個腳本就是 Dockerfile。
Dockerfile 是一個文本文件,其內包含了一條條的?指令(Instruction),每一條指令構建一層,因此每一條指令的內容,就是描述該層應當如何構建。
初識 Dockerfile
以之前定制?nginx
?鏡像為例,這次我們使用 Dockerfile 來定制。
在一個空白目錄中,建立一個文本文件,并命名為?Dockerfile
:
mkdir docker-test-volume
cd docker-test-volume
vim Dockerfile
Dockerfile 的內容如下:
FROM nginx
RUN echo 'Hello world!' > /usr/share/nginx/html/index.html

然后我們利用 Dockerfile 來構建鏡像!
docker build -t nginx:v3 .

然后我們運行新建的鏡像
docker run --name my-nginx -d -p 80:80 nginx:v3

出現這個界面,說明我們的鏡像成功啟動了!

Dockerfile 命令
FROM 指定基礎鏡像
所謂定制鏡像,那一定是以一個鏡像為基礎,在其上進行定制。就像我們之前運行了一個?nginx
?鏡像的容器,再進行修改一樣,基礎鏡像是必須指定的。而?FROM
?就是指定?基礎鏡像,因此一個?Dockerfile
?中?FROM
?是必備的指令,并且必須是第一條指令。
在 Docker Hub 上有非常多的高質量的官方鏡像,有可以直接拿來使用的服務類的鏡像,如?nginx
、redis
、mongo
、mysql
、httpd
、php
、tomcat
?等;也有一些方便開發(fā)、構建、運行各種語言應用的鏡像,如?node
、openjdk
、python
、ruby
、golang
?等。可以在其中尋找一個最符合我們最終目標的鏡像為基礎鏡像進行定制。
除了選擇現有鏡像為基礎鏡像外,Docker 還存在一個特殊的鏡像,名為?scratch
。這個鏡像是虛擬的概念,并不實際存在,它表示一個空白的鏡像。
FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
FROM centos:7 # 例子
MAINTAINER 設置 author
該命令已廢棄,我們可以使用 LABEL 來設置 author
LABEL org.opencontainers.image.authors="cyr@gmail.com"
RUN 執(zhí)行命令
RUN
指令將在當前鏡像上的新層中執(zhí)行任何命令并提交結果。生成的提交鏡像將用于Dockerfile中的下一步。其格式有兩種:
shell?格式:
RUN <命令>
,就像直接在命令行中輸入的命令一樣。剛才寫的 Dockerfile 中的?RUN
?指令就是這種格式。exec?格式:
RUN ["可執(zhí)行文件", "參數1", "參數2"]
,這更像是函數調用中的格式。
需要注意的是,Dockerfile 中每一個指令都會建立一層,RUN
?也不例外。每一個?RUN
?的行為,就會新建立一層,在其上執(zhí)行這些命令,執(zhí)行結束后,commit
?這一層的修改,構成新的鏡像。
COPY 復制文件
格式:
COPY [--chown=<user>:<group>] <源路徑>... <目標路徑>
COPY [--chown=<user>:<group>] ["<源路徑1>",... "<目標路徑>"]
和?RUN
?指令一樣,也有兩種格式,一種類似于命令行,一種類似于函數調用。
COPY
?指令將從構建上下文目錄中?<源路徑>
?的文件/目錄復制到新的一層的鏡像內的?<目標路徑>
?位置。比如:
COPY package.json /usr/src/app/
<源路徑>
?可以是多個,甚至可以是通配符,如:
COPY hom* /mydir/
COPY hom?.txt /mydir/
如果源路徑為文件夾,復制的時候不是直接復制該文件夾,而是將文件夾中的內容復制到目標路徑。
ADD 更高級的復制文件
ADD
?指令和?COPY
?的格式和性質基本一致。但是在?COPY
?基礎上增加了一些功能。
如果?<源路徑>
?為一個?tar
?壓縮文件的話,壓縮格式為?gzip
,?bzip2
?以及?xz
?的情況下,ADD
?指令將會自動解壓縮這個壓縮文件到?<目標路徑>
?去。
FROM scratch
ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /
如果?<源路徑>
?是一個?URL
,Docker 引擎會試圖去下載這個鏈接的文件放到?<目標路徑>
?去。下載后的文件權限自動設置為?600
,如果這并不是想要的權限,那么還需要增加額外的一層?RUN
?進行權限調整,另外,如果下載的是個壓縮包,需要解壓縮,也一樣還需要額外的一層?RUN
?指令進行解壓縮。所以不如直接使用?RUN
?指令,然后使用?wget
?或者?curl
?工具下載,處理權限、解壓縮、然后清理無用文件更合理。因此,這個功能其實并不實用,而且不推薦使用。
CMD 容器啟動命令
CMD
?指令的格式和?RUN
?相似,也是兩種格式:
shell
?格式:CMD <命令>
exec
?格式:CMD ["可執(zhí)行文件", "參數1", "參數2"...]
參數列表格式:
CMD ["參數1", "參數2"...]
。在指定了?ENTRYPOINT
?指令后,用?CMD
?指定具體的參數。
在運行時可以指定新的命令來替代鏡像設置中的這個默認命令,比如,ubuntu
?鏡像默認的?CMD
?是?/bin/bash
,如果我們直接?docker run -it ubuntu
?的話,會直接進入?bash
。我們也可以在運行時指定運行別的命令,如?docker run -it ubuntu cat /etc/os-release
。這就是用?cat /etc/os-release
?命令替換了默認的?/bin/bash
?命令了,輸出了系統(tǒng)版本信息。
在指令格式上,一般推薦使用?exec
?格式,這類格式在解析時會被解析為 JSON 數組,因此一定要使用雙引號?"
,而不要使用單引號。
如果使用?shell
?格式的話,實際的命令會被包裝為?sh -c
?的參數的形式進行執(zhí)行。比如:
CMD echo $HOME
在實際執(zhí)行中,會將其變更為:
CMD [ "sh", "-c", "echo $HOME" ]
這就是為什么我們可以使用環(huán)境變量的原因,因為這些環(huán)境變量會被 shell 進行解析處理。
ENTRYPOINT 入口點
ENTRYPOINT
?的格式和?RUN
?指令格式一樣,分為?exec
?格式和?shell
?格式。
ENTRYPOINT
?的目的和?CMD
?一樣,都是在指定容器啟動程序及參數。ENTRYPOINT
?在運行時也可以替代,不過比?CMD
?要略顯繁瑣,需要通過?docker run
?的參數?--entrypoint
?來指定。
當指定了?ENTRYPOINT
?后,CMD
?的含義就發(fā)生了改變,不再是直接的運行其命令,而是將?CMD
?的內容作為參數傳給?ENTRYPOINT
?指令,換句話說實際執(zhí)行時,將變?yōu)椋?/p>
<ENTRYPOINT> "<CMD>"
舉個例子:
假設我們需要一個得知自己當前公網 IP 的鏡像,那么可以先用?CMD
?來實現:
FROM ubuntu:18.04
RUN apt-get update \
? ?&& apt-get install -y curl \
? ?&& rm -rf /var/lib/apt/lists/*
CMD [ "curl", "-s", "http://myip.ipip.net" ]
我們構建鏡像執(zhí)行:
docker build myip
這時如果我們要加入參數?-i
?來顯示HTTP頭信息,我們試著運行下面命令
docker build myip -i
docker: Error response from daemon: invalid header field value "oci runtime error: container_linux.go:247: starting container process caused \"exec: \\\"-i\\\": executable file not found in $PATH\"\n".
這里顯示報錯了,原因是這里的-i
替換了原來的CMD
,而不是在原來的基礎后面加入-i
,我們需要輸入命令才行
docker build myip curl -s http://myip.ipip.net -i
我們再用ENTRYPOINT
實現:
FROM ubuntu:18.04
RUN apt-get update \
? ?&& apt-get install -y curl \
? ?&& rm -rf /var/lib/apt/lists/*
ENTRYPOINT [ "curl", "-s", "http://myip.ipip.net" ]
我們構建鏡像執(zhí)行:
$ docker run myip
當前 IP:61.148.226.66 來自:北京市 聯通
$ docker run myip -i
HTTP/1.1 200 OK
Server: nginx/1.8.0
Date: Tue, 22 Nov 2016 05:12:40 GMT
Content-Type: text/html; charset=UTF-8
Vary: Accept-Encoding
X-Powered-By: PHP/5.6.24-1~dotdeb+7.1
X-Cache: MISS from cache-2
X-Cache-Lookup: MISS from cache-2:80
X-Cache: MISS from proxy-2_6
Transfer-Encoding: chunked
Via: 1.1 cache-2:80, 1.1 proxy-2_6:8006
Connection: keep-alive
當前 IP:61.148.226.66 來自:北京市 聯通
顯然,ENTRYPOINT
在這里會更為方便。
ENV 設置環(huán)境變量
格式有兩種:
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
這個指令就是設置環(huán)境變量而已,無論是后面的其它指令,如?RUN
,還是運行時的應用,都可以直接使用這里定義的環(huán)境變量。
ENV VERSION=1.0 DEBUG=on NAME="Happy Feet"
比如在官方?node
?鏡像?Dockerfile
?中,就有類似這樣的代碼:
ENV NODE_VERSION 7.2.0
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
?&& curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
?&& gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
?&& grep " node-v$NODE_VERSION-linux-x64.tar.xz$" SHASUMS256.txt | sha256sum -c - \
?&& tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 \
?&& rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \
?&& ln -s /usr/local/bin/node /usr/local/bin/nodejs
VOLUME 定義匿名卷
格式為:
VOLUME ["<路徑1>", "<路徑2>"...]
VOLUME <路徑>
之前我們說過,容器運行時應該盡量保持容器存儲層不發(fā)生寫操作,對于數據庫類需要保存動態(tài)數據的應用,其數據庫文件應該保存于卷(volume)中。為了防止運行時用戶忘記將動態(tài)文件所保存目錄掛載為卷,在?Dockerfile
?中,我們可以事先指定某些目錄掛載為匿名卷,這樣在運行時如果用戶不指定掛載,其應用也可以正常運行,不會向容器存儲層寫入大量數據。
VOLUME /data
VOLUME ["data1", "data2"]
這里的?/data
?目錄就會在容器運行時自動掛載為匿名卷,任何向?/data
?中寫入的信息都不會記錄進容器存儲層,從而保證了容器存儲層的無狀態(tài)化。當然,運行容器時可以覆蓋這個掛載設置。比如:
$ docker run -d -v mydata:/data xxxx
在這行命令中,就使用了?mydata
?這個命名卷掛載到了?/data
?這個位置,替代了?Dockerfile
?中定義的匿名卷的掛載配置。
EXPOSE 暴露端口
格式為?EXPOSE <端口1> [<端口2>...]
。
EXPOSE
?指令是聲明容器運行時提供服務的端口,這只是一個聲明,在容器運行時并不會因為這個聲明應用就會開啟這個端口的服務。在 Dockerfile 中寫入這樣的聲明有兩個好處,一個是幫助鏡像使用者理解這個鏡像服務的守護端口,以方便配置映射;另一個用處則是在運行時使用隨機端口映射時,也就是?docker run -P
?時,會自動隨機映射?EXPOSE
?的端口。
WORKDIR 指定工作目錄
格式為?WORKDIR <工作目錄路徑>
。
使用?WORKDIR
?指令可以來指定工作目錄(或者稱為當前目錄),以后各層的當前目錄就被改為指定的目錄,如該目錄不存在,WORKDIR
?會幫你建立目錄。
之前提到一些初學者常犯的錯誤是把?Dockerfile
?等同于 Shell 腳本來書寫,這種錯誤的理解還可能會導致出現下面這樣的錯誤:
RUN cd /app
RUN echo "hello" > world.txt
如果將這個?Dockerfile
?進行構建鏡像運行后,會發(fā)現找不到?/app/world.txt
?文件,或者其內容不是?hello
。原因其實很簡單,在 Shell 中,連續(xù)兩行是同一個進程執(zhí)行環(huán)境,因此前一個命令修改的內存狀態(tài),會直接影響后一個命令;而在?Dockerfile
?中,這兩行?RUN
?命令的執(zhí)行環(huán)境根本不同,是兩個完全不同的容器。這就是對?Dockerfile
?構建分層存儲的概念不了解所導致的錯誤。
之前說過每一個?RUN
?都是啟動一個容器、執(zhí)行命令、然后提交存儲層文件變更。第一層?RUN cd /app
?的執(zhí)行僅僅是當前進程的工作目錄變更,一個內存上的變化而已,其結果不會造成任何文件變更。而到第二層的時候,啟動的是一個全新的容器,跟第一層的容器更完全沒關系,自然不可能繼承前一層構建過程中的內存變化。
因此如果需要改變以后各層的工作目錄的位置,那么應該使用?WORKDIR
?指令。
WORKDIR /app
RUN echo "hello" > world.txt
LABEL 為鏡像添加元數據
LABEL
?指令用來給鏡像以鍵值對的形式添加一些元數據(metadata)。
LABEL <key>=<value> <key>=<value> <key>=<value> ...
我們還可以用一些標簽來申明鏡像的作者、文檔地址等:
LABEL org.opencontainers.image.authors="cyr"
LABEL org.opencontainers.image.documentation="https://cyr.github.io"
實戰(zhàn):構建自己的 centos 鏡像
原先的 centos 鏡像無法執(zhí)行vim
和ifconfig
指令

我們自己構建一個centos,來實現這兩個指令,首先我們寫 Dockerfile
FROM centos:7
LABEL author="cyr@gmail.com"
ENV MYPATH /usr/local
WORKDIR $MYPATH
RUN yum -y install vim
RUN yum -y install net-tools
EXPOSE 80
CMD echo $MYPATH
CMD echo "=======end========"
CMD /bin/bash

接著我們構建 Dockerfile 生成鏡像
docker build -f Dockerfile -t mycentos:0.1 .

測試運行一下:
docker run -it mycentos:0.1

可以發(fā)現,指令運行成功了!
實戰(zhàn):制作tomcat鏡像
新建文件夾
mytomcat
將下載好的
jdk8
和tomcat9
放到mytomcat
目錄下新建?
readme.txt
?,Dockerfile
文件構建鏡像
啟動容器
訪問測試
添加
web.xml
,WEB-INF
,a.jsp
,重啟容器訪問自己的頁面
# 準備文件
[root@localhost home]# mkdir /home/mytomcat
[root@localhost home]# cd mytomcat/
[root@localhost mytomcat]# touch readme.txt
[root@localhost mytomcat]# vim Dockerfile
[root@localhost mytomcat]# ll
總用量 147336
-rw-r--r--. 1 root root ?11642299 5月 ? 1 14:23 apache-tomcat-9.0.74.tar.gz
-rw-r--r--. 1 root root ? ? ? 914 5月 ? 1 14:32 Dockerfile
-rw-r--r--. 1 root root 139219380 5月 ? 1 14:23 jdk-8u371-linux-x64.tar.gz
-rw-r--r--. 1 root root ? ? ? ? 0 5月 ? 1 14:28 readme.txt
[root@localhost mytomcat]# cat Dockerfile
FROM centos:7
MAINTAINER cyr<cyr@gmail.com>
#把宿主機當前上下文的readme.txt拷貝到容器/usr/local/路徑下
COPY readme.txt /usr/local/cincontainer.txt
#把java與tomcat添加到容器中
ADD jdk-8u371-linux-x64.tar.gz /usr/local/
ADD apache-tomcat-9.0.74.tar.gz /usr/local/
#安裝vim編輯器
RUN yum -y install vim
#設置工作訪問時候的WORKDIR路徑,登錄落腳點
ENV MYPATH /usr/local
WORKDIR $MYPATH
#配置java與tomcat環(huán)境變量
ENV JAVA_HOME /usr/local/jdk1.8.0_371
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.74
ENV CATALINA_BASE /usr/local/apache-tomcat-9.0.74
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin
#容器運行時監(jiān)聽的端口
EXPOSE ?8080
#啟動時運行tomcat
CMD /usr/local/apache-tomcat-9.0.74/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.74/bin/logs/catalina.out
# 構建鏡像
[root@localhost mytomcat]# docker build -t mytomcat .
[+] Building 44.4s (11/11) FINISHED ? ? ? ? ? ? ? ? ? ? ? ? ?
=> [internal] load build definition from Dockerfile ? ? ? ? ? ?
=> => transferring dockerfile: 1.01kB ? ? ? ? ? ? ? ? ? ? ? ? ?
=> [internal] load .dockerignore ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
=> => transferring context: 2B ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
=> [internal] load metadata for docker.io/library/centos:7 ? ? ?
=> [internal] load build context ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
=> => transferring context: 150.90MB ? ? ? ? ? ? ? ? ? ? ? ? ? ?
=> CACHED [1/6] FROM docker.io/library/centos:7@sha256:9d4bcbbb213dfd745b58be38b13b996ebb5ac315fe75711bd618426a630e0987
=> [2/6] COPY readme.txt /usr/local/cincontainer.txt ? ? ? ? ? ?
=> [3/6] ADD jdk-8u371-linux-x64.tar.gz /usr/local/ ? ? ? ? ? ?
=> [4/6] ADD apache-tomcat-9.0.74.tar.gz /usr/local/ ? ? ? ? ?
=> [5/6] RUN yum -y install vim ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
=> [6/6] WORKDIR /usr/local ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
=> exporting to image ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
=> => exporting layers ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
=> => writing image sha256:ca383e6a4bed230b3abe065830e7f4d717458f31ef609e67c43ed564e387e544 ? ?
=> => naming to docker.io/library/mytomcat
# 啟動容器 (這里之前一直啟動失敗,我的原因是掛載的路徑有點問題,未解之謎)
[root@localhost local]# docker run -d -p 9000:8080 --name mytomcat1 -v /home/mytomcat/test:/usr/local/apache-tomcat-9.0.74/webapps/test mytomcat
51655608be591c1aa2cf5acdffce9236c2a3de13df3a8534a6809efaa72abc37
[root@localhost local]# docker ps
CONTAINER ID ? IMAGE ? ? ?COMMAND ? ? ? ? ? ? ? ? ? CREATED ? ? ? ? STATUS ? ? ? ? PORTS ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? NAMES
51655608be59 ? mytomcat ? "/bin/sh -c '/usr/lo…" ? 3 seconds ago ? Up 2 seconds ? 0.0.0.0:9000->8080/tcp, :::9000->8080/tcp ? mytomcat1
訪問測試結果

容器內的test掛載到宿主機的test上

[root@localhost mytomcat]# cd test/
[root@localhost test]# vim web.xml
[root@localhost test]# mkdir WEB-INF
# [root@localhost test]# cd WEB-INF/
[root@localhost WEB-INF]# vim a.jsp # 我這里的a.jsp 必須與 WEB-INF 同級才能訪問,未解之謎
[root@localhost WEB-INF]# cat a.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
?<head>
? ?<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
? ?<title>Insert title here</title>
?</head>
?<body>
? ?-----------welcome------------
? ?<%="i am in docker tomcat self "%>
? ?<br>
? ?<br>
? ?<% System.out.println("=============docker tomcat self");%>
?</body>
</html>
[root@localhost WEB-INF]# cat ../web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
?xmlns="http://java.sun.com/xml/ns/javaee"
?xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
?id="WebApp_ID" version="2.5">
?
?<display-name>test</display-name>
</web-app>
# 重啟容器
docker restart 6400 # 6400 為容器id簡略版
下面是訪問結果!

文章鏈接:https://www.dianjilingqu.com/728332.html