Namespace

什么是 Namespace ?

这里的 "namespace" 指的是 Linux namespace 技术,它是 Linux 内核实现的一种隔离方案。简而言之,Linux 操作系统能够为不同的进程分配不同的 namespace,每个 namespace 都具有独立的资源分配,从而实现了进程间的隔离。如果你的 Linux 安装了 GCC,可以通过运行 man namespaces 命令来查看相关文档,或者你也可以访问在线手册获取更多信息。

介绍

下图为各种 namespace 的参数,支持的起始内核版本,以及隔离内容。

Namespace 系统调用参数 内核版本 隔离内容
UTS (Unix Time-sharing System) CLONE_NEWUTS Linux 2.4.19 主机名与域名
IPC (Inter-Process Communication) CLONE_NEWIPC Linux 2.6.19 信号量、消息队列和共享内存
PID (Process ID) CLONE_NEWPID Linux 2.6.19 进程编号
Network CLONE_NEWNET Linux 2.6.24 网络设备、网络栈、端口等等
Mount CLONE_NEWNS Linux 2.6.29 挂载点(文件系统)
User CLONE_NEWUSER Linux 3.8 用户和用户组
  1. PID Namespace:

    • 不同用户的进程通过 PID Namespace 进行隔离,并且不同的 Namespace 中可以有相同的进程 ID。在 Docker 中,所有的 LXC(Linux 容器)进程的父进程是 Docker 进程,每个 LXC 进程具有不同的 Namespace。由于支持嵌套 Namespace,因此可以方便地实现 Docker 中的 Docker(Docker in Docker)。
  2. Net Namespace:

    • 有了 PID Namespace,每个 Namespace 中的进程能够相互隔离,但是网络端口仍然共享主机的端口。通过 Net Namespace 实现网络隔离,每个 Net Namespace 具有独立的网络设备、IP 地址、IP 路由表和 /proc/net 目录。这样,每个容器的网络就能够得到隔离。Docker 默认使用 veth(虚拟以太网)方式将容器中的虚拟网卡与主机上的 Docker 桥接器(docker0)连接起来。
  3. IPC Namespace:

    • 容器中的进程仍然使用常见的 Linux 进程间通信(IPC)方法,包括信号量、消息队列和共享内存。然而,与虚拟机不同的是,容器中的进程实际上是在具有相同 PID Namespace 的主机进程之间进行通信,因此在申请 IPC 资源时需要加入 Namespace 信息,每个 IPC 资源都有一个唯一的 32 位 ID。
  4. MNT Namespace:

    • 类似于 chroot,将进程限制在特定的目录下执行。MNT Namespace 允许不同 Namespace 的进程看到不同的文件结构,从而隔离了每个 Namespace 中进程所看到的文件目录。与 chroot 不同的是,每个 Namespace 中的容器在 /proc/mounts 中的信息仅包含所在 Namespace 的挂载点。
  5. UTS Namespace:

    • UTS("UNIX Time-sharing System")Namespace 允许每个容器拥有独立的主机名和域名,使其在网络上可以被视为一个独立的节点,而不仅仅是主机上的一个进程。
  6. User Namespace:

    • 每个容器可以具有不同的用户和组 ID,这意味着容器内部的程序可以使用容器内部的用户执行,而不是主机上的用户。

涉及到三个系统调用(system call)的 API:

  1. clone():用于创建新进程。与 fork() 创建新进程不同的是,clone() 创建进程时可以传递 CLONE_NEW* 类型的命名空间隔离参数,以控制子进程共享的内容。要了解更多信息,请查阅clone 手册
  2. setns():用于将某个进程与指定的命名空间分离。通过 setns(),进程可以脱离一个特定的命名空间,使其不再与该命名空间中的其他进程共享资源。
  3. unshare():用于将某个进程加入到指定的命名空间中。通过 unshare(),进程可以加入到一个特定的命名空间,与该命名空间中的其他进程共享资源。

namespace 的操作

  • 查看当前系统的 namespace
lsns –t <type>
  • 查看某进程的 namespace
ls -la /proc/<pid>/ns/
  • 进入某 namespace 运行命令
nsenter -t <pid> -n ip addr

Test:

# Linux命令行中,可以使用`unshare`命令结合`clone()`创建一个新的进程,并在其中使用命名空间隔离参数。
# 创建一个新的进程,并在其中使用命名空间隔离参数
unshare --pid --net -- sleep 600 ps -ef|grep sleep
root 37915 34572 0 08:53 pts/1 00:00:00 sudo unshare --pid --net -- sleep 600
root 37916 37915 0 08:53 pts/3 00:00:00 sudo unshare --pid --net -- sleep 600
root 37917 37916 0 08:53 pts/3 00:00:00 sleep 600
zhy 37919 37896 0 08:53 pts/2 00:00:00 grep --color=auto sleep sudo lsns -t net
[sudo] password for zhy:
NS TYPE NPROCS PID USER NETNSID NSFS COMMAND
4026531840 net 277 1 root unassigned /sbin/init
4026532656 net 1 37347 root 0 /run/docker/netns/c986b82be683 bash
4026532718 net 1 37917 root unassigned sleep 600 sudo nsenter -t 37917 -n ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  1. docker 启动一个 ubuntu
docker run --rm -it docker.m.daocloud.io/ubuntu:22.10 bash
  1. 用另一个窗口 找到这个进程
ps -ef|grep ubuntu
# zhy 37247 34017 0 08:20 pts/0 00:00:00 docker run --rm -it docker.m.daocloud.io/ubuntu:22.10 bash
  1. 查看这个进程的 namespace
ls -la /proc/37247/ns/
total 0
dr-x--x--x 2 zhy zhy 0 May 27 08:24 .
dr-xr-xr-x 9 zhy zhy 0 May 27 08:23 ..
lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 mnt -> 'mnt:[4026531841]'
lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 net -> 'net:[4026531840]'
lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 time -> 'time:[4026531834]'
lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 user -> 'user:[4026531837]'
lrwxrwxrwx 1 zhy zhy 0 May 27 08:24 uts -> 'uts:[4026531838]'
  1. 查看namespace
sudo lsns -t pid
NS TYPE NPROCS PID USER COMMAND
4026531836 pid 275 1 root /sbin/init
4026532654 pid 1 37347 root bash sudo lsns -t net
NS TYPE NPROCS PID USER NETNSID NSFS COMMAND
4026531840 net 275 1 root unassigned /sbin/init
4026532656 net 1 37347 root 0 /run/docker/netns/c986b82be683 bash

为什么查出来执行 bash 的 pid 和 ps -ef 的不一样?

一个是docker run的进程 PID

一个是 容器内部 'bash' 进程的 PID 这个进程是由docker run的进程通过进程复制(process cloning)创建的子进程。

  1. 在 ubuntu 中执行 ip addr 在主机执行 nsenter -t <pid> -n ip addr
# 容器内
ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
11: eth0@if12: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 08:23 pts/0 00:00:00 bash
root 360 1 0 09:12 pts/0 00:00:00 ps -ef # 主机
sudo nsenter -t 37347 -n -- ip addr # -n 进入网络namespace执行
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
11: eth0@if12: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever sudo nsenter -t 37347 -a -- ps -ef # -a 进入所有namespace执行
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 08:23 pts/0 00:00:00 bash
root 359 0 0 09:12 ? 00:00:00 ps -ef

Cgroup

什么是 Cgroup

Linux cgroups 的全称是 Linux Control Groups,它是 Linux 内核的特性,主要作用是限制、记录和隔离进程组(process groups)使用的物理资源(cpu、memory、IO 等)

为什么要使用Cgroup?

可以做到对 cpu,内存等资源实现精细化的控制,容器技术就使用了 cgroups 提供的资源限制能力来完成cpu,内存等部分的资源控制。

核心概念

  • task:任务,对应于系统中运行的一个实体,一般是指进程
  • subsystem:子系统,具体的资源控制器(resource class 或者 resource controller),控制某个特定的资源使用。比如 CPU 子系统可以控制 CPU 时间,memory 子系统可以控制内存使用量
  • cgroup:控制组,一组任务和子系统的关联关系,表示对这些任务进行怎样的资源管理策略
  • hierarchy:层级有一系列 cgroup 以一个树状结构排列而成,每个层级通过绑定对应的子系统进行资源控制。层级中的 cgroup 节点可以包含零个或多个子节点,子节点继承父节点挂载的子系统。一个操作系统中可以有多个层级。

subsystem

subsystem 是一组资源控制的模块,一般包含有:

  • blkio 设置对块设备 (比如硬盘) 的输入输出的访问控制 (block/io)
  • cpu 设置 cgroup 中的进程的 CPU 被调度的策略
  • cpuacct 可以统计 cgroup 中的进程的 CPU 占用 (cpu account)
  • cpuset 在多核机器上设置 cgroup 中的进程可以使用的 CPU 和内存 (此处内存仅使用于 NUMA 架构)
  • devices 控制 cgroup 中进程对设备的访问
  • freezer 用于挂起 (suspends) 和恢复 (resumes) cgroup 中的进程
  • memory 用于控制 cgroup 中进程的内存占用
  • net_cls 用于将 cgroup 中进程产生的网络包分类 (classify),以便 Linux 的 tc (traffic controller) (net_classify) 可以根据分类 (classid) 区分出来自某个 cgroup 的包并做限流或监控。
  • net_prio 设置 cgroup 中进程产生的网络流量的优先级
  • ns 这个 subsystem 比较特殊,它的作用是 cgroup 中进程在新的 namespace fork 新进程 (NEWNS) 时,创建出一个新的 cgroup,这个 cgroup 包含新的 namespace 中进程。

v2

Cgroup v2手册

是否加载了Cgroup v2内核模块

cat /sys/fs/cgroup/cgroup.controllers
cpuset cpu io memory hugetlb pids rdma misc

test

Cpu

执行一段go代码

package main

func main() {
go func() { for{} }()
for {}
} /*
执行 go run test.go
top
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
39268 zhy 20 0 709572 868 584 R 200.0 0.0 2:12.27 test 可以看到使用了2个cpu 因为开个两个goroutine for阻塞
*/

限制cpu

sudo mkdir /sys/fs/cgroup/test
sudo echo "100000 100000" | sudo tee /sys/fs/cgroup/test/cpu.max >/dev/null
sudo echo "39268" | sudo tee /sys/fs/cgroup/test/cgroup.procs >/dev/null # top
# PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
# 39268 zhy 20 0 709572 868 584 R 100.3 0.0 7:45.04 test
# 马上就只占用一个cpu了
Memory
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h> #define BLOCK_SIZE (100 * 1024 * 1024)
#define NUM_ALLOCATIONS 10
#define SLEEP_SECONDS 30 char* allocMemory(int size) {
char* out = (char*)malloc(size);
memset(out, 'A', size);
return out;
} int main() {
int i; for (i = 1; i <= NUM_ALLOCATIONS; i++) {
char* block = allocMemory(i * BLOCK_SIZE);
printf("Allocated memory block of size %dMB at address: %p\n", i * 100, block);
sleep(SLEEP_SECONDS);
} return 0;
} /*
ps -p 3243 -o rss=,unit=M,cmd=
M
308512 session-4.scope ./test2
*/

限制内存

sudo echo "300000000" |sudo tee /sys/fs/cgroup/test/memory.max >/dev/null
sudo echo "64417" | sudo tee /sys/fs/cgroup/test/cgroup.procs >/dev/null #cat memory.current
#299839488

UnionFS

联合文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下 (unite several directories into a single virtual filesystem)。

联合文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。

另外,不同 Docker 容器就可以共享一些基础的文件系统层,同时再加上自己独有的改动层,大大提高了存储的效率。

最新版 Docker 使用的是 overlay2。

overlay2

现在主流基本都是 overlayFS

OverlayFS 属于文件级的存储驱动,包含了最初的 Overlay 和更新更稳定的 overlay2。

Overlay 只有两层:upper 层和 lower 层,Lower 层代表镜像层,upper 层代表容器可写层。

test

mkdir test && cd test
mkdir upper lower merged work
echo "file1 from lower" > lower/file1.txt
echo "file2 from lowerr" > lower/file2.txt
echo "file3 from lower" > lower/file3.txt
echo "file2 from upper" > upper/file2.txt
echo "file4 from upper" > upper/file4.txt current_dir=$(pwd)
sudo mount -t overlay -o lowerdir="$current_dir/lower",upperdir="$current_dir/upper",workdir="$current_dir/work" overlay "$current_dir/merged" cat merged/file1.txt
file1 from lower
cat merged/file2.txt
file2 from upper
cat merged/file3.txt
file3 from lower
cat merged/file4.txt
file4 from upper

docker iamge

每一条指令是一层, 下层可以共用

Docker 的文件系统

典型的Linux文件系统组成如下:

  • Bootfs(引导文件系统)

    • Bootloader(引导加载程序):负责加载内核。
    • Kernel(内核):一旦内核加载到内存中,就会卸载bootfs。
  • Rootfs(根文件系统)
    • /dev、/proc、/bin、/etc等标准目录和文件。
    • 对于不同的Linux发行版,bootfs基本上是一致的,但rootfs会有所差异。

Docker 启动

Linux

  • 在启动后,首先将 rootfs 设置为 readonly, 进行一系列检查,然后将其切换为 “readwrite” 供用户使用。

Docker 启动

  • 初始化时也是将 rootfs 以 readonly 方式加载并检查,然而接下来利用 union mount 的方式将一个 readwrite 文件系统挂载在 readonly 的 rootfs 之上;
  • 并且允许再次将下层的 FS(file system) 设定为 readonly 并且向上叠加。 这样一组 readonly 和一个 writeable 的结构构成一个 container 的运行时态,每一个 FS 被称作一个 FS 层。

写操作

由于镜像具有共享特性,所以对容器可写层的操作需要依赖存储驱动提供的写时复制和用时分配机制,以此来 支持对容器可写层的修改,进而提高对存储和内存资源的利用率。

  • 写时复制 即 Copy-on-Write。

    • 一个镜像可以被多个容器使用,但是不需要在内存和磁盘上做多个拷贝。
    • 在需要对镜像提供的文件进行修改时,该文件会从镜像的文件系统被复制到容器的可写层的文件系统 进行修改,而镜像里面的文件不会改变。
    • 不同容器对文件的修改都相互独立、互不影响。
  • 用时分配
  • 按需分配空间,而非提前分配,即当一个文件被创建出来后,才会分配空间。

容器基础-- namespace,Cgoup 和 UnionFS的更多相关文章

  1. C++ 顺序容器基础知识总结

    0.前言 本文简单地总结了STL的顺序容器的知识点.文中并不涉及具体的实现技巧,对于细节的东西也没有提及.一来不同的标准库有着不同的实现,二来关于具体实现<STL源码剖析>已经展示得全面细 ...

  2. docker容器基础

    一.docker容器基础6种名称空间:UTS.MOunt.IPC.PID.User.Net (1) Linux Namespaces:namespace 系统调用参数 隔离内容 内核版本  UTS   ...

  3. 08 | 白话容器基础(四):重新认识Docker容器

    你好,我是张磊.今天我和你分享的主题是:白话容器基础之重新认识Docker容器. 在前面的三次分享中,我分别从Linux Namespace的隔离能力.Linux Cgroups的限制能力,以及基于r ...

  4. 容器基础(二): 使用Namespace进行边界隔离

    Linux Namespace 容器技术可以认为是一种沙盒(sandbox), 为了实现沙盒/容器/应用间的隔离,就需要一种技术来对容器界定边界,从而让容器不至于互相干扰.当前使用的技术就是Names ...

  5. 容器基础(五): 实现一个简单容器sdocker

    在前面几部分的基础上, 我们更新一下代码,实现一个简单容器 sdocker. sdocker目录构成 linux: # tree . ├── Makefile ├── cpu-test.c # 由cp ...

  6. 容器基础(一): Docker介绍

    IaaS IaaS阶段, 用户租借基础设施,但是还是需要像以前管理服务器那样,用脚本或者手工方式在这些机器上部署应用.这个过程中当然难免会碰到云端机器和本地机器环境不一致的问题.想想每一次同步不同机器 ...

  7. Kubernetes 普及系列:容器基础入门

    随着云原生时代的来临,云以及分布式计算已经是时下最受欢迎的技术之一了.其中 Docker 作为最知名的容器平台,到底有着怎样的魅力来让其无人不知无人不晓?废话不多说,让我们开始逐层掀开容器技术的神秘面 ...

  8. 深入剖析Kubernetes学习笔记:容器基础(05-06)

    05 :从进程说起 1.容器本身没有价值,有价值的是"容器编排" 2.什么是进程? 一旦"程序"被执行起来,它就从磁盘上的二进制文件,变成 1.计算机内存中的数 ...

  9. 工作随笔—Java容器基础知识分享(持有对象)

    1. 概述 通常,程序总是运行时才知道的根据某些条件去创建新对象.在此之前,不会知道所需对象的数量,甚至不知道确切的类型,为解决这个普遍的编程问题:需要在任意时刻和任意位置创建任意数量的对象,所以,就 ...

  10. Docker容器基础认知

    ## 一. [docker](http://www.itxdm.me/archives/tag/docker/)概念 [Docker](http://www.itxdm.me/wp-content/p ...

随机推荐

  1. What's the best way to read and understand someone else's code?

    Find one thing you know the code does, and trace those actions backward, starting at the end Say, fo ...

  2. [Java SE]Unicode解码

    文由 将ASCII等其他非Unicode字符与Unicode混合的"脏的.不规范的"编码文本转为正常文本. 源码 unicodetoString(String unicodeTex ...

  3. [数据库]Oracle数据迁移至HIVE(待续)

    step1 导出csv数据,并 call sql_to_csv('select * from BE_JJZKJCQKTJ', 'OUT_PATH', 'DA.BE_JJZKJCQKTJ.csv'); ...

  4. 【Contest】Nowcoder 假日团队赛1 题解+赛后总结

    比赛链接 通过顺序:\(B\rightarrow D\rightarrow I\rightarrow J\rightarrow G\rightarrow H \rightarrow A \righta ...

  5. sql求每家店铺销量前三的sku, 附python解法

    背景 有一张表: date store_id sku sales 2023-01-01 CK005 03045 50 date 代表交易日期,store_id代表门店编号,sku代表商品,sales代 ...

  6. 深入理解python虚拟机:调试器实现原理与源码分析

    深入理解python虚拟机:调试器实现原理与源码分析 调试器是一个编程语言非常重要的部分,调试器是一种用于诊断和修复代码错误(或称为 bug)的工具,它允许开发者在程序执行时逐步查看和分析代码的状态和 ...

  7. 笔记:C++学习之旅---面向对象程序的设计1

    笔记:C++学习之旅---面向对象程序的设计1 面向对象的主要特征 1.抽象 2.封装 3.继承 4.多态 抽象:将程序的每一部分都看作一个抽象的对象,即程序有一组抽象的对象组成的更复杂点,这些对象根 ...

  8. ChatGPT不算新技术革命,带不来什么新机会(转载)

    吴军,1967年出生,毕业于清华大学和约翰霍普金斯大学,计算机专业博士,前Google高级资深研究员.原腾讯副总裁.硅谷风险投资人. 近日,计算机科学家.自然语言模型专家吴军,就人工智能和ChatGP ...

  9. OData WebAPI实践-Non-EDM模式

    前文说到了 EDM 与 OData 之间的关系,具有 EDM 的 OData 提供了强大的查询能力,但是 OData 并不必须要配置 EDM,我们也可以使用 Non-EDM 方案. Non-EDM 所 ...

  10. React笔记-生命周期(七)

    React笔记-生命周期(七) 生命周期值React组件从装载到卸载的全过程 在这个过程中React提供了多个内置函数供开发者在不同阶段执行需要的逻辑 状态组件由3个阶段组成 挂载阶段(MOUNTIN ...