linux内核篇之虚拟化和容器化

如今,inux 服务器也随之变得越来越强大了。无论是计算、网络、存储,都越来越牛。
但是也出现一些问题。

1、资源大小申请不灵活。比如想尝试新业务,只需要单独的4核8G的服务器资源,但是不可能采购这么小规格的机器;以及,每次申请这个资源都需要重新采购,周期长;

2、资源复用不灵活。别人的操作会导致冲突。必须要有自己单独的资源。

为了解决这些问题,可以在物理机上创建虚拟机。每个虚拟机都有自己单独的操作系统、灵活的规格,一个命令就能启动起来。每次创建都是新的操作系统,很好的解决了上面不灵活的问题。

虚拟机需要虚拟化层的支持。
如果你安装virtualBox桌面版,你可以用这个虚拟机软件创建虚拟机,在虚拟机里面安装一个linux,外面的操作吸引也可以是linux。VirtualBox 这个虚拟化软件,和你的 Excel 一样,都是在你的任务栏里面并排放着,是一个普通的应用。

在硬件操作系统看,VirtualBox 就是一个普通的用户态应用。但是它的代码都意味着它是内核是操作系统啊,比如要求虚拟机的程序访问网络,代码就要努力地去操作网卡。尽管我努力,但是我做不到啊,我没有权限!。

所以需要虚拟化层的支持。
三种虚拟化的方式。
完全虚拟化 这相当于骗人的方式,VirtualBox会模拟假的 CPU、内存、网络、硬盘给到我,让我自我感觉良好,感觉自己终于又像个内核了。其实每个CPU,内存等内核指令都是虚拟化软件经过物理内核得到结果的。
这种方式一个坏处就是,慢,而且往往慢到不能忍受。

硬件辅助虚拟化
CPU有四个权限等级,但是Linux大牛们之前没意识到虚拟机,0代表内核3代表用户已经用完了。没办法,只好另起炉灶弄一个新的标志位,表示当前是在虚拟机状态下,还是在真正的物理机内核下。对于虚拟机内核来讲,只要将标志位设为虚拟机状态,我们就可以直接在CPU上执行大部分的指令,不需要虚拟机软件在中间转述,除非遇到特别敏感的指令,才需要将标志位设为物理机内核态运行,这样大大提高了效率。
所以,安装虚拟机的时候,我们务必要将物理 CPU 的这个标志位打开。

半虚拟化
一旦我知道我不是物理机内核,痛定思痛,只好重新认识自己,反而能找出很多方式来优化我的资源访问。
写操作时,很可能和很多虚拟机共享物理资源,所以我要学会排队,我写硬盘其实写的是一个物理机上的文件,那我的写文件的缓存方式是不是可以变一下。我发送网络包,根本就不是发给真正的网络设备,而是给虚拟的设备,我可不可以直接在内存里面拷贝给它,等等等等。虚拟机内核加载特殊的驱动等

对于桌面虚拟化软件,我们多采用 VirtualBox,如果使用服务器的虚拟化软件,则有另外的选型。
服务器上的虚拟化软件,多使用 qemu,其中关键字 emu,全称是 emulator,模拟器。所以,单纯使用 qemu,采用的是完全虚拟化的模式。
当确认开始了标志位之后,通过 KVM,GuestOS 的 CPU 指令不用经过 Qemu 转译,直接运行,大大提高了速度。
然后qemu-kvm会进行优化。CPU 和内存主要使用硬件辅助虚拟化进行加速,需要配备特殊的硬件才能工作;网络和存储主要使用特殊的半虚拟化驱动加速,需要加载特殊的驱动程序

容器化

这可以说是现在非常流行的技术了,比如docker,k8s就是容器化技术。
其实早期就有chroot,linux server技术了。Chroot 可以将进程及其子进程与操作系统的其余部分隔离开来。但是,对于 root process ,却可以任意退出 chroot。
linux server技术主要问题是 VServer 应用程序针对 “chroot-again” 类型的攻击没有很好的进行安全保护,攻击者可以利用这个漏洞脱离限制环境,访问限制目录之外的任意文件。

前面说的虚拟化虽然可以实现资源创建的灵活性。但是虚拟化其实就是创立一个子公司,但毕竟是一些独立的公司,麻雀虽小,五脏俱全,因而就像上一章我们看到的那样,CPU、内存、网络、硬盘全部需要虚拟化,一个都不能偷懒。

那有没有一种更加灵活的方式,既可以**隔离出一部分资源,专门用于某个进程,又不需要费劲周折的虚拟化这么多的硬件呢?**毕竟最终我只想跑一个程序,而不是要一整个 Linux 系统。

容器其实就是打包和标准化的过程。
打包起到隔离作用。
容器实现封闭的环境主要要靠两种技术,一种是看起来是隔离的技术,称为 namespace(命名空间)。在每个 namespace 中的应用看到的,都是不同的 IP 地址、用户空间、进程 ID 等。另一种是用起来是隔离的技术,称为 cgroup(网络资源限制),即明明整台机器有很多的 CPU、内存,但是一个应用只能用其中的一部分。

如何“将这些集装箱标准化”,在哪艘船上都能运输。这里就要用到镜像了。就是在你焊好集装箱的那一刻,将集装箱的状态保存下来。当程序员根据产品设计开发完毕之后,可以将代码连同运行环境打包成一个容器镜像。无论是在开发环境、测试环境,还是生产环境运行代码,都可以使用相同的镜像。

Docker的简单使用
先启动;
systemctl start docker

拉取镜像,最基本的就是操作系统了。我们可以在一台虚拟机上创建任意的操作系统环境了。
docker pull ubuntu:14.04
ocker images查看镜像
如果要自己生成镜像就要dockerfile编译,也可以post到镜像库中。

启动容器
要确定入口,因为我们想尝试命令,所以这里 entrypoint 要设置为 bash
docker run -it --entrypoint bash ubuntu:14.04

几个应用场景:
1、你写了一个程序,然后把它打成了上面一样的镜像。你在本地一运行 docker run 就把他运行起来了。接下来,你交给测试的就不是一个“程序包 + 配置 + 手册”了,而是一个容器镜像了。测试小伙伴同样通过 docker run 也就运行起来了,不存在“你这里跑的起来,他那里跑不起来的情况。测试完了再上生产,交给运维的小伙伴也是这样一个镜像,同样的运行同样的顺畅。

2、弹性伸缩部署。,只要在每台机器上 docker run 一下就搞定了。等哪天想用另一个云了,不用怕应用迁移不走,只要在另外一个云上 docker run 一下就解决了。

你可能会问,**多个容器运行在一台机器上,不会相互影响吗?**如何限制 CPU 和内存的使用呢?
通过namespace和cgroup技术。

Namespace技术:内部创业公司应该独立运营
在编程中,是为了不同文件相同变量名的冲突。这里主要实现进程间的资源相互隔离的作用。如果开发人员启动了两个容器,那么某个服务器上的某两个地方将有两个进程运行,但它们是相互隔离的。这样改变一个namespace的系统资源只会影响当前namespace中的进程,对其它namespace中的资源没有影响。
这些资源主要包括 user id ,pid ,网络相关,IPC进程间通信相关。
查看进程所属的ns信息
/proc/[pid]/ns这样一个目录,这里面包含了该进程所属的namespace信息。 每个ns有一个ID,只要两个进程的ns id一样,就说明在同一个ns中。

一些主要的系统调用
nsenter,可以用来运行一个进程,进入指定的 namespace

nsenter --target 58212(nigix的pid) --mount --uts --ipc --net --pid –
env --ignore-environment – /bin/bas

unshare,它会离开当前的 namespace,创建且加入新的 namespace,然后执行参数中指定的命令。

unshare --mount --ipc --pid --net --mount-proc=/proc --fork /bin/bash
如果从 shell 上运行上面这行命令的话,好像没有什么变化,但是因为 pid 和 net 都进入了新的 namespace,所以我们查看进程列表和 ip 地址的时候应该会发现有所不同。

cgroup技术:内部创业公司应该独立核算成本
cgroup 定义了下面的一系列子系统,每个子系统用于控制某一类资源。(CPU,内存,设备,网络)
主要组成结构就是
若干进程会组成一个cgroup,若干个cgroup树状排列组成一个层级,每个cgroup可以有若干个子系统,子系统就是控制某一类资源的。进程只属于某个cgroup可以迁移。层级树可以有多个。

可以实现一些资源的监控,限制,挂起等操作。
一般我们可以用cgroup做以下事情
隔离一个进程集合(比如MySQL的所有进程),限定他们所占用的资源,比如绑定的CPU核限制
为这组进程分配内存
为这组进程的分配足够的带宽及进行存储限制
限制访问某些设备

Kubernetes
最初使用汇编语言的前辈,在程序中需要指定使用的硬件资源,例如,指定使用哪个寄存器、放在内存的哪个位置、写入或者读取哪个串口等等。为了将程序员从对硬件的直接操作中解放出来,提升程序设计的效率,于是,我们有了操作系统这一层,用来实现对于硬件资源的统一管理。
但是到目前为止,我们能管理的还是少数几台机器。当我们面临数据中心成千上万台机器的时候,仍然非常“痛苦”。如果我们运维数据中心依然像运维一台台物理机的前辈一样,天天关心哪个程序放在了哪台机器上,使用多少内存、多少硬盘,每台机器总共有多少内存、多少硬盘,还剩多少内存和硬盘,那头就大了。因而对应到数据中心,我们也需要一个调度器,将运维人员从指定物理机或者虚拟机的痛苦中解放出来,实现对于物理资源的统一管理,这就是 Kubernetes

形象来说,
从汇编到操作系统,实现了需要指定CPU,内存等资源位置到操作系统自动寻找;
从操作系统到Kubernetes,实现了指定程序运行的服务器及使用的 CPU 和内存到调度器 Scheduler,你只需要告诉它,你想运行 10 个 4 核 8G 的 Java 程序,它会自动帮你选择空闲的、有足够资源的服务器,去运行这些程序

Kubernetes 作为数据中心的操作系统还是主要管理数据中心里面的四种硬件资源:CPU、内存、存储、网络。
对于 CPU 和内存这两种计算资源的管理,我们可以通过 Docker 技术完成。它可以将 CPU 和内存资源,通过 namespace 和 cgroup,从大的资源池里面隔离出来,并通过镜像技术,实现计算资源在数据中心里面的自由漂移。

Kubernetes组成(类比汇编和操作系统)
Kubernetes 将多个 Docker 组装成一个 Pod 的概念。在一个 Pod 里面,往往有一个 Docker 为主,多个 Docker 为辅。对于操作系统上的进程来讲,有主线程做主要的工作,还有其它线程做辅助的工作。

操作系统上的进程会在 CPU 上切换来切换去,它使用的内存也会换入换出。在数据中心里面,这些运行中的程序能不能在机器之间迁移呢?Kubernetes 里面有 Controller 的概念,可以控制 Pod 们的运行状态以及占用的资源。Kubernetes 的 Scheduler 也是有亲和性功能的,你可以选择两个 Pod 永远运行在一台物理机上,这样本地通信就非常方便了;你也可以选择两个 Pod 永远不要运行在同一台物理机上,这样一个挂了不影响另一个。调度的原则有资源利用最大化,公平,高效等。切换策略一般先去除不符合条件的节点,然后根据优先级来选择节点。这跟redis选主优点相似之处啊。

数据怎么办?
如果数据放在每一台服务器上,其实就像散落在汪洋大海里面,用的时候根本找不到,所以必须要有统一的存储。正像一台操作系统上多个进程之间,要通过文件系统保存持久化的数据并且实现共享,在数据中心里面也需要一个这样的基础设施。
统一的存储常常有三种形式:对象存储,文件存储,块存储
对象存储:以key-value的形式。每个文件有唯一的key,相当于平面存储模型,比较容易根据唯一的 key 进行横向扩展,寻找文件很快。是保存非格式化数据的好方式。适合保存海量数据比如图片视频。缺点是你没办法像操作文件一样操作它,而是要将 value 当成整个的来对待。所以对于修改操作很多的文件不适合。

文件存储:这种是最容易习惯的,因为使用它和使用本地的文件系统几乎没有什么区别,只不过是通过网络的方式访问远程的文件系统。缺点是分布式文件系统的性能和规模是个矛盾,规模一大性能就难以保证,性能好则规模不会很大,所以不像对象存储一样能够保持海量的数据。

块存储:相当于云硬盘,也即存储虚拟化的方式,只不过将盘挂载给容器而不是虚拟机。这样做的缺点是,一般情况下,不同容器挂载的块存储都是不共享的,好处是在同样规模的情况下,性能相对分布式文件系统要好。所以适合数据迁移,并不适合数据共享。
这三种形式,对象存储使用 HTTP 进行访问,当然任何容器都能访问到,不需要 Kubernetes 去管理它。而分布式文件系统和分布式块存储,就需要对接到 Kubernetes,让 Kubernetes 可以管理它们。如何对接呢?Kubernetes 提供 Container Storage Interface(CSI)接口,这是一个标准接口,不同的存储可以实现这个接口来对接 Kubernetes。是不是特别像设备驱动程序呀?操作系统只要定义统一的接口,不同的存储设备的驱动实现这些接口,就能被操作系统使用了。

存储的问题解决了,接下来是网络。因为不同的服务器上的 Docker 还是需要互相通信的。Kubernetes 有自己的网络模型
.IP-per-Pod,每个 Pod 都拥有一个独立 IP 地址,Pod 内所有容器共享一个网络命名空间。
集群内所有 Pod 都在一个直接连通的扁平网络中,可通过 IP 直接访问。每一个 Docker 访问另一个 Docker 的时候,都是感觉在一个扁平的网络里面。
要实现这样的网络模型,有很多种方式,例如 Kubernetes 自己提供 Calico、Flannel。当然,也可以对接 Openvswitch 这样的虚拟交换机,也可以使用 brctl 这种传统的桥接模式,也可以对接硬件交换机。
看,这又是一种类似驱动的模式,和操作系统面临的问题是一样的。Kubernetes 同样是提供统一的接口 Container Network Interface(CNI,容器网络接口)。无论你用哪种方式实现网络模型,只要对接这个统一的接口,Kubernetes 就可以管理容器的网络。

接下来是用户态的工作模式问题了。我们能不能像操作一台服务器那样操作数据中心呢?
我们需要 yum 之类的包管理系统。ubernetes 就有这样一套包管理软件 Helm,你可以用它来很方便地安装、升级、扩容一些数据中心里面的常用软件,例如数据库、缓存、消息队列。

我们还需使用文件系统,或者使用网络发送数据。虽然在 Kubernetes 里面有 CSI 和 CNI 来对接存储和网络,但是在用户态,不能让用户意识到后面具体设备,而是应该有抽象的概念。(就像linux万物皆文件,open操作可以打开各种类型的文件一样)

对于存储来讲,Kubernetes 有 Volume 的概念。Volume 的生命周期与 Pod 绑定在一起,容器挂掉后,Kubelet 再次重启容器时,Volume 的数据依然还在,而 Pod 删除时,Volume 才会真的被清理。Volume 的概念是对具体存储设备的抽象,就像当我们使用 ext4 文件系统时,不用管它是基于什么硬盘一样。

对于网络来讲,Kubernetes 有自己的 DNS,有 Service 的概念。Kubernetes Service 是一个 Pod 的逻辑分组,这一组 Pod 能够被 Service 访问。每一个 Service 都一个名字,Kubernetes 会将 Service 的名字作为域名解析成为一个虚拟的 Cluster IP,然后通过负载均衡,转发到后端的 Pod。虽然 Pod 可能漂移,IP 会变,但是 Service 会一直不变。(Service就相当于一组pod,也就是实现某种功能的一个进程集合,比如mysql的一组进程)

总结一下k8s,主要搭上了云原生的东风,现在数据都放在云上,相当于虚拟的服务器集群数据中心。她就是用来管理数据中心的硬件软件资源的,就像操作系统是管理一台机器的资源一样。
包括CPU,内存,存储,网络,文件系统。
CPU和内存可以借助于docker的隔离标准化(ns,cgroup技术)实现。多个docker形成POD,pod类似于进程,docker就是主线程和辅助线程。 而进程的调度就是pod在哪个服务器节点运行。比如一台服务器坏了pod就要选择别的,这是调度器实现的。具体原则先筛选,再根据优先级选大致是这样。

存储,k8s有统一的接口,分布式存储有对象存储文件存储块存储几种方式,不同的存储可以实现这个接口来对接 Kubernetes。是不是特别像设备驱动程序呀?操作系统只要定义统一的接口,不同的存储设备的驱动实现这些接口,就能被操作系统使用了。

网络,也定义了自己的网络模型。每个pod一个ip地址,所有pod直接连通不需要NAT转换。Kubernetes 同样是提供统一的接口 Container Network Interface(CNI,容器网络接口

用户模式
也就是有包管理器helm装各种应用软件,比如数据库,缓存,消息队列等

以及文件系统。抽象。对于存储来讲,Kubernetes 有 Volume 的概念,对于网络来讲,Kubernetes 有自己的 DNS,有 Service 的概念

猜你喜欢

转载自blog.csdn.net/weixin_53344209/article/details/130767115
今日推荐