#云原生征文#Docker使用Linux内核的技术分析 原创 精华
@[toc](目录
一、Docker简介
Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从 Apache2.0 协议开源。
Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。
容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app),更重要的是容器性能开销极低。
Docker 包括三个基本概念:
镜像(Image):Docker 镜像(Image),就相当于是一个 root 文件系统。
容器(Container):镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
仓库(Repository):仓库可看成一个代码控制中心,用来保存镜像。
Docker 使用客户端-服务器 (C/S) 架构模式,使用远程API来管理和创建Docker容器。
二、什么是容器
在Linux中,容器技术是一种进程隔离的技术,应用可以运行在一个个相互隔离的容器中,与虚拟机相同的是,可以为这些容器设置计算资源限制,挂载存储,连接网络,而与虚拟机不同的是,这些应用运行时共用着一个Kernel。使用了Linux kernel的Namespaces、Cgroups、Selinux等技术实现。
三、什么是Docker
Docker实际上是一家公司,Docker是公司的一个容器管理产品。
Docker的使用是安装一个Docker引擎,安装完Docker引擎之后,就可以在容器中配置应用运行所需的依赖环境,接着安装App就可以运行起来了运行起来了。
四、Docker的基本原理
Docker是一个开源的软件项目,让用户程序部署在一个相对隔离的环境运行,借此在Linux操作系统上提供一层额外的抽象,以及操作系统层虚拟化的自动管理机制。需要额外指出的是,Docker并不等于容器(containers),Docker只是容器的一种,其他的种类的容器还有Kata container,Rocket container等等
Docker利用Linux中的核心分离机制,例如Cgroups,以及Linux的核心Namespace(名字空间)来创建独立的容器。一句话概括起来Docker就是利用Namespace做资源隔离,用Cgroup做资源限制,利用Union FS做容器文件系统的轻量级虚拟化技术。Docker容器的本质还是一个直接运行在宿主机上面的特殊进程,看到的文件系统是隔离后的,但是操作系统内核是共享宿主机OS,所以说Docker是轻量级的虚拟化技术。
五、Linux Namespaces
Linux Namespace 是Linux 提供的一种内核级别环境隔离的方法,使其中的进程好像拥有独立的操作系统环境。Linux Namespace 有 Mount Namespace,UTS Namespace, IPC Namespace, PID Namespace, Network Namespace, User Namespace, Cgroup Namespace。
分类 | 系统调用参数 | 隔离内容 |
---|---|---|
Mount Namespace | CLONE_NEWNS | 文件系统挂载点 |
UTS Namespace | CLONE_NEWUTS | Hostname和domain name |
IPC Namespace | CLONE_NEWIPC | 进程间通信方式,例如消息队列 |
PID Namespace | CLONE_NEWPID | 进程ID编号 |
Network Namespace | CLONE_NEWNET | 网络设备,协议栈,路由表,防火墙规则,端口等 |
User Namespace | CLONE_NEWUSER | 用户及组ID |
Cgroup Namespace | CLONE_NEWCGROUP | Cgroup根目录 |
上述系统调用参数CLONE_NEWNS等主要应用于以下三个系统调用: |
1.clone
创建新进程并设置它的Namespace,类似于fork系统调用,可创建新进程并且指定子进程将要执行的函数,通过上述CLONE_NEWNS等参数使某类资源处于隔离状态。
函数声明 :
#include <sched.h>
int clone(int (*fn)(void *), void *child_stack,
int flags, void arg, …
/ pid_t *ptid, struct user_desc *tls, pid_t *ctid */ );
例如:
int pid = clone(call_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL);
会让新创建的该进程执行call_function,例如/bin/bash,且该进程的PID进程编号是隔离状态,也就是新的PID编号,该进程ps将会看到它的PID是1。
如果多次执行上述clone就会创建多个PID Namespace,而每个Namespace里面的应用进程都认为自己是当前容器里的1号进程,它们既看不到宿主机里的真实进程空间,也看不到其他PID Namespace里面的具体情况。
#define _GNU_SOURCE
#include <sys/wait.h>
#include <sys/utsname.h>
#include <sched.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define CLONE_FAILURE -1
#define CLONE_SUCCESS 0
#define ERR_RETURN(msg, ret_val) \
do { \
perror(msg); return ret_val; \
} while (0)
static int childFunc(void *arg)
{
struct utsname uts;
if (sethostname(arg, strlen(arg)) == -1) {
ERR_RETURN("sethostname", CLONE_FAILURE);
}
if (uname(&uts) == -1) {
ERR_RETURN("uname", CLONE_FAILURE);
}
printf("uts.nodename in child: %s\n", uts.nodename);
/* Keep the namespace open for a while, by sleeping.
This allows some experimentation--for example, another
process might join the namespace. */
sleep(2);
return 0; /* Child terminates now */
}
#define STACK_SIZE (1024 * 1024) /* Stack size for cloned child */
int main(int argc, char *argv[])
{
char *stack; /* Start of stack buffer */
char *stackTop; /* End of stack buffer */
pid_t pid;
struct utsname uts;
if (argc < 2) {
fprintf(stderr, "Usage: %s <child-hostname>\n", argv[0]);
return CLONE_FAILURE;
}
/* Allocate stack for child */
stack = malloc(STACK_SIZE);
if (stack == NULL) {
ERR_RETURN("malloc", CLONE_FAILURE);
}
stackTop = stack + STACK_SIZE; /* Assume stack grows downward */
/* Create child that has its own UTS namespace;
child commences execution in childFunc() */
// 注意第3个参数
pid = clone(childFunc, stackTop, CLONE_NEWUTS | SIGCHLD, argv[1]);
if (pid == -1) {
ERR_RETURN("clone", CLONE_FAILURE);
}
printf("clone() returned %ld\n", (long) pid);
/* Parent falls through to here */
sleep(1); /* Give child time to change its hostname */
/* Display hostname in parent's UTS namespace. This will be
different from hostname in child's UTS namespace. */
if (uname(&uts) == -1) {
ERR_RETURN("uname", CLONE_FAILURE);
}
printf("uts.nodename in parent: %s\n", uts.nodename);
if (waitpid(pid, NULL, 0) == -1) { /* Wait for child */
ERR_RETURN("waitpid", CLONE_FAILURE);
}
printf("child has terminated\n");
return CLONE_FAILURE;
}
代码编译运行结果:
test$ gcc clone.c
test$ sudo ./a.out hello
clone() returned 45468
uts.nodename in child: hello
uts.nodename in parent: Box
child has terminated
2.unshare
int unshare(int flags) 使进程脱离某个Namespace,flags参数和clone的用法一致。
3.setns
int setns(int fd, int nstype) 使进程进入某个已经存在的Namespace。经常用于从宿主机进入已经启动的容器Network Namespace,然后设置它的网络。
六、Linux Cgroups
Docker 容器运行起来是一个直接运行在宿主机上面的进程,那么如果限定每个容器最多消耗多少CPU资源呢?如果一个容器疯狂的消耗资源岂不是会影响同一宿主机上面其他的容器?所以Docker就需要一个限制容器能够使用资源上限的机制,那就是Linux Cgroup技术。Linux Cgroup 全称是Linux Control Group。它最主要的作用是限制一个进程组能够使用的资源上限,包括CPU,MEM,DISK,NET等等。
1.编写一个计算密集型的程序:
#include "stdio.h"
#include "stdlib.h"
#include "time.h"
int main(int argc, char *argv[]){
long long i = 0;
clock_t start_time;
clock_t finish_time;
clock_t real_time;
float current_time;
float program_time;
start_time=clock();
current_time=(float)start_time / CLOCKS_PER_SEC;
printf("Program start time: %f\n",current_time);
for(i = 0;i < 1024 * 1024 * 16;i++){
real_time=clock();
current_time=((float)real_time / CLOCKS_PER_SEC);
}
finish_time=clock();
current_time=(float)finish_time/CLOCKS_PER_SEC;
printf("Program finish time:%f\n",current_time);
program_time=(float)(finish_time-start_time)/CLOCKS_PER_SEC;
printf("Program complete time: %f\n",program_time);
return 0;
}
2.使用cgroups之前
编译运行程序:
test$ gcc main.c -o main
test$ ./main &
top命令查看:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
45561 weimin 20 0 2496 576 512 R 100.0 0.0 3:06.87 main
CPU占有率100%
程序运行时间:Program complete time: 3.908352
3.使用cgroup之后
test$ sudo mkdir /sys/fs/cgroup/cpu/hello_cpu
创建后,系统会自动生成一堆文件:
test$ ll /sys/fs/cgroup/cpu/hello_cpu/
total 0
drwxr-xr-x 2 root root 0 5月 20 10:27 ./
dr-xr-xr-x 3 root root 0 5月 16 08:05 ../
-rw-r--r-- 1 root root 0 5月 20 10:27 cgroup.clone_children
-rw-r--r-- 1 root root 0 5月 20 10:27 cgroup.procs
-r--r--r-- 1 root root 0 5月 20 10:27 cpuacct.stat
-rw-r--r-- 1 root root 0 5月 20 10:27 cpuacct.usage
-r--r--r-- 1 root root 0 5月 20 10:27 cpuacct.usage_all
-r--r--r-- 1 root root 0 5月 20 10:27 cpuacct.usage_percpu
-r--r--r-- 1 root root 0 5月 20 10:27 cpuacct.usage_percpu_sys
-r--r--r-- 1 root root 0 5月 20 10:27 cpuacct.usage_percpu_user
-r--r--r-- 1 root root 0 5月 20 10:27 cpuacct.usage_sys
-r--r--r-- 1 root root 0 5月 20 10:27 cpuacct.usage_user
-rw-r--r-- 1 root root 0 5月 20 10:27 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 5月 20 10:27 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 5月 20 10:27 cpu.shares
-r--r--r-- 1 root root 0 5月 20 10:27 cpu.stat
-rw-r--r-- 1 root root 0 5月 20 10:27 cpu.uclamp.max
-rw-r--r-- 1 root root 0 5月 20 10:27 cpu.uclamp.min
-rw-r--r-- 1 root root 0 5月 20 10:27 notify_on_release
-rw-r--r-- 1 root root 0 5月 20 10:27 tasks
配置cgroup子系统,设置只能使用10%的CPU时间片。
修改cup.cfs_period_us, cpu.cfs_quota_us两个文件。
hello_cpu$ sudo chmod 777 *
hello_cpu$ sudo echo 100000 > cpu.cfs_period_us
hello_cpu$ sudo echo 10000 > cpu.cfs_quota_us
ubuntu安装Cgroup工具
sudo apt install cgroup-tools
使用cgroup工具运行程序
test$ cgexec -g cpu:hello_cpu ./main &
top命令查看cpu占有率平均在10%左右:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
47221 weimin 20 0 2496 516 448 R 10.3 0.0 0:02.32 main
运行时间:Program complete time: 10.011074 限制CPU占有率之后,程序的运行时间明显增长。
七、Linux Selinux
SELinux(security enhanced linux)安全增强型Linux系统,它是一个linux内核模块,也是Linux的一个安全子系统。
Selinux的主要作用就是最大限度地减小系统中服务进程可访问的资源(最小权限原则)
【本文正在参加云原生有奖征文活动】,活动链接:https://ost.51cto.com/posts/12598
666
厉害了,好像对docker的原理有那么点理解了