commit方式
我们现在有一个ubuntu的镜像,想制作一个带有python环境的镜像,我们可以这么实现,比如
我们想创建一个带有python环境的镜像。
1.通过docker run 命令创建并启动一个容器
2.进入容器,安装python环境
3.通过 docker commit 命令提交一个容器为镜像:
docker commit [options] <容器ID或容器名> [<仓库名>[:<标签>]]
options:
-a “like < like@qq.com >”
-m “安装了python3.7”
4.docker images 命令可以查看到刚创建的镜像
以上步骤就是在旧镜像上新加了一层(python环境)变成新镜像,体现了镜像的多层存储结构。
但是不推荐commit创建镜像,原因我引用了一段话:
慎用 docker commit
使用 docker commit 命令虽然可以比较直观的帮助理解镜像分层存储的概念,但是实际环境中并不会这样使用。
首先,如果仔细观察之前的 docker diff webserver 的结果,你会发现除了真正想要修改的 /usr/share/nginx/html/index.html 文件外,由于命令的执行,还有很多文件被改动或添加了。这还仅仅是最简单的操作,如果是安装软件包、编译构建,那会有大量的无关内容被添加进来,如果不小心清理,将会导致镜像极为臃肿。
此外,使用 docker commit 意味着所有对镜像的操作都是黑箱操作,生成的镜像也被称为 黑箱镜像,换句话说,就是除了制作镜像的人知道执行过什么命令、怎么生成的镜像,别人根本无从得知。而且,即使是这个制作镜像的人,过一段时间后也无法记清具体在操作的。虽然 docker diff 或许可以告诉得到一些线索,但是远远不到可以确保生成一致镜像的地步。这种黑箱镜像的维护工作是非常痛苦的。
而且,回顾之前提及的镜像所使用的分层存储的概念,除当前层外,之前的每一层都是不会发生改变的,换句话说,任何修改的结果仅仅是在当前层进行标记、添加、修改,而不会改动上一层。如果使用 docker commit 制作镜像,以及后期修改的话,每一次修改都会让镜像更加臃肿一次,所删除的上一层的东西并不会丢失,会一直如影随形的跟着这个镜像,即使根本无法访问到。这会让镜像更加臃肿。
简言之,慎用commit的原因有:
1.在旧的镜像上操作创建新的镜像,可能会使镜像非常臃肿庞大;
2.黑箱操作,无法知道这个镜像是怎么生成的。
所以我们推荐下一种方式:
Dockerfile
Dockerfile是一个文本文件,里面包含着多条指令,用来描述每一层如何构建。
docker镜像的多层存储结构
Docker镜像使用 Union FS 技术,设计成分层存储的架构,也就是说Docker镜像是由多层文件系统联合构成,因此,在构建镜像时,会一层一层的构建,每一层的构建都是在前一层的基础之上,每层构建之后就不会改变,即使在后一层删除前一层中的文件也只是一个标记的删除,实际文件还在,因此在构建docker镜像时,每一层一定要在构建时就做好该层多余文件的删除。
dockerfile的语法
0、说明信息
可以在dockerfile中写一些说明信息,便于更好的阅读和维护dockerfile。
- 注释:单独一行,用 # 加注释信息
- 换行:每个命令的末尾,通过反斜杠来表示换行,更便于阅读
1.FROM
from是dockerfile的必备指令,也必须是第一条指令,用来指定我们制作镜像依赖的基础镜像,这些基础镜像可以是Docker Hub上的官方镜像,比如服务类镜像nginx、redis、mysql,语言类镜像python、golang,或是操作系统镜像ubunt、centos等;
此外,还有一种特殊的镜像scratch,是一种并不存在的虚拟镜像,是一个空白镜像。
2.RUN
用来执行命令,有两种格式:
- shell格式:就像直接在命令行输入命令一样,run后面跟一条完整的命令;
- exec格式:run 可执行文件 参数1 参数2 …
举个例子,要建立一个redis镜像:
FROM debian:stretch
RUN apt-get update
RUN apt-get install -y gcc libc6-dev make wget
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install
这么做的缺点是:
-
每个run都会建立一层,并在此基础上执行下一次命令,这样7个run创建了7层,Union FS有最大层数的限制;
-
而且这么做会在镜像中残留下很多软件安装包等没用的东西,使镜像臃肿,部署变慢。
改进:
FROM debian:stretch
RUN buildDeps='gcc libc6-dev make wget' \
&& apt-get update \
&& apt-get install -y $buildDeps \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
&& mkdir -p /usr/src/redis \
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
&& make -C /usr/src/redis \
&& make -C /usr/src/redis install \
&& rm -rf /var/lib/apt/lists/* \
&& rm redis.tar.gz \
&& rm -r /usr/src/redis \
&& apt-get purge -y --auto-remove $buildDeps
一个run,使7层合为1层,每条命令用&&连接起来,通过 反斜杠 体现每行命令的换行;
在命令中也将没必要的安装包、编译环境等删除掉。
3.COPY
COPY [--chown=<user>:<group>] <源路径>... <目标路径>
该命令表示:
-
从构建上下文目录中,源路径的文件或目录复制到镜像内目标位置的路径
-
源路径可以是一个或多个,也可以带有通配符
-
目标路径可以不存在,命令会自动创建
COPY hom* /mydir/
COPY hom?.txt /mydir/
4.ADD
和COPY命令类似,一般在如下两个场景下使用,其他地方就不要用了。
- 本地文件是tar格式,可以在copy的同时解包。
- 本地文件可以是个url,从而从远程拉取。
5.WORKDIR
WORKDIR <工作目录路径>
执行这条命令后,下面构建的各层的当前目录就会改变。
6.VOLUME
定义匿名卷,对于容器中有需要保存数据的操作,应该将数据保存在卷中,因此可以在dockerfile中提前指定该镜像生成的容器挂在的卷的路径。
VOLUME /data
也可以在启动容器的时候指定容器挂在的卷,这个操作会覆盖dockerfile的设置。
docker run -d -v mydata:/data xxxx
该命令用mydata覆盖了data。
Dockerfile模板
FROM base_image:tag # 引用基础镜像 *必要*
ARG arg_key[=default_value1] # 声明变量
ENV env_key=value2 # 声明环境变量
# 构建几乎不变的部分,例如整体的目录结构,build 时依赖的文件和工具包等
COPY src dst
RUN command1 && command2 ...
WORKDIR /path/to/work/dir # 设置工作目录
# 构建较少变动的部分,例如应用的依赖的文件、依赖的包等
COPY src dst
RUN command3 && command4 ...
# 构建经常变动的部分,例如应用的编译生成
COPY src dst
RUN command5 && command6 ...
# 容器入口 *必要*
ENTRYPOINT ["/entry.app"] # 指定容器启动时默认执行的命令
CMD ["--options"] # 指定容器启动时默认命令的默认参数
Dockerfile的multi-stage build 机制
Dockerfile最佳实践
参考:http://dockone.io/article/9658
1.如果镜像很复杂,需要构建很多层,尽量分成基础镜像和应用镜像,基础镜像的目的是可以给多个应用使用,比如构建爬虫镜像,可以先构建python镜像,再构建爬虫镜像,而不是直接用ubuntu镜像。
2.Docker有build cache机制(多层构建),即在build镜像时,从已缓存的父镜像开始,将Dockerfile的下一条指令和这个父镜像当前的所有子镜像比较,看是否有同样的指令,有则直接使用缓存(存在同样的层),因此,在编写Dockerfile时,尽量把变动最不频繁的写在最前面,比如安装一些环境、库之类的,变动频繁的写在后面。
3.因为每次RUN 都会构建新的一层,所以建议使用&&来连接多个 RUN 命令。
4.在镜像中apt-get的官方推荐方式
RUN apt-get update && apt-get install -y \
curl \
nginx=1.16.* \
&& rm -rf /var/lib/apt/lists/*
5.ENV命令也会产生中间层,解决办法是通过RUN命令的export声明环境变量
FROM alpine
RUN export ADMIN_USER="mark" \
&& echo $ADMIN_USER > ./mark \
&& unset ADMIN_USER
CMD sh
6.除了复制需要解压的tar文件和拉取链接用ADD,其他复制的形式一律用COPY命令,且本地路径(加反斜线只复制路径下的所有文件)与容器路径末尾都加反斜线。
7.CMD和ENTRYPOINT单独使用都是执行命令,一起使用使CMD后的内容是ENTRYPOINT的参数,推荐使用的书写形式为exec形式,即 ENTRYPOINT [“entry.app”, “arg”] ,因为ENTRYPOINT entry.app arg会启动额外shell进程;
此外,在含有ENTRYPOINT的Dockerfile中,docker run命令时末尾直接加参数的话,参数会变成ENTRYPOINT 命令的参数,即覆盖掉Dockerfile中的CMD(如果有的话)
用已有的dockerfile构建镜像
docker build [OPTIONS] <PATH | URL | ->
- OPTIONS:
–tag, -t: 镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构建中为一个镜像设置多个标签。
–network: 默认 default。在构建期间设置RUN指令的网络模式
–ulimit :Ulimit配置。
–rm :设置镜像成功后删除中间容器;
–no-cache :创建镜像的过程不使用缓存;
–memory-swap :设置Swap的最大值为内存+swap,"-1"表示不限swap;
-m :设置内存最大值;
–isolation :使用容器隔离技术;
-f :指定要使用的Dockerfile路径;
–cpu-shares :设置 cpu 使用权重;
- <PATH | URL | ->:用来指定镜像构建上下文(Context),一个点表示当前目录,具体一点,比如dockerfile中有COPY命令,COPY的源路径。
加速build镜像
默认在build的时候会从Docker Hub拉取所需的基础镜像,但是Docker Hub在国内可能非常慢,
我们可以使用国内的镜像加速服务,比如:
Azure 中国镜像 https://dockerhub.azk8s.cn
阿里云加速器(需登录账号获取)
七牛云加速器 https://reg-mirror.qiniu.com
为了防止镜像服务宕机,我们可以配置多个服务:
cd /etc/docker
sudo touch daemon.json
chmod 777 daemon.json
vim daemon.json
# 加入以下内容
{
"registry-mirrors": [
"https://registry.docker-cn.com",
"http://hub-mirror.c.163.com",
"https://docker.mirrors.ustc.edu.cn",
"https://dockerhub.azk8s.cn",
"https://reg-mirror.qiniu.com"
"https://dockerhub.azk8s.cn",
"https://reg-mirror.qiniu.com"
]
}
esc -> :wq -> enter
或者使用DaoCloud,直接在终端执行
curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://f1361db2.m.daocloud.io
最后:
# 重启docker
sudo systemctl daemon-reload
sudo systemctl restart docker
# 查看是否生效
docker info
学习资料
http://dockone.io/article/9658