Docker 镜像(Image):就是一个只读的模板。
例如:一个镜像可以包含一个完整的 ubuntu 操作系统环境,里面仅安装了 Apache 或用户需要的其它应用程序。
镜像可以用来创建 Docker 容器。
Docker 提供了一个很简单的机制来创建镜像或者更新现有的镜像,用户甚至可以直接从其他人那里下载一个已经做好的镜像来直接使用。
]# docker search centos
INDEX NAME DESCRIPTION STARS OFFICIAL AUTOMATED
docker.io docker.io/centos The official build of CentOS. 2336 [OK]
docker.io docker.io/ansible/centos7-ansible Ansible on Centos7 74 [OK]
docker.io docker.io/jdeathe/centos-ssh CentOS-6 6.7 x86_64 / CentOS-7 7.2.1511 x8... 25 [OK]
docker.io docker.io/jdeathe/centos-ssh-apache-php CentOS-6 6.7 x86_64 / Apache / PHP / PHP M... 17 [OK]
docker.io docker.io/nimmis/java-centos This is docker images of CentOS 7 with dif... 12 [OK]
docker.io docker.io/million12/centos-supervisor Base CentOS-7 with supervisord launcher, h... 11 [OK]
docker.io docker.io/consol/centos-xfce-vnc Centos container with "headless" VNC sessi... 9 [OK]
docker.io docker.io/torusware/speedus-centos Always updated official CentOS docker imag... 8 [OK]
docker.io docker.io/nickistre/centos-lamp LAMP on centos setup 4 [OK]
… … 以下省略
在 docker 的镜像索引网站上面,镜像都是按照 “用户名/镜像名” 的方式来存储的。有一组比较特殊的镜像,比如 ubuntu、centos 这类基础镜像,经过官方的验证,值得信任,可以直接用镜像名来检索到。
]# docker pull centos
Using default tag: latest
Trying to pull repository docker.io/library/centos ... latest: Pulling from library/centos
1544084fad81: Pull complete
df0fc3863fbc: Pull complete
a3d54b467fad: Pull complete
a65193109361: Pull complete
Digest: sha256:1a62cd7c773dd5c6cf08e2e28596f6fcc99bd97e38c9b324163e0da90ed27562
Status: Downloaded newer image for docker.io/centos:latest
]# docker pull centos:6.8
6.8: Pulling from library/centos
b6d7b2ebc0a7: Pull complete
Digest: sha256:fd9058a6149809b2f4725bb4461294ceebb59e587435f3509c2c8dcc9ee1d5b4
Status: Downloaded newer image for centos:6.8
如果下载时不指定来源,默认从 Docker Hub 仓库中的官方源下载镜像,如果不指定 tag 内容,则会默认下载 tag 标签为 latest 的镜像
]# docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
docker.io/centos latest a65193109361 12 days ago 196.7 MB
使用下载的镜像,启动容器
]# docker run -t -i centos:6.8 /bin/bash
在启动的容器中,安装 ntp
[root@6dafb63fb2d9 /]# yum install ntp
退出容器
[root@6dafb63fb2d9 /]# exit
通过 docker commit 命令来将我们对容器的修改保存为新的镜像
语法:
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
-m 用来指定提交的说明信息
-a 指定更新的用户信息
]# docker commit -m "Added ntp(commit)" -a "yuankewen <15150551620@139.com>" 6dafb63fb2d9 yue/centos:v1
sha256:d23ea4e529b777f3f65df8bf018a9dc7adf86363748e51cd7e9a4556737f5cfb
查看新创建的容器
]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
yue/centos v1 d23ea4e529b7 6 minutes ago 334.2 MB
使用 docker commit 来扩展一个镜像比较简单,但是不方便在一个团队中分享。我们可以使用 docker build 来创建一个新的镜像。
下面我们通过一个 Dockerfile 文件,来完成上面同样的操作,即在 centos:6.8 镜像的基础上,添加一个 ntp 服务
编写 Dockerfile 文件
mkdir mkntp
cd mkntp/
vim Dockerfile
FROM centos:6.8
MAINTAINER "yuankewen <15150551620@139.com>"
RUN yum -y install ntp
通过 Dockerfile 文件生成新的镜像
语法:
docker build [OPTIONS] PATH | URL | -
-t 指定新的镜像的名字和标记。
. 是 Dockerfile 所在的路径(当前目录),也可以替换为一个具体的 Dockerfile 的路径。
]# docker build -t 'yue/centos:v2' .
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM centos:6.8
---> a374fd62f97f
Step 2 : MAINTAINER "yuankewen <15150551620@139.com>"
---> Using cache
---> 9ae202f1a68f
Step 3 : RUN yum -y install ntp
---> Running in 7efbf2be8f7a
# ... 中间 yum 安装时的输出省略 ...
Complete!
---> 5b91fa7247cd
Removing intermediate container 7efbf2be8f7a
Successfully built 5b91fa7247cd
build 进程所执行操作:
此外,还可以利用 ADD 命令复制本地文件到镜像;用 EXPOSE 命令来向外部开放端口;用 CMD 命令来描述容器启动后运行的程序等。
注:
具体 Dockerfile 所使用到的命令,请参见 附A:Dockerfile 的命令
查看新创建的容器
]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
yue/centos v2 5b91fa7247cd 21 minutes ago 287.2 MB
yue/centos v1 d23ea4e529b7 3 hours ago 334.2 MB
将镜像从本地镜像库导出到本地文件
语法:
docker save [OPTIONS] IMAGE [IMAGE...]
-o 用来将标准输出,写入一个文件
]# docker save -o centos_6.8.tar centos:6.8
]# ll
total 197480
-rw------- 1 root root 202211840 Aug 1 16:18 centos_6.8.tar
将镜像从本地文件系统导入本地镜像库
语法:
docker load [OPTIONS]
-i --input 从一个 tar 架构的文件读取
]# docker load -i centos_6.8.tar
c027abff19e1: Loading layer [==================================================>] 202.2 MB/202.2 MB
]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
centos 6.8 a374fd62f97f 4 weeks ago 194.5 MB
docker 中删除 images 的命令是 docker rmi,但有时候执行此命令并不能删除 images
]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
yuebaibai/centos v5 36877f7460ff 6 days ago 924.9 MB
yuebaibai/centos v4 20621c920fcc 6 days ago 924.9 MB
yuebaibai/centos v3 7c82b53eccbb 6 days ago 924.9 MB
yuebaibai/centos v2 675c9f34ee8b 6 days ago 924.9 MB
yuebaibai/centos v1 8a30f062c71c 6 days ago 924.9 MB
docker.io/centos 6.8 a374fd62f97f 4 weeks ago 194.5 MB
]# docker rmi 36877f7460ff
Failed to remove image (36877f7460ff): Error response from daemon: conflict: unable to delete 36877f7460ff (must be forced) - image is being used by stopped container 5274568a3492
报错很明确:不能删除这个镜像的原因是,这个镜像正在被一个已停止的容器使用。此时,有两种处理方法:
]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5274568a3492 36877f7460ff "/bin/sh -c '/etc/ini" 6 days ago Exited (137) 6 days ago adoring_mahavira
]# docker rm 5274568a3492
5274568a3492
]# docker rmi -f 36877f7460ff
Untagged: yuebaibai/centos:v5
Deleted: sha256:36877f7460ff515bd019298b665b5fb8f0f184ec56c531298b35a91ac6d5d40e
Deleted: sha256:560793c708c59d39943a67db69cb8d0570c3874e691ae81a466968a870982044
Deleted: sha256:53527fb3274e15ff8bff4a16c5a3498559afe6346b549784dc88b9d9b2bac61e
Deleted: sha256:8d1b4bb20450952c0723347e1530f22186e83baf9b82bfb8f7a6f6793e5d9429
注1:
推荐使用第一种方式
注2:
可以使用以下命令将所有已停止但未提交的容器删除
]# docker rm $(docker ps -a |grep Exited |awk '{print $1}')
语法:
docker history [OPTIONS] IMAGE
--no-trunc 对输出不进行删节
]# docker history --no-trunc centos:6.8
IMAGE CREATED CREATED BY SIZE COMMENT
sha256:a374fd62f97f4ba3c639da09a93e43ae84eb2ee19ea1c4c6d383deceaa6efc84 4 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0 B
<missing> 4 weeks ago /bin/sh -c #(nop) LABEL name=CentOS Base Image vendor=CentOS license=GPLv2 build-date=2016-06-02 0 B
<missing> 4 weeks ago /bin/sh -c #(nop) ADD file:beeefa390db7bc02afe6a885fecdd835435d30121252a0caf1f9215bd8e37fc2 in / 194.5 MB
<missing> 4 weeks ago /bin/sh -c #(nop) MAINTAINER The CentOS Project <cloud-ops@centos.org> 0 B
语法:
docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...]
语法:
docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]
基本结构:
注1:
建议每次都新建一个空目录来放置新的 Dockerfile 文件
注2:
Dockerfile 文件使用 # 作为注释
Dockerfile 的指令:
指令格式:
INSTRUCTION arguments
注:
Dockerfile 中 instruction 不区分大小写,但默认约定使用大写表示。
arguments 对大小写敏感。
FROM
FROM <image>
或
FROM <image>:<tag>
每个 Dockerfile 文件都必须以 FROM 指令开始,用于指定新的镜像基于哪个基础镜像创建。
例:
FROM docker.io/centos:6.8
MAINTAINER
MAINTAINER <name>
用于指定镜像创建者的信息。
例:
MAINTAINER yuankewen "15150551620@139.com"
WORKDIR
WORKDIR /path/to/workdir
为后续的 RUN、CMD、ENTRYPOINT 指令配置工作目录。
可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。例如:
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
则输出结果为:
/a/b/c
USER
USER daemon
指定运行容器时的用户名或 UID,后续的 RUN 也会使用指定用户。
当服务不需要管理员权限时,可以通过该命令指定运行用户。并且可以在之前创建所需要的用户,例如:
RUN groupadd -r postgres && useradd -r -g postgres postgres
ENV
ENV <key> <value>
指定一个环境变量,会被后续 RUN 指令使用,并在容器运行时保持。例如
ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH
RUN
RUN <command>
每条 RUN 指令将在当前镜像基础上执行指定命令,并提交为新的镜像。当命令较长时可以使用 \ 来换行。
例:
RUN sh /root/mk.sh
CMD
CMD ["executable","param1","param2"] 使用 exec 执行,推荐方式;
或
CMD command param1 param2 在 /bin/sh 中执行,提供给需要交互的应用;
或
CMD ["param1","param2"] 提供给 ENTRYPOINT 的默认参数;
指定启动容器时执行的命令,每个 Dockerfile 只能有一条 CMD 命令。如果指定了多条命令,只有最后一条会被执行。
如果用户启动容器时候指定了运行的命令,则会覆盖掉 CMD 指定的命令。
ENTRYPOINT
ENTRYPOINT ["executable", "param1", "param2"]
或
ENTRYPOINT command param1 param2(shell中执行)。
配置容器启动后执行的命令,并且不可被 docker run 提供的参数覆盖。
每个 Dockerfile 中只能有一个 ENTRYPOINT,当指定多个时,只有最后一个起效。
注:
三者的区别请参见附B: RUN、CMD、ENTRYPOINT 三者的区别
ADD
ADD <src> <dest>
该命令将复制指定的 到容器中的 。 其中 可以是Dockerfile所在目录的一个相对路径;也可以是一个 URL;还可以是一个 tar 文件(自动解压为目录)。
COPY
COPY <src> <dest>
复制本地主机的 (为 Dockerfile 所在目录的相对路径)到容器中的 。
当使用本地目录为源目录时,推荐使用 COPY。
EXPOSE
EXPOSE <port> [<port>...]
告诉 Docker 服务端容器暴露的端口号,供互联系统使用。在启动容器时需要通过 -P,Docker 主机会自动分配一个端口转发到指定的端口。
VOLUME
VOLUME ["/data"]。
创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等。
ONBUILD
ONBUILD [INSTRUCTION]
配置当所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。
例如,Dockerfile 使用如下的内容创建了镜像 image-A。
[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]
如果基于 image-A 创建新的镜像时,新的Dockerfile中使用 FROM image-A指定基础镜像时,会自动执行 ONBUILD 指令内容,等价于在后面添加了两条指令。
FROM image-A
#Automatically run the following
ADD . /app/src
RUN /usr/local/bin/python-build --dir /app/src
使用 ONBUILD 指令的镜像,推荐在标签中注明,例如 ruby:1.9-onbuild。
RUN 与 CMD、ENTRYPOINT 的区别:
三者的执行时间不太一样。
RUN 是 在Build 时执行的,先于 CMD 和 ENTRYPOINT。Build 完成了,RUN 也运行完成了。而 CMD 或者 ENTRYPOINT 在容器运行进才会执行。
ENTRYPOINT 和 CMD 的区别:
在于执行 docker run 时参数传递方式,CMD 指定的命令可以被 docker run 传递的命令覆盖,例如,如果用 CMD 指定:
CMD ["echo"]
然后运行
docker run CONTAINER_NAME echo foo
那么 CMD 里指定的 echo 会被新指定的 echo 覆盖,所以最终相当于运行 echo foo,所以最终打印出的结果就是:
foo
而 ENTRYPOINT 会把容器名后面的所有内容都当成参数传递给其指定的命令(不会对命令覆盖),比如:
ENTRYPOINT ["echo"]
然后运行
docker run CONTAINER_NAME echo foo
则 CONTAINER_NAME 后面的 echo foo 都作为参数传递给 ENTRYPOING 里指定的 echo 命令了,所以相当于执行了
echo "echo foo"
最终打印出的结果就是:
echo foo
另外,在 Dockerfile 中,ENTRYPOINT 指定的参数比运行 docker run 时指定的参数更靠前,比如:
ENTRYPOINT ["echo", "foo"]
执行
docker run CONTAINER_NAME bar
相当于执行了:
echo foo bar
打印出的结果就是:
foo bar
使用缓存
Dockerfile 的每条指令都会将结果提交为新的镜像。下一条指令基于上一条指令的镜像进行构建。如果一个镜像拥有相同的父镜像和指令(除了 ADD ),Docker将会使用镜像而不是执行该指令,即缓存。
因此,为了有效的利用缓存,尽量保持 Dockerfile 一致,并且尽量在末尾修改:
FROM ubuntu
MAINTAINER author <somebody@company.com>
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe"
RUN apt-get update
RUN apt-get upgrade -y
更改 MAINTAINER 指令会使Docker强制执行 run 指令来更新apt,而不是使用缓存。
如不希望使用缓存,在执行 docker build 的时候加上参数 --no-cache=true 。
Docker 匹配镜像决定是否使用缓存的规则如下:
从缓存中存在的基础镜像开始,比较所有子镜像,检查它们构建的指令是否和当前的是否完全一致。如果不一致则缓存不匹配。
多数情况中,使用其中一个子镜像来比较 Dockerfile 中的指令是足够的。然而,特定的指令需要做更多的判断。
ADD COPY 指令中,将要添加到镜像中的文件也要被检查。通常是检查文件的校验和(checksum)。
缓存匹配检查并不检查容器中的文件。例如,当使用 RUN apt-get -y update 命令更新了容器中的文件,并不会被缓存检查策略作为缓存匹配的依据。
使用标签
除非是在用 Docker 做实验,否则你应当通过 -t 选项来 docker build 新的镜像以便于标记构建的镜像。一个简单可读的标签可以帮助管理每个创建的镜像。
docker build -t="tuxknight/luckypython"
Note
始终通过 -t 标记来构建镜像。
公开端口
Docker 的核心概念是可重复和可移植,镜像应该可以运行在任何主机上并运行尽可能多的次数。在 Dockerfile 中你可以映射私有和公有端口,但永远不要通过 Dockerfile 映射公有端口。这样运行多个镜像的情况下会出现端口冲突的问题。
EXPOSE 80:8080 # 80映射到host的8080,不提倡这种用法
EXPOSE 80 # 80会被docker随机映射一个端口
CMD ENTRYPOINT语法
CMD 和 ENTRYPOINT 支持两种语法:
CMD /bin/echo
CMD ["/bin/echo"]
在第一种方式下,Docker会在命令前加上 /bin/sh -c ,可能会导致一些意想不到的问题。第二种方式下 CMD ENTRYPOINT 是一个数组,执行的命令完全和你期待的一样。
容器是短暂的
容器模型是进程而不是机器,不需要开机初始化。在需要时运行,不需要时停止,能够删除后重建,并且配置和启动的最小化。
.dockerignore 文件
在 docker build 的时候,忽略部分无用的文件和目录可以提高构建的速度。
不要在构建中升级版本
不在容器中更新,更新交给基础镜像来处理。
每个容器只运行一个进程
一个容器只运行一个进程。容器起到了隔离应用,隔离数据的作用,不同的应用运行在不同的容器让集群的纵向扩展以及容器的复用都变的更加简单。
最小化层数
需要掌握号 Dockerfile 的可读性和文件系统层数之间的平衡。控制文件系统层数的时候会降低 Dockerfile 的可读性。而 Dockerfile 可读性高的时候,往往会导致更多的文件系统层数。
使用小型基础镜像
基础镜像足够小并且没有包含任何不需要的包。
使用特定标签
Dockerfile 中 FROM 应始终包含依赖的基础镜像的完整仓库名和标签,如使用 FROM debian:jessie 而不是 FROM debian 。
多行参数顺序
apt-get update 应与 apt-get install 组合,采取 \ 进行多行安装。同时需要注意按照字母表顺序排序避免出现重复。
FROM debian:jessie
RUN apt-get update && apt-get install -y \
build-essential\
git \
make \
python \
RUN dpkg-reconfigure locales && \
locale-gen C.UTF-8 && \
/usr/sbin/update-locale LANG=C.UTF-8
ENV LC_ALL C.UTF-8
ADD 和 COPY
虽然 ADD 与 COPY 功能类似,但推荐使用 COPY 。 COPY 只支持基本的文件拷贝功能,更加的可控。而 ADD 具有更多特定,比如tar文件自动提取,支持URL。 通常需要提取tarball中的文件到容器的时候才会用到 ADD 。
如果在Dockerfile中使用多个文件,每个文件应使用单独的 COPY 指令。这样,只有出现文件变化的指令才会不使用缓存。
为了控制镜像的大小,不建议使用 ADD 指令获取URL文件。正确的做法是在 RUN 指令中使用 wget 或 curl 来获取文件,并且在文件不需要的时候删除文件。
VOLUME
VOLUME 指令应当暴露出数据库的存储位置,配置文件的存储以及容器中创建的文件或目录。由于容器结束后并不保存任何更改,你应该把所有数据通过 VOLUME 保存到host中。
USER
如果服务不需要特权来运行,使用 USER 指令切换到非root用户。使用 RUN groupadd -r mysql && useradd -r -g mysql mysql 之后用 USER mysql 切换用户
要避免使用 sudo 来提升权限,因为它带来的问题远比它能解决的问题要多。如果你确实需要这样的特性,那么可以选择使用 gosu 。
最后,不要反复的切换用户。减少不必要的layers。
WORKDIR
WORKDIR 的路径始终使用绝对路径可以保证指令的准确和可靠。 同时,使用 WORKDIR 来替代 RUN cd … && do-something 这样难以维护的指令。