云计算与云原生 — Docker 容器技术完全解析

Posted 范桂飓

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了云计算与云原生 — Docker 容器技术完全解析相关的知识,希望对你有一定的参考价值。

目录

文章目录

Docker Overview

2010 年,在美国旧金山成立了一家名叫 dotCloud 的公司。这家公司主要提供基于 PaaS 的云计算技术服务。具体来说,是和 LXC 有关的容器技术。

LXC(Linux Container)是一种轻量级的虚拟化技术,可以隔离进程和资源,提供了在单一可控主机节点上支持多个相互隔离的 Server Container 同时执行的机制。Container 有效地将由单个操作系统管理的资源划分到孤立的组中,以更好地在孤立的组之间平衡有冲突的资源使用需求。Container 将应用程序与操作系统解耦,这意味着用户拥有了一个 Container 就近似的拥有了一个轻量且具有一定隔离性的操作系统运行时(Runtime)。

LXC 类似于 Chroot,提供了一个拥有自己进程和网络空间的虚拟环境,但又有别于虚拟机,因为 LXC 是一种操作系统层次上的资源的虚拟化,本质是一种操作系统虚拟化技术,基于 Linux Kernel 的 cgroups 和 namespace 实现。

  • namespace 用于完成资源的隔离。
  • cgroups 用于完成进程对资源使用的限制和管理。

后来,dotCloud 公司将自己的容器技术进行了简化和标准化,并命名为 Docker。Docker 诞生之初,并没有引起行业的关注。而 dotCloud 公司在激烈的竞争之下,决定将 Docker 开源。

2013 年 3 月,dotCloud 公司的创始人之一,28 岁的 Solomon Hykes 正式决定将 Docker 项目开源。开源当月,Docker 0.1 版本发布。此后的每一个月,Docker 都会发布一个版本。到 2014 年 6 月 9 日,Docker 1.0 版本正式发布。此时的 Docker,已经成为行业里人气最火爆的开源技术。dotCloud 公司也干脆把公司名字也改成了 Docker Inc.。

在容器技术之前,业界的网红是虚拟机。虚拟机属于服务器虚拟化技术。而 Docker 这样轻量级的虚拟化,属于操作系统虚拟化技术。

Docker Container 的启动时间很快,几秒钟就能完成。而且,它对资源的利用率很高,一台主机可以同时运行几千个 Docker Container。此外,它占的空间很小,虚拟机一般要几 GB 到几十 GB 的空间,而容器只需要 MB 级甚至 KB 级。

实际上要理解 Docker 并不困难,主要是两句口号:

  1. “Build, Ship and Run.”:构建、分发、运行。
  2. “Build once, Run anywhere.”:构建一次,到处运行。

Docker 的三大核心概念,分别是:

  • 镜像(Image)
  • 容器(Container)
  • 仓库(Repository)

简而言之,Docker 就是一个 Golang 开发的开源容器引擎技术,最初基于 LXC 来实现。在 LXC 的基础之上,实现了几项重大的更改,通过类似 Git 的操作方式来构建、分发 Image,使 Container 的使用更加便捷和灵活。

Docker 的组件

Docker 的组件包括:

  • Docker Client:向 Docker Server 进程发起请求,如:build、pull、run 等操作。Docker Client 既可以在访问本地守护(local host)进程,也可以访问远程(remote host)守护进程。

  • Docker Server:侦听 REST API 请求并管理 Docker 对象,例如:镜像,容器,网络和卷。守护程序还可以与其他守护程序通信以管理 Docker 服务。

  • Docker Registry(注册表,仓库注册服务器):存储 Docker Image 的中央仓库。其中 Docker Hub 是任何人都可以使用的 Public Registry,Docker Server 默认配置在 Docker Hub 上查找 Images。个人也可以运行 Private Registry,如果使用 Docker DataCenter,则其中包括 Docker Trusted registry(DTR)。使用 docker pull 或 docker run 指令时,所需的 Image 将从 Docker Server 配置的 Registry 中提取。

Docker 的软件架构

从上图可以看出,Docker 主要的模块有:

  • Docker Client
  • Docker Daemon
  • Docker Registry
  • Graph
  • Driver
  • Libcontainer
  • Docker Container

用户使用 Client 与 Daemon 建立通信,并发送请求给后者 Daemon 作为 Docker 的核心,首先提供 Server 来接受 Client 的请求,而后通过 Engine 执行 Docker 内部的一系列工作,每一项工作都是以一个 Job 的形式的存在。

  • 当需要为 Container 提供 Image 时,则从 Registry 中下载镜像,并通过镜像管理驱动 Graphdriver 将下载镜像以 Graph 的形式存储;
  • 当需要为 Container 创建网络环境时,则通过网络管理驱动 Networkdriver 创建并配置 Container 网络环境;
  • 当需要为 Container 限制运行资源或执行用户指令等操作时,则通过 Execdriver 来完成。

而 Libcontainer 则作为一个独立的 Container 管理模块,Networkdriver 以及 Execdriver 都是通过 Libcontainer 来完成对 Container 进行的操作。当执行 docker run 的一系列工作后,一个实际的 Container 就处于运行状态,该 Container 拥有独立的文件系统,独立并且安全的运行环境等。

Docker Client

Docker Client 在 Linux 上表现为一个 docker 可执行文件,当 Client 接收到 Daemon 返回的响应并进行简单处理后,Client 一次完整的生命周期就结束了。Client 可以通过 3 种方式和 Daemon 建立通信:

  1. tcp://host:port
  2. unix:path_to_socket;fd://socketfd
  3. 通过设置命令行参数设置 TLS 连接

Docker Daemon

Docker Daemon 在 Linux 上表现为一个常驻在后台的系统进程,可以通过 systemd 来进行管理,实际上跟 Docker Client 是同一个 docker 可执行文件。

Docker Daemon 可以细分为以下模块:

  • API Server:Daemon 会在后台启动一个 Docker Server,是一个 API Server,基于 Golang 的 Gorilla/Mux 包,接受 Client 发送的请求并路由分发到不同的 Handler 进行处理。

值得注意的是:Docker Server 的启动是靠一个名为 serveapi 的 Job 运行来完成的。所以 Server 的本质是众多 Job 中的一个。

  • Engine:是 Docker 的运行引擎,它扮演 Docker Container 存储仓库的角色,并且通过执行 Job 的方式来操纵管理这些容器。Docker Engine 有两个不同的版本:Docker Engine Enterprise(企业版)和 Docker Engine Community(社区版)。

  • Job:一个 Job 可以认为是 Engine 内部最基本的工作执行单元。Docker 做的每一项工作,都可以抽象为一个 Job。例如:在容器内部运行一个进程,这是一个 Job;创建一个新的容器,这是一个 Job,从 Internet上 下载一个文档,这是一个 Job,等等。Job 的设计者,把 Job 设计得与 Unix Processor 相仿。比如说:Job 有一个名称,有参数,有环境变量,有标准的输入输出,有错误处理,有返回状态等。

Docker Registry

Docker Registry 是一个存储 Images 的仓库。在 Docker 的运行过程中,Daemon 会与 Registry 通信,并实现搜索镜像、下载镜像、上传镜像三个功能,这三个功能对应的 Job 分别为 search、pul 与 push。

Docker 可以使用公有的 Docker Registry,即:Docker Hub,可以从中找到来自开源项目、软件供应商、乃至个人账户的 Docker Image。同时,Docker 也允许用户构建本地私有的 Docker Registry,这样可以保证容器镜像的获取在内网完成。

Graph

Graph 充当已下载镜像的保管者,以及已下载镜像之间关系的记录者。一方面,Graph 存储着本地具有版本信息的文件系统镜像,另一方面也通过 GraphDB 记录着所有文件系统镜像彼此之间的关系。

其中,GraphDB 是一个构建在 SQLite 之上的小型图数据库,实现了节点的命名以及节点之间关联关系的记录。它仅仅实现了大多数图数据库所拥有的一个小的子集,但是提供了简单的接口表示节点之间的关系。

同时在 Graph 的本地目录中,关于每一个的容器镜像,具体存储的信息有:该容器镜像的元数据,容器镜像的大小信息,以及该容器镜像所代表的具体 Rootfs。

Driver

Driver 作为驱动模块,Docker 通过 Driver 来实现对 Container 执行环境的定制。可以分为以下三类驱动:

  1. Graphdriver
  2. Networkdriver
  3. Execdriver

Graphdriver

Graphdriver 用于完成 Image 的管理,包括存储与获取。当用户需要下载指定的镜像时,Graphdriver 就将镜像存储在本地的指定目录;当用户需要使用指定的镜像来创建容器的 Rootfs 时,Graphdriver 就从本地镜像存储目录中获取指定的容器镜像。

在 Graphdriver 初始化之前,有 4 种文件系统或类文件系统在其内部注册,它们分别是:

  1. Aufs
  2. Btrfs
  3. Vfs
  4. Devmapper

而 Graphdriver 在初始化之时,通过获取系统环境变量 DOCKER_DRIVER 来提取所使用 Driver 的指定类型。而之后所有的 Graph 操作,都使用该 Driver 来执行。

Graphdriver 的架构如下:

Networkdriver

Networkdriver 用于完成 Container 网络环境的配置,其中包括:

  • Docker deamon 启动时为其创建 Bridge 网桥;
  • Container 创建时为其创建专属虚拟网卡设备,以及为 Container 分配 IP、Port 并与宿主机做端口映射,设置容器防火墙策略等。

Networkdriver 的架构如下:

Execdriver

Execdriver 作为 Container 的执行驱动,负责创建 Container 运行时 namespace,负责容器资源使用的统计与限制,负责容器内部进程的真正运行等。

在 Execdriver 实现的初期使用了 LXC Driver 调用 LXC 的接口,来操纵容器的配置以及生命周期,而现在 Execdriver 默认使用 Native 驱动,不再依赖于 LXC。可以通过启动 Daemon 时指定 ExecDriverflag 参数来进行选择,默认为 native。

Execdriver 架构如下:

Libcontainer

Libcontainer 是 Docker 架构中一个使用 Golang 实现的库,设计初衷是希望该库可以不依靠任何依赖,直接访问 Kernel 中与容器相关的 API。

正是由于 Libcontainer 的存在,Docker 最终得以操纵 Container 的 Namespace、Cgroups、Apparmor、网络设备以及防火墙规则等。这一系列操作的完成都不需要依赖 LXC 或者其他库。

Libcontainer 架构如下:

Docker 将底层容器运行时剥离出来,实现更好的平台无关性。LibContainer 是对各种容器的抽象,发展为 RunC,并贡献给 OCP 组织作为定义容器环境的标准。

Docker Container

Docker Container 变现为一个运行在 Linux 操作系统之上的容器进程,是 Docker 服务交付的最终形式。

  • 用户通过指定 Image,使得 Container 可以自定义 Rootfs 等文件系统。
  • 用户通过指定计算资源的配额,使得 Container 使用指定的计算资源。
  • 用户通过配置网络及其安全策略,使得 Container 拥有独立且安全的网络环境。
  • 用户通过指定运行的命令,使得 Container 执行指定的工作。

Docker 的安装(CentOS)

  • 常规安装
yum install -y wget

systemctl stop firewalld
systemctl disable firewalld

wget -P /etc/yum.repos.d/ https://download.docker.com/linux/centos/docker-ce.repo

yum install -y docker-ce
  • 安装指定的版本
# 查看安装版本
yum list docker-ce --showduplicates | sort -r

# 安装 Docker CE 的版本 18.03.0
yum install -y docker-ce-18.03.0.ce-1.el7.centos
  • 启动服务
systemctl start docker
systemctl enable docker
systemctl status docker

Docker 的 Image 与 Dockerfile

Image

Docker Image 就是一个只读的文件,作为创建 Docker Container 的模板。镜像是容器的基石,容器基于镜像启动,镜像就像是容器的源代码,保存了用于容器启动的各种条件。

Docker 支持通过扩展现有镜像,继而创建新的镜像。实际上,Docker Hub 中 99% 的镜像都是通过在 base image 中安装和配置需要的软件构建出来的。

Docker Image 是一个层叠的只读文件系统,结构如下:

  • bootfs(引导文件系统):与 Linux Kernel 交互的引导系统。
  • rootfs(root 文件系统):根文件系统,即 base image,可以是一种或多种操作系统,如:Ubuntu 或 CentOS,rootfs 永远是只读状态。
  • unionFS(联合文件系统):即所有 base image 之上的文件系统。Docker 应用了 union mount(联合加载技术),一次可以加载多个只读文件系统到 rootfs 之上,从外面看到的只是一个文件系统。union mount 将各层文件系统叠加到一起,使最终呈现出来的文件系统包含了所有底层文件系统和目录,这样的文件系统就是镜像。

一个镜像可以放到另一个镜像的顶部,位于下边的镜像叫做父镜像,依次类推,最底部的镜像叫做 base image,指的就是 rootfs,即 Ubuntu 或 CentOS 等。

当使用 Image 启动一个 Container 后,一个新的可写的文件系统被加载到镜像的顶部,即:可写层,通常也称作 “容器层”,“容器层” 之下的都叫 “镜像层”。

Container 中运行的程序就是在这个 “容器层” 中执行的。第一次启动 Container 时,“容器层” 是空的,当文件系统发生变化,都会应用到这一层。如果想修改一个文件,该文件首先会从 “容器层” 下边的 “镜像只读层” 复制到可写层,该文件的只读版本依然存在,但是已经被可写层中的该文件副本所隐藏。这个是 Docker 重要的写时复制(copy on write)机制。

  • Docker Image 的生命周期

Dockerfile

每个 Docker Container 都从一个 Dockerfile 开始。Dockerfile 是一个使用易于理解的语法编写的文本文件,描述如何生成 Docker Image,指定了容器的操作系统、编程语言、环境变量、文件位置、网络端口和其他组件等配置信息,当然还指定了容器启动后要执行的内容。

Docker Image 是 Docker Container 的 “内容载体”,本质是一个可移植文件,包含容器将运行哪些软件组件以及如何运行的规范,每个 Container 都是一个 Image 的实例。因为 Dockerfile 可能包含关于从在线资源库获取某些软件包的说明,所以需要注意指定正确的版本,否则 Dockerfile 可能会根据调用的时间不同生成不一致的镜像。但是一旦创建了一个 Image,它就是静态的。

  • 编写 Dockerfile
# vim Dockerfile
FROM centos

RUN yum install openssh-server -y 
RUN echo "root:123456" |chpasswd
RUN /etc/init.d/sshd start 

CMD ["/usr/sbin/sshd","-D"]

Dockerfile 指令大小写不敏感的,但通常使用大写,使用 # 作为注释,每一行只支持一条指令,每条指令可以携带多个参数。

Dockerfile 指令根据作用可以分为两种:

  • 构建指令:用于构建 Image,其指定的操作不会在运行 Image 的容器上执行;
  • 设置指令:用于设置 Image 的属性,其指定的操作将在运行 Image 的容器中执行。

构建指令

FROM(指定 base image)

必须在 Dockerfile 头部指定,后续的指令都依赖 FROM 指定的 base image。

该指令有两种格式:

# 指定 base image 为该 image 的最后修改的版本。
FROM <image>       

# 指定 base image 为该 image 的一个 tag 版本。
FROM <image>:<tag>  

MAINTAINER(指定镜像创建者信息)

指定 image 的创建者,当使用 docker inspect 查看时,会输出中有相应的字段记录该信息。

MAINTAINER <name>

RUN(指定构建镜像时执行的指令)

RUN 可以运行任何被 base image 支持的指令,例如:软件管理命令。

  • base image 为 ubuntu,则使用 apt install。
  • base image 为 centos,则使用 yum install。

该指令有两种格式:

RUN <command> (the command is run in a shell - `/bin/sh -c`)
RUN ["executable", "param1", "param2" ... ]  (exec form)

设置指令

ENV(设置环境变量)

ENV 有 2 种格式:

ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...

设置了环境变量之后,后续的 RUN 指令就都可以使用了,容器启动后,可以通过 docker inspect 指令查看这个环境变量,也可以通过指令 docker run --env key=value 设置或修改指定的环境变量。

使用环境变量:

ENV NODE_VERSION 7.2.0
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz"

WORKDIR(设置目录的切换)

相当于 cd 命令,可以多次切换,为后续的 RUN、CMD、ENTRYPOINT、COPY、ADD 等命令配置工作目录。

# 在 /p1/p2 下执行 vim a.txt
WORKDIR /p1/p2
RUN vim a.txt

CMD(设置容器启动时执行的操作)

一个 Dockerfile 只能生效一条 CMD 指令,如果有多个,则仅执行最后一条。CMD 指定执行的操作类型可以是二进制程序、自定义脚本,或操作系统指令。

该指令有 3 种格式:

# like an exec, this is the preferred form
CMD ["executable","param1","param2"] 

# as a shell
CMD command param1 param2

# as default parameters to ENTRYPOINT
CMD ["param1", "param2"]

ENTRYPOINT(设置容器启动时执行的操作)

ENTRYPOINT 与 CMD 类似,用于指定一个可执行程序或脚本的路径,该程序会脚本会以 param1 和 param2 作为参数。

ENTRYPOINT ["executable", "param1", "param2"] (like an exec, the preferred form)
ENTRYPOINT command param1 param2 (as a shell)

与 CMD 的区别在于 ENTRYPOINT 不仅仅具有 CMD 的功能,还能够与 CMD 结合使用。

  1. 独自使用:当独自使用时,如果同一个 Dockerfile 也编写了 CMD 命令,且 CMD 是一个完整的可执行的命令,那么 CMD 指令和 ENTRYPOINT 指令会互相覆盖,且只有最后的一条 CMD 或者 ENTRYPOINT 指令生效。

  2. 和 CMD 指令配合使用:使用 CMD 来指定 ENTRYPOINT 的默认参数,这时 CMD 指令不是一个完整的可执行命令,仅仅提供了 parameters 部分。例如:

FROM ubuntu

CMD ["-l"]
ENTRYPOINT ["/usr/bin/ls"]

User Case1:使用 CMD 提供经常会被动态修改的参数部分,把稳定的参数部分写到 ENTRYPOINT,表示启动不同的 containe 时,可以灵活修改不同的参数部分。

FROM ubuntu

CMD ["-c"]

ENTRYPOINT ["top", "-b"]

# 使用
docker run -it --rm --name test top -H
# 效果
top -b -H

User Case2:启动容器就是启动主进程,但有些时候,启动主进程前,需要一些准备工作。比如 mysql 之类的数据库,可能需要一些数据库配置、初始化的工作,这些工作要在最终的 MySQL 服务器运行之前解决。这种情况下,可以写一个脚本,然后放入 ENTRYPOINT 中去执行,而这个脚本会将接到 CMD 的参数,并在脚本最后执行。比如 Redis 的官方镜像中就是这么做的:

FROM alpine:3.4
...
RUN addgroup -S redis && adduser -S -G redis redis
...
ENTRYPOINT ["docker-entrypoint.sh"]
EXPOSE 6379
CMD [ "redis-server" ]

# docker-entrypoint.sh
#!/bin/sh
...
# allow the container to be started with `--user`
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
    find . \\! -user redis -exec chown redis '' +
    exec gosu redis "$0" "$@"
fi

exec "$@"

该脚本的内容就是根据 CMD 的内容来判断,如果是 redis-server 的话,则切换到 redis 用户身份启动服务器,否则依旧使用 root 身份执行。

USER(设置启动容器的用户)

默认为 root 用户。指定 memcached 的运行用户例如:

ENTRYPOINT ["memcached"]
USER daemon
# 或
ENTRYPOINT ["memcached", "-u", "daemon"]

EXPOSE(设置容器需要映射到宿主机的端口)

该指令会将容器中的端口映射成宿主机中的某个端口,外部可以通过宿主机的 IP:Port 来访问容器的服务 Socket。

要实现这个效果需要两个步骤:

  1. 首先在 Dockerfile 中使用 EXPOSE 指令设置需要映射的容器端口。EXPOSE 指令可以一次设置多个端口号。
  2. 然后在运行容器的时使用 -p 选项加上 EXPOSE 所设置的端口。可以配套的多次使用 -p 选项。
# 映射一个端口
EXPOSE port1

# 相应的运行容器使用的命令 [主机端口:容器端口]
docker run -p host_port1:port1 image

这样 EXPOSE 设置的端口号会被随机映射成宿主机中的一个端口号了。当然,也可以指定需要映射到宿主机的具体端口号,这时首先就需要确保宿主机上的端口号没有被占用。

ADD 和 COPY

Dockerfile 中提供了两个非常相似的命令 COPY 和 ADD:

  • ADD:设置从 HostSrc 复制文件到 ContainerDest
  • COPY:如果仅仅是把本地的文件拷贝到容器镜像中,COPY 会更合适。
ADD <src> <dest>
COPY <src> <dest>

ADD 和 COPY 拥有相同的特点,只复制目录中的内容而不包含目录自身:

  • src 的路径是 docker build 目录的相对路径,也可以是一个远程的文件 URL;dest 是容器的绝对路径。
  • 如果 src 是文件且 dest 中不使用 “/” 斜杠结束,则会将 dest 也视为文件,src 的内容会写入 dest。
  • 如果 src 是文件且 dest 中使用 “/” 斜杠结束,则会将 src 文件拷贝到 dest 目录下。
  • 如果 src 是一个目录,那么会将该目录下的所有文件添加到 dest 中,不包括 src 目录本身。
  • 如果 src 是文件且是可识别的压缩格式,则进行解压缩(注意压缩格式)。

注意,所有从 HostOS 拷贝到容器中的文件或目录的权限为均为 0755,uid 和 gid 为 0。

COPY 区别于 ADD 的一个用法是在 multi-stage(多级别)场景下。在 multi-stage 场景中,可以使用 COPY 把前一阶段构建的产物拷贝到另一个镜像中,比如:其中的 COPY 命令通过指定 --from=0 参数,把前一阶段构建的产物拷贝到了当前的镜像中。

# Dockerfile1
FROM golang:1.7.3
WORKDIR /go/src/github.com/sparkdevo/href-counter/
RUN go get -d -v golang.org/x/net/html
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

# Dockerfile2
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/sparkdevo/href-counter/app .
CMD ["./app"]

而 ADD 除了不能用在 multistage 的场景下,ADD 命令可以完成 COPY 命令的所有功能,并且还可以完成两类超酷的功能:

  1. 解压压缩文件并把它们添加到镜像中。
  2. 从 URL 拷贝文件到镜像中。
WORKDIR /app
ADD nickdir.tar.gz .
# or
ADD http://example.com/big.tar.xz /usr/src/things/ # 推荐使用 curl

VOLUME(设置卷的挂载点)

设置卷的挂载点是为了让容器中的一个目录具有持久化存储数据的功能,该目录可以被容器本身使用,也可以共享给其他容器使用。

容器本身使用的文件系统是 AUFS,这种文件系统不能持久化数据,当容器关闭后,所有的更改都会丢失,即:容器的无状态性,或者称之为不变性。

所以,当容器中的应用需要持久化数据时就可以使用 VOLUME 指令来挂载一个宿主机的目录到容器,使用宿主机的文件系统来进行持久化。

FROM base
VOLUME ["src", "dest"]

需要注意的是,与 CLI 方式不同,-v /src:/dest 可以指定 HostOS 和 Container 的绝对路径,但通过 VOLUME 指令创建的挂载点,却无法指定 HostOS 对应的目录,是自动生成的。

    "Mounts": [
        
            "Name": "d411f6b8f17f4418629d4e5a1ab69679dee369b39e13bb68bed77aa4a0d12d21",
            "Source": "/var/lib/docker/volumes/d411f6b8f17f4418629d4e5a1ab69679dee369b39e13bb68bed77aa4a0d12d21/_data",
            "Destination": "/data1",
            "Driver": "local",
            "Mode": "",
            "RW": true
        
    ],

Image build

docker image build  -t repo:tag Dockerfile_dir_path
# or
docker build --build-arg http_proxy=http://<IP>:<PORT> --build-arg https_proxy=http://<IP>:<PORT> -t repo:tag Dockerfile_dir_path

构建镜像的过程中,docker daemon 会启动一个临时的 container 用于运行 Dockerfile Commands,同时还会构建一个临时的 image。当我们在 building 的过程中出现问题时,可以临时进入这个 image 中进行调试。

这样做的好处有两个:

  1. 方便调试。
  2. 方便缓存过程,无需重头再来。

例如:

$ docker image build -t vim-ide:gcc-9.1.0 ./gcc-9.1.0/

$ docker ps -a
CONTAINER ID   IMAGE                   COMMAND                  CREATED          STATUS                     PORTS     NAMES
24d4c83c959d   0ee0a12e5deb            "/bin/sh -c 'bash ./…"   40 seconds ago   Up 39 seconds                        nifty_mendel

$ docker images
REPOSITORY   TAG              IMAGE ID       CREATED          SIZE
<none>       <none>           0ee0a12e5deb   30 seconds ago   2.11GB

$ docker run --rm -it --entrypoint bash image_id

Build Context

在通过 docker build Dockerfile 创建镜像时,具有一个 build context 的概念。本质是 docker build 的 PATH 或 URL 指定的路径中的文件的集合。

在 docker build 的过程中经常会引用不同的文件,而 Build Context 就是为了指定这些文件的引用路径,从而支持 COPY 和 ADD 命令。换句话说,COPY 和 ADD 命令不能够引用 Build Context 之外的文件。

例如:docker build -t testx . 命令中的 . 就表示 Build Context 为当前目录。当然我们也可以指定一个 Build Context。

Build 镜像层

善于利用 Docker Build 镜像层,将有利于加速镜像构建。

例如:把那些最不容易发生变化的文件的拷贝操作放在较低的镜像层中,这样在重新 build 镜像时就会使用前面 build 产生的缓存(using cache)。

Docker 的基本使用

镜像操作

# 查看本地镜像
docker images

# 搜索仓库的镜像
docker search

# 拉取仓库的镜像
docker pull docker.io/centos

# 利用 DockerFile 创建镜像
docker build <dockerfile>

# 删除已经终止的容器
docker rm
# 可以删除正在运行的容器
docker -f rm 

# 跟据 ID 将镜像保存成一个文件。
docker save <image_id> > <image_name>.tar

# 同时将多个 image 打包成一个文件。
docker save postgres:9.6 mongo:3.4 -o <image_name>.tar

# 将容器提交为镜像
# OPTIONS:
# -a:提交的镜像作者
# -m:提交时的说明文字
# -p:提交时将容器暂停
# -c:使用 Dockerfile 指令来创建镜像
docker commit [OPTIONS] CONTAINER_ID [REPOSITORY[:TAG]]

# 将容器保存成一个文件。
docker export <container_id> > <image_name>.tar

# 从本地将镜像导入
docker load --input centos.tar
# 或
docker load < centos.tar


# 修改镜像标签
docker tag  <OLD REPOSITORY>:<OLD TAG>  <NEW REPOSITORY>:<NEW TAG>

# Push 指定的 image 到指定的 repo。
docker tag 300e315adb2f 172.27.100.93/developer/centos:latest
docker push 172.27.100.93/developer/centos:latest

容器操作

# 创建一个容器但不启动它
docker create 

# 创建并启动一个容器
docker run 

# 停止容器运行,发送信号 SIGTERM
docker stop 

# 启动一个停止状态的容器
docker start 

# 重启一个容器
docker restart

# 删除一个容器
docker rm 

# 发送信号给容器,默认为信号 SIGKILL
docker kill 

# 进入到一个正在运行的容器
docker attach 

# 阻塞一个容器,直到容器停止运行
docker wait 

# 显示状态为运行(Up)的容器
docker ps 

# 显示所有容器,包括运行中(Up)的和退出的(Exited)
docker ps -a 

# 深入容器内部获取容器所有信息
docker inspect 

# 查看容器的日志(stdout/stderr)
docker logs 

# 得到 Docker Server 的实时的事件
docker events 

# 显示容器的端口映射
docker port 

# 显示容器的进程信息
docker top 

# 显示容器文件系统的前后变化
docker diff 

# 在容器里执行一个命令,可以执行 bash 进入交互模式
docker exec

# docker 复制文件
docker cp mycontainer:/opt/testnew/file.txt /opt/test/

Docker 的容器网络技术原理

CNM 标准规范

Docker 的网络架构是建立在一系列称为 CNM(Container Networking Model,容器网络模型)的接口之上的。

CNM 的设计哲学是为了提供跨多种基础设施的应用可移植性。这一模型在应用可移植性和充分利用基础设施自有特性、能力之间,取得了一个平衡。

Libnetwork 是 CNM 标准的实现,Libnetwork 提供 Docker 守护程序和网络驱动程序之间的接口。网络控制器负责将驱动程序与网络配对。每个驱动程序负责管理其拥有的网络,包括提供给该网络的服务。每个网络有一个驱动程序,多个驱动程序可以与连接到多个网络的容器同时使用。

CNM 网络模型有 3 个组件,它们全部都是操作系统和基础硬件不可感的,因此应用可以在任何基础设施栈中拥有一致的表现。

  1. Sandbox(沙箱):一个沙箱作为容器的网络协议栈,包括:对容器接口、路由表以及 DNS 配置的管理。可以是 Linux Network Namespace、FreeBSD Jail 或是其他类似的技术。一个 Sandbox 可能包含多个 Endpoint。
  2. Endpoint(端点):端点负责将沙箱与网络相连,端点使得实际的网络连接可以从应用中抽象出来。这有助于维持可移植性,使服务可以采用不同的网络驱动,而无需顾虑如何与网络相连。可以是 Linux vEth Pair 设备,一端属于 Network,一端属于 Sandbox。
  3. Network:CNM 并不是用 OSI 模型中的概念来诠释 “网络”,网络就是一个相互连通的若干端点的集合,与网络不连通的端点不具有网络连通性。网络部件的实现可以是 Linux bridge,VLAN,VxLAN 等等,由一组能够相互通信的 Endpoint 组成。对于跨主机网络来说本质上是一个 Overlay 网络,这也是 Docker Swarm 网络的实现方式。

例如:

  • 容器的 Sandbox 上至少有两个 Endpoints。
  • gwbridge 是为了访问外部网络,br0 是为了容器间互通。
  • 为了跨主机通信还有一个全局的 KV 数据库(这里用的 Consul)。
  • 容器间通信是通过 VxLAN 实现的。

CNM 驱动接口

CNM 提供了两个可插拔的开放接口,供用户、社区和供应商使用,以更好地利用网络中的其他功能、可见性或可控性。

网络驱动

Docker 的网络驱动提供使网络运行的实际实现。它们是可插拔的,因此可以使用不同的驱动程序并轻松互换以支持不同的用例。可以在给定的 Docker Engine 或群集上同时使用多个网络驱动程序,但每个 Docker 网络仅通过单个网络驱动程序进行实例化。有两种类型的 CNM 网络驱动程序:

  • 原生网络驱动:是 Docker Engine 的原生部分,由 Docker 提供。有多种驱动程序可供选择,支持不同的功能,如覆盖网络或本地网桥。
  • 远程网络驱动:是社区和其他供应商创建的网络驱动程序。这些驱动程序可用于和现有软硬件相集成。用户还可以在需要用到现有网络驱动程序不支持的特定功能的情况下创建自己的驱动程序。

IPAM 驱动

  • 原生 IPAM(IP 地址管理)驱动程序:可以为 Docker 集群全局简单为网络和端点分配默认子网或 IP 地址,并防止重复分配。IP 地址也可以通过网络、容器和服务创建命令手动分配。

  • 远程 IPAM 驱动程序:使用来自其他供应商和社区的远程 IPAM 驱动程序的接口。这些驱动程序可以提供与现有供应商或自建 IPAM 工具的集成。

Docker 原生网络驱动

Docker 原生网络驱动程序是 Docker Engine 的一部分,不需要任何额外的模块。它们通过标准 Docker 网络命令调用和使用。共有以下几种原生网络驱动程序。它决定了容器之间、容器与外界之前的通信方式。

  • 基础网络类型


Docker 网络驱动程序具有 “范围(SCOPE)” 的概念。网络范围是驱动程序的作用域,可以是本地范围或 Swarm 集群范围。

  • 本地范围驱动程序在主机范围内提供连接和网络服务(如 DNS 或 IPAM)。
  • Swarm 范围驱动程序提供跨群集的连接和网络服务。

集群范围网络在整个群集中具有相同的网络 ID,而本地范围网络在每个主机上具有唯一的网络 ID。

  • 查看所有容器网络类型:
$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
c79756cf9cde        bridge              bridge              local
204025a5abbc        host                host                local
9b9024f5ac40        macvlan             macvlan             local
6478888548d8        none                null                local
p2e02u1zhn8x        overlay             overlay             swarm

Bridge 模式(默认)

当 Docker Daemon 启动后,会在 HostOS 上创建一个名为 docker0 的 Linux Bridge,它处于 Host Network Namespace。

在此 HostOS 上启动使用了 Bridge 模式的 Container 时:

  1. Docker Daemon 首先会为新的 Container 创建一个 Container Network Namespace。
  2. 然后 Docker Daemon 原生的 IPAM 会从 docker0(一个虚拟的 L2 网络)子网中分配一个 IP 地址给 Container 使用,并设置 docke

    以上是关于云计算与云原生 — Docker 容器技术完全解析的主要内容,如果未能解决你的问题,请参考以下文章

    云计算与云原生 — 微服务架构 Kong APIGW 完全解析

    容器:docker,k8s与云原生入门介绍

    云计算与云原生 — 云原生微服务架构的技术内涵

    云原生与云原生应用概念解析

    Docker和云计算的关系是啥?

    Docker和云计算的关系是啥?