Docker 学习笔记8 容器技术原理 UTS Namespace

一、 容器技术说明

容器的核心技术是Cgroup与Namespace,在此基础上还有一些其它工具共同构成容器技术。
容器是宿主机上的进程

  • 容器技术通过Namespace实现资源隔离
  • 通过Cgroup实现资源控制
  • 通过rootfs实现文件系统隔离
  • 容器引擎本身的特性来管理容器的生命周期。

Docker类似早期的LXC管理引擎。LXC是Cgrup的管理工具,Cgroup是Namespace的用户空间管理接口。
Namespace是Linux内核在task_struct中对进程组管理的基础机制。

二、Namespace

Linux Namespace是Linux提供的一种内核级别环境隔离的方法。chroot提供了一种简单的隔离模式:chroot内部的文件系统无法访问外部的内容。Linux Namespace在此基础上,提供了对UTS、IPC、mount、PID、network、User等的隔离机制。
一个容器基本需要做到6项基本隔离,也就是Linux内核中提供的6种Namespace隔离:

Namespace Constant 隔离内容 内核版本
IPC CLONE_NEWIPC 信号量,消息队列和共享内存 Linux 2.6.19
Network CLONE_NEWNET 网络资源 始于Linux 2.6.24 完成于 Linux 2.6.29
Mount CLONE_NEWNS 文件系统挂载点 Linux 2.4.19
PID CLONE_NEWPID 进程ID Linux 2.6.24
UTS CLONE_NEWUTS 主机名和域名 Linux 2.6.19
User CLONE_NEWUSER 用户ID和组ID 始于 Linux 2.6.23 完成于 Linux 3.8)

这些Namespace分别对进程的Cgroup root、进程间通信、网络、文件系统挂载点、进程ID、主机名域名进行隔离。

对Namespace的操作,主要是通过Clone,setns,unshare这三个系统调用来完成的。

  • clone() – 实现线程的系统调用,用来创建一个新的进程,并可以通过上述参数达到隔离
  • unshare() – 使某进程脱离某个namespace
  • setns() – 把某进程加入到某个namespace

clone()系统调用

Clone可以用来创建新的Namespace。clone有一个flags参数,这些flags参数以CLONE_NEW*为格式,传入这些参数后,由clone创建出来的新进程就位于新的Namespace之中了。

原型说明:

int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);

参数说明:

  • fn是函数指针,我们知道进程的4要素,这个就是指向程序的指针,就是所谓的“剧本"
  • child_stack是为子进程分配系统堆栈空间(在Linux下系统堆栈空间是2页面,就是8K的内存,其中在这块内存中,低地址上放入了值,这个值就是进程控制块task_struct的值)
  • flags就是标志用来描述你需要从父进程继承哪些资源
  • arg就是传给子进程的参数。下面是flags可以取的值

Flag标志含义

  • CLONE_PARENT 创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子”
  • CLONE_FS 子进程与父进程共享相同的文件系统,包括root、当前目录、umask
  • CLONE_FILES 子进程与父进程共享相同的文件描述符(file descriptor)表
  • CLONE_NEWNS 在新的namespace启动子进程,namespace描述了进程的文件hierarchy
  • CLONE_SIGHAND 子进程与父进程共享相同的信号处理(signal handler)表
  • CLONE_PTRACE 若父进程被trace,子进程也被trace
  • CLONE_VFORK 父进程被挂起,直至子进程释放虚拟内存资源
  • CLONE_VM 子进程与父进程运行于相同的内存空间
  • CLONE_PID 子进程在创建时PID与父进程一致
  • CLONE_THREAD Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群

一个最简单的clone()调用示例(Linux内核3.8以上):

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
/* 定义一个给 clone 用的栈,栈大小1M */
#define STACK_SIZE (1024 * 1024)
static char container_stack[STACK_SIZE];
char* const container_args[] = {
    "/bin/bash",
    NULL
};
int container_main(void* arg)
{
    printf("Container - inside the container!\n");
    /* 直接执行一个shell,以便我们观察这个进程空间里的资源是否被隔离了 */
    execv(container_args[0], container_args); 
    printf("Something's wrong!\n");
    return 1;
}
int main()
{
    printf("Parent - start a container!\n");
    /* 调用clone函数,其中传出一个函数,还有一个栈空间的(为什么传尾指针,因为栈是反着的) */
    int container_pid = clone(container_main, container_stack+STACK_SIZE, SIGCHLD, NULL);
    /* 等待子进程结束 */
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}

使用gcc编译运行:
在这里插入图片描述
这里clone类似pthread。 父子进程在进程空间没什么差别,子进程可以访问父进程的资源。

UTS Namespace

下面对代码简单修改:

int container_main(void* arg)
{
    printf("Container - inside the container!\n");
    sethostname("container",10); /* 设置hostname */
    execv(container_args[0], container_args);
    printf("Something's wrong!\n");
    return 1;
}
int main()
{
    printf("Parent - start a container!\n");
    int container_pid = clone(container_main, container_stack+STACK_SIZE, 
            CLONE_NEWUTS | SIGCHLD, NULL); /*启用CLONE_NEWUTS Namespace隔离 */
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}

运行效果:(需要root权限)
在这里插入图片描述
当运行程序后,子进程的hostname变成了container,exit退出后hostname显示的是主机的名称。

本文参考:

  • https://lwn.net/Articles/531114/
  • https://coolshell.cn/articles/17010.html
  • 《容器云运维实战》 电子工业出版社

猜你喜欢

转载自blog.csdn.net/xundh/article/details/106663309
今日推荐