コンテナの実装

実装1. Bashで作るコンテナ

mkdir rootfs
docker export $(docker create ubuntu:20.04) | tar -C rootfs -xvf -
unshare --mount --uts --net --pid --user --fork /bin/bash
chroot rootfs /bin/bash
mount -t proc proc /proc

psコマンドでプロセスが隔離されていることを確認しよう

実装2. C言語で作るコンテナ

コンテナのミニマム実装の例: c2

#define _GNU_SOURCE

#include <error.h>
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

// new_uidmap -- HostのUIDをコンテナのUID 0(root user)に割り当てる
void new_uidmap(uid_t uid) {
    int fd = open("/proc/self/uid_map", O_WRONLY);
    if (fd < 0) {
        perror("open /proc/self/uid_map");
        exit(EXIT_FAILURE);
    }

    char uid_map[255];
    sprintf(uid_map, "0 %d 1", (int)uid);

    int ret = write(fd, uid_map, sizeof(uid_map));
    if (ret != sizeof(uid_map)) {
        perror("write /proc/self/uid_map");
        exit(EXIT_FAILURE);
    }

    close(fd);
}

// new_gidmap -- HostのGIDをコンテナのGID 0(root group)に割り当てる
void new_gidmap(gid_t gid) {
    int fd = open("/proc/self/setgroups", O_WRONLY);
    if (fd < 0) {
        perror("open /proc/self/setgroups");
        exit(EXIT_FAILURE);
    }
    int ret = write(fd, "deny", sizeof("deny"));
    if (ret != sizeof("deny")) {
        perror("write /proc/self/setgroups");
        exit(EXIT_FAILURE);
    }
    close(fd);

    fd = open("/proc/self/gid_map", O_WRONLY);
    if (fd < 0) {
        perror("open /proc/self/gid_map");
        exit(EXIT_FAILURE);
    }

    char gid_map[255];
    sprintf(gid_map, "0 %d 1", (int)gid);

    ret = write(fd, gid_map, sizeof(gid_map));
    if (ret != sizeof(gid_map)) {
        perror("write /proc/self/gid_map");
        exit(EXIT_FAILURE);
    }
    close(fd);
}

int main(void) {
    const uid_t host_uid = getuid();
    const gid_t host_gid = getgid();

    // PID, UTS, Network, IPC, User, Mountを分離する
    const int clone_flags = CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNET |
                            CLONE_NEWIPC | CLONE_NEWUSER | CLONE_NEWNS;

    // Hostと別のLinux Namespaceを作成する
    int err = unshare(clone_flags);
    if (err < 0) {
        perror("unshare");
        exit(EXIT_FAILURE);
    }

    // HostのUIDをコンテナのUID 0に割り当てる
    new_uidmap(host_uid);
    // HostのGIDをコンテナのGID 0に割り当てる
    new_gidmap(host_gid);

    // 子プロセスの作成
    const pid_t pid = fork();

    if (pid < 0) {
        perror("fork");
        exit(EXIT_FAILURE);
    } else if (pid == 0) {
        // Child Process
        int ret = chroot("rootfs");
        if (ret < 0) {
            perror("chroot");
            exit(EXIT_FAILURE);
        }
        chdir("/");

        ret = mount("proc", "/proc", "proc", 0, NULL);
        if (ret < 0) {
            perror("mount procfs");
            exit(EXIT_FAILURE);
        }

        char *const child_argv[2] = {"/bin/bash", NULL};
        char *const env[] = {};
        execve(child_argv[0], child_argv, env);
        perror("execve");
        exit(EXIT_FAILURE);
    }

    // Parent Process
    int status;

    const pid_t ret = waitpid(pid, &status, 0);

    if (ret < 0) {
        perror("waitpid");
        exit(EXIT_FAILURE);
    }

    // プロセスが終了している場合
    if (WIFEXITED(status)) {
        printf("child exit code: %d\n", WEXITSTATUS(status));
    } else {
        printf("child status: %04d\n", status);
    }
}

その他の実装例

  • jail
  • lxc/lxd
  • docker
  • podman