以 Docker 为代表的容器技术在最近几年受到了越来越多的关注,大有成为新一代 IaaS 事实标准之势。最近也找来一本《Kubernetes in Action》,花了大概两周时间大致看了一遍,对 Kubernetes 原理大概也了解一点了,前几天在组内以《容器 & K8s》为主题,做了一次分享。周末有点时间,就写一篇文章记录一下目前对容器和 Kubernetes(简称 K8s)的理解。本文不会涉及具体的技术分析。

容器

顾名思义,就是用来装东西的,在我们的生活中,最常见的容器就是装水的水杯,水壶等等,这就是我们最早认识到的容器。而在计算机的世界里,容器这个概念用得就比较多了,比如 Web 容器 tomcat,它容纳的就是 web 程序,Java 编程框架 Spring 中的 Ioc 容器,它容纳的是程序员创建的 bean 对象。而目前遇到的较多的容器,比如 docker,就是操作系统层面的虚拟化技术,它的作用就是容纳操作系统的进程,给进程隔离出一个独立的运行运行环境,让进程以为自己就在一台独立的机器上运行。

什么是虚拟化

虚拟化以分层的设计为基础,下层向上层提供抽象层,上层面向抽象层运行,而不用关心底层的实际的实现。比如 JVM 就是语言运行的虚拟机,各种语言只需要将其源码编译成 JVM 定义的字节码,而接下来的运行就由 JVM 去执行,至于是在什么操作系统上运行,语言层面并不需要关心,这一切就由 JVM 来解决,从而达到了向上层语言屏蔽各个系统差异的目的。

JVM

常见的虚拟化技术还有 Vmware,VirtualBox 等硬件层面的虚拟化技术,我们可以在这种虚拟机上安装操作系统,来搭建独立的环境。目前各大云厂商提供的虚拟机采用的 KVM 技术,也是基于这种硬件层面的虚拟化技术实现的,比如 AWS 的 EC2,阿里云的 ECS 等。

容器技术也是一种虚拟化技术,与其他虚拟化技术不同的是,它是在操作系统层面的虚拟化。那么什么是操作系统层面的虚拟化?相对于在硬件层面的虚拟化技术又有什么不同呢?

基于容器虚拟化技术

虚拟机(VM)是利用了硬件虚拟化技术,那怎么虚拟硬件呢?那就要看上层是什么软件需要调用硬件的接口了,那就是操作系统了,所以基于硬件虚拟化的虚拟机就为操作系统提供了统一的硬件抽象层,这样就能在虚拟机上安装操作系统了。而操作层面的虚拟化技术则在同一个操作系统内核上为进程做虚拟化,让进程以为自己运行在一个独立的机器上,独占机器资源。如图所示。

VM VS Container

相对于 VM,容器有哪些优势呢?从图上可以看到,基于操作系统层面的虚拟化技术,在宿主机(物理机)操作系统层面就提供了容器的运行环境,以达到进程之间隔离的目的,而虚拟机则需要在宿主机的操作系统层面安装虚拟机上的操作系统(Guest OS),再在 Guest OS 上运行进程,这样不同 Guest OS 之间的进程就自然隔离了。由此可见,基于容器做虚拟化的资源消耗会更少,因为同一宿主机上的容器都运行在一个操作系统内核中。并且,容器的启动会更快一些,毕竟相对于 VM,容器并不需要启动其他额外的依赖。But,every coin has two sides,由于宿主及上的容器都运行在一个操作系统内核中,相对于 VM 在隔离性和安全性上会有不足。同样,容器运行的环境必须与宿主机保持一直,而虚拟机则可以通过硬件层的虚拟,不必依赖宿主机的运行环境,比如可以在 ARM 平台上提供统一的 x86 的硬件抽象层,这样就可以跑在 ARM 宿主机上虚拟出 x86 平台的运行所需的环境。

Evolution of Container

为什么要使用这种虚拟化的技术来部署应用?由于机器的性能越来越强,一般情况下,一个应用进程无法充分利用一个物理机的资源,而在一个物理机上同时运行多个应用进程又无法做到进程之间的隔离,如果一个进程占用了过多的资源,那就会导致其他应用进程无法正常服务。而且在线上应用规模越来越大,多服务的扩容缩容的需求,也越来越多,虚拟化的技术得到了更广泛的应用,也就不奇怪了。

为什么需要虚拟化

容器基于操作系统,那是如何做好隔离和资源限制的呢?那就是 Linux Namespace 和 Linux Control Groups( Cgroups ) 这两项 Linux 内核提供的技术,统称为 Linux Container( lxc )。

Linux Namespace

默认情况下,Linux 只会初始化一个 namespace,所有的资源,比如文件系统,进程 ID,用户,网络接口等等都属于这个 namespace,但是 Linux 提供了创建新的 namespace,并且在 namespace 中分配资源的能力。当在一个 namespace 中创建一个进程时,这个进程就只能看到属于这个 namespace 的资源。需要注意的是,一个进程并不属于一个 namespace,而是属于每种对应的 namespace,因为 Linux 定义了多种类型的 namespace。Linux 定义的 namespace 如下。

  • Mount (mnt)
  • Process ID (pid)
  • Network (net)
  • Inter-process communication (ipc)
  • UTS
  • User ID (user)

Cgroups

实现容器的另一项核心技术就是 Cgroups,通过 Cgroups,可以限制容器中进程使用的资源额度,通过限制资源的使用量,容器不会使用超过限额的资源,从而达到了和其他容器隔离的目的。

Docker

Linuix namespace 技术和 Cgroups 技术在 Docker 出现之前,就已经被一些大公司在使用了,比如 Google 的 GAE,但是直到 Docker 出现后,容器技术才逐渐被大众所接收。因为 Docker 提出了容器镜像的概念,通过镜像,将应用运行时的依赖都打包在一个镜像中分发,极大的降低了开发部署的成本。如果没有镜像,部署的流程就是,首先开发将应用的包打好给运维,运维再将应用部署到生产环境中,而在运维部署之前,还要准备好应用运行的环境,比如安装应用运行所需版本的软件,将所以来的配置项配置好,但是容器镜像出现后,开发在镜像配置中,直接定义好应用的以来以及配置,镜像将这些以来与应用一起打包成镜像文件,最后由容器管理工具分发到生产环境,启动容器,就是启动了应用了。

Docker 的最重要的贡献就是提出了镜像的概念,这真是一个天才的设计。关于镜像的技术分析,可以参考其他文章和书籍,这里不再赘述。

Kubernetes

容器和镜像解决了软件运行时和软件分发的的问题,那容器和应用的管理如何来做呢?容器进程挂了,如何重新拉起?宿主机故障了,如何做到容器转移?这些问题都需要由自动化的解决方案来解决。Docker 给出的解决方案是 Swarm,而 Google 的解决方案就是 Kubernetes。目前来看,Kubernetes 已经称为了事实上的容器编排和管理的标准。

Kubernetes

Kubernets 简称为 K8s,源自 Google 内部的容器管理系统 Borg。目前主要用于管理运行应用程序的容器,支持故障转移,服务发现和负载均衡,自动部署和回滚等能力。比如,一个容器发生故障,则需要启动另一个容器,那么 Kubernetes 就能自动化的完成这个操作,无需人工介入。

核心概念

Kubernetes 中有许多概念,下面选择部分用于理解 Kubernetes 工作原理的概念进行介绍。

  • Pod Pod 是最小部署、调度单元,是在容器之上的一层抽象,所以它包含一组容器(一个或多个),Pod 中容器共享存储,网络等,它拥有独立的 IP,单个 Pod 只会分配到一个主机,一个应用可能需要多个进程共同提供服务,所以一个应用可能会创建多个 Pod。那么 Pod 与容器有什么关系呢?为什么要将容器放在 Pod 中?我们知道,在生产环境中,大多数应用进程的部署都需要另外一个进程一起部署,比如收集日志的进程,比如执行命令通道的进程,这些进程都需要与应用进程一起部署在生产环境中。最简单的情况是,将这多个进程部署在同一个容器中,但是如果这样部署,那每个进程的健康检查,进程拉起就需要适配每个进程。此外,多个进程部署在一个容器中,资源的隔离又没法做了。所以针对这些问题, K8s 的做法则是以 Pod 为单位来对上述的多个进程进行管理。这样每个进程对应一个容器,这些容器就聚合在一个 Pod 中,这样既既可以以容器为单位进行健康检查和资源隔离与管理,又可以以 Pod 为单位进行管理。基于上述的解释,可以将 Pod 理解为一台“机器”,而这里的每台“机器”仅仅用于部署一个应用相关的进程。

Pod on Worker

  • 控制器
  • 服务
  • 调度
  • 存储

系统架构

K8s 系统架构

未完待续….

Reference