如何實現自己的linux container?

dy223 9年前發布 | 32K 次閱讀 Docker Linux

最近docker比較火爆, 我也研究了一下。docker 只是一個工具,container 技術的核心還是linux 內核的cgroup + chroot + namespace 技術。

本文主要講解利用這三個技術實現自己的container。

chroot  沒什么好說的,就是將根目錄設置成另外一個目錄。

namespace  有以下幾種:

如何實現自己的linux container? 

這幾個flag 可以在調用clone時候作為參數傳入,從而實現namespace的隔離,

從這個角度來說,container跟主要是進程角度的隔離,而不是傳統的虛擬機,

因為它底層用用的同一個內核來調度。

cgroup 是linux 內核的另外一個控制和隔離進程的特性,他分為cpu ,memory,net,io等幾個子系統,從而實現對進程cpu,內存,磁盤,網絡等資源使用的控制。


制作自己容器,需要一個image ,可以從網上下一個,也可以自己制作,制作很簡單,新裝一個操作系統,安裝一些需要用到的軟件包,然后用tar 制作 / 目錄下的壓縮包,去掉一些虛擬文件系統的文件,本文用的是自己制作的centos 6.5 的image。


容器實現過程可以歸納為

1,  用clone系統調用 創建子進程,傳入namespace的那幾個參數,實現namespace的隔離

2,  父進程中創建veth pair ,一個veth在自己的namespace,將另一個設置為子進程的namespace,實現container和宿主機的網絡通信

3,  父進程創建cgroup memory和cpuset子系統,將子進程attach到cgroup子系統上,實現container 的資源限制和隔離

4, 子進程在自己的namespace里,設置主機名,mount proc虛擬文件系統,設置veth ip,chroot到centos 6鏡像的位置, 最終將進程鏡像替換成/bin/bash

5, 父進程調用waitpid 等待子進程退出


最終代碼見最后

編譯代碼 gcc -lcgroup mydocker.c -o mydocker

我們來驗證一下結果

如何實現自己的linux container?

可以看到宿主機用的是centos 7的操作系統, 進入到container里面,宿主機用的是centos6,但用的都是3.10 的內核,根目錄下的文件也不同。 hostname 宿主機為

localhost,而container里面為mydocker , 說明UTS namespace 隔離成功。

再看看 container 里面:

如何實現自己的linux container?    網絡方面有回環網絡卡lo和veth1,veth1 169.254.1.2 能ping 通veth0的地址(宿主機上的veth)169.254.2.1,如果在外面加iptables 做nat 轉換的話,container里面還可以和外面通信。我們看不到外面宿主機的eth0 和 eth1,說明container 的network namespace 隔離成功。

目前container 里面只有/bin/bash , 且進程號為 1,不是我們常見的init進程,或者systemd 。因為/bin/bash 為該namespace 下的第一個進程,說明我們的pid namespace隔離成功。

如何實現自己的linux container? 

mount 自由一些基本的文件系統,和宿主機的不一樣,說明mount namespace隔離成功。

我們在來看看cgroup的隔離情況,

從cgroup文件系統來看,memory限制的是我們設置的512M,cpu使用0-1號,

如何實現自己的linux container?我們來實際測試一下:  如何實現自己的linux container? 可以看出,只有0號和1號cpu idle為0 ,其他的都接近100%,說明cgroup隔離效果是很好的。 如何實現自己的linux container?

我們的容器基本上是完成了。不可否認docker 是很好的工具,但這一切還是要歸功于linux強大的內核。本文若有錯誤之處,請指出。


</div>


完整代碼:

#define _GNU_SOURCE

include <sys/types.h>

include <sys/wait.h>

include <stdio.h>

include <sched.h>

include <signal.h>

include <unistd.h>

include <errno.h>

include <stdlib.h>

include <sys/mount.h>

include <libcgroup.h>

include <time.h>

include <signal.h>

define STACK_SIZE (1024 * 1024)

define MEMORY_LIMIT (51210241024)

const char rootfs = "/data1/centos6/rootfs/"; //centos6 鏡像位置 const char hostname = "mydocker"; //container 主機名 static char child_stack[STACK_SIZE]; char const child_args[] = { "/bin/bash", NULL }; int pipe_fd【2】; //父子進程同步 int child_main(void args) { char c; printf("In child process(container)\n"); chroot(rootfs); //用chroot 切換根目錄 if(errno != 0){ perror("chroot()"); exit(1); } //clone 調用中的 CLONE_NEWUTS起隔離主機名和域名的作用 sethostname(hostname, sizeof(hostname)); if( errno != 0 ){ perror("sethostname()!"); exit(1); } //掛載proc子系統,CLONE_NEWNS 起隔離文件系統作用 mount("proc", "/proc", "proc", 0, NULL); if (errno != 0){ perror("Mount(proc)"); exit(1); } //切換的根目錄 chdir("/"); close(pipe_fd【1】); read(pipe_fd【0】, &c, 1); //設置veth1 網絡 system("ip link set lo up"); system("ip link set veth1 up"); system("ip addr add 169.254.1.2/30 dev veth1"); //將子進程的鏡像替換成bash execv(child_args[0], child_args); return 1; } struct cgroup cgroup_control(pid_t pid){ struct cgroup cgroup = NULL; int ret; ret = cgroup_init(); char cgname = malloc(19sizeof(char)); if (ret) { printf("error occurs while init cgroup.\n"); return NULL; } time_t nowtime = time(NULL); sprintf(cgname, "mydocker%d", (int)now_time); printf("%s\n", cgname); cgroup = cgroup_new_cgroup(cgname); if( !cgroup ){ ret = ECGFAIL; printf("Error new cgroup%s\n", cgroup_strerror(ret)); goto out; } //添加cgroup memory 和 cpuset子系統 struct cgroup_controller cgc = cgroup_add_controller(cgroup, "memory"); struct cgroup_controller cgc_cpuset = cgroup_add_controller(cgroup, "cpuset"); if ( !cgc || !cgc_cpuset ){ ret = ECGINVAL; printf("Error add controller %s\n", cgroup_strerror(ret)); goto out; } // 內存限制 512M if( cgroup_add_value_uint64(cgc, "memory.limit_in_bytes", MEMORY_LIMIT) ){ printf("Error limit memory.\n"); goto out; } //限制只能使用0和1號cpu if ( cgroup_add_value_string(cgc_cpuset, "cpuset.cpus", "0-1") ){ printf("Error limit cpuset cpus.\n"); goto out; } //限制只能使用0和1塊內存 if ( cgroup_add_value_string(cgc_cpuset, "cpuset.mems", "0-1") ){ printf("Error limit cpuset mems.\n"); goto out; } ret = cgroup_create_cgroup(cgroup, 0); if (ret){ printf("Error create cgroup%s\n", cgroup_strerror(ret)); goto out; } ret = cgroup_attach_task_pid(cgroup, pid); if (ret){ printf("Error attach_task_pid %s\n", cgroup_strerror(ret)); goto out; } return cgroup; out: if (cgroup){ cgroup_delete_cgroup(cgroup, 0); cgroup_free(&cgroup); } return NULL; } int main() { char cmd; printf("main process: \n"); pipe(pipe_fd); if( errno != 0){
perror("pipe()"); exit(1); } int child_pid = clone(child_main, child_stack + STACK_SIZE, \ CLONE_NEWNET | CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWIPC | CLONE_NEWUTS | SIGCHLD, NULL); struct cgroup
cg = cgroup_control(child_pid); //添加veth pair ,設置veth1 namespace 為子進程的,veth0 在父進程的namespace //linl3 實現起來太繁瑣,借用命令行工具ip 實現 system("ip link add veth0 type veth peer name veth1"); asprintf(&cmd, "ip link set veth1 netns %d", child_pid); system(cmd); system("ip link set veth0 up"); system("ip addr add 169.254.1.1/30 dev veth0"); free(cmd); //等執行以上命令,通知子進程,子進程設置自己的網絡 close(pipe_fd【1】); waitpid(child_pid, NULL, 0); if (cg) { cgroup_delete_cgroup(cg, 0); //刪除cgroup 子系統 } printf("child process exited.\n"); return 0; }</pre>來自:http://weibo.com/p/1001603824282965777334

 本文由用戶 dy223 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!