当前位置: 首页 > 工具软件 > LXC > 使用案例 >

LXC使用指南

秦承安
2023-12-01

LXC使用指南

转自: https://xiaohui-p.iteye.com/blog/1180130

作者: xiaohui_p

发布: 2017-05-04 19:15

1. lxc简介

容器有效地将由单个操作系统管理的资源划分到孤立的组中,以更好地在孤立的组之间平衡有冲突的资源使用需求。与虚拟化相比,这样既不需要指令级模拟,也不需要即时编译。容器可以在核心 CPU 本地运行指令,而不需要任何专门的解释机制。此外,也避免了准虚拟化(paravirtualization)和系统调用替换中的复杂性。

2. lxc安装

2.1 系统及内核版本

Fedora 13, 2.6.33内核

2.2 安装lxc

    1. 安装lxc工具包
    # yum install lxc 
    
    1. 检查内核是否支持lxc容器
    # lxc-checkconfig 
    

    如果输出所有项均为enbale状态,则表明内核支持lxc容器,否则需重新编译内核打开相应功能项。

    1. 通过shell在容器中运行一个程序(超级用户下)
    • a) 在当前目录下编译配置文件conf.txt,其内容为:
    #Simplest lxc configuration 
    lxc.utsname = name 
    
    • b) 挂载cgroup
    # mkdir /cgroup 
    # mount –t cgroup cgroup /cgroup 
    
    • c) 输入如下shell命令
    # lxc-execute  -n test  -f conf.txt /bin/echo “hello world” 
    
    • d) 输出结果为
    hello world 
    
    1. 编辑/etc/fstab,使得在系统启动时自动挂载cgroup
    # /bin/echo “cgroup /cgroup cgroup defaults 0 0” >> /etc/fstab 
    

3. lxc工具集

3.1 创建和销毁容器

3.1.1 lxc-create

lxc-create  -n name [-f config_file] [ -t template] 

创建一个系统对象来存储配置和用户信息,该对象保存/usr/local/var/lib/lxc(注意该目录为版本相关的,不同版本的lxc可能不同)目录下,name为其标示符。如果配置文件未指定,则系统会支持默认的资源隔离:PID,sysv IPC和挂载点。
例如:

# lxc-create -n test 

创建容器test,可在/usr/local/var/lib/lxc目录下发现创建了目录test,该文件中包含了默认的配置文件config

3.1.2 lxc-destroy

lxc-destroy  -n name 

销毁一个名称为name的容器,主要删除在/usr/local/var/lib/lxc中相应的文件,注意一定要在容器处于STOPPED状态时才能销毁容器.
例如:

# lxc-destroy  -n test 

销毁容器test.

3.2 在容器中运行与结束应用

3.2.1 lxc-execute和lxc-start

  • a) lxc-execute -n name [-f config_file] [-s EKY=VAL] command

    在指定的容器name中运行由command指定的命令。如果容器name未创建,则lxc-execute使用指定的配置文件创建容器name。该命令通过中间进程lxc-init来启动需要运行的命令,并且lxc-init在创建命令后会等待该命令执行结束。因此lxc-init进程的pid为1,运行的命令的pid为2。

  • b) lxc-start -n name [-f config_file] [-s KEY=VAL] command

    lxc-start在指定容器name中运行command指定的命令。注意lxc-start不支持孤儿进程组和守护进程,且如果command未指定,则会运行/sbin/init命令。

    例如:

    # lxc-create -n test –f conf.txt /bin/bash #运行bash 
    # lxc-start  -n test /bin/bash #运行bash 
    

    注意,lxc-create用于在容器中运行一个应用,而lxc-start则用于在容器中运行一个系统。

3.2.2 lxc-stop

lxc-stop  -n name 
``
终止容器name的运行。例如: 
```sh
# lxc-stop  -n test #终止容器test的运行 

3.3 lxc-console

如果创建容器时候配置了tty,可通过如下命令连接到tty

lxc-console -n test -t 3 #连接到容器test的3号tty上。 

3.4 冻结和解冻容器

3.4.1 lxc-freeze

lxc-freeze  -n name 

冻结所有在容器name中运行的程序,此处冻结是指挂起所有在指定容器中运行的程序。必须显式的使用lxc-ufreeze命令解除冻结。

#lxc-freeze  -n  foo  #冻结容器foo中运行的所有程序 

3.4.2 lxc-unfreeze

lxc-unfreeze  -n  name 

解除容器name的冻结状态.

#lxc-ufreeze  -n  foo  #将foo中冻结的程序恢复运行。 

注意:该功能需要内核打开相应的功能!

3.5 获取容器的信息

3.5.1 lxc-ls

列出系统中存在的所有容器,选项和ls命令相同。

3.5.2 lxc-info

lxc-info  -n name 

列出容器name的运行信息:输出格式如下:

name is stopped 
name is running 

3.5.3 lxc-ps

显示特定容器的pids,选项和ps相同,为内置的ps

3.5.4 lxc-netstat

显示指定容器的网络状态,该命令建立在命令netstat之上,因此接受于netstat相同的参数。

3.5.5 lxc-cgroup

lxc-cgroup  -n name subsystem [value] 

显示或者设置与控制组相关联的子系统的值,如果value未指定则显示其值,否则设定相应子系统的值。 例如:

lxc-cgroup  -n foo devices.list       #显示允许容器foo使用的设备列表 
lxc-cgroup  -n foo cpuset.cpus “0,3”  #将处理器0和3分配给容器foo 

3.6 监控容器

3.6.1 lxc-monitor

lxc-monitor  -n name 

监控指定容器的状态,其中名称为正则表达式。 例如:

lxc-monitor  -n  ‘foo|bar’  #监控容器foo和bar 
lxc-monitor  -n  ‘.*’       #监控所有容器 
lxc-monitor  -n  ‘[f|b].*’  #监控所有名称以f或b开始的容器 

3.6.2 lxc-wait

lxc-wait  -n name  -s states 

等待容器到达某一状态,然后退出,其中states可为若干状态. 例如:

lxc-wait  -n foo ‘RUNNING’  #等待容器到RUNNING状态 
lxc-wait  -n foo ‘RUNNING|STOPPED’  #等待容器到达RUNNING或者STOPPED状态 

注意:容器的状态分为:STOPPED, STARTING ,RUNNING, STOPPING, ABORTING.

3.6.3 lxc-kill

lxc-kill  -n  name  SIGNUM 

lxc-kill将信号SIGNUM发送给容器中的第一个进程。例如:

lxc-kill  -n  foo 2 #向foo中第一个进程发送SIGINT信号 

注意:如果使用lxc-execute运行的命令,信号首先发送给lxc-init,然后lxc-init将该信号传递给运行的第二个进程。如果发送的信号使得lxc-init进程终止,那么该信号不会传递给运行的第二个进程,并且导致容器中得应用结束运行。

4. lxc配置

每个lxc容器都对应于一个配置文件,该文件可在使用lxc-create、lxc-execute和lxc-start命令时指定,如果通过lxc-create创建的容器,则会在/var/lib/lxc/container-name/config中保存该配置。配置信息包括:主机名、网络、cgroup信息等。lxc配置文件中每一行采用key=val的形式,每行保存一个配置项。注释以#开头。

4.1 配置主机名

lxc.utsname = foo #配置主机名为foo

4.2 配置容器使用的虚拟化网络

  • lxc.network.type: 指定容器使用的虚拟化网络类型,该域一般为新的网络配置的开始,其可能值为:

    类型
    empty创建仅有回路接口的网络
    veth创建一个对等网络,该网络的一端分配给容器,另一端与lxc.network.link指定的网桥连接。如果未指定网桥,端设备会被创建但是不会连接到任何网桥上。在默认情况下lxc会对容器外的网络设备选择一个名字,但是可以通过lxc.network.veth.pair选项来指定。
    vlan创建一个由lxc.network.link指定的虚拟局域网接口(网卡)分配给容器,vlan的标识符可由lxc.network.vlan.id指定。
    phys将由lxc.network.link指定的已经存在的接口(网卡)分配给容器。
    macvlan创建一个macvlan接口,该接口和由lxc.network.link指定的接口相连接。
  • lxc.network.flags:指定网络将要执行的动作,可选值为:

    • on: 激活接口
    • down: 关闭接口
  • lxc.network.link: 指定用于进行真实网络通信的接口(网卡)。

  • lxc.network.name: 指定网络接口的名称,在默认情况下,接口名称是动态分配的。

  • lxc.network.hwaddr: 指定虚拟网络接口(虚拟网卡)的MAC地址,在默认情况下,该值自动分配。

  • lxc.network.ipv4: 指定分配给虚拟网卡的ipv4地址。

  • lxc.network.ipv6: 指定分配给虚拟网卡的ipv6地址。

4.3 容器伪终端和系统控制台

  • lxc.pts:如果该项被设置,容器将要拥有一个新的伪终端,并且该终端为容器所私有。该值指定了该伪终端可拥有的最大实例数。
  • lxc.console:指定控制台输出文件的路径
  • lxc.tty:指定容器可得到的tty数量,注意在inittab中getty的数量不能超过指定的值。

4.4 挂载点

  • lxc.mount:指定包含挂载信息的文件,该文件内容采用fstab格式。fstab格式如下:

    spec    file    type    options    dump    pass 
    
    • spec:所需加载文件系统所在的设备或远程文件系统。
    • file:文件系统的挂载点。
    • type:文件系统类型.
    • options:加载文件系统的参数。
    • dump:文件系统转储频率,若不需转储则为0.
    • pass:启动时扫描文件系统的顺序。根文件系统为1,其他为2,若启动时无需扫描则为0.

    如下是一个典型的fstab文件:

    none  /lxc/rootfs.fedora/dev/pts  devpts defaults 0 0 
    none  /lxc/rootfs.fedora/proc     proc  defaults 0 0 
    none  /lxc/rootfs.fedora/sys      sysfs  defaults 0 0 
    
  • lxc.mount.entry: 指定一个挂载点,该行采用fstab格式。

4.5 根文件系统

  • lxc.rootfs:指定容器的根文件系统,若该项未指定,则采用和主机相同的根文件系统。
    例如:

    lxc.rootfs=/lxc/test/rootfs 
    
  • lxc.rootfs.mount:指定调用pivot_root前lxc.rootfs绑定点,一般情况下使用默认值即可。

    备注:pivot_root改变根文件系统,格式为:pivot_root new_root put_old。pivot_root将当前进程的根文件系统移动到目录put_old下,并使得new_root作为新的根文件系统。

  • lxc.pivotdir:指定原始根文件系统在lxc.rootfs下的支点(pivot)。

4.6 控制组配置

4.6.1 控制组CPU配置

  • lxc.cgroup.cpu.shares: 该组的CPU时间份额,默认情况下该值为1024。通过将该值修改为较大值,则该组会获得更多的CPU时间,否则,该组获得的CPU时间会相对较少。比如系统中仅存在控制组A和B ,这两个组中cpu.shares的值分别为1024和2048,则A获得1/3的CPU时间,B获得2/3的时间。注意,子控制组获得CPU时间份额不能超过其父控制组的CPU时间份额。
  • lxc.cgroup.cpu.rt_period_us: 该项指定了控制组CUP资源分配的周期,以防止某些任务一直占用CPU资源。该项以微秒为单位。
  • lxc.cgroup.cpu.rt_runtime_us : 定义了在CUP资源分配周期内控制组中连续占用的CPU资源的时间长度,时间以微秒为单位。比如CUP资源分配周期为5s,控制组连续占用最长时间为4s,则rt_period_us = 5000000,rt_runtime_us=4000000。

4.6.2 块设备配置

  • lxc.cgroup.blkio.weight: 指定当前组块设备优先级权重,权重允许的范围为100~1000.

    在控制组中另外只读的项:

  • blkio.time:控制组中指定使用块设备的时间(毫秒),格式为:major:minor times,其中前两个域为主从设备号,第三个域为时间。

  • blkio.sectors: 组传输的扇区数,格式为:major:minor sectors,其中前两个域为主从设备号,第三个域为传输的扇区数。

4.6.3 CUP计数信息(非配置信息)

在控制组中cpu时间统计信息项:

  • cpuacct.stat: 显示CPU时间信息,显示信息包括两个部分:user和system,分别为用户态和内核态运行的时间。
  • cpuacct.usage: 显示控制组中进程消耗的cpu时间统计。可通过向该文件中写入0来清空时间计数。
  • cpuacct.usage_percpu: 显示控制组中每个cpu时间统计。

4.6.4 CPU集合配置

  • lxc.cgroup.cpuset.cpus:在cpuset中得cpu列表。列表中cpu从0开始编号,如果系统中存在100个cpu那么cpu的编号为0~99. 例如:

    • lxc.cgroup.cpuset.cpus = 0-9 #将0到9号CPU分配给该组
    • lxc.cgroup.cpuset.cpus = 0,3,4-6 #将0,3,4,5,6号CPU分配给该组

    注意:该项指定的CPU集合必须为父cpu集合的子集。

  • lxc.cgroup.cpuset.mems:分配给该cpuset的内存节点列表,例如:

  • lxc.cgroup.cpuset.mems = 0,1 #将内存节点0和1分配给该组

  • lxc.cgroup.cpuset.memory_migrate: 如果该项设置为真,当将一个任务连接到新的cpuset时,任务所在前cpuset中分配给该任务的页也被迁移到新的cpuset中,并且尝试确保页所在节点列表的相对位置不变。比如,分配给任务的某一页在前cpuset内存节点中的第二个节点中,则迁移时会尝试将其放到新的cpuset内存节点列表中第二个内存节点中。此外,如果设定了该项,当cpuset中的内存节点列表(cpuset.mems)发生变化时,分配给任务的页被迁移到新的内存节点列表的节点中。

  • lxc.cgroup.cpuset.cpu_exclusive: cpuset中cpu独占标记,只有在父cpuset设定该项后,子cpuset才可设置该项。当设定该项后,除了祖先和后代cpuset外,lxc.cgroup.cpuset.cpus指定的cpu不允许出现在任何其他cpuset的cpu列表中。

  • lxc.cgroup.cpuset.mem_exclusive:cpuset中内存节点独占标记。配置规则和lxc.cgroup.cpuset.cpu_exclusive相同。

  • lxc.cgroup.cpuset.memory_hadwall:该项指定内核分配内存页和缓存数据时是否仅限于从分配给cpuset的内存节点中分配。在默认情况下,该项是关闭的,内核分配的内存页和缓存由多个用户所共享。

  • lxc.cgroup.cpuset.memory_spread_page和lxc.cgroup.cpuset.memory_spread_slab:这两项控制内核应该从什么地方分配文件系统和相关内核数据结构所需的页。

    • 如果 lxc.cgroup.cpuset.memory_spread_page项为真,那么内核倾向于在发生缺页的任务允许使用的所有内存节点中均匀的分配内存;
    • 否则,会倾向于在任务运行的节点上分配内存。在默认情况下,这两项是关闭的。
    • 例如:
    lxc.cgroup.cpuset.memory_spread_page = 1 # 打开spread page cache功能 
    lxc.cgroup.cpuset.memory_spread_slab = 0 # 关闭spread slab cache功能 
    
  • lxc.cgroup.cpuset.sched_load_balance:如果设定该项为真,内核会对整个cpuset中得所有CPU进行负载平衡调度。

    注意:如果两个重叠的cpuset均打开了该项,那么它们必须在同一个调度域中。

  • lxc.cgroup.cpuset.sched_relax_domain_level: 请求内核改变进行负载平衡时的CPU搜索范围,如果lxc.cgroup.cpuset.sched_load_balance为假,则该项不起作用。其可选等级如下:

    -1  : no request. use system default or follow request of others. 
       0  : no search. 
       1  : search siblings (hyperthreads in a core). 
       2  : search cores in a package. 
       3  : search cpus in a node [= system wide on non-NUMA system] 
    ( 4  : search nodes in a chunk of node [on NUMA system] ) 
    ( 5  : search system wide [on NUMA system] ) 
    

4.6.5 设备白名单

  • lxc.cgroup.devices.allow:将设备添加到白名单中。
  • lxc.cgroup.devices.deny:将设备从白名单中删除。

当前控制组中的白名单列表可从devices.list中得到,白名单列表的每个入口包含四个域,其形式如下:

type    major:minor  access 

其中type可选值为:a , c , b ,a表示所有设备,c为字符设备,b为块设备。

major和minor为设备的主从设备号,用整数或者*表示所有设备。

access为访问权限的结合r(读),w(写)和m(mknod)。

例如:

c   1:3   mr #允许对/dev/null进行读和mknode操作。 
a   *:*   rwm  #允许对所有设备进行读写和mknode操作。 

4.6.6 内存配置

  • lxc.cgroup.memory.limit_in_bytes:当前组的内存使用的最大数。

例如:

lxc.cgroup.memory.limit_in_bytes = 4096  #设定内存限制为4094字节 
lxc.cgroup.memory.limit_in_bytes = 4K #设定内存限制为4K 
lxc.cgroup.memory.limit_in_bytes = 256M #设定内存限制为256M 
lxc.cgroup.memory.limit_in_bytes = 1G #设定内存限制为1G字节 
lxc.cgroup.memory.limit_in_bytes = -1 #设定内存为无限制 
  • lxc.cgroup.memory.memsw.limit_in_bytes:设定当前组中内存+对换空间的限制,设定方式和lxc.cgroup.memory.limit_in_bytes相同,但需保证lxc.cgroup.memory.limit_in_bytes <= lxc.cgroup.memory.memsw.limit_in_bytes.

  • lxc.cgroup.memory.soft_limit_in_bytes:设定内存软限制,这使得系统更大程度上的共享内存,当系统检测到内存争用和短缺时,控制组的内存限制被退回到软限制,防止导致其他控制组饥饿的发生。设定该值时一般要小于硬限制,否则该值会被硬限制所覆盖。

  • lxc.cgroup.memory.use_hierarchy:打开内存的层次记账功能,注意如果父控制组已经打开该功能,则子控制组对该项的设置将不起作用。

  • lxc.cgroup.memory.swappiness:设定控制组的对换区的大小,在默认情况下为60。可选值为0~100。

内存子系统其他报告信息项:

  • memory.stat:显示内存使用的统计信息。
  • memory.usage_in_bytes: 报告当前容器中应用使用的内存量(字节)。
  • memory.memsw.usage_in_bytes: 报告当前容器使用的内存+对换空间的总量。
  • memory.max_usage_in_bytes: 报告容器中内存使用量的最大值。
  • memory.memsw.max_usage_in_bytes: 报告内存+对换空间的最大使用量。
  • memory.failcnt: 应用使用内存数量达到memory.limit_in_bytes的次数。
  • memory.memsw_failcnt: 应用使用内存+对换空间数量达到memory.memsw.limit_in_bytes的次数。

4.6.7 冷冻配置

  • lxc.cgroup.freezer.state: 挂起或者恢复控制组中的任务,其可选值如下:
    • FROZEN:挂起控制组中得任务
    • THAWED:恢复控制组中任务的运行。

在配置文件中配置该项为FROZEN可使得应用在容器开始运行的状态为挂起状态。在默认情况下该值设定为THAWED。注意,无法向处于冷冻状态的控制组中添加任务。

4.6.8 网络通信配置

net_cls子系统通过对网络包进行标记从而使得linux流量控制器(tc)识别网络报来自于那个特定的控制组,从而获得该网络包的优先级。

lxc.cgroup.net_cls.classid: 该值指示了当前控制组的类别句柄(handle),该值采用十六进制表示。句柄的格式如下:

0xAAAABBBB

其中AAAA是十六进制主ID号,BBBB是十六进制从ID号,比如0x100001表示10:1(iproute2工具集使用十六进制表示句柄ID)。此处设定的为linux流量控制器中的类标识符。具体可参考TC相关信息。

4.6.9 权限配置

如果容器作为超级用户运行,指定其可放弃的权限。

lxc.cap.drop:指定容器放弃的权限。该行指定了一些用空格分隔的权限。权限的格式为相应权限定义除去CAP_前缀后的小写形式,比如CAP_SYS_MODULE使用sys_module指定,通过指定sys_chroot可剥夺容器中所有进程进行chroot的权限,附录7.3中显示容器放弃的执行权限,注意,被剥夺的权限不能被恢复,即使root也没有权限来恢复被剥夺的权限,只能重启系统或者重新运行容器并将相应配置选项移除。

例:

lxc.cap.drop = sys_module mknod setuid net_raw 
lxc.cap.drop = mac_override 
lxc.cap.drop = sys_chroot 

4.6.10 其他配置项

  • notify_on_release: 当该项被打开时,在控制组最后一个程序结束时,内核执行由release_agent指定的程序。在默认情况下该项是关闭的。

  • release_agent: 保存控制组中最后一个程序结束运行时执行的程序的路径,该项只在根控制组中出现。

5. 连接容器实例

5.1 运行Fedora基本安装虚拟机

  • 1)安装febootstrap工具

    yum install febootstrap 
    
  • 2)生成fedora基本安装系统

    mkdir  /lxc 
    cd  /lxc 
    febootstrap   fedora-13  rootfs.fedora 
    

    在rootfs.fedora目录下生成了基本安装的fedora13系统。

  • 3)基本安装文件不能直接在linux容器使用,需做如下修改:

    • 1、由于udev不在lxc容器中运行,首先需要人工创建需要的设备,编写脚本如下:

      #!/bin/bash 
      
      ROOT=$(pwd) 
      DEV=${ROOT}/dev 
      if [ $ROOT = '/' ]; then 
      printf "\033[22;35m\nDO NOT RUN ON THE HOST NODE\n\n" 
      tput sgr0 
      exit 1 
      fi 
      if [ ! -d $DEV ]; then 
      printf "\033[01;33m\nRun this script in rootfs\n\n" 
      tput sgr0 
      exit 1 
      fi 
      rm -rf ${DEV} 
      mkdir ${DEV} 
      mknod -m 666 ${DEV}/null c 1 3 
      mknod -m 666 ${DEV}/zero c 1 5 
      mknod -m 666 ${DEV}/random c 1 8 
      mknod -m 666 ${DEV}/urandom c 1 9 
      mkdir -m 755 ${DEV}/pts 
      mkdir -m 1777 ${DEV}/shm 
      mknod -m 666 ${DEV}/tty c 5 0 
      mknod -m 666 ${DEV}/tty0 c 4 0 
      mknod -m 666 ${DEV}/tty1 c 4 1 
      mknod -m 666 ${DEV}/tty2 c 4 2 
      mknod -m 666 ${DEV}/tty3 c 4 3 
      mknod -m 666 ${DEV}/tty4 c 4 4 
      mknod -m 600 ${DEV}/console c 5 1 
      mknod -m 666 ${DEV}/full c 1 7 
      mknod -m 600 ${DEV}/initctl p 
      mknod -m 666 ${DEV}/ptmx c 5 2 
      
      exit 0 
      保存到/usr/local/bin/lxc-config中,并添加可执行权限。 
      chmod u+x /usr/local/bin/lxc-config 
      
      切换到rootfs.fedora下运行该脚本: 
      cd  /lxc/rootfs.fedora 
      /usr/local/bin/lxc-config 
      
    • 2、配置rootfs.fedora

      cp /etc/resolv.conf /lxc/rootfs.fedora/etc 
      chroot  /lxc/rootfs.fedora  /bin/bash 
      #挂载proc , sys, /dev/pts 
      mount  -t proc none /proc 
      mount  -t sysfs none /sys 
      mount  -t devpts none /dev/pts 
      #添加一些应用 
      yum update 
      yum  -y  reinstall  glibc-common 
      yum  install  httpd  php-mysql  mysql-server  nano openssh-clients vim 
      #在/下添加一些必要的文件 
      touch  /etc/fstab 
      rm  /etc/mtab 
      ln  -s  /proc/mounts   /etc/mtab 
      #卸载proc ,sys和/dev/pts 
      umount  -t proc none /proc 
      umount  -t sysfs none /sys 
      umount  -t devpts none  /dev/pts 
      #设定根密码 
      passwd 
      
    • 3、根文件系统配置

      #编辑/lxc/rootfs.feodar/etc/sysconfig/init文件,将最后一行改为: 
      ACTIVE_CONSOLES = /dev/tty[1-4] 
      #编辑/lxc/rootfs.fedora/etc/rc.sysinit将下行注释掉: 
      /sbin/start_udev 
      #编辑/lxc/rootfs.fedora/etc/sysconfig/network添加如下两行: 
      NETWORKING=yes 
      HOSTNAME=fedora 
      
    • 4、创建lxc配置文件/lxc/config.fedora

      lxc.utsname = fedora 
      lxc.tty = 4 
      lxc.network.type = veth 
      lxc.network.flags = up 
      lxc.network.link = br0 
      lxc.network.name = eth0 
      lxc.network.mtu = 1500 
      lxc.network.ipv4 = 192.168.0.65/24 
      lxc.rootfs = /lxc/rootfs.fedora 
      lxc.mount = /lxc/fstab.fedora 
      lxc.cgroup.devices.deny = a 
      # /dev/null and zero 
      lxc.cgroup.devices.allow = c 1:3 rwm 
      lxc.cgroup.devices.allow = c 1:5 rwm 
      # consoles 
      lxc.cgroup.devices.allow = c 5:1 rwm 
      lxc.cgroup.devices.allow = c 5:0 rwm 
      lxc.cgroup.devices.allow = c 4:0 rwm 
      lxc.cgroup.devices.allow = c 4:1 rwm 
      # /dev/{,u}random 
      lxc.cgroup.devices.allow = c 1:9 rwm 
      lxc.cgroup.devices.allow = c 1:8 rwm 
      # /dev/pts/* - pts namespaces are "coming soon" 
      lxc.cgroup.devices.allow = c 136:* rwm 
      lxc.cgroup.devices.allow = c 5:2 rwm 
      # rtc 
      lxc.cgroup.devices.allow = c 254:0 rwm 
      
    • 5、创建fstab文件/lxc/fstab.fedora

      none /lxc/rootfs.fedora/dev/pts devpts defaults 0 0 
      none /lxc/rootfs.fedora/proc proc defaults 0 0 
      none /lxc/rootfs.fedora/sys sysfs defaults 0 0 
      /etc/resolv.conf /lxc/rootfs.fedora/etc/resolv.conf none bind 0 0 
      
    • 6、删除不必要的init脚本

      # 删除/lxc/rootfs.fedora/etc/init目录下,除下述文件外的所有文件: 
      rc.conf 
      start-ttys.conf 
      tty.conf 
      
      # 将rc.conf和start-ttys.conf第一行修改为 
      start  on  startup 
      
      # 将rc.conf中最后一行的 
      exec  /etc/rc.d/rc  $RUNLEVEL 
      # 修改为: 
      exec  /etc/rc.d/rc.fedora 
      
      # 创建/lxc/rootfs.fedora/etc/rc.d/rc.fedora 
      #!/bin/bash 
      route  add default  gw 192.168.1.1 
      /etc/init.d/rsyslog  start  & 
      /etc/init.d/iptables  start  & 
      /etc/init.d/sshd  start  & 
      /etc/init.d/mysqld  start  & 
      /etc/init.d/httpd  start & 
      
      # 将其属性设置为可执行: 
      chmod  u+x  /lxc/rootfs.fedora/etc/rc.d/rc.fedora 
      
      # 删除/lxc/rootfs.fedora/etc/init.d目录下除下述文件外的所有文件: 
      httpd 
      iptables 
      mysqld 
      rsyslog 
      sshd 
      
  • 4)创建linux容器

    # 创建网桥: 
    brctl add br0 
    brctl setfd br0 0 
    brctl addif br0 eth0 
    ifconfig br0 192.168.74.130 up  #此处应为本机IP地址 
    ifconfig eth0 0.0.0.0 up 
    route add -net default gw 192.168.74.2 br0  #设置网关 
    
    # 创建容器 
    lxc-create  -f /lxc/config.fedora  -n fedora 
    
    # 运行容器 
    lxc-start  -n  fedora   #lxc-start在未指定运行的命令的情况下默认执行init程序 
    
    # 连接到该容器,你会看到登陆界面: 
    lxc-console  -n fedora 
    
    # 注意:在登陆后需用根用户添加网关才能联网: 
    route add -net default gw 192.168.74.2 eth0 
    

6. 网络流量配置

6.1 lxc运行虚拟机网络流量配置

linux容器通过内核流量控制子系统(traffic control subsystem)对流量进行限制,配置文件中lxc.net_cls.classid指示了流量控制器中类的标示。现在采用基于容器IP地址的方法,使用如下脚本利用TC工具创建HTB队列规则,tc命令的使用可参考附录和参考部分:

Shell代码

#!/bin/bash  
  
#remove all classful qdisc first  
tc qdisc del dev eth0 root  
  
#create root htb qdisc, specify all unclassified packet into subclass 4  
tc qdisc add dev eth0 root handle 1:0 htb default 4  
  
#create subclasses  
tc class add dev eth0 parent 1:0 classid 1:2 htb rate 256kbps ceil 512kbps prio 0  
tc class add dev eth0 parent 1:0 classid 1:3 htb rate 128kbps ceil 256kbps prio 1  
tc class add dev eth0 parent 1:0 classid 1:4 htb rate 64kbps  ceil 128kbps prio 2  
  
#attach a sfq qdisc for each leaf class  
tc qdisc add dev eth0 parent 1:2 handle 2: sfq perturb 5  
tc qdisc add dev eth0 parent 1:3 handle 3: sfq perturb 5  
tc qdisc add dev eth0 parent 1:4 handle 4: sfq perturb 5  
  
#attach filters to root htb qdisc  
tc filter add dev eth0 protocol ip parent 1:0 u32 match ip src 192.168.74.10  flowid 1:2  
tc filter add dev eth0 protocol ip parent 1:0 u32 match ip src 192.168.74.30  flowid 1:3  
tc filter add dev eth0 protocol ip parent 1:0 u32 match ip src 192.168.74.130 flowid 1:4  

注意,此处根据IP地址将数据包放置到不同的class中,也可采用其他的filter来实现不同的分类规则。u32过滤器可根据数据包的信息对数据包进行归类。此外,本章中容器通过配置桥接来进行网络通信,对于其他的链接网络的形式尚未进行测试。

6.2 利用classid进行流量控制

在linux控制组中net_cls子系统通过对网络包打标记classid来表示属于不同控制组的网络包。linux系统中流量控制系统可根据该标记来对控制组的网络包进行控制。注意,net_cls子系统并不是通过修改网络包来打标记的,当网络包通过协议栈的过程中,通过当前的进程上下文来的到控制组信息,进而获得当前网络包的classid,因此,如果网络包被包过滤器处理之前被重新调度,那么就无法获得该包的classid!因此,对于采用6.1中网桥方式进行网络通信的方式,运行fedora虚拟机时无法采用classid对网络包进行分类管理。下面给出了一个简单的利用classid进行网络流量限制的配置脚本,使用的cgroup过滤器,根据网络包进程上下文中得到的classid对网络包进行分类。

Shell代码

#!/bin/bash  
  
dev=eth0  
tc qdisc del dev $dev root  
# set qdisc as htb  
  
tc qdisc add dev $dev parent root handle 10: htb   
  
# create some claess for root qdisc  
  
tc class add dev $dev parent 10: classid 10:1 htb rate 1mbps ceil 1mbps  
tc class add dev $dev parent 10:1 classid 10:2 htb rate 256kbps ceil 256kbps   
tc class add dev $dev parent 10:1 classid 10:3 htb rate 256kbps ceil 256kbps   
tc class add dev $dev parent 10:1 classid 10:4 htb rate 512kbps ceil 1mbps  
  
# add sfq qdisc for every leaf class  
tc qdisc add dev $dev parent 10:2 handle 2:0 pfifo  
tc qdisc add dev $dev parent 10:3 handle 3:0 pfifo  
tc qdisc add dev $dev parent 10:4 handle 4:0 pfifo  
  
# add filter for root qdisc  
  
tc filter add dev $dev parent 10: protocol ip prio 1 handle 10: cgroup  

7. 附录和参考

7.1 brctl命令简介

该命令用于建立、维护和检查Ethernet网桥在Linux内核中的配置,如下是该命令的简要使用:

brctl  addbr  <name>:添加一个名称为name的网桥。 
brctl  delbr  <name>:删除名称为name的网桥。 
brctl   addif  <name>  <device>:将接口device连接到网桥name上。 
brctl   delif  <name>  <device>:将接口device从网桥name上移除。 
brctl   setfd  <name>  <time>:设定网桥name的转送延时。 

更多信息可参考 http://linux.die.net/man/8/brctl

7.2 tc命令简介

该命令用于linux内核的网络流量控制,在本文档中使用有类型的队列规则(classful qdisc)。下面给出一个配置实例,更多信息可参考http://linux.die.net/man/8/tc

1)假设由另个客户A和B通过接口eth0连接到网络,我们系统分配给用户B的带宽为60kbps,分配给用户A的带宽为40kbps。此外,希望将A用户的30kbps的带宽按照如下划分:www占用其中的30kbps,剩余的10kbps用于其他应用。系统中任何未使用的带宽均可允许A或者B使用。

  • a)在接口eth0上创建一个htb qdisc,其句柄为1:0,未分类数据包发送给类1:12

    tc qdisc add dev eth0 root handle 1:  htb  default 12 
    
  • b)为qdisc创建htb类以及子类

    tc class add dev eth0 parent 1:  classid 1:1  htb rate 100kbps ceil 100kbps 
    tc class add dev eth0 parent 1:1 classid 1:10 htb rate 30kbps ceil 100kbps 
    tc class add dev eth0 parent 1:1 classid 1:11 htb rate 10kbps ceil 100kbps 
    tc class add dev eth0 parent 1:1 classid 1:12 htb rate 60kbps ceil 100kbps 
    
  • c) 为qdisc添加filter,假设A用户的IP地址为1.2.3.4

    tc filter add  dev eth0 protocol ip parent 1:0 prio 1 u32 match ip src 1.2.3.4 ip dprot 80 0xFFFF flowid 1:10 
    tc filter add  dev eth0 protocol ip parent 1:0 prio 1 u32 match ip src 1.2.3.4 flowid 1:11 
    

    上面第一行指定A的www服务的包发到类1:10,第二行指定其他包发给类1:11

  • d) 为每个leaf class添加队列(可选)

    tc qdisc add dev eth0 parent 1:10 handle 20:  pfifo  limit 5 
    tc qdisc add dev eth0 parent 1:11 handle 30:  pfifo  limit 5 
    tc qdisc add dev eth0 parent 1:12 handle 40:  pfifo  limit 10 
    

7.3 超级用户权限

在linux2.2内核开始将超级用户的权限分为若干独立的子权限,每个子权限可独立的禁止或者打开,注意,一旦剥夺了根用户的某一权限,那么除非重启系统,否则无法恢复该权限。下面对这些权限进行简单的介绍:

权限说明
CAP_CHOWN允许改变文件的所有权
CAP_DAC_OVERRIDE忽略对文件的所有DAC访问限制
CAP_DAC_READ_SEARCH忽略所有对读、搜索操作的限制
CAP_FOWNER如果文件属于进程的UID,就取消对文件的限制
CAP_FSETID允许设置setuid位
CAP_KILL允许对不属于自己的进程发送信号
CAP_SETGID允许改变组ID
CAP_SETUID允许改变用户ID
CAP_SETPCAP允许向其它进程转移能力以及删除其它进程的任意能力
CAP_LINUX_IMMUTABLE允许修改文件的不可修改(IMMUTABLE)和只添加(APPEND-ONLY)属性
CAP_NET_BIND_SERVICE允许绑定到小于1024的端口
CAP_NET_BROADCAST允许网络广播和多播访问
CAP_NET_ADMIN允许执行网络管理任务:接口、防火墙和路由等。
CAP_NET_RAW允许使用原始(raw)套接字
CAP_IPC_LOCK允许锁定共享内存片段
CAP_IPC_OWNER忽略IPC所有权检查
CAP_SYS_MODULE插入和删除内核模块
CAP_SYS_RAWIO允许对ioperm/iopl的访问
CAP_SYS_CHROOT允许使用chroot()系统调用
CAP_SYS_PTRACE允许跟踪任何进程
CAP_SYS_PACCT允许配置进程记帐(process accounting)
CAP_SYS_ADMIN允许执行系统管理任务:加载/卸载文件系统、设置磁盘配额、开/关交换设备和文件等。
CAP_SYS_BOOT允许重新启动系统
CAP_SYS_NICE允许提升优先级,设置其它进程的优先级
CAP_SYS_RESOURCE忽略资源限制
CAP_SYS_TIME允许改变系统时钟
CAP_SYS_TTY_CONFIG允许配置TTY设备
CAP_MKNOD允许使用mknod()系统调用
CAP_LEASEAllow taking of leases on files

7.4 cgroup模块编写

lxc建立在cgroup的基础上,在2.6.35以上内核中支持将控制组子系统通过模块的形式加入到内核中,本节演示了如何编写一个控制组子系统模块。在当前的内核中支持的子系统的最大个数为unsigned long类型的bit位数,当前的子系统个数为8个。编写简单过程如下:

    1. 定义struct cgroup_subsys结构
struct cgroup_subsys  sample = {  
    .create = sample_create,  
    .destroy = sample_destroy,  
    .populate = sample_populate,  
    .module = THIS_MODULE,  
};  

struct cgroup_subsys结构定义了控制组子系统结构,其中主要包含了一些控制组子系统接口和一些属性信息,其中create和destory接口必须定义,这两个接口实现了创建和销毁控制组子系统状态对象。

    1. 定义子系统状态结构,该结构中必须要包含struct cgroup_subsys_state结构已便于编程处理.
struct  cgroup_hello_state {  
    struct cgroup_subsys_state  css;  
    int  helloid;  
    /*其他相关信息*/  
};  
    1. 定义一个全局变量只是hello子系统的id,记录系统分配给子系统的编号,以便于后面编程处理.
int  hello_subsys_id ; 
    1. 定义子系统在控制组目录中的控制文件接口.
struct  cftype cft[]={  
    {  
        .name = “hello”,  
        .read_u64 = sample_read_u64,  
        .write_u64 = sample_write_u64,  
    },  
}  

struct cftype结构定义了控制文件的处理句柄以及相关名称和属性信息,在这里仅仅实现文件的读写句柄.

    1. 实现接口。

完整的代码如下:

#include <linux/module.h>  
#include <linux/slab.h>  
#include <linux/types.h>  
#include <linux/cgroup.h>  
MODULE_LICENSE("Dual BSD/GPL");  
/*控制组子系统结构*/  
struct cgroup_hello_state{  
    struct cgroup_subsys_state css;  
    int helloid;  
    /*其他状态信息*/  
};  
  
int  hello_subsys_id;  
  
static struct cgroup_subsys_state *cgrp_create(struct cgroup_subsys *ss,struct cgroup *cgrp);  
static void cgrp_destroy(struct cgroup_subsys *ss,struct cgroup * cgrp);  
static int cgrp_populate(struct cgroup_subsys *ss,struct cgroup * cgrp);  
static void cgrp_attach(struct cgroup_subsys *ss,struct cgroup * cgrp,struct cgroup *old_cgrp,struct task_struct *task);  
/*定义控制组子系统结构*/  
struct cgroup_subsys cgroup_hello_subsys ={  
    .name = "hello",  
    .create = cgrp_create,  
    .destroy = cgrp_destroy,  
    .populate = cgrp_populate,  
    .attach = cgrp_attach,  
    .module = THIS_MODULE,  
};  
  
static void cgrp_attach(struct cgroup_subsys *ss,struct cgroup * cgrp,struct cgroup *old_cgrp,struct task_struct *task){  
    printk(KERN_ALERT "%s:%d\n",__func__,task->pid);  
}  
static inline struct cgroup_hello_state *cgrp_hello_state(struct cgroup *cgrp){  
    return container_of(cgroup_subsys_state(cgrp,hello_subsys_id),struct cgroup_hello_state,css);  
}  
  
static struct cgroup_subsys_state *cgrp_create(struct cgroup_subsys *ss,struct cgroup *cgrp){  
    struct cgroup_hello_state *cs;  
    cs = kmalloc(sizeof(*cs),GFP_KERNEL);  
    if(!cs)  
        return ERR_PTR(-ENOMEM);  
    printk(KERN_ALERT "%s\n",__func__);  
    return &cs->css;  
}  
  
static void cgrp_destroy(struct cgroup_subsys *ss,struct cgroup * cgrp){  
    kfree(cgrp_hello_state(cgrp));  
}  
  
static u64 read_helloid(struct cgroup *cgrp,struct cftype *cft);  
static int write_helloid(struct cgroup *cgrp,struct cftype *cft, u64 val);  
/*控制文件操作对象结构*/  
struct cftype ss_files[]={  
    {  
        .name = "hello",  
        .read_u64 = read_helloid,  
        .write_u64 = write_helloid,  
    },  
};  
/*创建本子系统的控制文件*/  
static int cgrp_populate(struct cgroup_subsys *ss,struct cgroup *cgrp){  
    return cgroup_add_files(cgrp,ss,ss_files,ARRAY_SIZE(ss_files));  
}  
  
static u64 read_helloid(struct cgroup *cgrp, struct cftype *cft){  
    printk(KERN_ALERT "%s:%s\n",__func__,cft->name);  
    return cgrp_hello_state(cgrp)->helloid;  
}  
static int write_helloid(struct cgroup *cgrp,struct cftype *cft,u64 val){  
    printk(KERN_ALERT "%s:%s\n",__func__,cft->name);  
    cgrp_hello_state(cgrp)->helloid = (int)val;  
    return 0;  
}  
  
static int hello_init(void){  
    int ret;  
    printk(KERN_ALERT "Hello,world\n");  
    ret = cgroup_load_subsys(&cgroup_hello_subsys);  
    if(ret)  
        goto out;  
    hello_subsys_id = cgroup_hello_subsys.subsys_id;  
    printk(KERN_ALERT "subsys id:%d\n",hello_subsys_id);  
out:  
    return ret;  
}  
  
static void hello_exit(void){  
    cgroup_unload_subsys(&cgroup_hello_subsys);  
    printk(KERN_ALERT "Goodbye, cruel world\n");  
}  
module_init(hello_init);  
module_exit(hello_exit);  

0. 结束语

上述文档可能存在疏漏错误的地方,还望大家指正~~~~





 类似资料: