首页 > 其他分享 >Docker分享

Docker分享

时间:2023-09-17 22:45:44浏览次数:34  
标签:容器 Namespace PID namespace 进程 Docker 分享 root

前置知识

本篇文章主要分享容器技术依赖的Namespace,在开始之前,有一些前置知识需要先阐明,也许它们很零碎,但开始之前我还是希望你能够完全理解这些概念。

前置知识这一段除了补短之外,还有一个目的,把一个很多人没解释清楚的问题解释清楚:

什么是容器,和虚拟机有什么区别?

进程树模型:fork和exec

在Linux中,创建进程只能通过fork系统调用(至少很久以前是这样的),它从当前进程拷贝一个分支出来作为子进程,子进程具有和当前进程完全一致的资源视图,比如它们能看到相同的内存,打开的文件描述符。

如果子进程想执行一个新的程序,可以通过exec系列系统调用,它会放弃这些已经拷贝的东西,加载指定的程序并开始执行。

所以,从Linux第一个init进程(pid=1)开始,系统上的所有进程构成一颗进程树:

img

现在linux创建子进程的系统调用可不止一个fork,比如还有我们后面介绍的clone,但还是遵循这样的树模型。推荐阅读:Namespace in operation, part 2: the namespaces API

演示:strace -f跟踪sh中运行ls的系统调用列表

隔离与共享

之所以要有进程,是因为操作系统希望你写程序的时候能简单点,你不用考虑你的程序会不会覆盖其它程序的内存,不用考虑如何和其它程序在CPU上分时运行,操作系统会把这些工作全包了,进程之间对于这些硬件资源的访问是隔离的。进程(至少不需要和其它进程通信的进程)几乎可以认为操作系统的硬件资源只有自己在使用。

img

操作系统提供这些抽象的同时,也引入了一些所有进程共享的资源,比如:

  1. 文件系统:所有进程看到的文件系统是一致的
  2. 进程树:所有进程能看到树中的PID,并且都拥有一个PID
  3. 主机名:所有进程看到的主机名都是一致的
  4. ipc:所有进程共用一套ipc基础组件,比如POSIX消息队列
  5. 网络接口:所有进程共用相同的网络设备,IP地址
  6. 用户&组:所有进程都看到相同的用户列表和组列表

上面都不是废话,我们终于可以解释容器是啥了!

理论上来说,如果操作系统可以使用一些魔法,像抽象硬件资源一般抽象这些系统公共资源,让一组进程看到和其它进程隔离的公共资源,那它们就会以为自己是操作系统中唯一的一组进程,但实际上它们只是缸中之脑,它们看到的只是外部的神之手为它们创造的假象。

哦,这就是容器

img

所以,容器和虚拟机有啥区别,自己去想一想吧

下图中,左下角的是虚拟机,可以看到每一个虚拟机在虚拟硬件层上运行着独立的操作系统,而操作系统的资源被隔离给上面的一组进程,就又形成了虚拟机中的容器
img

Namespace

Namespace就是限制一组进程与世隔绝,使用自己独立的操作系统资源的魔法,可以理解为是上图中套在一组进程外围的方框。

目前,Linux(针对其提供的全局资源)实现了多种不同类型的namespace,它的目的就是将特定的全局系统资源包装成一个抽象,让在这个namespace中的进程看起来拥有它们自己的全局资源实例,下面是部分关键的:

  • Mount: 隔离一组进程看到的文件系统挂载点,因此,在不同的mount名称空间的进程就会有不同的文件系统层级结构。由于有了mount名称空间,mountumount系统调用已经不只是在全局所有进程可见的挂载点集合上工作了,而是使用和调用进程相关联的名称空间。
  • UTS:隔离主机名和domainname,在新名称空间的进程可以通过sethostname()setdomainname()两个系统调用来设置。

    UTS是UNIX Time-sharing System的简写,因为返回主机名和domainname的uname()系统调用的核心结构体就叫utsname

  • IPC:隔离进程间通信(IPC)资源,即System V IPC对象和POSIX消息队列,每个IPC命名空间有自己的System V IPC标识符和POSIX消息队列文件系统。
  • PID:隔离进程ID的数字空间,在不同的PID名称空间中的进程可以有相同的PID,允许容器有自己的init进程(PID=1),它是所有进程的祖先,负责收养终止进程的子进程。
  • Network:隔离和网络相关的系统资源,每一个网络命名空间都有自己的网络设备,IP地址,IP路由表,/proc/net目录,端口号等。
  • User:隔离用户和组ID号空间,比较有趣的是进程可以在用户名称空间之外拥有一个普通的无特权用户ID,在名称空间内拥有一个0的uid(root)

上面对于六种namespace的介绍你可能看的晕乎乎的,很多概念你不理解,没关系,随着文章的推进,我们逐渐都会理解

文件系统、Mount和Umount

我们都知道文件和文件夹是保存在磁盘上的,但磁盘实际上是不知道文件和文件夹的概念的,如果没有操作系统,和它们交流的唯一方式就是通过扇区号来读写512字节的数据......你肯定不想......

操作系统中的文件系统负责提供文件和文件夹的概念,为了提供这些概念,它们需要在磁盘上建立自己的数据结构。具体使用的数据结构,不同的文件系统可能有不同,这也让它们有各自的性能特性,不过它们都向上提供一致的用户操作接口——文件和文件夹!

mount操作将一个文件系统挂到系统目录树的一个目录下,比如你可能将/dev/sda挂载到/下,这样你就有了操作系统的根目录,被挂载点目录称为挂载点,反之,umount就是卸载这个挂载点。

再大胆一点,文件系统数据的来源必须是磁盘吗?既然上层接口都一样,我们可不可以提供一种基于内存的文件系统,或许你看到的文件树只不过是你在数据结构课上写的很6的树结构?是的,linux中大名鼎鼎的procfs就是一个内存文件系统,它保存了所有和运行时进程相关的数据。

还有Docker依赖的OverlayFS,它通过镜像的层级目录以及一个上层容器目录来提供一个整合视图,使得容器能看见这些层级中的所有内容。后面的分享我可能会做和这个相关的。

Namespace API

Linux提供的和Namespace相关的API有三个:

  1. clone:创建一个新进程,可以通过CLONE_NEW*flag来将进程限制在新的独立的namespace中
  2. unshare:让当前进程退出当前namespace,进入一个新的独立的namespace中
  3. setns:让进程进入指定的namespace

setns已经足够造一个轮子来在外部观察docker容器了

虽然我才讲了三句,但我不知道你有没有同样的感觉,我们或许可以用setns来做点什么,也许是...让我们自己的进程进入docker容器的namespace去偷窥一番?

setns的签名如下:

int setns(int fd, int nstype);

fd可以是多种文件的文件描述符,nstype根据fd种类的不同又有不同解释,这里我们只说它们的一种用法,具体的可以man 2 setns

在我们上面提到的procfs中,保存了每一个进程的多种namespace信息,就在/proc/PID/ns目录下。比如下面是PID为1的init进程的namespace信息:

➜  ~ sd ls -al /proc/1/ns
total 0
dr-x--x--x 2 root root 0 Sep 17 21:03 .
dr-xr-xr-x 9 root root 0 Sep 17 21:03 ..
lrwxrwxrwx 1 root root 0 Sep 17 21:03 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Sep 17 22:07 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 root root 0 Sep 17 22:07 mnt -> 'mnt:[4026531841]'
lrwxrwxrwx 1 root root 0 Sep 17 22:07 net -> 'net:[4026531840]'
lrwxrwxrwx 1 root root 0 Sep 17 22:07 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Sep 17 22:07 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Sep 17 22:07 time -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 Sep 17 22:07 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 Sep 17 22:07 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Sep 17 22:07 uts -> 'uts:[4026531838]'

每一个文件都是一个符号链接,这个链接名中间的数字就是namespace的唯一标识,两个进程若某一个文件的标识相同,就说明它们在同一个namespace中。可以认为一个进程在procfs下的ns目录中的一个文件就定位了它所在的某个namespace。

setns的第一个参数可以是一个这种文件的文件描述符,第二个参数用于指定namespace的类型,主要用于系统进行校验,如果你清楚的知道fd代表的namespace类型,你可以直接把nstype填成0。

顺便提一嘴代表这些类型的flag常量,后面经常用到:

  • CLONE_NEWCGROUP:cgroup namespace
  • CLONE_NEWIPC:IPC namespace
  • CLONE_NEWNET:网络namespace
  • CLONE_NEWNS:mount namespace,因为它是linux支持的第一种namespace类型,所以这个命名是历史原因
  • CLONE_NEWPID:pid namespace
  • CLONE_NEWTIME:time namespace
  • CLONE_NEWUSER:用户和组的namespace
  • CLONE_NEWUTS:主机名和域名的namespace

下面的程序使用setns加入用户指定进程的某个namespace,并执行用户指定命令,如果用户没有指定,默认执行/bin/sh,这使得我们可以启动一个shell,在指定的namespace运行想运行的指令:

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include "utils.h"

char *runargv[] = {
    "/bin/sh", NULL
};

void print_usage() {
    printf("Usage. nsgo <pid> <type> [command. default to /bin/sh]\n");
}
int main(int argc, char *argv[]) {
    if (argc < 3) {
        print_usage();
        exit(1);
    }

    char *path = join(join(join("/proc", argv[1]), "ns"), argv[2]);
    printf("go to namespace %s\n", path);

    int fd = open(path, O_RDONLY);
    if (fd == -1) {
        perror("open");
        exit(1);
    }

    if (setns(fd, 0) == -1) {
        perror("setns");
        exit(1);
    }

    if (argc == 4) {
        runargv[0] = argv[3];
    }
    if (execv(runargv[0], runargv) == -1) {
        perror("execv");
        exit(1);
    }
    return 0;
}

运行一个docker容器,并查看容器进程的pid:

➜  dogefs git:(master) ✗ docker run --name redis -d redis
046cfec2dc41bed63b6015397b4303af31c0f63b098219ffc10b8c6eab794321
➜  dogefs git:(master) ✗ ps aux | grep redis
999         2207  1.2  0.1  55452 13860 ?        Ssl  22:21   0:00 redis-server *:6379

通过nsgo进入容器的mount namespace,并查看/usr/local/bin目录,我们看到了redis相关的一些程序和docker的启动脚本:

➜  dogefs git:(master) ✗ sudo nsgo 2207 mnt
go to namespaec /proc/2207/ns/mnt
# ls /usr/local/bin
docker-entrypoint.sh  redis-benchmark  redis-check-rdb	redis-sentinel
gosu		      redis-check-aof  redis-cli	redis-server

所以,docker通过mount namespace为容器提供了一个独立于外层操作系统的rootfs文件系统视图,如果我们在外层操作系统中运行同样的命令,将得到截然不同的结果:

➜  dogefs git:(master) ✗ ls /usr/local/bin
choose-mirror  cinf  Installation_guide  livecd-sound  nsgo

clone

PID Namespace

UTS Namespace

User Namespace

Mount Namespace

Network Namespace

IPC Namespace

既然道理都懂了,我们自己创造一个容器呗!

抽丝剥茧,docker不过如此

使用strace跟踪docker的容器创建过程

it's worth mentioning that although the processes in the child PID namespace will be able to see the PID directories exposed by the /proc mount point, those PIDs will not be meaningful for the processes in the child PID namespace, since system calls made by those processes interpret PIDs in the context of the PID namespace in which they reside.

each PID namespace shows only the processes that are members of that PID namespace or its descendant namespaces:

One use of PID namespaces is to implement a package of processes (a container) that behaves like a self-contained Linux system.

Specifying the CLONE_NEWPID flag in a call to unshare() creates a new PID namespace, but does not place the caller in the new namespace. Rather, any children created by the caller will be placed in the new namespace; the first such child will become the init process for the namespace.

  • MS_SHARED:
    1. 该挂载点的mount和umount事件传播到对等组中的所有成员
    2. 一个挂载点添加到该挂载点或从该挂载点移除,通知对等组中所有成员
    3. 对等组成员中的事件也会传播到该挂载点
  • MS_PRIVATE:不传播任何事件到对等组
  • MS_SLAVE:slave类型的mount有一个master,是一种master传播mount和umount事件到slave,但slave不传播事件到master到对等组

标签:容器,Namespace,PID,namespace,进程,Docker,分享,root
From: https://www.cnblogs.com/lilpig/p/17706430.html

相关文章

  • Docker中如何将容器重新打包成镜像
    最近做项目,建好了不少不同规则的容器。需要以后重复利用,于是重新打包为镜像。使用以下命令,查看容器名称:dockerps-aCONTAINERIDIMAGECOMMANDCREATEDSTATUSPORTSNAMES3e08cb5b24f8gareenbeam/kyv10:v4......
  • 使用TestContainers在Docker中进行集成测试
    现代软件应用很少独立工作。典型的应用程序会与几个外部系统进行通信,如:数据库、消息系统、缓存提供商其他第三方服务。你应该编写测试确保一切正常运行。单元测试有助于隔离地测试业务逻辑,不涉及任何外部服务。它们易于编写并提供几乎即时的反馈。有了单元测试还不够,集......
  • ⛳ Docker 安装、配置和详细使用教程-Win10专业版
    ⛳Docker安装、配置和详细使用教程-Win10专业版......
  • Docker-compose容器编排
    Docker容器编排的作用是管理和协调多个Docker容器的部署、启动、停止和扩展等操作。它可以确保应用程序在分布式环境中以可靠、高效和可伸缩的方式运行。1)安装步骤参考官网文档,选择对应的系统版本即可,官网提供两种安装方式请根据需要自行选择。https://docs.docker.com/desktop/in......
  • 网络协议学习地图分享
    最近在回顾网络知识点的时候,发现华为数通有关报文格式及网络协议地图神仙网站,这里涵盖了各个协议层及每个协议层对应的协议内容,最人性的化的一点是点击每个单独的协议可以跳转到该协议详细报文格式页面,有对应的说明和解释,对初学者或者巩固者等都有很大的帮助;当然该内容也支持华为的......
  • 分享攒了多年的mssql脚本
    分享攒了多年的mssql脚本 分享攒了多年的mssql脚本脚本类别包括:备份还原表分区常用函数错误日志定时自动抓取耗时SQL并归档发邮件脚本模块镜像批量脚本数据库收缩数据库损坏数据库账号统计数据库大小性能作业脚本数量:54个 github地址:https://github.com/xiaohuazi123/ms......
  • 关于医院电子病历评级的一些经验分享
     不可否认,2020年无疑是精彩纷呈的一年。随着疫情的消散,医疗机构终于回归了正常的运行轨迹。而本文,无疑是这一年精彩故事的一个片段。在这家公司中,我有幸成为了两次电子病历评级项目的负责人,尤其是首次执行,给我留下了深刻的印象。那场盛大的医疗检查之行,集结了众多医疗检查人员和......
  • docker常用的命令
    1、帮助类启动命令:启动docker:systemctlstartdocker停止docker:systemctlstopdocker重启docker:systemctlrestartdocker查看docker状态:systemctlstatusdocker开机启动:systemctlenabledocker查看docker概要信息:dockerinfo查看docker总体帮助文档:docker--he......
  • ubuntu实践遇见的BUG分享
    一、如果出现了如图情况 那就代表你创立得文件,无法执行写操作权限 只需要chmod-R777文件名即可 即所有者所在组其他人都可以新建啦!二、快捷键三、如图代表续行的意思按快捷键Ctrl+c或者+l即可退出哦!四、cp的误区使用将test下文件转移到下图地址中去不要这样要告诉hellow......
  • Docker 环境清理无用数据的方法?
    Docker让运维及开发部署变得容易了,正因为容易导致不经意的就在不断添加新的docker映像、容器等。这些都将占用了我们系统上的很多宝贵空间,又一直在快速地增加。以此我们有必要定期做下清理Docker环境,把一些不使用的Docker资源清理掉节省的空间出不来。查看本机磁盘空间大小[ro......