从Docker到Kubernetes——Kubernetes设计解读之ReplicationController、Service

2023-11-20

Kubernetes的设计解读

replication controller 设计解读

k8s第二个重要的概念就是replication controller,它决定了一个pod有多少同时运行的副本,并保证这些副本的期望状态与当前状态一致

replication controller在设计上依然体现出了“旁路控制”的思想,在k8s中并没有像Cloud Foundry那样设置一个专门的健康检查组件,而是为每个pod“外挂”了一个控制器进程,从而避免健康检查组件成为性能的瓶颈;即使这个控制器进程失效,容器仍然可以正常运行。

根据上一章的实践,我们大概知道了pod有这么几个状态:

  • Pending:pod已经被创建,但是pod中有一个或多个容器未启动,可能是因为下载镜像的网络传输慢或者pod的调度时间,或者pod没有找到绑定的工作节点等等。
  • Running:pod被绑定到工作节点上且pod中的所有容器均被创建。最重要的是pod中至少有一个容器处于运行、启动或者重启状态。
  • Succeeded:专指pod所有容器均成功正常退出,且不会发生重启。
  • Failed:pod内所有容器均退出且至少有一个容器退出发生错误。
  • Unknown:因为未知原因无法获取pod状态。

pod的状态转移与pod的重启策略息息相关,由于其复杂性在此不表。只需记住,replication controller 会识别pod某状态而实现重启策略。

replication controller的描述文件

与pod对象类似,k8s也可以使用一份JSON格式的资源配置文件来定义一个replication controller对象。主要由3方面组成:

  • 一个用于创建pod的pod template
  • 一个期望副本数(replicas)
  • 一个用于选择被控制pod集合的label selector
apiVersion: v1
kind: ReplicationController  //ReplicationController类型
metadata:
   name: nginx               //pod名字
spec:
   replicas: 2               //2个副本
   selector:                 
     app: nginx              //通过这个标签找到生成的pod
   template:                 //定义pod模板,在这里不需要定义pod名字,就算定义创建的时候也不会采用这个名字而是.metadata.generateName+5位随机数。
     metadata:
      labels:                //定义标签
       app: nginx            //key:v   这里必须和selector中定义的KV一样
     spec:
      containers:            //rc 的容器重启策略必须是Always(总是重启),这样才能保证容器的副本数正确
       - image: nginx
         name: nginx
         ports:  
         - containerPort: 80

replication controller 对pod数量和健康状况的监控是通过副本选择器(replica selector或者label selector的一种)来实现的。replica selector 定义了replication controller和它控制的pod之间的松耦合关系。这与pod和Docker容器之间强耦合的关系相反。因为可以修改pod的labels将一个pod从replication controller的控制中集中移除。

replication controller的典型场景:

  • 重调度:不管是想运行多少个pod副本,replication controller都能保证指定数目的pod正常运行。一旦当前的宿主机节点异常崩溃或者pod停止运行,k8s就会进行相应的重调度。
  • 弹性伸缩:顾名思义,修改relicas来达到pod数目的弹性伸缩。
  • 滚动更新(灰度发布):replication controller 被设计成为通过逐个替换pod的方式来进行副本的增删操作。实现起来很简单:创建新的replication controller ,并按时间逐步将新的replication controller副本数+1,旧的replication controller副本数-1即可。

replication controller 使用示例

{
        "apiVersion":"v1",
        "kind":"ReplicationController",
        "metadata":{
            "name":"redis-controller",
            "labels":{
                        "name":"redis"
                }
        },
        "spec":{
                "replicas":1,
                "selector":{
                        "name":"redis"
                },
                "template":{
                        "metadata":{
                                "labels":{
                                        "name":"redis"
                                }
                        },
                        "spec":{
                                "containers":[{
                                                "name":"redis",
                                                "image":"redis:latest",
                                                "ports":[{
                                                                "containerPort":6379,
                                                                "hostPort":6380
                                                        }]
                                        }]
                        }
                }
        }
}

使用资源配置文件配合kubectl create命令创建一个replication controller对象:

[root@master ~]# kubectl create -f redis-controller.json 
replicationcontroller/redis-controller created

使用kubectl get查询replication controller的基本信息并查看这个controller自动创建的pod。

[root@master ~]# kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
podtest                  1/1     Running   0          11m
redis-controller-vsmwz   1/1     Running   0          2m7s
[root@master ~]# kubectl get replicationController -o wide
NAME               DESIRED   CURRENT   READY   AGE     CONTAINERS   IMAGES         SELECTOR
redis-controller   1         1         1       2m24s   redis        redis:latest   name=redis

这时,我们要验证replication controller是不是真的有效。使用删除pod命令:

[root@master ~]# kubectl delete pod redis-controller-vsmwz
pod "redis-controller-vsmwz" deleted
[root@master ~]# kubectl delete pod podtest
pod "podtest" deleted
[root@master ~]# kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
redis-controller-2pzsh   1/1     Running   0          21s

我们发现,删除了之后,又创建了一个新的pod。

删除副本控制器的命令:

[root@master ~]# kubectl delete replicationController redis-controller
replicationcontroller "redis-controller" deleted
[root@master ~]# kubectl get pod
NAME                     READY   STATUS        RESTARTS   AGE
redis-controller-2pzsh   0/1     Terminating   0          99s
[root@master ~]# kubectl get replicationController
No resources found in default namespace.

service的设计解读

其实service这个名字很容易引起误解,我认为应该定义成为proxy或者router更为贴切。

service主要由一个IP地址和一个label selector组成。在创建之初,每个service便被分配了一个独一无二的IP地址,该IP地址与service的生命周期相同并且不可以更改(pod的IP会随pod的生命周期的产生及消亡)。

定义一个service
和pod一样,service也是一个k8s REST对象,客户端可以通过APIServer发送一个POST请求来创建一个新的service实例。假设有一个pod集,该pod集中所有pod均被贴上labels:{“app”:“MyAPP”},且所有容器均对外暴露TCP9376端口,那么一个service的JSON文件可以这么写:

{
	"kind":"Service",
	"apiVersion":"v1",
	"metadata":{
	"name":"my-service",
	"labels":{
		"enviroment":"testing"
		}
	},
	"spec":{
		"selector":{
			"app":"MyAPP"
			},
			"ports":[{
				"protocol":"TCP",
				"port":80,
				"targetPort":9376
				}]
	}
}

该service能将外部流量转发到所有label匹配{“app”:“MyAPP”}的pod的TCP9376端口上,每个service会由系统分配一个虚拟IP地址作为service的入口IP地址(cluster IP),然后监听上述文件中的指定端口(上述的是80端口)。
而当名为"my-service"的service对象被创建后,系统就会随之创建一个同样名为my-service的Endpoints对象,该对象即保存了所有匹配label selector后端pod的IP地址和端口。

使用kubectl get services即可获取到service的IP地址信息。

NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   23h

使用service来代理遗留系统
与replication controller不同,service的selector其实是可选项。**这样做的目的是能用service来代理一些非pod或者得不到的label资源。**比如:

  • 访问k8s集群外部的一个数据库。
  • 访问其他namespace或者其他集群的service。
  • 任何其他类型的外部遗留系统。

所以定义一个没有selector属性的service对象,如下:

{
	"kind":"Service",
	"apiVersion":"v1",
	"metadata":{
		"name":"my-service"
	},
	"spec":{
			"ports":[{
				"protocol":"TCP",
				"port":80,
				"targetPort":1111
				}]
	}
}

由于该service没有selector,所以系统不会自动创建一个EndPoints对象,此时可以通过自定义一个EndPoints对象,将上述service对象映射到一个或者多个后端

{
	"kind":"Service",
	"apiVersion":"v1",
	"metadata":{
		"name":"my-service"
	},
	"subsets":[{
		"address":[{
			"IP":"1.2.3.4"
			}],
		"ports":[{
			"port":1111
		}]
	}]
}

service的使用示例

首先定义两个pod,都分别运行nginx容器。第一个80:8088,第二个80:8089(即容器内的80端口映射到宿主机的8088和8089)。
第一个:

{
	"kind":"Pod",
	"apiVersion":"v1",
	"metadata":{
	"name":"nignx-a",
	"labels":{
		"name":"service-nginx"
		}
	},
	"spec":{
		"containers":[{
			"name":"nginx",
			"image":"nginx:latest",
			"ports":[{
				"containerPort":80,
				"hostPort":8088
				}]
		}]
	}
}

第二个:

{
	"kind":"Pod",
	"apiVersion":"v1",
	"metadata":{
	"name":"nignx-b",
	"labels":{
		"name":"service-nginx"
		}
	},
	"spec":{
		"containers":[{
			"name":"nginx",
			"image":"nginx:latest",
			"ports":[{
				"containerPort":80,
				"hostPort":8089
				}]
		}]
	}
}

创建以上两个pod:

[root@master ~]# vi nginx-a.json
[root@master ~]# kubectl create -f nginx-a.json 
pod/nignx-a created
[root@master ~]# vi nginx-b.json
[root@master ~]# kubectl create -f nginx-b.json 
pod/nignx-b created

配置文件中,我们同时把pod的labels设置成为了name=service-nginx。比如用下面的查询语句查询label。

[root@master ~]# kubectl get pod -l name=service-nginx
NAME      READY   STATUS    RESTARTS   AGE
nignx-a   1/1     Running   0          2m34s
nignx-b   1/1     Running   0          2m10s

接下来定义一个service:

{
	"kind":"Service",
	"apiVersion":"v1",
	"metadata":{
		"name":"nginx-service-clusterip",
		"labels":{
			"name":"nginx-service-clusterip"
		}
	},
	"spec":{
		"selector":{
			"name":"service-nginx"
			},
		"ports":[{
			"port":80
			}]
		}
}

selector字段表明的是service选择的labels是{“name”:“service-nginx”}。
创建service并查看:

[root@master ~]# kubectl create -f NginxService.json 
service/nginxservice created
[root@master ~]# kubectl get service
NAME                      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                         AGE
kubernetes                ClusterIP   10.96.0.1       <none>        443/TCP                         25h
nginx-service-clusterip   ClusterIP   10.98.112.219   <none>        80/TCP                          56s

我们可以看到 service nginxservice创建且分配到的入口是10.103.124.74,该Ip与8000端口组成该service的入口地址(cluster IP)。
可以再来查看映射关系:

[root@master ~]# kubectl get pod -o wide
NAME                READY   STATUS    RESTARTS   AGE    IP            NODE     NOMINATED NODE   READINESS GATES
nignx-a             1/1     Running   0          133m   10.244.0.14   master   <none>           <none>
nignx-b             1/1     Running   0          133m   10.244.0.15   master   <none>           <none>
sshd-test-service   1/1     Running   0          11m    10.244.0.18   master   <none>           <none>
[root@master ~]# kubectl describe service nginx-service-clusterip
Name:              nginx-service-clusterip
Namespace:         default
Labels:            name=nginxservice-clusterip
Annotations:       <none>
Selector:          name=service-nginx
Type:              ClusterIP
IP:                10.98.112.219
Port:              <unset>  80/TCP
TargetPort:        80/TCP
Endpoints:         10.244.0.14:80,10.244.0.15:80
Session Affinity:  None
Events:            <none>

可以看到EndPoints分别对应了两个pod的IP+端口。
Service 通过 Cluster 内部的 IP 对外提供服务,只有 Cluster 内的节点和 Pod 可访问,这是默认的 Service 类型,前面实验中的 Service 都是 ClusterIP。

再定义一个sshd客户端pod。首先先搜索images:docker search sshd
在这里插入图片描述
这里我们决定下载bmoorman/sshd这个镜像。
所以写sshd-pod.json如下:

{
	"kind":"Pod",
	"apiVersion":"v1",
	"metadata":{
		"name":"sshd-test-service",
		"labels":{
			"user":"service-nginx",
			"name":"client-sshd"
		}
	},
	"spec":{
		"containers":[{
			"name":"client-container",
			"image":"bmoorman/sshd:latest",
			"ports":[{
				"containerPort":22,
				"hostPort":1314
			}]
		}]
	}
}

创建完成后进入pod:

[root@master ~]# kubectl create -f sshd-pod.json 
[root@master ~]# kubectl get pod
NAME                READY   STATUS    RESTARTS   AGE
nignx-a             1/1     Running   0          103m
nignx-b             1/1     Running   0          103m
sshd-test-service   1/1     Running   0          49s
[root@master ~]# kubectl exec -it sshd-test-service /bin/bash
root@sshd-test-service:/# curl 10.98.112.219:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
.....

也可以使用NodePort和LoadBalance的方式访问。在此不表。

[root@master ~]# cat nginx-service.yaml 
apiVersion: v1 
kind: Service 
metadata: 
  name: nginx-service-nodeport 
spec: 
  selector: 
    app: service-nginx 
  ports: 
    - name: http 
      port: 8000 
      protocol: TCP 
      targetPort: 80 
    - name: https 
      port: 8443 
      protocol: TCP 
      targetPort: 443 
  type: NodePort

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

从Docker到Kubernetes——Kubernetes设计解读之ReplicationController、Service 的相关文章

  • k8s之ReplicaSet

    我们在定义pod资源时 可以直接创建一个kind Pod类型的自主式pod 但是这存在一个问题 假如pod被删除了 那这个pod就不能自我恢复 就会彻底被删除 线上这种情况非常危险 所以今天就给大家讲解下pod的控制器 所谓控制器就是能够管
  • kubeadm一键搭建kubernetes环境

    kubeadm一键搭建kubernetes环境 安装docker 按官网教程执行https docs docker com v17 09 engine installation linux docker ce centos install
  • K8s部署自己的web项目

    一 静态网页项目 1 前端项目源码下载 链接 https pan baidu com s 15jCVawpyJxa0xhCJ9SwTCQ 提取码 m4an 2 编写nginx conf和Dockerfile 放在项目根目录下 1 创建ngi
  • k8s Trouble Shooting 故障排除

    本文要讲的是k8s的故障排除 比较浅 最近刚入门 主要涵盖的内容是查看k8s对象的当前运行时信息 对于服务 容器的问题是如何诊断的 对于某些复杂的问题例如pod调度问题是如何排查的 1 查看系统的Event事件 在对象资源 pod serv
  • 从Docker到Kubernetes——Kubernetes设计解读之Pod

    文章目录 Kubernetes是个什么样的项目 Kubernetes的设计解读 典型案例 GuestBook pod设计解读 pod使用实例 pod内容器网络与通信 Kubernetes是个什么样的项目 简单的说 k8s是一个管理跨主机容器
  • Harbor镜像仓库搭建

    1 安装docker comprose docker comprose是docker容器批量管理工具 curl L https get daocloud io docker compose releases download 1 25 0
  • K8s-yaml的使用及命令

    YAML配置文件管理对象 对象管理 创建deployment资源 kubectl create f nginx deployment yaml 查看deployment kubectl get deploy 查看ReplicaSet kub
  • wireshark 抓包学习TLS握手(ECDHE)

    首先放出经典的流程图 TLS 握手共分四个阶段 为了便于理解 我用wireshark抓了包来分析每一个阶段 Client Hello 第一次握手 客户端首先会发一个 Client Hello 消息 消息里面有客户端使用的 TLS 版本号 支
  • k8s部署springboot

    前言 首先以SpringBoot应用为例介绍一下k8s的部署步骤 1 从代码仓库下载代码 比如GitLab 2 接着是进行打包 比如使用Maven 3 编写Dockerfile文件 把步骤2产生的包制作成镜像 4 上传步骤3的镜像到远程仓库
  • k8s Pod定义yaml配置文件详解

    此文件相关配置查询 此文件只做参考 以查询为准 kubectl explain 为文档查询命令如 kubectl explain pod spec volumes apiVersion v1 版本 kind pod 类型 pod metad
  • kube-flannel.yml

    flannel作为k8s的集群中常用的网络组件 其yml文件的获取 建议去github中获取 具体的获取方式如下 apiVersion policy v1beta1 kind PodSecurityPolicy metadata name
  • IDEA远程调试

    1 概述 原理 本机和远程主机的两个 VM 之间使用 Debug 协议通过 Socket 通信 传递调试指令和调试信息 被调试程序的远程虚拟机 作为 Debug 服务端 监听 Debug 调试指令 jdwp是Java Debug Wire
  • 如何解决K8S节点显示NotReady

    文章目录 kubernetes节点断电重启 kubernetes节点断电重启 背景 运行的好好的k8s集群 某天断电 发现一个节点炸了 显示NotReady kubectl get nodes 那么如何查找问题呢 我们用它 journalc
  • kubectl常用命令

    alias k kubectl alias kc k create f alias kgp k get pods alias kdp k describe pods alias kdep k delete pods alias kl k l
  • k8s基础5——Pod常用命令、资源共享机制、重启策略和健康检查、环境变量、初始化容器、静态pod

    文章目录 一 基本了解 二 管理命令 三 yaml文件参数大全 四 创建pod的工作流程 五 资源共享机制 5 1 共享网络 5 2 共享存储 六 生命周期 重启策略 健康检查 七 环境变量 八 Init Containe初始化容器 九 静
  • K8S暴露服务的三种方式

    文章目录 暴露服务的三种方式 NodePort LoadBalane Ingress 内容参考 暴露服务的三种方式 NodePort 将服务的类型设置成NodePort 每个集群节点都会在节点上打 开 一 个端口 对于NodePort服务
  • kubeadm 安装k8s

    关于k8s集群化部署 以下均是个人一步一步的完成部署 并且会罗列出在部署过程中遇到的各种问题及其解决方式 一 环境准备 环境准备阶段试用与master节点部署与work节点部署 即master和work节点全部都需要执行这些步骤 1 关闭防
  • K8s基础6——应用配置管理方案、调度策略、污点和污点容忍

    文章目录 一 应用配置管理方案 1 1 ConfigMap 1 1 1 注入变量 1 1 2 挂载数据卷 1 2 Secret 1 2 1 数据卷挂载 1 2 2 变量注入 二 调度策略 2 1 nodeSelector定向调度 2 1 1
  • 基于minikube的k8s单机环境部署ThingsBoard

    1 minikube安装k8s单机版 https blog csdn net qq 39879126 article details 121587678 2 安装ThingsBoard 下载 https github com thingsb
  • K8S学习--Kubeadm-7--Ansible二进制部署

    K8S学习 Kubeadm 安装 kubernetes 1 组件简介 K8S学习 Kubeadm 安装 kubernetes 2 安装部署 K8S学习 Kubeadm 3 dashboard部署和升级 K8S学习 Kubeadm 4 测试运

随机推荐

  • java中map的使用,及底层源码

    java util Map public interface Map
  • python运行不显示结果图示_pycharm运行和调试不显示结果的解决方法

    刚在虚拟机里面安装了pycharm 配置 setting 完后 新建一个py文件 键入 hello world 竟然没有结果 虽然运行成功 pycharm太不友好了吧 然后开始找问题 1 有人说是文件名的问题 这个可能有 但是我没有改 我觉
  • CAD 4种反应器

    CAD中4种反应器 按照执行的先后顺序分别为 文档反应器 编辑器反应器 数据库反应器 文档反应器当用户输入命令和命令结束之时 文档反应器函数documentLockModeChanged XXXXX const char pGlobalCm
  • Linux中设置开机启动脚本(fedora)

    rc是runlevel control directory的缩写 大多数的Linux 发行版本中 启动脚本都被放在 etc rc d init d 这些脚本被ln 命令来连接到 etc rc d rcn d 目录 这里的n 就是运行级0 6
  • AES加密及解密

    public class AesUtil static Security addProvider new BouncyCastleProvider private static final String ALGORITHM AES ECB
  • tensorflow运行报错解决方法

    1 ImportError DLL load failed 找不到指定的模块 解决方法 把TensorFlow卸载了重新安装 可能是因为版本不对应的问题 在anaconda里面删除TensorFlow库 再重新安装回去 2 keras报错
  • 旧电脑再利用:完整版 Chrome OS 安装指南

    很多人家里应该都有用了好几年 配置略微有些老旧的笔记本或台式电脑 如果不考虑二手变卖 为它们安装更加轻量的操作系统其实也是一个不错的旧物利用技巧 此前我曾经为大家介绍过在普通 PC 安装 Fyde OS 的方法 虽然无论是 Fyde OS
  • 联想E540笔记本电脑拆键盘、换键盘

    我的联想ThinkPad E540键盘坏了几个按键 按下去没有反应 在京东第三方买了一个新的原装键盘 自己把它给换上去 下图中 几个涂红色的按键是坏的 编程的时候特别不方便 第一步 在电脑背面卸掉几个螺丝 因为键盘上也有螺丝 刚开始我卸的螺
  • 分享16个Python接单平台,做私活他不香吗?(附100个爬虫源码)

    一 python爬虫是可以做副业的 主要是爬取网站 小程序或者APP的数据 对数据进行分析与处理 或者直接向客户提供爬虫程序与技术支持 当初学会Python那会儿 有朋友来介绍我去接私活 是为一家公司做网站 那一单我赚了3 5K 从那之后逐
  • 零基础学python-1.2 什么是idle

    下面是摘自百度百科 IDLE是开发python程序的基本IDE 集成开发环境 具备基本的IDE的功能 是非商业Python开发的不错的选择 当安装好python以后 IDLE就自动安装好了 不需要另外去找 同时 使用Eclipse这个强大的
  • ❤ 高德地图报错 AMap.ElasticMarker is not a constructor

    高德地图报错 AMap ElasticMarker is not a constructor 原因 未定义高德的插件 解决 出现这个报错可以添加 AMap plugin AMap Geocoder function tip 出现 xxx i
  • Git clone 时 出现SSL certificate problem error

    出现这个问题的原因是本地默认开启了SSL认证 但是在本地找不到SSL证书 解决办法就是关掉 SLL认证 git clone 时加上参数 no ssl check 完整的命令应该是 git clone no ssl check https g
  • 从XSS Payload学习浏览器解码

    从XSS Payload学习浏览器解码 HTML解析 URL解析 JavaScript解析 案例解析 总结 作为一个浏览器在解析一篇HTML文档时主要有三个处理过程 每个解析器负责解码和解析HTML文档中它所对应的部分 下面我将按照解码顺序
  • SpringBoot使用Swagger3出现Unable to infer base url.This is common when using dynamic servlet

    在使用SpringBoot中配置Swagger3的时候 出现 Unable to infer base url This is common when using dynamic servlet registration or when t
  • UDP协议以及代理服务器

    UDP协议 面向非连接的协议 指在正式通信前不必与对方先建立连接 不管对方状态就直接发送 对方是否可以接受这些内容 该协议则无法控制 适用于 一次性只传输少量数据 对可靠性要求不高的应用环境 UDP和TCP对比 TCP 可靠 传输大小无限制
  • 调制方式性能比较--BER,频带效率的极限

    文章目录 1 BER性能 1 1AWGN的QPSK 1 2常用的误比特率公式 2 频带效率 2 1香农定理 2 2两个重要的区域 2 3对于各个信号 1 BER性能 1 1AWGN的QPSK AWGN信道中无符号间干扰的QPSK的BER经常
  • Jmeter(十) - 从入门到精通 - JMeter逻辑控制器 - 中篇(详解教程)

    1 简介 Jmeter官网对逻辑控制器的解释是 Logic Controllers determine the order in which Samplers are processed 意思是说 逻辑控制器可以控制采样器 samplers
  • KVM(一) qemu-kvm

    前言 KVM即Kernel Virtual Machine 最初是由以色列公司Qumranet开发 2007年2月被导入Linux 2 6 20核心中 成为内核源代码的一部分 2008年9月4日 Redhat收购了Qumranet 至此Re
  • [R语言] R语言PCA分析教程 Principal Component Methods in R

    R语言PCA分析教程 Principal Component Methods in R 代码下载 主成分分析Principal Component Methods PCA 允许我们总结和可视化包含由多个相互关联的定量变量描述的个体 观察的数
  • 从Docker到Kubernetes——Kubernetes设计解读之ReplicationController、Service

    文章目录 Kubernetes的设计解读 replication controller 设计解读 replication controller 使用示例 service的设计解读 service的使用示例 Kubernetes的设计解读 r