其他分享
首页 > 其他分享> > 容器技术基本原理

容器技术基本原理

作者:互联网

1. 容器的定义

轻量级、可移植、自包含的软件打包技术,使应用程序可以在任何地方以相同的方式运行。简单来说,容器(container)本质是一个Linux进程,它共享主机的CPU、内存等资源,为分层结构,它有自己的IP地址,并且通过端口映射方式能与公网通信(容器IP映射到主机中能访问公网的IP地址),容器就是拥有不同IP地址的Linux进程。

容器由两部分组成:

2. 容器与虚拟机的区别

3. 为什么使用容器

传统应用部署通过插件或脚本来安装应用,应用的运行、配置、管理、所有生存周期将与当前操作系统绑定,这样做并不利于应用的升级更新/回滚等操作。在虚拟机中部署应用时,虚拟机体量比较大,可移植性不强,迁移不便。

你在Python2.7下测试,线上却运行着Python3,奇怪的事情就发生了;或者你依赖具体某个SSL版本的功能,但服务器上却安装着另外版本的SSL;你在Debian系统上进行了测试,生产环境却是Red Hat。

在容器中部署应用,每个容器中的代码都可以独立测试和部署,它包含了完整的运行时环境:一个应用、这个应用所需的全部依赖、类库、其他二进制文件、配置文件,它们统一被打入了一个包中。通过将应用平台和其依赖容器化,操作系统发行版本和其他基础环境造成的差异,都被抽象掉了。

相对于虚拟机,容器能快速部署,由于容器与底层设施、机器文件系统解耦的,所以它能在不同云、不同版本操作系统间进行迁移。

每个容器之间互相隔离,每个容器有自己的文件系统,容器之间进程不会相互影响,能区分计算资源。

容器占用资源少、部署快,每个应用可以被打包成一个容器镜像,每个应用与容器间成一对一关系

也使容器有更大优势,使用容器可以在 build或 release的阶段,为应用创建容器镜像,因为每个应用不需要与其余的应用堆桟组合,也不依赖于生产环境基础结构,这使得从研发到测试、生产能提供一致环境。类似地,容器比虚机轻量、更“透明”,这更便于监控和管理。

 4. 容器的优点

5. 容器的缺点 

6. 容器的原理

为了实现容器进程对外界的隔离,容器底层主要运用了命名空间(Namespaces)、控制组(Control groups)和切根(chroot)。

6.1 命名空间(Namespace)

命名空间是Linux操作系统内核的一种资源隔离方式,使不同的进程具有不同的系统视图。系统视图就是进程能够感知到的系统环境,如主机名、文件系统、网络协议栈、其他用户和进程等。使用命名空间后,每个进程都具备独立的系统环境,进程间彼此感觉不到对方的存在,进程之间相互隔离。目前,Linux中的命名空间共有6种,可以嵌套使用。

Namespace 技术实际上修改了应用进程看待整个计算机“视图”,即它的“视线”被操作系统做了限制,只能“看到”某些指定的内容。但对于宿主机来说,这些被“隔离”了的进程跟其他进程并没有太大区别。

6.2 控制组(Control groups)

命名空间实现了进程隔离功能,但由于各个命名空间中的进程仍然共享同样的系统资源,如CPU、磁盘I/O、内存等,所以如果某个进程长时间占用某些资源,其他命名空间里的进程就会受到影响,这就是“吵闹的邻居(noisy neighbors)”现象。因此,命名空间并没有完全达到进程隔离的目的。为此,Linux内核提供了控制组(Control Groups,cgroups)功能来处理这个问题。 

Linux把进程分成控制组,给每组里的进程都设定资源使用规则和限制。在发生资源竞争时,系统会根据每个组的定义,按照比例在控制组之间分配资源。控制组可设定规则的资源包括CPU、内存、磁盘I/O和网络等。通过这种方式,就不会出现某些进程无限度抢占其他进程资源的情况。在Linux的/sys/fs/cgroup目录中,有cpu、memory、devices、net_cls等子目录,可以根据需要修改相应的配置文件来设置某个进程ID对物理资源的最大使用率。

Linux系统通过命名空间设置进程的可见且可用资源,通过控制组规定进程对资源的使用量,这样隔离进程的虚拟环境(即容器)就建立起来了。

6.3 切根

切根的意思就是改变一个程序运行时参考的根目录位置,让不同容器在不同的虚拟根目录下工作,从而相互不直接影响。

7. 容器运行时

Linux 提供了命名空间和控制组两大系统功能,它们是容器的基础。但是,要把进程运行在容器中,还需要有便捷的SDK或命令来调用Linux的系统功能,从而创建出容器。容器的运行时(runtime)就是容器进程运行和管理的工具。

容器运行时分为低层运行时和高层运行时,功能各有侧重。低层运行时主要负责运行容器,可在给定的容器文件系统上运行容器的进程;高层运行时则主要为容器准备必要的运行环境,如容器镜像下载和解压并转化为容器所需的文件系统、创建容器的网络等,然后调用低层运行时启动容器。主要的容器运行时的关系如下。 

7.1 OCI运行时规范 

成立于2015年的OCI是Linux基金会旗下的合作项目,以开放治理的方式制定操作系统虚拟化(特别是Linux容器)的开放工业标准,主要包括容器镜像格式和容器运行时(runtime)。初始成员包括Docker、亚马逊、CoreOS、谷歌、微软和VMware等公司。OCI成立之初,Docker公司为其捐赠了容器镜像格式和运行时的草案及相应的实现代码。原来属于Docker的libcontainer项目被捐赠给OCI,成为独立的容器运行时项目runC。

OCI运行时规范定义了容器配置、运行时和生命周期的标准,主流的容器运行时都遵循OCI运行时的规范,从而提高系统的可移植性和互操作性,用户可根据需要进行选择。

首先,容器启动前需要在文件系统中按一定格式存放所需的文件。OCI运行时规范定义了容器文件系统包(filesystem bundle)的标准,在OCI运行时的实现中通常由高层运行时下载OCI镜像,并将OCI镜像解压成OCI运行时文件系统包,然后OCI运行时读取配置信息和启动容器里的进程。OCI运行时文件系统包主要包括以下两部分。 

7.1.1 config.json

这是必需的配置文件,存放于文件系统包的根目录下。OCI运行时规范对Linux、Windows、Solaris和虚拟机4种平台的运行时做了相应的配置规范。

7.1.2 容器的根文件系统

容器启动后进程所使用的根文件系统,由 config.json 中的root.path属性确定该文件系统的路径,通常是“rootfs/”。

然后,在定义文件系统包的基础上,OCI运行时规范制定了运行时和生命周期管理规范。生命周期定义了容器从创建到删除的全过程,可用以下三条命令说明。 

7.1.2.1 “create”命令

在调用该命令时需要用到文件系统包的目录位置和容器的唯一标识。在创建运行环境时需要使用config.json里面的配置。在创建的过程中,用户可加入某些事件钩子(hook)来触发一些定制化处理,这些事件钩子包括prestart、createRuntime和createContainer。

7.1.2.2 “start”命令

在调用该命令时需要运行容器的唯一标识。用户可在 config.json 的process 属性中指明运行程序的详细信息。“start”命令包括两个事件钩子:startContainer和poststart。

7.1.2.3 “delete”命令 

在调用该命令时需要运行容器的唯一标识。在用户的程序终止后(包括正常和异常退出),容器运行时执行“delete”命令以清除容器的运行环境。“delete”命令有一个事件钩子:poststop。

7.1.3 其它

除了上述生命周期命令,OCI运行时还必须支持另外两条命令。 

7.1.3.1 “state”命令

在调用该命令时需要运行容器的唯一标识。该命令查询某个容器的状态,必须包括的状态属性有ociVersion、id、status、pid和bundle,可选属性有annotation。不同的运行时实现可能会有一些差异。下面是一个容器状态的例子:

7.1.3.2 “kill”命令

在调用该命令时需要运行容器的唯一标识和信号(signal)编号。该命令给容器进程发送信号,如Linux操作系统的信号9表示立即终止进程。 

7.2 runC

runC是OCI运行时规范的参考实现,也是最常用的容器运行时,被其他多个项目使用,如containerd和CRI-O等。runC也是低层容器运行时,开发人员可通过runC实现容器的生命周期管理,避免繁琐的操作系统调用。根据OCI运行时规范,runC不包括容器镜像的管理功能,它假定容器的文件包已经从镜像里解压出来并存放于文件系统中。runC创建的容器需要手动配置网络才能与其他容器或者网络节点连通,为此可在容器启动之前通过OCI定义的事件钩子来设置网络。

由于runC提供的功能比较单一,复杂的环境需要更高层的容器运行时来生成,所以runC常常成为其他高层容器运行时的底层实现基础。 

7.3 containerd

在OCI成立时,Docker公司把其Docker项目拆分为runC的低层运行时及高层运行时功能。2017年,Docker公司把这部分高层容器运行时的功能集中到containerd项目里,捐赠给云原生计算基金会。

containerd 已经成为多个项目共同使用的高层容器运行时,提供了容器镜像的下载和解压等镜像管理功能,在运行容器时,containerd先把镜像解压成OCI的文件系统包,然后调用runC运行容器。containerd提供了API,其他应用程序可以通过API与containerd交互。“ctr”是containerd的命令行工具,和“docker”命令很相像。但作为容器运行时,containerd只注重在容器运行等方面,因而不包含开发者使用的镜像构建和镜像上传镜像仓库等功能。 

7.4 Docker

Docker引擎是最早流行也是最广泛使用的容器运行时之一,是一个容器管理工具,架构如下图。 

 

Docker的客户端(命令行CLI工具)通过API调用容器引擎Docker Daemon(dockerd)的功能,完成各种容器管理任务。

Docker引擎在发布时是一个单体应用,所有功能都集中在一个可执行文件里,后来按功能分拆成runC和containerd两个不同层次的运行时,分别捐献给了OCI和CNCF。上面两节已经分别介绍了runC和containerd的主要特点,剩下的dockerd就是Docker公司维护的容器运行时。

dockerd同时提供了面向开发者和面向运维人员的功能。其中,面向开发者的命令主要提供镜像管理功能。容器镜像一般可由Dockerfile构建(build)而来。Dockerfile是一个文本文件,通过一组命令关键字定义了容器镜像所包含的基础镜像(base image)、所需的软件包及有关应用程序。在Dockerfile编写完成以后,就可以用“docker build”命令构建镜像了。下面是一个Dockerfile的简单例子:

Docker还提供了容器存储和网络映射到宿主机的功能,大部分由containerd实现。应用的数据可以被保存在容器的私有文件系统里面,这部分数据会随着容器一起被删除。对需要数据持久化的有状态应用来说,可用数据卷Volume的方式导入宿主机上的文件目录到容器中,对该目录的所有写操作都将被保存到宿主机的文件系统中。Docker可以把容器内的网络映射到宿主机的网络上,并且可以连接外部网络。 

7.5 CRI和CRI-O

Kubernetes是当今主流的容器编排平台,为了适应不同场景的需求,Kubernetes需要有使用不同容器运行时的能力。为此,Kubernetes从1.5版本开始,在kubelet中增加了一个容器运行时接口CRI(Container Runtime Interface),需要接入Kubernetes的容器运行时必须实现CRI接口。由于kubelet的任务是管理本节点的工作负载,需要有镜像管理和运行容器的能力,因此只有高层容器运行时才适合接入CRI。CRI和容器运行时的关系如下图。 

 CRI和容器运行时之间需要有个接口层,通常称之为shim(垫片),用以匹配相应的容器运行时。CRI接口由shim实现,定义如下,分为RuntimeService和ImageServiceManager(代码参见GitHub上kubernetes/cri-api的项目文件“pkg/apis/services.go”):

1// RuntimeService接口必须由容器运行时实现
 2// 以下方法必须是线程安全的
 3type RuntimeService interface {
 4RuntimeVersioner
 5ContainerManager
 6PodSandboxManager
 7ContainerStatsManager
 8
 9// UpdateRuntimeConfig更新运行时配置
10UpdateRuntimeConfig(runtimeConfig *runtimeapi.RuntimeConfig) error
11
12// Status返回运行时的状态
13Status() (*runtimeapi.RuntimeStatus, error)
14}
15
16// ImageManagerService接口必须由容器管理器实现
17// 以下方法必须是线程安全的
18type ImageManagerService interface {
19// ListImages列出现有镜像
20ListImages(filter *runtimeapi.ImageFilter) ([]*runtimeapi.Image, error)
21
22// ImageStatus返回镜像状态
23ImageStatus(image *runtimeapi.ImageSpec) (*runtimeapi.Image, error)
24
25// PullImage用认证配置拉取镜像
26PullImage(image *runtimeapi.ImageSpec, auth *runtimeapi.AuthConfig, 
podSandboxConfig *runtimeapi.PodSandboxConfig) (string, error)
27
28// RemoveImage删除镜像
29RemoveImage(image *runtimeapi.ImageSpec) error
30
31// ImageFsInfo返回存储镜像的文件系统信息
32ImageFsInfo() ([]*runtimeapi.FilesystemUsage, error)
33} 

7.6 总结

Docker运行时被普遍使用,它的CRI shim被称为dockershim,内置在Kubernetes的kubelet中,由Kubernetes项目组开发和维护。其他运行时则需要提供外置的shim。containerd从1.1版本开始内置了CRI plugin,不再需要外置shim来转发请求,因此效率更高。在安装Docker的最新版本时,会自动安装containerd,所以在一些系统中,Docker和Kubernetes可以同时使用containerd来运行容器,但是二者的镜像用了命名空间隔离,彼此是独立的,即镜像不可以共用。因为Docker和containerd常常同时存在,因此在不需要使用Docker的系统中只安装containerd即可。

containerd最早是为Docker设计的代码,包含一些用户相关的功能。相比之下,CRI-O是替代Docker或者containerd的高效且轻量级的容器运行时方案,是CRI的一个实现,能够运行符合OCI规范的容器,所以被称为CRI-O。CRI-O是原生为生产系统运行容器设计的,有个简单的命令行工具供测试用,但并不能进行容器管理。CRI-O支持OCI的容器镜像格式,可以从容器镜像仓库中下载镜像。CRI-O支持runC和Kata Containers这两种低层容器运行时。

标签:OCI,容器,基本原理,技术,进程,镜像,Docker,运行
来源: https://blog.csdn.net/fedorafrog/article/details/122398746