一、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挂载形成联合文件系统后,其将只读的lowner
、upper
目录下面的所有文件都整合到merged
目录下面了。
我们进入到merged
目录下面
我们在merged下面将lower1.txt
、upper.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 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 namespace]#
[root@k8s-master namespace]# ls -l /proc/40682/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:41 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 namespace]#
在这个案例中我们可以看到通过echo $$
输出当前的shell环境线程号PID为40682
,由于其本身是从最顶级的父线程1
fork的,可以看到我们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_cpu
的cpu.cfs_quota_us
为50000
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镜像、容器实例这些。