Dockerfile 定義鏡像,依賴鏡像來運行容器,所以 Dockerfile 是鏡像和容器的關鍵,Dockerfile 能夠很是容易的定義鏡像內容,同時在咱們後期的微服務實踐中,Dockerfile 也是重點關注的內容,今天咱們就來一塊兒學習它。html
首先經過一張圖來了解 Docker 鏡像、容器和 Dockerfile 三者之間的關係。node
經過上圖能夠看出使用 Dockerfile 定義鏡像,運行鏡像啓動容器。nginx
Docker 鏡像是一個特殊的文件系統,除了提供容器運行時所需的程序、庫、資源、配置等文件外,還包含了一些爲運行時準備的一些配置參數(如匿名卷、環境變量、用戶等)。鏡像不包含任何動態數據,其內容在構建以後也不會被改變。docker
鏡像的定製實際上就是定製每一層所添加的配置、文件。若是咱們能夠把每一層修改、安裝、構建、操做的命令都寫入一個腳本,用這個腳原本構建、定製鏡像,那麼以前說起的沒法重複的問題、鏡像構建透明性的問題、體積的問題就都會解決。這個腳本就是 Dockerfile。ubuntu
Dockerfile 是一個文本文件,其內包含了一條條的指令(Instruction),每一條指令構建一層,所以每一條指令的內容,就是描述該層應當如何構建。有了 Dockerfile,當咱們須要定製本身額外的需求時,只需在 Dockerfile 上添加或者修改指令,從新生成 image 便可,省去了敲命令的麻煩。瀏覽器
Dockerfile文件格式以下:緩存
## Dockerfile文件格式 # This dockerfile uses the ubuntu image # VERSION 2 - EDITION 1 # Author: docker_user # Command format: Instruction [arguments / command] .. # 一、第一行必須指定 基礎鏡像信息 FROM ubuntu # 二、維護者信息 MAINTAINER docker_user docker_user@email.com # 三、鏡像操做指令 RUN echo "deb http://archive.ubuntu.com/ubuntu/ raring main universe" >> /etc/apt/sources.list RUN apt-get update && apt-get install -y nginx RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf # 四、容器啓動執行指令 CMD /usr/sbin/nginx
Dockerfile 分爲四部分:基礎鏡像信息、維護者信息、鏡像操做指令、容器啓動執行指令。一開始必需要指明所基於的鏡像名稱,接下來通常會說明維護者信息;後面則是鏡像操做指令,例如 RUN 指令。每執行一條RUN 指令,鏡像添加新的一層,並提交;最後是 CMD 指令,來指明運行容器時的操做命令。bash
docker build 命令會根據 Dockerfile 文件及上下文構建新 Docker 鏡像。構建上下文是指 Dockerfile 所在的本地路徑或一個URL(Git倉庫地址)。構建上下文環境會被遞歸處理,因此構建所指定的路徑還包括了子目錄,而URL還包括了其中指定的子模塊。服務器
將當前目錄作爲構建上下文時,能夠像下面這樣使用docker build命令構建鏡像:tcp
docker build . Sending build context to Docker daemon 6.51 MB ...
說明:構建會在 Docker 後臺守護進程(daemon)中執行,而不是CLI
中。構建前,構建進程會將所有內容(遞歸)發送到守護進程。大多狀況下,應該將一個空目錄做爲構建上下文環境,並將 Dockerfile 文件放在該目錄下。
在構建上下文中使用的 Dockerfile 文件,是一個構建指令文件。爲了提升構建性能,能夠經過.dockerignore
文件排除上下文目錄下不須要的文件和目錄。
在 Docker 構建鏡像的第一步,docker CLI 會先在上下文目錄中尋找.dockerignore
文件,根據.dockerignore
文件排除上下文目錄中的部分文件和目錄,而後把剩下的文件和目錄傳遞給 Docker 服務。
Dockerfile 通常位於構建上下文的根目錄下,也能夠經過-f
指定該文件的位置:
docker build -f /path/to/a/Dockerfile .
構建時,還能夠經過-t
參數指定構建成鏡像的倉庫、標籤。
docker build -t nginx/v3 .
若是存在多個倉庫下,或使用多個鏡像標籤,就可使用多個-t
參數:
docker build -t nginx/v3:1.0.2 -t nginx/v3:latest .
在 Docker 守護進程執行 Dockerfile 中的指令前,首先會對 Dockerfile 進行語法檢查,有語法錯誤時會返回:
docker build -t nginx/v3 . Sending build context to Docker daemon 2.048 kB Error response from daemon: Unknown instruction: RUNCMD
Docker 守護進程會一條一條的執行 Dockerfile 中的指令,並且會在每一步提交併生成一個新鏡像,最後會輸出最終鏡像的ID。生成完成後,Docker 守護進程會自動清理你發送的上下文。 Dockerfile文件中的每條指令會被獨立執行,並會建立一個新鏡像,RUN cd /tmp等命令不會對下條指令產生影響。 Docker 會重用已生成的中間鏡像,以加速docker build的構建速度。如下是一個使用了緩存鏡像的執行過程:
$ docker build -t svendowideit/ambassador . Sending build context to Docker daemon 15.36 kB Step 1/4 : FROM alpine:3.2 ---> 31f630c65071 Step 2/4 : MAINTAINER SvenDowideit@home.org.au ---> Using cache ---> 2a1c91448f5f Step 3/4 : RUN apk update && apk add socat && rm -r /var/cache/ ---> Using cache ---> 21ed6e7fbb73 Step 4/4 : CMD env | grep _TCP= | (sed 's/.*_PORT_\([0-9]*\)_TCP=tcp:\/\/\(.*\):\(.*\)/socat -t 100000000 TCP4-LISTEN:\1,fork,reuseaddr TCP4:\2:\3 \&/' && echo wait) | sh ---> Using cache ---> 7ea8aef582cc Successfully built 7ea8aef582cc
構建緩存僅會使用本地父生成鏈上的鏡像,若是不想使用本地緩存的鏡像,也能夠經過--cache-from
指定緩存。指定後將再也不使用本地生成的鏡像鏈,而是從鏡像倉庫中下載。
Docker 尋找緩存的邏輯其實就是樹型結構根據 Dockerfile 指令遍歷子節點的過程。下圖能夠說明這個邏輯。
FROM base_image:version Dockerfile:
+----------+ FROM base_image:version
|base image| RUN cmd1 --> use cache because we found base image +-----X----+ RUN cmd11 --> use cache because we found cmd1 / \ / \ RUN cmd1 RUN cmd2 Dockerfile: +------+ +------+ FROM base_image:version |image1| |image2| RUN cmd2 --> use cache because we found base image +---X--+ +------+ RUN cmd21 --> not use cache because there's no child node / \ running cmd21, so we build a new image here / \ RUN cmd11 RUN cmd12 +-------+ +-------+ |image11| |image12| +-------+ +-------+
大部分指令能夠根據上述邏輯去尋找緩存,除了 ADD 和 COPY 。這兩個指令會複製文件內容到鏡像內,除了指令相同之外,Docker 還會檢查每一個文件內容校驗(不包括最後修改時間和最後訪問時間),若是校驗不一致,則不會使用緩存。
除了這兩個命令,Docker 並不會去檢查容器內的文件內容,好比 RUN apt-get -y update
,每次執行時文件可能都不同,可是 Docker 認爲命令一致,會繼續使用緩存。這樣一來,之後構建時都不會再從新運行apt-get -y update
。
若是 Docker 沒有找到當前指令的緩存,則會構建一個新的鏡像,而且以後的全部指令都不會再去尋找緩存。
接下來用一個簡單的示例來感覺一下 Dockerfile 是如何用來構建鏡像啓動容器。咱們以定製 nginx 鏡像爲例,在一個空白目錄中,創建一個文本文件,並命名爲 Dockerfile:
mkdir mynginx
cd mynginx
vi Dockerfile
構建一個 Dockerfile 文件內容爲:
FROM nginx RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html vi Dockerfile
這個 Dockerfile 很簡單,一共就兩行涉及到了兩條指令:FROM 和 RUN,FROM 表示獲取指定基礎鏡像,RUN 執行命令,在執行的過程當中重寫了 nginx 的默認頁面信息,將信息替換爲:Hello, Docker!。
在 Dockerfile 文件所在目錄執行:
docker build -t nginx:v1 .
命令最後有一個. 表示當前目錄
構建完成以後,使用 docker images
命令查看全部鏡像,若是存在 REPOSITORY 爲 nginx 和 TAG 是 v1 的信息,就表示構建成功。
docker images REPOSITORY TAG IMAGE ID CREATED SIZE nginx v1 8c92471de2cc 6 minutes ago 108.6 MB
接下來使用 docker run 命令來啓動容器
docker run --name docker_nginx_v1 -d -p 80:80 nginx:v1
這條命令會用 nginx 鏡像啓動一個容器,命名爲docker_nginx_v1
,而且映射了 80 端口,這樣咱們能夠用瀏覽器去訪問這個 nginx 服務器:http://192.168.0.54/
,頁面返回信息:
這樣一個簡單使用 Dockerfile 構建鏡像,運行容器的示例就完成了!
容器啓動後,須要對容器內的文件進行進一步的完善,可使用docker exec -it xx bash
命令再次進行修改,以上面的示例爲基礎,修改 nginx 啓動頁面內容:
docker exec -it docker_nginx_v1 bash root@3729b97e8226:/# echo '<h1>Hello, Docker neo!</h1>' > /usr/share/nginx/html/index.html root@3729b97e8226:/# exit exit
以交互式終端方式進入 docker_nginx_v1 容器,並執行了 bash 命令,也就是得到一個可操做的 Shell。而後,咱們用<h1>Hello, Docker neo!</h1>
覆蓋了 /usr/share/nginx/html/index.html
的內容。
再次刷新瀏覽器,會發現內容被改變。
修改了容器的文件,也就是改動了容器的存儲層,能夠經過 docker diff 命令看到具體的改動。
docker diff docker_nginx_v1
...
原文