O kubernetes简介
k8s是一个可移植的、可扩展的开源平台,用于管理容器化的工作负载和服务,可以促进声明式配置和自动化。
1)服务发现和负载均衡
Kubernetes 可以使用 DNS 名称或自己的 IP 地址公开容器,如果到容器的流量很大,Kubernetes 可以负载均衡并分配网络流量,从而使部署稳定。
2)存储编排
Kubernetes 允许您自动挂载您选择的存储系统,例如本地存储、公共云提供商等。
3)自动部署和回滚
您可以使用 Kubernetes 描述已部署容器的所需状态,它可以以受控的速率将实际状态更改为所需状态。例如,您可以自动化 Kubernetes 来为您的部署创建新容器,删除现有容器并将它们的所有资源用于新容器。
4)自动二进制打包
Kubernetes 允许您指定每个容器所需 CPU 和内存(RAM)。当容器指定了资源请求时,Kubernetes 可以做出更好的决策来管理容器的资源。
5)自我修复
Kubernetes 重新启动失败的容器、替换容器、杀死不响应用户定义的运行状况检查的容器,并且在准备好服务之前不将其通告给客户端。
6)密钥与配置管理
Kubernetes 允许您存储和管理敏感信息,例如密码、OAuth 令牌和 ssh 密钥。您可以在不重建容器镜像的情况下部署和更新密钥和应用程序配置,也无需在堆栈配置中暴露密钥。
1)Kubernetes 不限制支持的应用程序类型。Kubernetes 旨在支持极其多种多样的工作负载,包括无状态、有状态和数据处理工作负载。如果应用程序可以在容器中运行,那么它应该可以在 Kubernetes 上很好地运行。
2)Kubernetes 不部署源代码,也不构建您的应用程序。持续集成(CI)、交付和部署(CI/CD)工作流取决于组织的文化和偏好以及技术要求。
3)Kubernetes 不提供应用程序级别的服务作为内置服务,例如中间件(例如,消息中间件)、数据处理框架(例如,Spark)、数据库(例如,mysql)、缓存、集群存储系统(例如,Ceph)。这样的组件可以在 Kubernetes 上运行,并且/或者可以由运行在 Kubernetes 上的应用程序通过可移植机制(例如,开放服务代理)来访问。
4)Kubernetes 不指定日志记录、监视或警报解决方案。它提供了一些集成作为概念证明,并提供了收集和导出指标的机制。
5)Kubernetes 不提供或不要求配置语言/系统(例如 jsonnet),它提供了声明性 API,该声明性 API 可以由任意形式的声明性规范所构成。
6)Kubernetes 不提供也不采用任何全面的机器配置、维护、管理或自我修复系统。
7)此外,Kubernetes 不仅仅是一个编排系统,实际上它消除了编排的需要。编排的技术定义是执行已定义的工作流程:首先执行 A,然后执行 B,再执行 C。相比之下,Kubernetes 包含一组独立的、可组合的控制过程,这些过程连续地将当前状态驱动到所提供的所需状态。从 A 到 C 的方式无关紧要,也不需要集中控制,这使得系统更易于使用且功能更强大、健壮、弹性和可扩展性。
etcd 保存了整个集群的状态;
kube-apiserver 提供了资源操作的唯一入口,并提供认证、授权、访问控制、API 注册和发现等机制;
kube-controller-manager 负责维护集群的状态,比如故障检测、自动扩展、滚动更新等;
kube-scheduler 负责资源的调度,按照预定的调度策略将 Pod 调度到相应的机器上;
kubelet 负责维持容器的生命周期,同时也负责 Volume(CVI)和网络(CNI)的管理;
Container runtime 负责镜像管理以及 Pod 和容器的真正运行(CRI),默认的容器运行时为 Docker;
kube-proxy 负责为 Service 提供 cluster 内部的服务发现和负载均衡;
1 集群架构图
一个简单架构图,不含dashboard,只有一个master和两个node官网配图:
kube-dns 负责为整个集群提供 DNS 服务
Ingress Controller 为服务提供外网入口
Heapster 提供资源监控
Dashboard 提供 GUI
Federation 提供跨可用区的集群
Fluentd-elasticsearch 提供集群日志采集、存储与查询
2 组件简介
2.1 master组件
2.1.1 kube-apiserver
1)提供了集群管理的REST API接口(包括认证授权、数据校验以及集群状态变更);
2)提供其他模块之间的数据交互和通信的枢纽(其他模块通过API Server查询或修改数据,只有API Server才直接操作etcd);
3)是资源配额控制的入口;
- 工作原理
k8s通过kube-apiserver这个进程提供服务,该进程运行在单个k8s-master节点上。默认有两个端口。
- 1)本地端口
1)该端口用于接收HTTP请求;
2)该端口默认值为8080,可以通过API Server的启动参数“–insecure-port”的值来修改默认值;
3)默认的IP地址为“localhost”,可以通过启动参数“–insecure-bind-address”的值来修改该IP地址;
4)非认证或授权的HTTP请求通过该端口访问API Server。
- 2)安全端口
1)该端口默认值为6443,可通过启动参数“–secure-port”的值来修改默认值;
2)默认IP地址为非本地(Non-Localhost)网络端口,通过启动参数“–bind-address”设置该值;
3)该端口用于接收HTTPS请求;
4)用于基于Tocken文件或客户端证书及HTTP Base的认证;
5)用于基于策略的授权;
6)默认不启动HTTPS安全访问控制。
- 1)curl
curl localhost:8080/api
curl localhost:8080/api/v1/pods
curl localhost:8080/api/v1/services
curl localhost:8080/api/v1/replicationcontrollers
- 2)kubectl proxy
Kubectl Proxy代理程序既能作为API Server的反向代理,也能作为普通客户端访问API Server的代理。通过master节点的8080端口来启动该代理程序。
- 3)kubectl客户端
命令行工具kubectl客户端,通过命令行参数转换为对API Server的REST API调用,并将调用结果输出。
- 4)编程方式调用
略
k8s API Server最主要的REST接口是资源对象的增删改查。
另外还有一类特殊的REST接口—k8s Proxy API接口,这类接口的作用是代理REST请求,即kubernetes API Server把收到的REST请求转发到某个Node上的kubelet守护进程的REST端口上,由该kubelet进程负责响应。
kubernetes API Server作为集群的核心,负责集群各功能模块之间的通信,集群内各个功能模块通过API Server将信息存入etcd,当需要获取和操作这些数据时,通过API Server提供的REST接口(GET\LIST\WATCH方法)来实现,从而实现各模块之间的信息交互。
- 1)kubelet与apiserver交互
每个Node节点上的kubelet定期就会调用API Server的REST接口报告自身状态,API Server接收这些信息后,将节点状态信息更新到etcd中。kubelet也通过API Server的Watch接口监听Pod信息,从而对Node机器上的POD进行管理。
监听信息 | kubelet动作 |
新的pod副本被调度绑定到本节点 | 执行pod对性的容器的创建和启动逻辑 |
pod对象被删除 | 删除本节点上相应的pod容器 |
修改pod信息 | 修改本节点的pod容器 |
- 2)kube-controller-manager与apiserver交互
kube-controller-manager中的Node Controller模块通过API Server提供的Watch接口,实时监控Node的信息,并做相应处理。
- 3)kube-scheduler与apiserver交互
Scheduler通过API Server的Watch接口监听到新建Pod副本的信息后,它会检索所有符合该Pod要求的Node列表,开始执行Pod调度逻辑。调度成功后将Pod绑定到目标节点上。
API Server 主要是和 etcd 打交道,并且对外提供 HTTP 服务,以及进行安全控制,因此它的命令行提供的参数也主要和这几个方面有关。下面是一些比较重要的参数以及说明(不同版本参数可能会有不同):
参数 | 含义 | 默认值 |
–advertise-address | 通过该 ip 地址向集群其他节点公布 api server 的信息,必须能够被其他节点访问 | nil |
–allow-privileged | 是否允许 privileged 容器运行 | false |
–admission-control | 准入控制 | AlwaysAdmit |
–authorization-mode | 授权模式 ,安全接口上的授权 | AlwaysAllow |
–bind-address | HTTPS 安全接口的监听地址 | 0.0.0.0 |
–secure-port | HTTPS 安全接口的监听端口 | 6443 |
–cert-dir | TLS 证书的存放目录 | /var/run/kubernetes |
–etcd-prefix | 信息存放在 etcd 中地址的前缀 | “/registry” |
–etcd-servers | 逗号分割的 etcd server 地址 | [] |
–insecure-bind-address | HTTP 访问的地址 | 127.0.0.1 |
–insecure-port HTTP | 访问的端口 | 8080 |
–log-dir | 日志存放的目录 | |
–service-cluster-ip-range | service 要使用的网段,使用 CIDR 格式,参考 kubernetes 中 service 的定义 |
2.1.2 kube-scheduler
kube-scheduler 负责分配调度 Pod 到集群内的节点上,它监听 kube-apiserver,查询还未分配 Node 的 Pod,然后根据调度策略为这些 Pod 分配节点(更新 Pod 的 NodeName 字段)。
https://feisky.gitbooks.io/kubernetes/components/scheduler.html
- 调度器需要充分考虑诸多的因素:
- 指定node节点调度
有三种方式可以指定 Pod 只运行在指定的 Node 节点上
1)nodeSelector:只调度到匹配指定 label 的 Node 上
2)nodeAffinity:功能更丰富的 Node 选择器,比如支持集合操作
3)podAffinity:调度到满足条件的 Pod 所在的 Node 上
- taints和tolerations
Taints 和 tolerations 用于保证 Pod 不被调度到不合适的 Node 上,其中 Taint 应用于 Node 上,而 toleration 则应用于 Pod 上。
目前支持的 taint 类型:
1)NoSchedule:新的 Pod 不调度到该 Node 上,不影响正在运行的 Pod
2)PreferNoSchedule:soft 版的 NoSchedule,尽量不调度到该 Node 上
3)NoExecute:新的 Pod 不调度到该 Node 上,并且删除(evict)已在运行的 Pod。Pod 可以增加一个时间(tolerationSeconds)
- 优先级调度
从 v1.8 开始,kube-scheduler 支持定义 Pod 的优先级,从而保证高优先级的 Pod 优先调度。并从 v1.11 开始默认开启。
- 多调度器
如果默认的调度器不满足要求,还可以部署自定义的调度器。并且,在整个集群中还可以同时运行多个调度器实例,通过 podSpec.schedulerName 来选择使用哪一个调度器(默认使用内置的调度器)。
- 调度器扩展
kube-scheduler 还支持使用 --policy-config-file 指定一个调度策略文件来自定义调度策略 - 其他影响调度的因素
- scheduler工作原理
kube-scheduler 调度分为两个阶段,predicate 和 priority
1)predicate:过滤不符合条件的节点
2)priority:优先级排序,选择优先级最高的节点
2.1.3 kube-controller-manager
- Controller Manager 由 kube-controller-manager 和 cloud-controller-manager 组成,是 Kubernetes 的大脑,它通过 apiserver 监控整个集群的状态,并确保集群处于预期的工作状态。
1)必须启动的控制器:18个
EndpointController
ReplicationController
PodGCController
ResourceQuotaController
NamespaceController
ServiceAccountController
GarbageCollectorController
DaemonSetController
JobController
DeploymentController
ReplicaSetController
HPAController
DisruptionController
StatefulSetController
CronJobController
CSRSigningController
CSRApprovingController
TTLController2)默认启动可选的控制器,可以通过选项设置是否开启
TokenController
NodeController
ServiceController
RouteController
PVBinderController
AttachDetachController3)默认禁止的可选控制器,可通过选项设置是否开启
BootstrapSignerController
TokenCleanerController
Node Controller
Route Controller
Service Controller
Controller manager metrics 提供了控制器内部逻辑的性能度量,如 Go 语言运行时度量、etcd 请求延时、云服务商 API 请求延时、云存储请求延时等。
Controller manager metrics 默认监听在 kube-controller-manager 的 10252 端口,提供 Prometheus 格式的性能度量数据,可以通过 http://localhost:10252/metrics 来访问。
在启动时设置 --leader-elect=true 后,controller manager 会使用多节点选主的方式选择主节点。只有主节点才会调用 StartControllers() 启动所有控制器,而其他从节点则仅执行选主算法。
从 Kubernetes 1.7 开始,所有需要监控资源变化情况的调用均推荐使用 Informer。Informer 提供了基于事件通知的只读缓存机制,可以注册资源变化的回调函数,并可以极大减少 API 的调用。
Node 控制器在节点异常后,会按照默认的速率(–node-eviction-rate=0.1,即每10秒一个节点的速率)进行 Node 的驱逐。Node 控制器按照 Zone 将节点划分为不同的组,再跟进 Zone 的状态进行速率调整:
1)Normal:所有节点都 Ready,默认速率驱逐。
2)PartialDisruption:即超过33% 的节点 NotReady 的状态。当异常节点比例大于 --unhealthy-zone-threshold=0.55 时开始减慢速率:
小集群(即节点数量小于 --large-cluster-size-threshold=50):停止驱逐
大集群,减慢速率为 --secondary-node-eviction-rate=0.01
3)FullDisruption:所有节点都 NotReady,返回使用默认速率驱逐。但当所有 Zone 都处在 FullDisruption 时,停止驱逐。
2.1.4 etcd
Etcd 是 CoreOS 基于 Raft 开发的分布式 key-value 存储,可用于服务发现、共享配置以及一致性保障(如数据库选主、分布式锁等)。etcd作为一个受到ZooKeeper与doozer启发而催生的项目,除了拥有与之类似的功能外,更专注于以下四点。
1)简单:基于HTTP+JSON的API让你用curl就可以轻松使用。
2)安全:可选SSL客户认证机制。
3)快速:每个实例每秒支持一千次写操作。
4)可信:使用Raft算法充分实现了分布式。
1)基本的 key-value 存储
2)监听机制
3)key 的过期及续约机制,用于监控和服务发现
4)原子性操作(CAS 和 CAD),用于分布式锁和 leader 选举
tips:
CAS的原理:CAS操作需要输入两个数值,一个旧值(期望操作之前的值)和一个新值,在操作期间先比较旧值有没有变化,如果没有发生变化,才交换新值,发生了变化则不交换
RAFT算法把一致性问题分解成了几个子问题:选举方法,日志复制,安全性和失效处理
- 1)选举算法
- 初始启动时,节点处于 follower 状态并被设定一个 election timeout,如果在这一时间周期内没有收到来自 leader 的 heartbeat,节点将发起选举:将自己切换为 candidate 之后,向集群中其它 follower 节点发送请求,询问其是否选举自己成为 leader。
- 当收到来自集群中过半数节点的接受投票后,节点即成为 leader,开始接收保存 client 的数据并向其它的 follower 节点同步日志。如果没有达成一致,则 candidate 随机选择一个等待间隔(150ms ~ 300ms)再次发起投票,得到集群中半数以上 follower 接受的 candidate 将成为 leader
- leader 节点依靠定时向 follower 发送 heartbeat 来保持其地位。
- 任何时候如果其它 follower 在 election timeout 期间都没有收到来自 leader 的 heartbeat,同样会将自己的状态切换为 candidate 并发起选举。每成功选举一次,新 leader 的任期(Term)都会比之前 leader 的任期大 1。
- 2)日志复制
当前 Leader 收到客户端的日志(事务请求)后先把该日志追加到本地的 Log 中,然后通过 heartbeat 把该 Entry 同步给其他 Follower,Follower 接收到日志后记录日志然后向 Leader 发送 ACK,当 Leader 收到大多数(n/2+1)Follower 的 ACK 信息后将该日志设置为已提交并追加到本地磁盘中,通知客户端并在下个 heartbeat 中 Leader 将通知所有的 Follower 将该日志存储在自己的本地磁盘中。
- 3)安全性
安全性是用于保证每个节点都执行相同序列的安全机制,如当某个 Follower 在当前 Leader commit Log 时变得不可用了,稍后可能该 Follower 又会被选举为 Leader,这时新 Leader 可能会用新的 Log 覆盖先前已 committed 的 Log,这就是导致节点执行不同序列;Safety 就是用于保证选举出来的 Leader 一定包含先前 committed Log 的机制;
- 选举安全性(Election Safety):每个任期(Term)只能选举出一个 Leader
- Leader 完整性(Leader Completeness):指 Leader 日志的完整性,当 Log 在任期 Term1 被 Commit 后,那么以后任期 Term2、Term3… 等的 Leader 必须包含该 Log;Raft 在选举阶段就使用 Term 的判断用于保证完整性:当请求投票的该 Candidate 的 Term 较大或 Term 相同 Index 更大则投票,否则拒绝该请求。
- 4)失效处理
- Leader 失效:其他没有收到 heartbeat 的节点会发起新的选举,而当 Leader 恢复后由于步进数小会自动成为 follower(日志也会被新 leader 的日志覆盖)
- follower 节点不可用:follower 节点不可用的情况相对容易解决。因为集群中的日志内容始终是从 leader 节点同步的,只要这一节点再次加入集群时重新从 leader 节点处复制日志即可。
- 多个 candidate:冲突后 candidate 将随机选择一个等待间隔(150ms ~ 300ms)再次发起投票,得到集群中半数以上 follower 接受的 candidate 将成为 leader
Etcd v2 和 v3 本质上是共享同一套 raft 协议代码的两个独立的应用,接口不一样,存储不一样,数据互相隔离。也就是说如果从 Etcd v2 升级到 Etcd v3,原来 v2 的数据还是只能用 v2 的接口访问,v3 的接口创建的数据也只能访问通过 v3 的接口访问。所以我们按照 v2 和 v3 分别分析。
推荐在 Kubernetes 集群中使用 Etcd v3,v2 版本已在 Kubernetes v1.11 中弃用。
- Etcd v2 存储,Watch 以及过期机制
Etcd v2 是个纯内存的实现,并未实时将数据写入到磁盘,持久化机制很简单,就是将 store 整合序列化成 json 写入文件。数据在内存中是一个简单的树结构。比如以下数据存储到 Etcd 中的结构就如图所示。
这里有几个影响使用的细节问题:
- EventHistroy 是有长度限制的,最长 1000。也就是说,如果你的客户端停了许久,然后重新 watch 的时候,可能和该 waitIndex 相关的 event 已经被淘汰了,这种情况下会丢失变更。
- 如果通知 watcher 的时候,出现了阻塞(每个 watcher 的 channel 有 100 个缓冲空间),Etcd 会直接把 watcher 删除,也就是会导致 wait 请求的连接中断,客户端需要重新连接。
- Etcd store 的每个 node 中都保存了过期时间,通过定时机制进行清理。
从而可以看出,Etcd v2 的一些限制:
- 过期时间只能设置到每个 key 上,如果多个 key 要保证生命周期一致则比较困难。
- watcher 只能 watch 某一个 key 以及其子节点(通过参数 recursive),不能进行多个 watch。
- 很难通过 watch 机制来实现完整的数据同步(有丢失变更的风险),所以当前的大多数使用方式是通过 watch 得知变更,然后通过 get 重新获取数据,并不完全依赖于 watch 的变更 event。
- Etcd v3 存储,Watch以及过期机制
Etcd v3 将 watch 和 store 拆开实现:
Etcd v3 store 分为两部分,一部分是内存中的索引,kvindex,是基于 google 开源的一个 golang 的 btree 实现的,另外一部分是后端存储。按照它的设计,backend 可以对接多种存储,当前使用的 boltdb。boltdb 是一个单机的支持事务的 kv 存储,Etcd 的事务是基于 boltdb 的事务实现的。Etcd 在 boltdb 中存储的 key 是 revision,value 是 Etcd 自己的 key-value 组合,也就是说 Etcd 会在 boltdb 中把每个版本都保存下,从而实现了多版本机制。
revision 主要由两部分组成,第一部分 main rev,每次事务进行加一,第二部分 sub rev,同一个事务中的每次操作加一。如上示例,第一次操作的 main rev 是 3,第二次是 4。当然这种机制大家想到的第一个问题就是空间问题,所以 Etcd 提供了命令和设置选项来控制 compact,同时支持 put 操作的参数来精确控制某个 key 的历史版本数。
了解了 Etcd 的磁盘存储,可以看出如果要从 boltdb 中查询数据,必须通过 revision,但客户端都是通过 key 来查询 value,所以 Etcd 的内存 kvindex 保存的就是 key 和 revision 之前的映射关系,用来加速查询。
然后我们再分析下 watch 机制的实现。Etcd v3 的 watch 机制支持 watch 某个固定的 key,也支持 watch 一个范围(可以用于模拟目录的结构的 watch),所以 watchGroup 包含两种 watcher,一种是 key watchers,数据结构是每个 key 对应一组 watcher,另外一种是 range watchers, 数据结构是一个 IntervalTree(不熟悉的参看文文末链接),方便通过区间查找到对应的 watcher。
同时,每个 WatchableStore 包含两种 watcherGroup,一种是 synced,一种是 unsynced,前者表示该 group 的 watcher 数据都已经同步完毕,在等待新的变更,后者表示该 group 的 watcher 数据同步落后于当前最新变更,还在追赶。
当 Etcd 收到客户端的 watch 请求,如果请求携带了 revision 参数,则比较请求的 revision 和 store 当前的 revision,如果大于当前 revision,则放入 synced 组中,否则放入 unsynced 组。同时 Etcd 会启动一个后台的 goroutine 持续同步 unsynced 的 watcher,然后将其迁移到 synced 组。也就是这种机制下,Etcd v3 支持从任意版本开始 watch,没有 v2 的 1000 条历史 event 表限制的问题(当然这是指没有 compact 的情况下)。
另外我们前面提到的,Etcd v2 在通知客户端时,如果网络不好或者客户端读取比较慢,发生了阻塞,则会直接关闭当前连接,客户端需要重新发起请求。Etcd v3 为了解决这个问题,专门维护了一个推送时阻塞的 watcher 队列,在另外的 goroutine 里进行重试。
Etcd v3 对过期机制也做了改进,过期时间设置在 lease 上,然后 key 和 lease 关联。这样可以实现多个 key 关联同一个 lease id,方便设置统一的过期时间,以及实现批量续约。
- 相比 Etcd v2, Etcd v3 的一些主要变化
- 接口通过 grpc 提供 rpc 接口,放弃了 v2 的 http 接口。优势是长连接效率提升明显,缺点是使用不如以前方便,尤其对不方便维护长连接的场景。
- 废弃了原来的目录结构,变成了纯粹的 kv,用户可以通过前缀匹配模式模拟目录。
- 内存中不再保存 value,同样的内存可以支持存储更多的 key。
- watch 机制更稳定,基本上可以通过 watch 机制实现数据的完全同步。
- 提供了批量操作以及事务机制,用户可以通过批量事务请求来实现 Etcd v2 的 CAS 机制(批量事务支持 if 条件判断)。
Etcd 和 Zookeeper 提供的能力非常相似,都是通用的一致性元信息存储,都提供 watch 机制用于变更通知和分发,也都被分布式系统用来作为共享信息存储,在软件生态中所处的位置也几乎是一样的,可以互相替代的。二者除了实现细节,语言,一致性协议上的区别,最大的区别在周边生态圈。Zookeeper 是 apache 下的,用 java 写的,提供 rpc 接口,最早从 hadoop 项目中孵化出来,在分布式系统中得到广泛使用(hadoop, solr, kafka, mesos 等)。Etcd 是 coreos 公司旗下的开源产品,比较新,以其简单好用的 rest 接口以及活跃的社区俘获了一批用户,在新的一些集群中得到使用(比如 kubernetes)。虽然 v3 为了性能也改成二进制 rpc 接口了,但其易用性上比 Zookeeper 还是好一些。
而 Consul 的目标则更为具体一些,Etcd 和 Zookeeper 提供的是分布式一致性存储能力,具体的业务场景需要用户自己实现,比如服务发现,比如配置变更。而 Consul 则以服务发现和配置变更为主要目标,同时附带了 kv 存储。
- Confd
在分布式系统中,理想情况下是应用程序直接和 Etcd 这样的服务发现 / 配置中心交互,通过监听 Etcd 进行服务发现以及配置变更。但我们还有许多历史遗留的程序,服务发现以及配置大多都是通过变更配置文件进行的。Etcd 自己的定位是通用的 kv 存储,所以并没有像 Consul 那样提供实现配置变更的机制和工具,而 Confd 就是用来实现这个目标的工具。
Confd 通过 watch 机制监听 Etcd 的变更,然后将数据同步到自己的一个本地存储。用户可以通过配置定义自己关注哪些 key 的变更,同时提供一个配置文件模板。Confd 一旦发现数据变更就使用最新数据渲染模板生成配置文件,如果新旧配置文件有变化,则进行替换,同时触发用户提供的 reload 脚本,让应用程序重新加载配置。
Confd 相当于实现了部分 Consul 的 agent 以及 consul-template 的功能,作者是 kubernetes 的 Kelsey Hightower,但大神貌似很忙,没太多时间关注这个项目了,很久没有发布版本,我们着急用,所以 fork 了一份自己更新维护,主要增加了一些新的模板函数以及对 metad 后端的支持。confd
- Metad
服务注册的实现模式一般分为两种,一种是调度系统代为注册,一种是应用程序自己注册。调度系统代为注册的情况下,应用程序启动后需要有一种机制让应用程序知道『我是谁』,然后发现自己所在的集群以及自己的配置。Metad 提供这样一种机制,客户端请求 Metad 的一个固定的接口 /self,由 Metad 告知应用程序其所属的元信息,简化了客户端的服务发现和配置变更逻辑。
Metad 通过保存一个 ip 到元信息路径的映射关系来做到这一点,当前后端支持 Etcd v3,提供简单好用的 http rest 接口。 它会把 Etcd 的数据通过 watch 机制同步到本地内存中,相当于 Etcd 的一个代理。所以也可以把它当做 Etcd 的代理来使用,适用于不方便使用 Etcd v3 的 rpc 接口或者想降低 Etcd 压力的场景。
- 1)Etcd cluster 初始化的问题
如果集群第一次初始化启动的时候,有一台节点未启动,通过 v3 的接口访问的时候,会报告 Error: Etcdserver: not capable 错误。这是为兼容性考虑,集群启动时默认的 API 版本是 2.3,只有当集群中的所有节点都加入了,确认所有节点都支持 v3 接口时,才提升集群版本到 v3。这个只有第一次初始化集群的时候会遇到,如果集群已经初始化完毕,再挂掉节点,或者集群关闭重启(关闭重启的时候会从持久化数据中加载集群 API 版本),都不会有影响。
- 2)Etcd 读请求的机制
v2 quorum=true 的时候,读取是通过 raft 进行的,通过 cli 请求,该参数默认为 true。
v3 --consistency=“l” 的时候(默认)通过 raft 读取,否则读取本地数据。sdk 代码里则是通过是否打开:WithSerializable option 来控制。
一致性读取的情况下,每次读取也需要走一次 raft 协议,能保证一致性,但性能有损失,如果出现网络分区,集群的少数节点是不能提供一致性读取的。但如果不设置该参数,则是直接从本地的 store 里读取,这样就损失了一致性。使用的时候需要注意根据应用场景设置这个参数,在一致性和可用性之间进行取舍。
- 3)Etcd 的 compact 机制
Etcd 默认不会自动 compact,需要设置启动参数,或者通过命令进行 compact,如果变更频繁建议设置,否则会导致空间和内存的浪费以及错误。Etcd v3 的默认的 backend quota 2GB,如果不 compact,boltdb 文件大小超过这个限制后,就会报错:”Error: etcdserver: mvcc: database space exceeded”,导致数据无法写入。
2.2 node组件
2.2.1 kubelet
每个节点上都运行一个 kubelet 服务进程,默认监听 10250 端口,接收并执行 master 发来的指令,管理 Pod 及 Pod 中的容器。每个 kubelet 进程会在 API Server 上注册节点自身信息,定期向 master 节点汇报节点的资源使用情况,并通过 cAdvisor 监控节点和容器的资源。
节点管理主要是节点自注册和节点状态更新:
- Kubelet 可以通过设置启动参数 --register-node 来确定是否向 API Server 注册自己;
- 如果 Kubelet 没有选择自注册模式,则需要用户自己配置 Node 资源信息,同时需要告知 Kubelet 集群上的 API Server 的位置;
- Kubelet 在启动时通过 API Server 注册节点信息,并定时向 API Server 发送节点新消息,API Server 在接收到新消息后,将信息写入 etcd
- 1)获取pod清单
Kubelet 以 PodSpec 的方式工作。PodSpec 是描述一个 Pod 的 YAML 或 JSON 对象。 kubelet 采用一组通过各种机制提供的 PodSpecs(主要通过 apiserver),并确保这些 PodSpecs 中描述的 Pod 正常健康运行。
向 Kubelet 提供节点上需要运行的 Pod 清单的方法:
- 文件:启动参数 --config 指定的配置目录下的文件 (默认 / etc/kubernetes/manifests/)。该文件每 20 秒重新检查一次(可配置)。
- HTTP endpoint (URL):启动参数 --manifest-url 设置。每 20 秒检查一次这个端点(可配置)。
- API Server:通过 API Server 监听 etcd 目录,同步 Pod 清单。
- HTTP server:kubelet 侦听 HTTP 请求,并响应简单的 API 以提交新的 Pod 清单。
- 2)通过apiserver获取pod清单及创建pod的过程
Kubelet 通过 API Server Client(Kubelet 启动时创建)使用 Watch 加 List 的方式监听 “/registry/nodes/$ 当前节点名” 和 “/registry/pods” 目录,将获取的信息同步到本地缓存中。
Kubelet 监听 etcd,所有针对 Pod 的操作都将会被 Kubelet 监听到。如果发现有新的绑定到本节点的 Pod,则按照 Pod 清单的要求创建该 Pod。
如果发现本地的 Pod 被修改,则 Kubelet 会做出相应的修改,比如删除 Pod 中某个容器时,则通过 Docker Client 删除该容器。 如果发现删除本节点的 Pod,则删除相应的 Pod,并通过 Docker Client 删除 Pod 中的容器。
Kubelet 读取监听到的信息,如果是创建和修改 Pod 任务,则执行如下处理:
- 为该 Pod 创建一个数据目录;
- 从 API Server 读取该 Pod 清单;
- 为该 Pod 挂载外部卷;
- 下载 Pod 用到的 Secret;
- 检查已经在节点上运行的 Pod,如果该 Pod 没有容器或 Pause 容器没有启动,则先停止 Pod 里所有容器的进程。如果在 Pod 中有需要删除的容器,则删除这些容器;
- 用 “kubernetes/pause” 镜像为每个 Pod 创建一个容器。Pause 容器用于接管 Pod 中所有其他容器的网络。每创建一个新的 Pod,Kubelet 都会先创建一个 Pause 容器,然后创建其他容器。
- 为 Pod 中的每个容器做如下处理:
为容器计算一个 hash 值,然后用容器的名字去 Docker 查询对应容器的 hash 值。若查找到容器,且两者 hash 值不同,则停止 Docker 中容器的进程,并停止与之关联的 Pause 容器的进程;若两者相同,则不做任何处理;
如果容器被终止了,且容器没有指定的 restartPolicy,则不做任何处理;
调用 Docker Client 下载容器镜像,调用 Docker Client 运行容器。
- 3)static pod
所有以非 API Server 方式创建的 Pod 都叫 Static Pod。Kubelet 将 Static Pod 的状态汇报给 API Server,API Server 为该 Static Pod 创建一个 Mirror Pod 和其相匹配。Mirror Pod 的状态将真实反映 Static Pod 的状态。当 Static Pod 被删除时,与之相对应的 Mirror Pod 也会被删除。
- Pod 通过两类探针检查容器的健康状态:
- LivenessProbe 探针:用于判断容器是否健康,告诉 Kubelet 一个容器什么时候处于不健康的状态。如果 LivenessProbe 探针探测到容器不健康,则 Kubelet 将删除该容器,并根据容器的重启策略做相应的处理。如果一个容器不包含 LivenessProbe 探针,那么 Kubelet 认为该容器的 LivenessProbe 探针返回的值永远是 “Success”;
- ReadinessProbe:用于判断容器是否启动完成且准备接收请求。如果 ReadinessProbe 探针探测到失败,则 Pod 的状态将被修改。Endpoint Controller 将从 Service 的 Endpoint 中删除包含该容器所在 Pod 的 IP 地址的 Endpoint 条目。
Kubelet 定期调用容器中的 LivenessProbe 探针来诊断容器的健康状况。LivenessProbe 包含如下三种实现方式:
- ExecAction:在容器内部执行一个命令,如果该命令的退出状态码为 0,则表明容器健康;
- TCPSocketAction:通过容器的 IP 地址和端口号执行 TCP 检查,如果端口能被访问,则表明容器健康;
- HTTPGetAction:通过容器的 IP 地址和端口号及路径调用 HTTP GET 方法,如果响应的状态码大于等于 200 且小于 400,则认为容器状态健康。
LivenessProbe 探针包含在 Pod 定义的 spec.containers.{某个容器} 中。
Kubernetes 集群中,应用程序的执行情况可以在不同的级别上监测到,这些级别包括:容器、Pod、Service 和整个集群。Heapster 项目为 Kubernetes 提供了一个基本的监控平台,它是集群级别的监控和事件数据集成器 (Aggregator)。Heapster 以 Pod 的方式运行在集群中,Heapster 通过 Kubelet 发现所有运行在集群中的节点,并查看来自这些节点的资源使用情况。Kubelet 通过 cAdvisor 获取其所在节点及容器的数据。Heapster 通过带着关联标签的 Pod 分组这些信息,这些数据将被推到一个可配置的后端,用于存储和可视化展示。支持的后端包括 InfluxDB(使用 Grafana 实现可视化) 和 Google Cloud Monitoring。
cAdvisor 是一个开源的分析容器资源使用率和性能特性的代理工具,已集成到 Kubernetes 代码中。
cAdvisor 自动查找所有在其所在节点上的容器,自动采集 CPU、内存、文件系统和网络使用的统计信息。
cAdvisor 通过它所在节点机的 Root 容器,采集并分析该节点机的全面使用情况。
cAdvisor 通过其所在节点机的 4194 端口暴露一个简单的 UI。
Kubelet 会监控资源的使用情况,并使用驱逐机制防止计算和存储资源耗尽。在驱逐时,Kubelet 将 Pod 的所有容器停止,并将 PodPhase 设置为 Failed。 Kubelet 定期(housekeeping-interval)检查系统的资源是否达到了预先配置的驱逐阈值,包括:这些驱逐阈值可以使用百分比,也可以使用绝对值,如
--eviction-hard=memory.available<500Mi,nodefs.available<1Gi,imagefs.available<100Gi
--eviction-minimum-reclaim="memory.available=0Mi,nodefs.available=500Mi,imagefs.available=2Gi"`
--system-reserved=memory=1.5Gi
这些驱逐信号可以分为软驱逐和硬驱逐:
- 软驱逐(Soft Eviction):配合驱逐宽限期(eviction-soft-grace-period和eviction-max-pod-grace-period)一起使用。系统资源达到软驱逐阈值并在超过宽限期之后才会执行驱逐动作。
- 硬驱逐(Hard Eviction ):系统资源达到硬驱逐阈值时立即执行驱逐动作。
驱逐动作包括回收节点资源和驱逐用户 Pod 两种:
容器运行时(Container Runtime)是 Kubernetes 最重要的组件之一,负责真正管理镜像和容器的生命周期。Kubelet 通过 Container Runtime Interface (CRI) 与容器运行时交互,以管理镜像和容器。
Kubelet 由许多内部组件构成
- Kubelet API,包括 10250 端口的认证 API、4194 端口的 cAdvisor API、10255 端口的只读 API 以及 10248 端口的健康检查 API
- syncLoop:从 API 或者 manifest 目录接收 Pod 更新,发送到 podWorkers 处理,大量使用 channel 处理来处理异步请求
- 辅助的 manager,如 cAdvisor、PLEG、Volume Manager 等,处理 syncLoop 以外的其他工作
- CRI:容器执行引擎接口,负责与 runtime shim 通信
- 容器执行引擎,如 dockershim、rkt 等(注:rkt 暂未完成 CRI 的迁移)
- 网络插件,目前支持 CNI 和 kubenet
- pod启动流程
通过 Kubelet 的 10255 端口可以查询 Node 的汇总指标。有两种访问方式
- 在集群内部可以直接访问 kubelet 的 10255 端口,比如 http://:10255/stats/summary
- 在集群外部可以借助 kubectl proxy 来访问,比如
kubectl proxy&
curl http://localhost:8001/api/v1/proxy/nodes/:10255/stats/summary
2.2.2 kube-proxy
每台机器上都运行一个 kube-proxy 服务,它监听 API server 中 service 和 endpoint 的变化情况,并通过 iptables 等来为服务配置负载均衡(仅支持 TCP 和 UDP)。
kube-proxy 可以直接运行在物理机上,也可以以 static pod 或者 daemonset 的方式运行。kube-proxy 当前支持以下几种实现:
- userspace:最早的负载均衡方案,它在用户空间监听一个端口,所有服务通过 iptables 转发到这个端口,然后在其内部负载均衡到实际的 Pod。该方式最主要的问题是效率低,有明显的性能瓶颈。
- iptables:目前推荐的方案,完全以 iptables 规则的方式来实现 service 负载均衡。该方式最主要的问题是在服务多的时候产生太多的 iptables 规则,非增量式更新会引入一定的时延,大规模情况下有明显的性能问题
- ipvs:为解决 iptables 模式的性能问题,v1.11 新增了 ipvs 模式(v1.8 开始支持测试版,并在 v1.11 GA),采用增量式更新,并可以保证 service 更新期间连接保持不断开
- winuserspace:同 userspace,但仅工作在 windows 节点上
注意:使用 ipvs 模式时,需要预先在每台 Node 上加载内核模块 nf_conntrack_ipv4,ip_vs,ip_vs_rr,ip_vs_wrr, ip_vs_sh 等。
kube-proxy 监听 API server 中 service 和 endpoint 的变化情况,并通过 userspace、iptables、ipvs 或 winuserspace 等 proxier 来为服务配置负载均衡(仅支持 TCP 和 UDP)。
2.3 插件简介
2.3.1 DNS
DNS 是 Kubernetes 的核心功能之一,通过 kube-dns 或 CoreDNS 作为集群的必备扩展来提供命名服务。
从 v1.11 开始可以使用 CoreDNS 来提供命名服务,并从 v1.13 开始成为默认 DNS 服务。CoreDNS 的特点是效率更高,资源占用率更小,推荐使用 CoreDNS 替代 kube-dns 为集群提供 DNS 服务。
从 kube-dns 升级为 CoreDNS 的步骤为:
$ git clone https://github.com/coredns/deployment
$ cd deployment/kubernetes
$ ./deploy.sh | kubectl apply -f -
$ kubectl delete --namespace=kube-system deployment kube-dns
如下图所示,kube-dns 由三个容器构成:
- kube-dns:DNS 服务的核心组件,主要由 KubeDNS 和 SkyDNS 组成
1)KubeDNS 负责监听 Service 和 Endpoint 的变化情况,并将相关的信息更新到 SkyDNS 中
2)SkyDNS 负责 DNS 解析,监听在 10053 端口 (tcp/udp),同时也监听在 10055 端口提供 metrics
3)kube-dns 还监听了 8081 端口,以供健康检查使用- dnsmasq-nanny:负责启动 dnsmasq,并在配置发生变化时重启 dnsmasq
dnsmasq 的 upstream 为 SkyDNS,即集群内部的 DNS 解析由 SkyDNS 负责- sidecar:负责健康检查和提供 DNS metrics(监听在 10054 端口)
kubectl apply -f https://github.com/feiskyer/kubernetes-handbook/raw/master/manifests/kubedns/kube-dns.yaml
从 Kubernetes 1.6 开始,可以通过为 kube-dns 提供 ConfigMap 来实现对存根域以及上游名称服务器的自定义指定。例如,下面的配置插入了一个单独的私有根 DNS 服务器和两个上游 DNS 服务器。
apiVersion: v1
kind: ConfigMap
metadata:
name: kube-dns
namespace: kube-system
data:
stubDomains: |
{“acme.local”: [“1.2.3.4”]}
upstreamNameservers: |
[“8.8.8.8”, “8.8.4.4”]
2.3.2 dashboard
https://kubernetes.io/zh/docs/tasks/access-application-cluster/web-ui-dashboard/
Dashboard 是 Kubernetes 集群的通用基于 Web 的 UI。它使用户可以管理集群中运行的应用程序以及集群本身并进行故障排除。
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta4/aio/deploy/recommended.yaml
2.3.3 容器资源监控
容器资源监控将关于容器的一些常见的时间序列度量值保存到一个集中的数据库中,并提供用于浏览这些数据的界面。
Prometheus,一个 CNCF 项目,可以原生监控 Kubernetes、节点和 Prometheus 本身。
2.3.4 集群层面日志
集群层面日志 机制负责将容器的日志数据保存到一个集中的日志存储中,该存储能够提供搜索和浏览接口.
https://kubernetes.io/zh/docs/concepts/cluster-administration/logging/虽然Kubernetes没有为集群级日志记录提供原生的解决方案,但您可以考虑几种常见的方法。以下是一些选项:
- 使用在每个节点上运行的节点级日志记录代理。
- 在应用程序的 pod 中,包含专门记录日志的 sidecar 容器。
- 将日志直接从应用程序中推送到日志记录后端。