前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >容器的本质

容器的本质

作者头像
编程黑洞
发布2023-03-06 18:56:26
2740
发布2023-03-06 18:56:26
举报
文章被收录于专栏:编程黑洞

# 前言

使用NameSpace技术来修改进程视图,创建出独立的文件系统、主机名、进程号、网络等资源空间,再使用Cgroups来实现对进程的 CPU、内存等资源的优先级和配额限制,最后使用chroot更改进程的根目录,也就是限制访问文件系统

# NameSpace

可以创建出独立的文件系统、主机名、进程号、网络等资源空间,实现系统全局资源和进程局部资源的隔离。

NameSpace有多种隔离类型,像常见的有PID NameSpace可以隔离进程ID、NET Namespace隔离网络设备端口号等。

举个例子

NameSpace可以让当前进程只能看到当前Namespace里的进程,看不到宿主机创建的进程。并且运行容器的命令为1号进程。

代码语言:javascript
复制
# docker run -it busybox /bin/sh

/ # ps aux
PID   USER     COMMAND
    1 root     /bin/sh
    8 root     ps aux

/ # echo $$
1

在宿主机中可以看到该容器进程ID并不为1。再次证明容器也只是宿主机中的一个进程而已。

代码语言:javascript
复制
[root@k8s-master ~]# ps aux | grep /bin/sh | grep -v grep
root     1398061  0.1  0.3 1368728 54104 pts/1   Sl+  10:36   0:00 docker run -it mirrors.sangfor.com/busybox /bin/sh
root     1398102  0.8  0.0   3176   188 pts/0    Ss+  10:36   0:00 /bin/sh

# chroot

可以更改进程的根目录,限制访问文件系统。

# 手动构造一个容器

我们使用clone创建一个子进程,传入的参数是CLONE_NEWPID代表着启用了PID NameSpace,当前进程看到的是一个全新的进程空间,在该命名空间中,自己是1号进程。

代码语言:javascript
复制
#define _GNU_SOURCE
#include <sys/mount.h> 
#include <sys/types.h>
#include <sys/wait.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 container_main(void* arg)
{  
  printf("Container - inside the container!\n");
  execv(container_args[0], container_args);
  printf("Something's wrong!\n");
  return 1;
}

int main()
{
  printf("Parent - start a container!\n");
  int container_pid = clone(container_main, container_stack+STACK_SIZE, CLONE_NEWPID | SIGCHLD , NULL);
  waitpid(container_pid, NULL, 0);
  printf("Parent - container stopped!\n");
  return 0;
}
代码语言:javascript
复制
[root@k8s-master k8s]# gcc a.c -o a

[root@k8s-master k8s]# ./a 
Parent - start a container!
Container - inside the container!

[root@k8s-master k8s]# echo $$
1

[root@k8s-master k8s]# exit
Parent - container stopped!

但是我们在使用ps aux时,还是看到整个宿主机的进程,并且进程ID为1的还是Systemd,为什么呢?

这是因为ps命令是读/proc文件系统的,所以我们还需要进行文件系统的隔离。

我们再使用Mount NameSpace进行文件系统的隔离,在clone中使用CLONE_NEWNS参数。

代码语言:javascript
复制
int main()
{
  printf("Parent - start a container!\n");
  int container_pid = clone(container_main, container_stack+STACK_SIZE, CLONE_NEWPID | CLONE_NEWNS  | SIGCHLD , NULL);
  waitpid(container_pid, NULL, 0);
  printf("Parent - container stopped!\n");
  return 0;
}

编译运行后,发现当前文件系统并未发生变化,这个是因为,创建子进程时,会继承父进程的挂载点。

所以我们需要在子进程中修改当前的挂载点,并且子进程在新的namespace的挂载动作只影响自身的挂载文件系统。

先拷贝一个文件系统出来作为我们容器的根文件系统

代码语言:javascript
复制
docker export 48ab2ddd04dc | tar -C ./testfs -xvf -

再挂载proc,并且将testfs作为该进程的根目录

代码语言:javascript
复制
int container_main(void* arg)
{  
  printf("Container - inside the container!\n");

  if (mount("proc", "testfs/proc", "proc", 0, NULL)) {
     perror("proc");
  }

  if ( chdir("./testfs")!=0 ||  chroot("./") != 0) {
     perror("chroot");
  }

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

再次运行进入容器中,当前的根目录是上面我们构造的testfs,并且ps aux命令只能看到当前namespace的进程,而看不到宿主机namespace的进程了。

代码语言:javascript
复制
[root@k8s-worker1 k8s]# ./a
Parent - start a container!
Container - inside the container!
root@k8s-worker1:/# ls
bin  boot  dev  etc  home  lib  lib32  lib64  libx32  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
root@k8s-worker1:/# ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.0  26680  5452 ?        S    07:42   0:00 /bin/bash
root          94  0.0  0.0   5352   692 ?        S    07:49   0:00 ./a
root          95  0.0  0.0   4620  3872 ?        S    07:49   0:00 /bin/bash
root          99  0.0  0.0   7056  1556 ?        R+   07:49   0:00 ps aux
root@k8s-worker1:/# echo $$
1

# cgroup

可以实现对进程的CPU、内存等资源的配额限制

cgroup在操作系统中暴露出来的接口是文件系统,会以文件和目录在/sys/fs/cgroup/目录中展示,下面的目录都是子系统,可以限制各种资源:

代码语言:javascript
复制
[root@iZwz93q4afq8ck02cesqh4Z ~]# ls /sys/fs/cgroup/
blkio  cpuacct      cpuset   freezer  memory   net_cls,net_prio  perf_event  systemd
cpu    cpu,cpuacct  devices  hugetlb  net_cls  net_prio          pids

如何限制进程CPU

执行以下命令将CPU吃到100%

代码语言:javascript
复制
$ while : ; do : ; done &

使用top命令查看是否cpu是否满负载

代码语言:javascript
复制
# top

PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                              
2503037 root      20   0   26672   5412   3520 R  99.0   0.0   0:46.09 -bash                                                                                  99.49%

在目录/sys/fs/cgroup/cpu,cpuacct/下创建目录container,操作系统会自动创建资源限制文件

代码语言:javascript
复制
[root@k8s-worker1 container]# mkdir container
[root@k8s-worker1 container]# ls
cgroup.clone_children  cpuacct.usage         cpuacct.usage_percpu_sys   cpuacct.usage_user  cpu.rt_period_us   cpu.stat
cgroup.procs           cpuacct.usage_all     cpuacct.usage_percpu_user  cpu.cfs_period_us   cpu.rt_runtime_us  notify_on_release
cpuacct.stat           cpuacct.usage_percpu  cpuacct.usage_sys          cpu.cfs_quota_us    cpu.shares         tasks

在文件夹下面可以查看到cpu.cfs_quota_us的默认值是-1,代表没有限制,cpu.cfs_period_us默认值为100ms(100000us)

代码语言:javascript
复制
# cat /sys/fs/cgroup/cpu/container/cpu.cfs_period_us
100000
# cat /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us
-1

向cpu.cfs_quota_us写入20ms(20000us),也就是每100ms时间里,限制的进程只能适用20ms,也就是这个进程只能使用到20%的CPU带宽

代码语言:javascript
复制
echo 20000 > /sys/fs/cgroup/cpu//cpu.cfs_quota_us

再将限制的进程ID写入到tasks文件中,可以看到该文件中已经包含了该容器进程ID

代码语言:javascript
复制
# echo 2503037 > tasks
# cat tasks
2503037

再使用top可以发现该进程的cpu被限制在20%

代码语言:javascript
复制
# top

PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                              
2503037 root      20   0   26672   5412   3520 R  20.4   0.0   6:43.71 -bash

容器被cgroup的情况

当然docker已经封装好了,直接调用以下命令即可实现上面CPU的限制

代码语言:javascript
复制
docker run -it --cpu-period=100000 --cpu-quota=20000 ubuntu /bin/bash

可以看到在/sys/fs/cgroup/cpu,cpuacct/docker目录下创建了该容器的目录,目录下面包含了资源限制文件

代码语言:javascript
复制
[root@k8s-worker1 docker]# pwd
/sys/fs/cgroup/cpu,cpuacct/docker
[root@k8s-worker1 docker]# ls
87ee72386a6079ba6411ac8f3030c12407558652d28a1cd16c03f4434581500c  cpuacct.usage             cpuacct.usage_percpu_user  cpu.cfs_quota_us   cpu.stat
cgroup.clone_children                                             cpuacct.usage_all         cpuacct.usage_sys          cpu.rt_period_us   notify_on_release
cgroup.procs                                                      cpuacct.usage_percpu      cpuacct.usage_user         cpu.rt_runtime_us  tasks
cpuacct.stat
[root@k8s-worker1 docker]# cd 87ee72386a6079ba6411ac8f3030c12407558652d28a1cd16c03f4434581500c/
[root@k8s-worker1 87ee72386a6079ba6411ac8f3030c12407558652d28a1cd16c03f4434581500c]# ls
cgroup.clone_children  cpuacct.usage         cpuacct.usage_percpu_sys   cpuacct.usage_user  cpu.rt_period_us   cpu.stat
cgroup.procs           cpuacct.usage_all     cpuacct.usage_percpu_user  cpu.cfs_period_us   cpu.rt_runtime_us  notify_on_release
cpuacct.stat           cpuacct.usage_percpu  cpuacct.usage_sys          cpu.cfs_quota_us    cpu.shares         tasks

在该目录下可以看到cpu.cfs_quota_us设置成了20000,并且tasks中包含了该容器进程的ID

代码语言:javascript
复制
[root@k8s-worker1 87ee72386a6079ba6411ac8f3030c12407558652d28a1cd16c03f4434581500c]# cat cpu.cfs_quota_us 
20000
[root@k8s-worker1 87ee72386a6079ba6411ac8f3030c12407558652d28a1cd16c03f4434581500c]# cat tasks
2560954
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-08-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • # 前言
  • # NameSpace
  • # chroot
  • # 手动构造一个容器
  • # cgroup
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档