跳到主要内容

Docker 基础知识

一、认识 Docker​

Docker 什么​

Docker 是 Linux 容器的封装,并提供了简单易用的容器接口,是目前最流行的容器解决方案。在开发中,docker 是一个用于开发、发布和运行应用程序的开放平台,类似一个 KVM。但跟 KVM 有很大区别,KVM 是基于硬件系统的完全虚拟化,需要硬件 CPU 虚拟化支持,相当于一个 OS 系统,需要的存储资源太过庞大,操作时冗余步骤太多,并且启动慢(分钟级)。而 docker 可共享宿主机内核,更轻量,资源占用小,操作简单,启动快(秒级)

Docker 组成​

一个 docker 容器能够完整地运行起来,需要 Linux 的 Namespace、Groups 和联合文件系统三大机制来完成的

1. Namespace​

linux 内核的一个功能,该功能可以对内核资源进行隔离,使得容器可以在单独的命名空间里运行,并且可以访问当前命名空间的所有资源。Namespace 可以隔离 PID、主机名、用户 ID 、文件名、网络访问和进程间通信等相关资源。

Docker 主要用到以下六种命名空间

  • Process ID(pid) namespace:用于隔离进程 ID。容器启动的进程在主机上是不同的,使用 pid namesoace,可以实现每个容器内的主进程 ID 为 1,而在主机上每个容器主进程 ID 是不一样的
  • Network(net) namespace:隔离网络设备、IP 和端口信息。Net Namespace 可以让每个进程拥有自己独立的 IP 地址,端口和网卡信息
  • mount(mnt) namespace:文件系统挂载点隔离。在容器内只能看到自己挂载点,容器内挂载信息的修改不会影响到主机的挂载目录
  • Interprocess Communication(ipc) namespace:隔离进程间通信。可以是实现同一 IPC nameplace 内的进程可以通信
  • UTS Namespace(uts) namespace:主机的隔离。dcoker 容器可以设置自己单独的主机名
  • User Namespace (user) namespace:隔离用户和用户组。我们在主机上的是主机用户组里的其中一个用户,但在 Docker 容器里默认是 root 用户,Docker 容器中用户和主机上的用户是完全隔离的,容器中是拿不到主机上的 root 权限的

当 Docker 新建一个容器时,就会创建上面这六种 Namespace,然后将容器的进程加入到这些容器的 namespace 中,使得容器中的进程只能看到当前 Namespace 内的资源,实现 Docker 容器隔离,可以说没有 Namespace,就没有 Docker 容器。

2. Cgroups​

也是内核的一项功能,可以限制进程资源的使用情况(CPU、内存、磁盘 I/O、网络等),避免多个容器之前或者和主机抢占资源。在容器的实现中,Cgroups 用来限制 CPU 和内存资源等的使用。

在 2006 年,Google 的工程师( Rohit Seth 和 Paul Menage 为主要发起人) 发起了这个项目,起初项目名称并不是 cgroups,而被称为进程容器(process containers)。在 2007 年 cgroups 代码计划合入 Linux 内核,但是当时在 Linux 内核中,容器(container)这个词被广泛使用,并且拥有不同的含义。为了避免命名混乱和歧义,进程容器被重名为 cgroups,并在 2008 年成功合入 Linux 2.6.24 版本中。cgroups目前已经成为 systemd、Docker、Linux Containers(LXC) 等技术的基础。

Cgroups 提供了以下功能

  • 资源限制:限制资源(CPU、内存、磁盘 I/O、网络等)的使用量。防止一个容器使用资源过多影响主机或者其他容器的运行
  • 优先级控制:控制不同的组对资源的使用优先级
  • 审计:计算控制组的资源使用情况。容器的可视化监控就是读的这个模块实现的
  • 控制:控制进程的关闭和恢复

如何实现的这些功能的,那就要了解三个核心概念:

  • 子系统(subsystem):是内核的一个组件,一个子系统代表一个资源调度控制器。例如:内存子系统限制内存的使用量、CPU 子系统限制 CPU 的使用量
  • 控制组(cgroup):表示一组进程与带有参数的子系统的惯量关系。一个进程使用了内存子系统限制内存使用量,那么这个进程和这个内存子系统的关联关系称为控制组。
  • 层级树(hierarchy):是由一系列的控制组按照树状结构排列组成的。子控制组会继承于父控制组,避免重复定义资源的限制条件
Linux Cgroups 的使用​

可以先查看下 linux 默认启动的子系统(不同的内核版本的 Cgroups 和使用方式会略有不同)

$ mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)

Linux 提供的主要 Cgroups 子系统

  • cpu 子系统,主要限制进程的 cpu 使用率。
  • cpuacct 子系统,可以统计 cgroups 中的进程的 cpu 使用报告。
  • cpuset 子系统,可以为 cgroups 中的进程分配单独的 cpu 节点或者内存节点。
  • memory 子系统,可以限制进程的 memory 使用量。
  • blkio 子系统,可以限制进程的块设备 IO。
  • devices 子系统,可以控制进程能够访问某些设备。
  • net_cls 子系统,可以标记 cgroups 中进程的网络数据包,然后可以使用 tc 模块(traffic control)对数据包进行控制。
  • freezer 子系统,可以挂起或者恢复 cgroups 中的进程。
  • ns 子系统,可以使不同 cgroups 下面的进程使用不同的 namespace。

下面以 cpu 子系统 为例使用下子系统

1. 在 cpu 子系统下创建 cgroup

创建一个目录即可:

# mkdir /sys/fs/cgroup/cpu/mydocker

查看下创建的目录里的内容

# ls -l /sys/fs/cgroup/cpu/mydocker
-rw-r--r-- 1 root root 0 7月 26 16:41 cgroup.clone_children
-rw-r--r-- 1 root root 0 7月 26 16:41 cgroup.procs
-r--r--r-- 1 root root 0 7月 26 16:41 cpuacct.stat
-rw-r--r-- 1 root root 0 7月 26 16:41 cpuacct.usage
-r--r--r-- 1 root root 0 7月 26 16:41 cpuacct.usage_all
-r--r--r-- 1 root root 0 7月 26 16:41 cpuacct.usage_percpu
-r--r--r-- 1 root root 0 7月 26 16:41 cpuacct.usage_percpu_sys
-r--r--r-- 1 root root 0 7月 26 16:41 cpuacct.usage_percpu_user
-r--r--r-- 1 root root 0 7月 26 16:41 cpuacct.usage_sys
-r--r--r-- 1 root root 0 7月 26 16:41 cpuacct.usage_user
-rw-r--r-- 1 root root 0 7月 26 16:41 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 7月 26 16:41 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 7月 26 16:41 cpu.rt_period_us
-rw-r--r-- 1 root root 0 7月 26 16:41 cpu.rt_runtime_us
-rw-r--r-- 1 root root 0 7月 26 16:41 cpu.shares
-r--r--r-- 1 root root 0 7月 26 16:41 cpu.stat
-rw-r--r-- 1 root root 0 7月 26 16:41 notify_on_release
-rw-r--r-- 1 root root 0 7月 26 16:41 tasks

cpu.cfs_quota_us文件代表某一个阶段限制的 CPU 使用量。例如:限制某个进程最多会用 1 核 CPU,就在该文件里写入 100000(代表限制 1 个核),tasks 文件中写入进程的 ID 即可(如果要限制多个进程 ID,在 tasks 文件中用换行符分隔即可

2. 创建进程,加入 cgroup

# cd /sys/fs/cgroup/cpu/mydocker
# echo $$ > tasks

查看一下 tasks 文件内容:

# cat tasks
591354
627101

其中第一个进程 ID 为当前 shell 的主进程,也就是说,当前 shell 主进程为 591354。

3. 验证

在当前 shell 进程里执行耗时任务,查看当前进程的 CPU 使用率

# while true;do echo;done;

CPU 使用率情况

# top -p 591354
top - 16:58:36 up 198 days, 41 min, 1 user, load average: 0.02, 0.02, 0.00
Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.7 us, 0.7 sy, 0.0 ni, 98.3 id, 0.0 wa, 0.0 hi, 0.3 si, 0.0 st
MiB Mem : 1827.0 total, 179.9 free, 664.8 used, 982.3 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 1000.3 avail Mem

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
591354 root 20 0 29804 5548 3664 S 99.7 0.3 0:00.07 bash

一核的 CPU使用率为 100%,验证过程看到 591354 进程 CPU 的使用率一直都小于 100%。配置成功,删除 cgroup 时,只需要删除创建的文件夹即可

Docker 中使用 cgroup 子系统​

docker 提供了命令参数来使用 cgroup 子系统,例如:限制 nginx 的内存上限为 1 G

# docker run -it -m=1g nginx

3. 联合文件系统​

又名 UnionFS(Union File System),是一种分层的轻量级的文件系统。它可以把多个文件目录联合挂载到同一目录下,从而形成一个单一的目录,使用上,就像是在操作一个目录一样来操作联合文件系统。

Docker 用来为容器提供构建层,使得容器可以实现写时复制以及镜像的分层构建和存储。可以说联合文件系统是 Docker 镜像和容器的基础,它使得 Docker 镜像可以为多层组成,并且每一层都可以共享。

联合文件系统只是一个概念,怎么实现才是关键。常用的联合文件系统有 AUFS、Overlay 和 Devicemapper 等。

a. AUFS​

AUFS 是 Docker 最早使用的文件系统驱动,最早相比于不成熟的 Overlay 和 Devicemapper ,AUFS 是最稳定的文件系统驱动了。

AUFS 目前并未合到 Linux 内核中,因此只有 Ubuntu 和 Debian 等少数系统支持,若在 CentOS 等 OS 使用,需要安装 AUFS,AUFS 通常不建议用在生产环境。

Docker 支持:

  • 在 /etc/docker新建 daemon.json,并写下配置

    {
      "storage-driver": "aufs"
    }
  • 重启 Docker:systemctl restart docker

  • 验证:docker info

    Client:
     Debug Mode: false
    Server:
     Containers: 0
      Running: 0
      Paused: 0
      Stopped: 0
     Images: 1
     Server Version: 19.03.12
     Storage Driver: aufs
      Root Dir: /var/lib/docker/aufs
      Backing Filesystem: extfs
      Dirs: 1
      Dirperm1 Supported: true

AUFS 存储文件

AUFS 联合文件系统,意味着在宿主机是使用多层目录来存储的,每一层在 AUFS 中叫节点,在 Docker 称为层,最终呈现给用户的是一个单层的文件系统,我们把多层以单一层的方式呈现出来的过程叫做联合挂载。

![image-20210806120420246](/Users/xiangliu/Library/Application Support/typora-user-images/image-20210806120420246.png)

由图看出,每一镜像层和容器层都是 /var/lib/docker下的子目录,镜像层和容器层都在/aufs/diff目录下,每一层目录名称都是镜像或容器的 ID 值,联合挂载点在 /aufs/mnt下,mnt 目录是是容器真正工作的目录,当镜像生成容器前后 mnt 的变化是怎样的呢?

未生成容器:

  • Diff 文件夹:存储镜像内容,每一层都存放在以镜像 ID 命名的文件夹中
  • layers 文件夹:存放镜像层关系元数据,在 diff 文件夹下的每一个镜像层在这里都会是一个文件,文件内容为该镜像层的父级镜像 ID
  • mnt 文件夹:未生成容器时,该目录为空

生成容器后:

  • Diff 文件夹:会在 diff 新增容器层,文件夹名称为 容器层 ID
  • layers 文件夹:增加容器层相关的元数据
  • mnt 文件夹:容器的联合挂载点,这和容器中看到的文件内容一致

AUFS 读取文件

会优先从容器层读取,容器层没有是在从镜像层读取

  • 文件存在容器层时:从容器层读取
  • 文件不存在容器层,存在镜像层时:从镜像层读取
  • 文件既存在容器层,又存在镜像层是:从容器层读取

AUFS 修改、删除文件/文件夹

修改:第一次修改文件时,会将文件复制到容器层(文件过大是会影响性能,但也只影响文件第一次修改),然后对容器层的该文件进行修改操作(镜像层只读)。

删除:AUFS 并不会真正的删除,而是创建一个特殊的文件或文件夹,这种特殊的文件活文件夹会阻止容器的访问。

b. Devicemapper​

Devicemapper 是 Linux 提供的框架,从 Linux 内核 2.6.9 版本开始引入,Devicemapper 与 AUFS 不同,AUFS 是文件系统,而 Devicemapper 是一种映射块设备的技术框架。

Devicemapper 提供了一种将物理块设备映射到虚拟块设备的机制,目前 Linux 下比较流行的 LVM (Logical Volume Manager 是 Linux 下对磁盘分区进行管理的一种机制)和软件磁盘阵列(将多个较小的磁盘整合成为一个较大的磁盘设备用于扩大磁盘存储和提供数据可用性)都是基于 Devicemapper 机制实现的。

工作部分

类似 C/S 架构模式,客户端负则具体的规则定义和配置,服务端根据客户端配置的规则来处理任务

  • 用户空间:配置设备的映射策略与相关内核空间的控制逻辑
  • 内核空间:负责用户空间的关联关系的实现,例如:接管 IO 请求,然后处理和过滤这些 IO 请求并转发的具体的物理设备上

工作机制

三个核心概念

  • 映射设备(mapped device):对外界提供的逻辑虚拟设备
  • 目标设备(target device):是映射设备对应的物理设备或者物理设备上的摸个逻辑分段,是宿主机的物理设备
  • 映射表(map table):记录映射设备到目标设备的映射关系,它记录了映射设备在目标设备的起始地址、范围和目标设备的类型等变量

![image-20210806124629262](/Users/xiangliu/Library/Application Support/typora-user-images/image-20210806124629262.png)

映射设备通过映射表记录的关联关系关联到对应的物理设备上或者另一个映射设备上。

怎样存储数据的

快照(snapshot)

全球网络存储工业协会 SNIA(StorageNetworking Industry Association)对快照(Snapshot)的定义:关于指定数据集合的一个完全可用拷贝,该拷贝包括相应数据在某个时间点(拷贝开始的时间点)的映像。快照可以是其所表示的数据的一个副本,也可以是数据的一个复制品。

简单说,快照是数据某一时间点的存储状态,快照的主要的功能就是备份,方面出现故障时,使用备份的快照将数据回复到某一时间点的数据

当 Docker 使用 Devicemapper 作为文件存储驱动时,Docker 将镜像和容器的文件存储在瘦供给池(thinpool,用多少申请多少)中,并将这些内容挂载在 /var/lib/docker/devicemapper/ 目录下

  • devicemapper 目录(/var/lib/docker/devicemapper/devicemapper/):存储镜像和容器实际内容,该目录由一个或多个块设备构成。

  • metadata 目录(/var/lib/docker/devicemapper/metadata/): 包含 Devicemapper 本身配置的元数据信息, 以 json 的形式配置,这些元数据记录了镜像层和容器层之间的关联信息。

  • mnt 目录( /var/lib/docker/devicemapper/mnt/):是容器的联合挂载点目录,未生成容器时,该目录为空,而容器存在时,该目录下的内容跟容器中一致。

实现分层与共享

Devicemapper 使用专用的块设备实现镜像的存储,使用了写时复制来保障最大程度的节省存储空间,Devicemapper 的分层是依赖快照来实现的

Devicemapper 的每一镜像层都是其下一层的快照,最底层的镜像层是我们的瘦供给池,通过这种方式实现镜像分层有以下优点。

  • 相同的镜像层,仅在磁盘上存储一次。例如,我有 10 个运行中的 busybox 容器,底层都使用了 busybox 镜像,那么 busybox 镜像只需要在磁盘上存储一次即可。

  • 快照是写时复制策略的实现,也就是说,当我们需要对文件进行修改时,文件才会被复制到读写层。

  • 相比对文件系统加锁的机制,Devicemapper 工作在块级别,因此可以实现同时修改和读写层中的多个块设备,比文件系统效率更高。

c. OverlayFS​

overlay2 目前已经是 Docker 官方推荐的文件系统了,也是目前安装 Docker 时默认的文件系统,因为 overlay2 在生产环境中不仅有着较高的性能,它的稳定性也极其突出。但是 overlay2 的使用还是有一些限制条件的,例如要求 Docker 版本必须高于 17.06.02,内核版本必须高于 4.0 等。因此,在生产环境中,如果你的环境满足使用 overlay2 的条件,请尽量使用 overlay2 作为 Docker 的联合文件系统。

OverlayFS 的存储和文件读取、修改和删除操作和 AUFS 很类似

Docker 核心概念​

提到 Docker,很容易想到 镜像、容器和仓库这几个名词。下面来分别介绍下

1. 镜像(Image)​

概念:一组可读文件和文件夹的组合,包含了容器运行时需要的基础文件和配置,是容器启动的先决条件

作用:

  • 基于已有镜像构建新的镜像
  • 容器启动的模板

获取:

  • 自己构建。一个镜像一般是是以另一个镜像为基础进行构建的,我们可以在已有的镜像上添加自己需要的内容
  • 远程拉取。从镜像仓库中拉取镜像,默认会在 Doeker Hub 中拉取镜像

2. 容器(Container)​

概念:是 Image 的运行实体,Image 是静态的只读的文件,容器实际上就是在 Image 的基础上增加了一个读写层,在运行容器做的任何改动都会保存在读写层内,若删除了容器,读写层也会被删除,改动也就全部丢失了。一个容器其实也是一个文件,称为容器文件,容器文件和 image 文件是隔离的,这就意味基于一个 Image 可运行多个 Container。容器的本质是主机上的一个运行进程,和普通进程的区别在于,有着自己独立的命名空间和资源限制,所有在容器内部是无法访问到主机上的进程、环境变量、网络等信息。

作用:

  • 运行 Image。通过 docker run [Image]:[Tag]来运行一个镜像
  • 基于运行 Container 可 commit 一个新的 Image,新的 Image 包含运行容器当前做的所有改动

3. 仓库(Repository)​

概念:保存镜像的仓库,类似代码仓库,负责存储和分发 Image。仓库又可分为公有仓库和私有仓库。

Docker Hub 是 Docker 官方的公开镜像仓库,也可以建立自己的私有仓库

镜像、容器和仓库的关系​

关系图

  • 基于镜像可创建容器
  • 镜像可从远程仓库拉取,也可将自己定制的镜像存在仓库中
  • 仓库就负责存放和分发镜像

Docker 的架构​

背景:起初容器也没有标准、也没人来制定标准,所有很多编排技术并不会将 Docker 作为唯一的运行时容器,例如:Kubernetes 和 Mesos,所以最终爆发了容器大战。OCI (Open Container Initiative)开放容器标准,是容器大战的产物,它是一个轻量级、开放的治理机构,与 2015 年 6 月份正式成立,旨在为用户围绕工业化的容器格式和镜像运行时,制定一个开发的容器标准。目前主要有两个标准:容器运行时标准(runtime spec)和容器镜像标准(image spec)

Docker 采用 C/S (客户端/服务端)架构模式,客户端负责发送操作指令,服务端负责接收和处理指令。

C/S 通信方式:

  • 可以再同一台主机上,通过UNINX套接字来通信
  • 也可以通过网络连接进行远程通信

可以看下 Docker 技术架构图

docker 架构图

Docker 客户端​

Docker 其实是一种泛称,用户和 Docker Server 交互方式有以下两种方式:

  • Docker 命令。Docker 提供简单易用的终端命令,在终端可以使用 Docker 命令交互
  • 可以使用 Docker 提供的 REST API 交互 或者 SDK。社区维护着 Go、Java、Python、PHP 等数十种语言的 SDK,足以满足你的日常需求

Docker 服务端​

Docker Server 是 Docker 提供所有后台的统称。

  • dockerd 是一个非常重要的后台管理进程,它负责响应和处理来自 Docker 客户端的请求,然后将客户端的请求转化为 Docker 的具体操作。例如镜像、容器、网络和挂载卷等具体对象的操作和管理。

  • runC 是 Docker 官方按照 OCI 容器运行时标准的一个实现。通俗地讲,runC 是一个用来运行容器的轻量级工具,是真正用来运行容器的。

  • containerd 是 Docker 服务端的一个核心组件,它是从dockerd中剥离出来的 ,它的诞生完全遵循 OCI 标准,是容器标准化后的产物。containerd通过 containerd-shim 启动并管理 runC,可以说 containerd 真正管理了容器的生命周期。

  • containerd-shim 的意思是垫片,类似于拧螺丝时夹在螺丝和螺母之间的垫片。containerd-shim 的主要作用是将 containerd 和真正的容器进程解耦,使用 containerd-shim 作为容器进程的父进程,从而实现重启 dockerd 不影响已经启动的容器进程。

我们可以查看 dockerd 的进程父子关系

$ sudo pstree -l -a -A [dockerd 进程 ID]
dockerd
  |-containerd --config /var/run/docker/containerd/containerd.toml --log-level info
  |   |-containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/d14d20507073e5743e607efd616571c834f1a914f903db6279b8de4b5ba3a45a -address /var/run/docker/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc
  |   |   |-sleep 3600

二、Docker 的使用​

镜像​

拉取镜像​

$ docker pull [Registry]/[Repository]/[Image]:[Tag]
  • Registry 为注册服务器,Docker 默认会从 docker.io 拉取镜像,如果你有自己的镜像仓库,可以把 Registry 替换为自己的注册服务器。

  • Repository 为镜像仓库,通常把一组相关联的镜像归为一个镜像仓库,library 为 Docker 默认的镜像仓库。

  • Image 为镜像名称。

  • Tag 为镜像的标签,如果你不指定拉取镜像的标签,默认为latest。

查看镜像​

$ docker images # 查看本地所有镜像
$ docker image ls [Image]:[Tag] # 查询 某一镜像,不指定镜像,就查询所有镜像

删除镜像​

当以镜像名称删除镜像时,删除的镜像 Tag 为 latest时,可以省略 tag,否则,必须要带上 Tag

$ docker rmi [Image ID]/[Image]:[Tag]

重命名镜像​

重名名的镜像和源镜像指向的其实是一个镜像,尽管 Image Name 和 Tag 可以自定义,但他们的 Image ID 是一样的。删除重命名的镜像时,必须使用docker rmi [Image]:[Tag]这个命令来删除

$ docker tag  [SOURCE_IMAGE][:TAG] [TARGET_IMAGE][:TAG]

构建镜像​

构建镜像有两种方式

1. 使用 docker commit 命令从运行中的容器提交为镜像​

其实就是将运行容器的镜像层和读写层提交为一个新的镜像。

以运行的 busybox 为例,我们可以进入容器对容器进行修改

# --rm:退出容器后删除容器,与 -d 互斥
# -it:交互式的伪终端
$ docker run --rm --name=busybox -it busybox sh
/ #
/ # touch hello.txt && echo "I love Docker. " > hello.txt # 增加一个 hello.txt 文件
/ #

使用 docker commit 命令构建镜像

$ docker commit busybox busybox:hello

然后可以查看镜像,就会有名称为 busybox ,Tag 为 hello ,ID 为新的镜像

2. 使用docker build命令从 Dockerfile 构建镜像​

Dockerfile 的特点

  • Dockerfile 的每一个对容器进行修改命令都会生成一个独立的镜像层,并且拥有唯一的 ID;
  • Dockerfile 的命令是完全透明的,通过查看 Dockerfile 的内容,就可以知道镜像是如何一步步构建的;
  • Dockerfile 是纯文本的,方便跟随代码一起存放在代码仓库并做版本管理。

常用的命令如下

指令含义示例
FROMDockerfile 除了注释第一行必须是 FROM ,FROM 后面跟镜像名称,代表我们要基于哪个基础镜像构建我们的容器。FROM centos:6
MAINTAINER镜像维护者姓名或邮箱地址MAINTAINER zhufengjiagou
RUN构建镜像时运行的 shell 命令RUN yum install httpd
CMDCMD 设置容器启动后默认执行的命令及其参数,但 CMD 能够被 docker run 后面跟的命令行参数替换CMD /usr/sbin/sshd -D
EXPOSE声明容器运行的服务器端口EXPOSE 80 443
ENV设置容器内的环境变量ENV MYSQL_ROOT_PASSWORD 123456
ADD拷贝文件或目录到镜像中,如果是远程 URL 或者压缩包会自动下载和解压ADD ,ADD https://xxx.com/html.tar.gz /var/www.html, ADD html.tar.gz /var/www/html
COPY拷贝文件或目录到镜像COPY ./start.sh /start.sh
ENTRYPOINT配置容器启动时运行的命令ENTRYPOINT /bin/bash -c '/start.sh'
VOLUME指定容器挂载点到宿主自动生成的目录或其它容器VOLUME ["/var/lib/mysql"]
USER为 RUN CMD和ENTRYPOINT执行命令指定运行用户USER zhufengjiagou
WORKDIR为RUN CMD ENTRYPOINT COPY ADD 命令设置工作目录WORKDIR /data
HEALTHCHECK健康检查HEALTHCHECK --interval=5m --timeout=3s --retries=3 CMS curl -f htp://localhost
ARG在构建镜像时指定一些参数ARG user

一个简单的 Dockerfile 文件

FROM centos:7 # centos:7 为基础镜像
COPY nginx.repo /etc/yum.repos.d/nginx.repo # copy 本地文件 nginx.repo 到 容器的 /etc/yum.repos.d/nginx.repo
RUN yum install -y nginx # 执行 shell 命令
EXPOSE 80 # 向外暴露容器的 80 端口
ENV HOST=mynginx # 定义 HOST 值为 mynginx 的变量
CMD ["nginx","-g","daemon off;"] # 设置默认运行的命令

就上诉 Dockerfile 生成的镜像,可分为 三个镜像层

FROM centos:7
COPY nginx.repo /etc/yum.repos.d/nginx.repo
RUN yum install -y nginx

可以进入到 /var/lib/docker/overlay2目录下使用tree .命令查看产生的镜像文件:

$ tree .
# 以下为 tree . 命令输出内容
|-- 3e89b959f921227acab94f5ab4524252ae0a829ff8a3687178e3aca56d605679
| |-- diff # 这一层为基础层,对应上述 Dockerfile 第一行,包含 busybox 镜像所有文件内容,例如 /etc,/bin,/var 等目录
... 此次省略部分原始镜像文件内容
| `-- link
|-- 6591d4e47eb2488e6297a0a07a2439f550cdb22845b6d2ddb1be2466ae7a9391
| |-- diff # 这一层对应上述 Dockerfile 第二行,拷贝 test 文件到 /tmp 文件夹下,因此 diff 文件夹下有了 /tmp/test 文件
| | `-- tmp
| | `-- test
| |-- link
| |-- lower
| `-- work
|-- backingFsBlockDev
|-- bec6a018080f7b808565728dee8447b9e86b3093b16ad5e6a1ac3976528a8bb1
| |-- diff  # 这一层对应上述 Dockerfile 第三行,在 /tmp 文件夹下创建 testdir 文件夹,因此 diff 文件夹下有了 /tmp/testdir 文件夹
| | `-- tmp
| | `-- testdir
| |-- link
| |-- lower
| `-- work
...

运行以下命令,使用 Dockerfile 文件制作镜像

  • -t 用来指定 Image 镜像的名称,后面还可以加冒号指定标签,如果不指定默认就是 latest
  • . 表示 Dockerfile 文件的所有路径,. 就表示当前路径
$ docker build -t mybusybox .

导出/引入​

export:将一个容器的文件系统作为一个tar归档文件导出

docker export [OPTIONS] CONTAINER

eg/ $ docker export -o xxx.tar [CONTAINER_ID]

导出后的tar归档文件可以使用 import导入到本地镜像中

import: 导入容器快照文件系统tar归档文件并创建镜像

docker import [OPTIONS] file/URL/- [REPOSITORY[:TAG]]

eg/ $ docker import xxx.tar

保存/加载镜像​

save: 将指定的镜像保存为 tar 文件

docker save [OPTIONS] IMAGE [IMAGE...]

eg/ $ docker save -o hello-world.tar hello-world:latest

保存后的tar镜像文件,可以使用load 命令来加载到本地镜像中

主要命令一览​

命令含义语法案例
ls查看全部镜像docker image ls
search查找镜像docker search [imageName]
history查看镜像历史docker history [imageName]
inspect显示一个或多个镜像详细信息docker inspect [imageName]
pull拉取镜像docker pull [imageName]
push推送一个镜像到镜像仓库docker push [imageName]
rmi删除镜像docker rmi [imageName] docker image rmi 2
prune移除未使用的镜像,没有标记或补任何容器引用docker image prunedocker image prune
tag标记本地镜像,将其归入某一仓库docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]docker tag centos:7 zhangrenyang/centos:v1
export将容器文件系统作为一个tar归档文件导出到STDOUTdocker export [OPTIONS] CONTAINERdocker export -o hello-world.tar b2712f1067a3
import导入容器快照文件系统tar归档文件并创建镜像docker import [OPTIONS] file/URL/- [REPOSITORY[:TAG]]docker import hello-world.tar
save将指定镜像保存成tar文件docker save [OPTIONS] IMAGE [IMAGE...]docker save -o hello-world.tar hello-world:latest
load加载tar文件并创建镜像docker load -i hello-world.tar
build根据Dockerfile构建镜像docker build [OPTIONS] PATH / URL / -docker build -t zf/ubuntu:v1 .

容器​

容器的运行生命周期

Alt text

启动容器​

docker run ubuntu /bin/echo "Hello world"
  • docker: Docker 的二进制执行文件。
  • run:与前面的 docker 组合来运行一个容器。
  • ubuntu指定要运行的镜像,Docker首先从本地主机上查找镜像是否存在,如果不存在,Docker 就会从镜像仓库 Docker Hub 下载公共镜像。
  • /bin/echo "Hello world": 在启动的容器里执行的命令

Docker以ubuntu镜像创建一个新容器,然后在容器里执行 bin/echo "Hello world",然后输出结果

  • Docker attach必须是登陆到一个已经运行的容器里。需要注意的是如果从这个容器中exit退出的话,就会导致容器停止
参数含义
-i --interactive交互式
-t --tty分配一个伪终端
-d --detach运行容器到后台
-a --attach list附加到运行的容器当我们同时使用docker attach命令同时在多个终端运行时,所有的终端窗口将同步显示相同内容,当某个命令行窗口的命令阻塞时,其他命令行窗口同样也无法操作。
-e --env list设置环境变量docker run -d -p 1010:80 -e username="zhufeng" nginx \ docker container exec -it 3695dc5b9c2d /bin/bash
-p --publish list发布容器端口到主机
-P--publish-all

查看容器​

docker ps
docker -a
docker -l
  • -a 显示所有的容器,包括已停止的
  • -l 显示最新的那个容器
字段含义
CONTAINER ID容器ID
IMAGE使用的镜像
COMMAND使用的命令
CREATED创建时间
STATUS状态
PORTS端口号
NAMES自动分配的名称

运行交互式的容器​

docker run -i -t ubuntu /bin/bash
exit
  • -t=--interactive 在新容器内指定一个伪终端或终端。
  • -i=--tty 允许你对容器内的标准输入 (STDIN) 进行交互。

我们可以通过运行exit命令或者使用CTRL+D来退出容器。

后台运行容器​

docker run --detach centos ping www.baidu.com
docker ps
docker logs --follow ad04d9acde94
docker stop ad04d9acde94

kill​

docker kill 5a5c3a760f61

kill是不管容器同不同意,直接执行kill -9,强行终止;stop的话,首先给容器发送一个TERM信号,让容器做一些退出前必须的保护性、安全性操作,然后让容器自动停止运行,如果在一段时间内,容器还是没有停止,再进行kill -9,强行终止

删除容器​

  • docker rm 删除容器
  • docker rmi 删除镜像
  • docker rm $(docker ps -a -q)
docker rm 5a5c3a760f61

启动容器​

docker start [containerId]

停止容器​

docker stop [containerId]

自动删除​

docker run --rm ubuntu /bin/bash

主要命令一览​

命令含义案例
run从镜像运行一个容器docker run ubuntu /bin/echo 'hello-world'
ls列出容器docker container ls
inspect显示一个或多个容器详细信息docker inspect
attach要attach上去的容器必须正在运行,可以同时连接上同一个container来共享屏幕docker attach [OPTIONS] CONTAINERdocker attach 6d1a25f95132
stats显示容器资源使用统计docker container stats
top显示一个容器运行的进程docker container top
update更新一个或多个容器配置docker update -m 500m --memory-swap -1 6d1a25f95132
port列出指定的容器的端口映射docker run -d -p 8080:80 nginx docker container port containerID
ps查看当前运行的容器docker ps -a -l
kill [containerId]终止容器(发送SIGKILL )docker kill [containerId]
rm [containerId]删除容器docker rm [containerId]
start [containerId]启动已经生成、已经停止运行的容器文件docker start [containerId]
stop [containerId]终止容器运行 (发送 SIGTERM )docker stop [containerId] docker container stop $(docker container ps -aq)
logs [containerId]查看 docker 容器的输出docker logs [containerId]
exec [containerId]进入一个正在运行的 docker 容器执行命令docker container exec -it f6a53629488b /bin/bash
cp [containerId]从正在运行的 Docker 容器里面,将文件拷贝到本机docker container cp [containerId]:/root/root.txt .
commit [containerId]根据一个现有容器创建一个新的镜像docker commit -a "zhufeng" -m "mynginx" a404c6c174a2 mynginx:v1

数据卷​

什么是卷?

卷的本质的文件或者目录,卷可以绕过容器的联合文件系统,将文件或者目录保存在宿主机上,这样不仅实现了数据的持久化存储,也解决了容器间数据共享的问题。使用卷可以将容器内的目录或者文件持久化存储,这样可以保证容器重启或删除数据不丢失

创建数据卷​

Docker 默认数据卷模式为local模式,就提供给主机访问。若要实现远程访问,需要使用网络存储。

local模式并未提供存储配额,所以需要自己手动来维护磁盘存储空间

  1. 使用docker volume create命令可以创建一个数据卷
docker volume create [VOLUME_NAME]