Linux Container

  1. Linux容器是一个轻量级的环境,你可以在容器中以接近裸机的性能运行一到几个隔离应用程序.

Linux Control Groups

  1. Control groups是一个内核特色,它限制、负责和隔离一个或多个进程对CPU、内存、磁盘或网络的使用。
  2. cgroup的主要设计目标是提供一个统一的接口来管理进程或整个操作系统级别的虚拟化,包括Linux容器或LXC,cgroups框架提供以下内容:
  • 资源限制:可以将组配置为不超过指定的内存限制或使用的处理器数量不超过期望的数量或者限制为特定的外围设备。
  • 优先级:可以将一个或多个组配置为利用更少或更多的CPU或者磁盘I/O吞吐量。
  • 记账:一个组的资源使用是被监视和测量的。
  • 控制:可以冻结或停止并重新启动进程组。
  1. 一个cgroup由一个或多个受到一系列相同限制的进程组成,这些组也能够分层,这意味着子组继承了对其父组管理的限制。
  2. Linux内核为cgroup技术提供了一系列控制或子系统的访问,控制器负责将特定的系统资源分配一组或多个进程,例如memory控制器限制内存使用,而cpuacct控制器监视CPU的使用。
  3. 可以直接或间接的访问和管理cgroup(使用LXC、libvirt或Docker),在这里通过sysfs和libcgroups库,你首先需要安装一些必要的包:
root@Ubuntu:~# sudo apt-get install libcgroup1 cgroup-tools
Reading package lists... Done
Building dependency tree       
Reading state information... Done
  1. 下面这个程序,我使用一个简单的shell脚本,它会在一个无限循环中运行两条命令:
root@Ubuntu:~# vim test.sh
#!/bin/sh

while [ 1 ]; do
        echo "hello world"
        sleep 60
done


  1. 在安装了正确的包后,可以通过sysfs层次结构直接配置cgroup,例如,要在内存子系统下面创建一个名为foo的cgroup,请在/sys/fs/cgroup/memory中创建一个名为foo的目录:
root@Ubuntu:~# mkdir /sys/fs/cgroup/memory/foo
  1. 默认情况下,每个新创建的cgroup会继承对系统整个内存池的访问权,对于一些应用程序,主要是那些继续分配更多内存但是拒绝释放已经分配的内存的应用程序,可能存在一些问题,因此,要将应用程序限制在合理的限制内,我们需要更新memory.limit_in_bytes文件。
  2. 限制任何运行在cgroup为foo的进程内存为50MB:
root@Ubuntu:~# echo 50000000 | tee /sys/fs/cgroup/memory/foo/memory.limit_in_bytes 
  1. 验证设置
root@Ubuntu:~# cat /sys/fs/cgroup/memory/foo/memory.limit_in_bytes 
49999872
  • 读回的值总是内核页面大小的背书(即4096字节或4KB),这个值是最小的可分配内存的大小。
  1. 启动程序
root@Ubuntu:~# sh ~/test.sh &
[1] 16219
  1. 使用进程PID,将应用程序移动到内存控制下cgroup foo:
echo 16219 > /sys/fs/cgroup/memory/foo/cgroup.procs
  1. 使用相同到PID号,列出正在运行的进程,并验证它是否在所需的cgroup中运行:
ps -o cgroup 16219
CGROUP
10:memory:/foo,8:pids:/user.slice/user-0.slice/session-136.scope,5:devices:/user
  1. 可以通过读取所需的文件来监视该cgroup正在使用的内容,在本例中,你需要查看进程分配的内存量:
cat /sys/fs/cgroup/memory/foo/memory.usage_in_bytes 
688128
  1. 现在让我们将cgroup foo的内存设置为5000字节:
root@Ubuntu:~# echo 5000 | tee /sys/fs/cgroup/memory/foo/memory.limit_in_bytes 
5000
root@Ubuntu:~# cat /sys/fs/cgroup/memory/foo/memory.limit_in_bytes
4096
  • 虽然我们分配了5000字节,但是内核只会分配页大小的整数倍,此外如果任务超出了定义的限制,内核将会进行干预,在某些情况下,会终止该任务。
  • 启动程序,把它放到cgroup下:
root@Ubuntu:~# sh test.sh &
[1] 1321
root@Ubuntu:~# hello world
echo 1321 > /sys/fs/cgroup/memory/foo/cgroup.procs
  • 只要应用程序使用的内存超过4MB,内核会杀死和这个应用程序,它不在运行,可以下面命令来验证:
root@Ubuntu:~# ps -o cgroup 1321
CGROUP

Using libcgroup

  1. 使用libcgroup提供的管理程序功能可以简化上述的步骤,要在内存子系统下创建一个名称为foo的组:
root@Ubuntu:~#  cgcreate -g memory:foo
  1. 使用相同的方法,设置内存能够使用的上限:
root@Ubuntu:~# echo 50000000 | tee /sys/fs/cgroup/memory/foo/memory.limit_in_bytes
50000000
root@Ubuntu:~# cat /sys/fs/cgroup/memory/foo/memory.limit_in_bytes        
49999872
  1. 使用cgexec命令在cgroup foo下运行程序
root@Ubuntu:~# cgexec -g memory:foo ~/test.sh &
[1] 1792
  1. 使用其PID编号,验证应用程序正在cgroup中并在已定义的子系统下运行:
ps -o cgroup 1792
CGROUP
12:devices:/user.slice,10:cpu,cpuacct:/user.slice/user-0.slice/session-9.scope,8
  1. 如果你的应用程序不在运行,并且你想要清理和移除cgroup,可以使用cgdelete命令,从内存控制器中移除group foo:
cgdelete memory:foo

永久性Groups

  1. 可以通过配置文件和启动服务来完成上述所有操作,你可以在/etc/cgconfig.conf文件来定义所有的cgroup的名字和属性,下面为组foo追加了一些新的属性:
group foo {
  cpu {
    cpu.shares = 100;
  }
  memory {
    memory.limit_in_bytes = 5000000;
  }
}
  • cpushares选项定义了组的CPU优先级,默认情况,所有组都继承了1024份或者100%的CPU时间,通过将此值降低到一个较为保守的值例如100,该组将被限制为大约CPU时间的10%。
  1. 一个运行在cgroup中的进程也可以限制它能访问CPU核心的数量,讲以下部分添加到相同的cgconfig.conf文件中,并在想要的组名中:
cpuset{
	cpuset.cpus = "0-5";
}
  • 有了这个限制,此cgroup会将应用程序绑定到核心0到5,也就说,它将仅能看到系统上前6个CPU核心。
  1. 启用上述配置
root@Ubuntu:~# cgconfigparser -l  /etc/cgconfig.conf
  1. 启动应用程序到cgroup foo并绑定内存和CPU限制:
root@Ubuntu:~# cgexec -g memory,cpu:foo ~/test.sh &
[2] 2021
  • 除了将应用程序启动到预先定义到cgroup中之外,其余所有这些都将在系统重新引导后继续存在,但是,可以自动化这个过程,通过定义依赖于cgconfig都自动化脚本来启动该应用程序。

Linux Container framework

  1. LXC无需创建完整的虚拟机,而是通过自己的进程和网络空间来实现虚拟环境,LXC使用命名空间来强制进程隔离,同时使用内核自己的cgroup来解释和限制一个或多个进程的CPU、内存、磁盘I/O和网络使用。
  2. 简而言之,容器将应用程序与操作系统分离,为用户提供了一个干净且最小的Linux环境,同时在一个或多个独立容器中运行其他所有东西,容器的目的是启动一组有限的应用程序或服务,并让他们运行在自包含的沙盒环境中。
    在这里插入图片描述
  3. 这种隔离可以组织部运行在一个给定容器中的进程监视或影响在另一个容器中运行的进程,而且,容器内的服务不会影响或干扰宿主计算机,能够将分散在多个物理服务器上的许多服务合并为一个服务的想法是数据中心选择采用该技术的众多原因中的一个。
  4. 容器的特色:
  • 安全性:网络服务可以在一个容器中运行,这限制了安全性破坏或违法带来的损害,一个成功利用容器中运行的应用程序的安全漏洞的入侵者被限制只能在该容器组执行一组操作。
  • 隔离:容器允许在一个相同的物理机上部署一个或多个应用程序,即使这些应用程序在不同的域中运行,每个需要访问各自资源所特有的权限,例如,运行在不同的容器中多个应用程序可以绑定到相同的物理网络接口,通过使用同容器关联的不同的IP地址。
  • 虚拟化和透明性:容器为系统提供了一个虚拟化的环境,这个虚拟化环境能够隐藏或者限制物理设备或者系统配置的可见性,容器背后的一般原则是,除了解决安全性或隔离型问题,避免改变应用程序正在运行的环境。

使用LXC

  1. 对于大多是现代Linux发行版,内核已经能够使用cgroups,但是大多数仍需要安装LXC实用程序。
root@Ubuntu:~# apt-get install lxc
  1. 现在,在开始修改这些实用程序之前,需要配置环境,需要验证当前用户是否在/etc/subuid和/etc/subgid中定义了uid和gid条目:
parallels@Ubuntu:~$ cat /etc/subuid
parallels:100000:65536
parallels@Ubuntu:~$ cat /etc/subgid
parallels:100000:65536
  1. 首先我们需要创建~/.config/lxc目录,然后将/etc/lxc/default.conf文件复制到 ~/.config/lxc目录下,然后在 ~/.config/lxc/default.conf文件追加两行内容:
parallels@Ubuntu:~$ mkdir ~/.config/lxc      
parallels@Ubuntu:~$ cp -r /etc/lxc/default.conf ~/.config/lxc/default.conf
parallels@Ubuntu:~$ vim ~/.config/lxc/default.conf
parallels@Ubuntu:~$ cat ~/.config/lxc/default.conf
lxc.net.0.type = veth
lxc.net.0.link = lxcbr0
lxc.net.0.flags = up
lxc.net.0.hwaddr = 00:16:3e:xx:xx:xx
###追加的两行内容
lxc.id_map = u 0 100000 65536
lxc.id_map = g 0 100000 65536
  1. 将以下内容追加到/etc/lxc/lxc-usernet文件中,其中第一列需要用自己的用户名:
parallels veth lxcbr0 10
  1. 使用这些设置生效的最快方法是重新启动节点或者注销用户,然后重新登陆,重新登陆回来后验证veth网络驱动是否已经被载入:
parallels@Ubuntu:/root$ lsmod|grep veth
veth                   16384  0
  • 如果未被载入,可以输入:
parallels@Ubuntu:/root$ sudo modprobe veth
  1. 你可以使用LXC使用程序下载、运行和管理Linux容器。
  2. 解下来下载一个容器镜像并将其命名为“example-container”,输入下面命令,在很多Linux的发行版中看到一长串受支持的容器:
parallels@Ubuntu:/root$ sudo lxc-create -t download -n example-container
[sudo] password for parallels: 
Setting up the GPG keyring
Downloading the image index
..
  1. 之后系统会提示您三个提示来选择distribution、release和architect。
Distribution: 
ubuntu
Release: 
xenial
Architecture: 
amd64
  1. 一旦你选择完毕上述选项,rootfs会在本地自动被下载或被配置,出于安全原因,每个容器不会附带OpenSSH服务或者用户账户,也没有默认的root密码,为了改变root密码并且登陆,你需要在一个容器目录内运行要么lxc-attack或者chroot指令。
  2. 启动容器:
 parallels@Ubuntu:/root$ sudo lxc-start -n example-container -d
  • 使用-d选项是守护容器,它会在后台运行,如果你想要观察引导进程,使用-F选项代替-d,它会运行在前台运行,并在登录提示时结束。
  • 发现出现如下报错:
parallels@Ubuntu:/root$ sudo lxc-start -n example-container -d
lxc-start: example-container: lxccontainer.c: do_lxcapi_start: 879 Ongoing container creation detected
lxc-start: example-container: tools/lxc_start.c: main: 330 The container failed to start
lxc-start: example-container: tools/lxc_start.c: main: 333 To get more details, run the container in foreground mode
lxc-start: example-container: tools/lxc_start.c: main: 336 Additional information can be obtained by setting the --logfile and --logpriority options
  • 我们需要通过运行lxc-start服务在前台调试它:
$ sudo lxc-start -n example-container -F
lxc-start: conf.c: instantiate_veth: 2685 failed to create veth
 pair (vethQ4NS0B and vethJMHON2): Operation not supported
...
  • 从上面的例子可以看出veth模块可能没有被插入,在插入它之后,这个问题会被解决。
  1. 打开另一个命令行窗口验证容器的状态:
$ sudo lxc-info -n example-container
Name:           example-container
State:          RUNNING
PID:            1356
IP:             10.0.3.28
CPU use:        0.29 seconds
BlkIO use:      16.80 MiB
Memory use:     29.02 MiB
KMem use:       0 bytes
Link:           vethPRK7YU
 TX bytes:      1.34 KiB
 RX bytes:      2.09 KiB
 Total bytes:   3.43 KiB
  1. 下面这条命令会列出所有已经安装了的容器的状态:
$ sudo lxc-ls -f
NAME         STATE   AUTOSTART GROUPS IPV4      IPV6
example-container RUNNING 0         -      10.0.3.28 -
  1. 但是现在存在一个问题就是你依旧不能登录,附加到一个正在运行的容器上,通过使用passwd命令来创建用户并更改相关密码:
$ sudo lxc-attach -n example-container
root@example-container:/#
root@example-container:/# useradd petros
root@example-container:/# passwd petros
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
  1. 一旦密码别改变,你能够直接从控制台登录到容器而不需要使用lxc-attach命令:
$ sudo lxc-console -n example-container
  1. 如果你想要通过网络连接一个正在运行的容器,请安装OpenSSH服务:
root@example-container:/# apt-get install openssh-server
  1. 获取容器的本地IP地址:
root@example-container:/# ip addr show eth0|grep inet
    inet 10.0.3.25/24 brd 10.0.3.255 scope global eth0
    inet6 fe80::216:3eff:fed8:53b4/64 scope link
  1. 从宿主计算机打开一个新的控制台窗口进行登陆:
$ ssh 10.0.3.25
  1. 从宿主系统中,而不是在容器中,我们可以观察在容器启动后启动和运行的LXC进程:
$ ps aux|grep lxc|grep -v grep
  1. 可以从宿主机器中输入以下命令暂停一个容器的执行:
sudo lxc-stop -n example-container
  1. 暂停之后,验证容器的状态:
$ sudo lxc-ls -f
NAME         STATE   AUTOSTART GROUPS IPV4 IPV6
example-container STOPPED 0         -      -    -

$ sudo lxc-info -n example-container
Name:           example-container
State:          STOPPED
  1. 从宿主计算机中输入如下命令可以销毁一个容器:
$ sudo lxc-destroy -n example-container
Destroyed container example-container
  1. 验证是否已经被销毁
$ sudo lxc-info -n example-container
example-container doesn't exist

$ sudo lxc-ls -f
$

高级配置

  1. 有时,可能需要配置一个或多个容器来完成一个或多个任务,LXC通过让管理员修改位于/var/liv/LXC中的容器配置文件来简化这一个过程:
$ sudo su
# cd /var/lib/lxc
# ls
example-container
  1. 容器的父目录至少有2个文件组成:1. 容器配置文件。2. 容器的完整rootfs文件:
# cd example-container/
# ls
config  rootfs
  1. 如果你想要在宿主系统启动的时候自动启动容器,你需要添加如下内容到容器的配置文件中,/var/lib/lxc/example-container/config:
...
lxc.start.auto = 1
  1. 在重新启动容器或系统之后,查看容器的状态:
$ sudo lxc-ls -f
NAME              STATE   AUTOSTART GROUPS IPV4      IPV6
example-container RUNNING 1         -      10.0.3.25 -
  • 可以看到AUTOSTART标志位被设置为1.
  1. 如果希望容器本地的/mnt目录访问宿主机器的/mnt目录,添加如下内容到配置文件,重启容器:
lxc.mount.entry = /mnt mnt none bind 0 0

特权容器和非特权容器

  1. 从设计上来说,非特权容器比特权容器更安全,一个非特权容器在宿主系统中运行时,容器的root UID映射到宿主系统的非root UID,对于攻击者来说通过损害容器来获得底层宿主系统root权限是困难的。
  2. 特权容器可以并将使系统暴露于此类攻击,这就是为什么在特权模型下运行很少的一些容器,识别出需要特权访问的容器,并确保定期更新并以其他方式锁定它们。

Docker

  1. Docker是一种获得Apache许可的开源容器化技术,旨在自动执行在容器中创建和部署微服务的重复任务,Docker将容器是为极其轻巧的、模块化的虚拟机,最初,Docker是在LXC之上构建的,但此后不再依赖于此,从而带来了更好的开发人员和用户体验,就像LXC一样,Docker继续使用内核cgroup子系统,该技术不仅仅运行容器,它还简化了创建容器,构建容器,构建镜像,共享这些构建的镜像并对其进行版本控制。
  2. Docker的优点:
  • 可移植性:Docker提供了基于镜像的部署模型,这种类型的可移植性允许以一种简单在多个环境之间的方式共享应用程序或服务集。
  • 版本控制:Docker镜像由一系列组合的层组成,每当修改镜像时,都会创建一个新层,例如,每当用户指定命令(例如运行或复制)时,都会创建一个新层,Docker将这些层用于新的容器构建,分层是Docker自己的版本控制方法。
  • 回滚:每个Docker镜像都有层,如果你不想要使用当前运行的层,你能够回滚到之前的版本,这种敏捷性使开发者能够持续的集成和部署其软件技术。
  • 快速部署:配置一个新的硬件通常需要好几天,安装和配置它的工作量和开销非常重,使用Docker,镜像启动和运行仅仅需要几秒钟,用完容器后,就可以轻松销毁。
  1. 从根本上讲,Docker和LXC是非常相似的,它们都使用用户空间和轻量级的虚拟化平台,它们实施cgroup和命名空间来进行资源隔离,可是,两者之间存在许多明显的差别。
  • 进程管理
    Docker限制容器作为单个进程运行,如果你的应用程序由x个并发的进程组成,则Docker将希望你运行X个容器,每个容器都有属于自己的不同进程。LXC并不在这种情况,LXC使用常规的初始化进程运行一个容器,并且可以在一个容器中运行多个进程。例如,如果你要托管LAMP(Linux+Apache+MySQL+PHP)服务器,则每个应用程序的每个进程将需要跨越多个Docker容器。
  • 状态管理
    Docker被设计为无状态的,意味着它不支持持久化存储,有很多方法可以解决这个问题,但同样,只有在进程需要是才需要。当一个Docker镜像创建时,他将由只读层组成,在运行期间,如果容器的进程对内部的状态进行了任何更改,则内部状态和镜像当前状态之间的差异将保持不变,知道提交Docker镜像(创建新层)或者删除容器,导致该差异消息。
  • 可移植性
    这是Docker相对于LXC最重要的又是,Docker在从应用程序中抽象出网络、存储和操作系统详细信息方法做的更好,这将会产生一个真正配置独立的引用程序,保持应用程序的执行环境始终相同,而不管启动该应用程序的机器如何。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值