• 容器技术基本概念


    容器的基本概念

    • 容器的作用对象是一个或一组特殊的进程,其通过Linux Namespace实现对资源的隔离 、Cgroups实现资源的限制、rootfs实现对根文件的切换,其对待创建的用户进程执行以下操作:1. 启
      动Linux Namespace配置;2. 设置指定的Cgroups参数;3. 切换进程的根目录(rootfs)。

    程序和进程

    程序

    • 放在磁盘上的二进制文件和程序运行所需需要的数据,就是通常说的程序,也称作为代码的可执行镜像,它是进程的静态表现。

    进程

    • 程序被执行起来,从磁盘上的二进制文件变成了计算机内存中的数据、寄存器里的值、堆栈中的指令、被打开的文件,以及各种设备的状态信息的一个集合。像这样一个程序运行起来后的计算机执行
      环境的总和,称之为进程,它是动态的。

    linux namespace

    • 命名空间将全局系统资源包装在一个抽象中,使命名空间内的进程看起来它们拥有自己独立的全局资源实例。命名空间内对全局资源的改变对同命名空间内其他进程可见,对非本命名空间的成员不可见。

    描述

    参考链接: https://man7.org/linux/man-pages/man7/namespaces.7.html
    A namespace wraps a global system resource in an abstraction that makes it appear to
    the processes within the namespace that they have their own isolated instance of the
    global resource. Changes to the global resource are visible to other processes that
    are members of the namespace, but are invisible to other processes. One use of
    namespaces is to implement containers.This page provides pointers to information on
    the various namespace types, describes the associated /proc files, and summarizes the
    APIs for working with namespaces.
    翻译:
    命名空间将全局系统资源封装在抽象中,使命名空间内的进程看起来拥有自己的全局资源单独实例。 对全局资源的更改对属于命名空间成员的其他进程可见,但对其他进程不可见。 命名空间的一种用途是实现容器。该页面提供了指向各种命名空间类型信息的指针,描述了相关的 /proc 文件,并总结了使用命名空间的 API。每个进程都有一个独立的/proc/[pid]/ns/子目录,其中包含了每个每个被支持操作的命名空间的条目,每个条目都是一个符号链接,其指向为对应namespace的文件的iNode ID,可以通过readlink命令查看这两个进程是否属于同一个命名空间,如果inode ID相同,则他们所属的相同的命名空间。
    

    类型

    namespace API操作类型别名 描述
    Cgroup CLONE_NEWCGROUP Cgroup root directory (since Linux 4.6)
    IPC CLONE_NEWIPC System V IPC, POSIX message queues (since Linux 2.6.19)
    Network CLONE_NEWNET Network devices, stacks, ports, etc. (since Linux 2.6.24)
    Mount CLONE_NEWNS Mount points (since Linux 2.4.19)
    PID CLONE_NEWPID Process IDs (since Linux 2.6.24)
    Time CLONE_NEWTIME Boot and monotonic clocks(since Linux 5.7)
    User CLONE_NEWUSER User and group IDs (started in Linux 2.6.23 and completed in Linux 3.8)
    UTS CLONE_NEWUTS Hostname and NIS domain name (since Linux 2.6.19)

    查看进程的Namespace

    • 每个进程都有一个独立的/proc/[pid]/ns/子目录,其中包含了每个每个被支持操作的命名空间的条目,每个条目都是一个符号链接,其指向为对应namespace的文件的iNode ID,可以通过readlink命令查看这两个进程是否属于同一个命名空间,如果inode ID相同,则他们所属的相同的命名空间。
    1 [root@master01 ~]# ls -l /proc/$$/ns | awk '{print $1, $9, $10, $11}'
    2 total 
    3 lrwxrwxrwx ipc -> ipc:[4026531839] 4 lrwxrwxrwx mnt -> mnt:[4026531840] 5 lrwxrwxrwx net -> net:[4026531956] 6 lrwxrwxrwx pid -> pid:[4026531836] 7 lrwxrwxrwx user -> user:[4026531837] 8 lrwxrwxrwx uts -> uts:[4026531838]
    
    1 [root@master01 ~]# pidof bash
    2 12224 2037
    3 [root@master01 ~]# readlink /proc/12224/ns/uts
    4 uts:[4026531838] 5 [root@master01 ~]# readlink /proc/2037/ns/uts 
    6 uts:[4026531838]
    

    shell unshare命令理解命名空间7大隔离实现

    PID Namespace
    • PID Namespace 的作用用来隔离进程,利用PID Namespace可以实现每个容器的主进程为1号进程,而容器内的进程在主机上却拥有不同的PID。
    1
    2 # 获取当前bash环境下,bash的ns信息
    3 [root@master01 ~]# ls -l /proc/$$/ns | awk '{print $1, $9, $10, $11}'
    4 total 
    5 lrwxrwxrwx ipc -> ipc:[4026531839] 6 lrwxrwxrwx mnt -> mnt:[4026531840] 7 lrwxrwxrwx net -> net:[4026531956] 8 lrwxrwxrwx pid -> pid:[4026531836] 9 lrwxrwxrwx user -> user:[4026531837]
    10 lrwxrwxrwx uts -> uts:[4026531838]
    11 # 使用unshare命令为执行bash命令时,为其赋予一个新的PID Namespace.
    12 [root@master01 ~]# unshare --fork --pid --mount-proc /bin/bash
    13 # 通过ps -ef,可以看到PID的1号进程和Host OS的1号进程不一致。
    14 [root@master01 ~]# ps -ef
    15 UID PID PPID C STIME TTY TIME CMD
    16 root 1 0 0 11:17 pts/0 00:00:00 /bin/bash
    17 root 28 1 0 11:17 pts/0 00:00:00 ps -ef
    18 # 通过如下命令,可以看到在新的PID Namespace中我们只能看到自身命名空间的进程,并且当前的bash的PID,MNT和上述
    19 # PID, MNT不一致。
    20 [root@master01 ~]# ls -l /proc/$$/ns | awk '{print $1, $9, $10, $11}'
    21 total 
    22 lrwxrwxrwx ipc -> ipc:[4026531839]
    23 lrwxrwxrwx mnt -> mnt:[4026532410]
    24 lrwxrwxrwx net -> net:[4026531956]
    25 lrwxrwxrwx pid -> pid:[4026532411]
    26 lrwxrwxrwx user -> user:[4026531837]
    27 lrwxrwxrwx uts -> uts:[4026531838]
    28 [root@master01 ~]# exit
    29 exit
    30 [root@master01 ~]#
    
    Mount Namespace
    • Mount Namespace 用来隔离不同的进程或者进程组看到的挂载点。在容器内的挂载操作不会影响主机的挂载目录。
    1 # 使用unshare命令为执行bash命令时,为其赋予一个新的Mount Namespace.
    2 [root@master01 ~]# unshare --mount --fork /bin/bash
    3 [root@master01 ~]# mkdir /tmp/mnt
    4 [root@master01 ~]# mount -t tmpfs -o size=1m tmpfs /tmp/mnt
    5 [root@master01 ~]# df -h|grep mnt
    6 tmpfs 1.0M 0 1.0M 0% /tmp/mnt
    7 [root@master01 ~]# 8 [root@master01 ~]# 9 # 退出该namespace下bash环境
    10 [root@master01 ~]# exit
    11 # 另外起一个终端,查看Host OS内的挂载信息,
    12 [root@master01 ~]# df -h|grep mnt
    
    IPC Namespace
    • IPC Namespace主要是用来隔离进程间通讯的。使用PID Namespace和IPC Namespace一起使用可以实现同一IPC Namespace内的进程彼此间可以通信,不同IPC Namespace的进程却不能通信
    1 # 使用unshare命令为执行bash命令时,为其赋予一个新的IPC Namespace.
    2 [root@master01 ~]# unshare --fork --ipc /bin/bash
    3 [root@master01 ~]# 4 [root@master01 ~]# 5 # 使用ipcs -q 命令查看当前IPC Namespace下的系统通信队列列表,可以看到当前为空
    6 [root@master01 ~]# ipcs -q 78 ------ Message Queues --------
    9 key msqid owner perms used-bytes messages 
    10 # 使用 ipcmk -Q 命令创建一个系统通信队列
    11 [root@master01 ~]# ipcmk -Q
    12 Message queue id: 0
    13 # 再次执行ipcs -q,可以看到当前通信队列列表已经有数据了。
    14 [root@master01 ~]# ipcs -q
    15
    16 ------ Message Queues --------
    17 key msqid owner perms used-bytes messages 
    18 0xe0397442 0 root 644 0 0 
    19
    20 # 我们新打开一个窗口(实际生成一个新的bash进程),使用ipcs -q 发现是无法看到
    21 # 另一个IPC Namespace下的队列信息的,从而实现了系统通信队列的隔离。
    22 Last login: Tue Apr 12 16:26:01 2022 from 172.16.5.10
    23 [root@master01 ~]# ipcs -q
    24
    25 ------ Message Queues --------
    26 key msqid owner perms used-bytes messages 
    
    UTS Namespace
    • UTS Namespace主要用来隔离主机名,他允许每个UTS Namespace拥有一个独立的主机名称
    1 # 使用unshare命令为执行bash命令时,为其赋予一个新的UTS Namespace.
    2 [root@master01 ~]# unshare --fork --uts /bin/bash
    3 # 在当前uts namespace下修改主机名为docker
    4 [root@master01 ~]# hostname -b docker
    5 # 查看主机名,可以看到已经被修改为docker
    6 [root@master01 ~]# hostname
    7 docker
    8 [root@master01 ~]# exit
    9 exit
    10 # 新打开一个终端,查看主机名称,可以发现主机主机名并未发生改变。
    11 [root@master01 ~]# hostname
    12 master01.ws.infra
    
    User Namespace
    • User Namespace 主要是用来隔离用户和用户组的。一个比较典型的应用场景就是在主机上以非 root 用户运行的进程可以在一个单独的 User Namespace 中映射成 root 用户。使用 User Namespace 可以实现进程在容器内拥有 root 权限,而在主机上却只是普通用户。
    • User Namespace的创建是可以不使用root权限的,普通用户也可以使用。
    1 # centos7 max_user_namespaces默认为0,unshare 命令会返回的错误为 unshare: unshare failed:
    Invalid argument, 需要修改该值
    2 [root@master01 ~]# echo 65535 > /proc/sys/user/max_user_namespaces
    3 # 创建ops用户
    4 [root@master01 ~]# useradd ops
    5 # 切换到ops用户
    6 [root@master01 ~]# su - ops
    7 # 使用unshare命令为执行bash命令时,为其赋予一个新的User Namespace.,并在这个namespace中将用户
    映射为root用户
    8 [ops@master01 ~]$ unshare --user -r /bin/bash 
    9 # 执行id命令,可以发现我们用户已经变成了root,uid, gid都变成0了。但是在执行reboot命名的时候,我们发现在这个user namespace中 root用户并没有获取到主机的root权限,通过User Namespace实现了用户和用户组的隔离。
    10 [root@master01 ~]# 
    11 [root@master01 ~]# id
    12 uid=0(root) gid=0(root) groups=0(root)
    13 [root@master01 ~]# reboot
    14 Failed to open /dev/initctl: Permission denied
    15 Failed to talk to init daemon.
    
    Net Namespace
    • Net Namespace 是用来隔离网络设备、IP 地址和端口等信息的。Net Namespace 可以让每个/组进程拥有自己独立的 IP 地址,端口和网卡信息。例如主机 IP 地址为 172.16.4.1 ,容器内可以设置独立的 IP 地址为 192.168.1.1。
    1 # 这里我们可以看到我们主机的网络配置相关的信息
    2 [root@master01 ~]# ip a
    3 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen
    1000
    4 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    5 inet 127.0.0.1/8 scope host lo
    6 valid_lft forever preferred_lft forever
    7 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default
    qlen 1000
    8 link/ether 00:50:56:aa:c1:94 brd ff:ff:ff:ff:ff:ff
    9 inet 172.16.50.150/24 brd 172.16.50.255 scope global noprefixroute eth0
    10 valid_lft forever preferred_lft forever
    11 3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group
    default
    12 link/ether 02:42:bb:30:ba:ed brd ff:ff:ff:ff:ff:ff
    13 inet 192.168.1.1/24 brd 192.168.1.255 scope global docker0
    14 valid_lft forever preferred_lft forever
    15 # 使用unshare命令为bash进程生成一个Net Namespace,使用ip a 命名可以看到该Net Namespace下网络设备和主机的网络设备不一样的。
    16 [root@master01 ~]# unshare --net --fork /bin/bash
    17 [root@master01 ~]# ip a
    18 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    19 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    
    Cgroup Namespace
    • Cgroup Namespace是对进程的cgroup视图虚拟化。 每个 cgroup 命名空间都有自己的一组 cgroup 根目录。Linux 4.6开始支持。
    • cgroup 命名空间提供的虚拟化有多种用途:
      1. 防止信息泄漏。否则容器外的cgroup 目录路径对容器中的进程可见。
      2. 简化了容器迁移等任务。
      3. 允许更好地限制容器化进程。可以挂载容器的 cgroup 文件系统,这样容器无需访问主机 cgroup 目录。
    Time Namespace
    • 虚拟化两个系统时钟名称空间,用于隔离时间。 linux 5.7内核开始支持

    Linux Cgroup

    • cgroup是内核提供的一种可以限制单个或者多个进程可以使用的资源的机制,可以对CPU、内
      存、磁盘IO等资源实现精细化控制、统计等功能。

    相关概念

    1. task: 在cgroups中,任务就是系统的一个进程。
    2. control group: 控制组,指明了资源的配额限制。进程可以加入到某个控制组,也可以迁移到另一个控制组。
    3. hierarchy: 层级结构,控制组有层级结构,子节点的控制组继承父节点控制组的属性(资源配额、限制等)
    4. subsystem: 子系统,一个子系统其实就是一种资源的控制器,比如memory子系统可以控制进程内存的使用。子系统需要加入到某个层级,然后该层级的所有控制组,均受到这个子系统的控制。

    关联关系

    1. 子系统可以附加到多个层级,当且仅当这几个层级附加的子系统完全相同,否则是无法附加成功的。

    2. 一个层级可以附加多个子系统。

    3. 一个任务可以是不同的cgroup成员,但是这些cgroup必须位于不同的层级中

    4. 子进程自动成为父进程的cgroup成员,可按需将自己才能移到不同的cgroup中

    5. 两个任务组组成了一个Task Group,并使用了CPU和Memory两个子系统的cgroup,用于控制CPU和MEM的资源限制

    子系统

    • CPU:限制进程的CPU使用率
    • cpuacct:统计cgroups中的进程的cpu使用报告
    • cpuset:为cgroups中的进程分配单独的cpu节点或者内存节点
    • memory:限制进程的内存使用量
    • blkio:限制进程的块设备io
    • devices:控制进程能够访问某些设备
    • net_cls: 标记cgroups中进程的网络数据包,然后可以使用tc模块(traffic control)对数据包进行控制
    • net_prio:限制进程网络流量的优先级
    • huge_tlb:限制HugeTLB的使用
    • freezer:挂起或者恢复cgroups中的进程
    • ...

    cgroups文件系统

    • Linux通过文件的方式,将cgroups的功能和配置暴露给用户,这得益于linux的虚拟文件系统(VFS)。VFS将具体的文件系统的细节隐藏起来,给用户态提供一个统一的文件系统API接口,cgroups和VFS之间链接的部分,称为cgroups文件系统,比如挂载cpu,cpuacct两个子系统挂载到/cgroups/cpu_cpuacct目录下
    # mount -t cgroup -o cpu,cpuacct cpu_cpuacct /cgroups/cpu_cpuacct
    
    CPU子系统
    • cpu子系统限制对CPU的访问,每个参数独立存在于cgroups虚拟文件系统的伪文件系中,参数如
      下:
    1 cpu.shares # cgroup对时间的分配。比如cgroup A设置的是1,cgroup B设置的是2,那么B中的任务获取cpu的时间,是A中任务的2倍。
    2 cpu.cfs_period_us # 完全公平调度器的调整时间配额的周期。
    3 cpu.cfs_quota_us # 完全公平调度器的周期当中可以占用的时间。
    4 cpu.stat 统计值
    5 nr_periods # 进入周期的次数
    6 nr_throttled # 运行时间被调整的次数
    7 throttled_time # 用于调整的时间
    
    cpuacct子系统
    • cpuacct子系统生成cgroup任务所使用的CPU资源报告,不做资源限制功能。
    1 cpuacct.usage # 该cgroup中所有任务总共使用的CPU时间(ns纳秒)
    2 cpuacct.stat # 该cgroup中所有任务总共使用的CPU时间,区分user和system时间。
    3 cpuacct.usage_percpu # 该cgroup中所有任务使用各个CPU核数的时间。
    
    cpuset子系统
    • 适用于分配独立的CPU节点和Mem节点,比如将进程绑定在指定的CPU或者内存节点上运行,各
      参数解释如下
    1 cpuset.cpus # 可以使用的cpu节点
    2 cpuset.mems # 可以使用的mem节点
    3 cpuset.memory_migrate # 内存节点改变是否要迁移?
    4 cpuset.cpu_exclusive # 此cgroup里的任务是否独享cpu? 5 cpuset.mem_exclusive # 此cgroup里的任务是否独享mem节点?
    6 cpuset.mem_hardwall # 限制内核内存分配的节点(mems是用户态的分配)
    7 cpuset.memory_pressure # 计算换页的压力。
    8 cpuset.memory_spread_page # 将page cache分配到各个节点中,而不是当前内存节点。
    9 cpuset.memory_spread_slab # 将slab对象(inode和dentry)分散到节点中。
    10 cpuset.sched_load_balance # 打开cpu set中的cpu的负载均衡。
    11 cpuset.sched_relax_domain_level # 迁移任务的搜索范围the searching range when migrating tasks
    12 cpuset.memory_pressure_enabled # 是否需要计算 memory_pressure?
    
    memory子系统
    • memory子系统主要涉及内存一些的限制和操作,主要有以下参数:
    1 memory.usage_in_bytes # 当前内存中的使用量
    2 memory.memsw.usage_in_bytes # 当前内存和交换空间中的使用量
    3 memory.limit_in_bytes # 设置or查看内存使用量
    4 memory.memsw.limit_in_bytes # 设置or查看 内存加交换空间使用量
    5 memory.failcnt # 查看内存使用量被限制的次数
    6 memory.memsw.failcnt # - 查看内存和交换空间使用量被限制的次数
    7 memory.max_usage_in_bytes # 查看内存最大使用量
    8 memory.memsw.max_usage_in_bytes # 查看最大内存和交换空间使用量
    9 memory.soft_limit_in_bytes # 设置or查看内存的soft limit
    10 memory.stat # 统计信息
    11 memory.use_hierarchy # 设置or查看层级统计的功能
    12 memory.force_empty # 触发强制page回收
    13 memory.pressure_level # 设置内存压力通知
    14 memory.swappiness # 设置or查看vmscan swappiness 参数
    15 memory.move_charge_at_immigrate # 设置or查看 controls of moving charges?
    16 memory.oom_control # 设置or查看内存超限控制信息(OOM killer)
    17 memory.numa_stat # 每个numa节点的内存使用数量
    18 memory.kmem.limit_in_bytes # 设置or查看 内核内存限制的硬限
    19 memory.kmem.usage_in_bytes # 读取当前内核内存的分配
    20 memory.kmem.failcnt # 读取当前内核内存分配受限的次数
    21 memory.kmem.max_usage_in_bytes # 读取最大内核内存使用量
    22 memory.kmem.tcp.limit_in_bytes # 设置tcp 缓存内存的hard limit
    23 memory.kmem.tcp.usage_in_bytes # 读取tcp 缓存内存的使用量
    24 memory.kmem.tcp.failcnt # tcp 缓存内存分配的受限次数
    25 memory.kmem.tcp.max_usage_in_bytes # tcp 缓存内存的最大使用量
    
    blkio子系统
    • 主要用于控制设备IO的访问。有两种限制方式:权重和上限,权重是给不同的应用一个权重值,按百分比使用IO资源,上限是控制应用读写速率的最大值。
    1 # 按权重分配IO资源:
    2 blkio.weight #填写 100-1000 的一个整数值,作为相对权重比率,作为通用的设备分配比。
    3 blkio.weight_device #针对特定设备的权重比,写入格式为 device_types:node_numbers weight,空
    格前的参数段指定设备,weight参数与blkio.weight相同并覆盖原有的通用分配比。
    4 # 按上限限制读写速度:
    5 blkio.throttle.read_bps_device #按每秒读取块设备的数据量设定上限,格式
    device_types:node_numbers bytes_per_second。 6 blkio.throttle.write_bps_device # 按每秒写入块设备的数据量设定上限,格式
    device_types:node_numbers bytes_per_second。 7 blkio.throttle.read_iops_device #按每秒读操作次数设定上限,格式device_types:node_numbers
    operations_per_second。 8 blkio.throttle.write_iops_device #按每秒写操作次数设定上限,格式device_types:node_numbers
    operations_per_second
    9 # 针对特定操作 (read, write, sync, 或 async) 设定读写速度上限
    10 blkio.throttle.io_serviced # 针对特定操作按每秒操作次数设定上限,格式
    device_types:node_numbers operation operations_per_second
    11 blkio.throttle.io_service_bytes # 针对特定操作按每秒数据量设定上限,格式
    device_types:node_numbers operation bytes_per_second
    

    参考连接

    https://man7.org/linux/man-pages/man7/namespaces.7.html
    https://www.jianshu.com/p/8e50a1082d1d

  • 相关阅读:
    20170926-构建之法:现代软件工程-阅读笔记
    我的swift的ui标签
    内存管理:内存泄漏和空悬指针
    闭包
    泛型,修饰符和异常处理
    类型转换,接口和扩展
    初始化2
    类的继承和初始化1
    枚举与可选值
    swift中的类和结构
  • 原文地址:https://www.cnblogs.com/qingfengfumian/p/16278943.html
Copyright © 2020-2023  润新知