Hello Docker(三)——Docker鏡像製做

Hello Docker(三)——Docker鏡像製做

1、Dockerfile腳本

一、Dockerfile腳本簡介

Dockerfile是一個文本文件,其內包含一系列指令(Instruction),每一條指令構建一層,所以每一條指令的內容就是描述該層應當如何構建。
Dockerfile文件示例以下:node

##  Dockerfile文件格式
# This dockerfile uses the ubuntu image
# VERSION 2 - EDITION 1
# Author: docker_user
# Command format: Instruction [arguments / command] ..

# 一、第一行必須指定 基礎鏡像信息
FROM centos

# 二、維護者信息
MAINTAINER docker_user docker_user@email.com

# 三、鏡像操做指令
RUN yum install -y nginx

# 四、容器啓動執行指令
CMD /usr/sbin/nginx

Dockerfile分爲四部分:基礎鏡像信息、維護者信息、鏡像操做指令、容器啓動執行指令。第一部分必須指明基礎鏡像名稱;第二部分一般說明維護者信息;第三部分是鏡像操做指令,例如RUN指令,每執行一條RUN 指令,鏡像添加新的一層,並提交;第四部分是CMD指令,指明運行容器時的操做命令。
Dockerfile官方文檔:
https://docs.docker.com/engine/reference/builder/
Dockerfile最佳實踐文檔:
https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/
Docker官方鏡像Dockerfile:
https://github.com/docker-library/docspython

二、FROM指令

FROM用於指定基礎鏡像,所以Dockerfile中FROM是必備指令,而且必須是第一條指令。
Docker Hub中有很是多的高質量的官方鏡像,有直接可用的服務類鏡像,如nginx、redis、mongo、mysql 、httpd、ph、tomcat 等;有方便開發、構建、運行各類語言應用的鏡像,如node、openjdk、 python、ruby、golang等;有基礎的操做系統鏡像,如ubuntu、debian、centos、fedora、alpine等。
Docker存在一個特殊的scratch鏡像,scratch鏡像是虛擬的概念,並不實際存在,表示一個空白的鏡像。
若是以scratch爲基礎鏡像,不以任何鏡像爲基礎,後續所寫的指令將做爲鏡像第一層開始存在。不以任何系統爲基礎,直接將可執行文件複製進鏡像如swarm、coreos/etcd。Linux下靜態編譯的程序並不須要有操做系統提供運行時支持,所需的一切庫都已經在可執行文件裏,所以直接FROM scratch會讓鏡像體積更加小巧。
FROM語法格式爲:mysql

FROM <image>
FROM <image>:<tag>
FROM <image>:<digest>

FROM限制以下:
A、FROM必須是Dockerfile中第一條非註釋命令
B、在一個Dockerfile文件中建立多個鏡像時,FROM能夠屢次出現。只需在每一個新命令FROM前,記錄提交上次的鏡像ID。
C、tag或digest是可選的,若是不使用tag值時,會使用latest版本的基礎鏡像。linux

三、RUN指令

在鏡像的構建過程當中執行特定的命令,並生成一箇中間鏡像。格式:ios

#shell格式
RUN <command>
#exec格式
RUN ["executable", "param1", "param2"]

RUN命令將在當前image中執行任意合法命令並提交執行結果。命令執行提交後,就會自動執行Dockerfile中的下一個指令。
RUN指令建立的中間鏡像會被緩存,並會在下次構建中使用。若是不想使用緩存鏡像,能夠在構建時指定--no-cache參數,如:docker build --no-cache。nginx

四、COPY指令

COPY指令語法格式:git

COPY <源路徑>...   <目標路徑>  
COPY ["<源路徑1>",...  "<目標路徑>"]

COPY 指令將從構建上下文目錄中<源路徑>的文件/目錄複製到新的一層的鏡像內的<目標路徑>位置。
<源路徑>能夠是多個,甚至能夠是通配符,其通配符規則要知足Go的filepath.Match規則,如:github

COPY hom* /mydir/
COPY hom?.txt /mydir/

<目標路徑>能夠是容器內的絕對路徑,也能夠是相對於工做目錄的相對路徑(工做目錄能夠用WORKDIR指令來指定)。目標路徑不須要事先建立,若是目錄不存在會在複製文件前先行建立缺失目錄。
使用COPY指令,源文件的各類元數據都會保留,好比讀、寫、執行權限、文件變動時間等。golang

五、ADD指令

ADD指令在COPY基礎上增長了一些功能,好比<源路徑>能夠是一個URL,Docker引擎會試圖去下載URL連接的文件放到<目標路徑>。
在構建鏡像時,複製上下文中的文件到鏡像內,格式:web

ADD <源路徑>... <目標路徑>
ADD ["<源路徑>",... "<目標路徑>"]

若是Docker發現文件內容被改變,則後續指令都不會再使用緩存。
ADD指令會將相應文件增長到目標目錄,若是源文件是壓縮文件會進行解壓操做。

六、CMD指令

CMD用於指定在容器啓動時所要執行的命令。CMD有三種格式:

CMD ["executable","param1","param2"]
CMD ["param1","param2"]
CMD command param1 param2

默認爲exec格式,使CMD中的參數當作ENTRYPOINT的默認參數,此時ENTRYPOINT應該是exec格式。
若是CMD是/bin/bash,使用docker run -it ubuntu啓動容器時,會直接執行進入bash。docker run -it ubuntu cat /etc/os-release會在啓動容器時輸出系統版本信息。
在CMD指令格式上,推薦使用exec格式,exec格式在解析時會被解析爲JSON數組,所以必須使用雙引號,而不要使用單引號。
若是使用shell格式,CMD命令會被包裝爲sh -c的參數的形式進行執行。好比:
CMD echo $HOME會將其變動爲:
CMD ["sh", "-c","echo $HOME"]
exec格式不能使用shell中的環境變量,若是要使用shell中環境變量,須要在exec格式命令中指定使用shell腳本。

七、ENTRYPOINT指令

ENTRYPOINT指令用於給容器配置一個可執行程序。每次使用鏡像建立容器時,經過ENTRYPOINT指定的程序都會被設置爲默認程序。ENTRYPOINT有兩種形式:

ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT command param1 param2

經過docker run執行的命令不會覆蓋ENTRYPOINT,而docker run命令中指定的任何參數,都會被當作參數再次傳遞給ENTRYPOINT。Dockerfile中只容許有一個ENTRYPOINT命令,多指定時會覆蓋前面的設置,而只執行最後的ENTRYPOINT指令。
docker run運行容器時指定的參數都會被傳遞給ENTRYPOINT,且會覆蓋 CMD命令指定的參數。執行docker run &lt;image&gt; -d時,-d指定的參數將被傳遞給入口點,也能夠經過docker run --entrypoint重寫ENTRYPOINT入口點。
ENTRYPOINT ["/usr/bin/nginx"]

八、ENV指令

ENV指令用於設置環境變量,後續的指令能夠直接使用。

ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...

ENV示例以下:

ENV VERSION=1.0 DEBUG=on \
NAME="Happy Feet"

九、ARG指令

ARG指令用於指定傳遞給構建運行時的變量。
ARG &lt;name&gt;[=&lt;default value&gt;]
經過ARG指定兩個變量:

ARG site
ARG build_user=scorpio

上述指令指定site和build_user兩個變量,其中build_user指定了默認值。使用docker build構建鏡像時,能夠經過--build-arg &lt;varname&gt;=&lt;value&gt; 選項參數來指定或重設置相應變量的值。
docker build --build-arg site=www.baidu.com -t baidu/test .
build_user變量使用默認值scorpio。

十、VOLUME指令

VOLUME指令用於建立掛載點,即向基於所構建鏡像創始的容器添加捲:
VOLUME ["/data"]
一個卷能夠存在於一個或多個容器的指定目錄,該目錄能夠繞過聯合文件系統,並具備如下功能:
A、卷能夠容器間共享和重用
B、容器並不必定要和其它容器共享卷
C、修改卷後會當即生效
D、對卷的修改不會對鏡像產生影響
E、卷會一直存在,直到沒有任何容器在使用它
VOLUME能夠將源代碼、數據或其它內容添加到鏡像中,而不提交到鏡像中,並使多個容器間共享數據。

十一、EXPOSE指令

EXPOSE指令爲構建的鏡像設置監聽端口,使容器在運行時監聽。格式以下:
EXPOSE &lt;port&gt; [&lt;port&gt;...]
EXPOSE指令並不會讓容器監聽host的端口,若是須要容器監聽Host端口,須要在docker run時使用 -p、-P 參數來發布容器端口到host的某個端口上。

十二、WORKDIR指令

WORKDIR指令用於在容器內設置一個工做目錄。
WORKDIR /path/to/workdir
經過WORKDIR設置工做目錄後,Dockerfile中的後續命令RUN、CMD、ENTRYPOINT、ADD、COPY 等命令都會在工做目錄下執行。

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

pwd最終將會在 /a/b/c 目錄中執行。使用docker run運行容器時,能夠經過-w參數覆蓋構建時所設置的工做目錄。
WORKDIR指令推薦使用絕對路徑。

1三、USER指令

USER指令用於指定運行鏡像所使用的用戶。
USER daemon
使用USER指定用戶時,可使用用戶名、UID 或 GID,或是二者的組合。

USER user
USER user:group
USER uid
USER uid:gid
USER user:gid
USER uid:group

使用USER指定用戶後,Dockerfile中的後續命令RUN、CMD、ENTRYPOINT 都將使用該用戶。鏡像構建完成後,經過docker run 運行容器時,能夠經過-u參數來覆蓋所指定的用戶。

1四、HEALTHCHECK指令

HEALTHCHECK [OPTIONS] CMD command  
經過運行一個容器內部的命令來檢測容器是否健康
HEALTHCHECK NONE
關閉任何來自基礎image的健康檢測
options
--interval=DURATION (default: 30s)
--timeout=DURATION (default: 30s)
--retries=N (default: 3)

1五、ONBUILD指令

ONBUILD指令用於設置鏡像觸發器。
ONBUILD [INSTRUCTION]
當所構建的鏡像被用做其它鏡像的基礎鏡像,鏡像中的觸發器將會被觸發。當鏡像被使用時,可能須要作一些處理:

[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]

1六、LABEL

LABEL指令用於爲鏡像添加元數據,元數以鍵值對的形式指定。
LABEL &lt;key&gt;=&lt;value&gt; &lt;key&gt;=&lt;value&gt; &lt;key&gt;=&lt;value&gt; ...
使用LABEL指定元數據時,一條LABEL指定能夠指定一或多條元數據,指定多條元數據時不一樣元數據之間經過空格分隔。推薦將全部的元數據經過一條LABEL指令指定,以避免生成過多的中間鏡像。
LABEL version="1.0" description="hello world" by="scorpio"
LABEL指定的元數據能夠經過docker inspect查看。

1七、STOPSIGNAL

STOPSIGNAL指令用於設置中止容器所要發送的系統調用信號。
STOPSIGNAL signal
所使用的信號必須是內核系統調用表中的合法的值,如:SIGKILL。

1八、SHELL

SHELL指令用於設置執行命令(shell)所使用的的默認shell類型。
SHELL ["executable", "parameters"]
SHELL在Windows環境下比較有用,Windows下一般會有cmd和 powershell兩種shell,能夠經過SHELL來指定所使用的shell類型。

2、Dockerfile構建鏡像

一、Dockerfile構建簡介

docker build命令會根據Dockerfile文件及上下文構建新Docker鏡像。構建上下文是指Dockerfile所在的本地路徑或一個URL(Git倉庫地址)。構建上下文環境會被遞歸處理,因此構建所指定的路徑還包括子目錄,而URL還包括其中指定的子模塊。
構建會在Docker後臺守護進程(daemon)中執行,而不是CLI中。構建前,構建進程會將所有內容(遞歸)發送到守護進程。一般,應該將一個空目錄做爲構建上下文環境,並放入Dockerfile文件。
在構建上下文中使用的Dockerfile文件是一個構建指令文件。爲了提升構建性能,能夠經過.dockerignore文件排除上下文目錄下不須要的文件和目錄。
在Docker構建鏡像的第一步,docker CLI會先在上下文目錄中尋找.dockerignore文件,根據.dockerignore 文件排除上下文目錄中的部分文件和目錄,而後把剩下的文件和目錄傳遞給Docker服務。
Dockerfile文件通常位於構建上下文的根目錄下,也能夠經過-f指定Dockerfile文件的位置:
docker build -f /path/to/a/Dockerfile .
構建時,還能夠經過-t參數指定構建成鏡像的倉庫、標籤。若是存在多個倉庫下,或使用多個鏡像標籤,就可使用多個-t參數:
docker build -t nginx/v3:1.0.2 -t nginx/v3:latest .
在Docker守護進程執行Dockerfile中的指令前,首先會對Dockerfile進行語法檢查,有語法錯誤時會返回錯誤提示信息。
Dockerfile文件:

FROM ubuntu:14.04  
ADD run.sh /  
VOLUME /data  
CMD ["./run.sh"]

Dockerfile文件構建的容器以下:
Hello Docker(三)——Docker鏡像製做

二、同構鏡像構建

同構鏡像構建是指鏡像構建環境與運行環境兼容。
同構鏡像構建通常要求編譯環境與鏡像所使用的base image是兼容的,好比在Ubuntu 14.04上編譯應用,並將應用打入基於ubuntu系列base image的鏡像。由於應用的編譯環境與其部署運行的環境是兼容的,在Ubuntu 14.04下編譯出來的應用,能夠基本無縫地在基於ubuntu:14.04及後續版本base image鏡像中運行;但在不徹底兼容的base image中,好比CentOS中就可能會運行失敗。

package main 

import ( 
        "net/http" 
        "log" 
        "fmt" 
) 

func home(w http.ResponseWriter, req *http.Request) { 
        w.Write([]byte("Welcome to this website!\n")) 
} 

func main() { 
        http.HandleFunc("/", home) 
        fmt.Println("Webserver start") 
        fmt.Println("  -> listen on port:1111") 
        err := http.ListenAndServe(":1111", nil) 
        if err != nil { 
                log.Fatal("ListenAndServe:", err) 
        } 
}

編譯:
go build -o httpserver httpserver.go 
Dockerfile文件:

From ubuntu:14.04 

COPY ./httpserver /root/httpserver 
RUN chmod +x /root/httpserver 

WORKDIR /root 
ENTRYPOINT ["/root/httpserver"]

構建httpserver服務鏡像:
docker build -t httpserver:latest . 
啓動httpserver服務容器:
docker run httpserver 
基於ubuntu基礎鏡像構建出的應用鏡像太過臃腫,所以有必要基於golang:latest構建本身專用的golang-builder image,Dockerfile.build能夠用於build golang-builder image:

FROM golang:latest 
WORKDIR /go/src 
COPY httpserver.go . 
RUN go build -o httpserver ./httpserver.go

構建golang-builder鏡像:
docker build -t golang-builder:latest -f Dockerfile.build . 
從golang-builder建立一個容器appsource
docker create --name appsource golang-builder:latest
從appsource容器中將httpserver拷貝到主機當前目錄
docker cp appsource:/go/src/httpserver ./
刪除appsource容器
docker rm -f appsource
刪除golang-builder鏡像
docker rmi golang-builder:latest
從當前目錄構建出httpserver鏡像
docker build -t httpserver:latest .
httpserver鏡像的大小依舊停留在200MB。要想減少httpserver鏡像的大小,必須使用更小的base image,即alpine 。 Alpine image的大小不到4M,再加上應用的size,最終應用鏡像的大小估計能夠縮減到20M如下。
Dockerfile.alpine 文件:

From alpine:latest 

COPY ./httpserver /root/httpserver 
RUN chmod +x /root/httpserver 

WORKDIR /root 
ENTRYPOINT ["/root/httpserver"]

構建alpine版應用鏡像:
docker build -t httpserver-alpine:latest -f Dockerfile.alpine .
啓動httpserver-alpine容器會失敗,由於alpine image並不是ubuntu環境的同構image。

三、異構鏡像構建

異構鏡像構建是指構建環境與運行環境不兼容。
Go將runtime中的C代碼都用Go重寫,對libc的依賴已經降到最低,但提供了兩個版本的實現:C實現和Go實現。默認狀況下,即在CGO_ENABLED=1狀況下,程序和預編譯的標準庫都採用C實現。所以採用不一樣libc實現的debian系和alpine系天然存在不兼容的狀況。考慮異構鏡像建立首先對Go程序進行靜態構建,而後將靜態構建後的Go應用放入alpine image中。
Dockerfile.build文件以下:

FROM golang:alpine 

WORKDIR /go/src 
COPY httpserver.go . 

RUN go build -o httpserver ./httpserver.go

構建builder鏡像:

docker build -t myrepo/golang-static-builder:latest -f Dockerfile.build . 
docker create --name appsource golang-static-builder:latest 
docker cp appsource:/go/src/httpserver ./ 
docker rm -f appsource 
docker rmi golang-static-builder:latest 
docker build -t httpserver-alpine:latest -f Dockerfile.alpine .

運行httpserver服務容器:
docker run httpserver-alpine:latest 
alpine版golang builder鏡像Dockerfile:

FROM golang:alpine 

WORKDIR /go/src 
COPY httpserver.go . 

RUN go build -o httpserver ./httpserver.go

3、Docker多階段構建

一、Dockerfile多階段構建

2017年5月發佈的 Docker 17.05.0-ce 中,Docker官方提供了簡便的多階段構建(multi-stage build)方案。
對於多階段構建,能夠在Dockerfile中使用多個FROM語句。每一個FROM指令可使用不一樣的基礎鏡像,做爲一個構建階段,多條 FROM 就是多階段構建,雖然最後生成的鏡像只能是最後一個階段的結果,但可以將前邊階段中的文件拷貝到後邊的階段中。
多階段構建最大的使用場景是將編譯環境和運行環境分離。

# 編譯階段
FROM golang:1.10.3

COPY server.go /build/

WORKDIR /build

RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOARM=6 go build -ldflags '-w -s' -o server

# 運行階段
 FROM scratch

# 從編譯階段中拷貝編譯結果到當前鏡像中
COPY --from=0 /build/server /

ENTRYPOINT ["/server"]

Dockerfile的COPY指令--from=0參數,從前邊的階段中拷貝文件到當前階段中,多個FROM語句時,0表明第一個階段。除了使用數字,還能夠給階段命名,好比:

# 編譯階段
FROM golang:1.10.3 as builder

COPY server.go /build/

WORKDIR /build

RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOARM=6 go build -ldflags '-w -s' -o server

# 運行階段
 FROM scratch

# 從編譯階段的中拷貝編譯結果到當前鏡像中
COPY --from=builder /build/server /

ENTRYPOINT ["/server"]

COPY –from指令從單獨的image中複製,使用本地image名稱,本地或Docker鏡像倉庫中可用的標記或標記ID。 

二、停在特定構建階段

構建映像時,不必定須要構建整個Dockerfile文件的每一個階段,能夠指定目標構建階段。
docker build --target builder -t builder:latest .
使用Dockerfile構建,在builder階段中止。
停在特定構建階段很是適合的場景以下:
A、調試特定的構建階段
B、在debug階段,啓用全部調試或工具,而在production階段儘可能精簡
C、在testing階段,應用程序將填充測試數據,但在production階段則使用生產數據

三、Dockerfile多項目構建

利用多階段構建能夠多個項目的二進制文件構建在一個鏡像中發佈。

from debian as build-essential
arg APT_MIRROR
workdir /src

from build-essential as A
copy srcA .
run make

from build-essential as B
copy srcB .
run make

from alpine
copy --from=A binA .
copy --from=B binB .
cmd ...

4、C++ Docker鏡像製做

一、C++應用開發

HelloDocker.cpp文件以下:

#include <iostream>

int main()
{
    std::cout << "Hello, Docker!" << std::endl;
    return 1;
}

二、查找C++鏡像

docker search gcc
Hello Docker(三)——Docker鏡像製做
包含多種版本的gcc,包括嵌入式版本的gcc-arm-embedded-docker

三、下載C++鏡像

docker pull gcc
Hello Docker(三)——Docker鏡像製做

四、gcc鏡像查看

docker images

五、使用GCC鏡像製做鏡像

Dockerfile文件編寫:

FROM gcc:latest
RUN  mkdir -p /home/user/docker/src/HelloDocker
COPY HelloDocker.cpp /home/user/docker/src/HelloDocker
WORKDIR /home/user/docker/src/HelloDocker
RUN  g++ HelloDocker.cpp -o HelloDocker
CMD ["./HelloDocker"]

使用Dockerfile文件建立鏡像:
docker build -t hellodocker:v1 .
Hello Docker(三)——Docker鏡像製做
鏡像查看:
docker images
啓動鏡像:
docker run -d hellodocker:v1
容器運行狀況查看
docker ps

六、使用可執行程序製做鏡像

Dockerfile文件編寫:

FROM gcc:latest
RUN  mkdir -p /home/user/docker/HelloDocker
COPY HelloDocker /home/user/docker/HelloDocker
WORKDIR /home/user/docker/HelloDocker
#RUN  g++ HelloDocker.cpp -o HelloDocker
CMD ["./HelloDocker"]

構建鏡像:
docker build -t hellodocker:v1 .
啓動容器:
docker run -d hellodocker:v1

相關文章
相關標籤/搜索