0. 容器简介
从生活上来说,容器是一种工具,可以装东西的工具,如衣柜、背包、行李箱等等。
从IT技术方面来说,容器是一种全新的虚拟化技术,它提高了硬件资源利用率,结合k8s还可以让企业业务快速横向扩容、业务宕机自愈 。
1. Docker简介
1.1 Docker是什么
官方简介文档:CHANGELOG.mod
Docker是一个在2013年开源的PAAS(平台即服务)级别的应用程序,基于GO语言编写而成。
Docker是基于Linux内核实现的容器运行,最早采用LXC技术(Linux Container),LXC是linux原生支持的容器技术,可以提供轻量级的虚拟化。
2016年4月13日,Docker版本为1.11.0时,Dokcer使用自己研发并开源的runc容器技术。
1.2 Docker的特点
Docker相比虚拟机的交付速度更快,资源消耗更低,Docker采用客户端、服务端的架构,使用远程API来管理和创建Docker容器,可以创建一个轻量级的(基础镜像体积小)、可移植的(有docker环境的都能run)、自给自足(根据不同需求内置的不同运行环境,如java)的容器。
Docker的三大理念是build(构建镜像)、ship(分发镜像)、run(运行),并遵从Apache·2.0协议,并通过namespace和cgroup来提供容器的资源隔离和安全保障,所以Docker容器在运行时不会有类似虚拟机(空运行的虚拟机占用一定性能开销)的额外开销,因此可以大幅提高资源利用率。
1.3 Docker的组成
Docker官方文档:https://docs.docker.com/get-started/overview/
Docker官方镜像仓库:https://hub.docker.com/
Docker宿主机(HOST):一个物理机或虚拟机,用于运行Docker服务进程和容器。
Docker服务端(Service):Docker守护进程,运行docker容器。
Docker客户端(Client):客户端使用docker命令或其他工具调用docker API。
Docker仓库(Registry):保存镜像的仓库,类似于git或者svn这种的版本控制工具。
Docker镜像(Images):镜像可以理解成创建实例使用的模板。
Docker容器(Container):容器是从镜像生成的对外提供服务的一个或一组服务。
1.3.1 重要组件
(1)docekrd
为客户端提供RESTful API,响应来自客户端的请求,采用模块化的架构,通过专门的Engine模块来分发管理各个来自客户端的任务。可以单独升级;
(2)docker-proxy
是dockerd的子进程,当需要进行容器端口映射时,docker-proxy完成网络映射配置;
(3)containerd
是dockerd的子进程,提供gRPC接口响应来自dockerd的请求,对下管理runC镜像和容器环境。可以单独升级;
(4)containerd-shim
是containerd的子进程,为runC容器提供支持,同时作为容器内进程的根进程。
(5)runC
是Docker引擎,容器的创建,运行,销毁等等操作最终都将通过调用runC完成。
1.4 Docker对比虚拟机
资源利用率更高:一台物理机可以运行数百个容器,但是运行虚拟机的话,一般只能运行数十个。
开销更小:不需要启动单独的虚拟机占用硬件资源。
启动速度更快:可以在数秒内完成启动。
1.4.1 Docker容器对和VM虚拟机的不同之处
如下图:传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;
而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。
并且每个容器之间互相隔离,每个容器有自己的文件系统,容器之间进程不会相互影响,能区分计算资源。
1.4.2 为什么docker容器比VM虚拟机更快
(1)因为docker不需要Hypervisor(虚拟机)实现硬件资源虚拟化,运行在docker容器上的程序直接使用的都是实际物理机的硬件资源。因此在CPU、内存利用率上docker将会在效率上有明显优势。
(2)当新建一个容器时,docker不需要和虚拟机一样重新加载一个操作系统内核。进而避免引寻、加载操作系统内核返回等比较费时费资源的过程,当新建一个虚拟机时,虚拟机软件需要加载OS,返回新建过程是分钟级别的。而docker由于直接利用宿主机的操作系统,则省略了返回过程,因此新建一个docker容器只需要几秒钟。
1.5 使用Docker面临的问题
如上图,一个宿主机运行了多个容器,多个容器带来的以下问题怎么解决?
(1)怎样保证每个容器都有不同的文件系统,并且能互不影响?
(2)一个docker主进程内的各个容器都是其子进程,那么同一个主进程下不同类型子进程如何实现资源隔离?各个进程间通信能相互访问(内存数据)吗?
(3)每个容器怎么解决IP及端口分配的问题?
(4)多个容器的主机名能一样吗?
(5)每个容器要不要有root用户?怎么解决账户重名问题?
1.6 Linux Namespace技术
简单来说:Linux Namespace是Linux提供的一种内核级别环境隔离的方法。
官方文档:https://lwn.net/Articles/531114/
Linux Namespace是Linux提供的一种内核级别环境隔离的方法。很早以前的Unix有一个叫chroot的系统调用(通过修
改根目录把用户jail到一个特定目录下),chroot提供了一种简单的隔离模式:chroot内部的文件系统无法访问外部的内容。
Linux Namespace在此基础上,提供了对UTS、IPC、mount、PID、network、User等的隔离机制。
举个例子,我们都知道,Linux下的超级父亲进程的PID是1,所以,同chroot一样,如果我们可以把用户的进程空间jail到某个进程分支下,
并像chroot那样让其下面的进程 看到的那个超级父进程的PID为1,于是就可以达到资源隔离的效果了(不同的PID namespace中的进程
无法看到彼此)
隔离类型 | 功能 | 系统调用参数 | 内核版本 |
---|---|---|---|
MNT Namespace(mount ns) | 提供磁盘挂载点(文件系统)的隔离能力 | CLONE_NEWNS | Linux 2.4.19 |
IPC Namespace(Inter-Process Communication) | 提供进程间通信隔离能力 | CLONE_NEWIPC | Linux 2.6.19 |
UTS Namespace(UNIX Timesharing System) | 提供主机名和域名隔离能力 | CLONE_NEWUTS | Linux 2.6.19 |
PID Namespace(Process Identification) | 提供进程隔离能力 | CLONE_ NEWPID | Linux 2.6.24 |
Net Namespace(network) | 提供网络隔离能力 | CLONE_NEWNET | Linux 2.6.29 |
User Namespace(user) | 提供用户隔离能力 | CLONE_NEWUSER | Linux 3.8 |
1.6.1 MNT Namespace
实现文件系统隔离
使用 MNT Namespace 可以实现容器内只能看到自己的挂载信息,在容器内的挂载操作不会影响主机的挂载目录。
每个容器都要有独立的根文件系统、有独立的用户空间,以实现在容器里面启动服务并且使用容器的运行环境。
即有一台服务器,安装的操作系统是Ubuntu的操作系统,我可以在上面启动一个以centos为运行环境的容器,
并在容器里再启动一个nginx服务,此nginx运行时使用的运行环境就是centos系统目录的运行环境,但是在
容器里面是不能访问宿主机的资源的,因为早期宿主机会使用一种叫chroot的技术(现在是mnt ns),把容器锁定到一个指定的运行目录里面。
如: /var/lib/docker/containerd/io.containerd.runtime.v1.linux/moby/容器ID
为什么我们可以访问容器?为什么容器可以访问互联网?
[root@test01 ~]# iptables -t nat -vnL
Chain PREROUTING (policy ACCEPT 4 packets, 208 bytes)
pkts bytes target prot opt in out source destination
7 416 DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT 4 packets, 208 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 26 packets, 1787 bytes)
pkts bytes target prot opt in out source destination
0 0 DOCKER all -- * * 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT 28 packets, 1891 bytes)
pkts bytes target prot opt in out source destination
0 0 MASQUERADE all -- * !docker0 172.17.0.0/16 0.0.0.0/0 # 上网是这里,所有从容器发起的请求都会转换成0.0.0.0/0
0 0 MASQUERADE tcp -- * * 172.17.0.2 172.17.0.2 tcp dpt:80
Chain DOCKER (2 references)
pkts bytes target prot opt in out source destination
0 0 RETURN all -- docker0 * 0.0.0.0/0 0.0.0.0/0
2 104 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80 # 这条目的地址转换策略,把所有进来访问80的请求都转换成了容器的IP:端口
- Docker MNT NameSpace实现过程
Docker使用Linux内核提供的Mount Namespace来实现容器级文件系统隔离,具体实现过程如下:
(1)在创建Docker容器时,Docker会调用Linux内核的Clone系统调用来创建一个新的进程,并指定CLONE_NEWNS标志来创建一个新的Mount Namespace。
(2)Docker会将Docker容器的根文件系统挂载到容器的根目录上,并为容器中的各个进程创建相应的挂载点。
(3)Docker可以使用Dockerfile或docker run命令中的“-v”选项,将主机的文件系统挂载到Docker容器的指定挂载点上。这些挂载点是Docker运行时为容器创建的文件夹,可以在容器中通过路径访问它们。
(4)在Docker容器中运行的应用程序会被限制在Docker容器的Mount Namespace中,只能看到在该Namespace下挂载的文件系统,无法访问主机或其他Docker容器的文件系统。
通过使用Mount Namespace,Docker容器的文件系统可以与主机或其他容器的文件系统进行隔离,使得容器应用程序在运行过程中可以获取到独立的文件系统环境。这大大增强了Docker容器的可移植性和安全性。
除了上述这些,我们在运行了docker容器的机上执行命令,也可以看到相关进程。如下图:
- containerd-shim -namespace moby -workdir
这段命令是用来启动containerd-shim进程,并指定进程所在的Mount Namespace和工作目录的。具体含义如下:
containerd-shim:是Docker所有容器运行时的核心组件之一,负责在容器中创建进程、管理标准输入/输出、加载应用程序等操作。
-namespace moby:指定containerd-shim进程所在的Mount Namespace,这样进程就可以使用独立的Mount Namespace来挂载文件系统,防止与其他容器或主机中的文件系统发生干扰。moby是Docker的项目名称,因此这里使用了moby作为命名空间的名称。
-workdir:指定containerd-shim进程的工作目录,即在容器中的工作目录。这个目录是通过Mount Namespace挂载到容器中的,容器中的应用程序可以在这个目录中进行读写操作,不会影响主机上的文件系统。
因此,这个命令的功能是启动一个独立的containerd-shim进程,并为它创建一个独立的Mount Namespace和工作目录,以实现容器级别的文件系统隔离和运行时环境隔离。
1.6.2 IPC Namespace
实现容器进程间的通信隔离
IPC Namespace是Linux操作系统的一种Namespace类型,它用于隔离进程间通信(IPC)机制。在同一个IPC Namespace中运行的进程可以
通过共享内存、消息队列、信号量等方式进行相互通信,而不会影响到其他IPC Namespace中运行的进程。
在Docker中,默认情况下,每个容器都会拥有独立的IPC Namespace。这样的好处是避免了容器之间进行进程通信时可能出现的冲突和干扰。
例如,如果在主机上运行了多个容器,它们共享同一个IPC Namespace,则一个容器中的消息队列可能会影响到其他容器中的应用程序。
而在单个容器内部,IPC Namespace的隔离可以帮助不同的进程进行相互隔离和保护。例如,在一个Web应用程序的容器中,Web服务器进程
与后台处理进程可以运行在不同的IPC Namespace中,这样可以避免Web服务器进程直接与后台进程进行通信,提高了容器内部的安全性。
- Docker IPC NS实现过程
Docker运行容器时会使用Linux内核提供的clone()系统调用,创建一个新的进程。这个新进程的IPC Namespace会被设置为一个全新的IPC Namespace,与本机的IPC Namespace完全隔离。在这个新进程内,Docker使用了两个系统调用来实现IPC Namespace的隔离:
(1)unshare()系统调用:用于将当前进程支离出一个独立的IPC Namespace。
(2)setns()系统调用:将当前进程加入到一个已经存在的IPC Namespace内。
对于Docker中的容器,使用的是第一种方式。当Docker创建一个新容器时,它会使用unshare().CLONE_NEWIPC系统调用生成一个新的IPC Namespace的实例,并将容器中的所有进程都添加到这个IPC Namespace中。这样,该IPC Namespace内的进程就与主机上其他进程隔离开来了,从而保证了容器间的IPC机制不相互干扰。在容器的IPC Namespace中,容器内的进程通过IPC机制进行通信时,只会在该IPC Namespace内部发生,对主机上其他IPC Namespace完全无感知。
需要注意的是,在Docker容器内部,IPC Namespace不能跨越多个容器/主机间进行分享。也就是说,在一个容器内部访问另一个容器/主机的IPC机制是不可能的。而且,Docker也不支持动态地将容器内的IPC Namespace与主机上的IPC Namespace进行交叉共享,这也保证了每个容器运行时IPC Namespace的独立性和安全性。
- Docker 为什么需要使用IPC Ns
Docker需要使用IPC Namespace主要是为了隔离容器内的进程间通信,其主要的优点包括:
(1)隔离:通过IPC Namespace,Docker为每个容器分配了独立的IPC资源,避免了容器间资源的干扰和冲突,有效保证了容器的可靠性和安全性。
(2)提高资源利用率:IPC Namespace提高了容器内IPC资源的利用率,不同的容器可以共享不同的IPC资源,减少了资源浪费。
(3)改进容器可用性:如果一个容器因进程间通信问题挂起或崩溃,它将不会影响其他容器或宿主机中的进程。容器内的进程仅共享与容器内一致的IPC结构和数据,从而改善了容器的可用性。
综上所述,使用IPC Namespace可以提高容器的可靠性和安全性,同时还可以有效提高IPC资源的利用率。因此,Docker非常适合使用IPC Namespace隔离容器内IPC资源。
1.6.3 UTS Namespace
隔离宿主机、不同容器间的主机名和域名
UTS Namespace是Linux命名空间(Namespace)的一种类型,用来隔离主机节点的主机名和域名。UTS是Unix Time-Sharing System(Unix的分时系统)的缩写,Unix系统中的主机名和域名通常被称为UTS名称。在同一个UTS Namespace中运行的进程共享相同的主机名和域名,而在不同的UTS Namespace中运行的进程拥有独立的主机名和域名。
在Docker中,每个容器默认都有自己的UTS Namespace。这意味着每个容器具有独立的主机名和域名。这种隔离可以帮助用户在多个应用程序/服务之间进行快速切换并降低这些应用程序/服务之间的干扰。这个主机名标识独立于宿主机系统和其他容器。
- Docker UTS Namespace 实现过程
Docker在创建容器时会使用Linux内核提供的clone()系统调用,生成一个新的进程。这个新进程的UTS Namespace会被设置为一个全新的UTS Namespace,与本机的UTS Namespace完全隔离。在这个新进程内,Docker使用了两个系统调用来实现UTS Namespace的隔离:
(1)unshare()系统调用:用于将当前进程支离出一个独立的UTS Namespace。
(2)setns()系统调用:将当前进程加入到一个已经存在的UTS Namespace内。
对于Docker中的容器,使用的是第一种方式。当Docker创建一个新容器时,它会使用unshare()系统调用生成一个新的UTS Namespace的实例,并将容器中的所有进程都添加到这个UTS Namespace中。这样,容器内的所有进程就共享相同的主机名和域名,而且与主机节点上的UTS Namespace完全隔离,避免了容器与宿主机之间的UTS Namespace冲突。
需要注意的是,在Docker容器内部,UTS Namespace不能跨越多个容器/主机间进行分享。也就是说,在一个容器内部访问另一个容器/主机的UTS Namespace是不可能的。而且,Docker也不支持动态地将容器内的UTS Namespace与主机上的UTS Namespace进行交叉共享, 这也保证了每个容器运行时UTS Namespace的独立性和安全性。
- Docker为什么要使用UTS NameSpace
Docker需要使用UTS Namespace主要是为了隔离容器中的主机名和域名。每个UTS Namespace内有一个hostname和一个domainname,通过这两个参数可以唯一标识这个Namespace。当Docker容器创建时,会使用unshare()系统调用创建一个新的UTS Namespace,与宿主机的UTS Namespace完全隔离,然后在这个新的UTS Namespace中启动容器内部的进程。
这种使用UTS Namespace的方式提供了多个优点:
(1)隔离:UTS Namespace提供了隔离容器与宿主机之间主机名和域名,避免了容器与宿主机之间的命名空间冲突。
(2)独立性:每个容器都有自己的UTS Namespace,容器内的进程共享相同的主机名和域名,但不会影响其他容器或宿主机中的进程。
(3)可配置性:Docker允许用户在创建容器时定义自己的主机名和域名,方便用户对容器设置自定义的主机名和域名
1.6.4 PID Namespace
容器PID和宿主机PID隔离
PID Namespace是Linux内核中的一种机制,用于隔离进程ID(PID)命名空间,每个PID Namespace都有一个独立的PID号码空间,从而可以让不同的进程在不同的PID Namespace中具有相同的PID,从而隔离了不同PID Namespace之间的进程。
- Docker PID Namespace 实现过程
在Docker中,每个容器运行在一个独立的PID Namespace中,实现步骤如下:
(1)创建PID Namespace
当我们启动Docker容器时,Docker会为该容器创建一个独立的PID Namespace。
(2)分配PID号码:在PID Namespace中,Docker会分配唯一的PID号码给每个容器内的进程,并且这些PID号码在该Namespace中是唯一的。
(3)跟踪进程:Docker会在宿主机上创建一个Daemon进程,该进程负责跟踪容器内的进程,并将其PID号码转换为在宿主机上的PID号码。这对于容器内的进程与宿主机环境的交互非常重要。
(4)进程绑定:Docker会使用Linux内核提供的clone()系统调用,将PID Namespace与容器内的进程进行绑定,使得这些进程只能在该Namespace中运行。当容器内的进程退出时,在进程树的根进程退出后,容器的PID Namespace也会被销毁,从而清理PID号码空间中的任何资源。
- Docker为什么要使用PID Namespace
Docker使用PID Namespace主要是为了实现进程的隔离和统一管理。在Docker中,每个容器都运行在一个独立的PID Namespace中,其主要作用有:
(1)隔离性:由于每个容器都有自己独立的PID Namespace,因此不同容器内的进程PID号码不会冲突,互相独立运行,提高了系统运行的稳定性和安全性。
(2)安全性:使用PID Namespace可以将容器内的进程与宿主机上的进程进行隔离,从而可以避免容器内的进程对宿主机上的进程造成影响。
(3)更好的管理:使用PID Namespace可以方便容器内进程的管理和监控,通过查看容器的PID Namespace,可以快速了解容器内的进程状态。
(4)资源利用率:使用PID Namespace可以提高系统资源的利用率,多个容器可以共享同一个PID Namespace,减少了系统资源的浪费。
下图是容器内使用top命令看到的pid为1的进程是nginx
容器内的Nginx主进程和工作进程
宿主机的PID究竟和容器内的PID是什么关系?
- 容器进程与宿主进程间的关系
1.6.5 Net Namespace
网络隔离
Net Namespace是Linux内核中的一个功能,可以帮助我们隔离网络资源的使用。它通过创建虚拟网络设备和IP地址来隔离应用程序对网络的访问,使得在同一主机上运行的多个应用程序之间不会出现网络冲突,并且应用程序可以使用不同的网络配置进行通信。
在Docker中,每个容器都运行在自己的Net Namespace中,这样就可以保证容器内的网络隔离性。具体实现过程如下:
(1)创建Net Namespace:当我们启动Docker容器时,Docker会为该容器创建一个独立的Net Namespace。
(2)虚拟网络设备:Docker会为该容器创建一个虚拟的网络设备,比如veth x,这个设备与宿主机上的一个设备相连。同时,Docker会为该容器分配一个IP地址,和IP路由表等网络资源。
(3)路由规则:Docker会在容器所在的Net Namespace中创建一组路由规则,这些规则定义了如何将容器的网络流量路由到宿主机上的网络设备。
(4)端口映射:如果我们在容器中使用了端口映射,Docker会为该容器配置iptables规则,将容器端口与宿主机的端口进行映射。
- Docker Net Namespace 实现过程
(1)创建Net Namespace:当我们启动Docker容器时,Docker会为该容器创建一个独立的Net Namespace。
(2)虚拟网络设备:Docker会在容器所在的Net Namespace中为该容器创建一个虚拟的网络设备,比如veth x。该设备与宿主机上的一个网络设备连接在一起,它们一端连接到容器,另一端连接到宿主机上的一个bridge device。Docker会为该虚拟设备分配一个名字,并且配置一个IP地址,可以使用“ip addr”命令来查看。
(3)网络路由:Docker还通过配置路由规则,将容器内部的网络流量路由到与容器相连的虚拟网络设备上。Docker会为容器创建一个网络命名空间(Net Namespace),并且在这个命名空间中配置一组路由规则以及IP地址。
(4)iptables规则:如果我们在容器中使用了端口映射,Docker会为该容器配置iptables规则,将容器内部的端口映射到主机上的特定端口上。
- Docker为什么要使用Net Namespace
Docker使用Net Namespace来实现容器的网络隔离,主要有以下几个原因:
(1)隔离性:使用Net Namespace,Docker可以为每个容器创建独立的网络命名空间,每个容器拥有自己的独立网络配置、IP地址和路由表。这种方式防止了不同容器之间的网络干扰和冲突。
(2)安全性:通过网络隔离,Docker可以为每个容器提供与主机不同的网络配置和访问权限,可以限制容器对网络资源的访问,提高容器的安全性。
(3)灵活性:使用Net Namespace可以为容器定义独立的网络配置,包括IP地址、路由规则等,使得应用程序可以直接访问网络资源,而不需要额外的配置和管理。
(4)跨主机:使用Net Namespace可以方便跨主机的容器通信和管理,因为网络命名空间可以在不同主机之间转移和迁移。
综上所述,Docker使用Net Namespace可以为容器提供独立的网络隔离和配置,提高容器应用程序的灵活性和安全性,并且方便跨主机管理和通信。
- 查看宿主机网卡信息
- 容器网络逻辑图
1.6.5.3 宿主机iptables规则
1.6.6 User Namespace
用户与用户组隔离
User Namespace是Linux内核提供的一种安全机制,用于隔离用户(UID/GID)级别的权限。具体来说,User Namespace提供了将特权用户映射成非特权用户,从而允许在容器中运行的进程以非特权用户的身份运行,保护宿主机的安全。
在Docker中,User Namespace允许容器中的进程以非特权用户的身份运行,从而大大减少了潜在的安全风险。通过将容器内的特权用户映射为宿主机上的非特权用户,可以限制容器对宿主机的访问权限,同时提高容器应用程序的安全性。
具体来说,Docker使用User Namespace实现容器内部的用户隔离,主要通过以下两种途径:
(1)映射用户ID:使用映射UID(user ID)和GID(group ID)的方式,将容器内的特权用户(如root)映射为宿主机上的非特权用户(如nobody),从而限制容器对宿主机的访问权限。
(2)创建新的用户:在容器中创建新的用户,使得容器的运行进程不会使用宿主机上已有的用户ID,从而实现了容器内用户的隔离。
简而言之,使用User Namespace可以提高Docker容器的安全性,通过限制容器对宿主机的访问权限,使得容器内的进程以非特权用户身份运行,从而避免了潜在的安全隐患。
- Docker User Namespace 实现过程
Docker使用User Namespace来实现容器内的用户隔离,具体实现过程如下:
(1)配置Docker Daemon:在Docker Daemon配置文件中,需要开启User Namespace的支持。主要是通过设置“userns-remap”选项,将容器内的特权用户映射为宿主机上的非特权用户。
(2)映射UID和GID:Docker会为每个容器生成一个映射表来存储User Namespace的映射配置。这个映射表将Linux用户ID和组ID映射到新的UID和GID,从而实现容器内用户的隔离。
(3)容器进程启动:当Docker启动一个容器时,它会在容器中运行的进程上使用新的UID和GID。Docker会自动在容器中为用户创建一个新的UID和GID,并将它们映射到宿主机上的非特权用户。这样,容器内的进程就可以在USR Namespace中以非特权用户的身份运行。
(4)组权限管理:除了UID和GID以外,Docker还实现了组权限管理,使得容器内的进程可以完整地使用Linux用户权限和组权限。
综上所述,Docker使用User Namespace来隔离容器内的用户权限,通过映射UID和GID,将特权用户映射为宿主机上的非特权用户,从而实现容器内用户的隔离。这种机制确保了容器集中的进程以更高的安全性运行,并保护了宿主机免受恶意容器进程的攻击。
- Docker 为什么要使用User Namespace
Docker使用User Namespace来提高容器的安全性,主要由以下几方面原因:
(1)隔离性:使用User Namespace,Docker可以隔离容器内的用户身份,将容器内的特权用户映射为宿主机上的非特权用户,从而避免了对宿主机资源的非授权访问。
(2)安全性:使用User Namespace可以避免恶意容器或者攻击者利用Docker容器漏洞进行提权攻击。通过映射UID/GID,使得容器内的特权用户对宿主机没有访问权限,从而提高了容器应用程序的安全性。
(3)灵活性:使用User Namespace可以为容器定义独立的用户ID和组ID,使得应用程序可以直接访问本地资源,而不需要其他权限和管理软件。
(4)可移植性:使用User Namespace可以保证Docker容器在同一主机上运行时的可移植性,从而为容器的部署和管理提供了便利。
1.6.7 关于NameSpace的三个系统调用
(1)clone():实现线程的系统调用,用来创建一个新的进程,并可以通过设计上述参数达到隔离。
(2)unshare():使某进程脱离某个namespace。
(3)setns():把某进程加入到某个namespace。
- clone()
Linux系统调用clone()是一种具有针对性的系统调用,它可以用于创建一个新进程,该进程与父进程共享某些资源,同时对其他资源进行剥离,其中一个使用场景就是在Docker容器中实现隔离性。在Docker中,容器启动时,就是使用了clone()系统调用特性来实现创建隔离的进程。
典型的,clone()系统调用可以使用位掩码参数来指定创建的新进程与父进程共享的资源,其中包括进程空间、虚拟内存、文件系统和网络等资源。下面是一些常用的位掩码参数:
CLONE_NEWNS:创建新的文件系统挂载点(mount namespace),使得根文件系统的挂载点不同于宿主机和其他容器,从而实现隔离的文件系统。
CLONE_NEWUTS:创建新的UTS namespace,隔离hostname和domain name空间。
CLONE_NEWIPC:创建新的IPC namespace,隔离进程间通讯的资源,例如消息队列、共享内存、信号量等。
CLONE_NEWPID:创建新的PID namespace,隔离进程ID空间,使得容器内进程看起来跟宿主机中的进程ID没有任何关系。
CLONE_NEWNET:创建新的network namespace,隔离网络资源,包括网络接口、路由表、ARP等。
在Docker中,clone()的具体使用方式通常结合多个参数来实现容器的隔离性,而没有显式的调用clone()函数。Docker利用namespaces和cgroups等技术,创建出独立的进程空间并隔离进程间的资源,从而使得容器的应用程序得到更好的隔离,安全和可靠性。
总的来说,clone() 作为一个基础的系统调用函数,对于Docker容器的隔离性至关重要,它通过利用namespaces和cgroups技术帮助Docker实现容器的隔离,并协助在Docker容器内创建独立的进程空间,从而保证了应用程序的安全性、可靠性和灵活性。
- unshare()
Docker利用Linux的Namespaces机制来实现容器的隔离性,而unshare()是Linux内核提供的一个系统调用函数,可以使当前进程脱离某些或者全部的namespaces。
具体来说,unshare()函数是在某个进程内部调用的,它可以使这个进程“unshare”(脱离)某个或者多个namespaces,从而使这个进程变得与拥有该namespace的进程不再相关。
在Docker中,unshare()函数被用于创建容器时调用,以便在容器实例中实现各种namespace的Isolation(隔离性)。例如,Docker利用unshare()函数来创建新的mount namespace和network namespace,以隔离容器的文件系统和网络和宿主机分离。
通过使用unshare()函数,Docker可以实现一个更加安全和独立的runtime环境,同时,也可以确保容器与主机之间的隔离,从而提高了容器的安全性和可靠性。
- setns()
Linux内核提供的setns()系统调用可以将一个进程加入一个已经存在的namespace中,从而实现不同进程之间共享一个namespace的效果。在Docker中,setns()系统调用被用于在运行中的Docker容器中进入/退出namespace。
具体来说,当Docker启动一个容器时,它会创建一个新的Namespace并在其中运行容器进程,这个容器进程在这个Namespace中享有完整的隔离性。而当我们使用Docker exec命令来进入一个正在运行的container中时,其实就是利用了setns()系统调用,将当前进程加入到容器中的namespace中去,从而使得在该容器中可以执行命令操作。
setns()系统调用正是通过改变调用者进程的namespace文件句柄,将调用者进程从一个namespace转移到另一个namespace中去。在Docker中,它确保了在container内部的进程能够与主机上的进程隔离开,并通过container namespace隔离文件系统、网络、用户等的权限,因此,应用程序可以在被容器隔离的环境中运行,而不会对主机产生影响。
总的来说,setns()是一个非常基础且重要的系统调用,因为它提供了进程隔离性的核心机制之一,可以确保不同进程具有一定的隔离性,从而保证应用程序的安全可靠性。在Docker中,结合运行时创建和进入container过程中的setns(),彻底实现了container及其内部进程的隔离性及安全性。
1.7 Linux control groups
对容器进行资源限制
在一个容器内,如果不对其做任何资源限制,则宿主机会允许其占用无限大的内存和cpu,有时候会因为代码的BUG,导致程序一直申请内存,直到系统OOM。
为了避免此类问题的出现,宿主机有必要对容器进行资源分配限制,比如cpu、内存等。
Linux Cgroups的全称是 Linux control groups,它主要的作用,就是限制一个进程能使用的资源上限,包括cpu、内存、磁盘、网络带宽等,此外,还能对进程进行优先级设置,以及将进程挂起和恢复等操作。
1.7.1 验证系统cgroups
Cgroups在内核层默认已经开启了,从CentOS和Ubuntu对别结果来看,显然是内核较新的Ubuntu支持的功能更多。
1.7.1.1 CnetOS 7 Cgroups
1.7.1.2 Ubuntu Cgroups
1.7.1.3 Cgroups中的内存模块
[root@test01 ~]# cat /boot/config-3.10.0-1062.el7.x86_64 |grep MEMCG
CONFIG_MEMCG=y
CONFIG_MEMCG_SWAP=y
CONFIG_MEMCG_SWAP_ENABLED=y
CONFIG_MEMCG_KMEM=y
1.7.1.4 Cgroups的具体实现
blkio:块设备IO限制。
cpu:使用调度程序为cgroup任务提供的cpu的访问。
cpuacct:产生cgroup任务的cpu资源报告。
cpuset:如果是多核心的cpu,这个子系统会为cgroup任务分配单独的cpu和内存。
devices:允许或拒绝cgroup任务对设备的访问。
freezer:暂停和恢复cgroup任务。
memory:设置每个cgroup的内存限制以及产生内存资源报告。
net_cls:标记每个网络包以供cgroup方便使用。
ns:命名空间子系统。
perf_event:增加了对每group的监测跟踪能力,可以监测属于某个特定的group的所有线程以及运行在特定cpu上的线程。
1.7.1.5 Docker使用cgroups实现容器隔离和资源控制的步骤
(1)创建cgroup并将容器进程加入其中。
Docker创建容器时,首先创建一个cgroup,用于隔离容器内部资源。
然后将容器中的所有进程加入到该cgroup中,使得cgroup能够控制这些进程的资源使用。
(2)在cgroup中设置容器的资源限制。
Docker通过cgroups设置容器的资源限制,包括CPU、内存和磁盘等。
通过设置cgroup参数,可以限制容器中进程所使用的资源。例如,可以限制容器中进程使用的CPU和内存大小。
(3)监控cgroup中的资源使用情况。
Docker可以通过查询cgroups中的统计信息来监控容器的资源使用情况。
例如,可以查看容器中进程的CPU使用率、内存使用量等信息,以快速检测容器内部的资源使用情况。
总的来说,Docker使用cgroups实现容器资源控制主要分为三个步骤,即:创建cgroup并将容器进程加入其中,设置容器的资源限制以及监控cgroup中的资源使用情况。利用cgroups特性,Docker可以实现对容器内部资源的隔离和管理,确保应用程序的性能和稳定性。
1.7.1.6 查看系统cgroups
[root@localhost ~]# ll /sys/fs/cgroup/
total 0
drwxr-xr-x 5 root root 0 Mar 9 06:21 blkio
lrwxrwxrwx 1 root root 11 Mar 9 06:21 cpu -> cpu,cpuacct
lrwxrwxrwx 1 root root 11 Mar 9 06:21 cpuacct -> cpu,cpuacct
drwxr-xr-x 5 root root 0 Mar 9 06:21 cpu,cpuacct
drwxr-xr-x 3 root root 0 Mar 9 06:21 cpuset
drwxr-xr-x 5 root root 0 Mar 9 06:21 devices
drwxr-xr-x 3 root root 0 Mar 9 06:21 freezer
drwxr-xr-x 3 root root 0 Mar 9 06:21 hugetlb
drwxr-xr-x 5 root root 0 Mar 9 06:21 memory
lrwxrwxrwx 1 root root 16 Mar 9 06:21 net_cls -> net_cls,net_prio
drwxr-xr-x 3 root root 0 Mar 9 06:21 net_cls,net_prio
lrwxrwxrwx 1 root root 16 Mar 9 06:21 net_prio -> net_cls,net_prio
drwxr-xr-x 3 root root 0 Mar 9 06:21 perf_event
drwxr-xr-x 5 root root 0 Mar 9 06:21 pids
drwxr-xr-x 5 root root 0 Mar 9 06:21 systemd
有了以上的chroot、namespace、cgroups,就具备了基础的容器运行环境,但是还需要有相应的容器创建与删除的管理工具、以及怎样把容器运行起来、容器数据怎么处理、怎么进行启动与关闭等问题需要解决,于是容器管理技术出现了。
1.8 容器管理工具
目前主要还是用的是docker,早期有使用lxc。
1.8.1 lxc
LXC为LinuxContainer简写,提供轻量级的虚拟化,以便隔离进程和资源,官网:https://linuxcontainers.org/
LXC仅做了解即可。
1.8.2 docker
Docker启动一个容器也需要一个模板,就是我们称之为镜像的东西(镜像本身其实是个tar文件,所以可以被tar解压),docker的镜像可以保存在一个公共的地方共享使用,只要把镜像拉取下来就可以使用,最主要的是可以在镜像基础之上做自定义配置并且可以再把其提交为一个镜像,一个镜像可以被启动为多个容器。
Docker的镜像是分层的,镜像底层为库文件且只读,既不能写入,也不能删除数据,从镜像加载启动为一个容器后会在上层生成一个可写层,其写入的数据会复制到容器目录,但是容器内的数据在容器被删除后也会被随之删除。
1.8.3 阿里巴巴的pouch
https://www.infoq.cn/article/alibaba-pouch
https://github.com/alibaba/pouch
1.9 Docker的优缺点
1.9.1 Docker的优点
快速部署:短时间内可以部署成百上千个应用,更快速交付到线上。
高效虚拟化:不需要额外的hypervisor支持,直接基于linux实现应用虚拟化,相比虚拟机大幅提升性能和效率。
节省开支:提高服务器利用率,降低IT支出。
简化配置:将运行环境打包保存至容器,使用时直接启动即可。
快速迁移和扩展:可跨平台运行在物理机、虚拟机、公有云等环境,良好的兼容性可以方便将应用从A宿主机迁移到B宿主机,甚至是A平台迁移到B平台。
1.9.2 Docker的缺点
隔离性:各应用之间的隔离性不如虚拟机彻底。
安全性:root用户创建的容器,将普通用户加入docekr组后,可以把root创建的容器给删除掉。
2. Docker(容器)的核心技术
2.1 容器规范
容器技术除了docker之外,还有coreOS的rkt、阿里巴巴的Pouch,为了保证容器生态的标准性和健康可持续发展,包括linux基金会、docker、微软、红帽、谷歌、ibm等,在2015年6月共同成立了一个叫open container(OCI)的组织,其目的就是制定开放的标准的容器规范。
目前OCI一共发布了两个规范,分别是runtime spec(容器运行时)和image format spec(镜像格式),有了这两个规范,不同的容器公司开发的容器只要兼容这两个规范,就可以保证容器的可移植性和相互可操作性。
2.1.1 runtime规范(容器运行时 runtime spec)
runtime是真正运行容器的地方,因此为了运行不同的容器runtime,需要和操作系统内核紧密合作相互支持,以便为容器提供相应的运行环境。
主流的三种runtime:
(1)Lxc:linux上早期的runtime,docker早期就是采用lxc作为runtime。(已废弃)
(2)runc:目前docker默认的runtime,runc遵守OCI规范,因此可以兼容rkt。
(3)rkt:是CoreOS开发的容器runtime,也符合OCI规范,所以使用rktruntime也可以运行docker容器。
runtime主要定义了以下规范,并以json格式保存在/run/docker/runtime-runc/moby/容器ID/state.json文件,此文件会根据容器的状态实时更新内容
(1)版本信息:存放OCI标准的具体版本号。
(2)容器ID:通常是一个哈希值,可以在所有state.json文件中提取出容器ID,对容器进行批量操作(关闭、删除等),此文件在容器关闭后会被删除,容器启动后会自动生成。
(3)PID:在容器中运行的首个进程在宿主机上的进程号,即将宿主机的那个进程设置为容器的守护进程。
(4)容器文件目录:存放容器rootfs及相应配置的目录,外部程序只需要读取state.json就可以定位到宿主机上的容器文件目录。
(5)容器创建:创建包括文件系统、namespace、cgroups、用户权限在内的各项内容。
(6)容器进程的启动:运行容器启动进程,该文件在/run/containerd/io.containerd.runtime.v1.linux/moby/容器ID/config.json。
(7)容器生命周期:容器进程可以被外部程序关停,runtime规范定义了对容器操作信号的捕获,并做相应资源回收的处理,避免僵尸进程的出现。
2.1.2 镜像规范(image format spec)
OCI容器镜像主要包含以下内容
(1)文件系统:定义了以layer保存的文件系统,在镜像里面是layer.tar(每层都是一个layer.tar),每个layer保存了和上层之间的变化的部分,image format spec定义了layer应该保存哪些文件,怎么表示增加、修改和删除的文件操作。
(2)manifest文件:描述有哪些layer,tag标签以及config文件名称。
(3)config文件:是一个以hash命名的json文件,保存了镜像平台,容器运行时需要的一些信息,比如环境变量、工作目录、命令参数等。
(4)index文件:可选的文件,指向不同平台的manifest文件,这个文件能保证一个镜像可以跨平台使用,每个平台拥有不同的manifest文件,使用index作为索引。
(5)父镜像:大多数层的元信息结构都包含一个parent字段,指向该镜像的父镜像。
(6)参数:
ID:镜像ID,每一层都有ID。
tag标签:标签用于将用户指定的、具有描述性的名称对应到镜像ID。
仓库:Repository镜像仓库。
os:定义系统类型。
architecture:定义CPU架构。
author:作者信息。
create:镜像创建日期。
[root@test01 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 904b8cb13b93 11 days ago 142MB
解压镜像查看
[root@test01 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 904b8cb13b93 11 days ago 142MB
[root@test01 ~]# docker save 904b8cb13b93 > nginx/nginx.image
[root@test01 ~]# cd nginx/
[root@test01 nginx]# ls
nginx.image
[root@test01 nginx]# tar xf nginx.image
tar: manifest.json: implausibly old time stamp 1970-01-01 08:00:00
[root@test01 nginx]# ll
total 142848
drwxr-xr-x 2 root root 50 Mar 2 02:43 022c07a52a220a5b0aa85c3a824850ce1fb0f626ad37987bed0d12cf4b5cb75f # 这些目录记录每一层的详情
drwxr-xr-x 2 root root 50 Mar 2 02:43 2bed87a39fb052307bc9771710f277c1ea99a45a1ae2401a224571349acf0a4f
drwxr-xr-x 2 root root 50 Mar 2 02:43 840c8426c9fb38d180b7707e5a7c604bc6123f94426de0cac0162628e0949ee8
-rw-r--r-- 1 root root 7656 Mar 2 02:43 904b8cb13b932e23230836850610fa45dce9eb0650d5618c2b1487c2a4f577b8.json
drwxr-xr-x 2 root root 50 Mar 2 02:43 9966e75239f521d16163dc5d699d3d46cd2e101adb83f18454112c8ad543aac7
drwxr-xr-x 2 root root 50 Mar 2 02:43 d92404db1780f763711ef26c68784b25309566e2b4c5bd13dea8f40479a13389
drwxr-xr-x 2 root root 50 Mar 2 02:43 db7652f1b7d48578873762fa2ebee6860f0596fd5e95f63411a4b815a16ab48e
-rw-r--r-- 1 root root 574 Jan 1 1970 manifest.json
-rw-r--r-- 1 root root 146263552 Mar 13 15:17 nginx.image
[root@test01 nginx]# cd db7652f1b7d48578873762fa2ebee6860f0596fd5e95f63411a4b815a16ab48e
[root@test01 db7652f1b7d48578873762fa2ebee6860f0596fd5e95f63411a4b815a16ab48e]# ll
总用量 60764
-rw-r--r-- 1 root root 482 3月 2 02:43 json # 该层的描述信息
-rw-r--r-- 1 root root 62213632 3月 2 02:43 layer.tar # 这个tar包就是该层的具体内容
-rw-r--r-- 1 root root 3 3月 2 02:43 VERSION
[root@test01 db7652f1b7d48578873762fa2ebee6860f0596fd5e95f63411a4b815a16ab48e]# cat json
{"id":"db7652f1b7d48578873762fa2ebee6860f0596fd5e95f63411a4b815a16ab48e","parent":"2bed87a39fb052307bc9771710f277c1ea99a45a1ae2401a224571349acf0a4f","created":"1970-01-01T08:00:00+08:00","container_config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":null,"Image":"","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"os":"linux"}
# 格式转化下
{
"id":"db7652f1b7d48578873762fa2ebee6860f0596fd5e95f63411a4b815a16ab48e",
"parent":"2bed87a39fb052307bc9771710f277c1ea99a45a1ae2401a224571349acf0a4f", # 父镜像ID(也就是该层的上一层)
"created":"1970-01-01T08:00:00+08:00",
"container_config":{ # 容器配置
"Hostname":"",
"Domainname":"",
"User":"",
"AttachStdin":false,
"AttachStdout":false,
"AttachStderr":false,
"Tty":false,
"OpenStdin":false,
"StdinOnce":false,
"Env":null,
"Cmd":null,
"Image":"",
"Volumes":null,
"WorkingDir":"",
"Entrypoint":null,
"OnBuild":null,
"Labels":null
},
"os":"linux"
}
上面的这些文件我们不要去动,只要了解就行了
2.2 容器管理工具
管理工具连接runtime与用户,对用户提供图形或命令方式操作,然后管理工具将用户操作传递给runtime执行。
lxc是lxd的管理工具。
Runc的管理工具是docker engine,docker engine包含后台deamon和cli两部分,我们经常提到的docker就是指docker engine。
Rkt的管理工具是rkt cli。
2.3 容器定义工具
容器定义工具允许用户定义容器的属性和内容,以便容器能够被保存、共享和重建。
Docker image:是docker容器的模板,runtime依据docker image创建容器。
Dockerfile:包含N个命令的文本文件,通过dockerfile创建出docker image。
ACI(App container image):与docker image类似,是CoreOS开发的rkt容器的镜像格式。
2.4 Registry
统一保存镜像而且是多个不同镜像版本的地方,被称为镜像仓库。
Image registry:docker官方提供的私有仓库部署工具。
Docker hub:docker官方的公共仓库,已经保存了大量的常用镜像,可以直接拉取使用。
Harbor:vmware提供的自带web界面、自带认证功能的镜像仓库,目前有很多公司使用。
2.5 容器编排工具
2.5.1 为什么需要容器编排工具
当多个容器在多个主机运行的时候,单独管理容器是相当复杂而且很容器出错的,并且也无法实现某一台主机宕机后容器自动迁移到其他主机,从而实现高可用的目的,也无法实现动态伸缩的功能,因此需要有一种工具可以实现统一管理、动态伸缩、故障自愈、批量执行等功能,这就是容器编排引擎。
容器编排通常包括容器管理、调度、集群定义和服务发现等功能。
2.5.2 容器编排工具介绍
(1)Docker swarm:docker开发的容器编排引擎。
(2)Kubernetes:google领导开发的容器编排引擎,内部项目为Borg,且同时支持docker和CoreOS,也是现在主流的容器编排工具。
(3)Mesos+Marathon:通用的集群调度平台,mesos(资源分配)与marathon(容器编排平台)一起提供容器编排功能。
3. Docker(容器)的依赖技术
3.1 容器网络
docker自带的网络docker network(docker 0)仅支持管理单机上的容器网络,当多主机运行的时候需要使用第三方的开源网络软件,如:calico(三层)、flannel(二层)等。
docker 0是我们安装完docker并启动后,产生的一块网卡,它是不具备跨宿主机通信的能力的,如果硬是要在局域网内使用,这个IP一定要改。
[root@localhost ~]# ifconfig
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
inet6 fe80::42:92ff:fec1:111 prefixlen 64 scopeid 0x20
ether 02:42:92:c1:01:11 txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 5 bytes 446 (446.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
3.2 服务发现
容器的动态扩容特性决定了容器IP也会随之变化,因此需要有一种机制可以自动识别并将用户请求动态转发到新建的容器上,kubernetes自带服务发现功能,需要结合kube-dns(现在基本是使用coredns)服务解析内部域名。
3.3 容器监控
可以通过原生命令docker ps/top/stats 查看容器运行状态,另外也可以还是用 heapster/prometheus等第三方的监控工具来监控容器的运行状态。
3.4 数据管理
容器的动态迁移会导致其在不同的Host之间迁移,因此如何保证与容器相关的数据也能随之迁移或随时访问,可以使用逻辑卷/存储卷的方式解决。
3.5 日志收集
docker原生的日志查看工具docker logs,但是容器内部的日志需要通过ELK等专门的工具进行收集分析和展示工具处理。
为什么我们可以通过docker logs查看容器日志?
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8f3944218613 605c77e624dd "/docker-entrypoint.…" 5 days ago Up 31 minutes 0.0.0.0:80->80/tcp sweet_nightingale
[root@localhost ~]# docker exec -it 8f3944218613 /bin/bash
root@8f3944218613:/# ls -l /var/log/nginx/access.log
lrwxrwxrwx 1 root root 11 Dec 29 2021 /var/log/nginx/access.log -> /dev/stdout # 是以为在容器中,把日志重定向到了标准输出中,所以可以在宿主机上通过docker logs查看日志。
4. Docker安装及基础命令
Docker官网:www.docker.com
4.1 宿主机系统版本选择
Docker目前已经支持多种操作系统的安装运行,比如Ubuntu、CentOS、Redhat、Debian、Fedora,甚至还支持了Mac和windows,在linxu系统上需要内核版本在3.10或以上,docker版本号之前一直是0.x版本或1.x版本,但是从2017年3月1号开始改为每个季度发布一次稳定版,其版本号规则也统一变更为YY.MM,例如17.09表示是2017年9月发布,本次学习使用CentOS 7.9,内核版本3.10.0。
4.2 Docker版本选择
Docker早期是没有区分版本的,但是2017年初推出(将docker更名)新的项目Moby,github地址:github.com/moby/moby,Moby项目属于Docker项目的全新上游,Docker将是一个隶属于的Moby子产品,而且之后的版本开始区分为CE版本(社区版)和EE版本(企业收费版),CE社区版本和EE企业版本都是每个季度发布一次新版,但是EE版本提供后期安全维护1年,而CE版本是4个月。
与kubernetes结合使用的时候,要安装经过kubernetes官方测试通过的docker版本,避免出现不兼容等未知的及不可预估的问题发生,kubernetes测试过的docker版本可以在github查询,地址如下:(找了半天没找着)
4.3 下载docker安装包并安装
4.3.1 下载docker的rpm包
官方rpm包下载地址:https://download.docker.com/linux/centos/7/x86_64/stable/Packages/
二进制包下载地址:https://download.docker.com/,https://mirrors.aliyun.com/docker-ce/linux/static/stable/x86_64/
阿里云镜像下载地址:https://mirrors.alivyun.com/docker-ce/linux/centos/7/x86_64/stable/Packages/
4.3.2 YUM安装docker
清华源地址:https://mirrors.tuna.tsinghua.edu.cn/
(1)如果你之前安装过 docker,请先删掉,没有安装过可以不执行这一步
[root@localhost ~]# yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine
Loaded plugins: fastestmirror
No Match for argument: docker
No Match for argument: docker-client
No Match for argument: docker-client-latest
No Match for argument: docker-common
No Match for argument: docker-latest
No Match for argument: docker-latest-logrotate
No Match for argument: docker-logrotate
No Match for argument: docker-engine
No Packages marked for removal
(2)安装依赖
[root@localhost ~]# yum install -y yum-utils device-mapper-persistent-data lvm2
(3)添加repo文件
[root@localhost ~]# yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
Loaded plugins: fastestmirror
adding repo from: https://download.docker.com/linux/centos/docker-ce.repo
grabbing file https://download.docker.com/linux/centos/docker-ce.repo to /etc/yum.repos.d/docker-ce.repo
repo saved to /etc/yum.repos.d/docker-ce.repo
(4)把软件仓库地址替换为 TUNA
[root@localhost ~]# sed -i 's+download.docker.com+mirrors.tuna.tsinghua.edu.cn/docker-ce+' /etc/yum.repos.d/docker-ce.repo
[root@localhost ~]# yum makecache fast
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
docker-ce-stable | 3.5 kB 00:00:00
Metadata Cache Created
(5)安装docker
# 这里默认是安装最新的,可以通过命令来选择要安装的版本
[root@localhost ~]# yum list docker-ce --showduplicates | sort -r
Loading mirror speeds from cached hostfile
Loaded plugins: fastestmirror
docker-ce.x86_64 3:23.0.1-1.el7 docker-ce-stable
docker-ce.x86_64 3:23.0.0-1.el7 docker-ce-stable
docker-ce.x86_64 3:20.10.9-3.el7 docker-ce-stable
docker-ce.x86_64 3:20.10.8-3.el7 docker-ce-stable
docker-ce.x86_64 3:20.10.7-3.el7 docker-ce-stable
docker-ce.x86_64 3:20.10.6-3.el7 docker-ce-stable
……省略部分输出
(6)选择指定版本安装
[root@localhost ~]# yum install docker-ce-20.10.5 docker-ce-cli-20.10.5 -y
(7)启动
[root@localhost ~]# systemctl start docker
[root@localhost ~]# docker version
Client: Docker Engine - Community
Version: 20.10.5 # 这里注意服务端版本和客户端版本要相同
API version: 1.41
Go version: go1.13.15
Git commit: 55c4c88
Built: Tue Mar 2 20:33:55 2021
OS/Arch: linux/amd64
Context: default
Experimental: true
Server: Docker Engine - Community
Engine:
Version: 20.10.5 # 这里注意服务端版本和客户端版本要相同
API version: 1.41 (minimum version 1.12)
Go version: go1.13.15
Git commit: 363e9a8
Built: Tue Mar 2 20:32:17 2021
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.6.18
GitCommit: 2456e983eb9e37e47538f59ea18f2043c9a73640
runc:
Version: 1.1.4
GitCommit: v1.1.4-0-g5fd4c4d
docker-init:
Version: 0.19.0
GitCommit: de40ad0
4.3.3 二进制安装docker
官网文档:https://docs.docker.com/engine/install/binaries/
# (1)下载二进制安装包
## 版本选择:https://download.docker.com/linux/static/stable/x86_64/
[root@localhost ~]# wget https://download.docker.com/linux/static/stable/x86_64/docker-19.03.13.tgz
#(2)解压
[root@localhost ~]# ls
anaconda-ks.cfg docker-19.03.13.tgz update.sh
[root@localhost ~]# tar xf docker-19.03.13.tgz
[root@localhost ~]# ls docker
containerd containerd-shim ctr docker dockerd docker-init docker-proxy runc
# (3)复制相关文件到bin
[root@localhost ~]# cp docker/* /usr/bin/ # 做这一步是为了让普通用户也能使用docker命令
#(4)systemd 管理docker
[root@test01 ~]# vim /etc/systemd/system/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
[root@test01 ~]# chmod +x /etc/systemd/system/docker.service
[root@test01 ~]# systemctl daemon-reload
[root@test01 ~]# systemctl start docker
[root@test01 ~]# docker version
Client: Docker Engine - Community
Version: 19.03.13
API version: 1.40
Go version: go1.13.15
Git commit: 4484c46
Built: Wed Sep 16 16:58:04 2020
OS/Arch: linux/amd64
Experimental: false
Server: Docker Engine - Community
Engine:
Version: 19.03.13
API version: 1.40 (minimum version 1.12)
Go version: go1.13.15
Git commit: 4484c46
Built: Wed Sep 16 17:04:43 2020
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: v1.3.7
GitCommit: 8fba4e9a7d01810a393d5d25a3621dc101981175
runc:
Version: 1.0.0-rc10
GitCommit: dc9208a3303feef5b3839f4323d9beb36df0a9dd
docker-init:
Version: 0.18.0
GitCommit: fec3683
4.3.4 docker的存储引擎
docker支持的存储引擎的选择:https://docs.docker.com/storage/storagedriver/select-storage-driver/
目前docker的存储引擎为overlay2,不同的存储引擎需要相应的系统内核支持,如果需要磁盘分区的时候传递d-type文件分层功能,即需要传递内核参数开启格式化磁盘的时候指定功能。
4.3.4.1 AUFS
AUFS (Another UnionFS)是一种 Union FS,是文件级的存储驱动。所谓UnionFS 就是把不同物理位置的目录合并mount到同一个目录中。简单来说就是支持将不同目录挂载到同一个虚拟文件系统下的文件系统。这种文件系统可以一层一层地叠加修改文件。无论底下有多少层都是只读的,只有最上层的文件系统是可写的。当需要修改一个文件时,AUFS创建该文件的一个副本,使用Cow将文件从只读层复制到可写层进行修改,结果也保存在可写层。在Docker中,底下的只读层就是image,可写层就是Container,是Docker 18.06及更早版本的首选存储驱动程序,在内核3.13上运行Ubuntu 14.04时不支持overlay2。
4.3.4.2 Overlay
Overlay是一种Union FS文件系统,Linux内核3.18后支持。
4.3.4.3 overlay2
overlay2是Overlay的升级版,到目前为止,所有Linux发行版推荐使用的存储类型(docker官方推荐)。
4.3.4.4 devicemapper
devicemapper是CentOS和RHEL的推荐存储驱动程序,因为之前的内核版本不支持overlay2,但是当前较新版本的CentOS和RHEL现在已经支持overlay2,因此推荐使用overlay2。
4.3.4.5 ZFS/btrfs
ZFS(Sun-2005年推广使用)/btrfs(Oracle-2007年推广使用):目前没有广泛使用。
4.3.4.6 vfs
vfs用于测试环境,适用于无法使用copy-on-write文件系统的情况。此存储驱动程序的性能很差,通常不建议用于生产。
4.3.4.7 使用存储引擎注意事项
Docker官方推荐首选存储引擎为overlay2, devicemapper存在使用空间方面的一些限制,虽然可以通过后期配置解决,但是官方依然推荐使用overlay2,以下是网上查到的部分资料:https://www.cnblogs.com/youruncloud/p/5736718.html
上图的ftype=1表示开启镜像分层功能,7.3及以上的系统基本都是自动开启的。
如果docker数据目录是一块单独的磁盘分区而且是xfs格式的,那么需要在格式化的时候加上参数-n ftype=1,否则后期在启动容器的时候会报错不支持d-type。下图是CentOS 7.2
Centos 7.2报错界面
CentOS7.3修复此问题
4.3.5 docker服务进程
通过查看docker进程,了解docker的运行及工作方式
4.3.5.1 查看宿主机进程树
[root@localhost ~]# docker version
Server: Docker Engine - Community
Engine:
Version: 19.03.13
API version: 1.40 (minimum version 1.12)
Go version: go1.13.15
Git commit: 4484c46
Built: Wed Sep 16 17:04:43 2020
OS/Arch: linux/amd64
Experimental: false
4.3.5.2 查看containerd进程关系
有四个进程
dockerd:被docker-client直接访问,其父进程为宿主机的systemd守护进程。
docker-proxy:实现容器通信,主要维护IPtables规则,其父进程为dockerd
containerd:被dockerd进程调用以实现与runc交互来创建容器。
containerd-shim:真正运行容器的载体,其父进程为containerd。
4.3.5.3 containerd-shim命令使用
这个一般不直接使用,都是由docker命令来进行容器操作
[root@localhost ~]# containerd-shim -h
Usage of containerd-shim:
-address string
grpc address back to main containerd
-containerd-binary containerd publish
path to containerd binary (used for containerd publish) (default "containerd")
-criu string
path to criu binary
-debug
enable debug output in logs
-namespace string
namespace that owns the shim
-runtime-root string
root directory for the runtime (default "/run/containerd/runc")
-socket string
abstract socket path to serve
-systemd-cgroup
set runtime to use systemd-cgroup
-workdir string
path used to storge large temporary data
4.3.5.4 容器的创建与管理过程
通信流程
- 我们通过docker命令访问dockerd,dockerd通过grpc协议和containerd模块通信(runc)交换,dockerd和containerd通信的socket文件:/run/containerd/containerd.sock。
- containerd在 dockerd 启动时被启动,然后containerd启动grpc请求监听,containerd 处理grpc请求,根据请求做相应动作。
(dockerd靠 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock,来和containerd通信)
- 若是创建容器,containerd拉起一个container-shim 容器进程,并进行相应的创建操作。
- container-shim被拉起后, start/exec/create 拉起runC进程,通过exit、control文件和containerd通信,通过父子进程关系和 SIGCHLD(信号)监控容器中进程状态。
5.在整个容器生命周期中,containerd通过epoll监控容器文件,监控容器事件。
4.3.5.5 docker信息验证
[root@test01 ~]# docker info
Client:
Debug Mode: false # 客户段是否开启debug模式
Server:
Containers: 1 # 当前主机上的容器总数
Running: 1 # 有几个容器是正在运行的
Paused: 0 # 暂停的容器数量
Stopped: 0 # 已停止的容器数量
Images: 3 # 本地主机上镜像的数量
Server Version: 19.03.13 # Docker引擎的版本号(服务端)
Storage Driver: overlay2 # Docker宿主机上使用的存储驱动程序
Backing Filesystem: xfs # 后端文件系统,即服务器的磁盘文件系统
Supports d_type: true # 是否支持d_type
Native Overlay Diff: true # 是否支持差异数据存储
Logging Driver: json-file # 日志类型
Cgroup Driver: cgroupfs # cgroups类型
Plugins: # 本地主机上可用的Docker插件列表
Volume: local # 卷
Network: bridge host ipvlan macvlan null overlay # docker支持的网络类型
Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog # 日志类型
Swarm: inactive # 本地主机上Swarm模式的状态信息
Runtimes: runc # 本地主机上支持的运行时列表
Default Runtime: runc # 默认的容器运行时
Init Binary: docker-init # 初始化容器的守护进程,即pid为1的进程
containerd version: 8fba4e9a7d01810a393d5d25a3621dc101981175 # containerd组件的版本号
runc version: dc9208a3303feef5b3839f4323d9beb36df0a9dd # runc组件的版本号
init version: fec3683 # 器初始化组件的版本号
Security Options: # Docker宿主机上的安全选项
seccomp # 审计
Profile: default # 默认的配置文件
Kernel Version: 3.10.0-1062.el7.x86_64 # 宿主机内核版本
Operating System: CentOS Linux 7 (Core) # 宿主机操作系统
OSType: linux # 宿主机操作系统类型
Architecture: x86_64 # 宿主机架构
CPUs: 2 # 宿主机CPU数量
Total Memory: 3.701GiB # 宿主机总内存
Name: test01 # 宿主机主机名
ID: NFCG:7O4K:ZAIA:PJNQ:EBFF:47RQ:ETQ7:4CZY:W6LP:UL6G:XGUM:L37O # 宿主机ID
Docker Root Dir: /var/lib/docker # docker在宿主机上的数据目录
Debug Mode: false # 是否开启debug
Registry: https://index.docker.io/v1/ # 默认的镜像仓库地址
Labels: # 其他标签
Experimental: false # 是否为测试版
Insecure Registries: # 非安全的镜像仓库
127.0.0.0/8
Registry Mirrors: # 自定义的镜像仓库地址
https://carcbv39.mirror.aliyuncs.com/
Live Restore Enabled: false # 是否开启活动重启(重启docker-daemon不关闭容器)
Product License: Community Engine # 产品许可信息
4.3.6 重点知识总结
(1)namespace和cgroup。# 面试
(2)docker的各种安装方式。
(3)docker镜像操作命令。
(4)docker进程对应关系。
(5)docker存储引擎。
(7)Union File system联合文件挂载。
4.4 docker安装后的配置(优化)
4.4.1 配置镜像下载加速
docker默认下载镜像是从国外下载的,有时候会很慢,这个时候我们可以添加国内的下载地址来进行加速。
4.4.1.1 获取加速地址
阿里云镜像加速文档:https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors
# mkdir -p /etc/docker 这个目录安装启动完docker后就自动创建好了
[root@localhost ~]# tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://carcbv39.mirror.aliyuncs.com"]
}
EOF
[root@localhost ~]# cat /etc/docker/daemon.json
{
"registry-mirrors": ["https://carcbv39.mirror.aliyuncs.com"]
}
[root@localhost ~]# systemctl daemon-reload
[root@localhost ~]# systemctl restart docker
[root@localhost ~]# docker info|grep "Registry M" -A 1 # 这里可以看到镜像加速配置成功了
Registry Mirrors:
https://carcbv39.mirror.aliyuncs.com/
4.4.2 修改存储引擎
注意:修改存储引擎会有数据丢失,请先做好备份。谨慎操作
操作前备份数据
换存储引擎会导致已有的容器和镜像及容器数据全部丢失,所以做之前一定要备份。
备份:镜像、数据(容器里面的)、找到容器的启动命令
4.4.2.1 操作前检查
# 这里可以看到有一个容器和一个镜像
[root@localhost ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f8853c279fda nginx "/docker-entrypoint.…" 56 minutes ago Up 17 seconds 0.0.0.0:8080->80/tcp nginx-test
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 605c77e624dd 14 months ago 141MB
[root@localhost ~]# docker info|grep "Storage Driver: " # 这里默认的存储引擎是overlay2
Storage Driver: overlay2
4.4.2.2 修改存储引擎为overlay
[root@localhost ~]# dockerd -h|grep 'storage-driver' # 可以通过dockerd -h来查看可变更的地方
-s, --storage-driver string Storage driver to use # 这就修改存储引擎的参数
方式1:添加参数到docker.service启动配置中
不推荐该方式
[root@localhost ~]# vim /etc/systemd/system/docker.service
[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
After=network-online.target docker.socket firewalld.service
Wants=network-online.target
[Service]
Type=notify
ExecStart=/usr/bin/dockerd --storage-driver overlay # --storage-driver overlay添加到ExecStart这一行的末尾
ExecReload=/bin/kill -s HUP $MAINPID
LimitNOFILE=1048576
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
TimeoutStartSec=0
Restart=on-abnormal
[Install]
WantedBy=multi-user.target
[root@localhost ~]# systemctl daemon-reload
[root@localhost ~]# systemctl restart docker
[root@localhost ~]# docker info | grep "Storage Driver: "
Storage Driver: overlay #这里可以看到存储引擎已经被修改
# 下面可以看到容器和镜像全部没有了
[root@localhost ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
方式2:添加参数到daemon.json中
推荐该方式
# 操作前复原,删除在启动配置文件中添加的配置
[root@localhost ~]# grep 'ExecStart' /etc/systemd/system/docker.service
ExecStart=/usr/bin/dockerd # 这里添加的配置我已经删除了
[root@localhost ~]# cat /etc/docker/daemon.json
{
"registry-mirrors": ["https://carcbv39.mirror.aliyuncs.com"],
"storage-driver": "overlay2" # 修改存储引擎的配置
}
[root@localhost ~]# systemctl restart docker
[root@localhost ~]# systemctl status docker
[root@localhost ~]# docker info | grep "Storage Driver: "
Storage Driver: overlay2
4.4.3 修改docker数据存储目录
注意:该操作也会有数据丢失,操作前做好备份。
数据目录默认在/va/lib/docker下
这个操作不是必须的,主要是看盘的大小。
如果只有一块盘,且空间比较大,可以不用修改。
如果只有一块盘,磁盘空间还很小,就需要添加一块数据盘,修改docker数据存储到数据盘上。
[root@localhost ~]# dockerd -h | grep 'data-root'
--data-root string Root directory of persistent Docker state (default "/var/lib/docker") # 使用该参数修改数据存储目录
[root@localhost ~]# docker info|grep 'Docker Root Dir'
Docker Root Dir: /var/lib/docker # 修改前
# 开始修改
[root@localhost ~]# cat /etc/docker/daemon.json
{
"registry-mirrors": ["https://carcbv39.mirror.aliyuncs.com"],
"storage-driver": "overlay2",
"data-root": "/data/docker" # 修改数据存储目录
}
[root@localhost ~]# mkdir -p /data/docker
[root@localhost ~]# systemctl daemon-reload
[root@localhost ~]# systemctl restart docker
[root@localhost ~]# docker info|grep 'Docker Root Dir'
Docker Root Dir: /data/docker # 修改后
[root@localhost ~]# ll /data/docker/ # dockr重启后在指定的目录生成了新的数据
total 0
drwx------ 2 root root 24 Mar 16 00:24 builder
drwx--x--x 4 root root 92 Mar 16 00:24 buildkit
drwx--x--x 3 root root 20 Mar 16 00:24 containerd
drwx------ 2 root root 6 Mar 16 00:24 containers
drwx------ 3 root root 22 Mar 16 00:24 image
drwxr-x--- 3 root root 19 Mar 16 00:24 network
drwx------ 3 root root 40 Mar 16 00:24 overlay2
drwx------ 4 root root 32 Mar 16 00:24 plugins
drwx------ 2 root root 6 Mar 16 00:24 runtimes
drwx------ 2 root root 6 Mar 16 00:24 swarm
drwx------ 2 root root 6 Mar 16 00:24 tmp
drwx------ 2 root root 6 Mar 16 00:24 trust
drwx------ 2 root root 25 Mar 16 00:24 volumes
4.4.4 开启内核转发功能(必做)
# 容器要想访问外部网络,需要本地系统的转发支持。在Linux 系统中,检查转发是否打开。
$sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1
# 如果为 0,说明没有开启转发,则需要手动打开。
sysctl -w net.ipv4.ip_forward=1
如果在启动 Docker 服务的时候设定 --ip-forward=true , Docker 就会自动设定系统的ip_forward 参数为 1。
4.5 docker镜像管理
Docker镜像含有启动容器所需要的文件系统及所需要的内容,因此镜像主要用于创建并启动docker容器。
Docker镜像含里面是一层层文件系统,叫做Union File System (Union FS联合文件系统),2004年由纽约州立大学石溪分校开发,联合文件系统可以将多个目录挂载到一起从而形成一整个虚拟文件系统,该虚拟文件系统的目录结构就像普通linux的目录结构一样,docker通过这些文件再加上宿主机的内核提供了一个linux的虚拟环境,每一层文件系统我们叫做一层 layer,联合文件系统可以对每一层文件系统设置三种权限,只读(readonly) 、读写(readwrite)和写出(whiteout-able),但是docker镜像中每一层文件系统都是只读的,构建镜像的时候,从一个最基本的操作系统开始,每个构建的操作都相当于做一层的修改,增加了一层文件系统,一层层往上叠加,上层的修改会覆盖底层该位置的可见性,这也很容易理解,就像上层把底层遮住了一样,当使用镜像的时候,我们只会看到一个完全的整体,不知道里面有几层也不需要知道里面有几层,结构如下:
联合文件系统演示
# cd /opt/
# mkdir a b system # 这里可以把a理解成镜像层、b理解可写层、system理解为容器的文件系统
# touch a/a.txt b/b.txt
# mount -t aufs -o dirs=./a:./b none ./system/ # 把a和b挂载到system下
4.5.1 镜像结构
关于镜像结构描述官方文档:https://docs.docker.com/storage/storagedriver/
一个典型的Linux文件系统由bootfs和rootfs两部分组成,bootfs(boot filesystem)主要包含bootloader和 kernel, bootloader主要用于引导加载kernel,当kernel被加载到内存中后bootfs会被umount掉,rootfs (root file system)包含的就是典型Linux系统中的/dev,/proc,/bin,/etc等标准目录和文件,下图就是docker image 中最基础的两层结构,不同的 linux发行版(如ubuntu和CentOS )在rootfs这一层会有所区别。
但是对于docker镜像通常都比较小,官方提供的centos基础镜像在20OMB左右,一些其他版本的镜像甚至只有几MB, docker镜像直接调用宿主机的内核,镜像中只提供rootfs,也就是只需要包括最基本的命令、工具和程序库就可以了,比如alpine镜像,在5M左右。
下图就是有两个不同的镜像在一个宿主机内核上实现不同的rootfs
容器、镜像父镜像
查看镜像层次
[root@test01 ~]# docker save nginx:latest > nginx.image
[root@test01 ~]# mkdir nginx
[root@test01 ~]# tar xf nginx.image -C nginx
[root@test01 ~]# cd nginx
[root@test01 nginx]# cat manifest.json
[{"Config":"605c77e624ddb75e6110f997c58876baa13f8754486b461117934b24a9dc3a85.json","RepoTags":["nginx:latest"],"Layers":["5df3f744c961b85bb93947d6a4288543c06e64b3130562ae7be85fdc9f7a5691/layer.tar","0cb59b23812a64ae9115a5cf081f05d3d1915c5d7d2304c64b1c3a07629122a9/layer.tar","728205feeb04d15cbf0b889a9336e553bf6d472a311724e567a156afe3cb105d/layer.tar","0e9f59de25dca39e1cc33d1b7a83f27668c800221d7f1062a5da0df9ba684f44/layer.tar","7e8578b7bc47a5f07742264aace8ee761078c30c1c2c2dafbc79a8ebd340c191/layer.tar","8c46511b06be8a5ed3d1478039cd8e3bcb0842b8a68c42babb5e9c4b6dc6beeb/layer.tar"]}]
命令行查看镜像是如何构成的
[root@test01 ~]# docker history nginx
IMAGE CREATED CREATED BY SIZE COMMENT
605c77e624dd 14 months ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon… 0B # 最后一步
<missing> 14 months ago /bin/sh -c #(nop) STOPSIGNAL SIGQUIT 0B
<missing> 14 months ago /bin/sh -c #(nop) EXPOSE 80 0B
<missing> 14 months ago /bin/sh -c #(nop) ENTRYPOINT ["/docker-entr… 0B
<missing> 14 months ago /bin/sh -c #(nop) COPY file:09a214a3e07c919a… 4.61kB
<missing> 14 months ago /bin/sh -c #(nop) COPY file:0fd5fca330dcd6a7… 1.04kB
<missing> 14 months ago /bin/sh -c #(nop) COPY file:0b866ff3fc1ef5b0… 1.96kB
<missing> 14 months ago /bin/sh -c #(nop) COPY file:65504f71f5855ca0… 1.2kB
<missing> 14 months ago /bin/sh -c set -x && addgroup --system -… 61.1MB
<missing> 14 months ago /bin/sh -c #(nop) ENV PKG_RELEASE=1~bullseye 0B
<missing> 14 months ago /bin/sh -c #(nop) ENV NJS_VERSION=0.7.1 0B
<missing> 14 months ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.21.5 0B
<missing> 15 months ago /bin/sh -c #(nop) LABEL maintainer=NGINX Do… 0B
<missing> 15 months ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 15 months ago /bin/sh -c #(nop) ADD file:09675d11695f65c55… 80.4MB # 第一步
[root@test01 ~]# docker history alpine # 像这种操作系统,体积小,层数也很少,因为里面就一个操作系统
IMAGE CREATED CREATED BY SIZE COMMENT
c059bfaa849c 15 months ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 15 months ago /bin/sh -c #(nop) ADD file:9233f6f2237d79659… 5.59MB
4.5.2 镜像相关命令
4.5.2.1 搜索镜像
在官方的docker仓库中搜索指定名称的docker镜像,也会有很多镜像。
官方镜像仓库:https://hub.docker.com/
推荐在官方搜索,展示更直观
[root@test01 ~]# docker search nginx # 不指定版本默认搜最新版
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
nginx Official build of Nginx. 18236 [OK]
linuxserver/nginx An Nginx container, brought to you by LinuxS… 188
bitnami/nginx Bitnami nginx Docker Image 153 [OK]
ubuntu/nginx Nginx, a high-performance reverse proxy & we… 79
privatebin/nginx-fpm-alpine PrivateBin running on an Nginx, php-fpm & Al… 72 [OK]
bitnami/nginx-ingress-controller Bitnami Docker Image for NGINX Ingress Contr… 23 [OK]
rancher/nginx-ingress-controller 11
……省略部分输出
[root@test01 ~]# docker search nginx:1.18 # 指定版本搜索
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
sspilchin/nginx-waf Nginx from upstream nginx:1.18 with added mo… 0
royye/php7.3 php:7.3.27-fpm-alpine3.12 extensions: redis … 0
biggerwing/nginx1.18.0 nginx:1.18.0 0
benzoasis/rsmall-frontend - php:lastest - node:lastest - nginx:1.18.0-… 0
# 参数说明:
NAME: 镜像仓库源的名称
DESCRIPTION: 镜像的描述
OFFICIAL: 是否 docker 官方发布。有[OK]的就是官方的。
stars: 类似 Github 里面的 star,表示点赞、喜欢的意思。
AUTOMATED: 自动构建。
4.5.2.2 下载镜像
# 命令格式
docker pull 仓库服务器:端口/项目名称/镜像名称:tag(版本)号
# 如:harbor.test.com/test/nginx-ubuntu:20230316_152540
[root@test01 ~]# docker pull tomcat
4.5.2.3 镜像下载状态
参数 | 描述 |
---|---|
Downloading | 正在下载镜像文件 |
Extracting | 正在解压镜像文件 |
Verifying Checksum | 正在校验下载的镜像文件的正确性。 |
Pull complete | 镜像下载完成 |
Image already exists | 本地已经存在相同的镜像,无需下载。 |
Error | 镜像下载出现错误。 |
4.5.2.4 查看镜像
下载完成的镜像比下载的大,因为下载完成后会解压
[root@test01 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 605c77e624dd 14 months ago 141MB
tomcat latest fb5657adc892 14 months ago 680MB
alpine latest c059bfaa849c 15 months ago 5.59MB
# 参数
REPOSITORY #镜像所属的仓库名称
TAG #镜像版本号(标识符),默认为 latest
IMAGE ID #镜像ID,镜像的唯一标识符
CREATED #镜像创建时间
SIZE #镜像大小
4.5.2.5 镜像导出
可以将镜像从本地导出问为一个压缩文件,然后复制到其他服务器进行导入使用。
方法1
[root@test01 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 605c77e624dd 14 months ago 141MB
tomcat latest fb5657adc892 14 months ago 680MB
alpine latest c059bfaa849c 15 months ago 5.59MB
[root@test01 ~]# docker save -o /tmp/alpine-latest.image alpine:latest # alpine:latest也可以换成镜像ID
[root@test01 ~]# ll -rt /tmp/
total 5740
drwxr-xr-x 2 root root 6 Mar 16 17:15 hsperfdata_root
-rw------- 1 root root 5875712 Mar 16 17:41 alpine-latest.image
方法2
[root@test01 ~]# docker save alpine:latest > /opt/alpine-latest-image.tar.gz # 这个后缀 可以自定义
[root@test01 ~]# ll -rth /opt/
total 5.7M
drwx--x--x 4 root root 28 Mar 15 14:59 containerd
drwxr-xr-x 2 root root 6 Mar 16 13:37 system
drwxr-xr-x 2 root root 19 Mar 16 13:38 b
drwxr-xr-x 2 root root 19 Mar 16 13:38 a
-rw-r--r-- 1 root root 5.7M Mar 16 17:50 alpine-latest-image.tar.gz
4.5.2.6 镜像导入
方式1
[root@test01 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 605c77e624dd 14 months ago 141MB
tomcat latest fb5657adc892 14 months ago 680MB
alpine latest c059bfaa849c 15 months ago 5.59MB
[root@test01 ~]# docker rmi c059bfaa849c # 因为我只开了一台虚拟机 所以要先删除,再做导入测试
[root@test01 ~]# cd /tmp/
[root@test01 tmp]# ls
alpine-latest.image hsperfdata_root
[root@test01 tmp]# docker load -i alpine-latest.image # -i,从指定文件中导入镜像
8d3ac3489996: Loading layer [==================================================>] 5.866MB/5.866MB
Loaded image: alpine:latest
[root@test01 tmp]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 605c77e624dd 14 months ago 141MB
tomcat latest fb5657adc892 14 months ago 680MB
alpine latest c059bfaa849c 15 months ago 5.59MB
方式2
[root@test01 tmp]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 605c77e624dd 14 months ago 141MB
tomcat latest fb5657adc892 14 months ago 680MB
alpine latest c059bfaa849c 15 months ago 5.59MB
[root@test01 tmp]# docker rmi c059bfaa849c
Untagged: alpine:latest
Deleted: sha256:c059bfaa849c4d8e4aecaeb3a10c2d9b3d85f5165c66ad3a4d937758128c4d18
Deleted: sha256:8d3ac3489996423f53d6087c81180006263b79f206d3fdec9e66f0e27ceb8759
[root@test01 tmp]# docker load < /tmp/alpine-latest.image
8d3ac3489996: Loading layer [==================================================>] 5.866MB/5.866MB
Loaded image: alpine:latest
[root@test01 tmp]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 605c77e624dd 14 months ago 141MB
tomcat latest fb5657adc892 14 months ago 680MB
alpine latest c059bfaa849c 15 months ago 5.59MB
4.5.2.7 删除镜像
[root@test01 tmp]# docker rmi c059bfaa849c # 通过id删除或者容器名删除都可以,但是更推荐通过id删除,这样更加安全。
Untagged: alpine:latest
Deleted: sha256:c059bfaa849c4d8e4aecaeb3a10c2d9b3d85f5165c66ad3a4d937758128c4d18
Deleted: sha256:8d3ac3489996423f53d6087c81180006263b79f206d3fdec9e66f0e27ceb8759
# 参数
- `-f, --force`:强制删除镜像,即使有容器正在使用该镜像也会被强制删除。 # 不推荐使用该参数
- `-no-prune`:不会删除与底层镜像关联的任何镜像和容器。
- `-quiet, --quiet`: 安静地删除,不打印任何输出。
4.5.2.8 批量清理未使用的docker镜像
工作中,镜像长时间不清理,磁盘空间会越来越小,所以需要定期进行清理
docker image prune -a -f
# 参数
- `docker image prune`:用于清理 Docker 系统中未被使用的镜像。
- `-a`:指定清理所有无用镜像,包括被容器使用的和没有被使用的镜像。
- `-f`:强制执行清理操作,不需要确认删除操作