Kubernetes 入门

New to Kubernetes

Posted by PYQ on April 2, 2024

Kubernetes官方提供了十分详细且完整的教程,完整的去阅读学习下来需要耗费大量的时间。本篇博客旨在帮助阅读者可以快速入门和上手使用Kubernetes,只对基本的内容进行记录(不包括使用部分,都放一篇内容太多了x),更多细节可以翻阅官方手册对应部分。

参考的视频教程:Kubernetes一小时轻松入门——GeekHour

Kubernetes简介

在Kubernetes(太长了,后续都简称k8s)出现之前,⼀般都是使⽤Docker来管理容器化的应⽤,但是Docker只是⼀个单机的容器管理⼯具,它只能管理单个节点上的容器,当我们的应⽤程序需要运⾏在多个节点上的时候,就需要使⽤⼀些其他的⼯具来管理这些节点,⽐如Docker Swarm、Mesos、Kubernetes等等,这些⼯具都是容器编排引擎,可以⽤来管理多个节点上的容器。Docker Swarm是Docker官⽅提供的⼀个容器编排引擎,功能⽐较简单,适合于⼀些⼩型的、简单的场景;Mesos和Kubernetes则是更加复杂的容器编排引擎,Mesos是Apache基⾦会的⼀个开源项⽬;⽽Kubernetes是Google在2014年开源的,⽬前已经成为了CNCF(Cloud Native Computing Foundation)的⼀个顶级项⽬,基本上已经成为了容器编排引擎的行业标准了。

K8s的特性:

  • 编排容器:通过配置文件来定义应用的部署方式,使得容器创建、维护和管理更加简单和高效。
  • 高可用:提供自动重启、自动重建和自我修复等,提高集群可用性。
  • 可扩展性:系统根据负载变化来动态扩展或缩减系统资源。
  • 灾难恢复
  • 弹性伸缩
  • ……

K8s组件

Node

一个Node可以是一个物理机或虚拟机,在Node上可以运行多个Pod。

Pod

Pod是K8s的最小调度单元,是一个或多个容器的组合。在Pod中,容器可以共享一些资源:网络、存储和一些运行时的配置等。一般来说一个Pod建议只运行一个容器,实现应用的解耦和扩展。如果一个容器要运行多个容器,最好是高度耦合的,也就是这些容器为了实现某种功能或共享资源不得不放置在一个Pod中,例如Sidecar(边车)模式。在这种模式下,主应用的旁边会附加一个或多个辅助容器,这些辅助容器扩展或增强主应用的功能,而不需要改变主应用本身。这些辅助容器被称为 “Sidecar” 容器,它们与主应用容器共享相同的生命周期(即同时创建和销毁)、网络空间(可以使用 localhost 进行通信)和存储卷。Sidecar的常见用途包括日志记录、监控、网络服务和安全服务等。

Pod创建时会被自动分配IP地址,用于Pod之间的通信,这个IP地址是集群内部的IP地址,集群外部访问不到。但K8s中的Pod十分不稳定,当Pod被销毁和重建时,Pod的IP地址也会发生变化。为了解决这个问题,K8s提供了Service。

Service

Service可以将一组Pod封装成一个服务,这个服务可以通过一个统一的入口来访问(相较于Pod,service更加稳定,IP地址不易变化)。例如当我们有两个Pod时,一个Pod是一个应用程序,另外一个Pod是数据库,应用程序需要访问数据库获取数据。通过将数据库封装成一个服务,应用程序通过服务来访问service服务的IP地址来访问数据库,service会将请求转发到健康的Pod中。

Service的主要特点包括:

  • 稳定的IP地址:每个Service都有一个固定的IP地址(ClusterIP),即使后端Pods发生变化,这个地址也不会改变。
  • 负载均衡:Service可以自动地将流量分发到后端的多个Pods上,实现负载均衡。
  • 服务发现:通过Kubernetes的DNS服务,可以使用Service的名称来解析到它的IP地址,从而实现服务的自动发现。
  • 抽象化:Service为Pods提供了一个抽象层,使得客户端不需要关心后端Pods的具体情况,只需要通过Service就可以访问到所需的服务。

Service可以根据它们的访问范围和目的被分为内部服务和外部服务。这种分类主要取决于服务是否需要从集群外部被访问,以及访问服务的客户端所在的位置。

内部服务是指仅在K8s集群内部可访问的服务。这些服务通常用于集群内部的组件之间的通信,例如,一个前端应用程序访问后端API服务,或者不同的微服务之间的互相调用。内部服务通常使用以下两种类型之一:

  • ClusterIP:默认的Service类型。它为服务分配一个集群内部的IP地址,使得服务只能从集群内部访问。这个IP地址对集群外部是不可见的。

  • Headless Service:当你不需要负载均衡和单一的服务IP,而是希望直接访问Pods 时,可以使用无头(Headless)服务。在这种情况下,服务的DNS记录会直接返回后端Pods的IP地址列表,而不是单一的服务IP。

外部服务是指需要从K8s集群外部访问的服务。这些服务可以是公开给最终用户的Web应用程序,或者是需要与外部系统交互的服务。外部服务通常使用以下两种类型之一:

  • NodePort:这种类型的服务在集群的每个Node上打开一个特定的端口,然后将外部请求路由到正确的Pods。这允许从集群外部通过:的形式访问服务。

  • LoadBalancer:这种类型的服务为K8s服务提供了一个外部负载均衡器(通常由云服务提供商支持),它将外部请求路由到集群内的 Pods。这是在云环境中公开服务的最直接方式。

相较于:,我们访问集群服务通常是使用域名,这就需要用到Ingress。

Ingress

Ingress管理外部访问到集群服务的HTTP和HTTPS请求。Ingress可以提供负载均衡、SSL终端和基于名称的虚拟托管。Ingress允许通过定义规则来将外部请求路由到集群内的不同服务。与Service不同,Ingress操作在OSI模型的第七层(应用层),这意味着它能够处理更复杂的流量路由规则,例如基于请求路径或主机名来路由流量。例如,你可以配置Ingress规则将http://example.com/api的请求路由到一个服务,而将http://example.com/web的请求路由到另一个服务。

通常情况下,Service和Ingress一起工作以提供对集群内部服务的完整访问控制。这种模式下,Service负责在集群内部提供稳定的访问点给Pods,而Ingress负责管理从外部到这些Service的访问。

一个典型的工作流程如下:

  • Service定义了如何访问一组特定的Pods,为这些Pods提供了一个内部访问点。
  • Ingress定义了从外部到这些Service的访问规则,包括如何处理不同的域名和请求路径。

ConfigMap

回到前面应用程序和数据库的例子,当应用程序需要访问数据库时,需要url、username和password等信息。通常我们会将这些信息写入配置文件或环境变量中,应用程序再读取这些信息来实现对数据库的访问。但这样使得配置信息和应用程序耦合在一起,一旦这些信息发生变化,就需要重新编译应用程序和部署。为了解决这个问题,K8s提供了ConfigMap。ConfigMap是一种用于存储非机密性配置数据的API对象,允许将配置信息与容器镜像分离,从而增加容器应用的可移植性和灵活性。ConfigMap可以用来存储细粒度信息(如单个属性或配置文件),也可以用来存储粗粒度信息(如整个配置文件或JSON blobs)。

ConfigMap的使用场景

  • 设置环境变量:通过ConfigMap来设置应用程序运行时需要的环境变量。
  • 配置命令行参数:将命令行参数存储在ConfigMap中,然后通过配置引用这些参数。
  • 配置文件:将应用程序的配置文件存储在ConfigMap中,并通过卷挂载到Pod中,让应用程序读取这些配置。
  • 运行时配置:为应用提供运行时需要的各种配置信息。

当然configmap的功能也可以通过直接将信息保存在Node上的一个文件,再通过卷挂载到容器的对应目录进行访问。但使用configmap更加灵活高效(k8s的特性)。

但ConfigMap中存储的数据应该是非机密的配置信息。对于敏感信息,应该使用Secret来存储。

Secret

Secret是一种用来存储和管理敏感信息的对象,如密码、OAuth令牌、SSH密钥等。与 ConfigMap类似,Secret也允许将应用配置信息与容器镜像分离,但它专门用于处理敏感数据,提供了更好的安全性。

但Secret也仅通过一层base64编码,而非数据加密(存储在etcd中的secret是加密的),因此如果需要对这些内容进行保护,还需要结合K8s提供的安全功能,比如RBAC和认证等。

Volumes

容器本身的文件系统是临时的,这意味着当容器被重启或销毁时(这里的重启并非docker里面的stop再start),存储在容器文件系统内的数据会丢失。在K8s中,可以通过卷(Volumes)来持久化数据。卷可以被挂载到容器内的特定路径上,容器内的应用可以像访问本地文件系统一样访问卷中的数据。由于卷是独立于容器生命周期的,因此即使容器重启,卷中的数据也不会丢失。

Deployment

在集群中,当某个Pod需要更新或者重启时,我们的服务就会被暂时停掉,这对于需要7*24运转的服务来说是不可接受的。K8s提供的办法也很简单,那就是多复制几个相同的Pod到不同的Nod上,实现高可用,这就需要用到Deployment。

Deployment可以定义和管理应用程序的副本数量以及应用程序的更新策略,可以简化应用程序的部署和更新操作:

  • 声明式更新:你可以声明你的目标应用状态,Deployment控制器会负责实现这一状态。
  • 滚动更新:当应用的配置或镜像被更新时,Deployment可以自动进行滚动更新,逐步替换旧的Pod实例,以最小化应用的停机时间。
  • 自动回滚:如果新的更新有问题,Deployment可以自动回滚到之前的版本,确保应用的稳定性。
  • 扩缩容:可以很容易地通过修改Deployment中的replicas字段来扩大或缩小应用的规模。
  • 版本记录和管理:Deployment会记录每次的更新历史,允许用户回滚到之前的任何版本。

但Deployment通常用于管理无状态的应用,无状态应用是指不需要保持任何状态或者其状态可以通过外部存储(如数据库、缓存服务等)来维护的应用(例如某个后端服务)。这类应用的每个实例都是相互独立的,不需要在实例之间同步状态信息。因此,使用Deployment管理无状态应用时,可以很方便地进行水平扩展、更新和回滚操作,因为每个副本都是等价的,替换过程中不需要考虑状态同步的问题。而对于有状态的应用(如数据库),则推荐使用StatefulSet。

StatefulSet

有状态应用是指需要保持状态信息的应用,这些状态信息可能包括但不限于用户会话、实时数据处理等。这类应用的实例通常需要在重启后保持其身份标识和存储状态,或者需要在多个副本之间进行状态同步。数据库、缓存和消息队列就是典型的有状态应用。

StatefulSet是为了满足这类有状态应用的需求而设计的。与Deployment相比,StatefulSet提供了以下关键特性:

  • 稳定的、唯一的网络标识符:每个StatefulSet中的Pod都有一个基于序号的稳定名称,即使在重新调度时也不会改变。
  • 稳定的、持久的存储:StatefulSet可以使用持久卷保证数据在Pod重新调度后仍然保留。
  • 有序的部署、扩展和删除:StatefulSet中的Pod是按照严格的顺序创建和终止的,这对于需要顺序启动的应用(如分布式数据库)来说非常重要。
  • 有序的滚动更新:StatefulSet支持有序和渐进的应用更新,确保应用的稳定性和可用性。

但并不是所有有状态的应用都使用statefulset,另外一种更加通用和简单的方式是把诸如数据库这种有状态的应用程序从集群中剥离出来,在集群外单独部署。

K8s架构

K8s是经典的master-worker架构,master-node负责管理整个集群(图中的Control Plane),worker-node负责运行具体的应用和服务(图中的Compute Machines)。

Control-plane/Master-node

Master节点(也称为控制平面节点)负责管理集群的整体操作。它是K8s集群的大脑,负责决策(例如调度应用)、集群事件的响应(例如扩缩容操作)以及集群状态的维护(例如跟踪所有K8s对象的信息)。

kube-apiserver

kube-apiserver是K8s API的服务器端实现,是控制平面的前端,是集群内所有组件(例如kubelet、control-manager)和外部用户(使用kubectl cli)与K8s集群交互的唯一入口。我们可以通过REST调用、kubectl命令行界面或其他命令行工具(例如 kubeadm)来访问kube-apiserver来与集群进行交互。

kube-apiserver的主要功能包括:

  • API请求的处理:kube-apiserver处理所有的REST请求,验证它们,并执行业务逻辑。这包括对资源的增、删、改、查(CRUD)操作,以及对集群状态的监控和管理。
  • 数据存储:虽然kube-apiserver不直接存储数据,但它负责将数据持久化到etcd中,etcd是一个分布式键值存储,用于保存所有集群数据,确保集群状态的一致性。
  • 认证和授权:kube-apiserver在处理请求之前,会进行认证(验证请求者的身份)和授权(确定请求者是否有权限执行请求的操作)。
  • API聚合:kube-apiserver支持 API 聚合,允许添加额外的API服务来扩展 Kubernetes API,使得开发者可以添加自定义资源到他们的Kubernetes集群中。
kube-scheduler

kube-scheduler负责将Pods调度到集群中的节点上。它根据多种调度策略和约束条件(例如node的cpu和内存状态等)对节点进行打分,决定将Pod放置在哪个节点上运行(分数最高那个),以确保集群资源的有效利用,并满足各种服务质量(QoS)要求。

kube-scheduler的主要功能包括:

  • Pod调度:当用户创建或者系统生成新的Pod时,kube-scheduler会选择一个最合适的节点,以便启动和运行该Pod。
  • 负载均衡:kube-scheduler试图通过合理分配Pod到各个节点,保持整个集群的负载均衡,优化资源利用率和应用性能。
  • 满足约束条件:在选择节点时,kube-scheduler考虑多种约束条件,包括但不限于硬性要求(如CPU、内存资源),以及软性要求(如亲和性/反亲和性规则)。
kube-controller-manager

kube-controller-manager负责运行控制器进程。这些控制器进程负责在系统中执行各种管理任务,如维护副本集、处理节点故障、调整负载均衡等。kube-controller-manager封装了这些控制器进程,使得它们能够共享API服务,从而减少资源开销,并简化了管理操作。

kube-controller-manager的主要功能(或者主要的控制器)包括:

  • 节点控制器(Node Controller):负责在节点出现故障时做出响应,例如,当节点无响应时,该控制器负责注意节点的状态并在必要时采取措施。
  • 副本控制器(Replication Controller):确保系统中的副本集(ReplicaSet)中的Pod副本数量始终符合用户定义的数量。如果副本数量过少或过多,副本控制器将相应地启动或终止Pod。
  • 端点控制器(Endpoints Controller):填充Endpoints对象(即加入Service和Pod)。
  • 服务账户和令牌控制器(Service Account & Token Controllers):为新的命名空间创建默认账户和API访问令牌。

kube-controller-manager通过K8s API周期性监听集群的状态,并在需要时进行调整。

etcd

etcd是一个开源的、分布式的键值存储系统,用于可靠地存储关键数据,并在分布式系统中实现强一致性。K8s使用etcd来存储所有集群的数据,包括但不限于:

  • Pod的状态
  • Replication Controllers的信息
  • 服务(service)信息
  • 网络配置
  • Secrets

etcd的特点:

  • 可靠性:etcd是为分布式系统设计的,可以在多个节点之间复制数据,确保容错性和高可用性。
  • 一致性:它基于Raft共识算法来保证数据的强一致性,即集群中的所有成员在同一时间点看到的数据是一致的。
  • 简单性:提供了一个简单的HTTP/JSON API,使得存储和检索键值对变得简单。
  • 安全性:支持传输层安全(TLS)以加密客户端和服务器之间的通信,并提供身份验证机制。

由于etcd存储了整个集群的状态,因此它可以用于备份和灾难恢复。

cloud controller manager

如果使用的是云服务商提供的k8s集群,还有个cloud controller manager组件(云控制器管理器,简称CCM)。CCM用于抽象出云服务商提供的底层云服务和K8s集群之间的交互。它使得K8s不仅能够在任何云平台上运行,还能够利用云平台提供的特定功能,如负载均衡、存储卷等。

Worker-node

Worker节点负责运行应用容器,是实际承载工作负载的节点。每个Worker节点都由Master节点进行管理,以确保分配给它的容器正常运行。

kubelet

kubelet运行在集群中的每个节点上,主要职责是维护和管理容器的生命周期,确保容器的状态与Pod中定义的期望状态相匹配。简而言之,Kubelet是节点级别的容器编排器(实际是个守护进程),它接受来自控制平面的指令(与kube-apiserver通信),然后在其所在的节点上启动、停止和维护容器。

kubelet的功能:

  • Pod的生命周期管理:Kubelet负责实现Pod的生命周期,包括创建、启动、监控、重启、停止和删除容器。它根据从API服务器接收的PodSpec(Pod规范)来管理容器。
  • 健康检查:Kubelet定期执行容器的健康检查,包括存活性(Liveness)探针和就绪性(Readiness)探针。如果检查失败,Kubelet可以重启容器,帮助保持应用的健康和可用性。
  • 资源管理:Kubelet负责管理节点上的资源,包括CPU、内存、存储和网络资源。它确保每个容器根据PodSpec中定义的资源限制和请求来使用资源。
  • 日志和监控:Kubelet收集容器的日志,并支持容器的监控。
  • 节点状态报告:Kubelet定期向K8s的控制平面报告节点的状态,包括节点上运行的Pod、资源使用情况、系统健康状况等信息。
  • 容器运行时接口(CRI):Kubelet通过容器运行时接口与容器运行时交互,如Docker、containerd等。CRI定义了容器运行时应该实现的操作和功能,使得Kubelet可以与不同的容器运行时兼容。
kube-proxy

kube-proxy是K8s集群中的一个关键组件,负责在集群内部的每个节点上进行网络代理,以允许不同节点上的Pod之间或集群外部与Pod之间的通信。它确保了网络流量能够被正确地转发到这些服务背后的容器。

container-runtime

负责节点中容器的创建和管理,容器运行时提供了一个环境,其中容器可以运行,包括必要的操作系统级别的功能,如进程隔离、网络配置和存储管理。常见的容器运行时包括Docker、containerd和cri-o,Kubelet通过CRI(容器运行时接口)与容器运行时进行交互。CRI是一套 gRPC API,定义了kubelet与容器运行时之间的接口。

其实容器运行时可以分为上层的底层的,上层的诸如上面列举这些,最终创建容器的是底层的容器运行时,比如runc。

其他

此外,k8s集群还需要持久存储、容器镜像仓库(docker hub、harbor)和底层基础架构。

安装K8s

搭建k8s集群可以通过多种工具和方法完成,下面介绍常见的几种工具(我只用kubeadm搭建过,kubeadm也是最接近实际生产环境的)。

kubeadm

kubeadm是K8s官方提供的一个工具,旨在提供一个最佳实践的快速路径来搭建一个只需最小配置即可运行的安全K8s集群。适用于那些希望自己控制的环境中搭建集群,包括物理服务器、云服务器、虚拟机等。

一个不错的安装教程

minikube

Minikube是一个轻量级的K8s实现,它在本地计算机上创建一个虚拟机并在其中部署一个简单的集群,只有一个节点。适合学习和开发用途,当你想在本地机器上快速启动一个K8s集群时。

kind

kind (Kubernetes IN Docker) 是一个用于运行本地K8s集群的工具,它通过在Docker 容器中运行“节点”来工作。它主要用于测试K8s本身,但也适用于本地开发或CI(持续集成)。

k3s

K3s是由Rancher Labs开发的一个轻量级K8s发行版,旨在简化K8s的安装和操作,尤其是在资源受限的环境中,如边缘计算场景、物联网 (IoT)、和小型云环境等。K3s通过移除K8s中不常用的功能和模块,以及替换某些组件来减小其体积和简化安装过程,从而实现了对资源的低要求和易于安装的特点。