yurtctl convert --provider [minikube|kubeadm|kind] // To convert an existing Kubernetes cluster to an OpenYurt cluster yurtctl revert // To uninstall and revert back to the original cluster settings
Service Topology enables a service to route traffic based on the Node topology of the cluster. For example, a service can specify that traffic be preferentially routed to endpoints that are on the same Node as the client, or in the same available NodePool.
The following picture shows the general function of the service topology.
To use service topology, the EndpointSliceProxyingfeature gate must be enabled, and kube-proxy needs to be configured to connect to Yurthub instead of the API server.
You can set the topologyKeys values of a service to direct traffic as follows. If topologyKeys is not specified or empty, no topology constraints will be applied.
$ kubectl get node NAME STATUS ROLES AGE VERSION kind-control-plane Ready master 6m21s v1.18.19 kind-worker Ready <none> 5m42s v1.18.19 kind-worker2 Ready <none> 5m42s v1.18.19
Ensure that yurt-app-manager is deployed in the cluster.
To use service topology, the EndpointSliceProxyingfeature gate must be enabled, and kube-proxy needs to be configured to connect to Yurthub instead of the API server.
$ kubectl delete pod --selector k8s-app=kube-proxy -n kube-system pod "kube-proxy-cbsmj" deleted pod "kube-proxy-cqwcs" deleted pod "kube-proxy-m9dgk" deleted
Create test united-deployment1. To facilitate testing, we use a serve_hostname image. Each time port 9376 is accessed, the hostname container returns its own hostname.
We use the nginx pod in the shanghai nodepool to test service topology. Therefore, its traffic can only be routed to the nodes that in shanghai nodepool when it accesses a service with the openyurt.io/topologyKeys: openyurt.io/nodepool annotation.
For comparison, we first test the service without serviceTopology annotation. As we can see, its traffic can be routed to any nodes.
func HandleResponse(rw http.ResponseWriter, resp *http.Response) { bodyBytes, _ := ioutil.ReadAll(resp.Body) go func() { // cache response on local disk cacher.Write(bodyBytes) } // client reads data from response rw.Write(bodyBytes) }
// https://github.com/openyurtio/openyurt/blob/master/pkg/yurthub/util/util.go#L156 // NewDualReadCloser create an dualReadCloser object func NewDualReadCloser(rc io.ReadCloser, isRespBody bool) (io.ReadCloser, io.ReadCloser) { pr, pw := io.Pipe() dr := &dualReadCloser{ rc: rc, pw: pw, isRespBody: isRespBody, } return dr, pr } type dualReadCloser struct { rc io.ReadCloser pw *io.PipeWriter // isRespBody shows rc(is.ReadCloser) is a response.Body // or not(maybe a request.Body). if it is true(it's a response.Body), // we should close the response body in Close func, else not, // it(request body) will be closed by http request caller isRespBody bool } // Read read data into p and write into pipe func (dr *dualReadCloser) Read(p []byte) (n int, err error) { n, err = dr.rc.Read(p) if n > 0 { if n, err := dr.pw.Write(p[:n]); err != nil { klog.Errorf("dualReader: failed to write %v", err) return n, err } } return } // Close close two readers func (dr *dualReadCloser) Close() error { errs := make([]error, 0) if dr.isRespBody { if err := dr.rc.Close(); err != nil { errs = append(errs, err) } } if err := dr.pw.Close(); err != nil { errs = append(errs, err) } if len(errs) != 0 { return fmt.Errorf("failed to close dualReader, %v", errs) } return nil }
root@master-node:~# kubectl get pods -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx 1/1 Running 0 11s 192.168.0.100 edge-node <none>
root@master-node:~# kubectl get nodes NAME STATUS ROLES AGE VERSION edge-node NotReady <none> 5m13s v1.16.6 master-node Ready master 6m4s v1.16.6
继续等待 5s 之后(正常节点变为 NotReady 之后,5m 才开始驱逐 pod,此处为了测试效果,将 pod 的容忍时间配成了 5s),应用 pod 被驱逐,状态变为 Terminating。
root@master-node:~# kubectl get pods NAME READY STATUS RESTARTS AGE nginx 1/1 Terminating 0 3m45s
将网络恢复,观察节点及 pod 变化。
root@master-node:~# kubectl get pods No resources found in default namespace.
网络恢复后,节点状态变成 ready,业务 pod 被清除,这是因为边缘节点的 Kubelet 获取到业务 Pod 的 Terminating 状态,对业务 Pod 做删除操作,并返回删除成功,云端也做了相应的清理。至此,业务 Pod 由于云边网络的不稳定而被驱逐,然而在断网期间,边缘节点其实是可以正常工作的。
重新创建应用 nginx,用于下面测试。
root@master-node:~# kubectl get pods -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx 1/1 Running 0 4s 192.168.0.100 edge-node <none> <none>
重启前边缘节点容器列表(此时云边端开,虽然在云端获取的 pod 是 Terminating 状态,但是边缘并未 Watch 到 Terminating 的操作,所以边缘的应用还正常运行)。
root@edge-node:~# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9671cbf28ca6 e86f991e5d10 "/docker-entrypoint.…" About a minute ago Up About a minute k8s_nginx_nginx_default_efdf11c6-a41c-4b95-8ac8-45e02c9e1f4d_0 6272a46f93ef registry.cn-hangzhou.aliyuncs.com/edge-kubernetes/pause:3.1 "/pause" 2 minutes ago Up About a minute k8s_POD_nginx_default_efdf11c6-a41c-4b95-8ac8-45e02c9e1f4d_0 698bb024c3db f9ea384ddb34 "/usr/local/bin/kube…" 8 minutes ago Up 8 minutes k8s_kube-proxy_kube-proxy-rjws7_kube-system_51576be4-2b6d-434d-b50b-b88e2d436fef_0 31952700c95b registry.cn-hangzhou.aliyuncs.com/edge-kubernetes/pause:3.1 "/pause" 8 minutes ago Up 8 minutes k8s_POD_kube-proxy-rjws7_kube-system_51576be4-2b6d-434d-b50b-b88e2d436fef_0
重启后节点容器列表,断网重启后,kubelet 无法从云端获取 Pod 信息,不会重建 Pod。
root@edge-node:~# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES root@edge-node:~#
从重启前后的对比看,边缘节点在断网重启之后,节点上的 Pod 全部无法恢复。这就会导致在云边断网时,一旦节点重启,应用将无法工作。
将网络恢复,观察节点及 pod 变化,同上面测试结果,网络恢复后,节点变为 Ready,业务 Pod 被清除。
root@master-node:~# kubectl get nodes NAME STATUS ROLES AGE VERSION edge-node Ready <none> 11m v1.16.6 master-node Ready master 12m v1.16.6 root@master-node:~# kubectl get pods No resources found in default namespace.
root@master-node:~# kubectl get pods -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx 1/1 Running 0 12s 192.168.0.100 edge-node <none> <none>
转换大概需要 2min,转换完成之后,观察业务 pod 的状态,可以看到转换过程中对业务 pod 无影响(也可以在转换过程中在新的终端使用 kubectl get pod -w 观察业务 pod 的状态)。
root@master-node:~# kubectl get pods -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx 1/1 Running 0 2m4s 192.168.0.100 edge-node <none> <none>
root@master-node:~# kubectl logs nginx /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/ /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh 10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf 10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh /docker-entrypoint.sh: Configuration complete; ready for start up root@master-node:~# kubectl exec -it nginx sh kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead. # ls bin dev docker-entrypoint.sh home media opt root sbin sys usr boot docker-entrypoint.d etc lib mnt proc run srv tmp var # exit root@master-node:~# kubectl port-forward pod/nginx 8888:80 Forwarding from 127.0.0.1:8888 -> 80 Handling connection for 8888
root@master-node:~# kubectl get nodes NAME STATUS ROLES AGE VERSION edge-node NotReady <none> 24m v1.16.6 master-node Ready master 25m v1.16.6 root@master-node:~# kubectl get pods NAME READY STATUS RESTARTS AGE nginx 1/1 Running 0 5m7s
恢复网络,观察 Node 和 Pod 的状态,Node 状态变为 Ready,Pod 保持 Running。可见云边网络不稳定时,对边缘节点的业务 Pod 无影响。
root@master-node:~# kubectl get nodes NAME STATUS ROLES AGE VERSION edge-node Ready <none> 25m v1.16.6 master-node Ready master 26m v1.16.6 root@master-node:~# kubectl get pods NAME READY STATUS RESTARTS AGE nginx 1/1 Running 0 6m30s
接下来,我们测试在断网的情况下,边缘节点的重启对业务的影响。断网 1 分钟之后,Node 和 Pod 状态同上面测试结果,Node 变为 NotReady,Pod 保持 Running。同样,我们登录到树莓派上,将树莓派重启,观察重启前后节点上的容器列表。
重启前边缘节点容器列表:
root@edge-node:~# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 38727ec9270c 70bf6668c7eb "yurthub --v=2 --ser…" 7 minutes ago Up 7 minutes k8s_yurt-hub_yurt-hub-edge-node_kube-system_d75d122e752b90d436a71af44c0a53be_0 c403ace1d4ff registry.cn-hangzhou.aliyuncs.com/edge-kubernetes/pause:3.1 "/pause" 7 minutes ago Up 7 minutes k8s_POD_yurt-hub-edge-node_kube-system_d75d122e752b90d436a71af44c0a53be_0 de0d693e9e74 473ae979be68 "yurt-tunnel-agent -…" 7 minutes ago Up 7 minutes k8s_yurt-tunnel-agent_yurt-tunnel-agent-7l8nv_kube-system_75d28494-f577-43fa-9cac-6681a1215498_0 a0763f143f74 registry.cn-hangzhou.aliyuncs.com/edge-kubernetes/pause:3.1 "/pause" 7 minutes ago Up 7 minutes k8s_POD_yurt-tunnel-agent-7l8nv_kube-system_75d28494-f577-43fa-9cac-6681a1215498_0 80c247714402 e86f991e5d10 "/docker-entrypoint.…" 7 minutes ago Up 7 minutes k8s_nginx_nginx_default_b45baaac-eebc-466b-9199-2ca5c1ede9fd_0 01f7770cb0f7 registry.cn-hangzhou.aliyuncs.com/edge-kubernetes/pause:3.1 "/pause" 7 minutes ago Up 7 minutes k8s_POD_nginx_default_b45baaac-eebc-466b-9199-2ca5c1ede9fd_0 7e65f83090f6 f9ea384ddb34 "/usr/local/bin/kube…" 17 minutes ago Up 17 minutes k8s_kube-proxy_kube-proxy-rjws7_kube-system_51576be4-2b6d-434d-b50b-b88e2d436fef_1 c1ed142fc75b registry.cn-hangzhou.aliyuncs.com/edge-kubernetes/pause:3.1 "/pause" 17 minutes ago Up 17 minutes k8s_POD_kube-proxy-rjws7_kube-system_51576be4-2b6d-434d-b50b-b88e2d436fef_1
重启后边缘节点容器列表:
root@edge-node:~# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 0c66b87066a0 473ae979be68 "yurt-tunnel-agent -…" 12 seconds ago Up 11 seconds k8s_yurt-tunnel-agent_yurt-tunnel-agent-7l8nv_kube-system_75d28494-f577-43fa-9cac-6681a1215498_2 a4fb3e4e8c8f e86f991e5d10 "/docker-entrypoint.…" 58 seconds ago Up 56 seconds k8s_nginx_nginx_default_b45baaac-eebc-466b-9199-2ca5c1ede9fd_1 fce730d64b32 f9ea384ddb34 "/usr/local/bin/kube…" 58 seconds ago Up 57 seconds k8s_kube-proxy_kube-proxy-rjws7_kube-system_51576be4-2b6d-434d-b50b-b88e2d436fef_2 c78166ea563f registry.cn-hangzhou.aliyuncs.com/edge-kubernetes/pause:3.1 "/pause" 59 seconds ago Up 57 seconds k8s_POD_yurt-tunnel-agent-7l8nv_kube-system_75d28494-f577-43fa-9cac-6681a1215498_1 799ad14bcd3b registry.cn-hangzhou.aliyuncs.com/edge-kubernetes/pause:3.1 "/pause" 59 seconds ago Up 57 seconds k8s_POD_nginx_default_b45baaac-eebc-466b-9199-2ca5c1ede9fd_1 627673da6a85 registry.cn-hangzhou.aliyuncs.com/edge-kubernetes/pause:3.1 "/pause" 59 seconds ago Up 58 seconds k8s_POD_kube-proxy-rjws7_kube-system_51576be4-2b6d-434d-b50b-b88e2d436fef_2 04da705e4120 70bf6668c7eb "yurthub --v=2 --ser…" About a minute ago Up About a minute k8s_yurt-hub_yurt-hub-edge-node_kube-system_d75d122e752b90d436a71af44c0a53be_1 260057d935ee registry.cn-hangzhou.aliyuncs.com/edge-kubernetes/pause:3.1 "/pause" About a minute ago Up About a minute k8s_POD_yurt-hub-edge-node_kube-system_d75d122e752b90d436a71af44c0a53be_1
从重启前后的对比看,边缘节点在断网重启之后,节点上的 pod 能正常拉起,OpenYurt 的节点自治能力可以在断网下保证业务的稳定运行。
恢复网络,节点 Ready,观察业务 pod 的状态,网络恢复后,业务 pod 状态保持 running,有一次重启记录,符合预期。
root@master-node:~# kubectl get pods -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx 1/1 Running 1 11m 192.168.0.100 edge-node <none> <none>
Yurttunnel是OpenYurt近期开源的一个重要组件,用来解决云边通信问题。反向通道是解决跨网络通信的一种常见方式,而 Yurttunnel 的本质就是一个反向通道。一个边缘集群下属的节点常位于不同的 network region 中,而位于同一个 region 内的节点之间是可以相互通信的,因此在设置反向通道时,我们只需保证在每个 region 内设置一个与 proxy server 相连的 agent 即可。具体包括以下几个步骤:
在管控组件所在网络内,部署 proxy server。
proxy server 对外开放一个公网可以访问的 IP。
在每个 region 部署个 agent,并通过 server 的公网 IP 与 server 建立长连接。
如何确保 server 副本覆盖所有的 region -- 在生产环境中,一个边缘集群常常包含上万个节点,并同时服务上百名用户,如果一个集群只有一个 proxy server, 那么,一旦 proxy server 出现故障,所有用户都将无法对位于边缘节点上的 pod 进行操作。因此,我们必须同时部署多个 proxy server 副本以保证集群高可用。同时,proxy server 的工作负荷将随着访问流量的增大而增大,用户的访问延时也势必增加。因此在部署 proxy server 时,我们还需考虑如何对 proxy server 进行水平拓展,以应对高并发场景。一个应对单点故障和高并发场景的经典解决方案是,部署多个 proxy server 副本,并使用负载均衡进行流量分发。然而在 OpenYurt 场景下,对于 KAS 发来的任意请求,LoadBalancer (LB) 会转发给哪个 server 副本是不可控的,因此,需要解决的第二个问题是,如何保证每个 server 副本都能与所有的 agent 建立连接。
如何将请求转发给正确的 agent -- 在运行过程中,proxy server 在收到请求后,需根据请求的 destination IP,将请求转发至位于对应 network region 内的 agent。然而,ANP目前的实现,假设所有的节点都位于一个网络空间内, server 会随机挑选一个 agent 转发请求。因此,我们需要解决的第三个问题是,如何将请求正确地转发给指定的 agent。
如何解除组件对节点证书的依赖 -- 在运行时,我们需要为 server 提供一套 TLS 证书,以实现 server 与 KAS,server 与 agent 间的安全通信。同时,我们也需要为 agent 准备一套 TLS client 证书,用以建立 agent 和 server 间的 gRPC 信道。ANP 目前的实现,要求 server 必须和 KAS 部署在同一个节点上,并且在启动时挂载节点 volume 共享 KAS tls 证书。同样,agent 也需要在启动时挂载 volume 共享 kubelet tls 证书。这无形中降低了部署的灵活性,造成了组件对节点证书的强依赖,在有些情况下,用户可能希望将 server 部署在非 KAS 所在节点上。因此,另一个需要关注的问题是,如何解除组件对节点证书的依赖。
OpenYurt 能帮用户解决在海量边、端资源上完成大规模应用交付、运维、管控的问题,并提供中心服务下沉通道,实现和边缘计算应用的无缝对接。在设计 OpenYurt 之初,我们就非常强调保持用户体验的一致性,不增加用户运维负担,让用户真正方便地 “Extending your native kubernetes to edge”。
$ yurtctl convert --cloud-nodes node1 --provider ack I0603 14:34:33.714304 40825 convert.go:164] mark node1 as the cloud-node I0603 14:34:33.719816 40825 convert.go:172] mark node2 as the edge-node I0603 14:34:33.736609 40825 convert.go:198] deploy the yurt controller manager I0603 14:34:33.742272 40825 convert.go:210] deploying the yurt-hub and resetting the kubelet service... I0603 14:34:53.810165 40825 util.go:168] servant job(yurtctl-servant-convert-node2) has succeeded
成功配置节点之后,我们需要将边缘节点标记为自治状态,具体命令如下:
$ yurtctl markautonomous # 如果用户只想标记部分边缘节点,则可以使用 --autonomous-nodes 选项指定 I0602 11:22:05.610222 89160 markautonomous.go:149] mark node2 as autonomous
$ kubectl get node NAME STATUS ROLES AGE VERSION node1 Ready master 14m v1.14.8 node2 NotReady <none> 12m v1.14.8 $ kubectl get pod NAME READY STATUS RESTARTS AGE bbox 1/1 Running 0 5m12s