目录
文章目录
- 构建指令
- 设置指令
- ENV(设置环境变量)
- WORKDIR(设置目录的切换)
- CMD(设置容器启动时执行的操作)
- ENTRYPOINT(设置容器启动时执行的操作)
- USER(设置启动容器的用户)
- EXPOSE(设置容器需要映射到宿主机的端口)
- ADD 和 COPY
- VOLUME(设置卷的挂载点)
- Bridge 模式(默认)
- 用户自定义 Bridge 网络
- Host 模式
- 外部访问容器(容器的端口映射)
- MACVLAN 模式
- 使用 MACVLAN 进行 VLAN 中继
- 使用 MACVLAN 实现跨主机容器之前的通信
- Container 模式
- None 模式(隔离)
- 数据卷管理
- 数据容器管理
- 注册表操作
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 并不困难,主要是两句口号:
- “Build, Ship and Run.”:构建、分发、运行。
- “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 建立通信:
- tcp://host:port
- unix:path_to_socket;fd://socketfd
- 通过设置命令行参数设置 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 执行环境的定制。可以分为以下三类驱动:
- Graphdriver
- Networkdriver
- Execdriver
Graphdriver
Graphdriver 用于完成 Image 的管理,包括存储与获取。当用户需要下载指定的镜像时,Graphdriver 就将镜像存储在本地的指定目录;当用户需要使用指定的镜像来创建容器的 Rootfs 时,Graphdriver 就从本地镜像存储目录中获取指定的容器镜像。
在 Graphdriver 初始化之前,有 4 种文件系统或类文件系统在其内部注册,它们分别是:
- Aufs
- Btrfs
- Vfs
- 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 结合使用。
- 独自使用:当独自使用时,如果同一个 Dockerfile 也编写了 CMD 命令,且 CMD 是一个完整的可执行的命令,那么 CMD 指令和 ENTRYPOINT 指令会互相覆盖,且只有最后的一条 CMD 或者 ENTRYPOINT 指令生效。
- 和 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
...
# 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。
要实现这个效果需要两个步骤:
- 首先在 Dockerfile 中使用 EXPOSE 指令设置需要映射的容器端口。EXPOSE 指令可以一次设置多个端口号。
- 然后在运行容器的时使用 -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 命令的所有功能,并且还可以完成两类超酷的功能:
- 解压压缩文件并把它们添加到镜像中。
- 从 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 中进行调试。
这样做的好处有两个:
- 方便调试。
- 方便缓存过程,无需重头再来。
例如:
$ 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 个组件,它们全部都是操作系统和基础硬件不可感的,因此应用可以在任何基础设施栈中拥有一致的表现。
- Sandbox(沙箱):一个沙箱作为容器的网络协议栈,包括:对容器接口、路由表以及 DNS 配置的管理。可以是 Linux Network Namespace、FreeBSD Jail 或是其他类似的技术。一个 Sandbox 可能包含多个 Endpoint。
- Endpoint(端点):端点负责将沙箱与网络相连,端点使得实际的网络连接可以从应用中抽象出来。这有助于维持可移植性,使服务可以采用不同的网络驱动,而无需顾虑如何与网络相连。可以是 Linux vEth Pair 设备,一端属于 Network,一端属于 Sandbox。
- 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 时:
- Docker Daemon 首先会为新的 Container 创建一个 Container Network Namespace。
- 然后 Docker Daemon 原生的 IPAM 会从 docker0(一个虚拟的 L2 网络)子网中分配一个 IP 地址给 Container 使用,并设置 docker0 自身的 IP 地址为 Container 的 Default GW,容器接口的 MAC 地址是动态生成的。
- 同时,Docker Daemon 还会在 HostOS 上创建一对 veth pair 虚拟网线设备,然后将 veth pair 设备的一端插入新建的 Container 中,并命名为 eth0(容器的网卡),另一端插入 docker0 Linux Bridge 中,以 vethxxx 格式命名。
Bridge 模式使得只有在同一个 Bridge 网络中的 Containers 之间可以相互通信,外界想要访问到这个网络中的 Containers 也同样需要接入 Bridge 网络并通过 iptables 做了 DNAT 规则,实现内外部地址转换。Container 可以具有任意个 Bridge 模式的网络接口。
可见,Bridge 模式的 Docker 网络基于 Linux 的虚拟网络技术来实现。Docker Container 的网络接口默认都是虚拟接口,可以充分发挥数据在不同 Container 之间或跨主机的 Container 之间的转发效率。这是因为 Linux 虚拟网络技术通过在内核中的数据复制来实现虚拟接口之间的数据转发,即:发送接口的发送缓存中的数据包将被直接复制到接收接口的接收缓存中,而无需通过外部物理网络设备进行交换。
$ ip a
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:46:c3:00:eb brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:46ff:fec3:eb/64 scope link
valid_lft forever preferred_lft forever
$ docker run -itd --name box1 busybox
$ docker exec -it box1 sh
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
6: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::42:acff:fe11:2/64 scope link
valid_lft forever preferred_lft forever
/ # ip r
default via 172.17.0.1 dev eth0
172.17.0.0/16 dev eth0 scope link src 172.17.0.2
$ brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.024246c300eb no vethd4ae072
用户自定义 Bridge 网络
默认情况下,Docker Daemon 会从以下范围分配一个子网 172.[17-31].0.0/16 或 192.168.[0-240].0/20 给 Bridge 设备,尽量的与 HostOS Interface 不重叠。
除了默认 Bridge 网络,用户也可以创建自己的 Bridge 网络,相当于在 HostOS 上设置了新的 Linux Bridge 设备。与默认 Bridge 网络不同,用户定义的 Bridge 网络支持手动分配 IP 地址和子网,否则 Docker Daemon 的 IPAM 驱动程序会进行隐式分配。
$ docker network create -d bridge --subnet 10.0.0.0/24 my_bridge
$ docker run -itd --name c2 --net my_bridge busybox sh
$ docker run -itd --name c3 --net my_bridge --ip 10.0.0.254 busybox sh
$ brctl show
bridge name bridge id STP enabled interfaces
br-b5db4578d8c9 8000.02428d936bb1 no vethc9b3282
vethf3ba8b5
docker0 8000.0242504b5200 no vethb64e8b8
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
b5db4578d8c9 my_bridge bridge local
e1cac9da3116 bridge bridge local
Host 模式
如果启动 Container 的时候使用 host 模式(Docker Host 网络驱动),那么这个容器将不会获得一个独立的 Linux Kernel Network Namespace,而是和 HostOS 共用一个 Network Namespace。
也就是说 Container 不会虚拟出自己的网卡,配置自己的 IP 等,而是直接使用 HostOS 的 IP 和端口。Host 网络中的所有 Containers 都能够在 HostOS Interface 上相互通信。可见,Host 模式对外界是完全开放的,能够访问到主机,就能访问到 Containers。
从网络角度来看,Containers 相当于直接在 HostOS 上运行的多个进程,它们使用相同的主机接口,所以任意两个 Containers 不能绑定到同一个 TCP 端口,否则会导致端口争用。
当然,Container 的其他方面,如:文件系统、进程列表等还是和 HostOS Namespace 隔离的。
在此示例中,HostOS、C1 和 nginx 都共享相同的 eth0 接口。
#Create containers on the host network
$ docker run -itd --net host --name C1 alpine sh
$ docker run -itd --net host --name nginx
#Show host eth0
$ ip add | grep eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP group default qlen 1000
inet 172.31.21.213/20 brd 172.31.31.255 scope global eth0
#Show eth0 from C1
$ docker run -it --net host --name C1 alpine ip add | grep eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP qlen 1000
inet 172.31.21.213/20 brd 172.31.31.255 scope global eth0
需要注意的是,因为 Host 模式不会新建 Network Namespace,所以端口映射、路由规则等一系列的网络协议栈操作都会被忽略。这意味着像 -p 和 --icc 这样的常见网络选型没有任何意义。
外部访问容器(容器的端口映射)
上述内容我们知道,当 Containers 处于同一个 Network(e.g. host、bridge、overlay)时,互相之间是可以互通的。但当 External Network 希望访问 Containers 时,还需要将容器的服务端口映射到主机上的某个端口上。并且,出于基本的安全考虑,我们通常需要配置一些端口安全策略。
# 自定义映射
docker run -d -p 8888:80 nginx:latest
# 随机映射(需要镜像支持)
docker run -P
- Ingress:显式进行端口映射。
- Egress:隐式 SNAT 到临时端口(通常在 32768 到 60999 的范围内)。
MACVLAN 模式
MACVLAN 网络设备是 Linux Kernel 的新特性,在 Linux kernel v3.9-3.19 和 v4.0+ 版本中支持,比较稳定的版本推荐 4.0+。
通过 MACVLAN 可以将一个 Host Physical NIC 虚拟成多个 Virtual Sub-NIC(虚拟网络子接口),这些 VNIs 可以拥有自己独立的 MAC/IP 地址,且与主机网络处在同一个 LAN 里面,共享同一个广播域。
有时候我们可能会需求对一块 Physical NIC 绑定多个 IP 以及多个 MAC 地址。绑定多个 IP 很容易,但是这些 IP 会共享 Physical NIC 的 MAC 地址,这样就无法满足我们的设计需求,所以就有了支持在同一块物理网卡上同时虚拟出 IP/MAC 地址的 MACVLAN 技术。
从实现效果上看,MACVLAN 与 Bridge 很类型,但相比,MACVLAN 没有了 Bridge 带来的 MAC 地址学习和翻译的负担,是一种高效直接的互联技术。
通过不同的子接口设备,MACVLAN 做到了流量的隔离。MACVLAN 会根据数据帧的 dstMAC 地址来判断这个二层帧需要交给哪张 VNI,VNI 再把包交给上层的内核协议栈处理。
MACVLAN 的使用场景包括:
- 超低延时应用。
- 设计一个网络,要求容器在同一子网内,并使用和外部主机网络相同的 IP 地址。
- 实现跨主机容器之前的通信。
此示例将 MACVLAN 网络绑定到主机上的 eth0 物理网卡,每个容器在物理网络 192.168.0.0/24 的子网上都有一个 IP 地址,并指定其 Default GW 是物理网络中的网关地址。需要注意的是,这个网关地址必须是主机外部的 Provider Underlay L3 网关地址,否者无法在不同的 MACVLAN 网络之间进行互访。
- 创建一个 macvlan 网络:
#Creation of MACVLAN network "mvnet" bound to eth0 on the host
$ docker network create -d macvlan --subnet 192.168.0.0/24 --gateway 192.168.0.1 -o parent=eth0 mvnet
- 设置网卡为混杂模式:由于不同的 Containers 可能拥有多个 MAC 地址,因此可能还需要在 HostOS Interface 上启用混杂模式,具体取决于 NIC 对 MAC 过滤的支持。
$ ip link set eth0 promisc on
- 创建使用 macvlan 网络的容器:
#Creation of containers on the "mvnet" network
$ docker run -itd --name c1 --net mvnet --ip 192.168.0.3 busybox sh
$ docker run -it --name c2 --net mvnet --ip 192.168.0.4 busybox sh
/ # ping 192.168.0.3
PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.052 ms
注意,虽然 MACVLAN 驱动程序提供了这些独特的优势,但它牺牲的是可移植性。MACVLAN 配置和部署与底层网络密切相关。除了防止重叠地址分配之外,容器寻址必须遵守容器放置的物理位置。因此,必须注意在 MACVLAN 网络外部维护 IPAM。重复的 IP 地址或不正确的子网可能导致容器连接丢失。
使用 MACVLAN 进行 VLAN 中继
通常情况下,将 VLAN 802.1q 中继到 Linux 主机是非常痛苦的,可能需要频繁的手动的对 HostOS VLAN Sub-Interface 的配置进行更改。使用 MACVLAN 技术则可以自动化的完成 MACVLAN 网络的子接口和其他配置的创建、销毁与持久化。
该示例中,使用了 HostOS Interface eth0 的 VLAN Sub-Interface 来进行 MACVLAN 网络的创建,它实现了将 VLAN 中继到 HostOS,在 L2 层对 Containers 网络进行隔离。
Docker Daemon 的 MACVLAN 驱动程序将会动创 VLAN Sub-Interface 并将它们连接到容器接口。继而实现处于不同 MACVLAN 网络中的 Containers 具有不同的 VLAN,除非在物理网络中进行 L3 网关路由流量,否则它们之间无法进行通信。
#Creation of macvlan10 network in VLAN 10
$ docker network create -d macvlan --subnet 192.168.10.0/24 --gateway 192.168.10.1 -o parent=eth0.10 macvlan10
#Creation of macvlan20 network in VLAN 20
$ docker network create -d macvlan --subnet 192.168.20.0/24 --gateway 192.168.20.1 -o parent=eth0.20 macvlan20
#Creation of containers on separate MACVLAN networks
$ docker run -itd --name c1--net macvlan10 --ip 192.168.10.2 busybox sh
$ docker run -it --name c2--net macvlan20 --ip 192.168.20.2 busybox sh
注意,必须将主机接口和上游交换机设置为 switch port mode trunk,以便在 HostOS Interface 上标记 VLAN。
使用 MACVLAN 实现跨主机容器之前的通信
#Creation of local macvlan network on both hosts
host-A $ docker network create -d macvlan --subnet 192.168.0.0/24 --gateway 192.168.0.1 -o parent=eth0 petsMacvlan
host-B $ docker network create -d macvlan --subnet 192.168.0.0/24 --gateway 192.168.0.1 -o parent=eth0 petsMacvlan
#Creation of db container on host-B
host-B $ docker run -d --net petsMacvlan --ip 192.168.0.5 --name db consul
#Creation of Web container on host-A
host-A $ docker run -it --net petsMacvlan --ip 192.168.0.4 -e 'DB=192.168.0.5:8500' --name Web chrch/docker-pets:1.0
Container 模式
Container 模式,又称为 Docker links,是一种 Docker Container 之间的通信机制。如果一个新容器链接到一个已有容器,新容器将会通过环境变量获得已有容器的链接信息。通过提供给信任容器有关已有容器的链接信息,实现容器间的通信。
Container 模式和 host 模式很类似,只是 Container 模式创建容器共享的是其他容器的 IP 和 Port 而不是物理机的,此模式容器自身是不会配置网络和端口,创建此模式的容器进去后会发现里边的 IP 是你所指定的那个容器 IP 并且 Port 也是共享的。当然,其它还是互相隔离的,如进程等。
docker run -it --network container:<container ID>
None 模式(隔离)
使用 None 模式 Container 会拥有自己的 Network Namespace,但是,并不为 Container 进行任何网络配置。也就是说,这个 Container 不会具有网卡、IP、路由等信息,只有一个 loopback 接口。所以,需要手动的为 Container 添加网卡、配置 IP 等。
因此,使用 None 网络的容器是完全隔离的,不会去参与网络通信,这样就能够保证容器的安全性。
$ docker run -itd --network none --name box3 busybox
$ docker exec -it box3 sh
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
Docker 远程网络驱动
以下社区和供应商创建的远程网络驱动程序与 CNM 兼容,每个都为容器提供独特的功能和网络服务。
- contiv:由 Cisco Systems 领导的开源网络插件,为多租户微服务部署提供基础架构和安全策略。Contiv 还为非容器工作负载和物理网络(如 ACI)提供兼容集成。Contiv 实现了远程网络和 IPAM 驱动。
- weave:作为网络插件,weave 用于创建跨多个主机或多个云连接 Docker 容器的虚拟网络。Weave 提供应用程序的自动发现功能,可以在部分连接的网络上运行,不需要外部群集存储,并且操作友好。
- calico:云数据中心虚拟网络的开源解决方案。它面向数据中心,大多数工作负载(虚拟机,容器或裸机服务器)只需要 IP 连接。Calico 使用标准 IP 路由提供此连接。工作负载之间的隔离都是通过托管源和目标工作负载的服务器上的 iptables 实现的,无论是根据租户所有权还是任何更细粒度的策略。
- kuryr:作为 OpenStack Kuryr 项目的一部分开发的网络插件。它通过利用 OpenStack 网络服务 Neutron 实现 Docker 网络(libnetwork)远程驱动程序 API。Kuryr 还包括一个 IPAM 驱动程序。
Docker 远程 IPAM 驱动
社区和供应商创建的 IPAM 驱动程序还可用于提供与现有系统或特殊功能的集成。
- infoblox:一个开源 IPAM 插件,提供与现有 Infoblox 工具的集成。
Docker 的容器存储技术原理
数据卷管理
核心选项:
- -v 宿主机目录:指定挂载到容器内的目录。
映射多个宿主机目录,只需要多写几个 -v 即可。
挂载时创建卷
- 挂载卷:
docker run -d -p 80:80 -v /data:/usr/share/nginx/html nginx:latest
- 设置共享卷,使用同一个卷启动一个新的容器:
docker run -d -p 8080:80 -v /data:/usr/share/nginx/html nginx:latest
创建卷后挂载
- 查看卷列表:
docker volume ls
- 查看未被容器使用的数据盘
docker volume ls -f dangling=true
- 创建一个卷:
$ docker volume create
f3b95f7bd17da220e63d4e70850b8d7fb3e20f8ad02043423a39fdd072b83521
$ docker volume ls
DRIVER VOLUME NAME
local f3b95f7bd17da220e63d4e70850b8d7fb3e20f8ad02043423a39fdd072b83521
- 查看卷路径:
$ docker volume inspect <volume_name>
[
{
"CreatedAt": "2018-02-01T00:39:25+08:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/clsn/_data",
"Name": "clsn",
"Options": {},
"Scope": "local"
}
]
- 使用卷创建容器:
docker run -d -p 9000:80 -v <volume_name>:/usr/share/nginx/html nginx:latest
- 删除卷
docker rm -v <volume_name>
数据容器管理
可以创建一个特殊的容器,来充当数据容器,也就是在创建容器时指定这个容器的数据盘,然后让其他容器可以使用这个数据容器作为他们的数据盘。
- 创建一个数据容器:
docker create -v /mnt -it --name newnginx docker.io/nginx /bin/bash
- 利用此数据容器容器运行一个容器
docker run --volumes-from newnginx --name nginx1 -it docker.io/nginx /bin/bash
Docker 的 Register
- 官方文档:https://docs.docker.com/registry/
Docker Registry 是一个存储 Images 的仓库。在 Docker 的运行过程中,Daemon 会与 Registry 通信,并实现了搜索镜像、下载镜像、上传镜像三个功能,这三个功能对应的 Job 分别为 search、pul 与 push。
Docker Registry 分为两大类型:
- Pubilc Registry:如 Docker Hub,可以从中找到来自开源项目、软件供应商、乃至个人账户的 Docker Image。
- Private Registry:这样可以保证容器镜像的获取在内网完成。
NOTE:Registry(注册表)和 Repository(仓库)是有区别的,Daemon 可以 Push/Pull Repository,也可以 Push/Pull Image。
- 每个 Registry 上可以存放多个 Repository。
- 每个 Repository 中又包含了多个 Images。
- 每个 Image 有着不同的 Tag(标签)。
当 Docker Server 的 Local Images 不存在时,才会到 Remote Registry/Repository 下载 Images。
Private Registry
Pubilc Registry 有着明显的缺陷:
- Push 和 Pull 的速度慢,带宽大。
- 隐私安全性差。
所以,在生产环节中的多数时候还是需要创建自己的 Private Registry。
搭建 Private Registry 有两种方式:
- 使用 Docker 提供的 docker-distribution:可以通过 Docker Container 或者 YUM 的方式安装。采用 Docker Container 安装的话,需要把 Image Storage Directory(镜像存储目录)挂载到宿主机的某目录下持久化数据,防止容器意外中止或者删除导致 Repository(仓库)不可用。此种 Registry 功能比较单一。
- 使用 Harbor:这是 VMware 基于 docker-distribution 二次开发的软件,功能强大,现已加入了 CNCF。
使用 docker-distribution 搭建 Private Registry
软件包安装
- 安装 docker-distribution,在 Host 192.168.1.226 上搭建 Private Registry。
$ yum -y install docker-distribution
# 镜像会储存在 /var/lib/registry 下,默认监听在 HTTP 5000 端口。
$ cat /etc/docker-distribution/registry/config.yml
version: 0.1
log:
fields:
service: registry
storage:
cache:
layerinfo: inmemory
filesystem:
rootdirectory: /var/lib/registry
http:
addr: :5000
$ systemctl start docker-distribution && systemctl enable docker-distribution && systemctl status docker-distribution
$ netstat -lpntu | grep 500
tcp6 0 0 :::5000 :::* LISTEN 8512/registry
- 从 Docker1 上传 Image 到 Private Registry。
# 配置 Private Registry 地址,并配置支持 HTTP insecure 方式推送镜像。
$ vi /etc/docker/daemon.json
{
"registry-mirrors": [
"https://hub-mirror.c.163.com",
"https://mirror.baidubce.com"
],
"insecure-registries": ["192.168.1.226:5000"]
}
$ systemctl restart docker.service
$ docker pull docker.io/busybox
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/busybox latest 69593048aa3a 7 days ago 1.24 MB
# Image repo name 的格式为 <private_registry_ip:port>/name 才可以与配置中的 insecure-registries 匹配上。
$ docker tag docker.io/busybox:latest 192.168.1.226:5000/busybox:latest
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
192.168.1.226:5000/busybox latest 69593048aa3a 7 days ago 1.24 MB
docker.io/busybox latest 69593048aa3a 7 days ago 1.24 MB
$ docker push 192.168.1.226:5000/busybox:latest
The push refers to a repository [192.168.1.226:5000/busybox]
5b8c72934dfc: Pushed
latest: digest: sha256:dca71257cd2e72840a21f0323234bb2e33fea6d949fa0f21c5102146f583486b size: 527
- 在 Private Registry 查看 Images 文件。
$ ll /var/lib/registry/docker/registry/v2/repositories/
drwxr-xr-x 5 root root 55 6月 15 23:08 busybox
- Docker2 从 Private Registry 拉取 Docker1 上传的 Images
$ vim /etc/docker/daemon.json
{
"insecure-registries": ["192.168.1.226:5000"]
}
$ systemctl restart docker.service
$ docker pull 192.168.1.226:5000/busybox
Using default tag: latest
Trying to pull repository 192.168.1.226:5000/busybox ...
latest: Pulling from 192.168.1.226:5000/busybox
b71f96345d44: Pull complete
Digest: sha256:dca71257cd2e72840a21f0323234bb2e33fea6d949fa0f21c5102146f583486b
Status: Downloaded newer image for 192.168.1.226:5000/busybox:latest
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
192.168.1.226:5000/busybox latest 69593048aa3a 7 days ago 1.24 MB
Docker Container 安装(推荐)
使用 Docker Container 来安装 docker-distribution 并搭建成为 Private Registry 是一种简易的手段。
# 将宿主机的 /opt/registry 挂载到容器的 /var/lib/registry。
$ docker run -d -v /opt/registry:/var/lib/registry -p 5000:5000 --restart=always --name registry registry:2
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/registry 2 1fd8e1b0bb7e 2 months ago 26.2 MB
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
59f0159a62c4 registry:2 "/entrypoint.sh /e..." 26 seconds ago Up 25 seconds 0.0.0.0:5000->5000/tcp registry
$ netstat -lpntu | grep 5000
tcp6 0 0 :::5000 :::* LISTEN 21070/docker-proxy-
- 查看 Docker Container 的进程:
$ docker exec -it -u root 59f0159a62c4 ps -ef
PID USER TIME COMMAND
1 root 0:00 registry serve /etc/docker/registry/config.yml
16 root 0:00 ps -ef
$ docker exec -it -u root 59f0159a62c4 cat /etc/docker/registry/config.yml
version: 0.1
log:
fields:
service: registry
storage:
cache:
blobdescriptor: inmemory
filesystem:
rootdirectory: /var/lib/registry
http:
addr: :5000
headers:
X-Content-Type-Options: [nosniff]
health:
storagedriver:
enabled: true
interval: 10s
threshold: 3
可见,跟我们手动安装的方式没有本质的区别。
注册表操作
- 创建仓库:
docker run -d -p 5000:5000 --restart=always --name registry -v /opt/myregistry:/var/lib/registry registry
- 修改配置文件,使之支持 HTTP
# cat /etc/docker/daemon.json
{
"registry-mirrors": ["https://registry.docker-cn.com"],
"insecure-registries": ["10.0.0.100:5000"]
}
- 重启生效
systemctl restart docker.service
搭建带 basic 认证的仓库:
- 安装加密工具
yum install httpd-tools -y
- 设置认证密码
mkdir /opt/registry-var/auth/ -p
htpasswd -Bbn fanguiju 123456 > /opt/registry-var/auth/htpasswd
- 启动 Registry 容器,在启动时传入认证参数
docker run -d -p 5000:5000 -v /opt/registry-var/auth/:/auth/ -e "REGISTRY_AUTH=htpasswd" -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd registry
- 使用验证用户测试
# 登陆用户
$ docker login 10.0.0.100:5000
Username: fanguiju
Password: 123456
Login Succeeded
# 推送镜像到仓库
$ docker push <Image ID>
The push refers to repository [10.0.0.100:5000/clsn/busybox]
4febd3792a1f: Pushed
1.0: digest: sha256:4cee1979ba0bf7db9fc5d28fb7b798ca69ae95a47c5fecf46327720df4ff352d size: 527
# 认证文件的保存位置
$ cat .docker/config.json
{
"auths": {
"10.0.0.100:5000": {
"auth": "Y2xzbjoxMjM0NTY="
},
"https://index.docker.io/v1/": {
"auth": "Y2xzbjpIenNAMTk5Ng=="
}
},
"HttpHeaders": {
"User-Agent": "Docker-Client/17.12.0-ce (linux)"
}
}