Namespace

 

Namespace 관리 리소스

리눅스에서 namespace는 lightweight 가상화 솔루션이다. XEN이나 KVM 같은 가상화 솔루션들은 커널 인스턴스들을 생성하여 동작시키는 것에 반하여 리눅스의 namespace는 커널 인스턴스를 만들지 않고 기존의 리소스들을 필요한 만큼의 namespace로 분리하여 묶어 관리하는 방법으로 사용한다. 리눅스의 cgroup과 namespace를 사용하여 container를 생성하여 사용하는 LXC, 그리고 LXC를 사용하는 docker 솔루션 등이 구현되었다.  커널이 부팅된 후 관리 자원은 각각의 초기 디폴트 namespace에서 관리한다. 그런 후 사용자의 필요에 따라 namespace를 추가하여 자원들을 별도로 분리하여 관리할 수 있다. 관리 가능한 namespace 리소스들은 다음과 같다.

 

Namespace 초기화

리눅스에서 user namespace를 제외한 다른 리소스들의 구현은 nsproxy를 통해 연결된다.

struct nsproxy init_nsproxy = {
        .count                  = ATOMIC_INIT(1),
        .uts_ns                 = &init_uts_ns,
#if defined(CONFIG_POSIX_MQUEUE) || defined(CONFIG_SYSVIPC)
        .ipc_ns                 = &init_ipc_ns,
#endif
        .mnt_ns                 = NULL,
        .pid_ns_for_children    = &init_pid_ns,
#ifdef CONFIG_NET
        .net_ns                 = &init_net,
#endif
};

 

nsproxy_cache_init()

kernel/nsproxy.c

int __init nsproxy_cache_init(void)
{
        nsproxy_cachep = KMEM_CACHE(nsproxy, SLAB_PANIC);
        return 0;
}

nsproxy 구조체를 할당해줄 수 있는 kmem 슬랩 캐시를 준비한다.

 

새로운 Namespace 생성

kernel/nsproxy.c

SYSCALL_DEFINE2(setns, int, fd, int, nstype)
{
        struct task_struct *tsk = current;
        struct nsproxy *new_nsproxy;
        struct file *file;
        struct ns_common *ns;
        int err;

        file = proc_ns_fget(fd);
        if (IS_ERR(file))
                return PTR_ERR(file);

        err = -EINVAL;
        ns = get_proc_ns(file_inode(file));
        if (nstype && (ns->ops->type != nstype))
                goto out;

        new_nsproxy = create_new_namespaces(0, tsk, current_user_ns(), tsk->fs);
        if (IS_ERR(new_nsproxy)) {
                err = PTR_ERR(new_nsproxy);
                goto out;
        }

        err = ns->ops->install(new_nsproxy, ns);
        if (err) {
                free_nsproxy(new_nsproxy);
                goto out;
        }
        switch_task_namespaces(tsk, new_nsproxy);
out:
        fput(file);
        return err;
}

setns 라는 이름의 syscall을 호출하여 현재 태스크(스레드)를 새로 지정한 namespace에 연결시킨다.

  • 코드 라인 8~11에서 proc 인터페이스에 해당하는 파일 디스크립터로 file 구조체 포인터를 알아온다.
    • 예) “/proc/1/ns/mnt”
  • 코드 라인 13~16에서 file 디스크립터에 연결된 ns_common 구조체 포인터인 ns를 얻어온다. 만일 nstype이 지정된 경우 file 디스크립터의 namespace type과 다른 경우 에러를 반환한다.
  • 코드 라인 18~22에서 새로운 namespace에 현재 태스크를 지정한다.
  • 코드 라인 24~28에서 해당 리소스의 namespace의 install에 등록된 콜백 함수를 호출하여 설치한다.
    • pid: pidns_install()
    • ipc: ipcns_install()
    • net: netns_install()
    • user: userns_install()
    • mnt: mntns_install()
    • uts: utsns_install()
  • 코드 라인 29에서 태스크의 멤버 nsproxy 가 새로운 nsproxy로 연결하게 한다. 기존에 연결해두었던 nsproxy의 참조 카운터가 0이되면 해제(free)한다.

 

create_new_namespaces()

kernel/nsproxy.c

/*
 * Create new nsproxy and all of its the associated namespaces.
 * Return the newly created nsproxy.  Do not attach this to the task,
 * leave it to the caller to do proper locking and attach it to task.
 */
static struct nsproxy *create_new_namespaces(unsigned long flags,
        struct task_struct *tsk, struct user_namespace *user_ns,
        struct fs_struct *new_fs)
{
        struct nsproxy *new_nsp;
        int err;

        new_nsp = create_nsproxy();
        if (!new_nsp)
                return ERR_PTR(-ENOMEM);

        new_nsp->mnt_ns = copy_mnt_ns(flags, tsk->nsproxy->mnt_ns, user_ns, new_fs);
        if (IS_ERR(new_nsp->mnt_ns)) {
                err = PTR_ERR(new_nsp->mnt_ns);
                goto out_ns;
        }

        new_nsp->uts_ns = copy_utsname(flags, user_ns, tsk->nsproxy->uts_ns);
        if (IS_ERR(new_nsp->uts_ns)) {
                err = PTR_ERR(new_nsp->uts_ns);
                goto out_uts;
        }

        new_nsp->ipc_ns = copy_ipcs(flags, user_ns, tsk->nsproxy->ipc_ns);
        if (IS_ERR(new_nsp->ipc_ns)) {
                err = PTR_ERR(new_nsp->ipc_ns);
                goto out_ipc;
        }

        new_nsp->pid_ns_for_children =
                copy_pid_ns(flags, user_ns, tsk->nsproxy->pid_ns_for_children);
        if (IS_ERR(new_nsp->pid_ns_for_children)) {
                err = PTR_ERR(new_nsp->pid_ns_for_children);
                goto out_pid;
        }

        new_nsp->net_ns = copy_net_ns(flags, user_ns, tsk->nsproxy->net_ns);
        if (IS_ERR(new_nsp->net_ns)) {
                err = PTR_ERR(new_nsp->net_ns);
                goto out_net;
        }

        return new_nsp;

out_net:
        if (new_nsp->pid_ns_for_children)
                put_pid_ns(new_nsp->pid_ns_for_children);
out_pid:
        if (new_nsp->ipc_ns)
                put_ipc_ns(new_nsp->ipc_ns);
out_ipc:
        if (new_nsp->uts_ns)
                put_uts_ns(new_nsp->uts_ns);
out_uts:
        if (new_nsp->mnt_ns)
                put_mnt_ns(new_nsp->mnt_ns);
out_ns:
        kmem_cache_free(nsproxy_cachep, new_nsp);
        return ERR_PTR(err);
}

nsproxy를 새로 할당한 후 namespace들을 연결하고 nsproxy를 반환한다. 플래그에는 새롭게 생성할 namespace를 지정한 플래그 값을 사용할 수 있다.

  • 코드 라인 13~15에서 nsproxy 구조체를 새로 할당해온다.
  • 코드 라인 17~21에서 CLONE_NEWNS 플래그가 지정된 경우 새롭게 mnt_namespace 구조체를 할당하고, 플래그가 지정되지 않은 경우 기존 mnt_namespace를 알아온다. 그리고 요청한 태스크를 mnt_namespcae에 지정한다.
    • namespace 생성 플래그:
      • CLONE_NEWNS
      • CLONE_NEWUTS
      • CLONE_NEWIPC
      • CLONE_NEWUSER
      • CLONE_NEWPID
      • CLONE_NEWNET
  • 코드 라인 23~46에서 mnt namespace와 비슷한 방식으로 uts, ipc, pid, net namespace도 처리한다.
  • 코드 라인 48에서 새롭게 만들어진 rxproxy 구조체 포인터를 반환한다.

 

create_nsproxy()

kernel/nsproxy.c

static inline struct nsproxy *create_nsproxy(void)
{
        struct nsproxy *nsproxy;

        nsproxy = kmem_cache_alloc(nsproxy_cachep, GFP_KERNEL);
        if (nsproxy)
                atomic_set(&nsproxy->count, 1);
        return nsproxy;
}

nsproxy 구조체를 할당해온다.

 

copy_pid_ns()

kernel/pid_namespace.c

struct pid_namespace *copy_pid_ns(unsigned long flags,
        struct user_namespace *user_ns, struct pid_namespace *old_ns)
{      
        if (!(flags & CLONE_NEWPID))
                return get_pid_ns(old_ns);
        if (task_active_pid_ns(current) != old_ns)
                return ERR_PTR(-EINVAL);
        return create_pid_namespace(user_ns, old_ns);
}

 

create_pid_namespace()

kernel/pid_namespace.c

static struct pid_namespace *create_pid_namespace(struct user_namespace *user_ns,
        struct pid_namespace *parent_pid_ns)
{
        struct pid_namespace *ns;
        unsigned int level = parent_pid_ns->level + 1;
        int i;
        int err;

        if (level > MAX_PID_NS_LEVEL) {
                err = -EINVAL;
                goto out;
        }

        err = -ENOMEM;
        ns = kmem_cache_zalloc(pid_ns_cachep, GFP_KERNEL);
        if (ns == NULL)
                goto out;

        ns->pidmap[0].page = kzalloc(PAGE_SIZE, GFP_KERNEL);
        if (!ns->pidmap[0].page)
                goto out_free;

        ns->pid_cachep = create_pid_cachep(level + 1);
        if (ns->pid_cachep == NULL)
                goto out_free_map;

        err = ns_alloc_inum(&ns->ns);
        if (err)
                goto out_free_map;
        ns->ns.ops = &pidns_operations;

        kref_init(&ns->kref);
        ns->level = level;
        ns->parent = get_pid_ns(parent_pid_ns);
        ns->user_ns = get_user_ns(user_ns);
        ns->nr_hashed = PIDNS_HASH_ADDING;
        INIT_WORK(&ns->proc_work, proc_cleanup_work);

        set_bit(0, ns->pidmap[0].page);
        atomic_set(&ns->pidmap[0].nr_free, BITS_PER_PAGE - 1);

        for (i = 1; i < PIDMAP_ENTRIES; i++)
                atomic_set(&ns->pidmap[i].nr_free, BITS_PER_PAGE);

        return ns;

out_free_map:
        kfree(ns->pidmap[0].page);
out_free:
        kmem_cache_free(pid_ns_cachep, ns);
out:
        return ERR_PTR(err);
}

새로 만들어지는 child pid namespace는 최대 32단계까지 생성가능하다.  pid_namespace를 생성하고 초기화하고 내부 pidmap의 구성은 다음을 참고한다.

 

Namespace 예)

아래 <pid> 값은 해당 태스크의 pid 숫자로 치환되어야 한다.

root@jake-VirtualBox:/proc/<pid>/ns# ls /proc/1/ns/ -la
합계 0
dr-x--x--x 2 root root 0  1월 11 11:16 .
dr-xr-xr-x 9 root root 0  1월  8 14:06 ..
lrwxrwxrwx 1 root root 0  1월 11 11:16 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0  1월 11 11:16 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0  1월 11 11:16 net -> net:[4026531957]
lrwxrwxrwx 1 root root 0  1월 11 11:16 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0  1월 11 11:16 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0  1월 11 11:16 uts -> uts:[4026531838]

 

# unshare --help

Usage:
 unshare [options] <program> [<argument>...]

Run a program with some namespaces unshared from the parent.

Options:
 -m, --mount               unshare mounts namespace
 -u, --uts                 unshare UTS namespace (hostname etc)
 -i, --ipc                 unshare System V IPC namespace
 -n, --net                 unshare network namespace
 -p, --pid                 unshare pid namespace
 -U, --user                unshare user namespace
 -f, --fork                fork before launching <program>
     --mount-proc[=<dir>]  mount proc filesystem first (implies --mount)
 -r, --map-root-user       map current user to root (implies --user)
 -s, --setgroups allow|deny  control the setgroups syscall in user namespaces

 -h, --help     display this help and exit
 -V, --version  output version information and exit

 

참고

 

 

3 thoughts to “Namespace”

  1. 안녕하세요 글 잘봤습니다..^^
    다름이 아니라 하나 여쭤보고 싶은 것이 있는데요
    init_proxy에서 왜 mnt_ns는 NULL로 초기화하는지 아시나요?
    검색해봤는데 마땅한 답변이 없어서요
    그냥 기본 파일시스템을 가져다 써서 그런건가.. 궁금하네요~

    1. 안녕하세요?
      init_nsproxy 는 커널에서 처음 namespace를 관리하기 위한 초기 namespace라고 알고 있습니다.
      그런데 pid, uts, ipc, … 등과는 다르게 mnt의 경우 유저에서만 사용하기 때문에 새로운 nsproxy를 생성하기 전에는 필요 없는 것으로 판단합니다.
      정확한 답변이 아닐 수도 있습니다. 참고 바랍니다. ^^;
      감사합니다.

문영일에게 댓글 남기기 댓글 취소