从零实现一个容器运行时:Docker的核心设计
前言
你有没有想过:Docker是怎么做到让一个程序看起来像在独立系统里运行的?隔离是怎么实现的?
Docker的核心技术是 Namespace + Cgroups + 联合文件系统。
今天我们用C语言从零实现一个容器运行时的核心功能:
· Namespace隔离(PID、NET、IPC、UTS、Mount)
· Cgroups资源限制(CPU、内存)
· Rootfs切换(chroot/pivot_root)
· 容器生命周期管理
· 镜像分层(联合文件系统)
---
一、容器运行时核心原理
1. 容器技术栈
```
┌─────────────────────────────────────────────────────────────┐
│ 容器 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Namespace隔离 │ │
│ │ PID | NET | IPC | UTS | Mount | User │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Cgroups资源限制 │ │
│ │ CPU | Memory | Disk I/O | Network I/O │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Rootfs (chroot/pivot_root) │ │
│ │ /bin /etc /usr /var /lib ... │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
2. 核心概念
概念 说明
Namespace 资源隔离,让容器看到独立的PID、网络等
Cgroups 资源限制,控制CPU、内存使用上限
Rootfs 容器的根文件系统
联合文件系统 镜像分层存储(OverlayFS)
runc 容器运行时(实际执行者)
---
二、完整代码实现
1. 基础数据结构
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/prctl.h>
#include <sched.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <dirent.h>
#include <grp.h>
#include <linux/capability.h>
#define MAX_CONTAINERS 100
#define MAX_CMD_LEN 256
#define MAX_ROOTFS_LEN 512
#define STACK_SIZE (1024 * 1024)
// 容器状态
typedef enum {
CONTAINER_CREATED = 0,
CONTAINER_RUNNING,
CONTAINER_PAUSED,
CONTAINER_STOPPED,
CONTAINER_EXITED
} container_state_t;
// 容器配置
typedef struct container_config {
char name[64];
char rootfs[MAX_ROOTFS_LEN];
char cmd[MAX_CMD_LEN];
char **argv;
int argc;
// 资源限制
int cpu_limit; // CPU核心数
int memory_limit_mb; // 内存限制(MB)
// 隔离配置
int isolate_pid;
int isolate_net;
int isolate_ipc;
int isolate_uts;
int isolate_mount;
int isolate_user;
// 网络配置
char veth_host[32];
char veth_container[32];
char bridge[32];
char hostname[64];
} container_config_t;
// 容器实例
typedef struct container {
char id[64];
container_config_t config;
container_state_t state;
pid_t pid;
int pty_master;
int pty_slave;
time_t start_time;
time_t stop_time;
int exit_code;
struct container *next;
} container_t;
// 容器运行时
typedef struct container_runtime {
container_t *containers;
int container_count;
char data_dir[256];
pthread_mutex_t mutex;
int running;
} container_runtime_t;
```
2. Namespace隔离
```c
// 创建Namespace标志
#define NAMESPACE_FLAGS \
(CLONE_NEWPID | CLONE_NEWNET | CLONE_NEWIPC | \
CLONE_NEWUTS | CLONE_NEWNS | CLONE_NEWUSER)
// 容器进程入口
int container_main(void *arg) {
container_config_t *config = (container_config_t*)arg;
// 设置hostname
if (config->hostname[0]) {
sethostname(config->hostname, strlen(config->hostname));
}
// 挂载proc文件系统
mount("proc", "/proc", "proc", 0, NULL);
mount("sysfs", "/sys", "sysfs", 0, NULL);
mount("tmpfs", "/dev", "tmpfs", 0, NULL);
mount("devpts", "/dev/pts", "devpts", 0, NULL);
// 切换根目录
if (chdir(config->rootfs) != 0) {
perror("chdir");
return 1;
}
if (chroot(config->rootfs) != 0) {
perror("chroot");
return 1;
}
// 执行容器命令
execvp(config->argv[0], config->argv);
perror("execvp");
return 1;
}
// 创建容器进程
pid_t create_container_process(container_config_t *config) {
char *stack = malloc(STACK_SIZE);
if (!stack) return -1;
// 创建子进程(在隔离环境中)
pid_t pid = clone(container_main, stack + STACK_SIZE,
SIGCHLD | NAMESPACE_FLAGS, config);
if (pid < 0) {
perror("clone");
free(stack);
return -1;
}
free(stack);
return pid;
}
```
3. Cgroups资源限制
```c
// Cgroup路径
void cgroup_path(char *path, const char *controller, const char *name) {
snprintf(path, 256, "/sys/fs/cgroup/%s/%s", controller, name);
}
// 创建Cgroup
int cgroup_create(const char *name) {
char path[256];
char controllers[][16] = {"cpu", "memory", "pids"};
for (int i = 0; i < 3; i++) {
cgroup_path(path, controllers[i], name);
if (mkdir(path, 0755) < 0 && errno != EEXIST) {
perror("mkdir cgroup");
return -1;
}
}
return 0;
}
// 设置CPU限制
int cgroup_set_cpu(const char *name, int cpu_limit) {
char path[256];
cgroup_path(path, "cpu", name);
char filepath[512];
snprintf(filepath, sizeof(filepath), "%s/cpu.cfs_quota_us", path);
FILE *fp = fopen(filepath, "w");
if (!fp) return -1;
// 每个CPU核心100000us,乘以核心数
fprintf(fp, "%d", cpu_limit * 100000);
fclose(fp);
// 设置周期
snprintf(filepath, sizeof(filepath), "%s/cpu.cfs_period_us", path);
fp = fopen(filepath, "w");
if (fp) {
fprintf(fp, "100000");
fclose(fp);
}
return 0;
}
// 设置内存限制
int cgroup_set_memory(const char *name, int memory_limit_mb) {
char path[256];
cgroup_path(path, "memory", name);
char filepath[512];
snprintf(filepath, sizeof(filepath), "%s/memory.limit_in_bytes", path);
FILE *fp = fopen(filepath, "w");
if (!fp) return -1;
fprintf(fp, "%d", memory_limit_mb * 1024 * 1024);
fclose(fp);
return 0;
}
// 添加进程到Cgroup
int cgroup_add_process(const char *name, pid_t pid) {
char controllers[][16] = {"cpu", "memory", "pids"};
for (int i = 0; i < 3; i++) {
char path[256], filepath[512];
cgroup_path(path, controllers[i], name);
snprintf(filepath, sizeof(filepath), "%s/cgroup.procs", path);
FILE *fp = fopen(filepath, "w");
if (!fp) return -1;
fprintf(fp, "%d", pid);
fclose(fp);
}
return 0;
}
```
4. 容器管理
```c
// 创建容器运行时
container_runtime_t *runtime_create(const char *data_dir) {
container_runtime_t *r = malloc(sizeof(container_runtime_t));
memset(r, 0, sizeof(container_runtime_t));
strcpy(r->data_dir, data_dir);
r->running = 1;
pthread_mutex_init(&r->mutex, NULL);
mkdir(data_dir, 0755);
printf("[运行时] 初始化完成,数据目录: %s\n", data_dir);
return r;
}
// 生成容器ID
void generate_container_id(char *buf) {
snprintf(buf, 64, "%d-%ld", getpid(), time(NULL));
}
// 创建容器
container_t *runtime_create_container(container_runtime_t *r,
container_config_t *config) {
pthread_mutex_lock(&r->mutex);
container_t *c = malloc(sizeof(container_t));
generate_container_id(c->id);
memcpy(&c->config, config, sizeof(container_config_t));
c->state = CONTAINER_CREATED;
c->pid = 0;
c->start_time = 0;
c->stop_time = 0;
c->exit_code = 0;
c->next = r->containers;
r->containers = c;
r->container_count++;
pthread_mutex_unlock(&r->mutex);
printf("[容器] 创建: %s (%s)\n", c->id, config->name);
return c;
}
// 启动容器
int runtime_start_container(container_runtime_t *r, const char *id) {
pthread_mutex_lock(&r->mutex);
container_t *c = r->containers;
while (c) {
if (strcmp(c->id, id) == 0) break;
c = c->next;
}
if (!c) {
pthread_mutex_unlock(&r->mutex);
return -1;
}
if (c->state == CONTAINER_RUNNING) {
pthread_mutex_unlock(&r->mutex);
return -2;
}
// 创建Cgroup
cgroup_create(c->id);
if (c->config.cpu_limit > 0) {
cgroup_set_cpu(c->id, c->config.cpu_limit);
}
if (c->config.memory_limit_mb > 0) {
cgroup_set_memory(c->id, c->config.memory_limit_mb);
}
// 创建容器进程
pid_t pid = create_container_process(&c->config);
if (pid < 0) {
pthread_mutex_unlock(&r->mutex);
return -3;
}
// 添加到Cgroup
cgroup_add_process(c->id, pid);
c->pid = pid;
c->state = CONTAINER_RUNNING;
c->start_time = time(NULL);
pthread_mutex_unlock(&r->mutex);
printf("[容器] 启动: %s (PID: %d)\n", c->id, pid);
return 0;
}
// 停止容器
int runtime_stop_container(container_runtime_t *r, const char *id, int signal) {
pthread_mutex_lock(&r->mutex);
container_t *c = r->containers;
while (c) {
if (strcmp(c->id, id) == 0) break;
c = c->next;
}
if (!c) {
pthread_mutex_unlock(&r->mutex);
return -1;
}
if (c->state != CONTAINER_RUNNING) {
pthread_mutex_unlock(&r->mutex);
return -2;
}
if (kill(c->pid, signal) < 0) {
pthread_mutex_unlock(&r->mutex);
return -3;
}
c->state = CONTAINER_STOPPED;
c->stop_time = time(NULL);
pthread_mutex_unlock(&r->mutex);
printf("[容器] 停止: %s (信号: %d)\n", c->id, signal);
return 0;
}
// 删除容器
int runtime_remove_container(container_runtime_t *r, const char *id) {
pthread_mutex_lock(&r->mutex);
container_t *c = r->containers;
container_t *prev = NULL;
while (c) {
if (strcmp(c->id, id) == 0) break;
prev = c;
c = c->next;
}
if (!c) {
pthread_mutex_unlock(&r->mutex);
return -1;
}
if (c->state == CONTAINER_RUNNING) {
pthread_mutex_unlock(&r->mutex);
return -2;
}
if (prev) {
prev->next = c->next;
} else {
r->containers = c->next;
}
r->container_count--;
free(c);
pthread_mutex_unlock(&r->mutex);
printf("[容器] 删除: %s\n", id);
return 0;
}
```
5. 联合文件系统(OverlayFS模拟)
```c
// 联合挂载(模拟OverlayFS)
int setup_overlay_rootfs(const char *lower_dir, const char *upper_dir,
const char *work_dir, const char *merged_dir) {
// 创建目录
mkdir(upper_dir, 0755);
mkdir(work_dir, 0755);
mkdir(merged_dir, 0755);
char options[512];
snprintf(options, sizeof(options),
"lowerdir=%s,upperdir=%s,workdir=%s",
lower_dir, upper_dir, work_dir);
if (mount("overlay", merged_dir, "overlay", 0, options) < 0) {
perror("mount overlay");
return -1;
}
printf("[Overlay] 挂载: %s + %s -> %s\n", lower_dir, upper_dir, merged_dir);
return 0;
}
// 镜像拉取(模拟)
int pull_image(const char *image_name, const char *target_dir) {
char path[256];
snprintf(path, sizeof(path), "%s/%s", target_dir, image_name);
mkdir(path, 0755);
// 创建基本rootfs结构
mkdir(path, 0755);
mkdir(path, 0755);
mkdir(path, 0755);
mkdir(path, 0755);
mkdir(path, 0755);
// 创建基本的/etc/hosts
char hosts_path[512];
snprintf(hosts_path, sizeof(hosts_path), "%s/etc/hosts", path);
FILE *fp = fopen(hosts_path, "w");
if (fp) {
fprintf(fp, "127.0.0.1 localhost\n");
fclose(fp);
}
printf("[镜像] 拉取: %s -> %s\n", image_name, path);
return 0;
}
```
6. 测试代码
```c
void test_runtime() {
printf("=== 容器运行时测试 ===\n\n");
container_runtime_t *r = runtime_create("./containers");
// 配置容器
container_config_t config;
memset(&config, 0, sizeof(config));
strcpy(config.name, "test-container");
strcpy(config.rootfs, "./rootfs");
strcpy(config.cmd, "/bin/sh");
config.argv = malloc(sizeof(char*) * 3);
config.argv[0] = "/bin/sh";
config.argv[1] = "-c";
config.argv[2] = "echo 'Hello from container!' && sleep 10";
config.argc = 3;
config.cpu_limit = 1;
config.memory_limit_mb = 256;
strcpy(config.hostname, "container");
// 创建容器
container_t *c = runtime_create_container(r, &config);
// 启动容器
runtime_start_container(r, c->id);
sleep(2);
// 显示状态
printf("\n=== 容器状态 ===\n");
printf("ID: %s\n", c->id);
printf("状态: %d\n", c->state);
printf("PID: %d\n", c->pid);
// 停止容器
sleep(5);
runtime_stop_container(r, c->id, SIGTERM);
// 删除容器
runtime_remove_container(r, c->id);
free(config.argv);
free(r);
}
int main() {
test_runtime();
return 0;
}
```
---
三、编译和运行
```bash
gcc -o runtime runtime.c -lpthread
./runtime
```
---
四、Docker vs 本实现
特性 本实现 Docker
Namespace隔离 ✅ ✅
Cgroups限制 ✅ ✅
联合文件系统 模拟 OverlayFS
容器生命周期 基础 完整
镜像管理 基础 完整
网络管理 无 Bridge/Host/Overlay
镜像仓库 无 Docker Hub
---
五、总结
通过这篇文章,你学会了:
· 容器的核心技术(Namespace + Cgroups)
· 进程隔离实现
· 资源限制(CPU/内存)
· 根文件系统切换
· 容器生命周期管理
容器运行时是云原生的基石。掌握它,你就理解了Docker、Podman、CRI的底层设计。
下一篇预告:《从零实现一个Kubernetes调度器:Pod调度与资源分配》
---
评论区分享一下你对容器技术的理解~
