k8s控制器

一、Pod控制器及其功用

Pod控制器,又称之为工作负载(workload),是用于实现管理pod的中间层,确保pod资源符合预期的状态 ,确保以一定的数量和副本可以运行,pod的资源出现故障时,会尝试进行重启,当根据重启策略无效,则会重新新建pod的资源。

二、pod控制器有多种类型

1、ReplicaSet: 代用户创建指定数量的pod副本,确保pod副本数量符合预期状态,并且支持滚动式自动扩容和缩容功能。

ReplicaSet主要三个组件组成:

(1)用户期望的pod副本数量

(2)标签选择器,判断哪个pod归自己管理

(3)当现存的pod数量不足,会根据pod资源模板进行新建

帮助用户管理无状态的pod资源,精确反应用户定义的目标数量,但是RelicaSet不是直接使用的控制器,而是借助于使用Deployment。

2、Deployment:工作在ReplicaSet之上,用于管理无状态应用,目前来说最好的控制器。支持滚动更新和回滚功能,还提供声明式配置。
ReplicaSet 与Deployment 这两个资源对象逐步替换之前RC的作用。

3、DaemonSet:用于确保集群中的每一个节点只运行特定的pod副本,通常用于实现系统级后台任务。比如ELK服务

特性:服务是无状态的

服务必须是守护进程

4、StatefulSet:管理有状态应用

5、Job:只要完成就立即退出,不需要重启或重建

6、Cronjob:周期性任务控制,不需要持续后台运行

三、Pod与控制器之间的关系

Pod和控制器之间的关系是通过Label和Label Selector建立的。

每个Pod都可以定义一组Label,而控制器则使用Label Selector来选择和管理这些Pod。

例如,一个Deployment可以定义一个Label Selector来选择具有特定Label的Pod,并管理这些Pod的副本数量、滚动更新等。

四、具体实验

4.1、Deployment

应用场景:web服务

部署无状态应用

管理Pod和ReplicaSet

具有上线部署、副本设定、滚动升级、回滚等功能

提供声明式更新,例如只更新一个新的image

示例:

vim nginx-deployment.yaml

bash 复制代码
apiVersion: apps/v1  # 指定使用的Kubernetes API版本
kind: Deployment     # 声明这是一个Deployment资源
metadata:
  name: nginx-deployment  # Deployment的名称
  labels:               # 为Deployment添加标签,方便后续筛选和管理
    app: nginx-bq1       # 标签的键为app,值为nginx-bq1
spec:
  replicas: 3           # 指定要运行的Pod副本数量
  selector:             # 定义Pod选择器,用于匹配哪些Pod应该被该Deployment管理
    matchLabels:
      app: nginx-bq1    # 选择器使用的标签键和值,与Pod模板中的标签一致
  template:             # 定义Pod模板,用于创建Pod
    metadata:
      labels:
        app: nginx-bq1  # Pod的标签,与选择器中的标签一致,确保Deployment能够正确管理这些Pod
    spec:
      containers:       # Pod中容器的列表
      - name: nginx     # 容器的名称
        image: nginx:1.15.4  # 容器使用的镜像,这里使用的是nginx的1.15.4版本
        ports:
        - containerPort: 80  # 容器暴露的端口,这里暴露的是80端口

kubectl create -f nginx-deployment.yaml

kubectl get pods,deploy,rs

查看控制器配置

kubectl editdeployment/nginx-deployment

查看历史版本

kubectl rollout history deployment/nginx-deployment

4.2、SatefulSet

部署有状态应用

  • 常见的应用场景:数据库(主从复制)

需要三个条件

1、启动顺序

2、pod有独立的存储

3、IP地址有独立的变化

StatefulSet 运行一组 Pod,并为每个 Pod 保留一个稳定的标识。 这可用于管理需要持久化存储(2),稳定唯一网络标识(3)的应用。↓

StatefulSet 为它们的每个 Pod 维护了一个有粘性的 ID。这些 Pod 是基于相同的规约来创建的, 但是不能相互替换:无论怎么调度,每个 Pod 都有一个永久不变的 ID

尽管 StatefulSet 中的单个 Pod 仍可能出现故障, 但持久的 Pod 标识符使得将现有卷与替换已失败 Pod 的新 Pod 相匹配变得更加容易。在这里可以使用存储卷为工作负载提供持久存储

  1. 稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现
  2. 稳定的网络标志,即Pod重新调度后其PodName和HostName不变,基于Headless Service(无头模式,即没有Cluster IP的Service)来实现有序部署,
  3. 有序扩展,即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次进行(即从0到N-1,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态),基于init containers来实现有序收缩,有序删除(即从N-1到0)

总结一下

  • 给定 Pod 的存储必须由pv provisioner 基于所请求的 storage class 来制备,或者由管理员预先制备。
  • 删除或者扩缩 StatefulSet 并不会删除它关联的存储卷。 这样做是为了保证数据安全,它通常比自动清除 StatefulSet 所有相关的资源更有价值。
  • StatefulSet 当前需要无头来负责 Pod 的网络标识。你需要负责创建此服务。
  • 当删除一个 StatefulSet 时,该 StatefulSet 不提供任何终止 Pod 的保证。 为了实现 StatefulSet 中的 Pod 可以有序且体面地终止,可以在删除之前将 StatefulSet 缩容到 0。
  • 在默认pod管理策略(OrderedReady) 时使用滚动更新, 可能进入需要人工干预才能修复的损坏状态。

查看statefulset的定义:

bash 复制代码
kubectl explain statefulset

示例

bash 复制代码
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx # has to match .spec.template.metadata.labels
  serviceName: "nginx"
  replicas: 3 # by default is 1
  template:
    metadata:
      labels:
        app: nginx # has to match .spec.selector.matchLabels
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        image: k8s.gcr.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "my-storage-class"
      resources:
        requests:
          storage: 1Gi

创建无头模式

三个组件

从上面的应用场景可以发现,StatefulSet由以下几个部分组成:

  • Headless Service(无头服务):用于为Pod资源标识符生成可解析的DNS记录。
  • volumeClaimTemplates(存储卷申请模板):基于静态或动态PV供给方式为Pod资源提供专有的固定存储。
  • StatefulSet:用于管控Pod资源。

4.2.1、为什么要有headless

在deployment中,每一个pod是没有名称,是随机字符串,是无序的。而statefulset中是要求有序的,每一个pod的名称必须是固定的。当节点挂了,重建之后的标识符是不变的,每一个节点的节点名称是不能改变的。pod名称是作为pod识别的唯一标识符,必须保证其标识符的稳定并且唯一。

为了实现标识符的稳定,这时候就需要一个headless service 解析直达到pod,还需要给pod配置一个唯一的名称。

4.2.2、为什么要有volumeClainTemplate?

大部分有状态副本集都会用到持久存储,比如分布式系统来说,由于数据是不一样的,每个节点都需要自己专用的存储节点。而在 deployment中pod模板中创建的存储卷是一个共享的存储卷,多个pod使用同一个存储卷,而statefulset定义中的每一个pod都不能使用同一个存储卷,由此基于pod模板创建pod是不适应的,这就需要引入volumeClainTemplate,当在使用statefulset创建pod时,会自动生成一个PVC,从而请求绑定一个PV,从而有自己专用的存储卷。

服务发现:就是应用服务之间相互定位的过程。

应用场景:

动态性强:Pod会飘到别的node节点

更新发布频繁:互联网思维小步快跑,先实现再优化,老板永远是先上线再慢慢优化,先把idea变成产品挣到钱然后再慢慢一点一点优化

支持自动伸缩:一来大促,肯定是要扩容多个副本

K8S里服务发现的方式---DNS,使K8S集群能够自动关联Service资源的"名称"和"CLUSTER-IP",从而达到服务被集群自动发现的目的。

4.2.3、 实现K8S里DNS功能的插件

  • skyDNS:Kubernetes 1.3之前的版本
  • kubeDNS:Kubernetes 1.3至Kubernetes 1.11
  • CoreDNS:Kubernetes 1.11开始至今

安装CoreDNS,仅二进制部署环境需要安装CoreDNS((按照我之前文章部署默认会有))

方法一:

下载链接:

官方网站https://github.com/kubernetes/kubernetes/blob/master/cluster/addons/dns/coredns/coredns.yaml.base

vim transforms2sed.sed

s/DNS__SERVER/10.0.0.2/g

s/DNS__DOMAIN/cluster.local/g

s/DNS__MEMORY__LIMIT/170Mi/g

s/MACHINE_GENERATED_WARNING/Warning: This is a file generated from the base underscore template file: coredns.yaml.base/g

sed -f transforms2sed.sed coredns.yaml.base > coredns.yaml

方法二:上传 coredns.yaml 文件

kubectl create -f coredns.yaml

kubectl get pods -n kube-system

kubectl get pod -n kube-system

查看statefulset的定义:

kubectl explain statefulset

kubectl explain statefulset.spec

部署

定义一个dns-pod.yaml

vim dns-pod.yaml

kubectl apply -f dns-pod.yaml

bash 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: dns-busybox
spec:
  containers:
  - name: busybox
    image: busybox:1.28.4
    args:
    - /bin/sh
    - -c
    - sleep 36000
  restartPolicy: Never

进入容器测试CoreDNS解析

kubectl exec -it dns-busybox sh

nslookup nginx

接着部署:

vim sts-svc.yaml

bash 复制代码
apiVersion: v1
kind: Service
metadata:
  name: sts-svc
  namespace: default
  labels:
    app: sts-svc
spec:
  type: ClusterIP
  clusterIP: None
  ports:
  - port: 80
    name: http
    protocol: TCP
    targetPort: 80
  selector:
    app: myapp-sts

kubectl apply -f sts-svc.yaml

kubectl get svc

定义资源清单

vim myapp-sts.yaml

bash 复制代码
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: myapp-sts
spec:
  selector:
    matchLabels:
      app: myapp-sts # 必须匹配 .spec.template.metadata.labels
  serviceName: "sts-svc"
  replicas: 3 # 默认值是 1
  template:
    metadata:
      labels:
        app: myapp-sts # 必须匹配 .spec.selector.matchLabels
    spec:
      containers:
      - name: myapp
        image: soscscs/myapp:v1
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "nfs-client-storageclass"
      resources:
        requests:
          storage: 1Gi

kubectl apply -f myapp-sts.yaml

olumeClaimTemplates 向前面创建的PV进行了请求大小为1Gi 的专用存储卷。

创建pv

stor01节点(stor01节点即nfs机器操作

mkdir -p /data/volumes/v{1,2,3,4,5}

使用 mkdir 命令在 /data/volumes/ 目录下创建五个新的目录(v1 到 v5)。这些目录将用作 NFS 共享的存储卷

配置 NFS 共享

vim /etc/exports

/data/volumes/v1 192.168.88.0/24(rw,no_root_squash)

/data/volumes/v2 192.168.88.0/24(rw,no_root_squash)

/data/volumes/v3 192.168.88.0/24(rw,no_root_squash)

/data/volumes/v4 192.168.88.0/24(rw,no_root_squash)

/data/volumes/v5 192.168.88.0/24(rw,no_root_squash)

编辑 /etc/exports 文件,添加上述创建的目录作为共享资源。每个目录都配置了 IP 地址范围 192.168.88.0/24,表示只有该子网内的客户端可以访问这些共享。rw 表示允许读写权限,no_root_squash 允许 root 用户以 root 权限访问共享。

重启 NFS 服务

systemctl restart rpcbind

systemctl restart nfs

重新导出共享

exportfs -arv

查看共享列表

showmount -e

定义PV的yaml文件

pv-demo.yaml 文件包含了五个 PersistentVolume (PV) 的定义,这些 PV 将使用 NFS 服务器 stor01 上的共享目录。每个 PV 都有一个指定的容量和访问模式。

vim pv-demo.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv001
  labels:
    name: pv001
spec:
  nfs:
    path: /data/volumes/v1
    server: stor01
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 1Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv002
  labels:
    name: pv002
spec:
  nfs:
    path: /data/volumes/v2
    server: stor01
  accessModes: ["ReadWriteOnce"]
  capacity:
    storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv003
  labels:
    name: pv003
spec:
  nfs:
    path: /data/volumes/v3
    server: stor01
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv004
  labels:
    name: pv004
spec:
  nfs:
    path: /data/volumes/v4
    server: stor01 
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 4Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv005
  labels:
    name: pv005
spec:
  nfs:
    path: /data/volumes/v5
    server: stor01 #可以是nfs机器的ip地址也可以是主机名,此处主机名,验证下
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 5Gi

kubectl apply -f pv-demo.yaml

kubectl get pv

使用 kubectl get pv 命令查看集群中的 PV 列表,确认这些 PV 是否已成功创建。

这些 PV 定义使得 Kubernetes 集群能够使用 NFS 共享作为持久化存储。在 StatefulSet 或其他需要持久化存储的工作负载中,可以通过 PersistentVolumeClaim (PVC) 来请求这些 PV。这样,即使 Pod 在集群中重新调度,它们也能保持对这些共享存储的访问,这对于有状态的应用(如数据库)来说非常重要。

五、DaemonSet(工作负载)控制器

DaemonSet 确保全部(或者一些)Node 上运行一个 Pod 的副本。当有 Node 加入集群时,也会为他们新增一个 Pod 。当有 Node 从集群移除时,这些 Pod 也会被回收。

删除 DaemonSet 将会删除它创建的所有 Pod。

使用 DaemonSet 的一些典型用法:

  • 运行集群存储 daemon,例如在每个 Node 上运行 glusterd、ceph。
  • 在每个 Node 上运行日志收集 daemon,例如fluentd、logstash。
  • 在每个 Node 上运行监控 daemon,例如 Prometheus Node Exporter、collectd、Datadog 代理、New Relic 代理,或 Ganglia gmond。

应用场景:Agent

DaemonSet 是 Kubernetes 中的一种工作负载控制器,它确保在集群中的所有(或指定的)节点上运行一个 Pod 的副本。这使得 DaemonSet 成为运行集群级服务的理想选择,如日志收集、监控代理、存储守护进程等。

官方案例(监控)

https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/

关键特性

节点覆盖:DaemonSet 确保在集群中的每个节点上都有一个 Pod 副本运行,或者根据需要在特定节点上运行。

自动管理:当新节点加入集群时,DaemonSet 会自动在这些节点上创建 Pod。当节点被移除时,相应的 Pod 也会被清理。

删除行为:删除 DaemonSet 会删除它创建的所有 Pod,这有助于维护集群的整洁。
典型用例

集群存储守护进程:在每个节点上运行如 GlusterFS 的 glusterd 或 Ceph 的守护进程,以提供分布式存储服务。

日志收集:部署如 Fluentd、Logstash 等日志收集代理,以便在每个节点上收集日志并将其发送到中央日志存储。

监控代理:在每个节点上运行监控代理,如 Prometheus 的 Node Exporter、Collectd、Datadog 代理、New Relic 代理或 Ganglia 的 gmond,以收集节点的监控数据。
应用场景

Agent:DaemonSet 常用于部署代理或守护进程,这些代理或守护进程作为集群的"代理",执行特定的任务,如数据收集、服务代理等。

vim ds.yaml

bash 复制代码
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: nginx-daemonset
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14
        ports:
        - containerPort: 80

kubectl apply -f ds.yaml

我们看到DaemonSet会在每个node节点都创建一个Pod

kubectl get pod -owide

六、Job

Job分为普通任务(Job)和定时任务(CronJob)

常用于运行那些仅需要执行一次的任务

应用场景:数据库迁移、批处理脚本、kube-bench扫描、离线数据处理,视频解码等业务

https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/

应用场景

数据库迁移:执行数据库迁移脚本,直到迁移成功。

批处理脚本:运行批处理作业,如数据备份、日志清理等。

安全扫描:使用 kube-bench 等工具对集群进行安全扫描。

离线数据处理:处理不需要持续运行的大量数据,如数据分析、报告生成等。

视频解码:执行视频转码等耗时的一次性任务

vim job.yaml

bash 复制代码
apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
  backoffLimit: 4

参数解释

.spec.template.spec.restartPolicy该属性拥有三个候选值:OnFailure,Never和Always。

默认值为Always。它主要用于描述Pod内容器的重启策略。

在Job中只能将此属性设置为OnFailure或Never,否则Job将不间断运行。

.spec.backoffLimit用于设置job失败后进行重试的次数,默认值为6。

默认情况下,除非Pod失败或容器异常退出,Job任务将不间断的重试,此时Job遵循 .spec.backoffLimit上述说明。一旦.spec.backoffLimit达到,作业将被标记为失败。

  • 定义了一个名为 pi 的 Job,它将运行一个 Perl 容器来计算圆周率(π)。
  • .spec.template.spec 定义了 Pod 的规格,包括容器的名称、镜像和执行的命令。
  • restartPolicy: Never 表示 Pod 失败后不会重启。
  • backoffLimit: 4 表示 Job 在失败后最多重试 4 次。

注意:在所有node节点下载perl镜像,因为镜像比较大,所以建议提前下载好

docker pull perl

kubectl apply -f job.yaml

kubectl get pods

pi-bqtf7 0/1 Completed 0 41s

结果输出到控制台

kubectl logs pi-bqtf7

3.14159265......

清除job资源

kubectl delete -f job.yaml

可以设置

设置 Job 的重试次数 (job-limit.yaml)

backoffLimit

七、CronJob

  • 周期性任务,像Linux的Crontab一样。
  • 周期性任务
  • 应用场景:通知,备份

CronJob 是 Kubernetes 中处理周期性任务的强大工具,它使得自动化任务的创建和管理变得简单。通过定义 Cron 表达式,您可以轻松地安排任务在特定的时间执行,从而实现自动化运维。

官方文档链接

https://kubernetes.io/docs/tasks/job/automated-tasks-with-cron-jobs/
应用场景
通知系统:定期发送系统状态通知、日志报告或其他类型的定期通知。

数据备份:执行数据库备份、文件备份等周期性数据保护任务。

定期清理:清理日志文件、临时文件或其他不再需要的数据。

监控和报告:定期执行监控任务并生成报告。

如何在 Kubernetes 中创建一个 CronJob,该 CronJob 每分钟执行一次打印 "Hello" 的任务。

示例:每分钟打印hello

bash 复制代码
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox
            imagePullPolicy: IfNotPresent
            args:
            - /bin/sh
            - -c
            - date; echo Hello from the Kubernetes cluster
          restartPolicy: OnFailure

kubectl apply -f cronjob.yaml

kubectl get cronjob

使用 kubectl get cronjob 命令查看 CronJob 的状态,包括调度时间、活动状态和最近一次执行的时间。

查看执行的 Pod

使用 kubectl get pods 命令查看由 CronJob 创建的 Pod。这些 Pod 通常会在任务完成后标记为 Completed

cronjob其它可用参数的配置

spec:
concurrencyPolicy: Allow #要保留的失败的完成作业数(默认为1)
schedule: '*/1 * * * *' #作业时间表。在此示例中,作业将每分钟运行一次
startingDeadlineSeconds: 15 #pod必须在规定时间后的15秒内开始执行,若超过该时间未执行,则任务将不运行,且标记失败
successfulJobsHistoryLimit: 3 #要保留的成功完成的作业数(默认为3)
terminationGracePeriodSeconds: 30 #job存活时间 默认不设置为永久
jobTemplate: #作业模板。这类似于工作示例

八、 其他补充

headless 服务(Headless Service)是一种没有 ClusterIP 的 Kubernetes 服务,通常用于 StatefulSet 中,以便让每个 Pod 都能有自己的 DNS 名称,从而实现直接的 Pod 到 Pod 通信。

  1. 服务创建: 当你创建一个 Headless 服务时,Kubernetes 会为该服务分配一个 DNS 记录,格式通常是:

    <pod-name>.<service-name>.<namespace>.svc.cluster.local

    例如,对于 example-headless-service 服务,位于 default 命名空间下,它的完整 DNS 名称会是:

    example-headless-service.default.svc.cluster.local 
    
  2. Pod 创建: 假设你使用 StatefulSet 创建了 3 个副本,Kubernetes 会为每个副本分配一个特定的 DNS 名称。例如:

    • pod-0.example-headless-service.default.svc.cluster.local
    • pod-1.example-headless-service.default.svc.cluster.local
    • pod-2.example-headless-service.default.svc.cluster.local
  3. 访问方式 : 通过这些 DNS 名称,其他 Pod 或服务可以直接访问每个特定的 Pod。例如,pod-0.example-headless-service.default.svc.cluster.local 可以直接与 pod-0 通信,而不需要通过负载均衡。

为什么是这样设计的?

这个设计使得 StatefulSet 中的每个 Pod 都有一个稳定且可预测的 DNS 名称,从而可以通过 DNS 精确定位每个 Pod。这对于有状态服务(如数据库集群、分布式存储等)来说非常重要,因为它们需要能够准确地识别和通信每个节点。

在 StatefulSet 中,DNS 名称不仅仅是为了访问服务本身,而是为了访问每个 Pod。这样,应用程序能够通过 DNS 查询到每个 Pod 的具体位置,而无需依赖服务负载均衡器。这种方式提供了更高的灵活性,适合有状态应用程序的需求。

相关推荐
IT-民工211101 小时前
K8s部署MySQL
mysql·容器·kubernetes
rocksun3 小时前
基于Kubernetes优先的方法扩展数据平台
kubernetes
alden_ygq3 小时前
K8S OOM killer机制
云原生·容器·kubernetes
SRExianxian3 小时前
k8s中设置annotation的方法总结
云原生·容器·kubernetes
网络笨猪4 小时前
第九篇:k8s 通过helm发布应用
云原生·容器·kubernetes
怡雪~4 小时前
k8s的污点与容忍度
云原生·容器·kubernetes
勇-子4 小时前
k8s Service的基础使用
云原生·容器·kubernetes
喝醉酒的小白6 小时前
K8s驱逐阈值调整
java·算法·kubernetes
liuzhenghua666 小时前
k8s调度策略
容器·kubernetes