7.4 Docker:关于镜像

image0


一、镜像的基本内容

# 查看本地已下载的镜像
$ docker images

# 下载镜像
$ docker pull hello-world

# 查看镜像信息
$ docker images hello-world

当我们下载镜像并执行一些命令的时候,docker实际上是记录下来的。

所有的命令都被转化成了bash命令存储在文件里。 这也就解释了为什么hello-world文件才1.5k

二、什么是Dockerfile

如何构建镜像 我们可以从简单的分析:hello-world 每个镜像的创建都需要有一个Dockerfile,从docker hub下载的image可以在这里搜索dockerfile,包括后面我们自己构建也是一样。

现在分析一下hello-world的[Dockerfile]

FROM scratch  # 从0开始
COPY hello /  # 将镜像里的hello可执行文件拷贝到容器根目录
CMD ["/hello"] # 当容器启动后执行hello文件内容

而hello的内容,也很简单就是打印这样一段内容

image1

三、理解base镜像

1. 不依赖其他镜像,从 scratch 构建。
2. 其他镜像可以之为基础进行扩展。

这个怎么理解呢?很重要。 比如说,我们下载的CentOS镜像

image2

你一定很纳闷。怎么才200M不到?我们平时下载的都几个G的。

这里就来解答一下 Linux 操作系统由内核空间和用户空间组成。

内核空间是 kernel,Linux 刚启动时会加载 bootfs 文件系统,之后 bootfs 会被卸载掉。
用户空间的文件系统是 rootfs,包含我们熟悉的 /dev, /proc, /bin 等目录。

对于 base 镜像来说,【底层直接用 Host 的 kernel】,自己只需要提供 rootfs 就行了。
而对于一个精简的 OS,rootfs 可以很小,只需要包括最基本的命令、工具和程序库就可以了。相比其他 Linux 发行版,CentOS 的 rootfs 已经算臃肿的了,alpine 还不到 10MB。

我们平时安装的 CentOS 除了 rootfs 还会选装很多软件、服务、图形桌面等,需要好几个 GB 就不足为奇了。

照样来分析一下,Dockerfile:地址

FROM scratch
MAINTAINER The CentOS Project <cloud-ops@centos.org>
ADD centos-7.2.1511-docker.tar.xz /
CMD ["/bin/bash"]

对于容器来说,他底层使用的Host的kernel,所以假如我们的容器是CentOS的版本(内核是3.x),当我们把容器放到Ubuntu(内核是4.x)上使用是,他实际上使用的是Ubuntu的内核。 image3

容器只能使用 Host 的 kernel,并且不能修改。 所有容器都共用 host 的 kernel,在容器中没办法对 kernel 升级。如果容器对 kernel 版本有要求(比如应用只能在某个 kernel 版本下运行),则不建议用容器,这种场景虚拟机可能更合适。

四、镜像的分层结构

从上面我们知道,每个软件都信赖存活于一个base镜像,而每个base镜像都200M,是不是意味着我们每次都要重复下载base镜像呢?

肯定不是,这里就要说到镜像的分层结构,它使得多个容器,可以共用一个base镜像。 那么就产生一个问题,不同容器要是修改同一文件呢?该怎么办?会不会互相影响?

答案是不会。

因为,镜像层的文件都是只读的,如果容器层需要对文件进行操作,都要将文件进行拷贝,然后编辑,而容器会对这份文件进行管理,而这份文件只存在于当前容器层。不会对镜像层的文件产生影响,如果有新的容器以这个容器层做为基础镜像,那么这个文件将会被容器层所检索到。

添加文件
在容器中创建文件时,新文件被添加到容器层中。

读取文件
在容器中读取某个文件时,Docker 会从上往下依次在各镜像层中查找此文件。一旦找到,打开并读入内存。

修改文件
在容器中修改已存在的文件时,Docker 会从上往下依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层,然后修改之。

删除文件
在容器中删除文件时,Docker 也是从上往下依次在镜像层中查找此文件。找到后,会在容器层中记录下此删除操作。

五、镜像的缓存特性

为了理解这个我们来构建个自己的容器。

第一步,先下载base镜像:centos

$ docker pull centos

第二步,编辑Dockerfile

FROM centos
RUN yum install -y tree

第三步,创建容器,并命名为centos-tree

$ docker build -t centos-tree .

image4 第四步,在原始的Dockerfile上添加内容

FROM centos
RUN yum install -y tree
COPY testfile /

第五步,再创建一个容器

docker build -t centos-tree-testfile .

image5

注意点,镜像的缓存要求构建的命令顺序要严格一致,只要有一行命令不同,缓存就会失效,即使最后的容器内容完全一致,也无法使用缓存。 比如,我们把第二次的Dockerfile改成如下,也是需要重新构建

FROM centos
COPY testfile /
RUN yum install -y tree

六、如何调试Dockerfile

当我们build一个新的镜像,也许会有很多的很复杂的步骤,如果一次性build很有可能遇到各种意外情况,导致build失败,这时候,我们就需要进行调试,找出失败原因,修改Dockerfile,最终成功。

那么如何调试?在我们build镜像的时候,每一步都会有一个镜像id,通过这个id我们就可以进入该层镜像。 image6 先来看看第二步的结果,/目录下没有testfile文件

$ docker run -it e316b390cf2a

再来看看第三步,/目录下已经有testfile文件 image7

友情提示

进入容器后,如何退出
1. exit   # 退出并关闭容器
2. ctl+d  # 退出并关闭容器
3. ctl+p+q  # 退出不关闭容器

退出后,可以使用 docker kill <id> 手动关闭

七、Dockerfile常用指令

FROM
指定 base 镜像。

MAINTAINER
设置镜像的作者,可以是任意字符串。

COPY
将文件从 build context 复制到镜像。

COPY 支持两种形式:
    COPY src dest
    COPY ["src", "dest"]

    注意:src 只能指定 build context 中的文件或目录。

ADD
与 COPY 类似,从 build context 复制文件到镜像。不同的是,如果 src 是归档文件(tar, zip, tgz, xz 等),文件会被自动解压到 dest。

ENV
设置环境变量,环境变量可被后面的指令使用。例如:

    ENV MY_VERSION 1.3
    RUN apt-get install -y mypackage=$MY_VERSION


EXPOSE
指定容器中的进程会监听某个端口,Docker 可以将该端口暴露出来。我们会在容器网络部分详细讨论。

VOLUME
将文件或目录声明为 volume。我们会在容器存储部分详细讨论。

WORKDIR
为后面的 RUN, CMD, ENTRYPOINT, ADD 或 COPY 指令设置镜像中的当前工作目录,当我们进入容器时,即为工作目录。

RUN
在容器中运行指定的命令。

CMD
容器启动时运行指定的命令。
Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效。CMD 可以被 docker run 之后的参数替换。

ENTRYPOINT
设置容器启动时运行的命令。
Dockerfile 中可以有多个 ENTRYPOINT 指令,但只有最后一个生效。CMD 或 docker run 之后的参数会被当做参数传递给 ENTRYPOINT。

八、理解CMD和ENTRYPOINT

这两个很容易混淆,但是又很重要。所以单独拿出来讲。

相同的点:都是容器启动后执行。相当于开机自启 不同的点:一个会被覆盖,一个不会被覆盖

CMD:两个功能

1. 执行一些命令
  - 若run时,没有指定其他命令,则会在启动容器的时候默认执行
  - 若run时,指定了其他命令,则该命令会被覆盖,不被执行
  - exec和bash格式都可以使用
    exec:CMD ["/bin/echo", "hello world"]
    bash:CMD echo "hello world"

image8

2. 给ENTRYPOINT传递参数
  - 若run时,指定了参数,CMD传递的参数同样被覆盖,而ENTRYPOINT永远不会被覆盖。
  - 注意:如果要传递参数,必须使用exec格式,诸如CMD [""]

image9

推荐用法

1. CMD:若想做开机启动命令,两种格式都可以;若想传递参数,则必须使用exec格式
2. ENTRYPOINT:同样也接受两种格式,但是请使用exec格式,即方便传递参数,实现定制,而且不容易出错。

九、上传镜像

为了方便多台Host,使用同一Image,有如下三种方法。

1. 在多台Host上,使用同一Dockerfile创建镜像;
2. 上传至Docker Hub,在需要的Host上下载
3. 搭建本地私有仓库。

使用Docker Hub

1. 需要联网,而且速度不快
2. 任何人都可以访问,不安全,可能不适合企业

使用方法

1. 先到 Docker Hub 上注册一个账号
2. 在 Docker Host 上登录
   $ docker login -u username
   输入密码,登陆。
3. 修改镜像名字和tag:格式:[username]/name:tag
   $ docker tag hello wangbm/hello:v0.1
4. 上传
   $ docker push wangbm/hello:v0.1
5. 下载,在其他Host
   $ docker pull wangbm/hello:v0.1
6. 若要删除,只能在网上删除,https://hub.docker.com

搭建本地仓库步骤

1. 下载并启动Registry容器
   $ docker -d -p 5000:5000 -v /myregistry:/var/lib/registry registry:2
   参数说明
   -d  后台运行
   -p  将本机5000端口和容器的5000端口绑定
   -v  将容器内的/var/lib/registry 和本机的 /myregistry 路径进行映射。
   下载的是registry:2 版本

2. 重命令镜像
   $ docker tag hello [host-ip]:5000/[username]/name:tag
   $ docker tag hello 192.168.2.55:5000/wangbm/hello:v0.1

3. 上传
   $ docker push 192.168.2.55:5000/wangbm/hello:v0.1

4. 下载
   $ docker pull 192.168.2.55:5000/wangbm/hello:v0.1

十、操作镜像

# 删除镜像必须先停止并删除容器
docker ps -a # 找到对应id
docker stop <container_id>
docker rm <container_id>
docker rmi <image_id>

# 在容器内创建新镜像
docker commit

# 给镜像打tag
docker tag old_tag new_tag

# 上传/下载镜像
docker push image
docker pull image

# 搜索Docker Hub中的镜像
docker search image

# 从 Dockerfile 构建镜像
docker build -t image_tag .

# 显示镜像构建历史
docker history image

image10