容器与镜像
进程
什么是进程
进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本的单位,是操作系统结构的基础。
进程的特点
- 进程可以相互看到、相互通信;
- 进程使用的是同一个文件系统,可以对同一个文件进行读写操作;
- 进程会使用相同的系统资源。
进程特点带来的问题
- 因为进程能够相互看到并且进行通信,高级权限的进程可以攻击其他进程;
- 因为使用同一个文件系统,因此会带来两个问题,这些进程可以对已有的数据进行增删改查,具有高级权限的进程可能会将及其他进程的数据删除掉,破坏其他进程的正常运行;此外,进程与进程之间的依赖可能会存在冲突,这样会给运维带来很大的压力;
- 因为这些进程使用的是同一个宿主机的资源,应用之间可能会存在资源抢占的问题,当一个应用需要消耗大量CPU和内存资源的时候,就可能会破坏其他应用的运行,导致其他应用无法正常地提供服务。
如何为进程提供独立的运行环境
- 针对不同进程使用同一个文件系统造成的问题而言,Linux和Unix操作系统可以通过chroot系统调用将子目录变成根目录,达到视图级别的隔离;进程在chroot的帮助下可以具有独立的文件系统,对于这样的文件系统进行增删改查不会影响到其他进程;
- 使用Namespace1技术可以实现进程在资源的视图上进行隔离,在chroot和Namespace的帮助下,进程就能够运行在一个独立的环境下了;
- 在独立的环境下,进程使用的还是同一个操作系统的资源,一些进城可能会侵蚀掉整个系统的资源,为了减少进程彼此之间的影响,可以通过Cgroup2来限制其资源使用率,设置其能够使用的CPU以及内存量。
什么是容器
容器就是一个视图隔离,资源可限制,独立文件系统的进程集合。所谓“视图隔离”就是能够看到部分进程及具有独立的主机名等;控制资源使用率则是可以对于内存大小以及CPU使用个数进行限制。容器就是一个进程集合,他将系统的其他资源隔离开来,具有自己独立的资源视图。
容器具有一个独立的文件系统,因为使用的是系统的资源,所以在独立的文件系统内不需要具备内核相关的代码或者工具,我们只需要提供容器所需的二进制文件、配置文件以及依赖即可。只要容器运行时所需的文件集合都能够具备,那么这个容器就能够运行起来。
什么是镜像
容器运行时所需要的的所有文件集合称之为容器镜像
如何构建镜像
那么,一般都是通过什么样的方式来构建镜像的呢?通常情况下,我们会采用 Dockerfile 来构建镜像,这是因为 Dockerfile 提供了非常便利的语法糖,能够帮助我们很好地描述构建的每个步骤。当然,每个构建步骤都会对已有的文件系统进行操作,这样就会带来文件系统内容的变化,我们将这些变化称之为 changeset。当我们把构建步骤所产生的变化依次作用到一个空文件夹上,就能够得到一个完整的镜像。 changeset 的分层以及复用特点能够带来几点优势:
- 第一,能够提高分发效率,简单试想一下,对于大的镜像而言,如果将其拆分成各个小块就能够提高镜像的分发效率,这是因为镜像拆分之后就可以并行下载这些数据;
- 第二,因为这些数据是相互共享的,也就意味着当本地存储上包含了一些数据的时候,只需要下载本地没有的数据即可,举个简单的例子就是 golang 镜像是基于 alpine 镜像进行构建的,当本地已经具有了 alpine 镜像之后,在下载 golang 镜像的时候只需要下载本地 alpine 镜像中没有的部分即可;
- 第三,因为镜像数据是共享的,因此可以节约大量的磁盘空间,简单设想一下,当本地存储具有了 alpine 镜像和 golang 镜像,在没有复用的能力之前,alpine 镜像具有 5M 大小,golang 镜像有 300M 大小,因此就会占用 305M 空间;而当具有了复用能力之后,只需要 300M 空间即可。
如何运行容器
运行一个容器一般情况下分为三步:
- 第一步:从镜像仓库中将相应的镜像下载下来;
- 第二步:当镜像下载完成之后就可以通过 docker images 来查看本地镜像,这里会给出一个完整的列表,我们可以在列表中选中想要的镜像;
- 第三步:当选中镜像之后,就可以通过 docker run 来运行这个镜像得到想要的容器,当然可以通过多次运行得到多个容器。一个镜像就相当于是一个模板,一个容器就像是一个具体的运行实例,因此镜像就具有了一次构建、到处运行的特点。