概述
Docker容器的出现终结了DevOps中交付和部署环节因环境、配置及程序本身的不同而造成的动辄几种甚至十几种部署配置的困境,将它们统一在了容器镜像之上。
但Docker容器格式运行的应用程序间的协同以及大规模容器应用的治理却成为一个新的亟待解决的问题,这种需求在微服务架构中表现得尤为明显。
容器编排的必要性
Docker本身非常适合管理单个容器,若运行的是构建于有限几个或十几个容器上的应用程序,则可以仅在Docker引擎上自主运行,部署和管理这些容器并不会遇到太大的困难。然而,对于包含成百上千个容器的企业级应用程序来说,这种管理将变得极其复杂,甚至无法实现。此时,容器编排就很有必要。
📒 容器编排提供的能力
概括来说,容器编排系统能够为用户提供如下关键能力。
- 集群管理与基础设施抽象:将多个虚拟机或物理机构建成协同运行的集群,并将这些硬件基础设施抽抽象为一个统一的资源池。
- 资源分配和优化:基于配置清单中指定的资源需求与现实可用的资源量,利用成熟的调度算法合理调度工作负载。
- 应用部署:支持跨主机自动部署容器化应用,支持多版本并存、滚动更新和回滚等机制。
- 应用伸缩:支持应用实例规模的自动或手动伸缩。
- 应用隔离:支持为租户、项目或应用进行访问隔离。
- 服务可用性:利用状态监测和应用重构等机制确保服务始终健康运行。
如果是单机,可以使用docker compose来做容器编排和管理。比如使用 docker compose up -d --scale service_xxx=3
就可以很轻松地将docker实例扩展到 3 个。
Docker compose的局限
Docker compose 有一些局限性:
- 单主机部署:Docker Compose 主要设计用于单主机部署。它不支持跨多个 Docker 主机的容器编排。
- 有限的网络配置:虽然 Docker Compose 允许定义自定义网络,但它的网络配置选项相对有限,可能无法满足复杂的网络需求。
- 资源限制:Docker Compose 允许设置 CPU 和内存限制,但不支持更复杂的资源管理,如磁盘 I/O 限制或网络带宽限制。
- 扩展性问题:Docker Compose 不提供自动扩展服务实例的功能。如果需要根据负载自动扩展服务,可能需要使用更高级的容器编排工具,如 Kubernetes。
- 服务发现:虽然 Docker Compose 支持服务发现,但它的服务发现机制相对简单,可能不适合大规模或动态变化的部署环境。
- 存储卷管理:Docker Compose 允许定义持久化存储卷,但不支持复杂的存储卷管理,如存储卷的备份、恢复或迁移。
- 安全性:Docker Compose 的安全性主要依赖于 Docker 守护进程的安全性。如果 Docker 守护进程受到攻击,那么通过 Docker Compose 部署的应用程序也可能受到影响。
- 版本控制:Docker Compose 的
docker-compose.yml
文件可能包含敏感信息,如数据库密码等。直接将这些信息提交到版本控制系统可能会带来安全风险。 - 依赖管理:Docker Compose 不提供内置的依赖管理机制,如果服务之间存在复杂的依赖关系,可能需要手动处理服务启动顺序。
- 调试和日志记录:虽然 Docker Compose 提供了日志记录功能,但它的调试和日志管理功能相对有限,可能需要额外的工具来支持复杂的调试和日志分析。
- 与 CI/CD 集成:Docker Compose 可能不如其他容器编排工具那样容易与持续集成/持续部署(CI/CD)流水线集成。
k8s成为基础设施
Kubernetes、Mesos和Docker Swarm一度作为竞争对手在容器编排领域三分天下,但这一切从2017年以来发生了根本性的变化,Kubernetes已成为广受认可的基础设施领域工业标准。
引入k8s后,docker的形态发生了变化。一个或多个docker实例组成一个Pod,运行在k8s中。
引入k8s后,关注点也发生了变化。纯粹使用Docker时,我们关注容器层面的技术;而使用Kubernetes 后,我们更侧重于容器编排层面的自动化和管理。
k8s认知
k8s系统组件
Kubernetes 项目的架构,由 Master 和 Node 两种节点组成,而这两种角色分别对应着控制节点和计算节点。
其中,控制节点,即 Master 节点,由三个紧密协作的独立组件组合而成,它们分别是负责 API 服务的 kube-apiserver、负责调度的 kube-scheduler,以及负责容器编排的 kube-controller-manager。整个集群的持久化数据,则由 kube-apiserver 处理后保存在 Etcd 中。
而计算节点上最核心的部分,则是一个叫作 kubelet 的组件。
Master组件
Master组件是集群的「脑力」输出者,它维护有Kubernetes的所有对象记录,负责持续管理对象状态并响应集群中各种资源对象的管理操作,以及确保各资源对象的实际状态与所需状态相匹配。控制平面的各组件支持以单副本形式运行于单一主机,也能够将每个组件以多副本方式同时运行于多个主机上,提高服务可用级别。控制平面各组件及其主要功能如下:
API Server :API Server是Kubernetes控制平面的前端,支持不同类型应用的生命周期编排,包括部署、缩放和滚动更新等。它还是整个集群的网关接口,由kube-apiserver守护程序运行为服务,通过HTTP/HTTPS协议将RESTful API公开给用户,是发往集群的所有REST操作命令的接入点,用于接收、校验以及响应所有的REST请求,并将结果状态持久存储于集群状态存储系统(etcd)中。
集群状态存储 :Kubernetes集群的所有状态信息都需要持久存储于存储系统etcd中。etcd是由CoreOS基于Raft协议开发的分布式键值存储,可用于服务发现、共享配置以及一致性保障(如数据库主节点选择、分布式锁等)。显然,在生产环境中应该以etcd集群的方式运行以确保其服务可用性,并需要制定周密的备份策略以确保数据安全可靠。 etcd还为其存储的数据提供了监听(watch)机制,用于监视和推送变更。API Server是Kubernetes集群中唯一能够与etcd通信的组件,它封装了这种监听机制,并借此同其他各组件高效协同。
控制器管理器 :控制器负责实现用户通过API Server提交的终态声明,它通过一系列操作步骤驱动API对象的当前状态逼近或等同于期望状态。Kubernetes提供了驱动Node、Pod、Server、Endpoint、ServiceAccount和Token等数十种类型API对象的控制器。从逻辑上讲,每个控制器都是一个单独的进程,但是为了降低复杂性,它们被统一编译进单个二进制程序文件kube-controller-manager(即控制器管理器),并以单个进程运行。
调度器 :Kubernetes系统上的调度是指为API Server接收到的每一个Pod创建请求,并在集群中为其匹配出一个最佳工作节点。kube-scheduler是默认调度器程序,它在匹配工作节点时的考量因素包括硬件、软件与策略约束,亲和力与反亲和力规范以及数据的局部性等特征。
Node组件
Node组件是集群的「体力」输出者,因而一个集群通常会有多个Node以提供足够的承载力来运行容器化应用和其他工作负载。每个Node会定期向Master报告自身的状态变动,并接受Master的管理。
kubelet
- kubelet是Kubernetes中最重要的组件之一,是运行于每个Node之上的“节点代理”服务,负责接收并执行Master发来的指令,以及管理当前Node上Pod对象的容器等任务。它支持从API Server以配置清单形式接收Pod资源定义,或者从指定的本地目录中加载静态Pod配置清单,并通过容器运行时创建、启动和监视容器。
- kubelet会持续监视当前节点上各Pod的健康状态,包括基于用户自定义的探针进行存活状态探测,并在任何Pod出现问题时将其重建为新实例。它还内置了一个HTTP服务器,监听TCP协议的10248和10250端口:10248端口通过/healthz响应对kubelet程序自身的健康状态进行检测;10250端口用于暴露kubelet API,以验证、接收并响应API Server的通信请求。
容器运行时环境:Pod是一组容器组成的集合并包含这些容器的管理机制,它并未额外定义进程的边界或其他更多抽象,因此真正负责运行容器的依然是底层的容器运行时。kubelet通过CRI(容器运行时接口)可支持多种类型的OCI容器运行时,例如docker、containerd、CRI-O、runC、fraki和Kata Containers等。
kube-proxy:kube-proxy也是需要运行于集群中每个节点之上的服务进程,它把API Server上的Service资源对象转换为当前节点上的iptables或(与)ipvs规则,这些规则能够将那些发往该Service对象ClusterIP的流量分发至它后端的Pod端点之上。kube-proxy是Kubernetes的核心网络组件,它本质上更像是Pod的代理及负载均衡器,负责确保集群中Node、Service和Pod对象之间的有效通信。
核心附件
附件(add-ons)用于扩展Kubernetes的基本功能,它们通常运行于Kubernetes集群自身之上,可根据重要程度将其划分为必要和可选两个类别。网络插件是必要附件,管理员需要从众多解决方案中根据需要及项目特性选择,常用的有Flannel、Calico、Canal、Cilium和Weave Net等。KubeDNS通常也是必要附件之一,而Web UI(Dashboard)、容器资源监控系统、集群日志系统和Ingress Controller等是常用附件。
- CoreDNS:Kubernetes使用定制的DNS应用程序实现名称解析和服务发现功能,它自1.11版本起默认使用CoreDNS——一种灵活、可扩展的DNS服务器;之前的版本中用到的是kube-dns项目,SkyDNS则是更早一代的解决方案。
- Dashboard:基于Web的用户接口,用于可视化Kubernetes集群。Dashboard可用于获取集群中资源对象的详细信息,例如集群中的Node、Namespace、Volume、ClusterRole和Job等,也可以创建或者修改这些资源对象。
- 容器资源监控系统:监控系统是分布式应用的重要基础设施,Kubernetes常用的指标监控附件有Metrics-Server、kube-state-metrics和Prometheus等。
- 集群日志系统:日志系统是构建可观测分布式应用的另一个关键性基础设施,用于向监控系统的历史事件补充更详细的信息,帮助管理员发现和定位问题;Kubernetes常用的集中式日志系统是由ElasticSearch、Fluentd和Kibana(称之为EFK)组合提供的整体解决方案。
- Ingress Controller:Ingress资源是Kubernetes将集群外部HTTP/HTTPS流量引入到集群内部专用的资源类型,它仅用于控制流量的规则和配置的集合,其自身并不能进行“流量穿透”,要通过Ingress控制器发挥作用;目前,此类的常用项目有Nginx、Traefik、Envoy、Gloo、kong及HAProxy等。
K8s核心功能「全景图」
- 应用与应用之间的关系
- (Docker)容器本质:容器的本质是进程。Namespace 做隔离,Cgroups 做限制,rootfs 做文件系统。
- Pod:解决容器间“紧密协作”关系的难题。
- Deployment:有了 Pod 之后,我们希望能一次启动多个应用的实例,这样就需要 Deployment 这个 Pod 的多实例管理器。
- Service:有了这样一组相同的 Pod 后,我们又需要通过一个固定的 IP 地址和端口以负载均衡的方式访问它,于是就有了 Service。
- Secret:把 Credential 信息以 Secret 的方式存在 Etcd 里,Kubernetes 就会在你指定的 Pod(比如,Web 应用的 Pod)启动时,自动把 Secret 里的数据以 Volume 的方式挂载到容器里。
- 应用运行的形态
- Job:用来描述一次性运行的 Pod(比如,大数据任务)。
- DaemonSet:用来描述每个宿主机上必须且只能运行一个副本的守护进程服务。
- CronJob:用于描述定时任务。
==> 「声明式」API
定义「声明式」API,如何思考
Kubernetes (k8s) 是一个开源的容器编排平台,它提供了一套用于自动部署、扩展和管理容器化应用程序的工具。在定义 Kubernetes Deployment 时,你需要考虑以下几个关键方面:
- 应用程序需求:首先,了解你的应用程序需要什么。这包括资源需求(CPU、内存)、运行环境、依赖关系等。
- 可扩展性:考虑你的应用程序是否需要水平扩展(即增加更多的实例来处理更多的负载)。
- 更新策略:定义 Deployment 时,你需要选择一个更新策略,比如滚动更新、重新创建或蓝绿部署。
- 健康检查:设置适当的健康检查(Liveness 和 Readiness Probes),以确保 Kubernetes 可以自动重启失败的容器或在更新时逐步替换实例。
- 配置管理:考虑如何管理应用程序的配置。使用 ConfigMaps 和 Secrets 来管理配置信息和敏感数据。
- 服务发现和负载均衡:使用 Kubernetes 服务(Service)来实现服务发现和负载均衡。
- 存储需求:如果应用程序需要持久化存储,考虑使用 Persistent Volumes (PV) 和 Persistent Volume Claims (PVC)。
- 安全性:确保你的 Deployment 遵循安全最佳实践,包括使用 RBAC(基于角色的访问控制)和网络策略。
- 环境差异:如果你的应用程序在多个环境中运行(如开发、测试和生产),考虑如何管理这些环境之间的差异。
- 日志和监控:集成日志和监控工具,以便跟踪应用程序的性能和健康状况。
- 资源限制和请求:为 Pod 设置资源限制和请求,以防止它们消耗过多资源。
- 环境变量:使用环境变量来传递运行时配置。
- 标签和选择器:使用标签来标记资源,并使用选择器来指定哪些 Pods 属于特定的 Deployment。
- 命令和参数:定义 Pod 启动时执行的命令和参数。
- 生命周期钩子:使用生命周期钩子(如 PostStart 和 PreStop)来执行应用程序特定的启动和关闭任务。
- 模板化:使用模板来定义 Deployment,这样可以更容易地进行修改和扩展。
定义 Deployment 的 YAML 文件通常包括 apiVersion
, kind
, metadata
, 和 spec
等字段。spec
字段是定义 Deployment 的核心,包括了上述许多方面的配置。
apiVersion: apps/v1 #版本号
kind: Deployment #类型
metadata: #元数据
name: #rs名称
namespace: #所属命名空间
labels: #标签
controller: deploy
spec: #详情描述
replicas: #副本数量
revisionHistoryLimit: #保留历史版本,默认是10
paused: #暂停部署,默认是false
progressDeadlineSeconds: #部署超时时间(s),默认是600
strategy: #策略
type: RollingUpdates #滚动更新策略
rollingUpdate: #滚动更新
maxSurge: #最大额外可以存在的副本数,可以为百分比,也可以为整数
maxUnavaliable: #最大不可用状态的pod的最大值,可以为百分比,也可以为整数
selector: #选择器,通过它指定该控制器管理哪些pod
matchLabels: #Labels匹配规则
app: nginx-pod
matchExpressions: #Expression匹配规则
- {key: app, operator: In, values: [nginx-pod]}
template: #模板,当副本数量不足时,会根据下面的模板创建pod副本
metadata:
labels:
app: nginx-pod
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- containerPort: 80
在定义你的 Deployment 时,根据你的具体需求调整上述示例中的参数和配置。
k8s生态系统
k8s特性
Kubernetes整合并抽象了底层的硬件和系统环境等基础设施,对外提供了一个统一的资源池供终端用户通过API进行调用。Kubernetes具有以下几个重要特性:
- 自动装箱:构建于容器之上,基于资源依赖及其他约束自动完成容器部署且不影响其可用性,并在同一节点通过调度机制混合运行关键型应用和非关键型应用的工作负载,以提升资源利用率。
- 自我修复(自愈) :支持容器故障后自动重启、节点故障后重新调度容器到其他可用节点、健康状态检查失败后关闭容器并重新创建等自我修复机制。
- 水平扩展:支持通过简单命令或UI手动水平扩展,以及基于CPU等资源负载率的自动水平扩展机制。
- 服务发现和负载均衡:Kubernetes通过其附加组件之一的KubeDNS(或CoreDNS)为系统内置了服务发现功能,它会为每个Service配置DNS名称,并允许集群内的客户端直接使用此名称发出访问请求,而Service通过iptables或ipvs内置了负载均衡机制。
- 自动发布和回滚:Kubernetes支持“灰度”更新应用程序或其配置信息,它会监控更新过程中应用程序的健康状态,以确保不会在同一时刻杀掉所有实例,而此过程中一旦有故障发生,它会立即自动执行回滚操作。
- 密钥和配置管理:Kubernetes的ConfigMap实现了配置数据与Docker镜像解耦,需要时,仅对配置做出变更而无须重新构建Docker镜像,这为应用开发部署提供了很大的灵活性。此外,对于应用所依赖的一些敏感数据,如用户名和密码、令牌、密钥等信息,Kubernetes专门提供了Secret对象使依赖解耦,既便利了应用的快速开发和交付,又提供了一定程度上的安全保障。
- 存储编排:Kubernetes支持Pod对象按需自动挂载不同类型存储系统,这包括节点本地存储、公有云服务商的云存储(如AWS和GCP等),以及网络存储系统,例如NFS、iSCSI、Gluster、Ceph、Cinder和Flocker等。
- 批量处理执行:除了服务型应用,Kubernetes还支持批处理作业、CI(持续集成),以及容器故障后恢复。
基于k8s的容器编排系统
以应用为中心的Kubernetes本身并未直接提供一套完整的“开箱即用”的应用管理体系,需要基础设施工程师基于云原生社区和生态的实际需求手动构建。换句话说,在典型的生产应用场景中,Kubernetes还需要同网络、存储、遥测(监控和日志)、镜像仓库、负载均衡器、CI/CD工具链及其他服务整合,以提供完整且API风格统一的基础设施平台,如下图所示:
下面对容器编排系统中的要素进行简单介绍:
- Docker Registry和工件仓库:通过Harbor工件仓库、Docker Registry等项目实现。
- 网络:借助Flannel、Calico或WeaveNet等项目实现。
- 遥测:借助Prometheus和EFK栈(或者由Promtail、Loki和Grafana组成的PLG栈)等项目实现。
- 容器化工作负载:借助Kubernetes内置的工作负载控制器资源,甚至由社区扩展而来的各种Operator完成应用的自动化编排,包括自愈和自动扩缩容等;而便捷的应用打包则要借助Helm或Kustomize等项目完成。
- 基于容器编排系统的CI/CD:借助Jenkins、Tekton、Flagger或Kepton等项目,甚至遵循GitOps规范规范实现应用交付、发布和部署等。
小结
Kubernetes集群主要由Master和Node两类节点组成。
Master主要包含apiserver、controller-manager、scheduler和etcd这几个组件,其中apiserver是整个集群的网关。
Node主要由kubelet、kube-proxy和容器引擎等组件构成,kubelet是工作在Kubernetes集群节点之上的代理组件。
完整的Kubernetes集群还需要部署KubeDNS、HeapSter(或Prometheus)、Dashboard和Ingress Controller等几个附加组件。
Kubernetes的网络中主要有4种类型的通信:同一Pod内的容器间通信、各Pod彼此间通信、Pod与Service间的通信以及集群外部的流量同Service之间的通信。