kubernetes,简称为k8s(k12345678s)。用于自动部署、扩展和管理“容器化(containerized)应用程序”的开源系统。可以理解成 K8S 是负责自动化运维管理多个容器化程序(比如 Docker)的集群,是一个生态极其丰富的容器编排框架工具。K8S是Google开源的容器集群管理系统,在Docker等容器技术的基础上,为容器化的应用提供部署运行、资源调度、服务发现和动态伸缩等一系列完整功能,提高了大规模容器集群管理的便捷性。
kubernetes的本质是一组服务器集群,它可以在集群的每个节点上运行特定的程序,来对节点中的容器进行管理。目的是实现资源管理的自动化,主要提供了如下的主要功能:
自我修复:一旦某一个容器崩溃,能够在1秒中左右迅速启动新的容器
弹性伸缩:可以根据需要,自动对集群中正在运行的容器数量进行调整
服务发现:服务可以通过自动发现的形式找到它所依赖的服务
负载均衡:如果一个服务起动了多个容器,能够自动实现请求的负载均衡
版本回退:如果发现新发布的程序版本有问题,可以立即回退到原来的版本
存储编排:可以根据容器自身的需求自动创建存储卷
1.应用部署方式演变
在探究k8s之前,我们先来看一下应用部署方式的演变过程,以便我们更容易理解为什么需要K8s。在部署应用程序的方式上,主要经历了三个时代:
传统部署:互联网早期,会直接将应用程序部署在物理机上。优点:简单,不需要其它技术的参与。缺点:不能为应用程序定义资源使用边界,很难合理地分配计算资源,而且程序之间容易产生影响。
虚拟化部署:可以在一台物理机上运行多个虚拟机,每个虚拟机都是独立的一个环境。优点:程序环境不会相互产生影响,提供了一定程度的安全性。缺点:增加了操作系统,浪费了部分资源。
容器化部署:与虚拟化类似,但是共享了操作系统。优点:可以保证每个容器拥有自己的文件系统、CPU、内存、进程空间等,运行应用程序所需要的资源都被容器包装,并和底层基础架构解耦,容器化的应用程序可以跨云服务商、跨Linux操作系统发行版进行部署。
2.k8s集群架构与组件
一个kubernetes集群主要是由控制节点(master)、工作节点(node)构成,每个节点上都会安装不同的组件,如下图:
1.master:集群的控制平面,负责集群的决策 ( 管理 )
1.ApiServer : 资源操作的唯一入口,接收用户输入的命令,提供认证、授权、API注册和发现等机制。用于暴露 Kubernetes API,任何资源请求或调用操作都是通过 kube-apiserver 提供的接口进行。以 HTTP Restful API 提供接口服务,所有对象资源的增删改查和监听操作都交给 API Server 处理后再提交给 Etcd 存储。可以理解成 API Server 是 K8S 的请求入口服务。API Server 负责接收 K8S 所有请求(来自 UI 界面或者 CLI 命令行工具), 然后根据用户的具体请求,去通知其他组件干活。可以说 API Server 是 K8S 集群架构的大脑。
2.Scheduler : Scheduler 是 Kubernetes 集群中的另一个重要组件,主要负责根据集群中各个节点的负载情况,以及用户的调度策略,将新创建的 Pod 分配到合适的节点上。Scheduler 会根据 Pod 的资源需求、节点的资源情况、节点之间的网络距离等因素进行智能调度,从而实现负载均衡和资源最大化利用的目标。 遵循预选策略+优选策略
3.ControllerManager : Controller Manager 是 Kubernetes 集群中的另一个核心组件,它负责监控和维护集群中所有资源对象的状态,以及进行自动化控制和管理操作。Controller Manager 中包含多个控制器,每个控制器负责监控和维护一种资源对象的状态,如 Deployment、ReplicaSet、DaemonSet 等,同时根据用户的需求,自动进行相应的容器调度、扩容、缩容等操作。 在K8S集群中,一个资源{应用}对应一个控制器,而 Controller manager 就是负责管理这些控制器的。
4.Etcd : Etcd 是 Kubernetes 集群中的分布式键值存储系统,用于保存集群中的所有状态信息和元数据。所有与 Kubernetes 集群相关的信息,包括 Pod、Service、Deployment 等对象的创建、更新和删除等操作,都将被记录在 Etcd 中。这样可以使得 Kubernetes 系统具有高可用和复原能力,并且允许多个 Master 节点之间进行数据同步和共享。
2.node:集群的数据平面,负责为容器提供运行环境 ( 干活 )
1.Kubelet : kubelet 是运行在每个 Node 节点上的代理程序,它负责与 Master 节点上的 API Server 进行通信,并根据 Master 节点下发的指令,调度和管理本地节点上的容器。kubelet 可以监控本地节点上的容器状态,如启动、停止、健康状况等,并定期向 Master 节点报告节点状态信息,进而更新到etcd中。
2.KubeProxy : kube-proxy 是 Kubernetes 集群中的网络代理组件,它主要负责实现集群内 Service 资源的负载均衡和访问控制等功能。每个Node节点上都会部署一个 kube-proxy 组件来负责处理该节点上所有 Service 的流量转发和路由等操作。
实时监控API,获取service和pod信息,来保持pod和虚拟IP的映射关系
维护本地netfilter、iptables、IPVS等内核组件,实现数据报文的转发规则
实现每个node节点上虚拟IP的发布和路由维护
构建路由信息,通过转发规则转发报文到虚拟IP对应的pod
3.Docker : 容器引擎,容器运行时环境,负责本机的容器创建和管理工作。当 kubernetes 把 pod 调度到节点上,节点上的 kubelet会指示 docker 启动特定的容器。接着,kubelet 会通过 docker 持续地收集容器的信息{拉取容器镜像、启动或停止容器}, 然后提交到主节点上。 容器运行时的环境除了docker外还有 Containerd 或 CRI-O,Pod 是由一个或多个容器组成的基本部署单元,三者的主要作用就是为了pod中的容器提供环境。
3.k8s创建pod的工作流程
用户通过客户端发送创建Pod的请求到master节点上的apiserver。
apiserver会先把请求信息写入到etcd中保存,再找controller-manager根据预设的资源配置模板解析创建Pod资源,比如按照yml配置信息创建相应控制器。然后将解析的资源信息通过apiservice 写入到etcd中 此时资源状态是pengding
然后apiserver去找scheduler为已解析需要创建的Pod选择最合适的node节点。
scheduler通过apiserver获取存储在etcd中的所有node节点信息,按照预选策略和优选策略的调度算法筛选出最合适的node节点进行调度。比如运行这个Pod需要2C4G的资源,Scheduler会通过预选策略过滤掉不满足策略的Node节点。Node节点中还剩多少资源是通过汇报给API Server存储在etcd里,API Server会调用一个方法找到etcd里所有Node节点的剩余资源,再对比Pod所需要的资源,如果某个Node节点的资源不足或者不满足预选策略的条件则无法通过预选。预选阶段筛选出的节点,在优选阶段会根据优选策略为通过预选的Node节点进行打分排名,选择得分最高的Node。例如,资源越富裕、负载越小的 Node 可能具有越高的排名。
5. 然后再通过apiserver找到对应的node节点上的kubelet去创建和管理Pod。
6. kubelet会跟容器引擎交互来管理Pod/容器的生命周期。
7. 用户还可通过apiserver在kube-proxy上写入网络规则,创建service资源,实现对Pod的服务发现和负载均衡。
以上是k8s的基础组成以及如何创建一个pod,那么我们的应用部署到pod中,是如何被访问到的呢,这里离不开k8s的一些关键资源信息,接下来我们就主要介绍一下service和ingress资源。弄明白了这两种资源后再去梳理外部请求如何访问到具体应用的过程就会比较容易了。
3.Service
在kubernetes中,pod是应用程序的载体,我们可以通过pod的ip来访问应用程序,但是pod的ip地址不是固定的,这也就意味着不方便直接采用pod的ip对服务进行访问。
为了解决这个问题,kubernetes提供了Service资源,Service会对提供同一个服务的多个pod进行聚合(根据标签),并且提供一个统一的入口地址。通过访问Service的入口地址就能访问到后面的pod服务。如下图:
Service在很多情况下只是一个概念,真正起作用的其实是kube-proxy服务进程,每个Node节点上都运行着一个kube-proxy服务进程。当创建Service的时候会通过api-server向etcd写入创建的service的信息,而kube-proxy会基于监听的机制发现这种Service的变动,然后它会将最新的Service信息转换成对应的访问规则,更新到所有的节点iptables或者ipvs中。
1.service类型
由上面的图可以看出,service存在着四种类型,分别是ClusterIP、NodePort、LoadBalancer、ExternalName。再研究这四种类型之前,我们先来看一下service资源定义的配置文件,对service有一个大体的概念,如下图分别为ClusterIP和NodePort类型的service(type标签):
ClusterIP:默认值,它是Kubernetes系统自动分配的虚拟IP,只能在集群内部访问(只能够在mster和node上互相访问 外网不可以),如上图1。ClusterIP通过selector标签关联同名称空间中相同标签的endpoint,同时也是nodePort和loadBalancer的基础。
NodePort:将Service通过指定的Node上的端口暴露给外部,通过此方法,就可以在集群外部访问服务。在所有集群成员节点上暴露相同的nodePort(可用范围30000~32767),用户通过访问任意集群成员节点的nodePort以转发ClusterIP。如上图2。 缺点:假如有4个节点,也就是需要对外提供四个node节点的ip进行访问,用户可能大部分访问第一个节点的ip,后面几个没有被访问,造成第一个ip的压力比较大,所以延伸出了LoadBalancer,增加了一层负载均衡器,用户访问该负载均衡器,由该负载均衡器将流量平均打到四个节点上。 负载均衡器有硬件还有软件之分,如果是硬件比如ngnix,那么我们需要除了k8s集群外还需要再维护一套主从nginx架构,比较麻烦复杂,所以LoadBalancer出现了,它使用的公有云上的api 即软件负载均衡器,无需开发者自己进行维护。
LoadBalancer:Loadbalancer 基于NodePort,如果集群部署在公有云上,调用云供应商 API 自动创建一个4层LB以统一成员节点 nodeport。用户可通过访问云供应商提供的负载均衡器以转发到成员节点的 NodePort。也可以使用metalLB在本地搭建软件负载均衡。
ExternalName: 把集群外部的服务引入集群内部,直接使用。kubectl create service externalname search--external-name www.baidu.com 也就是我们在集群内部使用search会帮我们转发到百度。
2.NodePort类型时工作流程
当你创建一个NodePort类型的Service时,Kubernetes不仅为它分配了一个NodePort,同时也为这个Service分配了一个唯一的ClusterIP。这个ClusterIP是内部的,仅在集群内部可见。具体流程如下:
请求到达:客户端向Node的NodePort(例如30002)发送请求。
kube-proxy处理:kube-proxy在每个Node上运行,监听配置的NodePort。当请求到达时,kube-proxy捕获这个请求。
匹配Service:kube-proxy根据NodePort 确定请求对应的是哪个Service,此时也能拿到svc对应的ClusterIp。
获取Endpoint信息:kube-proxy会根据ClusterIP查询API Server或本地缓存,以获取该Service关联的Endpoint信息,即后端Pod的IP地址和端口。
负载均衡:根据配置的负载均衡策略(如轮询、随机等),kube-proxy选择一个Endpoint中的Pod。
转发请求:kube-proxy将请求直接转发到选定Pod的指定端口(通常是Service定义的targetPort,如果指定了的话)
4.ingress controller、ingress
注意这两个是不同的东西,具体如下:
Ingress:
定义:Ingress 是一种Kubernetes资源,定义了如何通过HTTP和HTTPS路由外部流量到集群内部的服务。
功能:通过Ingress,可以设置基于主机名或URL路径的路由规则,从而将外部请求转发到适当的服务。
配置:Ingress 资源通常会包含规则,这些规则指定了哪些请求应该被路由到哪些服务。可以配置TLS、重定向等。
Ingress Controller:
定义:Ingress Controller 是一个控制器,它负责监视Ingress资源并执行相应的操作,将流量路由到服务。
功能:Ingress Controller 实现了Ingress的具体逻辑,通常是一个负载均衡器或反向代理。它会根据Ingress资源中定义的规则配置自身。
类型:有多种Ingress Controller可供选择,如NGINX Ingress Controller、Traefik、HAProxy、istio等。
区别:
层次:
Ingress 是一种K8s资源定义。
Ingress Controller 是实现Ingress规则的具体组件。
功能:
Ingress 定义了路由规则和配置。
Ingress Controller 执行这些规则,将流量路由到后端服务。
部署:
Ingress 资源是用户在K8s集群中创建的对象。
Ingress Controller 通常是以Pod的形式运行在集群中,负责处理Ingress资源。
注意:ingress controller是以pod形式运行在k8s集群中,所以他可以访问暴露在集群中的svc的clusterIp,所以我们只需要对外暴漏ingress controller对应的pod,然后以他为桥梁去访问集群内的业务pod即可。其工作原理如下图:
1.ingress controller、ingress两者如何实现关联的
两者的结合是在定义ingress资源对象时(注意再生成ingress资源之前我们必须得先配置好我们的ingress controller),通过其ingressClassName属性来指定ingress controller的实现类来进行关联的,每一种ingress controller的实现产品均会提供其本身的实现类来给我们进行使用。再使用之前我们需要将其导入到k8s集群中。如下图:
也可以通过命令行的方式创建ingress,如下: kubectl create ingress nginx-ingress --rule='/test/=nginx1:80' --class=nginx -n ingress-test,意味着当我们访问路径中匹配到test会帮我们转发到nginx1 svc的80号端口 /test。
2.ingress controller是如何对外暴漏的
常用的ingress controller一般有nginx、istio,不管哪种方式我们都需要先进行安装配置,生成ingress controller对应的pod和svc(该svc的类型为nodePort)。这些前提工作完成后才能创建我们的ingress资源。而nodePort类型的service就是我们对外暴漏的端点。
ingress controller部署使用方式一般有两种,如下图:
5.Kubernetes外部HTTP请求到达Pod容器的全过程
ingress是k8s中的一种资源对象,如果想要使用需要引入ingress controller(多种实现nginx,istio等),而在安装引入ingress controller的时候,其会帮我们创建一个nodePort类型的service和ingress对应的pod,该pod会根据再ingress资源中定义的规则信息匹配对应的目标service。
用户从web/mobile/pc等客户端发出HTTP/HTTPS请求。
由于应用服务通常是通过域名的形式对外暴露,所以请求将会先进行DNS域名解析,得到对应的公网IP地址。
公网IP地址通常会绑定一个Load Balancer负载均衡器,此时请求会进入此负载均衡器。
Load Balancer再将请求转发到kubernetes集群的某个流量入口点(集群中的任意节点上),该请求对应的端口会映射到ingress资源实现类对外暴漏的svc上,而svc定义后会通过kube-proxy最终会转化为每个node节点上的iptables或者IPVS规则。
请求到达任意node节点后,该节点的kube-proxy会时刻监控svc对外暴漏的端口,然后根据映射获取的该svc,进而获取到该svc 对应的endpoint信息(里面包含ingress controller对应的pod ip信息),负载均衡到任意ingress对应的pod。
ingress pod会拿到定义的ingress资源对象信息,根据用户自定义的路由规则得到目标service名称,比如根据请求的path路径或host。
ingress pod(控制器)根据目标 svc名称,通过ApiService访问etcd进而得到svc的详细信息,包括Cluster IP和端口等。
Ingress控制器(ingress pod)不直接与应用Pod通信,但它会将请求转发到目标Service的Cluster IP。这个Cluster IP是集群内部的虚拟IP地址,由Kubernetes系统自动分配,不直接映射到任何Node的物理IP,所以请求会随机打到某个node节点上。
在每个Node上,kube-proxy监听集群网络中的流量,特别是对Service Cluster IP的请求。注意:kube-proxy会实时监控svc的状态信息,来维护对应的转发规则,换句话说就是kube-proxy中知道所有的svc信息,包括其cluster ip,端口,endpoint信息,尤其是endpoint信息,里面包含了当前svc所对应的所有的pod信息,包含pod再集群中的ip和端口。
kube-proxy接收到该请求后根据Cluster IP找到对应的svc信息,获取里面的endpoint信息,然后负载均衡找到一个目标pod。
将请求重定向到该目标pod所在的node节点上,进而访问pod中的应用,注意:当请求被转发到目标Pod所在Node时,实际上不需要该Node上的 kube-proxy再次进行处理。请求通过集群内部网络直接路由到了目标Pod的IP地址和端口。