更新历史
- 2022-08-16 初稿
Docker是什么
Docker封装了整个软件运行时环境,是可以做资源隔离和限制的进程。既然可以做隔离和限制,那么也可以不做隔离和限制,这样就和在宿主机上启动一个进程差不多了。同宿主机直接启动进程相比,Docker容器由Docker Engine进行进程管理,裸机进程由Systemd做进程管理。容器内的文件系统可以是其他的Linux发行版。
Docker Engine根据需求将隔离和限制后的容器,以指定的容器镜像做文件系统挂载,启动容器内的指定可执行文件作为子进程,提供服务,将服务端口转发映射到宿主机上。可以看到Docker Engine做了大量底层工作,着重可以关注隔离用的Namespace概念,限制用的Cgroup概念,容器镜像概念。
Namespace
“Namespace做隔离,Cgroup做限制。”学过Docker原理的同学都知道这么一句话。那么,有哪些类型的Namespace呢?
| 类型 | 用途 |
|---|---|
| UTS | UTS(UNIX Time-sharing System) namespace提供了主机名和域名的隔离,这样,每个docker容器就可以拥有独立的主机名和域名了,在网络上可以被视为一个独立的节点,而非宿主机上的一个进程。Docker中,每个镜像基本都以自身所提供的服务名称来命名镜像的hostname,且不会对宿主机产生任何影响,其原理就是利用了UTS namespace。 |
| IPC | 进程间通信(Inter-Process Communication, IPC) 涉及的IPC资源包括常见的信号量、消息队列和共享内存。申请IPC资源就申请了一个全局唯一的32位ID,所以IPC namespace中实际上包含了系统IPC标识符以及实现POSIX消息队列的文件系统。在同一个IPC namespace下的进程彼此可见,不同的IPC namespace下的进程互不可见。 |
| PID | PID namespace隔离非常实用,它对进程PID重新标号,即两个不同的PID namespace下的进程可以有相同的PID。每个PID namespace都有自己的计数程序。内核为所有的PID namespace维护了一个树状结构,最顶层的是系统初始时创建的,被称为root namespace。它所创建的新PID namespace被称为child namespace,而原先的PID namespace就是新创建的PID namespace的parent namespace。通过这种方式,不同的PID namespace就会形成一个层级体系。所属的父节点可以看到字节点中的进程,并可以通过信号等方式对字节点中的进程产生影响。反过来,子节点却不能看到父节点PID namespace中的任何内容,由此产生如下结论。1,每个PID namespace 中的第一个进程PID 1,都会像传统Linux中的init进程一样拥有特权,起特殊作用。2,一个namespace中的进程,不可能通过kill或ptrace影响父节点或者兄弟节点中的进程,因为其他节点的PID在这个namespace中没有任何意义。3,如果你在新的PID namespace中重新挂载/proc文件系统,就会发现其下只显示同属于一个PID namespace中的其他进程。4,在root namespace中可以看到所有的进程,并且递归包含所有子节点中的进程。 |
| MNT | mount namespace通过隔离文件系统挂载点对隔离文件系统提供支持,它是历史上第一个Linux namspace,所以标识位比较特使,为CLONE_NEWNS。隔离后,不同mount namespace中的文件结构发生变化也互不影响。可以通过/proc/[pid]mounts查看到所有挂载在当前namespace中的文件系统,还可以通过/proc/[pid]/mountstats看到mount namespace中文件设备的统计信息,包括挂载文件的名字、文件系统类型、挂载位置等。 |
| NET | network namespace主要提供了关于网络资源的隔离,包括网络设备、IPv4和IPv6协议栈、IP路由表、防火墙、/proc/net目录、/sys/class/net目录、套接字等。一个物理的网络设备最多存在于一个network namespace中,可以通过创建veth pair(虚拟网络设备对:有两端,类似管道,如果数据从一端传入另一端也能收到,反之亦然)在不同的network namespace间创建通道,以达到通信目的。 |
| USER | user namespace主要隔离了安全相关的标识符(identifiers)和属性(attributes),包括用户ID,用户组ID,root目录,key(密钥)以及特殊权限。说的通俗一点,一个普通用户的进程通过clone()创建的新进程在新user namespace中可以拥有不同的用户和用户组。这意味着一个进程在容器外属于一个没有特权的普通用户,但是它创建的容器进程却属于拥有所有权限的超级用户,这个技术为容器提供了极大的自由。 |
| Cgroup | Linux 内核4.6之后又添加了Cgroup这namespace, 用于隔离Cgroup的根目录 |
| Time | Linux 内核5.6 之后又添加了时钟namespace,用于隔离容器和宿主机的时间 |
Cgroup
Cgroup到底可以限制哪些资源呢?
- CPU
- 内存
- 磁盘IO
容器镜像
容器隔离文件系统之后,只能看到挂载点下的文件,并共享宿主机的内核。
容器、Docker Engine和宿主机的关系
容器实际上是宿主机上的一个进程,只是其很多namespace和宿主机不同或相同。Docker Engine是容器进程的父进程。
容器和镜像的关系
可以用可执行文件和进程的关系,来类比镜像和容器的关系。即镜像是容器的静态表现,容器是镜像启动后的动态表现。
容器和虚拟机的区别
- 容器的性能比虚拟机好,几乎接近宿主机的性能
- 容器的隔离性不如虚拟机好,一个容器的崩溃,有可能影响到宿主机上其他的容器运行
- 虚拟机上可以启动容器,而几乎不会有人在容器中启动虚拟机
如何使用容器
- 将容器看成一个函数来使用,所有变量由外部传入,结果输出到容器外
- 避免使用容器内的变化配置,比如IP地址,避免变量写死在容器内,由外部传入管理更方便。
- 避免在容器内使用多进程,特别是业务不相关的不同进程
- 容器的进程要前台启动,否则启动后就停止了,避免后台启动主进程,然后使用无关命令挂前台的情况,比如"tail -f /var/log/message",这事关容器生命周期管理的正确性
- 尽量使用Dockerfile来编排容器镜像的构建,声明式API的优势明显
什么情况下不推荐用容器
- IO密集场景,例如数据库,日志管理等