docker容器基本原理简介

发布于:2024-06-17 ⋅ 阅读:(15) ⋅ 点赞:(0)

一、docker容器实例运行的在linux上是一个进程

1)、我们通过docker run 通过镜像运行启动的在linux上其实是一个进程,例如我们通过命令运行一个redis:

docker run -d --name myredis redis

在这里插入图片描述

2)、可以看到首先我们本地还没有redis镜像

Unable to find image 'redis:latest' locally

然后其就冲远程仓库拉取最新版本的redis镜像

latest: Pulling from library/redis

3)、然后我们可以看到镜像是一层一层的拉取的(镜像是分层的,一层一层从最基础的镜像往上叠)

c7a4e4382001: Pull complete 
4044b9ba67c9: Pull complete 
c8388a79482f: Pull complete 
413c8bb60be2: Pull complete 
1abfd3011519: Pull complete 

4)、拉取下来后,其就运行产生了一个镜像实例d3775e5e4a1659f2ee592a7e69482983d2aa70d35f814b9e4a64fd121c0f42cf

在这里插入图片描述

5)、可以看到linux宿主机已经有这个进程了

6)、然后我们通过docker exec -it d3775e5e4a1659f2ee592a7e69482983d2aa70d35f814b9e4a64fd121c0f42cf /bin/bash进入这个实例

在这里插入图片描述

可以看到我们进入了这个实例,然后其ps命令也没有,这个应该也是docker轻量化的体现,没必要的就不额外加

7)、我们进入到/usr/local/bin下面,就可以看到我们的redis相关的了。

在这里插入图片描述

8)、然后我们通过redis-cli连接下

在这里插入图片描述

二、Linux 联合文件系统

​ Linux 联合文件系统(Union File System,简称 UnionFS 或 UFS)是一种分层的文件系统,它用于将不同的文件系统的文件、目录集合在一起,形成一个单一的文件系统。直观简单来说,就是将不同文件目录整合到一个work工作目录,然后其使用了写时复制(copy-on-write),也就是当我们要对某个文件进行写操作的时候,将原来的再copy一份,在进行写操作的时候,不影响原来的文件,在这个新文件进行写。下面我们就用一个案例来说明

在这里插入图片描述

​ 我们可以看到,这里创建了4个目录:lowner、upper、merged、work:

​ 1、lowner目录我们是只读的,下面两个目录lowner、lowner2两个其下面创建两个文件。

​ 2、upper目录是可写的,我们在其下面创建了upper.txt文件。

​ 3、merged目录是我们联合合并lowner、upper目录形成的统一视图。有点类似于数据库的视图

​ 4、work目录被用于处理这些目录合并操作中的临时文件和元数据

​ 下面我们就通过具体的命令来将其合并:

mount -t overlay overlay -o lowerdir=./lowner/lowner1:./lowner/lowner2,upperdir=./upper,workdir=./work ./merged

在这里插入图片描述

可以看到我们通过overlay挂载形成联合文件系统后,其将只读的lownerupper目录下面的所有文件都整合到merged目录下面了。

在这里插入图片描述

​ 我们进入到merged目录下面

在这里插入图片描述

​ 我们在merged下面将lower1.txtupper.txt进行修改

在这里插入图片描述

​ 可以看到在upper下面,由于我们修改了lower1.txt文件,写时复制,所以在upper可写目录下面产生了新的文件

在这里插入图片描述

​ 新写的不影响原来lowner只读的文件,而upper是可写的,所以upper.txt直接在原来的上面改的。

​ 我们前面在运行redis容器实例拉取镜像的时候,其是一层一层拉取的,,也就是容器镜像也是这样一层、一层由基础的只读、还有可写的联合起来的,最底层的与linux相关的,则是与linux宿主机使用相同的文件,当容器运行时,它会在一个可写的上层(upper layer)中进行修改,而原始镜像层(lower layers)则保持不变,这也是镜像轻量化的一部分使用体现。

三、Namepsace

​ namespace是Linux提供的一种内核级别环境隔离的方法,主要是为了实现资源隔离。例如我们的不同的容器实例,是相互隔离的,它们都可以有pid为1的进程。还有网络隔离,挂载隔离这些。

1、不同Namespace介绍

1. PID Namespace

​ PID Namespace允许进程拥有其自己的PID空间

2. Mount Namespace

​ Mount Namespace允许进程拥有其自己的文件系统挂载点视图。这允许容器在其自己的文件系统树中挂载和卸载文件系统,而不会影响宿主机。

3. UTS Namespace

​ UTS Namespace允许进程拥有其自己的主机名和域名。

4. Network Namespace

​ Network Namespace允许进程拥有其自己的网络栈。这允许容器拥有其自己的网络接口、路由表、防火墙规则等。

5. IPC Namespace

​ IPC Namespace允许进程拥有其自己的IPC资源,如消息队列、信号量等。这有助于隔离进程间的通信。

6、User Namespace

​ User Namespace 主要是用来隔离用户和用户组的。

2、PID案例

我们的Java程序:

import java.util.Random;

public class TestCPU {

    public static void main(String[] args) {
        System.out.println("start exec ");
        while (true){
            Random random = new Random();
            double c =  random.nextDouble() * random.nextDouble();
        }
    }

}

​ 这个也就是一直循环计算一个数。

在这里插入图片描述

下面我们来看一个基本的PID案例:

root@k8s-master namespace]# echo $$
40682
[root@k8s-master namespace]# 
[root@k8s-master namespace]# ls -l /proc/1/ns/
总用量 0
lrwxrwxrwx 1 root root 0 615 22:41 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 615 22:41 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 615 22:41 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 615 22:20 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 615 22:41 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 615 22:41 uts -> uts:[4026531838]
[root@k8s-master namespace]# 
[root@k8s-master namespace]# ls -l /proc/40682/ns/
总用量 0
lrwxrwxrwx 1 root root 0 615 22:41 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 615 22:41 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 615 22:41 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 615 22:41 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 615 22:41 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 615 22:41 uts -> uts:[4026531838]
[root@k8s-master namespace]# 

​ 在这个案例中我们可以看到通过echo $$输出当前的shell环境线程号PID为40682,由于其本身是从最顶级的父线程1fork的,可以看到我们ls -l /proc/1/ns/ls -l /proc/40682/ns/两个输出的不同namespace的的编号是相同的,然后其的pid都是4026531836,下面我们通过unshare --pid --fork --mount-proc /bin/bash命令,这个命令就是从读取相处fork一个子线程,然后其的pid不共享,也就是独立。

[root@k8s-master home]# ls -l /proc/1/ns
总用量 0
lrwxrwxrwx 1 root root 0 6月  15 22:41 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 6月  15 22:41 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 6月  15 22:41 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 6月  15 22:20 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 6月  15 22:41 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 6月  15 22:41 uts -> uts:[4026531838]
[root@k8s-master home]# ls
common  docker  feverasa  package  test  TestCPU.class  TestCPU.java  testq.sh  usspa
[root@k8s-master home]# nohup java TestCPU &
[1] 23952
nohup: 忽略输入并把输出追加到"nohup.out"
[root@k8s-master home]# ps -ef | grep java
root      23952  14436 99 23:34 pts/0    00:00:18 java TestCPU
root      24088  14436  0 23:34 pts/0    00:00:00 grep --color=auto java
[root@k8s-master home]# ls -l /proc/23952/ns
总用量 0
lrwxrwxrwx 1 root root 0 6月  15 23:34 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 6月  15 23:34 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 6月  15 23:34 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 6月  15 23:34 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 6月  15 23:34 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 6月  15 23:34 uts -> uts:[4026531838]
[root@k8s-master home]# unshare --pid --fork --mount-proc /bin/bash
[root@k8s-master home]# nohup java TestCPU &
[1] 39
nohup: 忽略输入并把输出追加到"nohup.out"
[root@k8s-master home]# ls -l /proc/39/ns
总用量 0
lrwxrwxrwx 1 root root 0 6月  15 23:36 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 6月  15 23:36 mnt -> mnt:[4026532634]
lrwxrwxrwx 1 root root 0 6月  15 23:36 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 6月  15 23:36 pid -> pid:[4026532635]
lrwxrwxrwx 1 root root 0 6月  15 23:36 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 6月  15 23:36 uts -> uts:[4026531838]
[root@k8s-master home]# ps -ef | grep java
root         39      1 99 23:35 pts/0    00:09:31 java TestCPU
root         73      1  0 23:44 pts/0    00:00:00 grep --color=auto java
[root@k8s-master home]# 

​ 在上面的案例中,我们首先直接在当前shell进程中启动一个java应用23952,其的PID Namespace为pid:[4026531836],与PID1的Namespace是一样的。然后我们通过unshare --pid --fork --mount-proc /bin/bash fork一个新的,PID Namespace隔离其他,再启动一个引用,然后该应用的的pid为39,然后其的pid为4026532635,并且我们ps -ef | grep java可以看到,其只能看到自己的java应用,看不到其父类的java引用,已经隔离了。

在这里插入图片描述

​ 另一个要说明的是,我们右边的窗口是最外面的父类的shell进程,可以看到其能看到自己的java应用23952,也能看到其子的java应用26527,同时这里也表面,子的隔离的PID39是被映射在父类是26527

[root@k8s-master feverasa]# ps -ef | grep java
root      23952  14436 99 23:34 pts/0    00:01:32 java TestCPU
root      26527  25519 99 23:35 pts/0    00:00:20 java TestCPU
root      26989  12613  0 23:35 pts/1    00:00:00 grep --color=auto java
[root@k8s-master feverasa]# 
[root@k8s-master feverasa]# ls -l /proc/23952/ns
总用量 0
lrwxrwxrwx 1 root root 0 6月  15 23:34 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 6月  15 23:34 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 6月  15 23:34 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 6月  15 23:34 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 6月  15 23:34 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 6月  15 23:34 uts -> uts:[4026531838]
[root@k8s-master feverasa]# ls -l /proc/26527/ns
总用量 0
lrwxrwxrwx 1 root root 0 6月  15 23:37 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 6月  15 23:37 mnt -> mnt:[4026532634]
lrwxrwxrwx 1 root root 0 6月  15 23:37 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 6月  15 23:37 pid -> pid:[4026532635]
lrwxrwxrwx 1 root root 0 6月  15 23:37 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 6月  15 23:37 uts -> uts:[4026531838]
[root@k8s-master feverasa]# 

​ Docker容器就是通过Linux 的Namespace来实现不同容器实例的资源隔离的。

四、cgroups

1、基本介绍

cgroups(control groups),是Linux内核的一个功能,用来限制、控制与分离一个进程组的资源,如CPU、内存、磁盘、输入输出等),在2.6.24版本合并到内核中去的。自那以后,又添加了很多功能。

Cgroups提供了以下功能:

​ 1.限制进程组可以使用的资源数量(Resource limiting )。比如:memory子系统可以为进程组设定一个memory使用上限,一旦进程组使用的内存达到限额再申请内存,就会触发OOM(out of memory)。

​ 2.进程组的优先级控制(Prioritization )。比如:可以使用cpu子系统为某个进程组分配特定cpu share。

​ 3.记录进程组使用的资源数量(Accounting )。比如:可以使用cpuacct子系统记录某个进程组使用的cpu时间

​ 4.进程组隔离(Isolation)。比如:使用ns子系统可以使不同的进程组使用不同的namespace,以达到隔离的目的,不同的进程组有各自的进程、网络、文件系统挂载空间。

​ 5.进程组控制(Control)。比如:使用freezer子系统可以将进程组挂起和恢复。

简单来说,其可以精细的控制一个进程对CPU、内存这些资源的使用,例如我们在运行docker实例的时候,控制控制其使用的内存、cpu这些docker run -itd --name test6 --cpu-quota 50000 centos:7 /bin/bash。下面我们就用一个控制CPU使用的案例来说明这点。

2、使用案例

首先我们创建自己的Cgroups

mkdir /sys/fs/cgroup/cpu/my_cgroups_cpu

在这里插入图片描述

​ 我们可以看到/sys/fs/cgroup这个目录下,,就是各种cgroup控制类型的目录,我们创建了一个cpu控制my_cgroups_cpu,其就有对应的目录,这个目录下面就是各种控制类型。

在这里插入图片描述

cpu.cfs_quota_us:表示一个调度周期内可以使用的CPU时间,单位为微秒。要限制CPU使用率为50%,可以将cpu.cfs_quota_us设置为50000(即50毫秒)。因为cpu.cfs_period_us默认为100000,所以50000/100000 = 0.5,即50%的CPU使用率。

​ 我们当前要控制的是cpu.cfs_quota_us,其作用是限制CPU的使用率,我们当前限制其的使用率为50%。

​ 我们先设置当前my_cgroups_cpucpu.cfs_quota_us50000

cgset -r cpu.cfs_quota_us=50000 my_cgroups_cpu

在这里插入图片描述

​ 首先我们运行一个不控制的应用,可以看到其对CPU100%

在这里插入图片描述

​ 下面我们加上控制来运行,将当前执行的java应用添加到cpu的cgroups控制中

cgexec -g cpu:my_cgroups_cpu java TestCPU

在这里插入图片描述

​ 可以看到我们后面启动的java应用对cpu使用率为50%

​ 我们当前通过上面的这几个基础的案例,说明了linux的联合文件系统、Namespace、Cgroups控制组,docker就是对linux这些功能进行封装,来形成docker镜像、容器实例这些。