当前位置: 代码迷 >> 综合 >> Linux环境使用命名空间编写一个简单的容器应用程序:namespace,container,cgroups
  详细解决方案

Linux环境使用命名空间编写一个简单的容器应用程序:namespace,container,cgroups

热度:78   发布时间:2024-02-24 04:01:51.0

目录

使用命名空间编写一个简单的容器应用程序

创建一个子进程– fork vs clone

具有clone(2)的命名空间

简单示例 - CLONE_NEWPID

隔离网络接口 - CLONE_NEWNET

挂载点和文件系统 - CLONE_NEWNS

LINUX控制组

简单的例子

Freezer cgroup


 

使用命名空间编写一个简单的容器应用程序

https://devarea.com/simple-container-app-with-namespaces/#.X3PfymgzaUk


Linux命名空间是实现容器的构件之一。命名空间控制进程可以看到的内容。它可以是进程ID,安装点,网络适配器等。

要使用名称空间,我们调用clone(2)系统调用。

 

创建一个子进程– fork vs clone


要在Linux中创建新进程,我们可以使用fork(2)或clone(2)系统调用。我们使用fork(2)创建一个带有单独内存映射的新子进程(使用CoW),我们使用clone(2)创建一个与其父级共享资源的子进程。克隆的一种用途是实现多线程,另一种用途是实现名称空间。

 

具有clone(2)的命名空间


要在新的名称空间和隔离的资源中创建子进程,我们需要使用以下一个或多个标志:

  • CLONE_NEWNET –隔离网络设备
  • CLONE_NEWUTS –主机名和域名(UNIX时间共享系统)
  • CLONE_NEWIPC – IPC对象
  • CLONE_NEWPID – PID
  • CLONE_NEWNS –挂载点(文件系统)
  • CLONE_NEWUSER –用户和组

 

简单示例 - CLONE_NEWPID


要创建PID = 1(新进程树)的子进程,请使用CLONE_NEWPID调用clone(2):

clone(child_fn, child_stack+5000, CLONE_NEWPID , NULL);

子进程上的getpid()返回1,getppid()返回0。如果子进程创建了另一个子进程,它将从新树中获取一个进程ID。

完整示例:

#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>static char child_stack[5000];void grchild(int num)
{printf("child(%d) in ns my PID: %d Parent ID=%d\n", num, getpid(),getppid());sleep(5);puts("end child");
}int child_fn(int ppid) {int i;printf("PID: %ld Parent:%ld\n", (long)getpid(), getppid());for(i=0;i<3;i++){if(fork() == 0){grchild(i+1);  exit(0);}kill(ppid,SIGKILL); // no effect }sleep(2);kill(2,SIGKILL); // kill the first childsleep(10);return 0;
}int main() {pid_t pid = clone(child_fn, child_stack+5000, CLONE_NEWPID , getpid());printf("clone() = %d\n", pid);waitpid(pid, NULL, 0);return 0;
}

主体在新的PID名称空间中创建子进程,并将其PID发送给子进程。该孩子将创建3个孩子。

如果子进程尝试杀死父进程(不在其名称空间中)–则什么也不会发生,但是它可以杀死其命名空间中的进程(在这种情况下,第一个子进程)

如果您构建并运行此示例(使用sudo运行)

# sudo ./simple
clone() = 5439
PID: 1 Parent:0
child(3) in ns my PID: 4 Parent ID=1
child(2) in ns my PID: 3 Parent ID=1
child(1) in ns my PID: 2 Parent ID=1
end child
end child

如您所见,PID为1-4,第一个孩子没有完成(SIGKILL)

 

隔离网络接口 - CLONE_NEWNET


要使用不同的网络接口创建子进程,请使用CLONE_NEWNET

pid_t pid = clone(child_fn, child_stack+1024*1024, CLONE_NEWNET , NULL);

要创建虚拟网络适配器,我们可以运行ip命令:

# sudo ip link add name veth0 type veth peer name veth1 netns [child pid]
# sudo ifconfig veth0 10.0.0.3

现在,子进程应该运行命令:

# ifconfig veth1 10.0.0.4

我们可以对所有这些命令进行编码,但为简单起见,请使用system(3)库函数

完整示例:

#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>static char child_stack[1024*1024];static int child_fn() {sleep(1);system("ifconfig veth1 10.0.0.4"); puts("========= child network interfaces ========");system("ifconfig -a");puts("===========================================");sleep(1);system("ping -c 3 10.0.0.3");return 0;
}int main() {char buf[255]; pid_t pid = clone(child_fn, child_stack+1024*1024, CLONE_NEWNET , NULL);sprintf(buf,"ip link add name veth0 type veth peer name veth1 netns %d",pid); system(buf);system("ifconfig veth0 10.0.0.3"); waitpid(pid, NULL, 0);return 0;
}

运行此测试–输出:

========= child network interfaces ========
lo        Link encap:Local Loopback  LOOPBACK  MTU:65536  Metric:1RX packets:0 errors:0 dropped:0 overruns:0 frame:0TX packets:0 errors:0 dropped:0 overruns:0 carrier:0collisions:0 txqueuelen:1 RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)veth1     Link encap:Ethernet  HWaddr 7a:d6:68:fb:c0:04  inet addr:10.0.0.4  Bcast:10.255.255.255  Mask:255.0.0.0inet6 addr: fe80::78d6:68ff:fefb:c004/64 Scope:LinkUP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1RX packets:0 errors:0 dropped:0 overruns:0 frame:0TX packets:0 errors:0 dropped:0 overruns:0 carrier:0collisions:0 txqueuelen:1000 RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)===========================================
PING 10.0.0.3 (10.0.0.3) 56(84) bytes of data.
64 bytes from 10.0.0.3: icmp_seq=1 ttl=64 time=0.076 ms
64 bytes from 10.0.0.3: icmp_seq=2 ttl=64 time=0.048 ms
64 bytes from 10.0.0.3: icmp_seq=3 ttl=64 time=0.071 ms--- 10.0.0.3 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.048/0.065/0.076/0.012 ms

子代只能看到虚拟适配器,并且可以使用它来ping父代

 

挂载点和文件系统 - CLONE_NEWNS


要实现容器,我们还需要隔离文件系统。可以使用CLONE_NEWNS来完成。在编码之前,让我们使用BusyBox 或BuildRoot构建一个简单的文件系统

最简单的方法是使用buildroot –它基于busybox

下载并解压缩软件包,使用make menuconfig进入配置菜单,只需退出并保存默认选择并运行make

# tar xvf ./buildroot-2017.11.2.tar.bz2
# cd buildroot-2017.11.2
# make menuconfig
# make

这将需要几分钟,在构建完成之后,您将在buildroot-2017.11.2 / output / target中找到一个文件系统。

将内容复制到另一个文件夹–在我的示例fs中,并使用mknod命令将一些设备文件添加到/ dev目录(buildroot无法这样做,因为它不能与sudo一起运行)

完整的例子 

#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>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <string.h>
#define STACK_SIZE (1024 * 1024)static char stack[STACK_SIZE];int setip(char *name,char *addr,char *netmask) {struct ifreq ifr;int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);strncpy(ifr.ifr_name, name, IFNAMSIZ);ifr.ifr_addr.sa_family = AF_INET;inet_pton(AF_INET, addr, ifr.ifr_addr.sa_data + 2);ioctl(fd, SIOCSIFADDR, &ifr);inet_pton(AF_INET, netmask, ifr.ifr_addr.sa_data + 2);ioctl(fd, SIOCSIFNETMASK, &ifr);//get flagsioctl(fd, SIOCGIFFLAGS, &ifr);  strncpy(ifr.ifr_name, name, IFNAMSIZ);ifr.ifr_flags |= (IFF_UP | IFF_RUNNING);// set flagsioctl(fd, SIOCSIFFLAGS, &ifr);return 0;
}int child(void* arg)
{char c;sleep(1);sethostname("myhost", 6);chroot("./fs");chdir("/");mount("proc", "/proc", "proc", 0, NULL);setip("veth1","10.0.0.15","255.0.0.0");execlp("/bin/sh", "/bin/sh" , NULL);return 1;
}int main()
{char buf[255];pid_t pid = clone(child, stack+STACK_SIZE,CLONE_NEWNET | CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, NULL);sprintf(buf,"sudo ip link add name veth0 type veth peer name veth1 netns %d",pid);system(buf);setip("veth0","10.0.0.13","255.0.0.0");waitpid(pid, NULL, 0);return 0;
}

我们在新的名称空间(带有PID,网络,安装,IPC和UTS)中创建子进程,父进程配置虚拟适配器(使用ip链接)并设置其ip地址子级更改主机名,将根文件夹更改为我们的buildroot输出,将当前目录更改为“ /”,安装proc以便ps和其他工具可以工作并设置IP地址。

子进程做的最后一步是调用busybox shell(/ bin / sh)

使用sudo运行此程序–您将获得不同的shell,文件系统和网络:

 

您可以在此处找到具有完整Buildroot映像的代码:https://github.com/dev-area/containers

这只是理解该概念的简单示例。要实现完整的容器,您还需要添加功能,控制组(https://devarea.com/linux-control-groups/#.WnH9WyOB2_M)等。

 

 

LINUX控制组

https://devarea.com/linux-control-groups/#.X3QPFmgzaUl


使用cgroups可以限制进程的资源使用,例如,如果您有Web服务器,并且想要将其内存使用量限制为1GB,则使用cgroups很容易。

这也是容器的基本构建块

首先,我们需要安装一些工具(在我的示例中,我使用Ubuntu)

# sudo apt install cgmanager
# sudo apt install cgroup-tools

cgroup和cgroup2是Linux中的文件系统。在内核代码中,您可以在/kernel/cgroup/cgroup.c中找到它,并且可以从用户角度进行查看:

# cat /proc/filesystems
nodev	sysfs
nodev	rootfs
nodev	ramfs
nodev	bdev
nodev	proc
nodev	cgroup
nodev	cpuset
nodev	tmpfs
nodev	devtmpfs
nodev	debugfs
nodev	securityfs
nodev	sockfs
nodev	dax
nodev	bpf
nodev	pipefs
nodev	configfs
nodev	devpts
nodev	hugetlbfs
nodev	autofs
nodev	pstore
nodev	mqueuexfs
nodev	rpc_pipefs
nodev	nfsdext3ext2ext4
nodev	overlayfuseblk
nodev	fuse
nodev	fusectl
nodev	binfmt_misc

还运行挂载命令:

# mount…
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,…)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,…)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,…)
…

我的是:

[rongtao@localhost ~]$ mount
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
devtmpfs on /dev type devtmpfs (rw,nosuid,size=3968696k,nr_inodes=992174,mode=755)
securityfs on /sys/kernel/security type securityfs (rw,nosuid,nodev,noexec,relatime)
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000)
tmpfs on /run type tmpfs (rw,nosuid,nodev,mode=755)
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/
systemd-cgroups-agent,name=systemd)pstore on /sys/fs/pstore type pstore (rw,nosuid,nodev,noexec,relatime)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_prio,net_cls)
configfs on /sys/kernel/config type configfs (rw,relatime)
/dev/mapper/centos-root on / type xfs (rw,relatime,attr2,inode64,noquota)
hugetlbfs on /dev/hugepages type hugetlbfs (rw,relatime)
systemd-1 on /proc/sys/fs/binfmt_misc type autofs (rw,relatime,fd=33,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,
pipe_ino=17193)debugfs on /sys/kernel/debug type debugfs (rw,relatime)
mqueue on /dev/mqueue type mqueue (rw,relatime)
nfsd on /proc/fs/nfsd type nfsd (rw,relatime)
/dev/vda1 on /boot type xfs (rw,relatime,attr2,inode64,noquota)
/dev/vdb on /work type ext4 (rw,nosuid,nodev,relatime,data=ordered)
sunrpc on /var/lib/nfs/rpc_pipefs type rpc_pipefs (rw,relatime)
tmpfs on /run/user/42 type tmpfs (rw,nosuid,nodev,relatime,size=797020k,mode=700,uid=42,gid=42)
tmpfs on /run/user/1002 type tmpfs (rw,nosuid,nodev,relatime,size=797020k,mode=700,uid=1002,gid=1002)
fusectl on /sys/fs/fuse/connections type fusectl (rw,relatime)
binfmt_misc on /proc/sys/fs/binfmt_misc type binfmt_misc (rw,relatime)
/dev/fuse on /run/user/1002/doc type fuse (rw,nosuid,nodev,relatime,user_id=0,group_id=0)

 

简单的例子


首先我们创建一个新组,我们要限制内存使用量:

# sudo cgm create memory myg

它将创建以下文件夹:/sys/fs/cgroup/memory/user.slice/myg/

现在我们可以使用以下方式更改权限:

# sudo cgm chown memory myg 1000 1000
# sudo chown -R developer:developer /sys/fs/cgroup/memory/user.slice/myg/*

要将组的内存限制为20MB:

# echo "20000000"> /sys/fs/cgroup/memory/user.slice/myg/memory.limit_in_bytes

内存使用的代码示例:

#include<stdio.h>void main(void)
{int i=0;int *p;while(1){i++;p= (int *)malloc(1000000);if(p)printf("1MB allocated i=%d\n",i);elseprintf("out of memory\n");memset(p,0,1000000);sleep(10);}
}

如果按原样运行,则将看到输出,直到系统内存不足

要控制内存使用情况,我们需要将其移至上述cgroup:

# echo [pid] > /sys/fs/cgroup/memory/user.slice/myg/cgroup.procs

达到限制后,您将看到进程被杀死

 

Freezer cgroup


另一个例子是冷冻对照组。该组中的任何任务都可以暂停

# mkdir /sys/fs/cgroup/freezer/0
# echo [pid]>/sys/fs/cgroup/freezer/0/tasks# echo FROZEN > /sys/fs/cgroup/freezer/0/freezer.state
# echo THAWED > /sys/fs/cgroup/freezer/0/freezer.state

 

  相关解决方案