• Linux Container


    Docker官网对Container的定义是:Package Software into Standardized Units for Development, Shipment and Deployment
    Container这个单词的本意是集装箱,一般翻译成容器。里面装了运行某个项目所需要的代码、语言运行环境、工具和引用库等所有东西
    可以将Container理解为一个视图隔离、资源可限制、独立文件系统的进程集合
    下图简单描述了一个Container的启动过程:
    Container容器的本质是宿主机上的进程(容器进程),它的实现依赖于以下技术:
    Linux Namespace
    容器的资源隔离主要指进程资源的隔离。实现资源隔离的核心技术是Linux namespace。它和很多编程语言(如C++)的namespace的设计思想是一致的。
     
    隔离意味着可以抽象出多个轻量级的内核(容器进程),这些进程可以充分利用宿主机的资源,宿主机有的资源容器进程都可以享有,但彼此之间是隔离的。同样,不同容器进程之间使用资源也是隔离的,容器间进行相同的操作,都不会互相干扰,安全性得到保障。
     
    命名空间 系统调用参数 隔离内容 效果
    UTS CLONE_NEWUTS 主机名、NIS域名

    容器在网络上就可以被视为一个独立的节点,

    在容器中对hostname 的修改不会对宿主机造成任何影响

    IPC CLONE_NEWIPC
    vSystem V IPC、
    POSIX message queues
    容器进程间通信(信号量、消息队列和共享内存)被隔离
    PID CLONE_NEWPID 进程编号
    ps/top等命令底层读取的是/proc文件夹内的内容,
    如果/proc文件系统没有挂载到一个与原/proc不同的位置,
    仍然会显示与隔离前相同的内容
    Network   CLONE_NEWNET

    网络设备、IPv4和IPv6协议栈、

    端口、IP 路由表、防火墙、

    /proc/net目录、

    /sys/class/net目录、socket等

    跨容器通信需要通过veth pair;

    或先建立一个Bridge,

    将veth pair的两端分别绑定到 Bridge和容器中。

    Mount     CLONE_NEWNS 挂载点(文件系统)
    容器有独立的挂载点列表(文件系统视图)
    实现效果类似chroot(change root directory)
    User CLONE_NEWUSER 用户 ID、用户组 ID

    用户创建的容器进程,

    可属于与自己不同的用户、用户组(甚至是超级用户)

    Cgroup    CLONE_NEWCGROUP Cgroup根目录 容器拥有独立的Cgroup根目录
    Time CLONE_NEWTIME
    CLOCK_BOOTTIME
    CLOCK_MONOTONIC
    容器可以看到不同的系统时间
    为了支持这些特性,Linux namespace实现了8项资源隔离,基本上涵盖了一个小型操作系统的运行要素Namespace API提供了三种系统调用接口:
      ● clone():创建新的进程
      ● setns():允许指定进程加入特定的namespace
      ● unshare():将指定进程移出指定的namespace,加入一个新创的namespace
     
    使用clone系统调用创建进程时,传入上表中的参数,就可以实现资源隔离,建立一个可以自己控制隔离内容的容器。
    一个容器进程也可以再clone()出一个容器进程,这是容器的嵌套。
     
    Linux命令unshare就是使用了unshare系统调用,例如:
    [root@jfzwzxapp06 ~]# sudo unshare --mount-proc --pid --fork /bin/bash
    [root@jfzwzxapp06 ~]# ps
      PID TTY          TIME CMD
        1 pts/0    00:00:00 bash
       48 pts/0    00:00:00 ps
    可以看到这个bash进程的PID为1 ,说明它已经是在一个新的pid namespace里面。
     
    如果想要查看当前进程下有哪些namespace隔离,可以查看文件/proc/[pid]/ns。每一项namespace都附带一个编号,这是唯一标识namespace的。如果两个进程指向的namespace编号相同,则表示它们同在该namespace下。
    例如,查看Docker以默认运行时runC启动的容器进程
    可以看到,runC只实现了6种namespace。这6种namespace实际上没有完全隔离Linux的资源,比如SElinux、cgroup以及/sys、/proc等目录下的资源。
    例如,容器中执行free、top等命令,获得的信息与宿主机完全一致。docker top查看到的进程信息与ps -ef查询到的PID、执行命令等是一样的,只是筛选出来容器中的进程。
     
    Cgroup
    Cgroups的全称是Linux Control Groups,主要作用是限制、记录和隔离进程组(process groups)使用的物理资源(cpu、memory、IO等)
    cgroup内核功能没有提供任何的系统调用接口,而是通过linux vfs(虚拟文件系统)向用户层提供接口,因此可以用类似文件系统的方式进行操作,也可以通过systemd、lxc、docker这些封装了cgroups的软件定义的接口控制cgroups的内容
     
    docker容器有两种可使用的cgroup driver:
    1. cgroupfs:要限制进程的内存多少、CPU等,可以直接把pid、对应需要限制的资源也写入相应的memory cgroup文件、CPU cgroup文件等
    2. systemd:如果用systemd做cgroup驱动,所有写cgroup操作都必须通过systemd的接口来完成,不能手动更改cgroup的文件。
    kubelet使用的cgroup driver驱动必须和docker相同
    例如,在/etc/docker/daemon.json中指定docker使用的cgroup driver为systemd:
    {
      "exec-opts": ["native.cgroupdriver=systemd"]
    }
    在kubelet启动的环境变量中指定--cgroup-driver=systemd
     
    Linux内核本身提供了很多种cgroup,但是docker容器用到的只有下面六种:
    1、CPU cgroup,一般会设置cpu share和cupset,控制CPU的使用率
    2、memory ,控制了进程内存的使用量
    3、device cgroup,控制了可以在容器中看到的设备
    4、freezer cgroup。停止容器的时候,会把当前的进程全部都写入cgroup,然后把所有的进程都冻结掉。防止在停止的时候,有进程做fork逃逸到宿主机上
    5、blkio cgroup,用于限制块设备I/O速率
    docker run -it --rm --blkio-weight 100 ubuntu-stress:latest /bin/bash
    docker run -it --rm --blkio-weight-device "/dev/sda:100" ubuntu-stress:latest /bin/bash
    docker run -it --rm --device-write-bps /dev/sda:1mb ubuntu-stress:latest /bin/bash
    docker run -it --rm --device-write-iops /dev/sda:5 ubuntu-stress:latest /bin/bash
    --blkio-weight设定容器io操作优先级,在10~1000之间,linux io schedule必须设置为CFQ(Completely Fair Queueing)
    --blkio-weight-device指定某个设备的权重大小
    --device-read-bps、--device-write-bps设定每秒读写块设备的数据量设定上限,单位是kb、mb、gb
    --device-read-iops、--device-write-iops设定每秒读写操作次数设定上限
    6、pid cgroup,限制容器里面可以用到的最大进程数量
    使用--ulimit nproc=1:3参数进行限制,第一个数字是soft limit,第二个是hard limit
    docker不支持的cgroup有net_cls rdma cgroup、net_prio cgroup、hugetlb cgroup、perf_event cgroup、rdma cgroup。
    PS:除了rdma cgroup,其它的cgroup在runC里面其实都是支持的
     
    容器流程示例
    容器start的流程:
    创建容器的过程:首先创建一个matadata,然后发创建容器的请求给task service。通过中间一系列的组件,最终把请求下发到一个shim。containerd通过GRPC把创建请求发给shim之后,shim调用runtime创建一个容器出来。
     
    容器exec的流程:
    exec的操作还是发给containerd-shim的。对容器来说,start和exec其实并没有本质的区别。
    区别在于,是否对容器中跑的进程做namespace的创建:
        exec需要把这个进程加入到一个已有的namespace里面
        start时,容器进程的namespace需要去专门创建。
     
    总结:Container和虚拟机的区别
    Container的核心思想是利用内核机制,来实现类似VM的功能,从而利用更加节省的硬件资源提供给用户更多的计算资源。
    它比VM少了hypervisor层(一种运行在物理服务器和操作系统之间的中间层软件,可以允许多个操作系统和应用共享一套基础物理硬件),被设计用来运行单进程,无法很好地模拟一个完整的环境。
    使用Container时必须做出的最大思维变化之一就是:Container应该是短暂和一次性的。
     
    例如,虚拟机中,CentOS的进程发送syscall内核调用,该请求会被虚拟机内的CentOS的内核接到,然后CentOS内核访问虚拟硬件时,由虚拟机的服务软件截获,并使用宿主系统,也就是Ubuntu的内核及userland的库去执行。而且,Linux和Windows在这点上非常不同。Linux的进程是直接发syscall的,而Windows则把syscall隐藏于一层层的DLL服务之后,因此Windows的任何一个进程如果要执行,不仅仅需要Windows内核,还需要一群服务来支撑,所以如果Windows要实现类似的机制,容器内将不会像Linux这样轻量级,而是非常臃肿。
    例如,时间是从epoch到当前的秒数或者毫秒数,全球都一样,这是绝对值;而时区则是由于地理位置差异、行政区划导致各地显示时间的差异。对于容器而言,根本不存在宿主和容器的时间差异问题,因为他们使用的是同一个内核、同一个时钟,二者完全一样,所以根本不存在同步问题。
    所以,容器只是一个进程而已,只不过利用镜像提供的rootfs提供了调用所需的userland库支持,使得进程可以在受控环境下运行而已,它并没有虚拟出一个机器出来。
  • 相关阅读:
    jumpserver部署0.3版本 =====( ̄▽ ̄*)b
    zabbix监控服务
    jumpserver部署1.0版本
    yum安装nginx服务
    linux 最大文件打开数
    编译安装nginx
    ubuntu14.04安装mysql5.6.37
    Linux 报错:syntax error "C" 解决办法(此处选择bash系统)
    在配色这件事情上,总会有一些有趣且有用的小技巧
    黑历史!粉红色背后的文化现象和心理博弈
  • 原文地址:https://www.cnblogs.com/yangyuliufeng/p/14183462.html
Copyright © 2020-2023  润新知