k8s阶段04 service资源及用法, Service名称解析和CoreDNS, 应用编排和Deployment, kubeproxy改为ipvs, StatefulSet及有状态应用编排示例

1 Service和Endpoints

编排一个应用:
    (1)选择一个合适的工作负载型控制器,创建该控制器下的资源类型对应的资源对象;
        应用编排控制器:
            Deployment 
            StatefulSet 
            DaemonSet 
            Job 
            CronJob 
    (2)创建一个Service对象,为该应用提供一个固定的访问入口,服务发现和负载均衡
        客户端流量来源存在不同,Service需要分别为其提供流量入口

        External Client --> Nginx Service --> Nginx Pod --> Wowrdpress Service --> Wordpress Pod --> MySQL Service --> MySQL Pod 

Service: 
    netfilter: iptables/ipvs 
        ipvs集群:4层负载均衡器,port
            定义vs:识别并筛选出
                vip:port 

                Service: 独占的VIP,独占的Port(建议选择使用应用协议的well-known的端口)
                    ClusterIP, ServiceIP 

                    10.96.0.0/12
            向vs上添加rs
                Service,自动发现各上游端点,
                    标签选择器:
                        每个Pod,存在事先添加的标签


   规则添加到集群中的哪些节点上?

    Service的类型: 
        ClusterIP:客户端必须集群内部的地址;Service关键标识是ClusterIP;
        NodePort:客户端源自于集群外部(访问任一节点都行);Service的关键标识是NodePort;
            增强版的CLusterIP,它使用NodePort接入集群外部客户端流量,使用ClusterIP接入集群内部流量 
        LoadBalancer:客户端源自于集群外部;Service的关键标识是NodePort;但额外需要一个集群外部的负载均衡器;#云服务提供支持Lbaas的负载均衡器可用(会动态生成),如手动搭建自己写比较麻烦,可用下方ExternalIP方式
            LoadBalancer Service是增强版的NodePort

        #非云端采用如下方式,在节点加个ip地址对外暴露,OpenELB或MetalLB实现keepalive高可用
        ExternalIP:手动配置;存在一些解决方案,能自动完成该功能,例如OpenELB或MetalLB;


    Service的模式:取决kube proxy的配置;#如果规模特别大, iptables最好改为nftables或ipvs
        iptables    #默认
        nftables     #iptables的性能进阶版
        ipvs 
        
    Service:服务发现、负载均衡
        服务发现:标签选择器
        负载均衡:iptables/nftables/ipvs

Endpoints和EndpointSlice

 Endpoints负责维护由相关Service标签选择器匹配的Pod对象
  ◼ Endpoints对象上保存Service匹配到的所有Pod的IP和Port信息(即端点)
  ◼ 后端每个Pod的每次变动,都需更新整个Endpoints对象,并需要将该对象同步至每个节点的kube-proxy
  
 为什么需要EndpointSlice?
  ◼ 即便只有一个Pod的IP等信息发生变动,也必须向集群中的每个kube-proxy发送整个endpoints对象
  ◼ 存储在etcd中的对象的默认大小限制为1.5MB,这也决定了单个Endpoints对象的存储上限
    ◆至多可以存储5000个左右的端点信息
  ◼ 极端场景
    ◆一个由2000个节点组成的集群中,更新一个有着5000个Pod IP的Endpoints对象,需要发送3GB的数据
    ◆若以滚动更新机制,一次替换一个Pod的信息,更新这个Endpoints对象需要发送15T的数据
  ◼ EndpointSlice资源通过将Endpoints切分为多片来解决上述问题
    ◆自Kubernetes v1.16引入
    ◆每个端点信息的变动,仅需要更新和发送一个EndpointSlice,而非整个Endpoints
    ◆每个EndpointSlice默认存储100个端点信息,不会触达etcd对单个对象的存储限制
    ◆可在kube-controller-manager程序上使用“--max-endpoints-per-slice”选项进行配置
 EndpointSlice并未取代Endpoints,二者同时存在

2 Service及流量转发

创建Service资源

由Service表示的负载均衡器,主要定义如下内容
  ◼ 负载均衡器入口:ClusterIP及相关的Service Port、NodePort(每个节点的Node IP都可用)
    ◆ 根据通信需求,确定选择的类型
  ◼ 标签选择器:用于筛选Pod,并基于筛选出的Pod的IP生成后端端点列表(被调度的上游端点)
  ◼ Service类型的专有配置

 标签和标签选择器

标签:附加在资源对象上的键值型元数据
  ◼ 键标识:由“键前缀(可选)”和“键名”组成,格式为“key_prefix/key_name”
    ◆键前缀必须使用DNS域名格式
    ◆键名的命名格式:支持字母、数字、连接号、下划线和点号,且只能以字母或数字开头;最长63个字符;
  ◼ “kubectl label”命令可管理对象的标签
标签选择器:基于标签筛选对象的过滤条件,支持两种类型
  ◼ 基于等值关系的选择器
    ◆操作符:=或==、!=
  ◼ 基于集合关系的选择器
    ◆操作符:in、notin和exists
    ◆使用格式:KEY in (VALUE1, VALUE2, …)、 KEY notin (VALUE1, VALUE2, …)、KEY 和 !KEY
    
标签: 
    定义的方式1:
        metadata:
          labels:
            key1: value1
            key2: value2
            ...
    定义方式2:
        命令:kubectl label 
            kubectl label [--overwrite] (-f FILENAME | TYPE NAME) KEY_1=VAL_1 ... KEY_N=VAL_N
            增加:
                kubectl label TYPE NAME KEY_1=VAL_1 ... KEY_N=VAL_N
            修改:#overwrite进行覆盖
               kubectl label --overwrite TYPE NAME KEY_1=VAL_1 ... KEY_N=VAL_N 
            删除:#名称后面加-
                kubectl label TYPE NAME KEY_1-
                
#例:
#查看现有标签
]#kubectl get pods --show-labels
#给一个pod添加标签
]#kubectl label pods demoapp-7c58cd6bb-f5zvg version="v1.0"
#修改标签
]#kubectl label --overwrite pods demoapp-7c58cd6bb-f5zvg version="v1.1"
#删除标签
]#kubectl label pods demoapp-7c58cd6bb-f5zvg version-

#标签选择器示例
]#kubectl label pods demoapp-7c58cd6bb-f5zvg version="v1.0"
]#kubectl label pods demoapp-7c58cd6bb-f5zvg tier="backend"
]#kubectl label pods demoapp-7c58cd6bb-ptjcw version="v1.0"
#过滤出标签值为version=v1.0
]#kubectl get pods -l "version=v1.0"
#过滤出标签值不为version=v1.0 (没有version标签的也可以)
]#kubectl get pods -l "version!=v1.0"
#过滤出标签值在(frontend,backend,db)范围内
]#kubectl get pods -l "tiel in (frontend,backend,db)" --show-labels
#过滤出标签 (包含不存在tiel标签)
]#kubectl get pods -l "tiel notin (frontend,backend,db)" --show-labels
#过滤出存在version键的
]#kubectl get pods -l "version"
#过滤出不存在version键
]#kubectl get pods -l '!version'
#或逻辑
]#kubectl get pods -l "tier" -l "version=v1.0"
#与逻辑
]#kubectl get pods -l "tier,version=v1.0"

Service资源规范

#Kubernetes上标准的API资源类型
apiVersion: v1
kind: Service
metadata:
  name: …
  namespace: …
spec:
  type <string> # Service类型,默认为ClusterIP
  selector <map[string]string> # 等值类型的标签选择器,内含“与”逻辑
  ports: # Service的端口对象列表
  - name <string> # 端口名称
    protocol <string> # 协议,目前仅支持TCP、UDP和SCTP,默认为TCP
    port <integer> # Service的端口号
    targetPort <string> # 后端目标进程的端口号或名称,名称需由Pod规范定义
    nodePort <integer> # 节点端口号,仅适用于NodePort和LoadBalancer类型(尽量不要自己指)
clusterIP <string> # Service的集群IP,建议由系统自动分配
externalTrafficPolicy <string> # 外部流量策略处理方式,Local表示由当前节点处理,Cluster表示向集群范围调度
loadBalancerIP <string> # 外部负载均衡器使用的IP地址,仅适用于LoadBlancer
externalName <string> # 外部服务名称,该名称将作为Service的DNS CNAME值


#Service资源示例
#ClusterIP Service
kind: Service
apiVersion: v1
metadata:
  name: demoapp
spec:
  type: ClusterIP # 类型标识,默认即为ClusterIP;
  selector:
    app: demoapp
  ports:
  - name: http # 端口名称标识
    protocol: TCP # 协议,支持TCP、UDP和SCTP
    port: 80 # Service的端口号
    targetPort: 80 # 目标端口号,即后端端点提供服务的监听端口号

#实际操作示例
#先创建pod
]#kubectl create deployment demoapp --image=ikubernetes/demoapp:v1.0 --replicas=3
#编辑service.yaml
]#vim demoapp-clusterip-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: demoapp-clusterip
  namespace: default
spec:
  selector: #选择标签
    app: demoapp
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 80 #后端项目监听的80
#创建service
]#kubectl apply -f demoapp-clusterip-service.yaml
#查看service
[root@master01 ~]#kubectl get svc
NAME                TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
demoapp-clusterip   ClusterIP   10.96.150.225   <none>        80/TCP    26s
#自动创建endpoint,同名
[root@master01 ~]#kubectl get ep
NAME                ENDPOINTS                                      AGE
demoapp-clusterip   10.244.1.59:80,10.244.2.48:80,10.244.3.44:80   2m37s
#此时集群内部任意节点访问service,都会自动分配到其中的pod上

#让外部也能访问service,要用nodeport类型
#注意:nodeport是ClusterIP增强版,可以接受内部流量和外部流量,有nodeport就不需要ClusterIP了
]#cp demoapp-clusterip-service.yaml demoapp-nodeport-service.yaml
]#vim demoapp-nodeport-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: demoapp-nodeport
  namespace: default
spec:
  type: NodePort #指定类型
  selector: #选择标签
    app: demoapp
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 80 #后端项目监听的80
#创建时既会分配ClusterIP,又会分配nodeport
]#kubectl apply -f demoapp-nodeport-service.yaml
#查看service,80是service端口,32725是nodeport端口
[root@master01 ~]#kubectl get svc
NAME                TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
demoapp-clusterip   NodePort    10.111.228.210   <none>        80:32725/TCP   13s
#集群内部任一节点访问
[root@node01 ~]#curl 10.111.228.210
#集群外部访问,这里用windows的cmd访问,通过任一节点,nodeport端口号进行访问
C:\Users\ldc>curl 10.0.0.151:32725
iKubernetes demoapp v1.0 !! ClientIP: 10.244.0.0, ServerName: demoapp-8699b9895b-8b7w5, ServerIP: 10.244.2.48!

NodePort Service流量策略

流量策略一:Cluster,表示在整个Kubernetes集群范围内调度;
  ◼ 该流量策略下,请求报文从某个节点上的NodePort进入,该节点上的Service会将其调度至任何一个可用后端
Pod之上,而不关心Pod运行于哪个节点;

 

流量策略二:Local,表示仅将请求调度至当前节点上运行的可用后端端点;
  ◼ 该流量策略下,请求报文从某节点NodePort进入后,该节点上的Service仅会将请求调度至当前节点上适配到该Service的后端端点
  ◼ 仅应该从运行有目标Service对象后端Pod对象的节点的NodePort发起访问

 需要注意, local策略需要配合外部的LB才可以, 因为必须访问有pod存在的节点才行, 需要外部的LB来控制

NodePort Service的外部流量策略:
    Local:结合外部的LB使用,外部LB要能理解Service Pod与Node的映射关系 
    Cluster:默认值,存在跃点

#LoadBalancer
如何获得LB:#阿里云ACK,腾讯云,华为云CCE,国外的云都有相应解决方案
    MetalLB或OpenELB #也能实现LB功能(可能openstack不支持,云环境不一定支持)

 如果实在没有上面的LB使用时,可以尝试使用 ExternalIP 方案

ExternalIP

 在Service上使用ExternalIP
  ◼ Service可通过使用节点上配置的辅助IP地址接入集群外部客户端流量
  ◼ 流量入口仅能是配置有该IP地址的节点,其它节点无效,因而此时在节点间无负载均衡的效果
  ◼ external IP所在的节点故障后,该流量入口失效,除非将该IP地址转移配置到其它节点
  ◼ 是除了LoadBalancer和NodePort外,接入外部流量的又一种方式

#示例
#在节点1上加个ip地址,该地址要能被路由,加在设备ens33上(这里为临时添加,重启就没了)
[root@node01 ~]#ip addr add 10.0.0.200 dev ens33
#进行查看
[root@node01 ~]#ip a

#把10.0.0.200关联到service进行使用,ClusterIp或者NodePort类型都行
#主节点上编辑(这里直接在线编辑,也可以编辑yaml再apply) spec下追加externalIPs
[root@master01 ~]#kubectl edit svc demoapp-clusterip
spec:
  ...
  externalIPs:
  - 10.0.0.200 #必须某个节点上有,并且从外部可路由过去的

#此时查看service出现EXTERNAL-IP
[root@master01 ~]#kubectl get svc
NAME                TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
demoapp-clusterip   ClusterIP   10.111.228.210   10.0.0.200    80/TCP         86m
kubernetes          ClusterIP   10.96.0.1        <none>        443/TCP        9d

#外部访问测试
C:\Users\ldc>curl 10.0.0.200
iKubernetes demoapp v1.0 !! ClientIP: 10.244.1.0, ServerName: demoapp-8699b9895b-8b7w5, ServerIP: 10.244.2.48!
#注意,如果节点宕机了,就无法访问了,可通过keepalive漂移到另一节点(keepalive会自动解决缓存问题,手动切需要解决)

#当然非云端,MetalLB和OpenELB项目能直接解决外部访问,不用想上面通过ip绑定service

LoadBalancer类型的Service

非云环境中的LoadBalancer
  ◼ 部署于裸机或类似裸机环境中的Kubernetes集群,缺少可用的LBaaS服务
  ◼ MetalLB和OpenELB等项目为此提供了解决方案

MetalLB
#K8s上部署MetalLB,不用手动在节点上添加ip地址,给MetalLB一个地址池,每当创建一个service,会模拟像LB给该service中一个pod ip。如果那个节点挂了,MetalLB会自动发现并把ip迁移到其他节点上,并通知整个网络更新缓存
#某个service的节点成为唯一入口,该节点会成为性能瓶颈。如果前端网络设备支持BGP协议,地址不配置在任何节点上,流量通过路由,路由通过BGP分发给每一个节点,任一节点都可以作为服务负载均衡器使用
  ◼ A network load-balancer implementation for Kubernetes using standard routing protocols
  ◼ MetalLB核心功能的实现依赖于两种机制
    ◆Address Allocation(地址分配):基于用户配置的地址池,为用户创建的LoadBalancer分配IP地址,并配置在节点上
    ◆External Announcement(对外公告):让集群外部的网络了解新分配的IP地址,MetalLB使用ARP、NDP或BGP实现
  ◼ MetalLB 可配置为在二层模式或BGP 模式下运行
  ◼ 二层模式(ARP/NDP)
    ◆LoadBalancer IP地址配置在某一个节点上,并使用ARP(IPv4)或NDP(IPv6)对外公告
    ◆拥有LoadBalancer IP地址的节点将成为Service流量的惟一入口,并在节点故障时自动进行故障转移
    ◆并未真正实现负载均衡,存在性能瓶颈,且故障转移存在秒级的延迟
  ◼ BGP模式
    ◆集群中的所有节点与本地网络中的BGP Router建立BGP对等会话,通告LoadBalancer IP,从而告知Router如何进行流量路由
    ◆可以实现跨多个节点的真正意义上的负载均衡

MetalLB

#官网
https://metallb.universe.tf/

#安装MetalLB(这里采用上面的二层模式,某个节点成为唯一入口)
#主节点
[root@master01 ~]#kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.8/config/manifests/metallb-native.yaml
#会运行几个pod(每个主机上都有speaker,做ARP公告的)
[root@master01 ~]#kubectl get pods -n metallb-system
NAME                          READY   STATUS              RESTARTS   AGE
controller-8694df9d9b-ks75k   0/1     Running            0          71s
speaker-2lx2s                 0/1     Running            0          71s
speaker-fbwd6                 0/1     Running            0          71s
speaker-tbsg9                 0/1     Running            0          71s
speaker-wgrg4                 0/1     Running            0          71s

#地址池里必须是集群节点地址范围,并且是其他节点没有使用的地址
[root@master01 ~]#cd learning-k8s/MetalLB/ [root@master01 MetalLB]#vim metallb-ipaddresspool.yaml apiVersion: metallb.io/v1beta1 kind: IPAddressPool metadata: name: localip-pool namespace: metallb-system #metallb自己的名称空间 spec: addresses: #确保地址没人用 - 10.0.0.51-10.0.0.80 autoAssign: true #自动配置在某个节点上 avoidBuggyIPs: true #避免使用全0或全1的ip #创建地址池 [root@master01 MetalLB]#kubectl apply -f metallb-ipaddresspool.yaml #做公告模式 [root@master01 MetalLB]#vim metallb-l2advertisement.yaml apiVersion: metallb.io/v1beta1 kind: L2Advertisement metadata: name: localip-pool-l2a namespace: metallb-system spec: ipAddressPools: - localip-pool interfaces: #通过这个接口对外发ARP(自问自答) - ens33 #网卡地址(如果多个节点网卡名不同,那都列在这) #创建对应资源 [root@master01 MetalLB]#kubectl apply -f metallb-l2advertisement.yaml #使用loadbalance service(通过MetalLB实现) [root@master01 ~]#cp demoapp-nodeport-service.yaml demoapp-lb-service.yaml [root@master01 ~]#vim demoapp-lb-service.yaml apiVersion: v1 kind: Service metadata: name: demoapp-lb namespace: default spec: type: LoadBalancer #externalTrafficPolicy: Local #外部流量策略,默认是 Cluster selector: app: demoapp ports: - name: http protocol: TCP port: 80 targetPort: 80 #应用 [root@master01 ~]#kubectl apply -f demoapp-lb-service.yaml #查看LoadBalancer也有nodeport,其实就是增强版NodePort,就算没有EXTERNAL-IP地址,也可以nodeport访问 [root@master01 ~]#kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE demoapp-clusterip NodePort 10.111.228.210 10.0.0.200 80:32725/TCP 18h demoapp-lb LoadBalancer 10.107.254.160 10.0.0.51 80:32194/TCP 15s #外部访问测试(流量分发到不同pod上) C:\Users\ldc>curl 10.0.0.51 iKubernetes demoapp v1.0 !! ClientIP: 10.244.1.1, ServerName: demoapp-8699b9895b-j6rrh, ServerIP: 10.244.1.64! C:\Users\ldc>curl 10.0.0.51 iKubernetes demoapp v1.0 !! ClientIP: 10.244.1.0, ServerName: demoapp-8699b9895b-8b7w5, ServerIP: 10.244.2.54! C:\Users\ldc>curl 10.0.0.51 iKubernetes demoapp v1.0 !! ClientIP: 10.244.1.0, ServerName: demoapp-8699b9895b-fn4k9, ServerIP: 10.244.3.48! #修改lb service的流量策略为local(会检查后端节点状态,能响应就作为后端端点使用,没响应流量不会转发过去) [root@master01 ~]#vim demoapp-lb-service.yaml apiVersion: v1 kind: Service metadata: name: demoapp-lb namespace: default spec: type: LoadBalancer externalTrafficPolicy: Local #外部流量策略,默认是 Cluster selector: app: demoapp ports: - name: http protocol: TCP port: 80 targetPort: 80 #应用 [root@master01 ~]#kubectl apply -f demoapp-lb-service.yaml #外部访问测试,请求都打在了10.244.1.64上(也可能会变) C:\Users\ldc>curl 10.0.0.51 iKubernetes demoapp v1.0 !! ClientIP: 10.0.0.1, ServerName: demoapp-8699b9895b-j6rrh, ServerIP: 10.244.1.64! C:\Users\ldc>curl 10.0.0.51 iKubernetes demoapp v1.0 !! ClientIP: 10.0.0.1, ServerName: demoapp-8699b9895b-j6rrh, ServerIP: 10.244.1.64! #可能ip地址被配在1.64所在主机上,EXTERNAL-IP不能实现真正的负载均衡;如果碰巧配的ip地址上没有pod,可能依然无法响应 #要解决这个问题要么使用BGP模式,要么使用云端的LBaaS服务

 

3 Service名称解析

ClusterIP Service:
    CoreDNS: 
        API Server的客户端,注册监视每个Service的变动
            自动为每个Service生成FQDN格式的名称
                <service_name>.<namespace>.svc.<zone>
                    <zone>: cluster.local 
             
                例如:default/demoapp: demoapp.default.svc.cluster.local
           
                dev/pod --> kube-system/openebs 
                    dev.svc.cluster.local svc.cluster.local cluster.local 
                           
                    openebs.kube-system   
            自动生资源记录

Service和Cluster DNS

Cluster DNS(CoreDNS)是Kubernetes集群的必备附件,负责为Kubernetes提供名称解析和服务发现
  ◼ 每个Service资源对象,在CoreDNS上都会自动生成一个遵循“<service>.<ns>.svc.<zone>”格式的名称
    ◆<service>:当前Service对象的名称
    ◆<ns>:当前Service对象所属的名称空间
    ◆<zone>:当前Kubernetes集群使用的域名后缀,默认为“cluster.local”
  ◼ 围绕该名称会生成一些DNS格式的资源记录
  
CoreDNS会持续监视API Server上的Service资源对象的变动,并实时反映到相关的DNS资源记录中
Pod中各容器默认会在/etc/resolv.conf中,将nameserver指向CoreDNS相关的Service的ClusterIP
  ◼ 由kubelet创建Pod时根据指定的配置自动注入

Service在Cluster DNS上的资源记录

每个Service,在CoreDNS上都会有A/AAAA、SRV和PTR资源记录
  ◼ A/AAAA资源记录
    ◆<service>.<ns>.svc.<zone>. <ttl> IN A <cluster-ip><service>.<ns>.svc.<zone>. <ttl> IN AAAA <cluster-ip>
  ◼ 为每个定义了名称的端口生成一个SRV记录,以支持服务发现
    ◆_<port>._<proto>.<service>.<ns>.svc.<zone>. <ttl> IN SRV <weight> <priority> <port-number> 
<service>.<ns>.svc.<zone>.
  ◼ 为每个A记录(例如a.b.c.d)或AAAA记录生成对应的PTR记录,例如
    ◆<d>.<c>.<b>.<a>.in-addr.arpa. <ttl> IN PTR <service>.<ns>.svc.<zone>.                   ◆h4.h3.h2.h1.g4.g3.g2.g1.f4.f3.f2.f1.e4.e3.e2.e1.d4.d3.d2.d1.c4.c3.c2.c1.b4.b3.b2.b1.a4.a3.a2.a1.ip6.arpa <ttl> IN PTR <service>.<ns>.svc.<zone>.

Pod可基于Service的DNS名称向其发起服务访问请求

#示例:
#查看当前服务
[root@master01 ~]#kubectl get svc
NAME                TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
demoapp-clusterip   NodePort       10.111.228.210   10.0.0.200    80:32725/TCP   24h
demoapp-lb          LoadBalancer   10.107.254.160   10.0.0.51     80:32194/TCP   5h30m
#进入pod
[root@master01 ~]#kubectl exec -it demoapp-8699b9895b-8b7w5 -- /bin/sh
#注:这里名称空间default是当前所在的名称空间,如果属于其他名称空间,这里会变为对应名称空间
[root@demoapp-8699b9895b-8b7w5 /]# cat /etc/resolv.conf 
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10
options ndots:5 #限制路径最多5个.

#解析域名(名称后缀会根据resolv.conf选项自动补全)
[root@demoapp-8699b9895b-8b7w5 /]# host -t A demoapp-clusterip
demoapp-clusterip.default.svc.cluster.local has address 10.111.228.210
[root@demoapp-8699b9895b-8b7w5 /]# host -t A demoapp-clusterip.default
demoapp-clusterip.default.svc.cluster.local has address 10.111.228.210

Pod中的每个容器:#pod内部是可以互相互相通讯的,外部请求没办法解析service地址
    /etc/resolv.conf 
        nameserver 10.96.0.10

#请求调用
External Client --> Nginx Service --> Nginx Pod --> Wowrdpress Service --> Wordpress Pod --> MySQL Service --> MySQL Pod
#内部请求,可以直接通过名称进行调用service
External Client --> Nginx Service --> Nginx Pod --> Wowrdpress Service name --> Wordpress Pod --> MySQL Service name --> MySQL Pod  
#外部访问nginx不能使用域名解析
Nginx Service: NodeIP:NodePort 
    blog.magedu.com:NodePort 
LoadBalancer --> Nginx Service (NodeIP:NodePort) #改进
    blog.magedu.com --> LB_IP

Pod上的DNS解析策略

Kubernetes支持在单个Pod资源规范上自定义DNS解析策略和配置,并组合生效
  ◼ spec.dnsPolicy:解析策略
  ◼ spec.dnsConfig:名称解析机制
DNS解析策略 #dnsPolicy
  ◼ Default:从运行在的节点继承DNS名称解析相关的配置
  ◼ ClusterFirst:于集群DNS服务上解析集群域内的名称,其他域名的解析则交由从节点继承而来的上游名称服务器
  #hostNetwork:pod使用宿主机的网络
  ◼ ClusterFirstWithHostNet:专用于在设置了hostNetwork的Pod对象上使用的ClusterFirst策略
  ◼ None:用于忽略Kubernetes集群的默认设定,而仅使用由dnsConfig自定义的配置
DNS解析机制 #dnsConfig类配置
  ◼ nameservers <[]string>:DNS名称服务器列表,附加于由dnsPolicy生成的DNS名称服务器之后
  ◼ searches <[]string>:DNS名称解析时的搜索域,附加由于dnsPolicy生成的搜索域之后
  ◼ options <[]Object>:DNS解析选项列表,同dnsPolicy生成的解析选项合并成最终生效的定义
  
#示例(自定义dns解析策略)
[root@master01 services]#vim pod-with-dnspolicy.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-dnspolicy
  namespace: default
spec:
  containers:
  - name: demo
    image: ikubernetes/demoapp:v1.0
    imagePullPolicy: IfNotPresent
  dnsPolicy: None
  dnsConfig: #自定义
    nameservers: #指明dns服务器
    - 10.96.0.10 #本地dns
    - 223.5.5.5
    - 223.6.6.6
    searches:
    - svc.cluster.local
    - cluster.local
    - ilinux.io #自定义域名
    options:
    - name: ndots
      value: "5"
[root@master01 services]#kubectl apply -f pod-with-dnspolicy.yaml
[root@master01 services]#kubectl exec -it pod-with-dnspolicy -- cat /etc/resolv.conf
search svc.cluster.local cluster.local ilinux.io
nameserver 10.96.0.10
nameserver 223.5.5.5
nameserver 223.6.6.6
options ndots:5

ExternalName

类似无头服务

Service的特殊类型:
    ExternalName: #本质上就是pod去连接一个服务直接跳转(CName)到外部真正的服务
        Internal Client --> Service Name --> External Name --> 外部的某服务的IP地址;
         #使得访问集群外部就像访问集群内部服务一样
        wordpress Pod --> mysql.blog.svc.cluster.local --> mysql.magedu.com --> 172.29.5.101/172.29.5.102
        
#示例:
[root@master01 services]#vim externalname-redis-svc.yaml
kind: Service
apiVersion: v1
metadata:
  name: externalname-redis-svc
  namespace: default
spec:
  type: ExternalName
  externalName: redis.ik8s.io #CName解析为这个名字
  ports:
  - protocol: TCP
    port: 6379
    targetPort: 6379
    nodePort: 0
  selector: {} #不选择任何pod
[root@master01 services]#kubectl apply -f externalname-redis-svc.yaml
[root@master01 services]#kubectl get svc
NAME                     TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)        AGE
externalname-redis-svc   ExternalName   <none>           redis.ik8s.io   6379/TCP       17s

#随便进一个pod,看看能不能解析
[root@master01 services]#kubectl exec -it demoapp-8699b9895b-8b7w5 -- /bin/sh
[root@demoapp-8699b9895b-8b7w5 /]# host -t A externalname-redis-svc
externalname-redis-svc.default.svc.cluster.local is an alias for redis.ik8s.io.
redis.ik8s.io has address 1.2.3.4 #这个域名是互联网上的

Headless Service

Headless Service: 
    创建一个常规的ClusterIP类型的Service,但却要求不分配ClusterIP;
    被StatefulSet所依赖;
    
#示例
[root@master01 ~]#vim demoapp-headless-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: demoapp-headless
  namespace: default
spec:
  type: ClusterIP #指定类型
  clusterIP: None #不要分配任何ClusterIP
  selector:
    app: demoapp
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 80
[root@master01 ~]#kubectl apply -f demoapp-headless-service.yaml
[root@master01 ~]#kubectl get svc
NAME                TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
demoapp-headless    ClusterIP      None             <none>        80/TCP         20s
#查看endpoint
[root@master01 ~]#kubectl get ep
NAME                ENDPOINTS                                      AGE
demoapp-headless    10.244.1.64:80,10.244.2.54:80,10.244.3.48:80   119s
#进入任意pod,解析名称
[root@master01 services]#kubectl exec -it demoapp-8699b9895b-8b7w5 -- /bin/sh
#直接解析到各个pod,service负载均衡器不发挥作用,通过dns服务器默认轮询调度
[root@demoapp-8699b9895b-8b7w5 /]# host -t A demoapp-headless
demoapp-headless.default.svc.cluster.local has address 10.244.2.54
demoapp-headless.default.svc.cluster.local has address 10.244.1.64
demoapp-headless.default.svc.cluster.local has address 10.244.3.48

#Headless Service这种类型会被StatefulSet工作负载控制器所依赖

4 CoreDNS 配置方式简介(了解)

[root@master01 ~]#kubectl get cm -n kube-system
NAME                                                   DATA   AGE
coredns                                                1      10d

[root@master01 ~]#kubectl get cm coredns -n kube-system -o yaml
apiVersion: v1
data:
  Corefile: |
    .:53 {
        errors
        health {
           lameduck 5s
        }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
           ttl 30
        }
        prometheus :9153
        forward . /etc/resolv.conf { #转给/etc/resolv.conf解析
           max_concurrent 1000
        } #如果有个内网地址想要被解析,可以后面再加个forward(给dns服务器)
        cache 30
        loop
        reload
        loadbalance
    }
kind: ConfigMap
metadata:
  creationTimestamp: "2024-11-16T04:10:04Z"
  name: coredns
  namespace: kube-system
  resourceVersion: "225"
  uid: b97caacd-6428-451e-861e-aaad19b522d7

 

1 Kubernetes控制器模式

工作负载编排: 
    专用编排以Pod形式承载的应用程序的控制器
        Deployment Controller --> 管理一切Deployment资源类型下的资源对象 

        控制器模式: 
            Spec: 期望状态,由用户定义,并提交给API Server 
            Status: 实际状态,由控制器负责通过运行代码生成

声明式API和控制器模式

 控制器模型(控制回路)
  ◼ 初始,Controller负责根据Input(目标状态)控制System,并生成Output(结果状态)
  ◼ Feedback根据Output生成Feedback Signal,而后由Error Detector基于Feedback Signal和Input来判定是否存在错误,并在有错误时生成Error Signal
  ◼ Error Signal将驱动Controller生成Actuating Signal,并控制System的行为与Input要求相同

 Kubernetes Controller的控制回路
  ◼ Controller根据spec,控制System生成Status
  ◼ Controller借助于Sensor持续监视System的Spec和Status,在每一次控制回路中都会对二者进行比较,并确保System的Status不断逼近或完全等同Status

服务类应用:
    Deployment --> ReplicaSet --> Pod:无状态应用 #Deployment通过ReplicaSet来编排控制pod
        Nginx --> 3 Pods 
    StatefulSet:编排有状态应用
        Redis Cluster --> 3 Pods 
        RabbitMQ Cluster --> 3 Pods
         
        Operator:operatorhub.io  #增强版StatefulSet
            结合专用的自定义资源类型(CRD, Custom Resource Defination)来完成;
                ElasticSearch
    DaemonSet:编排系统级应用 #通常集群中每个节点部署一个pod(一个就够了)
作业类应用
    Job: 一次性作业
    CronJob:周期性作业
    
应用编排的基本配置框架
    replicas: 副本数
    selector: 标签选择器 
    template:Pod模板
    
Deployment: 
    ReplicaSet:
    
#示例
#查看ReplicaSet资源,简写rs
[root@master01 ~]#kubectl explain rs
GROUP:      apps
KIND:       ReplicaSet
VERSION:    v1
...
#定义replicaset
[root@master01 ~]#vim replicaset-demoapp.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: rs-demo
  namespace: dev #保证dev名称空间存在
spec:
  replicas: 2 #要创建几个pod
  selector:
    matchLabels: #service没有该字段,replicaset必须有
      app: demoapp
  template:
    metadata: #名字自动生成,(上面name后缀加随机字符);名称空间会和上面相同
      labels: #保证上面的标签选择器能选中
        app: demoapp 
        #tier: backend #其他标签随意定义
    spec:
      containers:
      - name: demoapp
        image: ikubernetes/demoapp:v1.0
        
#创建
[root@master01 ~]#kubectl create namespace dev
[root@master01 ~]#kubectl apply -f replicaset-demoapp.yaml
#查看replicaset,rs为简写
[root@master01 ~]#kubectl get rs -n dev
NAME      DESIRED   CURRENT   READY   AGE
rs-demo   2         2         2       45s
#显示更多字段
[root@master01 ~]#kubectl get rs -n dev -o wide
NAME      DESIRED   CURRENT   READY   AGE    CONTAINERS   IMAGES                     SELECTOR
rs-demo   2         2         2       2m4s   demoapp      ikubernetes/demoapp:v1.0   app=demoapp

#扩容
[root@master01 ~]#vim replicaset-demoapp.yaml
...
spec:
  replicas: 5 #修改为5
...
[root@master01 ~]#kubectl apply -f replicaset-demoapp.yaml
[root@master01 ~]#kubectl get rs -n dev
NAME      DESIRED   CURRENT   READY   AGE
rs-demo   5         5         5       9m37s
#缩容
#除了修改yaml文件,通过命令行命令也能实现
[root@master01 ~]#kubectl scale replicaset rs-demo --replicas=4 -n dev

#更新
[root@master01 ~]#vim replicaset-demoapp.yaml
...
    spec:
      containers:
      - name: demoapp
        image: ikubernetes/demoapp:v1.1 #修改镜像
[root@master01 ~]#kubectl apply -f replicaset-demoapp.yaml
#查看replicaset变为1.1,但是去访问里面的服务,依然是1.0
[root@master01 ~]#kubectl get rs -n dev -o wide
NAME      DESIRED   CURRENT   READY   AGE   CONTAINERS   IMAGES                     SELECTOR
rs-demo   5         5         5       22m   demoapp      ikubernetes/demoapp:v1.1   app=demoapp
#注意,这里只有删一个pod,它自动创建一个才会用新模板变1.1
#replicaset没有办法完成高级的更新功能

#Deployment建立在replicaset基础之上,提供滚动更新并保存历史的能力

2 Deployment资源及控制器

Deployment控制器简介

负责编排无状态应用的基础控制器是ReplicaSet,相应的资源类型通过三个关键组件定义如何编排一个无状态应用
  ◼ replicas:期望运行的Pod副本数
  ◼ selector:标签选择器
  ◼ podTemplate:Pod模板
Deployment是建立在ReplicaSet控制器上层的更高级的控制器
  ◼ 借助于ReplicaSet完成无状态应用的基本编排任务
  ◼ 它位于ReplicaSet更上面一层,基于ReplicaSet提供了滚动更新、回滚等更为强大的应用编排功能
  ◼ 是ReplicaSet资源的编排工具
    ◆Deployment编排ReplicaSet
    ◆ReplicaSet编排Pod
  ◼ 但是,应该直接定义Deployment资源来编排Pod应用,ReplicaSet无须显式给出

#示例:
[root@master01 ~]#cp replicaset-demoapp.yaml deployment-demoapp.yaml
[root@master01 ~]#vim deployment-demoapp.yaml
apiVersion: apps/v1
kind: Deployment #修改类型
metadata:
  name: deploy-demo #修改名称
  namespace: dev
spec:
  replicas: 4
  selector:
    matchLabels:
      app: demoapp
  template:
    metadata:
      labels:
        app: demoapp
    spec:
      containers:
      - name: demoapp
        image: ikubernetes/demoapp:v1.1
#删掉前面的replicaset
[root@master01 ~]#kubectl delete -f replicaset-demoapp.yaml
#创建
[root@master01 ~]#kubectl apply -f deployment-demoapp.yaml 
#查看所有资源
[root@master01 ~]#kubectl get all -n dev
NAME                              READY   STATUS    RESTARTS   AGE
pod/deploy-demo-5fbd75c9c-5bt7g   1/1     Running   0          101s
pod/deploy-demo-5fbd75c9c-hqzhs   1/1     Running   0          101s
pod/deploy-demo-5fbd75c9c-lkd5x   1/1     Running   0          101s
pod/deploy-demo-5fbd75c9c-qh8gp   1/1     Running   0          101s

NAME                          READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/deploy-demo   4/4     4            4           101s

NAME                                    DESIRED   CURRENT   READY   AGE
replicaset.apps/deploy-demo-5fbd75c9c   4         4         4       101s
#后缀5fbd75c9c对应yaml中的template内容hash码,template内容有变更,这个hash码一定会变更

[root@master01 ~]#kubectl get deploy -n dev -o wide
NAME          READY   UP-TO-DATE   AVAILABLE   AGE    CONTAINERS   IMAGES                     SELECTOR
deploy-demo   4/4     4            4           4m7s   demoapp      ikubernetes/demoapp:v1.1   app=demoa

#更新
[root@master01 ~]#vim deployment-demoapp.yaml
...
    spec:
      containers:
      - name: demoapp
        image: ikubernetes/demoapp:v1.2 #修改版本
#apply应用会自动创建ReplicaSet,并替换旧版本,默认一次更新25%
#这里应用的同时,查看deployment的状态(非必须)
[root@master01 ~]#kubectl apply -f deployment-demoapp.yaml && kubectl rollout status -f deployment-demoapp.yaml
[root@master01 ~]#kubectl get all -n dev
NAME                              READY   STATUS    RESTARTS   AGE
pod/deploy-demo-fdccdbb9d-6m5s8   1/1     Running   0          72s
pod/deploy-demo-fdccdbb9d-88fjl   1/1     Running   0          82s
pod/deploy-demo-fdccdbb9d-8jd56   1/1     Running   0          82s
pod/deploy-demo-fdccdbb9d-vdm2z   1/1     Running   0          71s

NAME                          READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/deploy-demo   4/4     4            4           12m

NAME                                    DESIRED   CURRENT   READY   AGE
replicaset.apps/deploy-demo-5fbd75c9c   0         0         0       12m
replicaset.apps/deploy-demo-fdccdbb9d   4         4         4       82s

#查看历史
[root@master01 ~]#kubectl rollout history -f deployment-demoapp.yaml 
deployment.apps/deploy-demo 
REVISION  CHANGE-CAUSE
1         <none>    #创建
2         <none>    #更新版本
#回顾,直接指或者用配置文件加载也行,不指定版本就回滚当前版本前一个版本
[root@master01 ~]#kubectl rollout undo -f deployment-demoapp.yaml 
deployment.apps/deploy-demo rolled back
#查看历史(因为1和3相同内容,历史中相同内容的版本号会被取消)
[root@master01 ~]#kubectl rollout history -f deployment-demoapp.yaml 
deployment.apps/deploy-demo 
REVISION  CHANGE-CAUSE
2         <none>
3         <none>

#只要修改yaml下的template都会触发更新
[root@master01 ~]#vim deployment-demoapp.yaml
...
    spec:
      containers:
      - name: demoapp
        image: ikubernetes/demoapp:v1.1
        env:    #追加内容
        - name: PORT
          value: "8080"
[root@master01 ~]#kubectl apply -f deployment-demoapp.yaml
#查看历史
[root@master01 ~]#kubectl rollout history -f deployment-demoapp.yaml
deployment.apps/deploy-demo 
REVISION  CHANGE-CAUSE
2         <none>
3         <none>
4         <none>
[root@master01 ~]#kubectl get -f deployment-demoapp.yaml -o yaml
...
spec:
  ...
  revisionHistoryLimit: 10 #历史保留最近的10个
  strategy:
    rollingUpdate:
      maxSurge: 25% #允许新加后超出的数量不超过,可以设置为个数
      maxUnavailable: 25% #允许减掉后缺少的数量不能超过,可以设置为个数
    type: RollingUpdate #更新策略:滚动更新

#第一步:加1个新的,干掉2个旧的
#第二步:加2个新的,干掉2个旧的
#第三步:加1个新的

配置滚动更新策略

Deployment的滚动更新支持使用如下两个字段来配置相关的策略
  ◼ maxSurge:指定升级期间存在的总Pod对象数量最多可超出期望值的个数,其值可以是0或正整数,也可以是相对于期望值的一个百分比
  ◼ maxUnavailable:升级期间正常可用的Pod副本数(包括新旧版本)最多不能低于期望值的个数,其值可以是0或正数,也可以是相对于期望值的一个百分比,默认值为1

 存在的问题:必须以Pod为原子单位切割规模比例,且无法控制流量路由比例;

  更新策略

Deployment控制器支持两种更新策略
  ◼ 滚动式更新(rolling updates)
    ◆逐批次更新Pod的方式,支持按百分比或具体的数量定义批次规模,默认策略
    ◆触发条件:podTemplate的hash码变动
      ⚫ 仅podTemplate的配置变动才会导致hash码改变
      ⚫ replicas和selector的变更不会导致podTemplate的hash变动
  ◼ 重建式更新(recreate)
    ◆在Pod资源被删除时,使用新的模板定义被足缺失的Pod数量,完成更新
    ◆触发条件:现有Pod被删除

命令:kubectl rollout 
    子命令:
        history    #查看历史
        status  #查看状态
        pause    #暂停
        resume    #继续
        undo     #回滚
        restart #重启

#示例:金丝雀发布(发布一个,暂停在那)
#定义service
[root@master01 ~]#vim demoapp-dev.yaml
apiVersion: v1
kind: Service
metadata:
  name: demoapp
  namespace: dev
spec:
  type: LoadBalancer #有MetalLB了,可以用LB类型,集群外部访问
  selector:
    app: demoapp
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 8080
[root@master01 ~]#kubectl apply -f demoapp-dev.yaml
[root@master01 ~]#kubectl get svc -n dev
NAME      TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
demoapp   LoadBalancer   10.111.169.250   10.0.0.52     80:32002/TCP   30s
#测试,在多个pod之间进行调度
[root@node01 ~]#curl 10.0.0.52

#改下更新策略(要求先加一个,不允许先删除)
[root@master01 ~]#vim deployment-demoapp.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy-demo
  namespace: dev
spec:
  strategy:    #追加
    type: RollingUpdate #更新策略:滚动更新
    rollingUpdate:
      maxSurge: 1 #允许新加后超出的数量不超过,可以设置为个数
      maxUnavailable: 0 #允许减掉后缺少的数量不能超过,可以设置为个数
  replicas: 4
  selector:
    matchLabels:
      app: demoapp
  template:
    metadata:
      labels:
        app: demoapp
    spec:
      containers:
      - name: demoapp
        image: ikubernetes/demoapp:v1.1
        env:
        - name: PORT
          value: "8080"
[root@master01 ~]#kubectl apply -f deployment-demoapp.yaml

#更新template,来触发更新
[root@master01 ~]#vim deployment-demoapp.yaml
...
    spec:
      containers:
      - name: demoapp
        image: ikubernetes/demoapp:v1.2 #修改
#应用来更新,使用rollout pause来暂停(加入一个新版本节点就会暂停)
[root@master01 ~]#kubectl apply -f deployment-demoapp.yaml && kubectl rollout pause -f deployment-demoapp.yaml
#也可以用deployments资源名称
[root@master01 ~]#kubectl apply -f deployment-demoapp.yaml && kubectl rollout pause deployments deploy-demo -n dev
#查看,会有5个pod,1个新版本,4个老版本(此时请求service,会有20%请求打在新版本节点上)
[root@master01 ~]#kubectl get pods -n dev

#继续更新
[root@master01 ~]#kubectl rollout resume -f deployment-demoapp.yaml

kubeproxy改为ipvs

#把kube-proxy配置记录到文件中
[root@master01 ~]#kubectl get cm kube-proxy -o yaml -n kube-system > kube-proxy.yaml
[root@master01 ~]#vim kube-proxy.yaml
...
    ipvs:
    ...
      strictARP: true #如果部署了matellb,这里要改成true
...
    mode: "ipvs"    #修改
#下方metadata把没用的信息删了,保留如下
metadata:
  #删除annotations:
  #删除creationTimestamp:
  labels:
    app: kube-proxy
  name: kube-proxy
  namespace: kube-system
  #删除resourceVersion:
  #删除uid:

#应用,当时kube-proxy的pod没有重启,依然没有生效(要重启,最好一个个重启)
[root@master01 ~]#kubectl apply -f kube-proxy.yaml
#重启daemonset资源类型
[root@master01 ~]#kubectl rollout restart daemonset kube-proxy -n kube-system
#查看kube-proxy的pod,会发现pod被依次更新了
[root@master01 ~]#kubectl get pods -n kube-system

通过Deployment部署wordpress示例

这里假设wordpress是无状态应用(实际wordpress有登录信息)

#编辑Deployment,pvc,service(用于对外访问)
[root@master01 ~]#vim deployment-wordpress.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wordpress-pvc
  namespace: blog
spec:
  accessModes: ["ReadWriteMany"]
  volumeMode: Filesystem
  resources:
    requests:
      storage: 5Gi
  storageClassName: nfs-csi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress
  namespace: blog
spec:
  replicas: 2
  selector:
    matchLabel:
      app: wordpress
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: wordpress
    spec:
      volumes:
      - name: pagestor
        persistentVolumeClaim:
          claimName: wordpress-pvc
      containers:
      - name: wordpress
        image: wordpress:6.4-apache
        volumeMounts:
        - name: pagestor
          mountPath: /var/www/html/
        env:
        - name: WORDPRESS_DB_HOST
          #value: mysql    #服务名就叫mysql,同一个名称空间下
          #mysql这个服务中的mysql-0这个pod(这里只连mysql主节点)
          value: mysql-0.mysql #后缀自动加.blog.svc.cluster.local
        - name: WORDPRESS_DB_USER
          value: wpuser
        - name: WORDPRESS_DB_PASSWORD
          value: wpP@ss
        - name: WORDPRESS_DB_NAME
          value: wpdb
        ports:
        - name: http
          containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: wordpress #不同的资源类型同名没关系
  name: blog
spec:
  type: LoadBalancer
  selector:
    app: wordpress
  ports:
  - name: http
    port: 80
    targetPort: 80

4 StatefulSet和Operator

StatefulSet的编排机制

功能:负责编排有状态(Stateful Application)应用
  ◼ 有状态应用会在其会话中保存客户端的数据,并且有可能会在客户端下一次的请求中使用这些数据
  ◼ 应用上常见的状态类型:会话状态、连接状态、配置状态、集群状态、持久性状态等
  ◼ 大型应用通常具有众多功能模块,这些模块通常会被设计为有状态模块和无状态模块两部分
    ◆业务逻辑模块一般会被设计为无状态,这些模块需要将其状态数据保存在有状态的中间件服务上,如消息队列、数
据库或缓存系统等
    ◆无状态的业务逻辑模块易于横向扩展,有状态的后端则存在不同的难题
    
StatefulSet控制器自Kubernetes v1.9版本才正式引入,为实现有状态应用编排,它依赖于几个特殊设计
  ◼ 各Pod副本分别具有唯一的名称标识,这依赖于一个专用的Headless Service实现
  ◼ 基于Pod管理策略(Pod Management Policy),定义创建、删除及扩缩容等管理操作期间,施加在Pod副本上的操作方式
    ◆OrderedReady:创建或扩容时,顺次完成各Pod副本的创建,且要求只有前一个Pod转为Ready状态后,才能进行后一
个Pod副本的创建;删除或缩容时,逆序、依次完成相关Pod副本的终止
    ◆Parallel:各Pod副本的创建或删除操作不存在顺序方面的要求,可同时进行
  ◼ 各Pod副本存储的状态数据并不相同,因而需要专用且稳定的Volume
    ◆基于podTemplate定义Pod模板
    ◆在podTemplate上使用volumeTemplate为各Pod副本动态置备PersistentVolume
Pod副本的专用名称标识
  ◼ 每个StatefulSet对象强依赖于一个专用的Headless Service对象
  ◼ StatefulSet中的各Pod副本分别拥有唯一的名称标识
    ◆前缀格式为“$(statefulset_name)-$(ordinal)”
    ◆后缀格式为“$(service_name).$(namespace).svc.cluster.local”
  ◼ 各Pod的名称标识可由ClustrDNS直接解析为Pod IP
  
#例:
StatefulSet:编排有状态应用 
    每个Pod的标识: #名称空间为blog
        statefulset:mysql 
        headless service: mysql
            #pod名字.service名字.名称空间.svc.cluster.local 能被独立解析为该pod的ip
            mysql-0.mysql.blog.svc.cluster.local --> Pod IP 

volumeTemplateClaim
  ◼ 在创建Pod副本时绑定至专有的PVC
  ◼ PVC的名称遵循特定的格式,从而能够与StatefulSet控制器对象的Pod副本建立关联关系
  ◼ 支持从静态置备或动态置备的PV中完成绑定
  ◼ 删除Pod(例如缩容),并不会一并删除相关的PVC
  
StatefulSet存在的问题
  ◼ 各有状态、分布式应用在启动、扩容、缩容等运维操作上的步骤存在差异,甚至完全不同,因而StatefulSet只能提供一个基础的编排框架
  ◼ 有状态应用所需要的管理操作,需要由用户自行编写代码完成

StatefulSet 资源规范及示例

 除了标签选择器和Pod模板,StatefulSet必须要配置一个专用的Headless Service,而且还可能要根据需要,编写代码
完成扩容、缩容等功能所依赖的必要操作步骤;

#
apiVersion: apps/v1 # API群组及版本;
kind: StatefulSet # 资源类型特有标识;
metadata:
  name <string> # 资源名称,在作用域中要惟一;
  namespace <string> # 名称空间;DaemonSet资源隶属名称空间级别;
spec:
  minReadySeconds <integer> # Pod就绪后多少秒内任一容器无crash方可视为“就绪”;
  serviceName <string> # 关联的Headless Service的名称,需要事先存在;
  selector <object> # 标签选择器,必须匹配template字段中Pod模板中的标签;
  replicas <integer> # Pod的副本数量;
  template <object> # Pod模板对象;
  volumeClaimTemplates <[]Object> # 卷请求模板;
  podManagementPolicy <string> #Pod管理策略,OrderedReady有主次关系,Parallel无主次,默认为前者;
  revisionHistoryLimit <integer> # 滚动更新历史记录数量,默认为10;
  updateStrategy <Object> # 滚动更新策略
    type <string> # 滚动更新类型,可用值有OnDelete和RollingUpdate;
    rollingUpdate <Object> # 滚动更新参数,专用于RollingUpdate类型;
      maxUnavailable <string> #更新期间可比期望的Pod数量缺少的数量或比例;(有状态只能先减后加)
      partition <integer> #更新策略中的partition号码,默认为0;(大于等于该编号都要被更新,更新为逆序)
#若Pod管理策略是OrderedReady,创建为顺序来,删除和缩容为逆序来

#示例
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: demodb
  namespace: default
spec:
  selector:
    matchLabels:
      app: demodb
  serviceName: "demodb" #依赖的headless service(事先创建好,否则StatefulSet无法运行)
  replicas: 2
  template:
    metadata:
      labels:
        app: demodb
    spec:
      containers:
      - name: demodb-shard
        image: ikubernetes/demodb:v0.1
        ports:
        - containerPort: 9907 #监听端口
          name: db
        env:
        - name: DEMODB_DATADIR #数据保存路径
          value: "/demodb/data" 
        volumeMounts:
        - name: data
          mountPath: /demodb/data
  volumeClaimTemplates: #为每一个pod做pvc模板
  - metadata:
      name: data #每一个pod卷pvc名字为 卷模板名字(这里为data) + pod名字
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: “nfs-csi"
      resources:
        requests:
          storage: 2Gi
        
#实际操作示例
[root@master01 statefulsets]#vim demodb.yaml
apiVersion: v1
kind: Service
metadata:
  name: demodb
  namespace: default
  labels:
    app: demodb
spec:
  clusterIP: None #headless service
  ports:
  - port: 9907
  selector:
    app: demodb
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: demodb
  namespace: default
spec:
  selector:
    matchLabels:
      app: demodb
  serviceName: "demodb"
  replicas: 2
  template:
    metadata:
      labels:
        app: demodb
    spec:
      containers:
      - name: demodb-shard
        image: ikubernetes/demodb:v0.1
        ports:
        - containerPort: 9907
          name: db
        env:
        - name: DEMODB_DATADIR
          value: "/demodb/data"
        livenessProbe:
          initialDelaySeconds: 2
          periodSeconds: 10
          httpGet:
            path: /status
            port: db
        readinessProbe:
          initialDelaySeconds: 15
          periodSeconds: 30
          httpGet:
            path: /status?level=full
            port: db 
        volumeMounts:
        - name: data
          mountPath: /demodb/data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "nfs-csi"
      resources:
        requests:
          storage: 2Gi
[root@master01 statefulsets]#kubectl apply -f demodb.yaml
#pod会一个一个创建
[root@master01 statefulsets]#kubectl get pods
NAME                       READY   STATUS    RESTARTS     AGE
demodb-0                   1/1     Running   0            112s
demodb-1                   1/1     Running   0            49s
#查看pvc,也是一个一个创建(kubectl get pv查看pv也一样)
[root@master01 ~]#kubectl get pvc
NAME                         STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS       VOLUMEATTRIBUTESCLASS   AGE
data-demodb-0                Bound    pvc-4d9b41a1-46ee-4af5-b3ec-44c256c74815   2Gi        RWO            nfs-csi            <unset>                 79s
data-demodb-1                Bound    pvc-f8c6b026-bcdb-4402-8a3c-3af40ee347fd   2Gi        RWO            nfs-csi            <unset>                 16s

#扩容
[root@master01 statefulsets]#vim demodb.yaml
...
spec:
  selector:
    matchLabels:
      app: demodb
  serviceName: "demodb"
  replicas: 4    #修改
...
[root@master01 statefulsets]#kubectl apply -f demodb.yaml
#当前是默认Pod管理策略,OrderedReady,创建是顺序,删除或缩容是逆序进行的
#先创建demodb-2,创建pvc:data-demodb-2;再创建demodb-3,创建pvc:data-demodb-3
# -w持续监控资源变化
[root@master01 statefulsets]#kubectl get pods -w

#更新(先更新一个,可以的话,剩下的全部更新)
[root@master01 statefulsets]#vim demodb.yaml
...
spec:
  selector:
    matchLabels:
      app: demodb
  serviceName: "demodb"
  replicas: 4
  updateStrategy: #追加更新策略
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1 #一次更新一个
      partition: 3 #从编号3开始,也就是第4个
  template:
...
#应用更新策略,pod没有变化不会更新
[root@master01 statefulsets]#kubectl apply -f demodb.yaml
#修改更新内容
[root@master01 statefulsets]#vim demodb.yaml
...
  template:
    metadata:
      labels:
        app: demodb
    spec:
      containers:
      - name: demodb-shard
        image: ikubernetes/demodb:v0.2 #修改版本
#进行更新(因上面更新策略只更新最后一个),监视下
[root@master01 statefulsets]#kubectl apply -f demodb.yaml && kubectl get pods -w

#没问题,就把所有pod都更新,修改更新策略中的编号为0
[root@master01 statefulsets]#vim demodb.yaml
...
spec:
  selector:
    matchLabels:
      app: demodb
  serviceName: "demodb"
  replicas: 4
  updateStrategy: #追加更新策略
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1 #一次更新一个
      partition: 0 #从编号0开始,也就是全部更新
  template:
...
#进行更新(3已经更新过了,不会再更新,从2开始依次往前更新)
[root@master01 statefulsets]#kubectl apply -f demodb.yaml

 

posted @ 2024-11-28 21:13  战斗小人  阅读(93)  评论(0编辑  收藏  举报