简介
Kubernetes(常简称为K8s)是用于自动部署、扩展和管理“容器化(containerized)应用程序”的开源系统。该系统由Google设计并捐赠给Cloud Native Computing Foundation(今属Linux基金会)来使用。
它旨在提供“跨主机集群的自动部署、扩展以及运行应用程序容器的平台”。 它支持一系列容器工具,包括Docker等。
部署技术演变
物理机
应用往往部署在物理机器上,部署方式存在弊端:空闲资源难以得到复用,部署异构系统时需要重新采购物理资源,大量中小容量的机器使得运维成本提升。
虚拟机
虚拟机使得用户在一台物理机上能够独立运行多个相互隔离的系统,通过对资源的抽象化使得主机资源能够被有效复用,也会带来一些问题:大量独立系统的运行会占用许多额外开销,消耗宿主机器资源,资源竞争时可能会严重影响系统响应;此外,每运行新的虚拟机都需要重新配置一遍环境,和在物理机上的情况基本无异,重复的环境配置操作则会消耗开发和运维人员的工作时间。此时需求便关注到如何减少虚拟化时的资源损耗,同时还能保证隔离性,以及使应用的上线周期更短,这便引导了容器技术的发展。
容器化时代
从2000年开始,各家类Unix操作系统厂商开始陆续推出容器相关的项目,2008年Google的Cgroups贡献给Linux kernel 2.6.24后创造了LXC( Linux Containers),实现了多个独立的Linux环境(容器)可运行在同一个内核。对于一个完整独立运行环境来说,需要包含三个关键:环境隔离、资源控制和文件系统。在LXC中则分别通过Namespace、Cgroups、rootfs来实现相应的能力。
环境隔离——Namespace:LXC将内核全局资源封装,每个Namespace都有一份独立的资源,使得不同的进程在各自Namespace内对同一种资源的使用互不干扰,不会影响其他Namespace下的资源,实现了进程隔离。
资源控制——Cgroups:LXC通过Cgroups对资源进行控制,限制和隔离一组进程对系统资源的使用。在Cgroups出现之前OS只能对一个进程做资源限制,而 Cgroups可以对进程进行任意分组,如何分组由用户自定义,借此实现对于一个Namespace的资源调度管理。
文件系统——rootfs:rootfs挂载在容器根目录上,用来为容器进程提供隔离后执行环境的文件系统。rootfs包含一个操作系统所涉及的文件、配置和目录,在Linux 操作系统内核启动时,内核会先挂载一个只读的rootfs,当系统检测其完整性之后,决定是否将其切换到读写模式。
在通过LXC构建容器后,一台宿主机能够实现多个相互隔离应用的运行。同时,共享内核使得每个容器又很轻量,解决了运行大量隔离应用时虚拟机资源消耗过重的弊端。然而,LXC虽解决了应用隔离的问题,但却只是轻量的容器技术,没有解决各平台软件交付标准不统一的问题,如不同的软件交付工具、应用运行规范不统一、环境依赖复杂等带来的配置开销。这些问题使容器技术的推广依然比较有限,直到Docker的出现。
Docker
早期Docker是基于LXC开发,因此Docker容器也有着和LXC相似的特性,仅需要较少资源便可以启动。但不同于LXC,Docker除了容器运行,还是一个打包、分发和运行应用程序的平台。Docker允许将应用和其依赖的运行环境打包在一起,打包好的“集装箱“(镜像)能够被分发到任何节点上执行,无需再进行配置环境的部署。这样使得Docker解决了开发和部署应用时环境配置的问题,规范化了应用交付和部署,降低了部署测试的复杂度以及开发运维的耦合度,极大提升了容器移植的便利性,便于构建自动化的部署交付流程。
Docker和虚拟机都是资源虚拟化发展的产物,但二者在架构上又有区别。虚拟机通过Hypervisor虚拟化主机硬件资源,然后构建客户机操作系统,由宿主机的管理程序管理;Docker直接运行于主机内核,应用在主操作系统的用户空间上执行独立任务,不需要从操作系统开始构建环境,赋予了应用从交付到部署再到运维的独立性。
虚拟机的启动时间可能是分钟级的,而Docker容器创建是秒级别。对于硬盘的使用Docker一般为MB级别,远小于包含操作系统的虚拟机GB级磁盘使用量。对于操作系统来说,能支持运行的Docker容器数量远多于虚拟机。
那么,如何用Docker启动一个应用呢?对于启动应用来说,首先需要获得程序本身和所需要的环境配置。Docker会将应用运行所有需要的静态资源如代码、运行时环境、配置封装为镜像,镜像通过Union FS采用分层存储架构,通过描述文件Dockerfile指令构建镜像层。
Dockerfile里包含多条指令描述该层应当如何构建,随着镜像层的逐层叠加,将一个完整的镜像所需要的信息全部包含。外部则通过统一文件系统将相互叠加的层整合起来,以只可读的统一文件(Union Read-Only File System)形式展现,这样的分层存储使得镜像的复用和定制变的更为容易,压缩了存储空间。
镜像运行之后的实体是容器。Docker容器同样采用分层存储,通过在镜像顶部增加一层可读可写层,外部以可读写的统一文件(Union Read-Wirte File System)形式展现。当容器运行时所有的进程操作均在可读可写层,而下面的镜像则不会被修改。容器的实质是进程,运行于自己独立的命名空间。容器存储层的生命周期和容器一样,一旦容器消亡,存储层也一并消亡,所以原生的容器是无状态的,这也就是为什么之后编排系统会引入有状态服务和持久化存储以支持有状态服务。
Docker的镜像和容器通过三端的服务操作和管理:请求端Docker Client、主机端Docker Host和远端拉取镜像的仓库Registry。Docker Client负责接收指令,与Docker Host下的守护进程Docker Daemon进行交互。Host提供了执行和运行应用程序的完整环境,其中的Docker Daemon用于管理Docker镜像、容器、网络和存储卷,负责所有与容器相关的操作如拉取镜像、创建容器等,会不断侦听Docker API请求并进行处理。Registry则是镜像管理的仓库,用户可以将创建的镜像提交到仓库进行存储,同时方便从仓库拉下来镜像为自己所用。
Kubernetes
2014年Google开源了名为Kubernetes(简称K8S)的项目,它是由Google内部Borg项目而开源出来的容器集群管理系统。Kubernetes继承了Google丰富的大规模集群运维的经验和基因,能够提供复杂的、大规模的容器编排管理服务。2015年Google发布了Kubernetes第一个商业版本,代表Kubernetes进军生产级容器规模管理,也意味着开始与Docker竞争PaaS平台的未来版图。
Kubernetes集群由两类节点构成:Master Node和Worker Node。Kubernetes采用声明式的设计,任何操作指令都通过声明式API与Master通信。Master Node可响应API声明,进行集群管理和容器调度。容器则运行在Worker Node,Worker负责响应Master指令,执行容器启停等维护操作。
Kubernetes中最基础的调度单元是Pod(而不是容器)。Pod内可以封装多个容器,以及这些容器共享的各种资源,包括存储、独立网络IP等。那么为什么不直接以容器为最小调度单位,而是以Pod呢?容器封装了不同组进程,但如果两个模块关系很紧密,例如通过共享存储通信,那分割为两个容器就比较困难了。为此Kubernetes提出把具有这种紧密关系的容器封装在一个Pod对象中调度
,可以近似理解Pod就是轻量的虚拟机,每个容器是运行在虚拟机的应用。
平台对比
特性 | Docker Swarm | Apache Mesos | Kubernetes (K8s) |
---|---|---|---|
简介 | Docker 的原生编排工具 | 分布式系统内核,支持多种工作负载 | 流行的容器编排和管理平台 |
设计目的 | 容器编排,简化 Docker 管理 | 资源管理器,支持容器和其他任务 | 完整的容器编排和管理平台 |
安装复杂度 | 简单 | 较复杂 | 相对复杂 |
扩展性 | 中等 | 非常高 | 非常高 |
负载均衡 | 支持内置负载均衡 | 基于 Marathon 或其他框架 | 通过服务发现和 Ingress 实现 |
故障恢复 | 有限的自动恢复 | 自定义恢复,依赖框架 | 强大的自动恢复和自愈能力 |
滚动更新 | 支持 | 支持(依赖框架) | 支持 |
存储管理 | 本地卷和网络卷 | 需自行配置,依赖第三方插件 | 提供丰富的存储插件和管理 |
网络模型 | 简单的覆盖网络 | 需第三方插件实现 | 复杂的网络和服务发现机制 |
服务发现 | 内置服务发现 | 使用外部框架实现 | 内置服务发现 |
伸缩管理 | 基本支持,水平伸缩 | 支持,需依赖 Marathon 或其他工具 | 自动伸缩和水平伸缩 |
社区支持 | 中等 | 稳定但不如 K8s 活跃 | 非常活跃 |
适用场景 | 简单的容器编排和 Docker 集成 | 大规模资源管理和多任务处理 | 大规模容器管理和编排 |
学习曲线 | 低 | 较高 | 较高 |
监控支持 | 基础的监控 | 自定义实现,支持 Prometheus | 完整的监控和告警系统 |
安全管理 | 基本 | 自定义实现 | 强大的安全功能 |
生态系统 | 与 Docker 紧密集成 | 依赖外部生态系统 | 丰富的插件和生态系统 |
典型用户 | 小型到中型部署 | 大型企业和多任务环境 | 中型到大型企业 |
k8s 优点
- 服务发现和负载均衡:Kubernetes 自动分配和管理服务的 IP 地址和 DNS 名称,为服务提供内置的负载均衡功能,确保应用的高可用性和稳定性。
- 存储编排:支持多种存储系统的自动挂载(如本地存储、云存储、网络存储等),可以根据需求动态分配存储资源,方便持久化数据管理。
- 自动部署和回滚:支持自动部署新版本,并在遇到问题时快速回滚到之前的版本,确保应用更新的稳定性。
- 自动分配 CPU/内存资源 - 弹性伸缩:根据实际的使用情况自动分配资源,并且可以进行水平和垂直的弹性伸缩,实现高效资源管理。
- 自我修复:当容器或节点发生故障时,Kubernetes 能够自动检测并重启或替换有问题的容器,确保服务的持续运行。
- Secret 和配置管理:提供 Secret 管理,用于存储和管理敏感信息(如密码、令牌、密钥等),增强了安全性。配置管理允许配置项和代码分离,使得配置更灵活。
- 支持大规模部署:Kubernetes 经过广泛验证,能够支持数千个节点和数万个容器,适合大型企业的应用场景。
- 开源:Kubernetes 是一个活跃的开源项目,有着广泛的社区支持和丰富的插件生态系统,用户可以自由扩展和定制。
K8s缺点
- 学习曲线陡峭:Kubernetes 需要掌握大量新概念,如 Pod、Node、Service、Deployment、Namespace 等。对新手和小型团队而言,全面掌握这些概念需要大量时间和学习成本。
- 部署和管理复杂:Kubernetes 需要多种组件的配合(如 API Server、Scheduler、Controller Manager 等),这使得部署和维护 Kubernetes 集群的过程相对复杂。特别是在自托管的环境中,部署、升级和监控等运维工作对团队要求较高。
- 资源消耗高:Kubernetes 本身占用一定的资源,每个节点都需要运行多个 Kubernetes 组件(如 kubelet、kube-proxy 等),而控制平面节点更需要大量内存和 CPU 资源。对于小规模应用或资源有限的环境来说,Kubernetes 的资源开销可能显得不划算。
- 网络和存储配置复杂:Kubernetes 的网络模型灵活多样,但也导致了配置和管理的复杂性,尤其是跨节点的网络通信、服务发现等配置需要较多的调试和管理工作。持久存储管理也是一个难点,特别是在混合云或多区域的环境中。
- 监控和日志管理难度大:Kubernetes 集群中的分布式服务、微服务架构让监控和日志管理变得更复杂。通常需要额外的工具(如 Prometheus、Grafana、ELK Stack)来实现可观测性,这增加了运维成本。
- 版本兼容性和升级挑战:Kubernetes 社区更新频繁,每隔几个月就会发布新版本,可能引入新的功能或 API 变更。升级过程中可能会遇到兼容性问题,对稳定性要求高的企业环境来说,升级可能带来一定风险。
- 生态系统庞大:Kubernetes 的生态系统包括了众多工具和插件(如 Helm、Istio、Prometheus 等),选择和集成这些工具需要时间和人力。而且每个工具有不同的版本更新和配置需求,增加了管理复杂性。
- 多租户隔离性不足:Kubernetes 原生的多租户隔离性较弱,虽然可以通过命名空间和网络策略实现基本的隔离,但不及传统虚拟机或多租户平台的隔离性,可能难以满足严格的隔离需求。
- 安全性配置复杂:Kubernetes 的安全设置涉及身份验证、授权控制、网络策略和资源配额等方面。正确配置这些安全措施需要丰富的 Kubernetes 知识,尤其在多租户和生产环境中,安全配置的复杂性可能带来隐患。
- 适合性有限:Kubernetes 主要为大规模分布式系统设计,不适合资源受限的小型项目或单体应用。对于只需基本容器管理的场景,使用 Kubernetes 可能显得过于复杂和臃肿。