前言
如果大家没使用过Docker,就在电脑上下载一个VMware Workstation Pro,创建一个虚拟机安装一个windows操作一下感受一下,为什么我的电脑上还以再安装一台windows主机?其实你可以理解为Docker就是Linux系统的一个虚拟机软件。
我的Windows也可以安装Docker啊?打开Docker官网https://www.docker.com/get-started/点击"Download for Window"下载一个安装查看一下。Docker在Windows上可以运行是因为Windows统提供了一种叫做 Docker Desktop for Windows的工具,它通过在Windows上创建一个Linux虚拟机来实现。Docker Desktop for Windows使用了一种叫做Hyper-V的虚拟化技术,Windows上创建了一个轻量级的Linux虚拟机,该虚拟机运行一个精简的Linux内核称为Docker宿主机。
VMware和docker
VMware和docker同样是虚拟机,但他们却有一些不同:普通虚拟化(完全型解耦)和Docker(半解耦)
耦合:耦合指的是软件组件之间的依赖关系和关联程度。
解耦:指的是减少软件组件之间的依赖关系,降低耦合度,提高代码的灵活性、可维护性和可重用性。
简单理解,就是软件之间的依赖关系紧密不紧密。
- 普通虚拟化(完全型解耦):每个虚拟机都拥有自己的完整操作系统和内核。这意味着虚拟机之间是完全解耦的,它们可以运行不同版本的操作系统,甚至不同类型的操作系统(例如 Linux、Windows),并且彼此之间没有任何冲突。每个虚拟机都相当于一个独立的物理服务器,可以独立地进行管理和维护。
- 和Docker(半解耦):在Docker中使用了一种更轻量级的容器化技术所有的容器共享宿主主机的操作系统和内核,它们之间是半解耦的。尽管容器之间共享了操作系统的核心部分,但它们仍然是相互隔离的,每个容器拥有自己的文件系统、进程空间、网络空间等。因此,容器可以运行在不同的操作系统发行版中,但它们仍然受到宿主主机操作系统的限制,并且可能会受到容器之间的冲突影响。
所以你在VMware的linux虚拟机中可以升级linux的系统内核,但是不能在Docker的容器中升级容器的系统内核。如果你的软件服务对内核版本有严格要求,这个服务就不太适合用docker来实现。
还有服务不能用Docker实现?
还有服务不能用Docker实现?一个完整的服务有可能是由多个服务在一起运行实现。开发人员开发了服务A对内核有要求需要服务器内核版本A,而开发的另一个服务B对内核也有要求需要服务器内核版本B,那么Docker就不能再同一台服务来运行A和B两个服务。这就和Docker的这种半解耦的状态有关系了。
Docker的虚拟化
Docker有个核心的组件是Containerd。在讲容器的时候大部分人会认为是容器就是Docker,我更愿意把Docker和容器分开理解,我感觉Docker和Kubernetes只是容器的管理工具而真正的容器是底层的Containerd。Docker没有Containerd组件就创建不了容器,Containerd没了Docker依然可以独立的创建容器提供服务。
docker要管理容器的生命周期
找台docker主机执行命令:docker version
[root@localhost ~]# docker version
Client: Docker Engine - Community
Version: 26.0.0
API version: 1.45
Go version: go1.21.8
Git commit: 2ae903e
Built: Wed Mar 20 15:21:09 2024
OS/Arch: linux/amd64
Context: default
Server: Docker Engine - Community
Engine:
Version: 26.0.0
API version: 1.45 (minimum version 1.24)
Go version: go1.21.8
Git commit: 8b79278
Built: Wed Mar 20 15:20:06 2024
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.6.28
GitCommit: ae07eda36dd25f8a1b98dfbf587313b99c0190bb
runc:
Version: 1.1.12
GitCommit: v1.1.12-0-g51d5e94
docker-init:
Version: 0.19.0
GitCommit: de40ad0
我们看到docker有两个重要的组件docker引擎Client、Server:
- Client:是与用户交互的主要方式,它提供了一个命令行界面(CLI)和API,使得用户可以使用Docker来构建、运行和管理容器化的应用程序。用户可以通过Docker CLI命令(例如 docker run、docker build)通过 Docker API来向Server发送请求。
- Server:是一个后台运行的进程,负责管理Docker上的容器、镜像、网络和存储等资源。它是Docker引擎的核心组件之一,负责接收来自 Docker客户端的请求,并根据这些请求来创建、启动、停止、删除容器,以及构建、拉取、推送镜像等操作。它还通过与containerd进行通信来管理容器的生命周期。
docker:的基本架构
1、 doker分为用户端和服务器端:我们刚刚已经查看过了。 Server为客户端提供UESTfulAPI,响应来自客户端的请求采用模块化的架构,通过专门的Engine模块来分发管理各个来自客户端的任务。
2、docker-proxy:是dockerd的子进程,当需要进行容器映射的时候docker-proxy完成网络映射配置。
3、containerd:是dockerd的子进程提供GRPC接口来响应来自dockerd的请求,对下管理runC镜像和容器环境。
4、containerd-shim:是dockerd的子进进程为Runc容器提供支持同事作为容器的根进程。
docker的隔离策略
docker是半耦合的虚拟化,那么它是如何实现隔离的呢?Docker的隔离主要是通过Linux内核提供的一系列的特性来实现的.
进入linux系统在进入任意进程查看一下:cd /proc/2538/ns | ll
[root@localhost ns]# pwd
/proc/2538/ns
[root@localhost ns]# ll
总用量 0
lrwxrwxrwx. 1 bwk root 0 4月 27 15:26 ipc -> ipc:[4026532679]
lrwxrwxrwx. 1 bwk root 0 4月 27 15:26 mnt -> mnt:[4026532677]
lrwxrwxrwx. 1 bwk root 0 4月 27 15:26 net -> net:[4026532682]
lrwxrwxrwx. 1 bwk root 0 4月 27 15:26 pid -> pid:[4026532680]
lrwxrwxrwx. 1 bwk root 0 4月 27 15:26 user -> user:[4026531837]
lrwxrwxrwx. 1 bwk root 0 4月 27 15:26 uts -> uts:[4026532678]
基于此Docker有六项隔离实现了容器与宿主机、容器与容器之间的隔离:
IPC: 信号量、消息队列和共享内存
MNT: 挂载点、文件系统
NET: 网络设备、网络栈、端口等等
PID: 进程编号
USER: 用户、组
UTS: 主机名、域名
主要包括以下几个方面:
- 命名空间(Namespace): Docker使用Linux内核的命名空间机制来隔离容器的进程视图、网络、文件系统、用户、主机名等资源。每个容器都有自己独立的命名空间,使得容器内的进程无法感知到其他容器或宿主机上的进程,从而实现了进程级别的隔离。
- 控制组(Cgroup): Docker使用Linux内核的控制组机制来限制容器对 CPU、内存、磁盘、网络等资源的使用。通过将容器的资源限制和监控与控制组关联起来,Docker 可以有效地管理和调度容器的资源使用,防止容器占用过多的系统资源,从而确保系统的稳定性和可靠性。
- 容器文件系统(Union File System): Docker使用联合文件系统(Union File System)来实现容器的文件系统隔离。联合文件系统允许容器在一个只读的基础文件系统上叠加多个可写的文件系统层,使得容器内的文件系统与宿主机和其他容器的文件系统相互隔离,同时可以实现文件系统的共享和复用。
- 网络隔离: Docker使用Linux内核的网络命名空间来实现容器之间的网络隔离。每个容器都有自己独立的网络命名空间,使得容器可以拥有自己独立的网络接口、IP 地址和路由表,从而实现了网络层面的隔离。
1、命名空间(Namespace)
我的docker运行了一个Elasticsearch集群:
命名空间有六项隔离,我们查看一下:es01有映射端口是9200
[root@localhost docker]# ps -ef | grep 9200
root 3744 1501 0 16:32 ? 00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 9200 -container-ip 172.18.0.4 -container-port 9200
root 3750 1501 0 16:32 ? 00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 9200 -container-ip 172.18.0.4 -container-port 9200
root 4098 1418 0 16:43 pts/0 00:00:00 grep --color=auto 9200
[root@localhost docker]# ps -ef | grep 1501
root 1501 1 0 11:36 ? 00:00:29 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
root 3744 1501 0 16:32 ? 00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 9200 -container-ip 172.18.0.4 -container-port 9200
root 3750 1501 0 16:32 ? 00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 9200 -container-ip 172.18.0.4 -container-port 9200
root 4100 1418 0 16:44 pts/0 00:00:00 grep --color=auto 1501
一个映射ipv4一个映射ipv6进程是3744和3750,我们看到两个9200的进程有同一个父进程1501并且这个正是docker的主进程。
那么在容器内部是怎么样的呢?
[root@localhost ns]# docker exec -it es01 /bin/bash
root@e67d6d5de8b2:/usr/share/elasticsearch# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 2484 312 ? Ss 16:32 0:00 /bin/tini -- /usr/local/bin/docker-entrypoint.sh eswrapper
elastic+ 7 4.8 26.9 4272660 1040204 ? SLl 16:32 1:23 /usr/share/elasticsearch/jdk/bin/java -Xshare:auto -Des.networkaddress.cache.ttl=60 -Des.networkaddres
elastic+ 209 0.0 0.1 108392 4600 ? Sl 16:32 0:00 /usr/share/elasticsearch/modules/x-pack-ml/platform/linux-x86_64/bin/controller
root 253 3.7 0.0 4100 2164 pts/0 Ss 17:01 0:00 /bin/bash
root 263 0.0 0.0 5884 1476 pts/0 R+ 17:01 0:00 ps aux
在容器内部完全是独立的系统进程,宿主机看到的是3744和3750,3744和3750在/proc/3744/ns中六项均都指向的是同一样的命名空间标识符其实是一个服务。
2、控制组(Cgroup)
Cgroup(Control Group)是属于linux的内核提供的一个特性,用于限制和隔离一组进程对系统资源的使用。
它的作用:
1)资源限制,可以将组设置一定的内存限制。
2)优先级,通过优先级让一些组优先获得更多资源。
3)资源审计,用来统计系统实际上把多少资源用到适合的目的上。
4)隔离,为组隔离命名空间,这样使得一个组不会看到另一个组的进程、网络链接和文件系统。
4)控制,执行挂起、恢复、和重启等操作。
devices:设备权限控制。
cpuset:分配指定的cpu和内存
cpu:控制cpu占用率。
cpuacct:统计cpu使用情况
memory:限制内存使用情况。
freezer:冻结(暂停)Cgroup中的进程
net_prio: 设置进程的网络流量优先级。
huge_tlb:限制HugeTLB的使用。
perf_event:允许perfect工具基于Cgroup分组做性能监测。
Cgroup是一个虚拟的文件系统,在Linux发行版中系统启动时已经完成挂载。
[root@python ~]# mount | grep cgroup
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_prio,net_cls)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
在/sys/fs/cgroup/下相应的属性文件下都有一个docker的目录里面存放生成的容器隔离文件。太多自己可以查看一下。
[root@localhost cgroup]# pwd
/sys/fs/cgroup
[root@localhost cgroup]# ls
blkio cpu cpuacct cpu,cpuacct cpuset devices freezer hugetlb memory net_cls net_cls,net_prio net_prio perf_event pids systemd
Cgroup子系统介绍简单理解:
1、cpuset子系统
cpuset.cpus 允许进程使用的CPU列表(例如0~4,9)
cpuset.mems 允许进程使用的内存列表 (例如:0~1)
2、cpu子系统
用于限制进程cpu占用率,有三个功能分别通过不同的接口来实现。
1)cpu比重分配:接口cpu.shares。假设在cgroupfs的根目录下创建(C1、C2)并且cpu.shares分别配置512和1024.那么在C1和C2使用cpu时候c2将比c1使用多一倍的cpu使用率,只有当他们争用cpu的时候才起效。
2)cpu带宽限制:接口cpu.cfs_quota_us。接口单位是微秒。可以将period设置为1秒将quota设置为0.5秒。那么Cgroup中的进程在1秒内最多只能运行0.5秒然后被强制睡眠直到进入到下一个1秒。实时进程的带宽限制:接口cpu.rt_peri-od_us和cpu.rt_runtime_us。
3、cpuacct子系统
用来统计cpu的使用情况。
4、memory子系统
memory子系统用来限制Cgroup所能使用的内存上限 接口memory.limit_in_bytes。单位k/K、m/M或者g/G表示
echo 1G > memory.limit_in_bytes
memory.memsw.lim-it_in_bytes 设定内存加交换分区的使用总量。防止进程用尽交换分区。
memory.oom_control: 如果设置为0,那么在内存使用量超过上限时不会杀死进程,而是阻塞进程直到内存释放可供使用。另一方面系统会向用户发送通知用户的监控程序可以根据该事件来做相应处理。
memory.stat: 汇报内存使用信息。
5、blkio 子系统
用来限制Cgroup的block I/O带宽。(就是磁盘IO)
.blkio.weight: 设置权重值,范围100~1000。和cpu.shares类似,是比重分配,不是绝对资源限制,只有在Cgroup在争用同一块设备时候才起作用。
.blkio.weight_device对具体的设备设置权重值,这个值会覆盖blkio.weight。
6、devices子系统
用来控制Cgroup的进程对那些设备具有访问权限。
.devices.list 只读文件显示目前允许访问的设备列表。每个条目分3个域。
1)类型:可以是a、c或b,分别表示所有设备、字符设备、块设备。
2)设备号
3)权限r、w或m的组合分别表示可读、可写、可创建设备节点。
.devices.allow 只写文件,以上描述的格式写入文件,就可以允许相应的设备访问权限。
.devices.deny 只写文件,以上描述的格式写入该文件,就可以禁止相应的设备访问权限。
我们用memroy看一下:
[root@localhost /]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e67d6d5de8b2 docker.elastic.co/elasticsearch/elasticsearch:7.17.14 "/bin/tini -- /usr/l…" 3 weeks ago Up 5 hours 0.0.0.0:9200->9200/tcp, :::9200->9200/tcp, 9300/tcp es01
55d5e73f9a94 docker.elastic.co/elasticsearch/elasticsearch:7.17.14 "/bin/tini -- /usr/l…" 3 weeks ago Up 5 hours 9200/tcp, 9300/tcp es02
53f7c8128cc1 docker.elastic.co/elasticsearch/elasticsearch:7.17.14 "/bin/tini -- /usr/l…" 3 weeks ago Up 5 hours 9200/tcp, 9300/tcp es03
看一下es01的内存配置这个内存我是没做任何限制,表示容器可以使用宿主机上的全部可用内存:
[root@localhost /]# docker inspect es01 --format='{{.HostConfig.Memory}}'
0
查看一下容器ID:
[root@localhost ~]# docker inspect es01
[
{
"Id": "e67d6d5de8b273e69fbc806d303f1bea169941779ade044be2081f488f5db5ba",
"Created": "2024-04-04T13:56:18.043205656Z",
在/sys/fs/cgroup/memory/docker目录下:
[root@localhost docker]# ll
总用量 0
drwxr-xr-x. 2 root root 0 4月 27 11:36 53f7c8128cc1a1117e4ec0b521d8fcc4c6ca4df490f5ac44c8463b5cf19920ac
drwxr-xr-x. 2 root root 0 4月 27 11:36 55d5e73f9a94677e00fa5a4a77e492a312fe1ab7101f27000b7c2b6f59c2956c
-rw-r--r--. 1 root root 0 4月 27 11:36 cgroup.clone_children
--w--w--w-. 1 root root 0 4月 27 11:36 cgroup.event_control
-rw-r--r--. 1 root root 0 4月 27 11:36 cgroup.procs
drwxr-xr-x. 2 root root 0 4月 27 11:36 e67d6d5de8b273e69fbc806d303f1bea169941779ade044be2081f488f5db5ba
进入到虚拟目录:
[root@localhost e67d6d5de8b273e69fbc806d303f1bea169941779ade044be2081f488f5db5ba]# cat memory.limit_in_bytes
9223372036854771712
[root@localhost e67d6d5de8b273e69fbc806d303f1bea169941779ade044be2081f488f5db5ba]# echo 1G > memory.limit_in_bytes
[root@localhost e67d6d5de8b273e69fbc806d303f1bea169941779ade044be2081f488f5db5ba]# cat memory.limit_in_bytes
1073741824
实际上我们对内存做了1G的限制。重启容器:
[root@localhost e67d6d5de8b273e69fbc806d303f1bea169941779ade044be2081f488f5db5ba]# docker restart es01
es01
[root@localhost e67d6d5de8b273e69fbc806d303f1bea169941779ade044be2081f488f5db5ba]# ls
[root@localhost e67d6d5de8b273e69fbc806d303f1bea169941779ade044be2081f488f5db5ba]# cd ..
[root@localhost docker]# cd e67d6d5de8b273e69fbc806d303f1bea169941779ade044be2081f488f5db5ba/
[root@localhost e67d6d5de8b273e69fbc806d303f1bea169941779ade044be2081f488f5db5ba]# cat memory.limit_in_bytes
9223372036854771712
因为他是虚拟文件,重启后目录下文件消失,恢复docker默认设置。
哪些是耦合的
- 共享内核:Docker 容器与宿主机共享同一个内核,因此容器内部的进程直接运行在宿主机的内核之上。这种共享内核的特性导致容器与宿主机之间有一定的耦合性,容器的行为受限于宿主机的内核版本和配置。
- 资源:虽然 Docker 容器提供了一定程度的资源隔离,如 CPU、内存和网络等,但是由于容器与宿主机共享同一个内核,因此容器之间的资源隔离并不是完全隔离的。例如,在容器内部可以通过一些技巧访问到宿主机的资源,将宿主机上的某些目录挂载到容器内部,以便与宿主机共享文件或配置。
- 网络:Docker 容器提供了一定程度的网络隔离,每个容器都有自己独立的网络栈和网络命名空间。但是由于容器共享宿主机的网络设备,因此容器之间仍然可以相互通信,容器的网络行为也受限于宿主机的网络配置。