Docker快速入门——Docker镜像制作
一、Dockerfile脚本
1、Dockerfile脚本简介
Dockerfile是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容就是描述该层应当如何构建。
Dockerfile文件示例如下:
## Dockerfile文件格式
# This dockerfile uses the ubuntu image
# VERSION 2 - EDITION 1
# Author: docker_user
# Command format: Instruction [arguments / command] ..
# 1、第一行必须指定 基础镜像信息
FROM centos
# 2、维护者信息
MAINTAINER docker_user [email protected]
# 3、镜像操作指令
RUN yum install -y nginx
# 4、容器启动执行指令
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/docs
2、FROM指令
FROM用于指定基础镜像,因此Dockerfile中FROM是必备指令,并且必须是第一条指令。
在Docker Store有非常多的高质量的官方镜像,有直接可用的服务类镜像,如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语法格式为:
FROM
FROM :
FROM :
FROM限制如下:
A、FROM必须是Dockerfile中第一条非注释命令
B、在一个Dockerfile文件中创建多个镜像时,FROM可以多次出现。只需在每个新命令FROM前,记录提交上次的镜像ID。
C、tag或digest是可选的,如果不使用这两个值时,会使用latest版本的基础镜像。
3、RUN指令
在镜像的构建过程中执行特定的命令,并生成一个中间镜像。格式:
#shell格式
RUN
#exec格式
RUN ["executable", "param1", "param2"]
RUN命令将在当前image中执行任意合法命令并提交执行结果。命令执行提交后,就会自动执行Dockerfile中的下一个指令。
RUN指令创建的中间镜像会被缓存,并会在下次构建中使用。如果不想使用缓存镜像,可以在构建时指定--no-cache参数,如:docker build --no-cache。
4、COPY指令
COPY指令语法格式:
COPY <源路径>... <目标路径>
COPY ["<源路径1>",... "<目标路径>"]
COPY 指令将从构建上下文目录中<源路径>的文件/目录复制到新的一层的镜像内的<目标路径>位置。
<源路径>可以是多个,甚至可以是通配符,其通配符规则要满足Go 的filepath.Match规则,如:
COPY hom* /mydir/
COPY hom?.txt /mydir/
目标路径可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用WORKDIR指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。
使用COPY指令,源文件的各种元数据都会保留,比如读、写、执行权限、文件变更时间等。
5、ADD指令
ADD指令在COPY基础上增加了一些功能,比如<源路径>可以是一个URL,Docker引擎会试图去下载URL链接的文件放到<目标路径>。
在构建镜像时,复制上下文中的文件到镜像内,格式:
ADD <源路径>... <目标路径>
ADD ["<源路径>",... "<目标路径>"]
如果Docker发现文件内容被改变,则后续指令都不会再使用缓存。
6、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"]
7、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 image -d时,-d指定的参数将被传递给入口点。也可以通过docker run --entrypoint重写 ENTRYPOINT 入口点。ENTRYPOINT ["/usr/bin/nginx"]
8、ENV指令
ENV指令用于设置环境变量,后续的指令可以直接使用。
ENV
ENV = =...
ENV示例如下:
ENV VERSION=1.0 DEBUG=on \
NAME="Happy Feet"
9、ARG指令
ARG指令用于指定传递给构建运行时的变量。
ARG [=]
通过ARG指定两个变量:
ARG site
ARG build_user=scorpio
上述指令指定site和build_user两个变量,其中build_user指定了默认值。使用docker build构建镜像时,可以通过
--build-arg =
选项参数来指定或重设置相应变量的值。docker build --build-arg site=www.baidu.com -t baidu/test .
build_user变量使用默认值scorpio。
10、VOLUME指令
VOLUME指令用于创建挂载点,即向基于所构建镜像创始的容器添加卷:VOLUME ["/data"]
一个卷可以存在于一个或多个容器的指定目录,该目录可以绕过联合文件系统,并具有以下功能:
A、卷可以容器间共享和重用
B、容器并不一定要和其它容器共享卷
C、修改卷后会立即生效
D、对卷的修改不会对镜像产生影响
E、卷会一直存在,直到没有任何容器在使用它
VOLUME可以将源代码、数据或其它内容添加到镜像中,而不提交到镜像中,并使多个容器间共享数据。
11、EXPOSE指令
EXPOSE指令为构建的镜像设置监听端口,使容器在运行时监听。格式如下:
EXPOSE [...]
EXPOSE指令并不会让容器监听host的端口,如果需要容器监听Host端口,需要在docker run时使用 -p、-P 参数来发布容器端口到host的某个端口上。
12、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参数覆盖构建时所设置的工作目录。
13、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参数来覆盖所指定的用户。
14、HEALTHCHECK指令
HEALTHCHECK [OPTIONS] CMD command
通过运行一个容器内部的命令来检测容器是否健康HEALTHCHECK NONE
关闭任何来自基础image的健康检测
options
--interval=DURATION (default: 30s)
--timeout=DURATION (default: 30s)
--retries=N (default: 3)
15、ONBUILD指令
ONBUILD指令用于设置镜像触发器。ONBUILD [INSTRUCTION]
当所构建的镜像被用作其它镜像的基础镜像,镜像中的触发器将会被触发。当镜像被使用时,可能需要做一些处理:
[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]
16、LABEL
LABEL指令用于为镜像添加元数据,元数以键值对的形式指定。
LABEL = = = ...
使用LABEL指定元数据时,一条LABEL指定可以指定一或多条元数据,指定多条元数据时不同元数据之间通过空格分隔。推荐将所有的元数据通过一条LABEL指令指定,以免生成过多的中间镜像。 LABEL version="1.0" description="hello world" by="scorpio"
LABEL指定的元数据可以通过docker inspect查看。
17、STOPSIGNAL
STOPSIGNAL指令用于设置停止容器所要发送的系统调用信号。STOPSIGNAL signal
所使用的信号必须是内核系统调用表中的合法的值,如:SIGKILL。
18、SHELL
SHELL指令用于设置执行命令(shell)所使用的的默认shell类型。SHELL ["executable", "parameters"]
SHELL在Windows环境下比较有用,Windows下通常会有cmd和 powershell两种shell,可以通过SHELL来指定所使用的shell类型。
二、Dockerfile构建镜像
1、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"]
2、同构的镜像构建
同构的镜像构建是指镜像构建环境与运行环境兼容。
同构镜像构建一般要求编译环境与镜像所使用的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创建一个容器appsourcedocker 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。
3、异构的镜像构建
异构镜像构建是指构建环境与运行环境不兼容。
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
三、Docker多阶段构建
1、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。
2、停在特定构建阶段
构建映像时,不一定需要构建整个Dockerfile文件的每个阶段,可以指定目标构建阶段。使用Dockerfile构建,在builder阶段停止:docker build --target builder -t builder:latest .
停在特定构建阶段非常适合的场景如下:
A、调试特定的构建阶段
B、在debug阶段,启用所有调试或工具,而在production阶段尽量精简
C、在testing阶段,应用程序将填充测试数据,但在production阶段则使用生产数据
3、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 ...
四、C++镜像制作
1、C++应用开发
HelloDocker.cpp文件如下:
#include
int main()
{
std::cout << "Hello, Docker!" << std::endl;
return 1;
}
2、查找C++镜像
docker search gcc
包含多种版本的gcc,包括嵌入式版本的gcc-arm-embedded-docker
3、下载C++镜像
4、gcc镜像查看
docker images
5、使用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 .
镜像查看:docker images
启动镜像:docker run -d hellodocker:v1
容器运行情况查看docker ps
6、使用可执行程序制作镜像
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