简介
一、什么是虚拟化和容器化
实体计算机叫做物理机,又时也称为寄主机;
虚拟化:将一台计算机虚拟化为多态逻辑计算机;
容器化:一种虚拟化技术,操作系统的虚拟化;将用户空间软件实例分割成多个独立的单元,变成多个实例;docker就是容器技术的事实标准;
二、为什么要虚拟化和容器化
1.资源利用率高;
2.环境标准化,保证测试的环境保持不变;
3.资源弹性伸缩,可以灵活实现资源扩缩容;
4.支持差异化的环境;
5.沙箱安全,容器之间不会互相影响;
6.比虚拟机更加的轻量化,启动更快,一般的虚拟机启动会加载整个内核,而容器以进程的方式运行于内核上,启动就会更快;
7.维护和扩展更加容易,进行复制粘贴镜像文件快速扩展;
三、虚拟化实现方式
3.1应用程序执行环境分层
应用程序层、库函数层、操作系统层、硬件层;
3.2虚拟化常见类别
虚拟机:对硬件层抽象接口进行模拟,模拟出一种虚拟的硬件设备再将操作系统包括之上的层嫁接到虚拟硬件上;
容器:对操作系统的接口进行模拟,将函数库层以上的功能置于操作系统中,可以使用进程的方式启动;
JVM之类的虚拟机:在函数库层与应用程序层之间添加一层接口,对下提供不同的版本支持不同操作系统,对上提供统一的运行环境;
3.3常见虚拟化实现
3.3.1主机虚拟化
主机虚拟化:通过在物理服务器上安装一个虚拟化层实现;分为两种一种是虚拟层运行在硬件之上(典型框架Vmware ESX),一种是虚拟机运行在操作系统之上(Vmware Workstation);使用Hypervisor实现;
3.3.2容器虚拟化
容器虚拟化实现原理:通过namespace进行各程序的隔离,加上cgroups的控制进行虚拟化;
3.3.2.1namespace
是什么:
namespace:是 Linux 内核用来隔离内核资源的方式。通过 namespace 可以让一些进程只能看到与自己相关的一部分资源,而另外一些进程也只能看到与它们自己相关的资源,这两拨进程根本就感觉不到对方的存在。具体的实现方式是把一个或多个进程的相关资源指定在同一个 namespace 中。
Linux namespaces 是对全局系统资源的一种封装隔离,使得处于不同 namespace 的进程拥有独立的全局系统资源,改变一个 namespace 中的系统资源只会影响当前namespace 里的进程,对其他 namespace 中的进程没有影响。
Linux 提供了多个 API 用来操作 namespace,它们是 clone()、setns() 和 unshare() 函数,为了确定隔离的到底是哪项 namespace,在使用这些 API 时,通常需要指定一些调用参数:CLONE_NEWIPC、CLONE_NEWNET、CLONE_NEWNS、CLONE_NEWPID、CLONE_NEWUSER、CLONE_NEWUTS 和CLONE_NEWCGROUP。如果要同时隔离多个 namespace,可以使用 | (按位或)组合这些参数。
namespace隔离的全局系统资源
UTS:主机名和域名,对应参数是CLONE_NEWUTS;
IPC:信号量、消息队列、共享内存等进程间通信资源,对应参数是CLONE_NEWIPC;
PID:进程编号,对应参数是CLONE_NEWPID ;
NETWORK:网络设备、网络栈、端口,对应参数是CLONE_NEWNET;
MOUNT:文件系统挂载点,对应参数是CLONE_NEWNS;
USER:用户和用户组,对应参数是:CLONE_NEWUSER;
3.3.2.2空间隔离实战
基础知识:
1.dd命令,用于读取,转换并输出数据;
dd 参数
#参数包括
#if=文件名:输入文件名;of=文件名:输出文件名;ibs=bytes:一次读入的字节数;obs=bytes:一次输出的字节数;bs=bytes:同时指定输入输出的字节数;cbs=bytes:一次转换的字节数;skip=blocks:从输入文件开头跳过blocks个块之后开始复制;seek=blocks:从输出文件开头跳过blocks个块之后再开始复制;count=blocks:仅拷贝blocks个块;conv=<关键字>:输出文件内容进行转换
#如:
#dd if=/dev/zero of=test.img bs=8k count=1024
#dd if=in.txt of=out.txt conv=ucase
2.mkfs命令,创建Linux文件系统,即格式化;
#如:
#mkfs -t ext4 ./test.img
对此文件进行格式化
3.df命令,显示磁盘使用情况;
df 参数
#-a表示将所有含有0blocks的文件系统显示出来
#-h使用人类可读的方式
#-H与-h类似,但是单位是1000不是1024
#-t限制文件系统的类型
#-T显示文件系统的形式
4.mount命令,加载文件系统到指定的加载点,Windows会自动挂载,但是Linux需要手动挂载;
mount 参数
#-l表示显示已加载的文件系统列表
#device表示指定设备的文件系统
#dir表示挂接到的目录下
#-t可以支持的文件系统,可以自动识别不指定
#-o 可选项 loop将一个文件当作一个磁盘,ro采用只读方式挂接,rw采用只写方式挂接
#如
#mount test.img /data/testmymount
5.unshare命令,使用与父程序不共享的命名空间运行程序,即给运行的程序创建一个新的命名空间;
unshare [选项] 程序 [参数]
#-i表示不共享ipc空间
#-m表示不共享mount空间
#-n表示不共享net空间
#-p表示不共享pid空间
#-u表示不共享uts空间
#-U表示不共享user空间
#-V版本查看
#--fork创建一个子进程,执行unshared传入的参数
#--mount-proc表示执行子进程之前将proc优先挂载过去
#案例1——uts隔离
unshare -u /bin/bash
#案例2——pid隔离
需要注意应该创建一个子进程,否则会因为新的命名空间会使用unshare的pid作为新空间的父进程,但是空间却看不到unshare进程而无法创建;
unshare --fork --mount-proc -p /bin/bash
#案例3——文件系统隔离
创建-m命名空间,目录和镜像文件,进行挂载;
6.umount 挂载点,取消文件挂载,之后再删除目录即可
3.3.2.3cgroups
是什么:
cgroups(Control Groups) 是 linux 内核提供的一种机制,这种机制可以根据需求把一系列系统任务及其子任务整合 ( 或分隔 ) **到按资源划分等级的不同组内,从而为系统资源管理提供一个统一的框架。**简单说,cgroups 可以限制、记录任务组所使用的物理资源。本质上来说,cgroups 是内核附加在程序上的一系列钩子(hook),通过程序运行时对资源的调度触发相应的钩子以达到资源追踪和限制的目的。
换句话说就是一堆进程配对不同的配置文件,实现对资源精细化地控制,优先级控制,进程控制,统计;
可以控制的子系统:
3.3.2.4控制组实战
基础知识:
1.pidstat命令,用于监控全部或者指定进程的CPU、内存、线程、设备IO等系统资源的占用情况;
pidstat [选项] [时间间隔] [次数]
#-u默认参数,不加选项显示cpu的统计情况
#-r显示内存的统计情况
#-d显示io统计情况
#-p指定进程号,all表示所有进程
#-C指定命令
#-l显示命令名和参数
2.stress命令,压力测试工具;
stress [选项 [参数]]
#-c 进程数,产生n个进程,每个进程循环调用sqrt函数产生CPU压力,用户CPU
#-i 进程数,产生n个进程,每个进程循环调用sync将内存缓冲区内容写到磁盘中产生IO压力,内核CPU
#-m 进程数,产生n个进程,每个进程循环调用malloc/free函数分配和释放内存,--vm-bytes B:指定分配内存大小 --vm-keep:一直占用内存,不释放
#-d 进程数,产生n个进程,每个进程不断执行write和unlink,--hdd-bytes B:指定文件大小
#-t 秒数,n秒结束进程
#-q 程序在运行过程中不进行输出
实操:
进行控制需要在控制组目录下创建控制组,进行配置并且挂接,需要注意取消控制并删除目录需要将tasks和cgroup.procs清空,当还是无法删除时,需要退出终端再次进行删除即可;
案例1:cgroups信息查看
#版本查看
cat /proc/filesystems|grep cgroup
#子系统查看
cat /proc/cgroups
#挂载信息查看
mount -l|grep cgroup
#查看进程的cgroup限制
cat /proc/$$/cgroup
/sys/fs/cgroup/cpu目录下存放着cpu控制组信息,cpu.cfs_period_us表示时间片的数量,cpu.cfs_quota_us表示可以使用数量,-1表示不受控制;
案例2:使用cgroups对内存进行控制
1.创建控制组
mount |grep cgroup
cd /sys/fs/cgroup/memory
mkdir test_memlimit/
cd test_memlimit/
2.限制内存20m
echo '20971520'>memory.limit_in_bytes
3.将进程添加到控制组中
echo 'pid'>tasks
#当内存不足是进程就会挂掉
案例3:使用cgroups对CPU进行控制
1.创建CPU控制组
mount |grep cgroup
cd /sys/fs/cgroup/cpu
mkdir test_mycpu
cd test_mycpu
2.创建stress进程打满CPU,使用pidstat监控stress
stress -c 1 -q
pidstat -C stress -p ALL -u 2 1000
3.使用控制组对CPU进行限制
cpu.cfs_period_us表示cpu带宽,将CPU的一次运行切成了10万份,每一个是一个时间片
cpu.cfs_quota_us表示控制组中进程可以使用的带宽;
4.将stress添加到控制组当中
echo 'pid'>tasks
#会导致cpu使用率倍限制
3.3.2.5Linux容器——LXC
是什么:
LXC(LinuX Containers)Linux 容器,一种操作系统层虚拟化技术,为 Linux 内核容器功能的一个用户空间接口。它将应用软件系统打包成一个软件容器(Container),内含应用软件本身的代码,以及所需要的操作系统核心和库。透过统一的名字空间和共享 API 来分配不同软件容器的可用硬件资源,创造出应用程序的独立沙箱运行环境,使得 Linux 用户可以容易的创建和管理系统或应用容器;
换句话说就是将软件本身和库以及软件运行所依赖的共享操作系统配置(内核资源)进行打包;通过使用namespace和cgroups技术来调用内核资源;
将LXC以进程的方式运行,内部存放大量的封装好的包,从上往下调用库文件,系统调用,内核中使用namespace和cgroups机制调用内核资源,在向下调用驱动程序;
但是缺点就是将容器放到另一台主机上运行,并不能很好地支持;
3.3.2.6LXC实战
基础知识:
常用命令
1.lxc-checkconfig,检查系统环境是否满足容器的使用需要;
lxc-checkconfig
2.lxc-create,创建lxc容器;
模板的路径在/usr/share/lxc/templates/目录下;
lxc-create -n 容器名 -t 模板名称 -- 后面加模板的参数 #-r 表示发行的版本 -a表示CPU架构
3.lxc-start,启动容器;
lxc-start -n 容器名 -d#-d表示在后台运行
4.lxc-ls,列出所有容器,-f表示打印常用的信息;
lxc-ls -f#以表格的形式打印常用信息
5.lxc-info,查看容器相关的信息;
lxc-info -n 容器名
6.lxc-attach,进入容器执行命令;
lxc-attach -n 容器名 命令
7.lxc-stop,停止容器,释放cpu和内存;
lxc-stop -n 容器名
8.lxc-destroy,删除处于停机状态的容器,释放磁盘空间;
lxc-destroy -n 容器名
安装lxc:
1.安装yum源
yum install -y epel-release
2.安装程序
yum install -y lxc lxc-templates bridge-utils lxc-libs libcgroup libvirt lxc-extra debootstrap
3.启动服务
systemctl start lxc
systemctl start libvirtd
systemctl status lxc
systemctl status lxc
实操:
案例1:容器的创建;
需要使用root权限执行,容器创建好之后会自动形成一个用户,此用户就是容器中对应的root;
lxc-create -n container1 -t ubuntu -- -r xenial -a amd64
案例2:设置成后台运行,否则就需要进行登录;
lxc-start -n lxchost1 -d
案例3:进入容器,进入之后可以观察到已经和原宿主机有了很大的差别,已经形成了隔离;
方式一:容器创建好之后的root用户名,ip使用lxc-ls -f查看
ssh 用户名@主机ip
方式二:
lxc-attach -n 容器名 --clear-env -- /bin/bash
案例4:停止容器
lxc-stop -n lxchost1
案例5:释放容器
lxc-destroy -n lxchost1
3.4Docker是什么
3.4.1docker本质
本质就是LXC的增强版,本身不是容器,是容器的易用工具,是使用Go语言实现的一个开源项目;容器是Linux内核中的技术,docker在使用上进行了简易处理;
相较于LXC,不再使用模板来实现容器的创建,而是使用了镜像技术(把一个操作系统用户空间所需要使用到的组件实现编排好,并整体打包成一个文件,image文件),然后将镜像文件集中放在一个仓库中;需要时只要执行命令就可以创建好,变成了基于镜像文件启动容器,使用docker-run,docker-stop实现启动和停止一个容器;
镜像打包,包括应用程序(包括依赖的动态库),配置文件,运行环境(相关开发包),操作系统相关依赖(操作系统库文件)全部分装成一个镜像文件;
优点就是可以支持一次封装,到处运行,通过build,ship,run实现;
3.4.2docker引擎迭代
最早的docker就是对LXC进行了二次包装,也就是容器引擎是LXC;之后形成了自己的容器引擎libcontainer,之后又研发了RunC,截至目前docker的容器引擎还是RunC;而运行时容器引擎是containerd;也就是说现在docker引擎是靠containerd调用RunC来完成容器管理;
3.4.3docker和虚拟机的区别
1.磁盘占用,传统虚拟机占用几到几十GB的空间,而docker容器占用几十到几百MB;
2…CPU和内存占用,传统虚拟机占用高,docker容器占用少,因为docker容器不经过中间的虚拟化层,效率高;另外虚拟化层的实例大小过大,一般是几百MB大小,而docker容器启动之后是一个进程,没有虚拟化实例的消耗;
3.启动速度,传统虚拟机启动慢,docker容器较快;
4.安装管理,传统虚拟机需要专门的运维技术,而docker容器仅需要使用一个命令就可以实现docker的启动和停止;
5.应用部署,传统虚拟机需要手动部署,将将镜像文件拷贝或者是部分文件拷贝部署,而docker容器可以实现自动部署,速度快,如:更新迭代时,生成新的镜像文件上传到仓库中,部署仅需要从仓库中拉取不同的版本即可;
6.隔离性,传统虚拟机是系统级别上的隔离,而docker容器是进程级别的隔离;
7.封装程度,传统虚拟机将整个操作系统进行了封装,而docker容器只需要将应用程序及相关的各种依赖进行打包即可;
3.4.4docker为什么比虚拟机利用率高,启动快
利用率高:
1.docker不需要对硬件资源进行虚拟化,运行在docker容器中的程序使用的是实际的物理机资源,不需要创建虚拟机实例占用资源;
2.运行效率高,docker容器中的程序直接靠实际的物理机运行,而传统虚拟机先是经过虚拟层,然后才经过实际物理机运行;
启动快:
传统虚拟机需要加载虚拟机实例内核,容器不需要加载内核,而是共用内核的方式运行;
3.4.5docker和JVM虚拟化的区别
1.性能,JVM上的程序运行时需要进行维护抽象出来的虚拟层,换句话说就是除了执行程序的正常逻辑消耗之外,要有部分消耗用来进行虚拟层和函数库层的交互,而docker几乎没有性能上的损失,直接就是调用函数库;
2.虚拟层面,JVM在函数库层,docker在操作系统层;
3.代码无关性,JVM只能支持特定代码的运行,而docker模拟的是操作系统支持任何相同平台的应用程序;
4.隔离性,JVM不涉及隔离主机,只是封装不同操作系统的函数库层差异,对上提供统一的接口,而docker隔离主机,使得不同容器执行时所看到的内容互不可见;