本文目的是让读者对namespace和cgroup有具象的认识,并不会深入。当然,由于笔者Linux知识有限,也无法深入。
“容器是一个与宿主机系统共享内核但与操作系统中的其它进程资源隔离的执行环境”,这是理解容器技术的核心。这句话可以翻译的更直白一点:容器是一个环境,该环境内运行的进程和操作系统中的其它进程是一样的,享受同样的硬件资源,唯一的差别是,该环境内的进程看不到其它进程的存在,操作也不会相互影响,即所谓的隔离;多个容器的运行,就是在各自的隔离环境中运行各自的进程。
容器只是一个抽象的逻辑概念,具有上述特性的就可以叫做容器,这些特性的实现,依赖于Linux操作系统提供的namespace和cgroup。namespace提供了资源隔离,保证不同namespace之前的资源操作不相互影响;cgroup提供资源限制,保障一个group内的资源使用不会超过预设。
namespace
资源隔离,一个完整的运行环境,即一个所谓的容器,需要隔离的资源包括哪些呢?大致有如下几类
- 隔离文件系统:文件操作相互不影响
- 隔离网络:容器需要有独立的IP、端口、路由规则等
- 隔离主机名:容器需要在网络中标识自己
- 隔离进程间通信:消息队列、共享内存等
- 隔离用户权限:容器内应该有完整的用户权限
- 隔离PID:容器内的PID与宿主机的PID需要隔离
针对每一类,Linux在namespace上都提供了隔离支持,即有六种不同类型的namespace,每种对应不同的资源。
namespace存在的目的,就是为了实现“轻量级虚拟化服务”(即容器),这是内核级别的支持。处于同一个namespace下的进程,相互可感知,可见;处于不同namespace下的进程,完全看不到对方的存在,就像是在一个独立的操作系统中一样。
启动一个容器,只需要在一个全新的namespace中创建该容器的进程即可,对此,Linux通过API的方式提供支持
- clone():创建一个独立的namespace的进程
- setns():将当前进程加入一个已经存在的namespace
- unshare():在原先进程上进行资源隔离,即原先进程还是处于原来的namespace,但是其创建出来的子进程在新的namespace中
为此,我们可以写一个简单的代码来体验和验证资源隔离,有如下c代码
// namespace.c
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
#define STACK_SIZE (1024 * 1024)
static char child_stack[STACK_SIZE];
char* const child_args[] = {
"/bin/bash",
NULL
};
// 在新的进程中
int child_main(void* args) {
printf("在子进程中!\n");
// 设置新的hostname
sethostname("NewNamespace", 12);
// 执行bash,进