别在说自己不知道docker了,全文通俗易懂的给你说明白docker的基础与底层原理

docker介绍

Docker 是一个开源的应用容器引擎,基于Go语言进行开发实现并遵从Apache2.0 协议开源,基于 Linux 内核的 cgroup,namespace,以及 OverlayFS 类的 Union FS 等技术,对进程进行封装隔离,属于 操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。

Docker 在容器的基础上,进行了进一步的封装,从文件系统、网络互联到进程隔离等等,极大的简化了容器的创建和维护。使得 Docker 技术比虚拟机技术更为轻便、快捷。

Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的linux。

容器是完全使用沙箱机制,相互之间不会有任何接口,更重要的是容器性能开销极低。

Docker 从17.03版本之后分为CE(Community Edition:社区版)和EE(Enterprise Edition :企业版),

相对于社区版本,企业版本强调安全性,但需付费使用。所以咱们使用社区版就可以。

docker 发展历史

Docker 公司前身是 DotCloud,由 Solomon Hykes 在2010年成立,2013年更名 Docker。同年发布了 Docker-compose 组件提供容器的编排工具。

2014年 Docker 发布1.0版本,2015年Docker 提供 Docker-machine,支持 windows 平台。

在此期间,Docker 项目在开源社区大受追捧,同时也被业界诟病的是 Docker 公司对于 Docker 发展具有绝对的话语权,比如 Docker 公司推行了 libcontainer 难以被社区接受。

为了防止 Docker 这项开源技术被Docker 公司控制,在几个核心贡献的厂商,于是在一同贡献 Docker 代码的公司诸如 Redhat,谷歌的倡导下,成立了 OCI 开源社区。

OCI 开源社区旨在于将 Docker 的发展权利回归社区,当然反过来讲,Docker 公司也希望更多的厂商安心贡献代码到Docker 项目,促进 Docker 项目的发展。

于是通过OCI建立了 runc 项目,替代 libcontainer,这为开发者提供了除 Docker 之外的容器化实现的选择。

OCI 社区提供了 runc 的维护,而 runc 是基于 OCI 规范的运行容器的工具。换句话说,你可以通过 runc,提供自己的容器实现,而不需要依赖 Docker。当然,Docker 的发行版底层也是用的 runc。在 Docker 宿主机上执行 runc,你会发现它的大多数命令和 Docker 命令类似,感兴趣的读者可以自己实践如何用 runc 启动容器。

至2017年,Docker 项目转移到 Moby 项目,基于 Moby 项目,Docker 提供了两种发行版,Docker CE 和 Docker EE, Docker CE 就是目前大家普遍使用的版本,Docker EE 成为付费版本,提供了容器的编排,Service 等概念。Docker 公司承诺 Docker 的发行版会基于 Moby 项目。这样一来,通过 Moby 项目,你也可以自己打造一个定制化的容器引擎,而不会被 Docker 公司绑定。

docker 与虚拟机有何区别

Docker 不是虚拟化方法。它依赖于实际实现基于容器的虚拟化或操作系统级虚拟化的其他工具。

Docker 最初使用 LXC 驱动程序,然后移动到libcontainer ,现在重命名为 runc。

Docker 主要专注于在应用程序容器内自动部署应用程序。应用程序容器旨在打包和运行单个服务,而系统容器则设计为运行多个进程,如虚拟机。

因此,Docker 被视为容器化系统上的容器管理或应用程序部署工具。

  • 容器不需要引导操作系统内核,因此可以在不到一秒的时间内创建容器。此功能使基于容器的虚拟化比其他虚拟化方法更加独特和可取。
  • 由于基于容器的虚拟化为主机增加了很少或没有开销,因此基于容器的虚拟化具有接近本机的性能。
  • 对于基于容器的虚拟化,与其他虚拟化不同,不需要其他软件。
  • 主机上的所有容器共享主机的调度程序,从而节省了额外资源的需求。
  • 与虚拟机映像相比,容器状态(Docker 或 LXC 映像)的大小很小,因此容器映像很容易分发。
  • 容器中的资源管理是通过 cgroup 实现的。Cgroups 不允许容器消耗比分配给它们更多的资源。虽然主机的所有资源都在虚拟机中可见,但无法使用。这可以通过在容器和主机上同时运行 top 或 htop 来实现。所有环境的输出看起来都很相似。

docker 与传统虚拟机的对比结果如下:

特性 Docker 虚拟机
启动速度 秒级 分钟级
交付/部署 开发、测试、生产环境一致 无成熟体系
性能 近似物理机 性能损耗大
体量 极小(MB) 较大(GB)
迁移/扩展 跨平台、可复制 较为复杂

(1)传统虚拟机是需要安装整个操作系统的,然后再在上面安装业务应用,启动应用,通常需要几分钟去启动应用,而docker是直接使用镜像来运行业务容器的,其容器启动属于秒级别;

(2)Docker需要的资源更少,Docker在操作系统级别进行虚拟化,Docker容器和内核交互,几乎没有性能损耗,而虚拟机运行着整个操作系统,占用物理机的资源就比较多;

(3)Docker更轻量,Docker的架构可以共用一个内核与共享应用程序库,所占内存极小;同样的硬件环 境,Docker运行的镜像数远多于虚拟机数量,对系统的利用率非常高;

(4)与虚拟机相比,Docker隔离性更弱,Docker属于进程之间的隔离,虚拟机可实现系统级别隔离;

(5)Docker的安全性也更弱,Docker的租户root和宿主机root相同,一旦容器内的用户从普通用户权限 提升为root权限,它就直接具备了宿主机的root权限,进而可进行无限制的操作。虚拟机租户root权限 和宿主机的root虚拟机权限是分离的,并且虚拟机利用如Intel的VT-d和VT-x的ring-1硬件隔离技术,这 种技术可以防止虚拟机突破和彼此交互,而容器至今还没有任何形式的硬件隔离;

(6)Docker的集中化管理工具还不算成熟,各种虚拟化技术都有成熟的管理工具,比如:VMware vCenter提供完备的虚拟机管理能力;

(7)Docker对业务的高可用支持是通过快速重新部署实现的,虚拟化具备负载均衡,高可用、容错、迁移和数据保护等经过生产实践检验的成熟保障机制,Vmware可承诺虚拟机99.999%高可用,保证业务连续性;

(8)虚拟化创建是分钟级别的,Docker容器创建是秒级别的,Docker的快速迭代性,决定了无论是开发、测试、部署都可以节省大量时间;

(9)虚拟机可以通过镜像实现环境交付的一致性,但镜像分发无法体系化,Docker在Dockerfile中记录了容器构建过程,可在集群中实现快速分发和快速部署。

别在说自己不知道docker了,全文通俗易懂的给你说明白docker的基础与底层原理_第1张图片

docker的应用场景

(1)web应用的自动化打包和发布;

(2)自动化测试和持续集成、发布;

(3)在服务型环境中部署和调整数据库或其他的后台应用;

(4)从头编译或者扩展现有的OpenShift 或者Cloud Foundry平台来搭建自己的PaaS环境。

基本概念

镜像(image)

操作系统分为内核和用户空间。对于linux来说,内核启动后,会挂载root文件系统为其提供用户空间支持。而 Docker 镜像(Image),就相当于是一个 root 文件系统。比如官方镜像 ubuntu:18.04 就包含了完整的一套 Ubuntu 18.04 最小系统的 root 文件系统。

Docker 镜像 是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。

镜像 不包含 任何动态数据,其内容在构建之后也不会被改变。

由于镜像包含操作系统完整的 root 文件系统,其体积往往是庞大的,因此在 Docker 设计时,就充分利用 Union FS 的技术,将其设计为分层存储的架构。

容器(container)

容器是独立运行的一个或一组应用,是镜像运行时的实体。

镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。

容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的命名空间。因此容器可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间,甚至自己的用户 ID 空间。

容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。也因为这种隔离的特性,很多人初学 Docker 时常常会混淆容器和虚拟机。

每一个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,我们可以称这个为容器运行时读写而准备的存储层为容器存储层。

容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。

仓库(Repository)

Docker Registry 提供一个集中的存储、分发镜像的服务。

一个 Docker Registry 中可以包含多个 仓库(Repository);每个仓库可以包含多个 标签(Tag);每个标签对应一个镜像。

仓库名经常以 两段式路径 形式出现,比如 jwilder/nginx-proxy,前者往往意味着 Docker Registry 多用户环境下的用户名,后者则往往是对应的软件名。但这并非绝对,取决于所使用的具体 Docker Registry 的软件或服务。

docker 基本架构

Docker 采用了 C/S 架构,包括客户端和服务端。Docker 守护进程 (Daemon)作为服务端接受来自客户端的请求,并处理这些请求(创建、运行、分发容器)。

客户端和服务端既可以运行在一个机器上,也可通过 socket 或者 RESTful API 来进行通信。
别在说自己不知道docker了,全文通俗易懂的给你说明白docker的基础与底层原理_第2张图片
Docker 守护进程一般在宿主主机后台运行,等待接收来自客户端的消息。

Docker 客户端则为用户提供一系列可执行命令,用户用这些命令实现跟 Docker 守护进程交互。

docker底层实现

Linux 命名空间、控制组和 UnionFS 三大技术支撑了目前 Docker 的实现,也是 Docker 能够出现的最重要原因。

别在说自己不知道docker了,全文通俗易懂的给你说明白docker的基础与底层原理_第3张图片

namespace,命名空间

Namespace是将内核的全局资源做封装,使得每个Namespace都有一份独立的资源,因此不同的进程在各自的Namespace内对同一种资源的使用不会互相干扰。

namespace是容器隔离的基础,保证A容器看不到B容器.

目前linux内核总共实现了6中Namespace:

  • IPC:隔离System V IPC和POSIX消息队列。
  • Network:隔离网络资源。
  • Mount:隔离文件系统挂载点。
  • PID:隔离进程ID。
  • UTS:隔离主机名和域名。
  • User:隔离用户ID和组ID。

Cgroups(Control Group),控制组

Cgroup 属于Linux内核提供的一个特性,用于限制和隔离一组进程对系统资源的使用,也就是做资源QoS,这些资源主要包括CPU、内存、block I/O和网络带宽。

Cgroup从2.6.24开始进入内核主线,目前各大发行版都默认打开了Cgroup特性。

从实现的角度来看,Cgroup实现了一个通用的进程分组的框架,而不同资源的具体管理则是由各个Cgroup子系统实现的。

对实际资源的分配和管理是由各个Cgroup子系统完成的,下面介绍几个主要的子系统。

  • cpuset子系统:为一组进程分配指定的CPU和内存节点。
  • cpu子系统:用于限制进程的CPU占用率。
  • cpuacct子系统:用来统计各个Cgroup的CPU使用情况。
  • memory子系统:用来限制Cgroup所能使用的内存上限。
  • blkio子系统:用来限制Cgroup的block I/O带宽。
  • devices子系统:用来控制Cgroup的进程对哪些设备有访问权限。

实际上 Docker 是使用了很多 Linux 的隔离功能,让容器看起来像一个轻量级虚拟机在独立运行,容器的本质是被限制了的 Namespaces,cgroup,具有逻辑上独立文件系统,网络的一个进程。

unionfs 联合文件系统

UnionFS(联合文件系统)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。

UnionFS 最常见的用途是在 Linux 系统中实现可写的只读文件系统,在不改变原始文件系统内容的前提下,给文件系统添加或修改文件。它也可以用来创建一个有多个层次结构的文件系统,其中最上层是可读写的,而底部的层次都是只读的。

UnionFS 主要具有以下特点:

  • 联合不会涉及原有的文件系统,所以不会破坏原有的数据。
  • 可以支持多个只读文件系统和一个可读写的文件系统联合在一起,实现可写的只读文件系统。
  • 可以通过加入新的层次来扩展文件系统,同时保留原有的数据和结构。
  • 可以使用类似于栈的方式来控制文件系统的层次结构。

UnionFS是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。

不同 Docker 容器就可以共享一些基础的文件系统层,同时再加上自己独有的改动层,大大提高了存储的效率。

Docker 中使用的 AUFS(Advanced Multi-Layered Unification Filesystem)就是一种联合文件系统。 AUFS 支持为每一个成员目录(类似 Git 的分支)设定只读(readonly)、读写(readwrite)和写出(whiteout-able)权限, 同时 AUFS 里有一个类似分层的概念, 对只读权限的分支可以逻辑上进行增量地修改(不影响只读部分的)。

Docker 目前支持的联合文件系统包括 OverlayFS, AUFS, Btrfs, VFS, ZFS 和 Device Mapper。

容器格式

最初,Docker 采用了 LXC 中的容器格式。从 0.7 版本以后开始去除 LXC,转而使用自行开发的 libcontainer,从 1.11 开始,则进一步演进为使用 runC 和 containerd。

网络

Docker 的网络实现其实就是利用了 Linux 上的网络命名空间和虚拟网络设备(特别是 veth pair)。

Docker 中的网络接口默认都是虚拟的接口。虚拟接口的优势之一是转发效率较高。 Linux 通过在内核中进行数据复制来实现虚拟接口之间的数据转发,发送接口的发送缓存中的数据包被直接复制到接收接口的接收缓存中。对于本地系统和容器内系统看来就像是一个正常的以太网卡,只是它不需要真正同外部网络设备通信,速度要快很多。

Docker 容器网络就利用了这项技术。它在本地主机和容器内分别创建一个虚拟接口,并让它们彼此连通(这样的一对接口叫做 veth pair)。

Docker 创建一个容器的时候,会执行如下操作:

  • 创建一对虚拟接口,分别放到本地主机和新容器中;
  • 本地主机一端桥接到默认的 docker0 或指定网桥上,并具有一个唯一的名字,如 veth65f9;
  • 容器一端放到新容器中,并修改名字作为 eth0,这个接口只在容器的命名空间可见;
  • 从网桥可用地址段中获取一个空闲地址分配给容器的 eth0,并配置默认路由到桥接网卡 veth65f9。

完成这些之后,容器就可以使用 eth0 虚拟网卡来连接其他容器和其他网络。

可以在 docker run 的时候通过 --net 参数来指定容器的网络配置,有4个可选值:
(1)–net=bridge 这个是默认值,连接到默认的网桥。
(2) --net=host 告诉 Docker 不要将容器网络放到隔离的命名空间中,即不要容器化容器内的网络。此时容器使用本地主机的网络,它拥有完全的本地主机接口访问权限。容器进程可以跟主机其它 root 进程一样可以打开低范围的端口,可以访问本地网络服务比如 D-bus,还可以让容器做一些影响整个主机系统的事情,比如重启主机。因此使用这个选项的时候要非常小心。如果进一步的使用 --privileged=true,容器会被允许直接配置主机的网络堆栈。
(3)–net=container:NAME_or_ID 让 Docker 将新建容器的进程放到一个已存在容器的网络栈中,新容器进程有自己的文件系统、进程列表和资源限制,但会和已存在的容器共享 IP 地址和端口等网络资源,两者进程可以直接通过 lo 环回接口通信。
(4)–net=none 让 Docker 将新容器放到隔离的网络栈中,但是不进行网络配置。之后,用户可以自己进行配置。

使用 --net=none 后,可以自行配置网络,让容器达到跟平常一样具有访问网络的权限。通过这个过程,可以了解 Docker 配置网络的细节。

首先,启动一个 /bin/bash 容器,指定 --net=none 参数。

$ docker run -i -t --rm --net=none base /bin/bash

在本地主机查找容器的进程 id,并为它创建网络命名空间。

$ docker inspect -f '{{.State.Pid}}' 63f36fc01b5f
2778
$ pid=2778
$ sudo mkdir -p /var/run/netns
$ sudo ln -s /proc/$pid/ns/net /var/run/netns/$pid

检查桥接网卡的 IP 和子网掩码信息。

$ ip addr show docker0
21: docker0: ...
inet 172.17.42.1/16 scope global docker0

创建一对 “veth pair” 接口 A 和 B,绑定 A 到网桥 docker0,并启用它

$ sudo ip link add A type veth peer name B
$ sudo brctl addif docker0 A
$ sudo ip link set A up

将B放到容器的网络命名空间,命名为 eth0,启动它并配置一个可用 IP(桥接网段)和默认网关。

$ sudo ip link set B netns $pid
$ sudo ip netns exec $pid ip link set dev B name eth0
$ sudo ip netns exec $pid ip link set eth0 up
$ sudo ip netns exec $pid ip addr add 172.17.42.99/16 dev eth0
$ sudo ip netns exec $pid ip route add default via 172.17.42.1

以上,就是 Docker 配置网络的具体过程。

当容器结束后,Docker 会清空容器,容器内的 eth0 会随网络命名空间一起被清除,A 接口也被自动从 docker0 卸载。

此外,用户可以使用 ip netns exec 命令来在指定网络命名空间中进行配置,从而配置容器内的网络。

docker安装

自动化安装(推荐)

Docker 官方和国内daocloud 都提供了一键安装的脚本,使得Docker的安装更加便捷。

官方的一键安装方式:

curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun

别在说自己不知道docker了,全文通俗易懂的给你说明白docker的基础与底层原理_第4张图片

耐心等待即可完成Docker的安装。

docker手动在线安装 (新手)

(1)内核版本检查

Docker 要求 CentOS 系统的内核版本高于 3.10 ,查看本页面的前提条件来验证你的CentOS 版本是否支持 Docker 。

uname -r

在这里插入图片描述

(2)确保 yum 包更新到最新

使用 root 权限登录 Centos。确保 yum 包更新到最新。

sudo yum update

(3)卸载Docker(可选)

卸载历史版本。这一步是可选的,如果之前安装过旧版本的Docker,可以使用如下命令进行卸载:

yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-selinux \
docker-engine-selinux \
docker-engine \
docker-ce

(4) 安装必要的系统工具

在设置仓库之前,需先安裝所需的软件包。yum-utils提供了yum-config-manager,并且device mapper存储驱动程序需要device-mapper-persistent-data和lvm2。

 yum install -y yum-utils device-mapper-persistent-data lvm2

(5)设置源仓库

官方源地址:

 sudo yum-config-manager \--add-repo \https://download.docker.com/linux/centos/docker-ce.repo

注意: 官方的源地址比较慢。

阿里云源地址:

yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

(4) docker安装

执行命令安装最新版本的Docker Engine-Community 和 containerd。

执行缓存:

yum makecache fast

执行安装最新社区版docker

yum install docker-ce

docker-ce为社区免费版本。稍等片刻,docker即可安装成功。但安装完成之后的默认是未启动的,需要进行启动操作。

如需要docker-ce-cli或containerd.io或docker-buildx-plugin 或 docker-compose-plugin可执行如下命令:

yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

(5)启动并加入开机启动

# 启动docker
sudo systemctl start docker
# 开机启动
sudo systemctl enable docker

(6)验证docker是否安装成功

通过运行hello-world镜像来验证是否正确安装了Docker Engine-Community。

docker version

docker 离线安装

基础环境

  • 操作系统: Centos 7
  • Docker 下载地址:docker 24.0.5下载
  • 官方参考文档: 文档

docekr 安装

(1)下载

wget 

注意:如果事先下载好了可以忽略这一步

(2)解压

把压缩文件存在指定目录下(如root),并进行解压

tar -zxvf docker-24.0.5.tgz -C /root/

(3)将解压出来的docker文件内容移动到 /usr/bin/ 目录下

cp /root/docker/* /usr/bin/

(4)将docker注册为service

cat /etc/systemd/system/docker.service

vi /etc/systemd/system/docker.service

docker.service 内容如下:

[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
After=network-online.target firewalld.service
Wants=network-online.target

[Service]
Type=notify
# the default is not to use systemd for cgroups because the delegate issues still
# exists and systemd currently does not support the cgroup feature set required
# for containers run by docker
ExecStart=/usr/bin/dockerd
ExecReload=/bin/kill -s HUP $MAINPID

# Having non-zero Limit*s causes performance problems due to accounting overhead

# in the kernel. We recommend using cgroups to do container-local accounting.

LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity

# Uncomment TasksMax if your systemd version supports it.
# Only systemd 226 and above support this version.
#TasksMax=infinity
TimeoutStartSec=0

# set delegate yes so that systemd does not reset the cgroups of docker containers

Delegate=yes

# kill only the docker process, not all processes in the cgroup

KillMode=process

# restart the docker process if it exits prematurely

Restart=on-failure
StartLimitBurst=3
StartLimitInterval=60s


[Install]
WantedBy=multi-user.target

(5)启动

chmod +x /etc/systemd/system/docker.service #添加文件权限并启动docker

systemctl daemon-reload #重载unit配置文件

systemctl start docker #启动Docker

systemctl enable docker.service #设置开机自启

(6)验证

systemctl status docker #查看Docker状态

docker -v #查看Docker版本

docker info

调整镜像仓库

如果需要调整镜像仓库,可以按照以下方式进行调整,(建议使用默认的)

修改/etc/docker目录下的daemon.json文件,在文件中加入如下内容:

{
  "registry-mirrors": ["https://registry.docker-cn.com"]
}

保存退出,重启docker

#添加文件权限并启动docker
chmod +x /etc/systemd/system/docker.service

#重载unit配置文件
systemctl daemon-reload

#启动Docker 
systemctl restart docker

#设置开机自启
systemctl enable docker.service 

docker 文件目录

Docker默认的文件目录位于/var/lib/docker

别在说自己不知道docker了,全文通俗易懂的给你说明白docker的基础与底层原理_第5张图片

|-----containers:用于存储容器信息

|-----image:用来存储镜像中间件及本身信息,大小,依赖信息

|-----network

|-----swarm

|-----tmp:docker临时目录

|-----trust:docker信任目录

|-----volumes:docker卷目录

还可以通过docker指令确认文件的位置:

别在说自己不知道docker了,全文通俗易懂的给你说明白docker的基础与底层原理_第6张图片

docker 常用命令

操作容器

查看docker版本信息

docker --version

在这里插入图片描述

查看docker 的系统相关信息

[root@localhost ~]# docker info
Client:
 Context:    default
 Debug Mode: false
 Plugins:
  app: Docker App (Docker Inc., v0.9.1-beta3)
  buildx: Docker Buildx (Docker Inc., v0.9.1-docker)
  scan: Docker Scan (Docker Inc., v0.17.0)

Server:
 Containers: 5
  Running: 5
  Paused: 0
  Stopped: 0
 Images: 21
 Server Version: 20.10.19
 Storage Driver: overlay2
  Backing Filesystem: xfs
  Supports d_type: true
  Native Overlay Diff: true
  userxattr: false
 Logging Driver: json-file
 Cgroup Driver: cgroupfs
 Cgroup Version: 1
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive
 Runtimes: io.containerd.runc.v2 io.containerd.runtime.v1.linux runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 9cd3357b7fd7218e4aec3eae239db1f68a5a6ec6
 runc version: v1.1.4-0-g5fd4c4d
 init version: de40ad0
 Security Options:
  seccomp
   Profile: default
 Kernel Version: 3.10.0-1160.76.1.el7.x86_64
 Operating System: CentOS Linux 7 (Core)
 OSType: linux
 Architecture: x86_64
 CPUs: 4
 Total Memory: 7.513GiB
 Name: localhost.localdomain
 ID: 4V4D:DEH4:4I6N:WBQA:GNUN:PROP:NT35:RK7N:43FA:BHHJ:EK2E:CWJE
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Registry Mirrors:
  https://yxzrazem.mirror.aliyuncs.com/
 Live Restore Enabled: false

查看守护进程

systemctl status docker

别在说自己不知道docker了,全文通俗易懂的给你说明白docker的基础与底层原理_第7张图片

查看docker相关的进程

ps -ef |grep docker

别在说自己不知道docker了,全文通俗易懂的给你说明白docker的基础与底层原理_第8张图片

linux 下 启动docker

systemctl start docker

linux下关闭docker

systemctl stop docker

linux下重启docker

systemctl restart docker

linux下设置随服务启动而启动

systemctl enable docker

启动容器

docker start 镜像名称

例如:
docker start redis

停止容器

docker stop 镜像名称

例如:
docker stop redis

重启容器

docker restart

例如:
docker restart redis

创建一个新的容器并运行一个命令

docker run

例如:

docker run -d  \
--restart=always \
--name rmqbroker \
--link rmqnamesrv:namesrv \
-p 8083:10911 \
-p 8084:10909 \
-v  /usr/local/rocketmq/data/broker/logs:/root/logs \
-v  /usr/local/rocketmq/data/broker/store:/root/store \
-v /usr/local/rocketmq/conf/broker.conf:/opt/rocketmq-4.4.0/conf/broker.conf \
-e "NAMESRV_ADDR=namesrv:8082" \
-e "MAX_POSSIBLE_HEAP=200000000" \
rocketmqinc/rocketmq \
sh mqbroker -c /opt/rocketmq-4.4.0/conf/broker.conf 

详细参数:

-i, --interactive=false   打开STDIN,用于控制台交互
-t, --tty=false            分配tty设备,该可以支持终端登录,默认为false
-d, --detach=false         指定容器运行于前台还是后台,默认为false
-u, --user=""              指定容器的用户
-a, --attach=[]            登录容器(必须是以docker run -d启动的容器)
-w, --workdir=""           指定容器的工作目录
-c, --cpu-shares=0        设置容器CPU权重,在CPU共享场景使用
-e, --env=[]               指定环境变量,容器中可以使用该环境变量
-m, --memory=""            指定容器的内存上限
-P, --publish-all=false    指定容器暴露的端口
-p, --publish=[]           指定容器暴露的端口
-h, --hostname=""          指定容器的主机名
-v, --volume=[]            给容器挂载存储卷,挂载到容器的某个目录    顺序:主机:容器
--volumes-from=[]          给容器挂载其他容器上的卷,挂载到容器的某个目录
--cap-add=[]               添加权限,权限清单详见:http://linux.die.net/man/7/capabilities
--cap-drop=[]              删除权限,权限清单详见:http://linux.die.net/man/7/capabilities
--cidfile=""               运行容器后,在指定文件中写入容器PID值,一种典型的监控系统用法
--cpuset=""                设置容器可以使用哪些CPU,此参数可以用来容器独占CPU
--device=[]                添加主机设备给容器,相当于设备直通
--dns=[]                   指定容器的dns服务器
--dns-search=[]            指定容器的dns搜索域名,写入到容器的/etc/resolv.conf文件
--entrypoint=""            覆盖image的入口点
--env-file=[]              指定环境变量文件,文件格式为每行一个环境变量
--expose=[]                指定容器暴露的端口,即修改镜像的暴露端口
--link=[]                  指定容器间的关联,使用其他容器的IP、env等信息
--lxc-conf=[]              指定容器的配置文件,只有在指定--exec-driver=lxc时使用
--name=""                  指定容器名字,后续可以通过名字进行容器管理,links特性需要使用名字
--net="bridge"             容器网络设置:
                            bridge 使用docker daemon指定的网桥
                            host    //容器使用主机的网络
                            container:NAME_or_ID  >//使用其他容器的网路,共享IP和PORT等网络资源
                            none 容器使用自己的网络(类似--net=bridge),但是不进行配置
--privileged=false         指定容器是否为特权容器,特权容器拥有所有的capabilities
--restart="no"             指定容器停止后的重启策略:
                            no:容器退出时不重启
                            on-failure:容器故障退出(返回值非零)时重启
                            always:容器退出时总是重启
--rm=false                 指定容器停止后自动删除容器(不支持以docker run -d启动的容器)
--sig-proxy=true           设置由代理接受并处理信号,但是SIGCHLD、SIGSTOP和SIGKILL不能被代理

进入容器

docker exec

例如:
docker exec -it mysql /bin/bash

详细参数

(1)-d :分离模式: 在后台运行

(2)-i :即使没有附加也保持STDIN 打开

(3)-t :分配一个伪终端

查看正在运行的容器

docker ps [OPTIONS]

详细参数说明:

1、-a : 显示所有的容器,包括未运行的

在这里插入图片描述

2、-f : 根据条件过滤显示的内容 ,目前支持过滤器:

(1)id (容器的id)
(2)label
(3)name(容器名称)
(4)xited (整数-容器退出状态码,只有在使用-all才有用)
(5)tatus 容器状态(created,restarting,running,paused,exited,dead)
(6)ncestor 过滤从指定镜像创建的容器
(7)efore (容器的名称或id),过滤在给定id或名称之后创建的容器
(8)solation (default process hyperv) (windows daemon only)
(9)olume (数据卷名称或挂载点),--过滤挂载有指定数据卷的容器
(10)etwork(网络id或名称),过滤连接到指定网络的容器

在这里插入图片描述

条件虽多,但万变不离其宗,只要再记住以下 3 条准则:

(1)选项后跟的都是键值对 key=value (可不带引号),如果有多个过滤条件,就多次使用 filter 选项。例如:

docker ps --filter id=a1b2c3 --filter name=bingohuang

(2)相同条件之间的关系是或,不同条件之间的关系是与。例如:

docker ps --filter name=bingo --filter name=huang --filter status=running

以上过滤条件会找出 name 包含 bingo 或 huang 并且 status 为 running 的容器。

(3)id 和 name,支持正则表达式,使用起来非常灵活。例如:

docker ps --filter name=^/bingohuang$

精确匹配 name 为 bingohuang 的容器。注意,容器实际名称,开头是有一个正斜线 / ,可用 docker inspect 一看便知。

docker ps --filter name=.bingohuang.

匹配 name 包含 bingohuang 的容器,和 --filter name=bingohuang 一个效果。

最后, 举一个复杂点的例子,用于清理名称包含 bingohuang,且状态为 exited 或 dead 的容器:

docker rm $(docker ps -q --filter name=.bingohuang. --filter status=exited --filter status=dead2>/dev/null)

3、–format :指定返回值的模板文件。

当使用了 --format 选项,那么 ps 命令只会输出 template 中指定的内容:

[root@localhost ~]# docker ps --format "{{.ID}}: {{.Command}}"
6cb60535636c: "sh -c 'java $JAVA_O…"
187721c7fcf2: "sh mqbroker -c /opt…"
4cc8f9661adf: "sh mqnamesrv"
35e0f7357c42: "docker-entrypoint.s…"
513edf42f9ff: "docker-entrypoint.s…"

4、 -l :显示最近创建的容器。

在这里插入图片描述

5、-n :列出最近创建的n个容器。

在这里插入图片描述

6、 --no-trunc :不截断输出。
别在说自己不知道docker了,全文通俗易懂的给你说明白docker的基础与底层原理_第9张图片

7、-q :静默模式,只显示容器编号。

清理容器时非常好用,filter 过滤显示一节有具体实例。
别在说自己不知道docker了,全文通俗易懂的给你说明白docker的基础与底层原理_第10张图片

8、-s :显示总的文件大小。

别在说自己不知道docker了,全文通俗易懂的给你说明白docker的基础与底层原理_第11张图片

查看容器的进程信息

查看容器中运行的进程信息,支持 ps 命令参数。

docker top [OPTIONS] CONTAINER [ps OPTIONS]

在这里插入图片描述

查看容器信息

#
docker ps -a 

docker inspect  镜像id
例如:
docker inspect 35e0f7357c42

查询结果:

[root@localhost ~]# docker inspect 35e0f7357c42
[
    {
        "Id": "35e0f7357c420335c970e507a452e4f0678e68bfbcb9d5f36d9fc4e9f7e6198d",
        "Created": "2023-08-18T13:29:42.313267686Z",
        "Path": "docker-entrypoint.sh",
        "Args": [
            "redis-server",
            "/etc/redis/redis.conf",
            "--appendonly",
            "yes",
            "--requirepass",
            "123456"
        ],
        "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 2520,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2023-08-18T13:29:43.064705738Z",
            "FinishedAt": "0001-01-01T00:00:00Z"
        },
        "Image": "sha256:7614ae9453d1d87e740a2056257a6de7135c84037c367e1fffa92ae922784631",
        "ResolvConfPath": "/var/lib/docker/containers/35e0f7357c420335c970e507a452e4f0678e68bfbcb9d5f36d9fc4e9f7e6198d/resolv.conf",
        "HostnamePath": "/var/lib/docker/containers/35e0f7357c420335c970e507a452e4f0678e68bfbcb9d5f36d9fc4e9f7e6198d/hostname",
        "HostsPath": "/var/lib/docker/containers/35e0f7357c420335c970e507a452e4f0678e68bfbcb9d5f36d9fc4e9f7e6198d/hosts",
        "LogPath": "/var/lib/docker/containers/35e0f7357c420335c970e507a452e4f0678e68bfbcb9d5f36d9fc4e9f7e6198d/35e0f7357c420335c970e507a452e4f0678e68bfbcb9d5f36d9fc4e9f7e6198d-json.log",
        "Name": "/redis",
        "RestartCount": 0,
        "Driver": "overlay2",
        "Platform": "linux",
        "MountLabel": "",
        "ProcessLabel": "",
        "AppArmorProfile": "",
        "ExecIDs": null,
        "HostConfig": {
            "Binds": [
                "/usr/local/redis/conf/redis.conf:/etc/redis/redis.conf",
                "/usr/local/redis/data:/data"
            ],
            "ContainerIDFile": "",
            "LogConfig": {
                "Type": "json-file",
                "Config": {
                    "max-file": "2",
                    "max-size": "100m"
                }
            },
            "NetworkMode": "default",
            "PortBindings": {
                "6379/tcp": [
                    {
                        "HostIp": "",
                        "HostPort": "8081"
                    }
                ]
            },
            "RestartPolicy": {
                "Name": "always",
                "MaximumRetryCount": 0
            },
            "AutoRemove": false,
            "VolumeDriver": "",
            "VolumesFrom": null,
            "CapAdd": null,
            "CapDrop": null,
            "CgroupnsMode": "host",
            "Dns": [],
            "DnsOptions": [],
            "DnsSearch": [],
            "ExtraHosts": null,
            "GroupAdd": null,
            "IpcMode": "private",
            "Cgroup": "",
            "Links": null,
            "OomScoreAdj": 0,
            "PidMode": "",
            "Privileged": false,
            "PublishAllPorts": false,
            "ReadonlyRootfs": false,
            "SecurityOpt": null,
            "UTSMode": "",
            "UsernsMode": "",
            "ShmSize": 67108864,
            "Runtime": "runc",
            "ConsoleSize": [
                0,
                0
            ],
            "Isolation": "",
            "CpuShares": 0,
            "Memory": 0,
            "NanoCpus": 0,
            "CgroupParent": "",
            "BlkioWeight": 0,
            "BlkioWeightDevice": [],
            "BlkioDeviceReadBps": null,
            "BlkioDeviceWriteBps": null,
            "BlkioDeviceReadIOps": null,
            "BlkioDeviceWriteIOps": null,
            "CpuPeriod": 0,
            "CpuQuota": 0,
            "CpuRealtimePeriod": 0,
            "CpuRealtimeRuntime": 0,
            "CpusetCpus": "",
            "CpusetMems": "",
            "Devices": [],
            "DeviceCgroupRules": null,
            "DeviceRequests": null,
            "KernelMemory": 0,
            "KernelMemoryTCP": 0,
            "MemoryReservation": 0,
            "MemorySwap": 0,
            "MemorySwappiness": null,
            "OomKillDisable": false,
            "PidsLimit": null,
            "Ulimits": null,
            "CpuCount": 0,
            "CpuPercent": 0,
            "IOMaximumIOps": 0,
            "IOMaximumBandwidth": 0,
            "MaskedPaths": [
                "/proc/asound",
                "/proc/acpi",
                "/proc/kcore",
                "/proc/keys",
                "/proc/latency_stats",
                "/proc/timer_list",
                "/proc/timer_stats",
                "/proc/sched_debug",
                "/proc/scsi",
                "/sys/firmware"
            ],
            "ReadonlyPaths": [
                "/proc/bus",
                "/proc/fs",
                "/proc/irq",
                "/proc/sys",
                "/proc/sysrq-trigger"
            ]
        },
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/ce422bd974b38b6d2e50cd3c6ae31cd097e7eca1309b1c726291f0d0e8c1fa0f-init/diff:/var/lib/docker/overlay2/551c90db13332f1053568b4fc7d8a44857732c7a3e50fd5f2babde4790648cbc/diff:/var/lib/docker/overlay2/2183aa3920c05a24269e1beb1b9ef02bc314799cdda483c56fc05b2a40e87c06/diff:/var/lib/docker/overlay2/0b71ca2907e4159780fd6f5aa79f6040366423b83d5d5d65308b622da7586d6e/diff:/var/lib/docker/overlay2/b81143c2a90a6d0a713b8d175c6ee14b779a6e3210585438674ebd7f515e0d98/diff:/var/lib/docker/overlay2/799533302effeb3a01127a342935b07ebdec17517202762be6d252ced54d0d98/diff:/var/lib/docker/overlay2/7f71f39debcdc970e1a6ca6a52aa8dfbcdffd71d9b1a9dcab4e43756aeb1cc19/diff",
                "MergedDir": "/var/lib/docker/overlay2/ce422bd974b38b6d2e50cd3c6ae31cd097e7eca1309b1c726291f0d0e8c1fa0f/merged",
                "UpperDir": "/var/lib/docker/overlay2/ce422bd974b38b6d2e50cd3c6ae31cd097e7eca1309b1c726291f0d0e8c1fa0f/diff",
                "WorkDir": "/var/lib/docker/overlay2/ce422bd974b38b6d2e50cd3c6ae31cd097e7eca1309b1c726291f0d0e8c1fa0f/work"
            },
            "Name": "overlay2"
        },
        "Mounts": [
            {
                "Type": "bind",
                "Source": "/usr/local/redis/conf/redis.conf",
                "Destination": "/etc/redis/redis.conf",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            },
            {
                "Type": "bind",
                "Source": "/usr/local/redis/data",
                "Destination": "/data",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],
        "Config": {
            "Hostname": "35e0f7357c42",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "ExposedPorts": {
                "6379/tcp": {}
            },
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "GOSU_VERSION=1.12",
                "REDIS_VERSION=6.2.6",
                "REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-6.2.6.tar.gz",
                "REDIS_DOWNLOAD_SHA=5b2b8b7a50111ef395bf1c1d5be11e6e167ac018125055daa8b5c2317ae131ab"
            ],
            "Cmd": [
                "redis-server",
                "/etc/redis/redis.conf",
                "--appendonly",
                "yes",
                "--requirepass",
                "123456"
            ],
            "Image": "redis",
            "Volumes": {
                "/data": {}
            },
            "WorkingDir": "/data",
            "Entrypoint": [
                "docker-entrypoint.sh"
            ],
            "OnBuild": null,
            "Labels": {}
        },
        "NetworkSettings": {
            "Bridge": "",
            "SandboxID": "d426e71fa5e3564e76fd6786010286fe07ab9e9d33b6b91f46a5b20d947452ea",
            "HairpinMode": false,
            "LinkLocalIPv6Address": "",
            "LinkLocalIPv6PrefixLen": 0,
            "Ports": {
                "6379/tcp": [
                    {
                        "HostIp": "0.0.0.0",
                        "HostPort": "8081"
                    },
                    {
                        "HostIp": "::",
                        "HostPort": "8081"
                    }
                ]
            },
            "SandboxKey": "/var/run/docker/netns/d426e71fa5e3",
            "SecondaryIPAddresses": null,
            "SecondaryIPv6Addresses": null,
            "EndpointID": "38efd7b6043e911a9b9c278c7d6aaa78f8da8808dc9e1dc24faa477d0661c329",
            "Gateway": "172.17.0.1",
            "GlobalIPv6Address": "",
            "GlobalIPv6PrefixLen": 0,
            "IPAddress": "172.17.0.3",
            "IPPrefixLen": 16,
            "IPv6Gateway": "",
            "MacAddress": "02:42:ac:11:00:03",
            "Networks": {
                "bridge": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "NetworkID": "8054906c5bc42e5260d9fc72aa07cb78508cf0ef9c2e19924a13975a0a2c0ddc",
                    "EndpointID": "38efd7b6043e911a9b9c278c7d6aaa78f8da8808dc9e1dc24faa477d0661c329",
                    "Gateway": "172.17.0.1",
                    "IPAddress": "172.17.0.3",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:11:00:03",
                    "DriverOpts": null
                }
            }
        }
    }
]

进入容器id对应的目录:

cd /sys/fs/cgroup/memory/docker/35e0f7357c420335c970e507a452e4f0678e68bfbcb9d5f36d9fc4e9f7e6198d/

进入结果:

[root@localhost ~]# cd /sys/fs/cgroup/memory/docker/35e0f7357c420335c970e507a452e4f0678e68bfbcb9d5f36d9fc4e9f7e6198d/

[root@localhost 35e0f7357c420335c970e507a452e4f0678e68bfbcb9d5f36d9fc4e9f7e6198d]# ll
总用量 0
-rw-r--r--. 1 root root 0 8月  18 21:29 cgroup.clone_children
--w--w--w-. 1 root root 0 8月  18 21:29 cgroup.event_control
-rw-r--r--. 1 root root 0 8月  18 21:29 cgroup.procs
-rw-r--r--. 1 root root 0 8月  18 21:29 memory.failcnt
--w-------. 1 root root 0 8月  18 21:29 memory.force_empty
-rw-r--r--. 1 root root 0 8月  18 21:29 memory.kmem.failcnt
-rw-r--r--. 1 root root 0 8月  18 21:29 memory.kmem.limit_in_bytes
-rw-r--r--. 1 root root 0 8月  18 21:29 memory.kmem.max_usage_in_bytes
-r--r--r--. 1 root root 0 8月  18 21:29 memory.kmem.slabinfo
-rw-r--r--. 1 root root 0 8月  18 21:29 memory.kmem.tcp.failcnt
-rw-r--r--. 1 root root 0 8月  18 21:29 memory.kmem.tcp.limit_in_bytes
-rw-r--r--. 1 root root 0 8月  18 21:29 memory.kmem.tcp.max_usage_in_bytes
-r--r--r--. 1 root root 0 8月  18 21:29 memory.kmem.tcp.usage_in_bytes
-r--r--r--. 1 root root 0 8月  18 21:29 memory.kmem.usage_in_bytes
-rw-r--r--. 1 root root 0 8月  18 21:29 memory.limit_in_bytes
-rw-r--r--. 1 root root 0 8月  18 21:29 memory.max_usage_in_bytes
-rw-r--r--. 1 root root 0 8月  18 21:29 memory.memsw.failcnt
-rw-r--r--. 1 root root 0 8月  18 21:29 memory.memsw.limit_in_bytes
-rw-r--r--. 1 root root 0 8月  18 21:29 memory.memsw.max_usage_in_bytes
-r--r--r--. 1 root root 0 8月  18 21:29 memory.memsw.usage_in_bytes
-rw-r--r--. 1 root root 0 8月  18 21:29 memory.move_charge_at_immigrate
-r--r--r--. 1 root root 0 8月  18 21:29 memory.numa_stat
-rw-r--r--. 1 root root 0 8月  18 21:29 memory.oom_control
----------. 1 root root 0 8月  18 21:29 memory.pressure_level
-rw-r--r--. 1 root root 0 8月  18 21:29 memory.soft_limit_in_bytes
-r--r--r--. 1 root root 0 8月  18 21:29 memory.stat
-rw-r--r--. 1 root root 0 8月  18 21:29 memory.swappiness
-r--r--r--. 1 root root 0 8月  18 21:29 memory.usage_in_bytes
-rw-r--r--. 1 root root 0 8月  18 21:29 memory.use_hierarchy
-rw-r--r--. 1 root root 0 8月  18 21:29 notify_on_release
-rw-r--r--. 1 root root 0 8月  18 21:29 tasks

容器与主机之间的数据拷贝

docker cp

例如:
docker cp  nginx:/www /tmp/    #将nginx容器的/www 拷贝到本地/tmp下

终止容器

终止一个运行中的容器

# 终止一个运行中的容器
docker container stop 

#查看终止状态的容器
docker container ls -a

#重新启动终止容器
docker container start

#将一个运行态的容器终止,然后再重新启动
docker container restart

保存镜像

(1) 方式1

用于将 Docker容器 里的文件系统作为一个 tar 归档文件导出到标准输出。

docker export [OPTIONS] CONTAINER

例子:
# 将name = redis 的docker容器 的文件系统归档信息输出到文件
docker export -o redis.tar redis

OPTIONS说明:

-o :将输入内容写到文件。

(2)方式二

将指定镜像保存成 tar 归档文件。

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

例子:
docker save -o redis.tar redis

OPTIONS 说明:

-o :输出到的文件。

载入镜像

docker import 归档文件名称

例子:
docker import redis.tar 

查看某个容器的文件目录

docker exec 容器名称 ls
例如:
docker exec mysql ls

别在说自己不知道docker了,全文通俗易懂的给你说明白docker的基础与底层原理_第12张图片

删除容器

docker rm  

详细参数:

(1)-f : 强制删除一个运行中的容器

(2)-l :移除容器间的网络连接,而非容器本身

(3)-v : 删除与容器关联的卷

使用镜像

列出镜像列表

docker images ls

别在说自己不知道docker了,全文通俗易懂的给你说明白docker的基础与底层原理_第13张图片

列表包含了 仓库名、标签、镜像 ID、创建时间 以及 所占用的空间。镜像 ID 则是镜像的唯一标识,一个镜像可以对应多个 标签。

搜索镜像

docker search 镜像名称

例如:
docker search redis

别在说自己不知道docker了,全文通俗易懂的给你说明白docker的基础与底层原理_第14张图片

拉取镜像

docker pull 镜像名称:版本号

例如:
docker pull redis

在这里插入图片描述

查看是否拉取成功

docker images -a

别在说自己不知道docker了,全文通俗易懂的给你说明白docker的基础与底层原理_第15张图片

打tag

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

例子:
docker tag centos centos:v1

删除镜像

docker rmi 

详细参数:

(1) -f :强制删除;

(2)–no-prune :不移除该镜像的过程镜像,默认移除;

你可能感兴趣的:(docker,容器,运维)