【Docker】制作Docker镜像

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值