Kubernetes 第二讲
在没有工作负载之前,我们都是直接手动创建一个一个的Pod。但是如果当我们需要将一个复杂的服务部署到庞大的集群上并且实现高可用,我们总不能每个节点上都部署一遍吧。况且如果你可以将所有的Pod手动都创建好,但是如果镜像发生了更新,我们也需要将一个一个的Pod手动进行更新。太麻烦了,这个时候我们就可以使用工作负载,它可以帮助我们更高的管理Pod。
工作负载
工作负载是在 Kubernetes 上运行的应用程序。
为了减轻用户的使用负担,通常不需要用户直接管理每个 Pod。 而是使用负载资源来替用户管理一组 Pod。 这些负载资源通过配置控制器来确保正确类型的、处于运行状态的 Pod 个数是正确的,与用户所指定的状态相一致。
Kubernetes 提供若干种内置的工作负载资源
- Deployment和ReplicaSet
- StatefulSet:有状态服务,为 Pod 提供持久存储和持久标识符。
- DaemonSet:守护型应用部署,如日志、监控组价。
- Job和CronJob:定时任务部署,指定时间运行。
Deployment
Deployment是Kubernetes中用于定义Pod副本集的对象,它负责管理应用的部署和更新。通过Deployment,可以实现应用的滚动更新、回滚以及自动修复。
工作模式
Deployment并不直接管理Pod,而是管理ReplicaSet,ReplicaSet再管理Pod。在我们创建 Deployment 的时候,它会用自己的 Pod 规范创建一个 ReplicaSet。当更新一个 Deployment 并修改副本数量时,它会把更新内容传递给下游的 ReplicaSet。
一个 ReplicaSet 对象就是由副本数目的定义和一个 Pod 模板组成的, 它的定义就是 Deployment 的一个子集。它主要关注的是Pod实例的数量,并在Pod出现问题时,维持所需副本数量。
Deployment则是建立在ReplicaSet之上的更高级别的抽象,用于管理Pod副本集和应用程序的部署。然后它多了滚动更新、回滚以及自动修复这些功能。
主要组成部分
- 模板(Template):定义了要创建的Pod的规范,包括镜像、环境变量、卷等。当副本数不足时会根据模板新建Pod。
- 副本数(Replica Count):指定了希望运行的Pod副本数量。
- 升级策略(Update Strategy):定义了应用更新的策略,包括滚动更新、Recreate等。
- 标签选择器(Label Selector):用于选择要进行管理的Pod副本集。
- 滚动升级(Rolling Update):一种升级策略,通过逐步替换旧的Pod实例来实现平滑的升级。
特点
- 滚动更新:保证更新过程不中断服务。
- 副本管理:保证始终有指定数量的实例在运行。
- 自动修复:当Pod发生故障时会自动替换为新的Pod。
- 版本回滚:允许用户回滚到先前的版本,以应对更新带来的问题
示例:使用Deployment部署Nginx
apiVersion: apps/v1 # API版本 |
kubectl apply -f deploy.yaml |
可以看到RS的NAME是在Deployment之后加了一段字符串,而Pod是在RS后又加了一段字符串,说明Pod其实是由RS直接管理的。
副本伸缩
方式一:命令行
kubectl scale deploy NAME --replicas=pod数量 -n NAMESPACE |
方式二:编辑yaml文件
修改replicas
值即可。
Pod为什么要有副本数?
- 使用多个副本来运行同一个服务,可以提高应用的可用性。如果一个Pod因为故障而被杀死,其他Pod会立即来替代它,从而保证了服务的稳定性。
- 多个副本可以将流量分散到不同的Pod,从而实现负载均衡,避免所有流量集中在一个Pod上导致负载过高。流量高时,我们可以增加副本数,少时可以减少副本数,从而节省资源。
- 多个Pod可以实现平滑的滚动更新。
镜像更新
Deployment支持两种镜像更新策略:重建更新(Recreate)和滚动更新(默认)(RollingUpdate),可以通过strategy
选项进行配置。
重建更新(Recreate)
在spec
下增加如下字段
spec: |
滚动更新(RollingUpdate)
strategy: |
MaxSurge
和MaxUnavailable
,这两个参数决定了更新过程的速度。这两个参数可以是 Pod 数量,也可以是 Deployment 的实例数量百分比。
为什么要有这两个参数?为什么要设置更新过程的速度?
- 我们的服务很重要,对服务的变更需要非常谨慎。因此决定在关闭旧版本Pod之前,需要首先启动新Pod。只有新 Pod 启动、运行并就绪之后,才能杀死旧 Pod。
- 假如我们的集群已经满载,无法负担多余的Pod消耗,我们自然需要先关掉旧Pod,然后才启动新Pod。
当MaxUnavailable
设置为 0 意味着:“在新 Pod 启动并就绪之前,不要关闭任何旧 Pod”。
当MaxSurge
设置为 100% 的意思是:“立即启动所有新 Pod”,也就是说我们有足够的资源,我们希望尽快完成更新。
这两个默认值是25%,假如我们有3个副本,意味着更新时允许 25%(0.75个取整为1) 的Pod处于不可用状态,允许最多增加1个新Pod。
版本回退
Deployment支持版本升级过程中的暂停,继续功能以及版本回退等诸多功能。
kubectl rollout option NAME -n NAMESPACE |
金丝雀发布
Deployment支持更新过程中的控制,如"暂停(pause)"或"继续(resume)"更新操作。
有时我们不想让测试版本影响所有用户,即使是短时间也不行。所以我们可以部分推出新版本。例如我们部署新旧两组实例,1% 的流量发送给新版本,先筛选一小部分的用户请求路由到新的pod应用,继续观察能否稳定地按期望的方式运行。确定没问题之后再继续完成余下的pod资源滚动更新,否则立即回滚更新操作。
现在的镜像版本是latest
,切换版本到1.19.2
,变更途中使用pause
kubectl set image deploy nginx-deployment nginx=reg.redrock.team/library/nginx:1.19.2 -n lesson-demo&& kubectl rollout pause deploy nginx-deployment -n lesson-demo |
发现1.19.2
版本只增加了一个RS,而latest
版本没有减少
查看更新状态,发现deployment正在等待更新,并且一个已经更新完成。
然后继续更新
kubectl rollout resume deploy nginx-deployment -n lesson-demo |
全部更新完成
服务发现与负载均衡
为什么需要服务发现?
传统的部署应用服务方式都是直接部署在给定的机器上,访问服务时,我们只需要访问该机器的IP即可。但K8s集群中的应用都是通过Pod去部署的,而 pod 生命周期是短暂的。在 Pod 的生命周期过程中,比如它创建或销毁,它的 IP 地址都会发生变化,这样就不能使用传统的部署方式,不能指定 IP 去访问指定的应用。
Service
前面我们通过Deployment创建了一组Pod,这些Pod组需要提供一个统一的访问入口,以及怎么去控制流量负载均衡到这个组里面。其实我们可以在部署时就提供一个模板以及访问方式,使应用服务暴露在外部。这时就需要服务发现,也就是K8s中的Service。
示例:通过Service暴露Nginx服务
apiVersion: v1 |
Service的类型
ClusterIP
- 只在集群内部生效的IP,集群内所有节点都能访问到它。
- ClusterIP类型也是我们不指定类型时的默认Service类型。
apply
之后,我们就可以通过访问ClusterIP:PORT
的形式来访问到我们的服务。
访问方式:ClusterIP:PORT
这个 IP 地址就是 Service 的 IP 地址,这个 IP 地址在集群里面可以被其它 pod 所访问,相当于通过这个 IP 地址提供了统一的一个 pod 的访问入口,以及服务发现。而Endpoints是每个Pod的后端IP。
NodePort
当需要从集群外部访问Service时,就可以使用NodePort。
NodePort会在每个NodeIP上启用一个相同的端口来暴露服务。NodeIP就是我们在创建集群时指定的节点IP,端口限定范围为30000-32767。
当我们访问NodeIP:PORT
时,流量会由 kube-proxy 网络组件进一步到给对应的 Pod。
访问方式:NodeIP:PORT
apiVersion: v1 |
LoadBalancer
NodePort会在每台Node上监听端口接收用户流量,但在实际情况下,对用户暴露的只会有一个IP和端口,那这么多台Node该使用哪台让用户来访问呢?而且如果只访问一个NodeIP,那么这个节点压力会很大。
这时就需要前面加一个公网负载均衡器为项目提供统一访问入口了。
LoadBalancer 通常是云服务厂商提供的负载均衡器,我们通过外部的负载均衡器将流量路由到创建的服务商。
将Service的类型设置为LoadBalancer后,我们有两个选择,一个是设置负载均衡器的IP,另一种负载均衡器会自动为我们分配一个ExternalIP作为出口。
访问方式:EXTERNALIP:PORT
apiVersion: v1 |
externalIPs
一般我们的服务只暴露了ClusterIP,而NodePort的端口又有30000-32767的范围限制。这时我们就可以配置上externalIP,表示来自externalIP的流量能进入我们的服务,
externalIP一般填负载均衡器的IP。
注意:externalIPs一定不能填NodeIP,不然会导致该节点直接崩溃(calico、kubelet、kube-proxy等组件无法与apiserver进行通信),出现The connection to the server apiserver.cluster.local:6443 was refused - did you specify the right host or port?
原因分析可以看k8s IPVS模式下externalIP导致节点故障浅析_external-ip-CSDN博客
apiVersion: v1 |
Service四种类型的对比
类型 | 用途介绍 |
---|---|
ClusterIP |
默认类型,自动分配一个仅clusterIp 内部可以访问的虚拟ip地址 |
NodePort |
在ClusterIP 基础上为Service 机器上绑定一个端口,这样就可以通过NodePort 来访问服务 |
LoadBalancer |
在NodePort 基础上借助云服务商创建一个外部负载均衡器,并将请求转发到NodePort 来访问服务 |
externalIPs |
把集群外部的服务引入到集群内部来,在集群内部直接使用 |
Ingress和IngressRoute
没有Ingress之前,我们使用NodePort对外暴露服务,但当服务多了以后,会存在一些弊端,比如暴露太多的的端口,无法实现域名转发等等。
Ingress其实就是从Kubernetes集群外部访问的一个入口,它可以帮助我们通过不同域名来将流量匹配到对应的服务,类似于Nginx。
Ingress在Service上面一层,可以定义集群外部到集群内Service的HTTP和HTTPS的路由。
使用方法:先自行部署IngressController → 再部署Ingress资源。
IngressController的功能:
- 接受外部流量,并将请求负载均衡到内部Pod
- 部署Ingress路由转发规则
- 监控Pod,并在添加或删除Pod后自动更新负载均衡规则。
可以把Ingress controller理解为一个监听器,它可以不断地与 kube-apiserver 打交道,实时的感知后端 service、pod 的变化,当得到这些变化信息后,Ingress controller 再结合 Ingress 的配置,更新反向代理负载均衡器,达到服务发现的作用。
Ingress Controller的实现有多种,比如Ingress-Nginx、Traefik等,由于Ingress-Nginx的配置比较麻烦,所以我们一般使用它的替代品Traefik。
Traefik Ingress Controller
优势:
- 部署过程简单
- 使用Go语言开发,完美贴合K8s
- Traefik衍生的IngressRoute配置更加简洁,我们一般使用IngressRoute
- 拥有多种中间件,可以用于将请求路由到目的地之前或之后来调整请求。
安装
使用Helm安装(Helm是K8s中的一个包管理器)
# helm安装 |
helm repo add traefik https://traefik.github.io/charts |
部署完成后,我们就可以把某个域名解析到我们的集群(下面把k8s.yiiong.top解析到172.20.14.20),然后我们可以配置IngressRoute规则,Traefik就可以根据IngressRoute规则来将流量路由到对应的服务,并为每个服务提供独立的域名。
路由实现
Traefik有两个服务入口(entryPoint)
- 80,对应entryPoint:web
- 443,对应entryPoint:websecure
Traefik默认以NodePort形式暴露,对于如下的Traefik来说,它的外部入口就是30350
和31971
,从30350
进入80
入口,从31971
进入443
入口,具体规则可以在values.yaml
中修改。
如果我们不想通过域名:端口
(k8s.yiiong.top:30350),我们可以通过配置Nginx的反向代理,实现直接访问域名到我们对应的服务。
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE |
路由实现
IngressRoute
Ingress是官方的东西,而IngressRoute则是Traefik Ingress Controller封装好的一种特殊Ingress资源。
配置一个HTTP的IngressRoute
apiVersion: traefik.containo.us/v1alpha1 |
接下来访问http://k8s.yiiong.top:30350
即可访问到我们对应的服务。
配置HTTPS的IngressRoute
我们这里先使用openssl来生成一个自签证书。
openssl genrsa -out ca.key 4096 |
创建证书的Secret
(K8s中一种存储和管理敏感数据的资源类型)
kubectl create secret -n lesson-demo generic traefik-tls --from-file=tls.crt --from-file=tls.key |
配置Yaml
apiVersion: traefik.containo.us/v1alpha1 |
接下来访问https://k8s.yiiong.top:31971
不在集群里使用HTTPS
- 在集群中使用HTTPS太麻烦了,要配这么多东西,有没有什么更好的办法可以把它绕开,然后我们也能使用HTTPS请求呢?
- 答案是在集群中配置一个网关机,让这个网关机来控制流量的进入,我们只需要关注流量进入网关后,把tls给卸载掉,最后用http的流量进入我们的集群,这种方案即安全,还方便,不过缺点是只适合在多节点的集群中使用
中间件middleware
主要用来对HTTP请求做一些修改
HTTP强制跳转HTTPS
实现效果:访问http://k8s.yiiong.top:30350
自动跳转至https://k8s.yiiong.top:31971
先apply
刚才的HTTPS的IngressRoute。
然后配置一个中间件
这里会用到RedirectScheme
的内置中间件,配置如下:
apiVersion: traefik.containo.us/v1alpha1 |
然后在HTTP的Yaml文件里最后添加刚刚创建的中间件。
middlewares: |
可见HTTP被成功重定向到HTTPS
去除请求前缀
有时候我们只有一个域名,但我们想通过这一个域名访问不同的应用。
在Nginx中,我们可以配置Location
来匹配流量,Traefik也可以这么做。
但是定制不同的前缀后,由于应用本身并没有这些前缀,导致请求返回404
,这时候我们就需要对请求的path
进行处理。
先创建一个带前缀的IngressRoute
apiVersion: traefik.containo.us/v1alpha1 |
直接访问会返回404
配置中间件stripPrefix
apiVersion: traefik.containo.us/v1alpha1 |
修改IngressRoute,使用中间件
middlewares: |
灰度发布
灰度发布我们有时候也会称为金丝雀发布(Canary),主要就是让一部分测试的服务也参与到线上去,经过测试观察看是否符合上线要求。
假设一个应用现在运行着V1
版本,新的V2
版本需要上线,这时候我们需要在集群中部署好V2
版本,然后通过Traefik
提供的带权重的轮询(WRR)
来实现该功能。
这和我们之前讲Nginx带权重的负载均衡很类似。
需要两个及以上的Service,然后再加一个TraefikService
apiVersion: traefik.containo.us/v1alpha1 |
我们给v1版本配置权重3,给要上线的v2版本配置权重1.也就是说我们3次访问domain.com
都会是v1版本,第4次就是v2版本。和我们当时Nginx举的8881和8882的那个例子一模一样。
创建TraefikService类型后,我们在IngressRoute类型中将service写为traefikService即可
apiVersion: traefik.containo.us/v1alpha1 |
待v2版本测试没问题后,就可以将流量全切到v2了。
Traefik的中间件还有很多,比如Basic Auth(用户身份认证)、ipWhiteList(定义IP白名单)、mirroring(流量复制)等等
大家可以通过Traefik Proxy Middleware Overview - Traefik进行学习更多的中间件,甚至可以自行开发自定义中间件。
总结:服务暴露模式
暴露模式 | 访问方式 |
---|---|
Service (NodePort) + Deployment | NodeIP:NodePort |
IngressRoute + Service(CluserIP) + Deployment | 域名 |
LoadBalancer + Service + Deployment | 负载均衡器IP:PORT |
第二种方式是最常用的,之所以Service被设置成ClusterIP,是因为流量进入集群后直接就由Ingress Controller管理,不再通过kube-proxy管理。我们常常将域名解析到集群主机,然后IngressRoute设置相同的主机名,就能实现流量匹配
作业:
1.将第三节课课后作业使用FastAPI开发的Web项目部署在K8s上,并通过**IngressRoute + Service(CluserIP) + Deployment + Middleware(去前缀)**的方式暴露服务。
2.部署2个以上的服务在K8s上,利用IngressRoute用域名区分访问。
最终将服务地址(要求能访问到)和整个部署流程记录文档提交到yiiong@redrock.team
截止时间5月5日