零基础学习 Docker(三)| 关于镜像

作者: 王炳明 分类: Docker 发布时间: 2020-12-25 14:27 热度:1,458

0. 系列导读

本系列共六篇:

第二篇:镜像管理

1. 镜像的基本命令

当你使用 docker pull 命令去拉取镜像时,默认是从 docker hub 上搜索并下载的

# 搜索镜像
docker search ubuntu

# 拉取镜像 docker pull ubuntu
$ docker image pull ubuntu

你在拉取的时候,其实是可以不从官方的 docker hub 上下载的,甚至还提供更高的自由度,只要你在拉取时,指定容器所在的 url、仓库名 和 tag 即可。

  • url:格式一般是 <域名/IP>[:端口号]。默认地址是 Docker Hub(docker.io)。
  • 仓库名:格式一般是 <用户名>/<软件名>。对于 Docker Hub,如果不给出用户名,则默认为 library,也就是官方镜像。
  • tag:格式是 [:tag], 如果不指定默认是 latest

下面这个例子使用 docker pull centos 拉取镜像,从输出来看,是从 docker.io/library/ubuntu (注意前边不能加 http 或 https)下载的 latest 的镜像

[root@localhost ~]$ docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
a02a4930cb5d: Pull complete 
Digest: sha256:184e5f35598e333bfa7de10d8fb1cebb5ee4df5bc0f970bf2b1e7c7345136426
Status: Downloaded newer image for centos:latest

上面这条命令,忽略指定的版本的话,等价于

$ docker pull docker.io/library/ubuntu:16.04

那么下载下来的镜像如何查看呢?

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

# 查看某一类镜像 docker image ls ubuntu

# 查看某个镜像的信息
docker images ubuntu:16.04

# 查看镜像详细信息 docker inspect [image_id]

如果想给镜像打一个新的 Tag,可以使用下面命令,但是请注意tag打完后,只是新增了一个 tag,原 tag 还会一直存在,并且新 tag 和 原 tag 共用一个 image id

$ docker tag [image_id] [repo:tag:image_name:tag]
$ docker tag bf756fb1ae65 docker.io/hello-docker:0.0.1

从 Docker Hub 上下载镜像,如果下载速度不理想,可以去阿里云申请一个加速器,并把申请到的加速地址配置在 docker 的配置文件中(/etc/docker/daemon.json),配置完记得重启 docker: systemctl restart docker

[root@osh-1 ~]# cat /etc/docker/daemon.json 
{
  "registry-mirrors": ["https://dcbl3c54.mirror.aliyuncs.com"]
}

想查看镜像更改历史,可以使用下面命令

# 查看镜像更改历史
docker history [image_name] docker image history [image_name]

如果想删除已拉取的镜像,使用下面的命令即可

# 删除镜像
docker rmi [id/name] [id/name] docker image rm  [id/name] [id/name]

如果想对一个容器做一个快照,保存成一个新的镜像,可以使用下面这条命令

# 把一个容器(只能是关机状态)保存为新的模板镜像
$ docker commit [container_id] [new_image_name]

镜像在所有信息都是存储在 /var/lib/docker/image/ 目录下,我们都知道镜像是分层的一种结构,如果迁移一个镜像,必须得先将其打包导出

# 打包本地镜像, 使用压缩包来完成迁移
docker save ubuntu>/root/wbm.img   # 默认为文件流输出 docker save -o /root/wbm.img ubuntu  # 或者使用 '-o' 选项指定输出文件路径

打包出来 的是 raw 格式,使用 qemu-img 可以查看

[root@localhost ~]# qemu-img info wbm.img
image: wbm.img
file format: raw
virtual size: 72M (75307008 bytes)
disk size: 72M

导出后的镜像,如何再导入到 docker 中呢?

# 默认从标准输入读取
[root@localhost ~]#  docker load < /root/wb.img

# 用 '-i' 选项指定输入文件路径
[root@localhost ~]#  docker load -i /root/wb.img

2. 什么是Dockerfile

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

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

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

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

零基础学习 Docker(三)|  关于镜像

3. 理解base镜像

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

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

零基础学习 Docker(三)|  关于镜像

你一定很纳闷。怎么才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的内核。
零基础学习 Docker(三)|  关于镜像

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

4. 镜像的分层结构

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

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

答案是不会。

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

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

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

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

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

5. 镜像的缓存特性

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

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

$ docker pull centos

第二步,编辑Dockerfile

FROM centos
RUN yum install -y tree

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

$ docker build -t centos-tree .

零基础学习 Docker(三)|  关于镜像
第四步,在原始的Dockerfile上添加内容

FROM centos
RUN yum install -y tree
COPY testfile /

第五步,再创建一个容器

docker build -t centos-tree-testfile .

零基础学习 Docker(三)|  关于镜像

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

FROM centos
COPY testfile /
RUN yum install -y tree

6. 如何调试Dockerfile

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

那么如何调试?在我们build镜像的时候,每一步都会有一个镜像id,通过这个id我们就可以进入该层镜像。
零基础学习 Docker(三)|  关于镜像
先来看看第二步的结果,/目录下没有testfile文件

$ docker run -it e316b390cf2a

再来看看第三步,/目录下已经有testfile文件
零基础学习 Docker(三)|  关于镜像

友情提示

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

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

7. 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。

7. 理解CMD和ENTRYPOINT

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

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

CMD:两个功能

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

零基础学习 Docker(三)|  关于镜像

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

零基础学习 Docker(三)|  关于镜像

推荐用法

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

9. 上传镜像

为了方便多台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

10. 操作镜像

# 删除镜像必须先停止并删除容器
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

文章有帮助,请作者喝杯咖啡?

发表评论