Kubernetes原理

背景

最近响应号召,千辛万苦地终于把Kubernetes集群搭建起来了。在能够基础使用的前提下,打算研究一下原理,让自己遇到新的问题的时候能定位到是什么的问题,同时加深对它理念的理解,及实现某些概念时采取的策略。

简介

Kubernetes是谷歌开源的容器集群管理系统,是Google多年大规模容器管理技术Borg的开源版本,主要功能包括:

  • 基于容器的应用部署、维护和滚动升级
  • 负载均衡和服务发现
  • 跨机器和跨地区的集群调度
  • 自动伸缩
  • 无状态服务和有状态服务
  • 广泛的Volume支持
  • 插件机制保证扩展性

基本概念

系统概念

Container

Container(容器)是一种便携式、轻量级的操作系统级虚拟化技术。它使用namespace隔离不同的软件运行环境,并通过镜像自包含软件的运行环境,从而使得容器可以很方便的在任何地方运行。

由于容器体积小且启动快,因此可以在每个容器镜像中打包一个应用程序。这种一对一的应用镜像关系拥有很多好处。使用容器,不需要与外部的基础架构环境绑定, 因为每一个应用程序都不需要外部依赖,更不需要与外部的基础架构环境依赖。完美解决了从开发到生产环境的一致性问题。

容器同样比虚拟机更加透明,这有助于监测和管理。尤其是容器进程的生命周期由基础设施管理,而不是由容器内的进程对外隐藏时更是如此。最后,每个应用程序用容器封装,管理容器部署就等同于管理应用程序部署。

在Kubernetes必须要使用Pod来管理容器,每个Pod可以包含一个或多个容器。

Pod

Pod是一组紧密关联的容器集合,它们共享PID、IPC、Network和UTS namespace,是Kubernetes调度的基本单位。Pod的设计理念是支持多个容器在一个Pod中共享网络和文件系统,可以通过进程间通信和文件共享这种简单高效的方式组合完成服务。

pod.png

在Kubernetes中,所有对象都使用manifest(yaml或json)来定义,比如一个简单的nginx服务可以定义为nginx.yaml,它包含一个镜像为nginx的容器:

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80

Node

Node是Pod真正运行的主机,可以物理机,也可以是虚拟机。为了管理Pod,每个Node节点上至少要运行container runtime(比如docker或者rkt)、kubeletkube-proxy服务。

node.png

Namespace

Namespace是对一组资源和对象的抽象集合,比如可以用来将系统内部的对象划分为不同的项目组或用户组。常见的pods, services, replication controllers和deployments等都是属于某一个namespace的(默认是default),而node, persistentVolumes等则不属于任何namespace。

Service

Service是应用服务的抽象,通过labels为应用提供负载均衡和服务发现。匹配labels的Pod IP和端口列表组成endpoints,由kube-proxy负责将服务IP负载均衡到这些endpoints上。

每个Service都会自动分配一个cluster IP(仅在集群内部可访问的虚拟地址)和DNS名(依赖kube-dns),其他容器可以通过该地址或DNS来访问服务,而不需要了解后端容器的运行。

Service.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
ports:
- port: 8078 # the port that this service should serve on
name: http
# the container on each pod to connect to, can be a name
# (e.g. 'www') or a number (e.g. 80)
targetPort: 80
protocol: TCP
selector:
app: nginx

Label

Label是识别Kubernetes对象的标签,以key/value的方式附加到对象上(key最长不能超过63字节,value可以为空,也可以是不超过253字节的字符串)。

Label不提供唯一性,并且实际上经常是很多对象(如Pods)都使用相同的label来标志具体的应用。

Label定义好后其他对象可以使用Label Selector来选择一组相同label的对象(比如ReplicaSet和Service用label来选择一组Pod)。Label Selector支持以下几种方式:

  • 等式,如app=nginxenv!=production
  • 集合,如env in (production, qa)
  • 多个label(它们之间是AND关系),如app=nginx,env=test

Annotations

Annotations是key/value形式附加于对象的注解。不同于Labels用于标志和选择对象,Annotations则是用来记录一些附加信息,用来辅助应用部署、安全策略以及调度策略等。比如deployment使用annotations来记录rolling update的状态。

API对象

通用属性

每个API对象都有三大类属性:元数据metadata、规范spec和状态status

metadata

元数据是用来标识API对象的,每个对象都至少有3个元数据:namespacenameuid;除此以外还有各种各样的标签labels用来标识和匹配不同的对象,例如用户可以用标签env来标识区分不同的服务部署环境,分别用env=dev、env=testing、env=production来标识开发、测试、生产的不同服务。

spec

规范描述了用户期望Kubernetes集群中的分布式系统达到的理想状态(Desired State),例如用户可以通过Replication Controller设置期望的Pod副本数为3。

Kubernetes中所有的配置都是通过API对象的spec去设置的,也就是用户通过配置系统的理想状态来改变系统,这是k8s重要设计理念之一,即所有的操作都是声明式(Declarative)的而不是命令式(Imperative)的。声明式操作在分布式系统中的好处是稳定,不怕丢操作或运行多次,例如设置副本数为3的操作运行多次也还是一个结果,而给副本数加1的操作就不是声明式的,运行多次结果就错了。

status

status描述了系统实际当前达到的状态(Status),例如系统当前实际的Pod副本数为2;那么Replication Controller当前的程序逻辑就是自动启动新的Pod,争取达到副本数为3。

ConfigMap

用于外化配置的管理。

可以用于保存配置数据的键值对,可以用来保存单个属性,也可以用来保存配置文件。ConfigMap 跟 secret 很类似,但它可以更方便地处理不包含敏感信息的字符串。

DaemonSet

DaemonSet 保证在每个 Node 上都运行一个容器副本,常用来部署一些集群的日志、监控或者其他系统管理应用。典型的应用包括:

  • 日志收集,比如 fluentd,logstash 等
  • 系统监控,比如 Prometheus Node Exporter,collectd,New Relic agent,Ganglia gmond 等
  • 系统程序,比如 kube-proxy, kube-dns, glusterd, ceph 等

Deployment

Deployment 为 Pod 和 ReplicaSet 提供了一个声明式定义 (declarative) 方法,用来替代以前的 ReplicationController 来方便的管理应用。

你只需要在 Deployment 中描述你想要的目标状态是什么,Deployment controller 就会帮你将 Pod 和 Replica Set 的实际状态改变到你的目标状态。你可以定义一个全新的 Deployment,也可以创建一个新的替换旧的 Deployment。

Ingress

通常情况下,service 和 pod 的 IP 仅可在集群内部访问。集群外部的请求需要通过负载均衡转发到 service 在 Node 上暴露的 NodePort 上,然后再由 kube-proxy 通过边缘路由器 (edge router) 将其转发给相关的 Pod 或者丢弃。

而 Ingress 就是为进入集群的请求提供路由规则的集合 。

Ingress 可以给 service 提供集群外部访问的 URL、负载均衡、SSL 终止、HTTP 路由等。为了配置这些 Ingress 规则,集群管理员需要部署一个 Ingress controller,它监听 Ingress 和 service 的变化,并根据规则配置负载均衡并提供访问入口。

Service

Service 是对一组提供相同功能的 Pods 的抽象,并为它们提供一个统一的入口。借助 Service,应用可以方便的实现服务发现与负载均衡,并实现应用的零宕机升级。Service 通过标签来选取服务后端,一般配合 Replication Controller 或者 Deployment 来保证后端容器的正常运行。这些匹配标签的 Pod IP 和端口列表组成 endpoints,由 kube-proxy 负责将服务 IP 负载均衡到这些 endpoints 上。

整体架构图

k8s.png

Kubernetes集群由两类节点构成:MasterNode

Master上运行组件:

  • API Server
  • Controller Manager
  • Scheduler
  • etcd(不一定非要在master)

Master负责对集群中的所有资源进行管控和调度。

Node上运行组件:

  • Kubelet
  • Proxy
  • Docker Daemon

Node负责对本节点上的Pod的生命周期进行管理,以及实现服务代理的功能。

组件概览

核心组件

Etcd

etcd是CoreOS开发,用于可靠地存储集群的配置数据的一种持久性,轻量型的,分布式的键值对数据存储。

Kubernetes使用etcd来持久化存储集群中所有的资源对象。

API Server

API Server则提供了操作etcd的封装接口API,以REST方式提供服务,这些API基本上都是集群中资源对象的增删改查及监听资源变化的接口。是资源对象的唯一操作入口,其它所有组件都必须通过它提供的API来操作资源数据,通过对相关的资源数据全量查询变化监听,这些组件可以很实时地完成相关的业务功能。比如某个新的Pod一旦被提交到API Server中,Controller Manager就会立即发现并开始调度。

API Server内部有一套完备的安全机制,包括认证、授权及准入控制等相关模块。API Server在收到一个REST请求后,会首先执行认证、授权和准入控制的相关逻辑,过滤掉非法请求,然后将请求发送给API Server中的REST服务模块去执行资源的具体操作逻辑。

即API Server是资源操作的唯一入口,并提供认证、授权、访问控制、API注册和发现等机制。

工作原理图

kube-apiserver.png

可扩展部分

它不仅提供了全面的REST API,还提供经了支持多种语言(Go、Python、Java、JavaScript)的SDK等,这样我们可以通过代码实现监控控制集群的伸缩,为我们的幂等设计提供好的方案。

Controller Manager

集群内部的管理控制中心,是Kubernetes的大脑,它通过apiserver监控整个集群的状态,确保集群处于预期的工作状态。维护集群的状态,比如故障检测、自动扩展、滚动更新等。例如

  • 根据RC定义来完成Pod的复制或者移除,确保Pod的实例数量符合RC副本的定义
  • 根据Service与Pod的管理关系,完成服务Endpoints对象的创建和更新
  • Node的发现、管理和状态监控
  • 死亡容器所占磁盘空间及本地缓存的镜像文件的清理工作
  • 等等

Scheduler

集群中资源的调度器,scheduler 负责分配调度 Pod 到集群内的节点上,它监听 kube-apiserver,查询还未分配 Node 的 Pod,然后根据调度策略为这些 Pod 分配节点(更新 Pod 的 NodeName 字段)。

调度器需要考虑诸多因素:

  • 公平调度
  • 资源高效利用
  • QoS
  • affinity和anti-affinity
  • 数据本地化(data locality)
  • 内部负载干扰(inter-workload interference)
  • deadlines

需要注意的是我们可以调整调度的策略,或者可以使用多种方式指定Pod只运行在指定的Node上。

Kubelet

接收并执行master发来的指令,负责本Node节点上的Pod的创建、修改、监控、删除等全生命周期管理,同时定时推送本Node的状态信息到API Server。

还负责Volume(CVI)和网络(CNI)的管理。

kubelet服务中内嵌了一个cAdvisor服务,用于实时监控Docker上运行的容器的性能指标。

Proxy

实现了Service的代理及软件模式的负载均衡器。即为Service提供集群内部的服务发现和负载均衡。

Kubectl & Kubectl Proxy

命令行工具,能够提供Kubernetes的集群的管理工具集。

客户端通过Kubectl命令行工具或者Kubectl Proxy来访问Kubernetes系统。

在Kubernetes集群内部的客户端可以直接使用Kubectl命令管理集群。

Kubectl Proxy是API Server的一个方向代理,在Kubernetes集群外部的客户端可以通过Kubectl Proxy来访问API Server。

Add-ons

kube-dns

负责为整个集群提供DNS服务

Ingress Controller

为服务提供外网入口

Heapster

提供资源监控

Dashboard

提供GUI

Federation

提供跨可用区的集群

Fluentd-elasticsearch

提供集群日志采集、存储与查询

基本流程

以下时序图描述如何创建一个Pod。

workflow.png

  1. 用户通过 REST API 创建一个 Pod
  2. apiserver 将其写入 etcd
  3. scheduluer 检测到未绑定 Node 的 Pod,开始调度并更新 Pod 的 Node 绑定
  4. kubelet 检测到有新的 Pod 调度过来,通过 container runtime 运行该 Pod
  5. kubelet 通过 container runtime 取到 Pod 状态,并更新到 apiserver 中
jinhy wechat
微信扫一扫,欢迎关注我的订阅号~
坚持原创,您的支持将鼓励我继续创作,去追寻星空~