參考 php
Linux Namespace是Linux提供的一種內核級別環境隔離的方法。
提供了對UTS、IPC、mount、PID、network、User等的隔離機制。html
分類 | 系統調用參數 | 相關內核版本 | 隔離內容 | |
---|---|---|---|---|
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 | 信號量、消息隊列和共享內存, inter-process communication,有全局id | |
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() | 使某進程脫離某個namespace |
setns() | 把某進程加入到某個namespace |
測試代碼node
#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;
}複製代碼
加入
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複製代碼
// 若是隔離了 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複製代碼
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.phusion.nl/2015/01/20/…python
➜ 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 Namespace的處理不太相同,當unshare PID namespace時,調用進程會爲它的子進程分配一個新的PID Namespace,可是調用進程自己不會被移到新的Namespace中。並且調用進程第一個建立的子進程在新Namespace中的PID爲1,併成爲新Namespace中的init進程。爲何建立其餘的Namespace時unshare()和setns()會直接進入新的Namespace,而惟獨PID Namespace不是如此呢?由於調用getpid()函數獲得的PID是根據調用者所在的PID Namespace而決定返回哪一個PID,進入新的PID namespace會致使PID產生變化。而對用戶態的程序和庫函數來講,他們都認爲進程的PID是一個常量,PID的變化會引發這些進程奔潰。換句話說,一旦程序進程建立之後,那麼它的PID namespace的關係就肯定下來了,進程不會變動他們對應的PID namespace。linux
#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複製代碼
關於mount命令git
模仿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; }複製代碼
進程在建立mount namespace時,會把當前的文件結構複製給新的namespace。新namespace中的全部mount操做都隻影響自身的文件系統,而對外界不會產生任何影響。這樣作很是嚴格地實現了隔離,可是某些狀況可能並不適用。好比父節點namespace中的進程掛載了一張CD-ROM,這時子節點namespace拷貝的目錄結構就沒法自動掛載上這張CD-ROM,由於這種操做會影響到父節點的文件系統。github
2006 年引入的掛載傳播(mount propagation)解決了這個問題,掛載傳播定義了掛載對象(mount object)之間的關係,系統用這些關係決定任何掛載對象中的掛載事件如何傳播到其餘掛載對象(參考自:www.ibm.com/developerwo…docker
進程在建立Mount Namespace時,會把當前的文件結構複製給新的Namespace,新的Namespace中的全部mount操做僅影響自身的文件系統。但隨着引入掛載傳播的特性,Mount Namespace變得並非徹底意義上的資源隔離,這種傳播特性使得多Mount Namespace之間的掛載事件能夠相互影響。shell
掛載傳播定義了掛載對象之間的關係,系統利用這些關係來決定掛載對象中的掛載事件對其餘掛載對象的影響。其中掛載對象之間的關係描述以下:ubuntu
一個掛載狀態可能爲以下的其中一種:
掛載的過程是經過mount系統調用完成的,它有兩個參數:一個是已存在的普通文件名,一個是能夠直接訪問的特殊文件,一個是特殊文件的名字。這個特殊文件通常用來關聯一些存儲卷,這個存儲卷能夠包含本身的目錄層級和文件系統結構。mount所達到的效果是:像訪問一個普通的文件同樣訪問位於其餘設備上文件系統的根目錄,也就是將該設備上目錄的根節點掛到了另一個文件系統的頁節點上,達到了給這個文件系統擴充容量的目的。
能夠經過/proc文件系統查看一個進程的掛載信息,具體作法以下:
cat /proc/$pid/mountinfo複製代碼
綁定掛載
的引入使得mount的其中一個參數不必定要是一個特殊文件,也能夠是該文件系統上的一個普通文件目錄。Linux中綁定掛載的用法以下:
mount --bind /home/work /home/qiniu
mount -o bind /home/work /home/qiniu複製代碼
要把容器中的uid和真實系統的uid給映射在一塊兒,須要修改 /proc//uid_map 和 /proc//gid_map 這兩個文件。這兩個文件的格式爲:ID-inside-ns ID-outside-ns length
User namespace主要隔離了安全相關的標識符(identifiers)和屬性(attributes),包括用戶ID、用戶組ID、root目錄、key(指密鑰)以及特殊權限。說得通俗一點,一個普通用戶的進程經過clone()建立的新進程在新user namespace中能夠擁有不一樣的用戶和用戶組。這意味着一個進程在容器外屬於一個沒有特權的普通用戶,可是他建立的容器進程卻屬於擁有全部權限的超級用戶,這個技術爲容器提供了極大的自由。
User Namespace除了隔離用戶ID和用戶組ID以外,還對每一個Namespace進行了Capability的隔離和控制,能夠經過添加和刪除相應的Capability來控制新Namespace中進程所擁有的權限,好比爲新的Namespace中增長CAP_CHOWN權限,那麼在這個Namespace的進程擁有改變文件屬主的權限。
#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而變成#號的提示符。複製代碼
在Linux下,咱們通常用ip命令建立Network Namespace
通常狀況下,物理網絡設備都分配在最初的root namespace(表示系統默認的namespace,在PID namespace中已經說起)中。可是若是你有多塊物理網卡,也能夠把其中一塊或多塊分配給新建立的network namespace。須要注意的是,當新建立的network namespace被釋放時(全部內部的進程都終止而且namespace文件沒有被掛載或打開),在這個namespace中的物理網卡會返回到root namespace而非建立該進程的父進程所在的network namespace。
在創建起veth pair以前,新舊namespace該如何通訊呢?答案是pipe(管道)。咱們以Docker Daemon在啓動容器dockerinit的過程爲例。Docker Daemon在宿主機上負責建立這個veth pair,經過netlink調用,把一端綁定到docker0網橋上,一端連進新建的network namespace進程中。創建的過程當中,Docker Daemon和dockerinit就經過pipe進行通訊,當Docker Daemon完成veth-pair的建立以前,dockerinit在管道的另外一端循環等待,直到管道另外一端傳來Docker Daemon關於veth設備的信息,並關閉管道。dockerinit才結束等待的過程,並把它的「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複製代碼
cgroups能夠限制、記錄、隔離進程組所使用的物理資源(包括:CPU、memory、IO等),爲容器實現虛擬化提供了基本保證,是構建Docker等一系列虛擬化管理工具的基石。
主要提供了以下功能:
對開發者來講,cgroups有以下四個有趣的特色:
本質上來講,cgroups是內核附加在程序上的一系列鉤子(hooks),經過程序運行時對資源的調度觸發相應的鉤子以達到資源追蹤和限制的目的。
術語
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)複製代碼
// 虛擬機操做,會影響系統
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);
}複製代碼
測試一個耗盡內存的程序,限制內存,能夠看到程序會被kill
#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複製代碼
你們在namespace技術的講解中已經瞭解到,傳統的Unix進程管理,其實是先啓動init進程做爲根節點,再由init節點建立子進程做爲子節點,而每一個子節點由能夠建立新的子節點,如此往復,造成一個樹狀結構。而cgroups也是相似的樹狀結構,子節點都從父節點繼承屬性。
它們最大的不一樣在於,系統中cgroup構成的hierarchy能夠容許存在多個。若是進程模型是由init做爲根節點構成的一棵樹的話,那麼cgroups的模型則是由多個hierarchy構成的森林。這樣作的目的也很好理解,若是隻有一個hierarchy,那麼全部的task都要受到綁定其上的subsystem的限制,會給那些不須要這些限制的task形成麻煩。
瞭解了cgroups的組織結構,咱們再來了解cgroup、task、subsystem以及hierarchy四者間的相互關係及其基本規則{![參照自:access.redhat.com/documentati…
規則1: 同一個hierarchy能夠附加一個或多個subsystem。以下圖1,cpu和memory的subsystem附加到了一個hierarchy。
圖1 同一個hierarchy能夠附加一個或多個subsystem
規則2: 一個subsystem能夠附加到多個hierarchy,當且僅當這些hierarchy只有這惟一一個subsystem。以下圖2,小圈中的數字表示subsystem附加的時間順序,CPU subsystem附加到hierarchy A的同時不能再附加到hierarchy B,由於hierarchy B已經附加了memory subsystem。若是hierarchy B與hierarchy A狀態相同,沒有附加過memory subsystem,那麼CPU subsystem同時附加到兩個hierarchy是能夠的。
規則3: 系統每次新建一個hierarchy時,該系統上的全部task默認構成了這個新建的hierarchy的初始化cgroup,這個cgroup也稱爲root cgroup。對於你建立的每一個hierarchy,task只能存在於其中一個cgroup中,即一個task不能存在於同一個hierarchy的不一樣cgroup中,可是一個task能夠存在在不一樣hierarchy中的多個cgroup中。若是操做時把一個task添加到同一個hierarchy中的另外一個cgroup中,則會從第一個cgroup中移除。在下圖3中能夠看到,httpd進程已經加入到hierarchy A中的/cg1而不能加入同一個hierarchy中的/cg2,可是能夠加入hierarchy B中的/cg3。實際上不容許加入同一個hierarchy中的其餘cgroup野生爲了防止出現矛盾,如CPU subsystem爲/cg1分配了30%,而爲/cg2分配了50%,此時若是httpd在這兩個cgroup中,就會出現矛盾。
規則4: 進程(task)在fork自身時建立的子任務(child task)默認與原task在同一個cgroup中,可是child task容許被移動到不一樣的cgroup中。即fork完成後,父子進程間是徹底獨立的。以下圖4中,小圈中的數字表示task 出現的時間順序,當httpd剛fork出另外一個httpd時,在同一個hierarchy中的同一個cgroup中。可是隨後若是PID爲4840的httpd須要移動到其餘cgroup也是能夠的,由於父子任務間已經獨立。總結起來就是:初始化時子任務與父任務在同一個cgroup,可是這種關係隨後能夠改變。
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.
是說再linux上就推薦用systemd來管理cgroup?並且這樣還能不依賴docker?
除了cgroup作資源限制,對於系統級別的資源限制相關的還有一個sysctl命令
sysctl命令被用於在內核運行時動態地修改內核的運行參數,可用的內核參數在目錄/proc/sys中。它包含一些TCP/ip堆棧和虛擬內存系統的高級選項, 這可讓有經驗的管理員提升引人注目的系統性能。用sysctl能夠讀取設置超過五百個系統變量。
Parameters are available via /proc/sys/ virtual process file system. The parameters cover various subsystems such as:
docker privileged能夠設置,可是有些參數是系統級別的,沒有隔離,改了會影響別的容器。後來版本docker作了限制,只能改一些whitelisted sysctls。
Only namespaced kernel parameters can be modified
k8s裏面的設置github.com/kubernetes/…