首页 > 技术文章 > Docker笔记

4thirteen2one 2020-07-29 02:29 原文

Docker 镜像包含了基础操作系统,以及应用程序运行所需的代码和依赖包

Docker 镜像可理解为 VM 中的模板,或 OOP 中的 Class

安装 Docker

使用 docker version 命令来检测客户端和服务端是否都已经成功运行

用户可以通过引用镜像的 ID 或名称来使用镜像。
一个镜像可以根据用户需要设置多个标签。


查找镜像

使用 docker search keyword 命令搜索Docker Hub 中的相关仓库

  • --filter
    • is-official=true:只显示官方镜像
    • is-automated=true:只显示自动创建的仓库
  • --limit:默认只显示25行结果,最多100行

拉取镜像

使用 docker image pull <repository>:<tag> 命令来拉取镜像(默认拉取标签为 latest 的镜像,但 latest 是一个非强制标签,不保证指向仓库中最新的镜像!)

  • -a:拉取仓库中的全部镜像,此时无需指定 tag

查看本地镜像

使用 docker image ls 命令来查看拉取的镜像

  • --digests:用于在本地查看镜像的 SHA256 摘要
  • --filter :用于过滤显示内容
    • dangling=false:指定仅返回非悬虚镜像(false)

      那些没有标签的镜像被称为悬虚镜像,在列表中展示为 <none>:<none>。通常出现这种情况,是因为构建了一个新镜像,然后为该镜像打了一个已经存在的标签。当此情况出现,Docker会构建新的镜像,然后发现已经有镜像包含相同的标签,接着Docker会移除旧镜像上面的标签,将该标签标在新的镜像之上。

    • before=python:3.7-slim:参数为镜像名称或者ID,返回在其之前被创建的全部镜像
    • since:返回在指定镜像之后创建的全部镜
    • reference="*:latest":仅显示标签为latest的镜像
  • --format:通过Go模板对输出内容格式化
    • "{{.Size}}":只返回各镜像的大小属性
    • "{{.Repository}}: {{.Tag}}: {{.Size}}" :只显示仓库、标签和大小信息
  • -q:返回本地拉取的全部镜像的ID列表

删除镜像

使用 docker image prune 命令移除全部的悬虚镜像

  • -a:Docker 会额外移除没有被任何容器使用的镜像

使用 docker image rm ID 命令删除指定镜像

快速删除镜像的方法:docker image rm $(docker image ls -q) -f

若被删除的镜像上存在运行状态的容器,那么删除操作不会被允许

若某个镜像层被多个镜像共享,那只有当全部依赖该镜像层的镜像都被删除后,该镜像层才会被删除。


一致性检验

从 Docker 1.10 版本开始,镜像就是一系列松耦合的独立层的集合。

镜像本身是一个配置对象,其中包含了镜像层的列表以及一些元数据信息。

镜像层才是实际数据存储的地方(比如文件等,镜像层之间是完全独立的,并没有从属于某个镜像集合的概念)。

镜像的唯一标识是一个加密ID,其值为配置对象本身的散列值

每个镜像层也由一个加密ID区分,其值为镜像层本身内容的散列值

镜像配置对象的散列值和各镜像层内容的散列值作为内容散列(Content Hash),被用于辨别内容修改。
而为节省网络带宽和存储空间,镜像传输(推送/拉取)时会被压缩,而压缩会导致前后内容散列可能不一致,所以还需要每个镜像层同时包含一个分发散列(Distribution Hash),用于标记压缩版镜像的一致性


查看镜像分层和配置

使用 docker image inspect <repository>:<tag> 命令查看镜像分层

在添加额外的镜像层的同时,镜像始终保持是当前所有镜像的组合

多个镜像之间可以并且确实会共享镜像层,这样可以有效节省空间并提升性能


使用 systemctl is-active dockerservice docker status 检查 Docker daemon 的状态


启动容器

使用 docker container run <option> <image>:<tag> <app> 命令从镜像来启动容器

  • --name:给容器命名
  • -it:选项使容器具备交互性并与终端进行连接
  • -d:表示后台模式,告知容器在后台运行
  • -p host-port:container-port:将 Docker 主机端口映射到容器内
  • --restart:重启策略,容器的一种自我修复能力,可以在指定事件或者错误后重启来完成自我修复
    • always:除非容器被明确停止,比如通过docker container stop 命令,否则该策略会一直尝试重启处于停止状态的容器。当 daemon 重启的时候,被明确停止的容器也会被重启
    • unless-stopped:与 always 的最大区别,就是那些指定了 --restart unless-stopped 并处于 Stopped (Exited) 状态的容器,不会在 Docker daemon 重启的时候被重启
    • on-failure:会在退出容器并且返回值不是0的时候,重启容器。就算容器处于 stopped 状态,在 Docker daemon 重启的时候,容器也会被重启

Docker客户端通过相应 API 调用 Docker daemon,Docker daemon 接收命令后检索 Docker 本地缓存是否有命令所请求的镜像。若本地缓存中无该镜像,则查询在 Docker Hub 中是否存在对应镜像。找到该镜像后,Docker 将镜像拉取到本地,存储在本地缓存当中
Docker daemon 通过位于 /var/run/docker.sock 的本地 IPC/Unix socket 来实现 Docker 远程 API。
Docker 默认非 TLS 网络端口为2375,TLS 默认端口为2376

容器会随着其中运行应用的退出而终止
容器如果不运行任何进程则无法存在

在Linux中启动容器的命令如下:

$ docker container run -it ubuntu:latest /bin/bash
root@6dc20d508db0:/#

其中 -it 参数告诉 Docker 开启容器的交互模式并将当前 Shell 连接到容器终端,基于 ubuntu:latest 镜像启动容器,并在容器内部运行 Bash Shell 进程


保持后台运行

Ctrl-PQ 组合键会断开 Shell 和容器终端之间的链接,并在退出后保持容器在后台处于运行(UP)状态。


查看容器

使用 docker container ls 命令列出所有在运行(UP)状态的容器

  • 选项 -a:让 Docker 列出所有容器,甚至包括那些处于停止(Exited)状态的容器

启动容器中的进程

使用 docker container exec <options><container-name or container-id> <command/app> 命令在运行状态的容器中启动一个新进程

  • 选项 -it:使容器具备交互性

停止容器

使用 docker container stop <container-id or container-name> 命令停止运行中的容器,并将状态置为 Exited(0)

通过发送 SIGTERM 信号给容器内 PID 为 1 的进程达到目的。如果进程没有在 10s 之内得到清理并停止运行,那么会接着发送 SIGKILL 信号来强制停止该容器


重启容器

使用 docker container start <container-id or container-name> 命令重启处于停止(Exited)状态的容器


删除容器

使用 docker container rm <container-id or container-name> 命令删除容器

删除容器的最佳方式还是分两步,先停止容器然后删除。这样可以给容器中运行的应用/进程一个停止运行并清理残留数据的机会

快速删除容器的方法:docker container rm $(docker container ls -aq) -f


查看容器配置

使用 docker container inspect <container-id or container-name> 命令显示容器的配置细节和运行时信息


容器的生命周期

创建,运行,根据需要多次停止、启动、暂停以及重启,销毁

直至明确删除容器前,容器都不会丢弃其中的数据。就算容器被删除了,如果将容器数据存储在卷中,数据也会被保存下来。


应用的容器化

将应用整合到容器中并且运行起来的这个过程,称为“容器化”(Containerizing),有时也叫作“Docker化”(Dockerizing)。

完整的应用容器化过程主要分为以下几个步骤:
(1)编写应用代码。
(2)创建一个 Dockerfile,其中包括当前应用的描述、依赖以及该如何运行这个应用。
(3)对该 Dockerfile 执行 docker image build 命令。
(4)等待 Docker 将应用程序构建到 Docker 镜像中。
一旦应用容器化完成(即应用被打包为一个 Docker 镜像),就能以镜像的形式交付并以容器的方式运行了。

使用 docker image build [选项] <上下文路径/URL/-> 命令基于上下文目录构建镜像

  • 选项 -t:指定 tag
  • 选项 -f:指定 Dockerfile 的路径和名称
  • 选项 --nocache=true 可以强制忽略对缓存的使用
  • 选项 --squash 可以指定构建一个合并的镜像

在构建 Windows 镜像时,尽量避免使用 MSI 包管理器。因其对空间的利用率不高,会大幅增加镜像的体积。


Dockerfile

在 Docker 当中,包含应用文件的目录通常被称为构建上下文(Build Context)。通常将 Dockerfile 放到构建上下文的根目录下

  • Dockerfile 中的注释行以 # 开头。
    除注释之外,每一行都是一条指令(Instruction),格式为 INSTRUCTION argument

Docker image build 命令会按行解析 Dockerfile 中的指令并顺序执行

  • FROM 指令指定要构建的镜像的基础镜像,通常是 Dockerfile 中的第一条指令

  • LABEL 以键值对的方式为镜像添加自定义元数据

  • RUN 指令在 FROM 指定的基础镜像之上,新建一个镜像层来存储安装内容

    通过使用 && 连接多个命令以及使用反斜杠(\)换行的方法,将多个命令包含在一个 RUN 指令中

  • COPY 指令将应用相关文件从构建上下文复制到了当前镜像中,并且新建一个镜像层来存储

    • 选项 --from 从之前阶段构建的镜像中仅复制生产环境相关的应用代码,而不复制生产环境不需要的构件
  • WORKDIR 指令为 Dockerfile 中尚未执行的指令设置工作目录。该目录与镜像相关,并且会作为元数据记录到镜像配置中,但不会创建新的镜像层

  • EXPOSE 指令设置向主机暴露的端口号。这个配置信息会作为镜像的元数据被保存下来,并不会产生新的镜像层

  • ENTRYPOINT 指令指定当前镜像的入口程序。ENTRYPOINT 指定的配置信息也是通过镜像元数据的形式保存下来,而不是新增镜像层


查看执行命令历史

使用 docker image history <repository>:<tag> 命令查看在构建镜像的过程中都执行了哪些指令


使用 docker login 命令登录 DockerHub


为镜像添加标签

Docker 默认 Registry=docker.io、Tag=latest。但是Docker并没有给Repository提供默认值,而是从被推送镜像中的REPOSITORY属性值获取,当无权限访问该仓库时,需更改所要推送至的仓库名

使用 docker image tag <current-tag> <new-tag> 命令为指定的镜像添加一个额外的标签,并且不需要覆盖已经存在的标签


推送Docker镜像

使用 docker image push <repository>:<tag> 命令推送镜像至 Docker 仓库


多阶段构建(Multi-Stage Build)

多阶段构建方式使用一个 Dockerfile,其中包含多个 FROM 指令。每一个 FROM 指令都是一个新的构建阶段(Build Stage),并且可以方便地复制之前阶段的构件。

每一个 FROM 指令构成一个单独的构建阶段。各个阶段在内部从 0 开始编号。


利用构建缓存

第一次构建会拉取基础镜像,并构建镜像层,构建过程需要花费一定时间;第二次构建几乎能够立即完成。这就是因为第一次构建的内容(如镜像层)能够被缓存下来,并被后续的构建过程复用。
docker image build 命令会从顶层开始解析Dockerfile 中的指令并逐行执行。而对每一条指令,Docker 都会检查缓存中是否已经有与该指令对应的镜像层。如果有,即为缓存命中(Cache Hit),并且会使用这个镜像层;如果没有,则是缓存未命中(Cache Miss),Docker 会基于该指令构建新的镜像层。缓存命中能够显著加快构建过程。

一旦有指令在缓存中未命中(没有该指令对应的镜像层),则后续的整个构建过程将不再使用缓存。

尽量将易于发生变化的指令置于Dockerfile文件的后方执行,尽量从缓存中获益。

Docker 会计算每一个被复制文件的 checksum 值,并与缓存镜像层中同一文件的 checksum 进行对比。如果不匹配,那么就认为缓存无效并构建新的镜像层。

推荐阅读