linux namespace and cgroup

Node.js задняя часть Linux Docker

namespace

Ссылаться на

Введение

Пространство имен Linux — это метод изоляции среды на уровне ядра, предоставляемый Linux.
Обеспечить механизм изоляции для UTS, IPC, Mount, Pid, ​​Network, user и т. Д.

Классификация

Классификация Параметры системного вызова Соответствующая версия ядра Содержание карантина
Mount namespaces CLONE_NEWNS Linux 2.4.19 точка монтирования (файловая система)
UTS namespaces CLONE_NEWUTS Linux 2.6.19 Имя хоста и доменное имя, влияющие на uname(hostname, domainname)
IPC namespaces CLONE_NEWIPC Linux 2.6.19 Семафоры, очереди сообщений и разделяемая память, межпроцессное взаимодействие с глобальным идентификатором
PID namespaces CLONE_NEWPID Linux 2.6.24 номер процесса
Network namespaces CLONE_NEWNET Начал с Linux 2.6.24 Закончил Linux 2.6.29 Сетевые устройства, сетевые стеки, порты и т. д.
User namespaces CLONE_NEWUSER Начал с Linux 2.6.23 и закончил Linux 3.8) пользователи и группы пользователей

три системных вызова

передача эффект
clone() Реализуйте системный вызов потока для создания нового процесса и можете добиться изоляции, разработав указанные выше параметры.
unshare() удалить процесс из пространства имен
setns() Добавить процесс в пространство имен

Детальное объяснение

тестовый код

#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,以便我们观察这个进程空间里的资源是否被隔离了 */
    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");
    /* 调用clone函数,其中传出一个函数,还有一个栈空间的(为什么传尾指针,因为栈是反着的) */
    // int container_pid = clone(container_main, container_stack+STACK_SIZE, SIGCHLD, NULL);
    // int container_pid = clone(container_main, container_stack+STACK_SIZE, 
            CLONE_NEWUTS | SIGCHLD, NULL); /*启用CLONE_NEWUTS Namespace隔离 */
    int container_pid = clone(container_main, container_stack+STACK_SIZE, 
            CLONE_NEWUTS | CLONE_NEWIPC | SIGCHLD, NULL);

    /* 等待子进程结束 */
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}

UTS Namespace

加入 
sethostname("container",10); /* 设置hostname */

int container_pid = clone(container_main, container_stack+STACK_SIZE, 
            CLONE_NEWUTS | SIGCHLD, NULL); /*启用CLONE_NEWUTS Namespace隔离 */

root@container:~/testnamespace# uname -a
Linux container 4.4.0-96-generic #119-Ubuntu SMP Tue Sep 12 14:59:54 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
root@container:~/testnamespace# hostname
container

IPC Namespace


// 如果隔离了 ipcs -q 看不到外面的,否则能看到

root@kube-master:~/testnamespace# ipcs -a

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages
0x10d91bac 0          root       644        0            0
0xb92f99fd 32769      root       644        0            0
0xfcebd528 65538      root       644        0            0

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
0x00000000 0          root       644        80         2
0x00000000 32769      root       644        16384      2
0x00000000 65538      root       644        280        2

------ Semaphore Arrays --------
key        semid      owner      perms      nsems
0x000000a7 0          root       600        1

PID Namespace

 int container_pid = clone(container_main, container_stack+STACK_SIZE, 
            CLONE_NEWUTS | CLONE_NEWPID | SIGCHLD, NULL); 

没有隔离
root@container:~/testnamespace# ps -a
  PID TTY          TIME CMD
10079 pts/0    00:00:00 a.out

隔离之后
root@container:~# echo ?
1

但是 ps -a没有变化,这是因为ps, top这些命令会去读/proc文件系统,所以,因为/proc文件系统在父进程和子进程都是一样的,所以这些命令显示的东西都是一样的

pid 1 — это специальный pid, который должен иметь возможности мониторинга процессов и повторного использования ресурсов, в docker 1.13 для решения этой проблемы введен параметр --init.
--init false Run an init inside the container that forwards signals and reaps processes
Ссылаться наblog.Pinghu City ion.Where/2015/01/20/…

➜  ke git:(alb) ✗ docker run  alpine  ps
PID   USER     TIME   COMMAND
    1 root       0:00 ps
➜  ke git:(alb) ✗ docker run --init  alpine  ps
PID   USER     TIME   COMMAND
    1 root       0:00 /dev/init -- ps
    5 root       0:00 ps

Системные вызовы unshare() и setns() обрабатывают пространство имен PID по-разному.При отмене совместного использования пространства имен PID вызывающий процесс выделяет новое пространство имен PID своему дочернему процессу, но сам вызывающий процесс не перемещается в новое пространство имен PID. . И первый дочерний процесс, созданный вызывающим процессом, имеет PID 1 в новом пространстве имен и становится процессом инициализации в новом пространстве имен. Почему unshare() и setns() напрямую входят в новое пространство имен при создании других пространств имен, но не в пространство имен PID? Поскольку PID, полученный при вызове функции getpid(), определяет, какой PID следует вернуть, на основе пространства имен PID, в котором находится вызывающий объект, ввод нового пространства имен PID приведет к изменению PID. Для программ пользовательского режима и библиотечных функций все они думают, что PID процесса является константой, и изменения в PID приведут к сбою этих процессов. Другими словами, после создания программного процесса определяется взаимосвязь его пространства имен PID, и процесс не будет изменять соответствующее пространство имен PID.

Mount Namespace

#include <stdlib.h>
system("mount -t proc proc /proc");
 /* 启用Mount Namespace - 增加CLONE_NEWNS参数 */
int container_pid = clone(container_main, container_stack+STACK_SIZE, 
        CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, NULL);

// 这时候 ps就干净多了
root@vm-master:~/testnamespace# ps -aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0  20036  3868 pts/1    S    12:24   0:00 /bin/bash
root        15  0.0  0.0  36084  3228 pts/1    R+   12:24   0:00 ps -aux

О команде монтирования

模仿Docker的Mount Namespace。
先要做一个rootfs文件夹
hchen@ubuntu:~/rootfs$ ls
bin  dev  etc  home  lib  lib64  mnt  opt  proc  root  run  sbin  sys  tmp  usr  var

// 拷贝必要的命令
hchen@ubuntu:~/rootfs$ ls ./bin ./usr/bin

./bin:
bash   chown  gzip      less  mount       netstat  rm     tabs  tee      top       tty
cat    cp     hostname  ln    mountpoint  ping     sed    tac   test     touch     umount
chgrp  echo   ip        ls    mv          ps       sh     tail  timeout  tr        uname
chmod  grep   kill      more  nc          pwd      sleep  tar   toe      truncate  which

./usr/bin:
awk  env  groups  head  id  mesg  sort  strace  tail  top  uniq  vi  wc  xargs


// 拷贝命令要用的sso
hchen@ubuntu:~/rootfs$ ls ./lib64 ./lib/x86_64-linux-gnu/

./lib64:
ld-linux-x86-64.so.2

./lib/x86_64-linux-gnu/:
libacl.so.1      libmemusage.so         libnss_files-2.19.so    libpython3.4m.so.1
libacl.so.1.1.0  libmount.so.1          libnss_files.so.2       libpython3.4m.so.1.0
libattr.so.1     libmount.so.1.1.0      libnss_hesiod-2.19.so   libresolv-2.19.so
libblkid.so.1    libm.so.6              libnss_hesiod.so.2      libresolv.so.2
libc-2.19.so     libncurses.so.5        libnss_nis-2.19.so      libselinux.so.1
libcap.a         libncurses.so.5.9      libnss_nisplus-2.19.so  libtinfo.so.5
libcap.so        libncursesw.so.5       libnss_nisplus.so.2     libtinfo.so.5.9
libcap.so.2      libncursesw.so.5.9     libnss_nis.so.2         libutil-2.19.so
libcap.so.2.24   libnsl-2.19.so         libpcre.so.3            libutil.so.1
libc.so.6        libnsl.so.1            libprocps.so.3          libuuid.so.1
libdl-2.19.so    libnss_compat-2.19.so  libpthread-2.19.so      libz.so.1
libdl.so.2       libnss_compat.so.2     libpthread.so.0
libgpm.so.2      libnss_dns-2.19.so     libpython2.7.so.1
libm-2.19.so     libnss_dns.so.2        libpython2.7.so.1.0

// 拷贝必要的配置文件
hchen@ubuntu:~/rootfs$ ls ./etc
bash.bashrc  group  hostname  hosts  ld.so.cache  nsswitch.conf  passwd  profile  
resolv.conf  shadow

// 供挂载用的配置文件
hchen@ubuntu:~$ ls ./conf
hostname     hosts     resolv.conf

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

#define STACK_SIZE (1024 * 1024)

static char container_stack[STACK_SIZE];
char* const container_args[] = {
    "/bin/bash",
    "-l",
    NULL
};

int container_main(void* arg)
{
    printf("Container [%5d] - inside the container!\n", getpid());

    //set hostname
    sethostname("container",10);

    //remount "/proc" to make sure the "top" and "ps" show container's information
    if (mount("proc", "rootfs/proc", "proc", 0, NULL) !=0 ) {
        perror("proc");
    }
    if (mount("sysfs", "rootfs/sys", "sysfs", 0, NULL)!=0) {
        perror("sys");
    }
    if (mount("none", "rootfs/tmp", "tmpfs", 0, NULL)!=0) {
        perror("tmp");
    }
    if (mount("udev", "rootfs/dev", "devtmpfs", 0, NULL)!=0) {
        perror("dev");
    }
    if (mount("devpts", "rootfs/dev/pts", "devpts", 0, NULL)!=0) {
        perror("dev/pts");
    }
    if (mount("shm", "rootfs/dev/shm", "tmpfs", 0, NULL)!=0) {
        perror("dev/shm");
    }
    if (mount("tmpfs", "rootfs/run", "tmpfs", 0, NULL)!=0) {
        perror("run");
    }
    /* 
     * 模仿Docker的从外向容器里mount相关的配置文件 
     * 你可以查看:/var/lib/docker/containers/<container_id>/目录,
     * 你会看到docker的这些文件的。
     */
    if (mount("conf/hosts", "rootfs/etc/hosts", "none", MS_BIND, NULL)!=0 ||
          mount("conf/hostname", "rootfs/etc/hostname", "none", MS_BIND, NULL)!=0 ||
          mount("conf/resolv.conf", "rootfs/etc/resolv.conf", "none", MS_BIND, NULL)!=0 ) {
        perror("conf");
    }
    /* 模仿docker run命令中的 -v, --volume=[] 参数干的事 */
    if (mount("/tmp/t1", "rootfs/mnt", "none", MS_BIND, NULL)!=0) {
        perror("mnt");
    }

    /* chroot 隔离目录 */
    if ( chdir("./rootfs") != 0 || chroot("./") != 0 ){
        perror("chdir/chroot");
    }

    execv(container_args[0], container_args);
    perror("exec");
    printf("Something's wrong!\n");
    return 1;
}

int main()
{
    printf("Parent [%5d] - start a container!\n", getpid());
    int container_pid = clone(container_main, container_stack+STACK_SIZE, 
            CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, NULL);
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}

Когда процесс создает пространство имен монтирования, он копирует текущую файловую структуру в новое пространство имен. Все операции монтирования в новом пространстве имен влияют только на его собственную файловую систему и не будут иметь никакого влияния на внешний мир. Это очень строго реализует изоляцию, но в некоторых случаях может не применяться. Например, если процесс в пространстве имен родительского узла монтирует компакт-диск, структура каталогов, скопированная пространством имен дочернего узла, не может автоматически монтировать компакт-диск, поскольку эта операция повлияет на файловую систему родительского узла. .

Эта проблема решается распространением монтирования, представленным в 2006 г. Распространение монтирования определяет отношения между объектами монтирования, которые система использует для определения того, как события монтирования в любом объекте монтирования распространяются на другие объекты монтирования (ссылка из:Woohoo. IBM.com/developer Я…

Когда процесс создает пространство имен монтирования, он копирует текущую файловую структуру в новое пространство имен, и все операции монтирования в новом пространстве имен влияют только на его собственную файловую систему. Однако с введением функции распространения монтирования пространства имен монтирования не полностью изолированы от ресурсов.Эта функция распространения позволяет событиям монтирования между несколькими пространствами имен монтирования влиять друг на друга.

Распространение монтирования определяет взаимосвязь между монтированием объекта, монтированием системы, чтобы определить влияние события монтирования объекта на другие объекты монтирования, используя эти отношения. При этом взаимосвязь между креплением объекта описывается следующим образом:

  • Отношения совместного использования (MS_SHARED): события монтирования объекта через общий ресурс пространства имен для монтирования других объектов.
  • Ведомые отношения (MS_SLAVE): Направление распространения является односторонним, то есть оно может распространяться только от ведущего к ведомому.
  • Частные отношения (MS_PRIVATE): события монтирования разных пространств имен не влияют друг на друга (опция по умолчанию).
  • Непривязываемое отношение (MS_UNBINDABLE): непривязываемое частное монтирование, похожее на частное монтирование, но не может выполнять операции монтирования.

Статус монтирования может быть одним из следующих:

  • Общий маунт (общий)
  • Ведомое крепление (ведомое)
  • общие и подчиненные крепления
  • частная гора
  • Необязательный

image
image

Процесс монтирования завершается системным вызовом mount, который имеет два параметра: один — это имя существующего обычного файла, другой — это специальный файл, к которому можно получить прямой доступ, а третий — это имя специального файла. Этот специальный файл обычно используется для связывания некоторого тома хранилища, который может содержать свою собственную иерархию каталогов и структуру файловой системы. Эффект, достигаемый монтированием, заключается в следующем: получить доступ к корневому каталогу файловой системы на других устройствах как к общему файлу, то есть повесить корневой узел каталога на устройстве на узел страницы другой файловой системы, для достижения этого Цель расширения емкости файловой системы.

Вы можете просмотреть информацию о монтировании процесса через файловую систему /proc следующим образом:

cat /proc/$pid/mountinfo

绑定挂载Введение mount делает одним из параметров mount не обязательно специальный файл, а также общий файловый каталог в файловой системе. Использование bind mount в Linux выглядит следующим образом:

mount --bind /home/work /home/qiniu  
mount -o bind /home/work /home/qiniu

User Namespace

Чтобы сопоставить uid в контейнере с uid реальной системы, вам нужно изменить два файла /proc//uid_map и /proc//gid_map. Формат этих двух файлов:
ID-inside-ns ID-outside-ns length

  • Первое поле ID-inside-ns представляет UID или GID, отображаемый в контейнере,
  • Второе поле ID-outside-ns представляет реальный UID или GID, отображаемый вне контейнера.
  • Третье поле указывает область сопоставления, обычно заполняется 1, что указывает на однозначное соответствие.

Пространство имен пользователя в основном изолирует связанные с безопасностью идентификаторы (идентификаторы) и атрибуты (атрибуты), включая идентификатор пользователя, идентификатор группы пользователей, корневой каталог, ключ (со ссылкой на ключ) и специальные разрешения. Проще говоря, новый процесс, созданный обычным пользовательским процессом с помощью clone(), может иметь разных пользователей и группы пользователей в новом пользовательском пространстве имен. Это означает, что процесс принадлежит непривилегированному обычному пользователю вне контейнера, а процесс контейнера, который он создает, принадлежит суперпользователю со всеми правами.Эта технология предоставляет контейнеру большую свободу.
В дополнение к изоляции идентификаторов пользователей и идентификаторов групп пользователей, пространство имен пользователей также изолирует и контролирует возможности для каждого пространства имен.Вы можете управлять разрешениями процессов в новом пространстве имен, добавляя и удаляя соответствующие возможности, например, для нового пространства имен.Увеличьте CAP_CHOWN в пространстве имен, то процесс в этом пространстве имен имеет разрешение на изменение владельца файла.

  • После создания пользовательского пространства имен первому процессу предоставляются все разрешения в пространстве имен, так что процесс инициализации может выполнить всю необходимую работу по инициализации без ошибок из-за недостаточных разрешений.
  • Мы видим, что UID и GID, видимые внутри пространства имен, отличаются от тех, что снаружи.По умолчанию отображается 65534, что означает, что он еще не сопоставлен с пользователем внешнего пространства имен. Нам нужно сопоставить начального пользователя внутри пользовательского пространства имен с пользователем в его внешнем пространстве имен, чтобы, когда дело доходит до некоторых операций во внешнем пространстве имен, система могла проверять свои разрешения (например, отправка сигнала или работа с определенным файлом). . Та же группа пользователей также должна быть сопоставлена.
  • Есть еще один момент, который стоит отметить, хотя и не очевидный из вывода. Пользователь имеет полные права доступа в новом пространстве имен, но у него нет никаких разрешений в родительском пространстве имен, в котором он был создан. Это верно, даже если вызывающий и создающий процесс имеет полные разрешения. Таким образом, даже если пользователь root вызывает clone() для создания нового пользователя в пользовательском пространстве имен, у него нет никаких внешних разрешений.
  • Наконец, создание пользовательского пространства имен на самом деле представляет собой древовидную структуру со слоями вложенности. Корневой узел верхнего уровня является корневым пространством имен.Каждое вновь созданное пространство имен пользователей имеет пространство имен пользователя родительского узла и ноль или более пространств имен пользователей дочерних узлов, что очень похоже на пространство имен PID.
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <sys/capability.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

#define STACK_SIZE (1024 * 1024)

static char container_stack[STACK_SIZE];
char* const container_args[] = {
    "/bin/bash",
    NULL
};

int pipefd[2];

void set_map(char* file, int inside_id, int outside_id, int len) {
    FILE* mapfd = fopen(file, "w");
    if (NULL == mapfd) {
        perror("open file error");
        return;
    }
    fprintf(mapfd, "%d %d %d", inside_id, outside_id, len);
    fclose(mapfd);
}

void set_uid_map(pid_t pid, int inside_id, int outside_id, int len) {
    char file[256];
    sprintf(file, "/proc/%d/uid_map", pid);
    set_map(file, inside_id, outside_id, len);
}

void set_gid_map(pid_t pid, int inside_id, int outside_id, int len) {
    char file[256];
    sprintf(file, "/proc/%d/gid_map", pid);
    set_map(file, inside_id, outside_id, len);
}

int container_main(void* arg)
{

    printf("Container [%5d] - inside the container!\n", getpid());

    printf("Container: eUID = %ld;  eGID = %ld, UID=%ld, GID=%ld\n",
            (long) geteuid(), (long) getegid(), (long) getuid(), (long) getgid());

    /* 等待父进程通知后再往下执行(进程间的同步) */
    char ch;
    close(pipefd[1]);
    read(pipefd[0], &ch, 1);

    printf("Container [%5d] - setup hostname!\n", getpid());
    //set hostname
    sethostname("container",10);

    //remount "/proc" to make sure the "top" and "ps" show container's information
    mount("proc", "/proc", "proc", 0, NULL);

    execv(container_args[0], container_args);
    printf("Something's wrong!\n");
    return 1;
}

int main()
{
    const int gid=getgid(), uid=getuid();

    printf("Parent: eUID = %ld;  eGID = %ld, UID=%ld, GID=%ld\n",
            (long) geteuid(), (long) getegid(), (long) getuid(), (long) getgid());

    pipe(pipefd);

    printf("Parent [%5d] - start a container!\n", getpid());

    int container_pid = clone(container_main, container_stack+STACK_SIZE, 
            CLONE_NEWUTS  | CLONE_NEWNS | CLONE_NEWUSER | SIGCHLD, NULL);


    printf("Parent [%5d] - Container [%5d]!\n", getpid(), container_pid);

    //To map the uid/gid, 
    //   we need edit the /proc/PID/uid_map (or /proc/PID/gid_map) in parent
    //The file format is
    //   ID-inside-ns   ID-outside-ns   length
    //if no mapping, 
    //   the uid will be taken from /proc/sys/kernel/overflowuid
    //   the gid will be taken from /proc/sys/kernel/overflowgid
    set_uid_map(container_pid, 0, uid, 1);
    set_gid_map(container_pid, 0, gid, 1);

    printf("Parent [%5d] - user/group mapping done!\n", getpid());

    /* 通知子进程 */
    close(pipefd[1]);

    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}


上面的程序,我们用了一个pipe来对父子进程进行同步,为什么要这样做?因为子进程中有一个execv的系统调用,这个系统调用会把当前子进程的进程空间给全部覆盖掉,我们希望在execv之前就做好user namespace的uid/gid的映射,这样,execv运行的/bin/bash就会因为我们设置了uid为0的inside-uid而变成#号的提示符。

Network Namespace

В Linux мы обычно используем команду ip для создания сетевого пространства имен.

image
image

Как правило, физические сетевые устройства размещаются в начальном корневом пространстве имен (представляющем пространство имен системы по умолчанию, которое было упомянуто в пространстве имен PID). Но если у вас есть несколько физических сетевых адаптеров, вы также можете назначить один или несколько из них вновь созданному сетевому пространству имен. Следует отметить, что когда вновь созданное сетевое пространство имен освобождается (все внутренние процессы завершаются, а файл пространства имен не монтируется и не открывается), физическая сетевая карта в этом пространстве имен возвращается в корневое пространство имен вместо создания процесса. сетевое пространство имен, в котором находится родительский процесс.

Как должны взаимодействовать старое и новое пространства имен перед созданием пары veth? Ответ - труба. В качестве примера возьмем процесс запуска Docker Daemon контейнера dockerit. Docker Daemon отвечает за создание этой пары veth на хосте и привязывает один конец к мосту docker0 через вызовы netlink, а другой конец подключается к вновь созданному процессу сетевого пространства имен. Во время процесса создания Docker Daemon и dockerit взаимодействуют через конвейер. Прежде чем Docker Daemon завершит создание veth-пары, dockerit ждет в цикле на другом конце конвейера, пока другой конец конвейера не передаст информацию о veth устройство из Docker Daemon и закройте канал. dockerit завершает процесс ожидания и запускает свой «eth0». Весь эффект аналогичен картинке ниже.

// docker 网络本质做的事就是 1. 创建网桥  2. 创建veth 虚拟网卡,一头在docker ns1,一头插在网桥上 3. 设置ip,路由规则,nat,让docker 网络能经过bridge 出去  外部访问容器网络 也是在本地的 iptable 的 nat 表中添加相应的规则 https://yeasy.gitbooks.io/docker_practice/content/advanced_network/port_mapping.html
calico 也是类似实现,没有用bridge模式

## 首先,我们先增加一个网桥lxcbr0,模仿docker0
brctl addbr lxcbr0
brctl stp lxcbr0 off
ifconfig lxcbr0 192.168.10.1/24 up #为网桥设置IP地址

## 接下来,我们要创建一个network namespace - ns1

# 增加一个namesapce 命令为 ns1 (使用ip netns add命令)
ip netns add ns1 

# 激活namespace中的loopback,即127.0.0.1(使用ip netns exec ns1来操作ns1中的命令)
ip netns exec ns1   ip link set dev lo up 

## 然后,我们需要增加一对虚拟网卡

# 增加一个pair虚拟网卡,注意其中的veth类型,其中一个网卡要按进容器中
# VETH 设备总是成对出现,送到一端请求发送的数据总是从另一端以请求接受的形式出现。该设备不能被用户程序直接操作,但使用起来比较简单。创建并配置正确后,向其一端输入数据,VETH 会改变数据的方向并将其送入内核网络核心,完成数据的注入。在另一端能读到此数据。

ip link add veth-ns1 type veth peer name lxcbr0.1

# 把 veth-ns1 按到namespace ns1中,这样容器中就会有一个新的网卡了
ip link set veth-ns1 netns ns1

# 把容器里的 veth-ns1改名为 eth0 (容器外会冲突,容器内就不会了)
ip netns exec ns1  ip link set dev veth-ns1 name eth0 

# 为容器中的网卡分配一个IP地址,并激活它
ip netns exec ns1 ifconfig eth0 192.168.10.11/24 up


# 上面我们把veth-ns1这个网卡按到了容器中,然后我们要把lxcbr0.1添加上网桥上
brctl addif lxcbr0 lxcbr0.1

# 为容器增加一个路由规则,让容器可以访问外面的网络
ip netns exec ns1     ip route add default via 192.168.10.1

# 在/etc/netns下创建network namespce名称为ns1的目录,
# 然后为这个namespace设置resolv.conf,这样,容器内就可以访问域名了
mkdir -p /etc/netns/ns1
echo "nameserver 8.8.8.8" > /etc/netns/ns1/resolv.conf

CGroup

cgroups могут ограничивать, записывать и изолировать физические ресурсы (включая ЦП, память, ввод-вывод и т. д.), используемые группами процессов, предоставлять базовые гарантии виртуализации контейнеров и являются краеугольным камнем для создания ряда инструментов управления виртуализацией, таких как Docker.

В основном он обеспечивает следующие функции:

  • Ограничение ресурсов: ограничение использования ресурсов, таких как ограничение использования памяти и ограничение кэша файловой системы.
  • Приоритизация: управление приоритетами, например: загрузка ЦП и пропускная способность дискового ввода-вывода.
  • Бухгалтерский учет: некоторый аудит или некоторая статистика, основная цель - выставление счетов.
  • Управление: приостановить процесс и возобновить выполнение процесса.

Для разработчиков контрольные группы имеют следующие четыре интересные особенности:

  • API cgroups реализовано в виде псевдофайловой системы, то есть пользователи могут организовывать cgroups и управлять ими через файловые операции.
  • Операционная единица организации и управления cgroups может быть детализирована до уровня потока, а код пользовательского режима также может создавать и уничтожать cgroups для ресурсов, выделенных системой, чтобы реализовать перераспределение ресурсов и управление ими.
  • Все функции управления ресурсами реализованы в виде «подсистемы» с единым интерфейсом.
  • Когда создается дочерний процесс, он находится в той же контрольной группе контрольных групп, что и его родительский процесс.

По сути, контрольные группы представляют собой серию ловушек (хуков), прикрепленных к программе ядром, и запускают соответствующие ловушки посредством планирования ресурсов во время работы программы для достижения цели отслеживания и ограничения ресурсов.

период, термин

  • задача (task): В терминологии контрольных групп задача представляет собой процесс системы.
  • cgroup (контрольная группа): Управление ресурсами в cgroups реализовано в единицах cgroups. cgroup представляет собой группу задач, разделенную в соответствии с определенным стандартом управления ресурсами, включающую одну или несколько подсистем. Задача может присоединиться к контрольной группе или мигрировать из контрольной группы в другую контрольную группу.
  • Подсистема: подсистема в cgroups — это контроллер планирования ресурсов (Resource Controller). Например, подсистема ЦП может управлять распределением времени ЦП, а подсистема памяти может ограничивать использование памяти cgroup.
  • Иерархия (дерево иерархии): Иерархия состоит из ряда контрольных групп, организованных в виде древовидной структуры, и каждая иерархия выполняет планирование ресурсов, связывая соответствующую подсистему. Узел cgroup в иерархии может содержать ноль или более дочерних узлов, а дочерние узлы наследуют атрибуты родительского узла. Вся система может иметь несколько иерархий
hchen@ubuntu:~$ mount -t cgroup #或者使用lssubsys -m命令: # lscgroup 查询
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,relatime,cpuset)
cgroup on /sys/fs/cgroup/cpu type cgroup (rw,relatime,cpu)
cgroup on /sys/fs/cgroup/cpuacct type cgroup (rw,relatime,cpuacct)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,relatime,memory)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,relatime,devices)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,relatime,freezer)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,relatime,blkio)
cgroup on /sys/fs/cgroup/net_prio type cgroup (rw,net_prio)
cgroup on /sys/fs/cgroup/net_cls type cgroup (rw,net_cls)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,relatime,perf_event)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,relatime,hugetlb)

Введение в использование контрольных групп

Запрос состояния подключения cgroup и подсистемы

  • Просмотреть все cgroups: lscgroup
  • Посмотреть все поддерживаемые подсистемы: lssubsys -a
  • Посмотреть, где смонтированы все подсистемы: lssubsys –m
  • Просмотр места монтирования отдельной подсистемы (например, памяти): lssubsys –m memory

Создание иерархии и монтирование подсистем

// 虚拟机操作,会影响系统
mount -t tmpfs cgroups /sys/fs/cgroup
mkdir /sys/fs/cgroup/cg1
// mount -t cgroup -o subsystems name /cgroup/name
mount –t cgroup –o cpu,memory cpu_and_mem /sys/fs/cgroup/cg1

Лимит ЦП

root@container:~# mkdir -p  /sys/fs/cgroup/cpu/wanglei
root@container:~# cat /sys/fs/cgroup/cpu/wanglei/cpu.cfs_quota_us
-1

测试程序
int main(void)
{
    int i = 0;
    for(;;) i++;
    return 0;
}

top->
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
 6121 root      20   0    4224    684    612 R 100.0  0.0   0:05.89 a.out

开始限制,6121查到是测试程序的pid
root@container:~/testcgroup# echo 20000 > /sys/fs/cgroup/cpu/wanglei/cpu.cfs_quota_us
root@container:~/testcgroup# echo 6121 >> /sys/fs/cgroup/cpu/wanglei/tasks

top->
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
 6121 root      20   0    4224    684    612 R  20.3  0.0   2:31.16 a.out

Код ниже является примером потока

#define _GNU_SOURCE         /* See feature_test_macros(7) */

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/syscall.h>


const int NUM_THREADS = 5;

void *thread_main(void *threadid)
{
    /* 把自己加入cgroup中(syscall(SYS_gettid)为得到线程的系统tid) */
    char cmd[128];
    sprintf(cmd, "echo %ld >> /sys/fs/cgroup/cpu/haoel/tasks", syscall(SYS_gettid));
    system(cmd); 
    sprintf(cmd, "echo %ld >> /sys/fs/cgroup/cpuset/haoel/tasks", syscall(SYS_gettid));
    system(cmd);

    long tid;
    tid = (long)threadid;
    printf("Hello World! It's me, thread #%ld, pid #%ld!\n", tid, syscall(SYS_gettid));

    int a=0; 
    while(1) {
        a++;
    }
    pthread_exit(NULL);
}
int main (int argc, char *argv[])
{
    int num_threads;
    if (argc > 1){
        num_threads = atoi(argv[1]);
    }
    if (num_threads<=0 || num_threads>=100){
        num_threads = NUM_THREADS;
    }

    /* 设置CPU利用率为50% */
    mkdir("/sys/fs/cgroup/cpu/haoel", 755);
    system("echo 50000 > /sys/fs/cgroup/cpu/haoel/cpu.cfs_quota_us");

    mkdir("/sys/fs/cgroup/cpuset/haoel", 755);
    /* 限制CPU只能使用#2核和#3核 */
    system("echo \"2,3\" > /sys/fs/cgroup/cpuset/haoel/cpuset.cpus");

    pthread_t* threads = (pthread_t*) malloc (sizeof(pthread_t)*num_threads);
    int rc;
    long t;
    for(t=0; t<num_threads; t++){
        printf("In main: creating thread %ld\n", t);
        rc = pthread_create(&threads[t], NULL, thread_main, (void *)t);
        if (rc){
            printf("ERROR; return code from pthread_create() is %d\n", rc);
            exit(-1);
        }
    }

    /* Last thing that main() should do */
    pthread_exit(NULL);
    free(threads);
}

лимит использования памяти

Протестируйте программу, которой не хватает памяти, ограничьте память, и вы увидите, что программа будет убита

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{
    int size = 0;
    int chunk_size = 512;
    void *p = NULL;

    while(1) {

        if ((p = malloc(chunk_size)) == NULL) {
            printf("out of memory!!\n");
            break;
        }
        memset(p, 1, chunk_size);
        size += chunk_size;
        printf("[%d] - memory is allocated [%8d] bytes \n", getpid(), size);
        sleep(1);
    }
    return 0;
}
root@container:~/testcgroup# mkdir /sys/fs/cgroup/memory/wanglei
root@container:~/testcgroup# echo 64k > /sys/fs/cgroup/memory/wanglei/memory.limit_in_bytes
root@container:~/testcgroup# echo [pid] > /sys/fs/cgroup/memory/haoel/tasks^C

Ограничения дискового ввода-вывода

root@container:~/testcgroup# dd if=/dev/vda of=/dev/null

iotop->
  TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO>    COMMAND
15660 be/4 root       73.81 M/s    0.00 B/s  0.00 % 82.47 % dd if=/dev/vda of=/dev/null

root@container:~/testcgroup# mkdir /sys/fs/cgroup/blkio/wanglei
root@container:~/testcgroup# ls -l /dev/vda
brw-rw---- 1 root disk 253, 0 Sep 25 12:49 /dev/vda
root@container:~/testcgroup# echo "253:0 1048576" > /sys/fs/cgroup/blkio/wanglei/blkio.throttle.read_bps_device
root@container:~/testcgroup# echo 16221  > /sys/fs/cgroup/blkio/wanglei/tasks

iotop-> 限制得不是很准
  TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO>    COMMAND
16221 be/4 root      978.21 K/s    0.00 B/s  0.00 % 95.28 % dd if=/dev/vda of=/dev/null

Подсистема CGroup

  • blkio: эта подсистема может устанавливать ограничения ввода/вывода для блочных устройств, таких как физические диски (включая диски, твердотельные накопители, USB и т. д.).
  • cpu: эта подсистема использует планировщик для управления использованием ЦП задачами.
  • cpuacct: эта подсистема автоматически генерирует отчеты об использовании ресурсов ЦП задачами в контрольной группе.
  • cpuset: эта подсистема может выделять независимые процессоры (здесь для многопроцессорных систем) и память для задач в контрольной группе.
  • устройства Эта подсистема может включать или отключать доступ к устройствам задачами в контрольной группе.
  • замораживатель Эта подсистема может приостанавливать или возобновлять задачи в cgroups.
  • memory Эта подсистема может устанавливать лимит использования памяти задачами в cgroup и автоматически генерировать отчеты об использовании ресурсов памяти этими задачами.
  • Использование подсистемы perfevent позволяет проводить унифицированное тестирование производительности задач в контрольных группах. {![perf: Детектор производительности процессора Linux, см. подробностиperf.wiki.kernel.org/index.PHP/M…
  • *net_cls Эта подсистема не используется Docker напрямую, она помечает сетевые пакеты идентификатором класса (classid), что позволяет программе управления трафиком Linux (TC: Traffic Controller) идентифицировать пакеты, сгенерированные из определенных контрольных групп.

Организационная структура и основные правила

Как вы уже узнали из объяснения технологии пространства имен, традиционное управление процессами Unix фактически запускает процесс инициализации в качестве корневого узла, а затем создает дочерние процессы в качестве дочерних узлов с помощью узла инициализации, и каждый дочерний узел может создавать новые дочерние узлы. и так далее, образуя древовидную структуру. Контрольные группы также представляют собой подобные древовидные структуры, в которых дочерние узлы наследуют атрибуты от родительских узлов.

Самая большая разница между ними заключается в том, что иерархия, состоящая из контрольных групп в системе, может существовать более одной. Если модель процесса представляет собой дерево, состоящее из init в качестве корневого узла, то модель cgroups представляет собой лес, состоящий из нескольких иерархий. Цель этого тоже хорошо понятна: если иерархия всего одна, то все задачи привязываются к привязанной к ней подсистеме, что создаст проблемы для тех задач, которым эти ограничения не нужны.

После понимания организационной структуры контрольных групп давайте разберемся с отношениями между контрольной группой, задачей, подсистемой и иерархией и их основными правилами {![Ссылка на:access.RedHat.com/document ATI…

Правило 1: Одна или несколько подсистем могут быть присоединены к одной и той же иерархии. Как показано на рис. 1 ниже, подсистемы процессора и памяти присоединены к иерархии.

image
image

Рисунок 1. Одна или несколько подсистем могут быть присоединены к одной и той же иерархии

Правило 2: Подсистема может быть присоединена к нескольким иерархиям тогда и только тогда, когда эти иерархии имеют только одну подсистему. Как показано ниже на Рисунке 2, числа в маленьких кружках представляют временную последовательность подключения подсистемы.Когда подсистема ЦП подключена к иерархии A, ее нельзя подключить к иерархии B, поскольку иерархия B уже присоединила подсистему памяти. Если иерархия B находится в том же состоянии, что и иерархия A, и подсистема памяти не подключена, то подсистема ЦП может быть подключена к двум иерархиям одновременно.

image
image

Рис. 2 Подсистема, уже присоединенная к иерархии, не может быть присоединена к другой иерархии, содержащей другие подсистемы

Правило 3: Каждый раз, когда система создает новую иерархию, все задачи в системе по умолчанию образуют контрольную группу инициализации новой иерархии, которая также называется корневой контрольной группой. Для каждой созданной вами иерархии задача может существовать только в одной из контрольных групп, то есть задача не может существовать в разных контрольных группах в одной иерархии, но задача может существовать в нескольких контрольных группах в разных иерархиях. Если операция добавляет задачу в другую контрольную группу в той же иерархии, она будет удалена из первой контрольной группы. Как видно на рис. 3 ниже, процесс httpd добавлен в /cg1 в иерархии A и не может быть добавлен в /cg2 в той же иерархии, но может быть добавлен в /cg3 в иерархии B. На самом деле не разрешено присоединяться к другим группам в той же иерархии.Чтобы предотвратить конфликты, например, подсистема ЦП выделяет 30% для /cg1 и 50% для /cg2.В это время, если httpd находится в этих две cgroups, это будет Противоречия возникнут.

image
image

Рис. 3. Задача не может принадлежать разным контрольным группам одной и той же иерархии

Правило 4: Дочерняя задача, созданная самой вилкой процесса (задачи), по умолчанию находится в той же контрольной группе, что и исходная задача, но дочерняя задача может быть перемещена в другую контрольную группу. То есть после завершения форка родительский и дочерний процессы полностью независимы. Как показано на Рисунке 4 ниже, числа в маленьких кружках представляют собой хронологический порядок задач.Когда httpd просто разветвляет другой httpd, он находится в той же контрольной группе в той же иерархии. Но тогда, если httpd с PID 4840 нужно переместить в другую cgroup, это тоже возможно, потому что родительская и дочерняя задачи уже независимы. Подводя итог: дочерняя задача находится в той же контрольной группе, что и родительская задача при инициализации, но это отношение может измениться позже.

image
image

Рис. 4. Только что разветвленный дочерний процесс находится в той же контрольной группе, что и его родительский процесс в начальном состоянии

Пополнить

systemd

kuberlet имеет документацию systemd, в которой говорится следующее:
This document describes how the node should be configured, and a set of enhancements that should be made to the kubelet to better integrate with these distributions independent of container runtime.

The Kernel direction for cgroup management is to promote a single-writer model rather than allowing multiple processes to independently write to parts of the file-system.In distributions that run systemd as their init system, the cgroup tree is managed by systemd by default since it implicitly interacts with the cgroup tree when starting units. Manual changes made by other cgroup managers to the cgroup tree are not guaranteed to be preserved unless systemd is made aware. systemd can be told to ignore sections of the cgroup tree by configuring the unit to have the Delegate= option.

Значит, для управления cgroups на linux рекомендуется использовать systemd?И это можно сделать не полагаясь на докер?

sysctl

В дополнение к cgroups для ограничения ресурсов, есть также команда sysctl, связанная с ограничениями ресурсов на уровне системы.
Команда sysctl используется для динамического изменения рабочих параметров ядра во время его работы Доступные параметры ядра находятся в каталоге /proc/sys. Он включает в себя некоторые дополнительные параметры для стека TCP/IP и системы виртуальной памяти, которые позволяют опытным администраторам значительно повысить производительность системы. С помощью sysctl можно прочитать и установить более 500 системных переменных.
Parameters are available via /proc/sys/ virtual process file system. The parameters cover various subsystems such as:

  • kernel (common prefix: kernel.)
  • networking (common prefix: net.)
  • virtual memory (common prefix: vm.)
  • MDADM (common prefix: dev.)

Можно установить привилегию Docker, но некоторые параметры являются системными, а не изолированными, и изменения повлияют на другие контейнеры. В более поздних версиях docker были введены ограничения, и можно было изменить только некоторые sysctl из белого списка.
Only namespaced kernel parameters can be modified
Настройки в к8сGitHub.com/Это так особенно/…