发布于 

Kubernetes 基础概念与原理解析 ☸

K8s 本质上是应用服务和服务器之间的中间层,通过暴露一系列 API 能力,简化了服务的部署运维流程

整体架构

K8s 整体上遵循 C/S 架构,左侧是一个官方提供的名为 kubectl 的 CLI (Command Line Interface)工具,用于使用 K8s 开放的 API 来管理集群和操作对象等,右侧则是 K8s 集群的后端服务及开放出的 API 等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
                               +-------------+                              
| |
| | +---------------+
| | +-----> | Node 1 |
| Kubernetes | | +---------------+
+-----------------+ | Server | |
| CLI | | | | +---------------+
| (Kubectl) |----------->| ( Master ) |<------+-----> | Node 2 |
| | | | | +---------------+
+-----------------+ | | |
| | | +---------------+
| | +-----> | Node 3 |
| | +---------------+
+-------------+

Node 是用于工作的机器,Master 是一种角色(Role),表示在这个 Node 上包含着管理集群的一些必要组件,生产环境中,为了保障集群的高可用,通常会部署多个 Master。

Master

Master 是整个 K8s 集群的“大脑”,与大脑类似,它有几个重要的功能:

  • 接收:外部的请求和集群内部的通知反馈
  • 发布:对集群整体的调度和管理
  • 存储:负责集群状态的存储

这些功能,也通过一些组件来共同完成,将其称为 control plane

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+----------------------------------------------------------+          
| Master |
| +-------------------------+ |
| +------->| API Server |<--------+ |
| | | | | |
| v +-------------------------+ v |
| +----------------+ ^ +--------------------+ |
| | | | | | |
| | Scheduler | | | Controller Manager | |
| | | | | | |
| +----------------+ v +--------------------+ |
| +------------------------------------------------------+ |
| | | |
| | Cluster state store | |
| | | |
| +------------------------------------------------------+ |
+----------------------------------------------------------+

Cluster state store

存储集群所有需持久化的状态,并且提供 watch 的功能支持,可以快速的通知各组件的变更等操作,目前 Kubernetes 的存储层选择是 etcd,一般情况下直接以 etcd 来代表集群状态存储服务,即将所有状态存储到 etcd 实例中。

得益于 etcd 的开发团队较为活跃,而且根据 K8s 社区的反馈做了相当大的改进,并且当时 K8s 团队主要的关注点也不在此,所以直到现在 etcd 仍不是一个可选项,后续也许将此处插件化也不是不可能

API Server

整个集群的入口,接收外部的信号和请求,并将一些信息写入到 etcd 中,它提供了认证相关的功能,用于判断是否有权限进行操作,API Server 支持多种认证方法,一般情况下都使用 X.509 证书进行认证。

X.509 是一种公钥基础设施(PKI)中使用的 数字证书标准,用于验证实体(例如用户、设备或服务器)的 身份。X.509 证书广泛应用于互联网安全、加密通信等领域,包括 HTTPS、电子邮件加密等。X.509 证书由一个可信任的证书颁发机构(CA)签名,用于证明证书中包含的公钥与持有证书的实体是可信的。

API Server 的目标是成为一个极简的 server,只提供 REST 操作,更新 etcd ,并充当着集群的网关,至于其他的业务逻辑之类的,通过插件或者在其他组件中完成。

Controller Manager

是 K8s 集群中最繁忙的部分,它在后台运行着许多不同的控制器进程,用来调节集群的状态,当集群的配置发生变更,控制器就会朝着预期的状态开始工作。

Scheduler

集群的调度器,它会持续的关注集群中未被调度的 Pod ,并根据各种条件,比如资源的可用性,节点的亲和性或者其他的一些限制条件,通过绑定的 API 将 Pod 调度/绑定到 Node 上。

节点亲和性 (Node Affinity) 是一种控制 Pod 调度位置的机制,可以指定 Pod 必须或优先调度到具有特定标签的节点上。节点亲和性比节点选择器 (Node Selector) 更为灵活,可以实现更复杂的调度规则,在资源优化、工作负载隔离、可用性提升等场景中有广泛应用,是 K8s 重要的 调度策略 之一。

Node

(Worker) Node 是加入集群中的机器,Node 加入集群并接受调度、运行服务,归功于运行在 Node 上的几个核心组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
+--------------------------------------------------------+       
| +---------------------+ +---------------------+ |
| | kubelet | | kube-proxy | |
| | | | | |
| +---------------------+ +---------------------+ |
| +----------------------------------------------------+ |
| | Container Runtime (Docker) | |
| | +---------------------+ +---------------------+ | |
| | |Pod | |Pod | | |
| | | +-----+ +-----+ | |+-----++-----++-----+| | |
| | | |C1 | |C2 | | ||C1 ||C2 ||C3 || | |
| | | | | | | | || || || || | |
| | | +-----+ +-----+ | |+-----++-----++-----+| | |
| | +---------------------+ +---------------------+ | |
| +----------------------------------------------------+ |
+--------------------------------------------------------+

Kubelet

Kubelet 实现了集群中最重要的关于 Node 和 Pod 的控制功能,如果没有 Kubelet 的存在,那 K8s 很可能是一个纯粹的通过 API Server CRUD 的应用程序。

K8s 原生的执行模式是 操作应用程序的容器,而不像传统模式那样,直接操作某个包或者是操作某个进程。基于这种模式,可以让应用程序之间相互隔离,和主机也是相互隔离的,毕竟它不依赖于主机,在任何的容器运行时(比如 Docker)上都可以部署和运行。

Pod 可以是一组容器(也可以包含存储卷),K8s 将 Pod 作为可调度的基本单位, 分离开了构建时和部署时的关注点:

  • 构建时,重点关注某个容器是否能正确构建,如何快速构建
  • 部署时,关心某个应用程序的服务是否可用,是否符合预期,依赖的相关资源是否都能访问到

这种隔离的模式,可以很方便的将应用程序与底层的基础设施解耦,极大的提高集群扩/缩容,迁移的灵活性。

Master 节点的 Scheduler 组件,它会调度未绑定的 Pod 到符合条件的 Node 上,而至于最终该 Pod 是否能运行于 Node 上,则是由 Kubelet 来裁定的。

Container runtime

容器运行时最主要的功能是下载镜像和运行容器,最常见的实现是 Docker , 目前还有其他的一些实现,比如 rkt, cri-o

K8s 提供了一套通用的容器运行时接口 CRI (Container Runtime Interface), 凡是符合这套标准的容器运行时实现,均可在 K8ss 上使用。

Kube Proxy

想要访问某个服务,那要么通过域名,要么通过 IP。每个 Pod 在创建后都会有一个虚拟 IP,K8s 中有一个抽象的概念,叫做 Servicekube-proxy 便是提供一种代理的服务,可以通过 Service 访问到 Pod。

实际的工作原理是在每个 Node 上启动一个 kube-proxy 的进程,通过编排 iptables 规则来达到此效果。

基本概念

  • Pod 是 K8s 中最小的可部署单元,中文可以翻译为“容器组”它是用于承载和管理容器的抽象层。一个 Pod 可以包含一个或多个紧密关联的容器,它们共享相同的网络命名空间、IP 地址和存储卷,并在同一个宿主机上运行。

  • ReplicaSet 的目的是维护一组在任何时候都处于运行状态的 Pod 副本的稳定集合。因此,它通常用来保证给定数量的、完全相同的 Pod 的可用性。通常不直接使用 ReplicaSet,而是在 Deployment 中声明。

  • Deployment 是对 ReplicaSet 和 Pod 更高级的抽象,它可以指挥 Kubernetes 如何创建和更新部署的应用实例,创建 Deployment 后,Kubernetes master 会将应用程序调度到集群中的各个节点上,一般用来部署无状态应用。

    • 自愈、缩放

    • 滚动更新、版本回滚

  • Service 是将运行在一个或一组 Pod 上的网络应用程序公开为网络服务的抽象方法。Service 为一组 Pod 提供相同的 DNS 名,并且在它们之间进行负载均衡。Kubernetes 为 Pod 提供分配了 IP 地址,但 IP 地址可能会发生变化,集群内的容器可以通过 service 名称访问服务,而不需要担心 Pod 的 IP 发生变化。

    • ClusterIP:将服务公开在 集群内部。kubernetes 会给服务分配一个集群内部的 IP,集群内的所有主机都可以通过这个 Cluster-IP 访问服务。集群内部的 Pod 可以通过 service 名称访问服务。

    • NodePort:通过每个节点的主机 IP 和静态端口(NodePort)暴露服务。 集群外部 的主机可以使用节点 IP 和 NodePort 访问服务。

    • ExternalName:将集群外部的网络引入集群内部。

    • LoadBalancer:使用云提供商的负载均衡器向外部暴露服务。

  • Ingress 是对集群中服务的外部访问进行管理的 API 对象,典型的访问方式是 HTTP。可以通过 Ingress 资源来配置不同的转发规则,从而达到根据不同的规则设置访问集群内不同的 Service 所对应的后端 Pod。当集群位于公有云或私有云上时,要从互联网进行访问,需要使用公网 IP 或者域名,公网 IP 是相对稀缺的资源,不可能给所有主机都分配公网 IP,并且随着公开的服务变多,众多的端口也变得难以管理。面对这种情况,可以使用 Ingress。

  • Kubectl 是 K8s 提供的命令行工具。

  • Namespace 是一种资源隔离机制,将同一集群中的资源划分为相互隔离的组。命名空间作用域仅针对带有名字空间的对象,例如 Deployment、Service 等。Kubernetes 会创建四个初始命名空间:

    • default 默认的命名空间,不可删除,未指定命名空间的对象都会被分配到 default 中。
    • kube-system Kubernetes 系统对象(控制平面和 Node 组件)所使用的命名空间。
    • kube-public 自动创建的公共命名空间,所有用户(包括未经过身份验证的用户)都可以读取它。通常我们约定,将整个集群中公用的可见和可读的资源放在这个空间中。
    • kube-node-lease 租约(Lease)对象使用的命名空间。每个节点都有一个关联的 lease 对象,lease 是一种轻量级资源。lease 对象通过发送心跳,检测集群中的每个节点是否发生故障。
  • 管理对象的方式

    • 命令行指令,使用 kubectl 命令来创建和管理 Kubernetes 对象。命令行就好比口头传达,简单、快速、高效。但它功能有限,不适合复杂场景,操作不容易追溯,多用于开发和调试。
    • 声明式配置,kubernetes 使用 yaml 文件来描述 Kubernetes 对象。声明式配置就好比申请表,学习难度大且配置麻烦。好处是操作留痕,适合操作复杂的对象,多用于生产。
    • 可视化界面,如云平台容器服务
  • 服务部署流程,YAML 文件 → kubectl → [API Server → etcd → Scheduler → Controller Mgr] → [Kubelet → Container runtime → Pod]

  • 服务调用流程,request → Ingress 控制器 → [Kube Proxy → Pod]

kubectl

K8s 遵循 C/S 架构,官方也提供了 CLI 工具 kubectl 用于完成大多数集群管理相关的功能。

  • 基础配置

    • 使用 kubectl options 可以看到所有全局可用的配置项
    • $HOME/.kube/config 中主要包含着:
      • K8s 集群的 API 地址
      • 用于认证的证书地址
    • 也可以使用 --kubeconfig 或者环境变量 KUBECONFIG 来传递配置文件
    • 也可以直接传递相关参数来使用,kubectl -client-key='xxx' --client-certificate='xxx'
  • get(读取数据类)

    • **kubectl cluster-info**:查看集群控制平面的信息,包括 API server 和 DNS 服务的访问地址
    • **kubectl get nodes**:获取集群节点的详细信息,可以通过 -o 参数选择输出格式
    • **kubectl api-resources**:列出集群中支持的所有 API 资源,帮助了解 Kubernetes 中可用的资源
    • **kubectl explain node**:查看 Node 资源的详细字段说明,帮助理解各字段的作用
  • kubectl run,运行容器,NAME--image 是必需项,分别代表此次部署的名字及所使用的镜像

    • 实际使用时,推荐编写配置文件并通过 kubectl create 进行部署
  • kubectl get all,列出当前命名空间中核心资源类型,比如 Pod、Service、ReplicaSet、Deployment、DaemonSet 等

部署一个 Redis 实例

1
2
➜  ~ kubectl run redis --image='redis:alpine'
deployment.apps/redis created
1
2
3
4
5
6
7
8
9
➜  ~ kubectl get all
NAME READY STATUS RESTARTS AGE
pod/redis-7c7545cbcb-2m6rp 1/1 Running 0 30s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 32s
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
deployment.apps/redis 1 1 1 1 30s
NAME DESIRED CURRENT READY AGE
replicaset.apps/redis-7c7545cbcb 1 1 1 30s

使用 kubectl get all 输出内容的格式 / 前代表类型,/ 后是名称

  • Deployment 是一种高级别的抽象,允许进行扩容,滚动更新及降级等操作

    • 利用 Deployment 也能很方便的进行金丝雀发布(Canary deployments),这主要也依赖 LabelSelector

    • Deployment 的创建更推荐的方式便是使用 yaml 格式的配置文件

    • Deployment 主要是声明一种预期的状态,并且会将 Pod 托管给 ReplicaSet

    • ReplicaSet 会检查当前的 Pod 数量及状态是否符合预期,并尽量满足这一预期

  • Service 是为了能有个稳定的入口访问应用服务或者是一组 Pod

    • 通过 Service 可以很方便的实现 服务发现和负载均衡
    • Service 目前有 4 种类型:
      • ClusterIP: 是 K8s 当前默认的 Service 类型,将 service 暴露于一个 仅集群内 可访问的虚拟 IP 上
        • 集群内主机 通过 ClusterIP:port 访问服务
        • 集群内容器 通过 service name:port 访问服务
      • NodePort: 是通过在集群内所有 Node 上都绑定固定端口的方式将服务暴露出来
        • 集群外主机 通过 <NodeIP>:<NodePort> 访问服务
      • LoadBalancer: 是通过 Cloud Provider 创建一个外部的负载均衡器,将服务暴露出来,并且会自动创建外部负载均衡器路由请求所需的 NodeportClusterIP
      • ExternalName: 是通过将服务由 DNS CNAME 的方式转发到指定的域名上将服务暴露出来
1
2
3
4
5
6
➜  ~ kubectl expose deploy/redis --port=6379 --protocol=TCP --target-port=6379 --name=redis-server  
service/redis-server exposed
➜ ~ kubectl get svc -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 49m <none>
redis-server ClusterIP 10.108.105.63 <none> 6379/TCP 4s run=redis

通过 kubectl expose 命令将 redis server 这个 Service 暴露出来

  • port: 是 Service 暴露出来的端口,可通过此端口访问 Service
  • protocol: 是所用协议,当前 K8s 支持 TCP/UDP 协议,默认是 TCP 协议
  • target-port: 是实际服务所在的目标端口,请求由 port 进入通过上述指定 protocol 最终流向这里配置的端口
  • nameService 的名字,它的用处主要在 dns 方面
  • type: 是前面提到的类型,如果没指定默认是 ClusterIP

redis 是使用的默认类型 ClusterIP,所以并不能直接通过外部进行访,使用 port-forward 的方式让它可在集群外部访问

1
2
3
4
➜  ~ kubectl port-forward svc/redis-server 6379:6379
Forwarding from 127.0.0.1:6379 -> 6379
Forwarding from [::1]:6379 -> 6379
Handling connection for 6379

也可以使用 NodePort 的方式对外暴露服务,可以通过任意 Node 上的 NodePort 端口连接 redis 服务,这个端口范围其实是可以通过 kube-apiserverservice-node-port-range 进行配置的,默认是 30000-32767

1
2
3
4
5
➜  ~ kubectl expose deploy/redis --port=6379 --protocol=TCP --target-port=6379 --name=redis-server-nodeport --type=NodePort
service/redis-server-nodeport exposed
➜ ~ kubectl get service/redis-server-nodeport -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
redis-server-nodeport NodePort 10.109.248.204 <none> 6379:31913/TCP 11s run=redis

认证和授权

K8s 中几乎所有的操作都需要经过 kube-apiserver 处理,所以为了安全起见,K8s 为它提供了三类安全访问的措施:

  • 用于识别用户身份的 认证(Authentication)
  • 用于控制用户对资源访问的 授权(Authorization)
  • 用于资源管理方面的 准入控制(Admission Control)

来自客户端的请求分别经过认证,授权,准入控制之后,才能真正执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
+-----------------------------------------------------------------------------------------------------------+
| |
| +---------------------------------------------------------------------------+ +--------+ |
| | | | | |
| +--------+ | +------------------+ +----------------+ +--------------+ +------+ | | | |
| | | | | | | | | Admission | | | | | | |
| | Client +------> | Authentication +-> | Authorization +-> | Control +-> |Logic | +--> | Others | |
| | | | | | | | | | | | | | | |
| +--------+ | +------------------+ +----------------+ +--------------+ +------+ | | | |
| | | | | |
| | | | | |
| | Kube-apiserver | | | |
| +---------------------------------------------------------------------------+ +--------+ |
| |
+-----------------------------------------------------------------------------------------------------------+

认证(Authentication)

认证是判断当前发起请求的用户身份是否正确。例如,通常登录服务器时候需要输入用户名和密码,或者 SSH Keys 之类的。K8S 支持以下认证机制,可选择同时开启多个认证机制:

  • X509 客户端证书
  • 引导 Token
  • 静态 Token 文件
  • 静态密码文件
  • Service Account Token
  • OpenID
  • 认证代理
  • Webhook

授权(Authorization)

授权就是在验证当前发起请求的用户是否有相关的权限。例如,在 Linux 系统中常见的文件夹权限之类的。授权是以认证的结果为基础的,授权机制检查用户通过认证后的请求中所包含的属性来进行判断。K8S 支持以下授权机制:

  • ABAC(Attribute-Based Access Control)基于属性的访问控制
  • RBAC(Role-based access control)基于角色的访问控制
  • Node:这是一种特殊用途的授权机制,专门用于对 kubelet 发出的 API 请求做授权验证
  • Webhook:使用外部的 Server 通过 API 进行授权校验
  • AlwaysAllow:默认配置,允许全部
  • AlwaysDeny:通常用于测试,禁止全部

Helm

Helm 是构建于 K8S 之上的包管理器,可与平时接触到的 Yum,APT,Homebrew 或者 Pip 等包管理器相类比。使用 Helm 可简化包分发,安装,版本管理等操作流程。同时它也是 CNCF 孵化项目。

Helm 是 C/S 架构,主要分为客户端 helm 和服务端 Tillerhelm 通过 gRPCchart 发送至 TillerTiller 则通过内置的 kubernetes 客户端库与 K8S 的 API server 进行交流,将 chart 进行部署,并生成 Release 用于管理。

原理解析

kube-apiserver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+----------------------------------------------------------+          
| Master |
| +-------------------------+ |
| +------->| API Server |<--------+ |
| | | | | |
| v +-------------------------+ v |
| +----------------+ ^ +--------------------+ |
| | | | | | |
| | Scheduler | | | Controller Manager | |
| | | | | | |
| +----------------+ v +--------------------+ |
| +------------------------------------------------------+ |
| | | |
| | Cluster state store | |
| | | |
| +------------------------------------------------------+ |
+----------------------------------------------------------+

kube-apiserver 作为集群的统一入口,接收来自外部的信号和请求,并将一些信息存储至 etcd 中

  • REST API Server
    • 对外提供接口,可处理来自客户端(无论 kubeclt 或者 curl 或者其他语言实现的客户端)的请求,并作出响应
    • kube-apiserver 有个 --secure-port 的参数,通过这个参数来配置它将要监听在哪个端口,默认情况下是 6443
  • 认证(Authentication)
    • kubectl version -v 8
      • 获取集群版本号的时候,其实也是向 kube-apiserver 发送了一个请求进行查询的,可以通过传递 -v 参数来改变 log level
      • 首先会加载 $HOME/.kube/config 下的配置,获的集群地址,进而请求 /version 接口,最后格式化输出
    • curl -k https://172.17.0.99:6443/version
      • 使用 curl -k 相当于忽略认证的过程,忽略掉认证过程的 curl 被判定为 system:anonymous 用户
  • 授权(Authorization)
    • K8S 支持多种授权机制,现在多数都在使用 RBAC
  • 准入控制(Admission Control)
    • 在请求进来时,会先经过认证、授权接下来会进入准入控制环节
    • 准入控制和前两项内容不同,它不只是关注用户和行为,它还会处理请求的内容,不过它对读操作无效
    • 准入控制与认证、授权插件类似,支持同时开启多个,几个比较常见的插件:
      • NamespaceLifecycle:它可以保证正在终止的 Namespace 不允许创建对象,不允许请求不存在的 Namespace 以及保证默认的 default, kube-system 之类的命名空间不被删除
      • LimitRanger:为 Pod 设置默认请求资源的限制
      • ServiceAccount:可按照预设规则创建 Serviceaccount,比如都有统一的前缀:system:serviceaccount:
      • DefaultStorageClass:为 PVC 设置默认 StorageClass
      • DefaultTolerationSeconds:设置 Pod 的默认 forgiveness toleration 为 5 分钟
      • MutatingAdmissionWebhook 和 ValidatingAdmissionWebhook:这两个都是通过 Webhook 验证或者修改请求,唯一的区别是一个是顺序进行,一个是并行进行的
      • ResourceQuota:限制 Pod 请求配额
      • AlwaysPullImages:总是拉取镜像
      • AlwaysAdmit:总是接受所有请求
  • 处理请求
    • 一个请求依次会经过认证,授权,准入控制等环节,当这些环节都已经通过后,该请求便到了 kube-apiserver 的实际处理逻辑中了
    • 和普通的 Web server 类似,kube-apiserver 提供了 restful 的接口,增删改查等基本功能都基本类似
  • kube-apiserver 包含的东西有很多,除了认证,授权,准入控制相关功能外,还有审计,证书,存储等配置

etcd

etcd is a consistent distributed key-value store. Mainly used as a separate coordination service, in distributed systems. And designed to hold small amounts of data that can fit entirely in memory.

etcd 是由 CoreOS 团队发起的一个分布式,强一致的键值存储。它用 Go 语言编写,使用 Raft 协议作为一致性算法。多数情况下会用于分布式系统中的服务注册发现,或是用于存储系统的关键数据。etcd 在 K8S 中,最主要的作用便是其 高可用强一致 的键值存储以及 监听机制

  • kube-apiserver 收到对应请求经过一系列的处理后,最终如果是集群所需要存储的数据,便会存储至 etcd 中,主部分主要是 集群状态信息元信息
  • 默认集群中的 etcd 会放到 kube-system Namespace 中,kubectl -n kube-system get pods | grep etcd
  • etcd2 使用 HTTP/JSON 作为默认的 API 通信协议,允许客户端通过 HTTP API 与 etcd 集群进行交互
  • etcd3 引入了 gRPC 作为主要的通信协议,虽然 etcd3 仍支持 HTTP API,但推荐使用 gRPC 进行高效通信,因为 gRPC 比传统的 HTTP API 更高效、更加支持双向流和更好的负载均衡
  • etcd3 中,HTTP API 一般仅用于兼容性和简单操作,在 K8s 1.13 发布时,etcd 2 的相关代码已经移除
  • 由于 etcd 集群使用 Raft 一致性算法,通常情况下 etcd 集群需要部署奇数个节点,如 3,5,7 等,etcd 集群维护也相对容易,很容易可以做成高可用集群

controller-manager

Controller Manager 实际由 kube-controller-manager 和 cloud-controller-manager 两部分组成,cloud-controller-manager 则是为各家云厂商提供了一个抽象的封装,便于让各厂商使用各自的 provide

kube-controller-manager 是一个 嵌入 了 K8s 核心 控制循环守护进程,负责在集群中运行核心控制循环,以确保集群的状态达到并维持在用户期望的状态

  • kube-controller-manager 运行多个控制器(例如节点控制器、资源配额控制器等),每个控制器都嵌入了特定的逻辑来管理和协调集群资源的状态

  • 控制kube-controller-manager 的任务是维护集群的目标状态。例如,当资源发生变化时,它会通过调用 API Server 将实际状态调整为目标状态

  • 循环:每个控制器都在一个循环中运行,反复检查资源的状态,并采取必要的操作以确保资源达到预期的状态。循环的执行间隔是可以通过参数配置的,例如 --sync-period 参数,控制各控制器的检查频率

  • kube-controller-manager 是独立部署的一个 守护进程,可以在 Kubernetes 集群的控制平面节点上以独立的进程或容器形式运行, 持续监视并管理资源的状态。守护进程的特点是持久运行,不间断地执行控制循环,确保集群资源的一致性和健康状态

  • kube-controller-manager10252 端口上不仅暴露出来了一个 /healthz 接口,还暴露出了一个 /metrics 的接口,可用于进行监控之类的

通过 kubectl -n kube-system describe pods -l component=kube-controller-manager 命令可以查看 kube-controller-manager 的 Pod 详细信息

kube-scheduler

The Kubernetes scheduler is a policy-rich, topology-aware, workload-specific function that significantly impacts availability, performance, and capacity.

kube-scheduler 是一个策略丰富,拓扑感知的调度程序,会显著影响可用性,性能和容量。

资源调度本就是 K8s 这类系统中的一个很复杂的事情,既要能满足系统对资源利用率的需要,同样还需要避免资源竞争,比如说端口冲突之类的,为了能完成这样的需求,kube-scheduler 便在不断的迭代和发展,通过支持多种策略满足各类需求,通过感知拓扑避免资源竞争和保障系统的可用性及容量等。

从上层的角度来看,kube-scheduler 的作用就是将待调度的 Pod 调度至最佳的 Node 上,而这个过程中则需要根据不同的策略,考虑到 Node 的资源使用情况,比如端口,内存,存储等。

kube-scheduler 将处理阶段主要分为三个阶段 Computing predicatesPrioritizingSelecting host

  • Computing predicates:主要解决的问题是 Pod 能否调度到集群的 Node
    • 过一个名为 podFitsOnNode 的函数进行实现,在检查的过程中也会先去检查下是否已经有已缓存的判断结果
    • 当然也会检查 Pod 是否是可调度的,以防有 Pod Affinity (亲合性) 之类的存在
  • Prioritizing:主要解决的问题是在上个阶段通过 findNodesThatFit 得到了 filteredNodes 的基础之上解决哪些 Node 是最优的,得到一个优先级列表 priorityList
    • 给每个经过第一步筛选出来的 Node 一个 Score,再按照各种条件进行打分,最终得到一个优先级列表
  • Selecting host:则是最终选择 Node 调度到哪台机器上

当实际进行部署操作的时候:

  1. 通过 kubectl 之类的客户端工具与 kube-apiserver 进行交互
  2. 在经过一系列的处理后,数据将持久化到 etcd
  3. kube-controller-manager 通过持续的观察,开始按照配置,将集群的状态调整至预期状态
  4. kube-scheduler 也在发挥作用,决定 Pod 应该调度至哪个或者哪些 Node
  5. 之后则通过其他组件的协作,最总将该 Pod 在相应的 Node 上部署启动

kubelet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
+--------------------------------------------------------+       
| +---------------------+ +---------------------+ |
| | kubelet | | kube-proxy | |
| | | | | |
| +---------------------+ +---------------------+ |
| +----------------------------------------------------+ |
| | Container Runtime (Docker) | |
| | +---------------------+ +---------------------+ | |
| | |Pod | |Pod | | |
| | | +-----+ +-----+ | |+-----++-----++-----+| | |
| | | |C1 | |C2 | | ||C1 ||C2 ||C3 || | |
| | | | | | | | || || || || | |
| | | +-----+ +-----+ | |+-----++-----++-----+| | |
| | +---------------------+ +---------------------+ | |
| +----------------------------------------------------+ |
+--------------------------------------------------------+

按照一般架构设计上的习惯,kubelet 所承担的角色一般会被叫做 agent,这里叫做 kubelet 很大程度上受 Borg 的命名影响,Borg 里面也有一个 Borglet 的组件存在,kubelet 便是 K8s 中的 agent,负责 NodePod 相关的管理任务

kubelet 的作用

  • 节点管理
    • 当执行 kubelet --help 的时候,会看到它所支持的可配置参数,其中有一个 --register-node 参数便是用于控制是否向 kube-apiserver 注册节点 的,默认是开启的
    • kubelet 不仅将自己注册给了 kube-apiserver,同时它所在机器的信息也都进行了上报,包括 CPU,内存,IP 信息等
    • kube-apiserver 发送心跳包等
  • Pod 管理
    • kube-scheduler 处理了 Pod 应该调度至哪个 Node,而 kubelet 则是保障该 Pod 能按照预期,在对应 Node 上启动并保持工作( kubelet 的作用之一就是负责镜像拉取)

    • kubelet 在保障 Pod 能按预期工作,主要是做了两方面的事情

      • 健康检查:通过 LivenessProbeReadinessProbe 探针进行检查,判断是否健康及是否已经准备好接受请求
      • 资源监控:通过 cAdvisor 进行资源监控
    • kubelet 还承担着清理 Node 上一些由 K8s 调度 Pod 所造成的磁盘占用之类的工作

kube-proxy

kube-proxy 是 K8s 运行于每个 Node 上的 网络代理组件,提供了 TCP 和 UDP 的连接转发支持,当 Pod 在创建和销毁的过程中,IP 可能会发生变化,而这就容易造成对其有依赖的服务的异常,所以通常情况下,我们都会使用 Service 将后端 Pod 暴露出来,而 Service 则较为稳定

kube-proxy 对于服务注册发现和代理访问等起到了很大的作用,在 Linux 系统上当前支持三种模式,可通过 --proxy-mode 配置:

  • userspace:这是很早期的一种方案,但效率上显著不足,不推荐使用
  • iptables:当前的默认模式,比 userspace 要快,但问题是会给机器上产生很多 iptables 规则
  • ipvs:为了解决 iptables 的性能问题而引入,采用增量的方式进行更新

默认情况下使用 iptables 的代理模式,当创建新的 Service ,或者 Pod 进行变化时,kube-proxy 便会去维护 iptables 规则,以确保请求可以正确的到达后端服务、

kube-proxyIngress 都是 Kubernetes 中用于网络管理的组件,两者都可以实现流量的负载均衡和分发,kube-proxy 更底层、面向内部流量,而 Ingress 更高层、面向外部流量,比如负载均衡层级:

  • kube-proxy:在四层(传输层)工作,只支持基于 IP 的负载均衡
  • Ingress:工作在七层(应用层),支持基于 HTTP/HTTPS 的请求路由

Container Runtime

kube-scheduler 决定了 Pod 将被调度到哪个 Node 上,而 kubelet 则负责 Pod 在此 Node 上可按预期工作,如果没有 Container Runtime,那 Pod 中的 container 在该 Node 上也便无法正常启动运行

Container Runtime (容器运行时)这一概念的产生也是由于容器化技术和 K8s 的大力发展,为了统一工业标准,也为了避免 K8s 绑定于特定的容器运行时,所以便成立了 OCI (Open Container Initiative)组织,致力于将容器运行时标准化和容器镜像标准化

自 K8s 1.5 (2016 年 11 月)开始,新增了一个容器运行时的插件 API,并称之为 CRI (Container Runtime Interface),通过 CRI 可以支持 kubelet 使用不同的容器运行时,而不需要重新编译

当前使用最为广泛的是 Docker,当前还支持的主要有 runcContainerdrunV 以及 rkt

Troubleshoot

  • 使用 describe 排查问题,kubectl -n work describe pod/xxx
  • 使用 events 排查问题,kubectl -n work get events
  • 通过详细内容排查错误,比如 kubectl -n work get endpoints

扩展增强

  • Dashboard - Web 端操作界面

    • Dashboard 并不能完全取代 kubectl,两者应该是相辅相成的
    • 后端使用 Kubernetes 的 client-go 库来与 API Server 通信,前端基于 Angular 框架开发
  • CoreDNS - K8s 集群中的 DNS 和服务发现插件

    • CoreDNS 是一个独立项目,它不仅可支持在 K8s 中使用,也可以在任何需要 DNS 服务的时候使用它
    • 自 K8s1.13 版本起,CoreDNS 成为了集群中的默认 DNS 服务器,替代了以前的 kube-dns 插件
    • 提供域名解析和服务发现功能,使 Pod 之间能通过服务名称直接访问
  • Ingress - K8s 中的流量管理资源

    • Ingress 是 Kubernetes 中的流量管理资源,主要用于集群外部的 HTTP/HTTPS 负载均衡、路由和SSL 终止等
    • 允许定义基于路径、主机名等的路由规则,引导流量到集群内的正确服务,需要一个 Ingress Controller 实现,常用的有 NGINX Ingress Controller 和 Traefik 等
  • 集群监控,K8s 是一个典型的分布式系统,组件很多,监控的目标就变的很重要了

    • 节点情况、K8s 集群自身状态、部署在 K8s 内的应用的状态
    • 用于实时收集和监测 Kubernetes 集群的性能和资源使用情况,帮助管理员发现潜在问题和优化集群资源
    • rometheus、Grafana 等工具常用于 Kubernetes 的集群监控,帮助展示 CPU、内存、存储等资源的使用情况,还能设置报警和警报管理

部署前后端应用

使用云平台的容器服务

Code → App → Docker Image → Hub → K8s

Golang

构建 Docker 镜像

交叉编译

1
2
3
4
5
6
7
# Windows → Linux
# powershell
$env:GOOS="linux"
$env:GOARCH="amd64"
go build -o .\build\webook
# Mac → Linux
GOOS=linux GOARCH=amd64 go build -o /build/webook

编写 Dockerfile

1
2
3
4
5
6
7
8
# 基础镜像
FROM ubuntu:20.04
# 把编译后的打包进这个镜像,放到工作目录 /app
COPY /build/wechatpay /app/wechatpay
# 拷贝配置文件
COPY ./etc /app/etc
WORKDIR /app
ENTRYPOINT ["/app/webook"]

打包镜像

1
2
3
4
5
6
7
# 构建
docker build -t wechatpay:v0.0.1 .
# 查看镜像信息
docker images wechatpay:v0.0.1
# 删除
docker rmi -f wechatpay:v0.0.1
# 可以将上述命令都写在 Makefile 里面

启动服务

1
2
docker run --rm -it -p 8888:8888 wechatpay:v0.0.1      
Starting server at 0.0.0.0:8888...

推送到远程仓库

1
2
3
4
5
# 登录腾讯云容器镜像服务 Docker Registry
docker login xxx.tencentcloudcr.com --username xxx --password xxx
# 向 Registry 中推送镜像
docker tag [imageId] xxx.tencentcloudcr.com/xxx/xxx:[tag]
docker push xxx.tencentcloudcr.com/xxx/xxx:[tag]

CI/CD

Jenkinsfile

  1. Check Workspace
  2. Go Mod Tidy
  3. Build Go Application
  4. Build Docker Image
  5. Push Docker Image

React

Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 使用较新的 Node.js 镜像作为构建环境
FROM node:18-alpine AS build
# 设置工作目录
WORKDIR /app
# 复制 package.json 和 package-lock.json
COPY package*.json ./
# 安装依赖
RUN npm install && npm cache clean --force
# 复制项目文件
COPY . .
# 构建项目
RUN npm run build
# 使用 Nginx 作为生产环境的 web 服务器
FROM nginx:alpine
# 复制构建的文件到 Nginx 的默认公共目录
COPY --from=build /app/build /usr/share/nginx/html
# 将 Nginx 配置文件替换为自定义配置文件
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 暴露端口
EXPOSE 9999
# 启动 Nginx
CMD ["nginx", "-g", "daemon off;"]

相关资料