暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

(逆向理解)-没有kube-proxy组件的Kubernetes集群会怎么样?

云原生CTO 2021-01-16
1060

       点击上方 云原生CTO,选择设为星标

               优质文章,每日送达

----------------------------------------------

     「【只做懂你de云原生干货知识共享】」

(逆向理解)-没有kube-proxy组件的Kubernetes集群会怎么样?

Kubernetes是一个复杂的平台,其中各个运动部件协同工作。Kubernetes网络是最关键的(如果不是最关键的话)之一。kubernetes网络有很多层,即Pod网络,服务IP,外部IP集群IP等。在此过程中,kube-proxy发挥着重要作用。在这里,我们尝试通过一些不同的CNI选项将Kubernetes与kube-proxy一起使用,我们将看到它是否具有任何好处,或者kube-proxy是否具有任何特定的限制。

快速理解kube-proxy

在各种kubernetes网络组件中,让我们看两个特别的组件-pod网络和service网络。每当我们设置Kubernetes时,我们总是会指定不重叠的Pod CIDR和Service CIDR。创建Pod时,它具有自己的IP,例如,下面的演示,我将现在的集群当前节点的master的pod ip列出来,因为你要知道路由表是当前节点,所以我将master节点的pod ip列出来

[root@master ~]# kubectl get po -A -owide |grep master |egrep -v "192"
kube-system   calico-kube-controllers-6b94766748-nx4b6   1/1     Running   1          12d     10.244.219.83    master   <none>           <none>
kube-system   coredns-9d85f5447-w9mg4                    1/1     Running   1          12d     10.244.219.84    master   <none>           <none>
kube-system   coredns-9d85f5447-xll29                    1/1     Running   1          12d     10.244.219.81    master   <none>           <none>

容器IP是来自容器CIDR的虚拟网络的一部分,它充当每个节点上的网桥。我们可以通过登录主节点之一并打印路由表来查看 可以使用route -n

[root@master ~]# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.1.1     0.0.0.0         UG    100    0        0 ens160
10.244.104.0    192.168.2.13    255.255.255.192 UG    0      0        0 tunl0
10.244.167.128  192.168.2.11    255.255.255.192 UG    0      0        0 tunl0
10.244.219.64   0.0.0.0         255.255.255.192 U     0      0        0 *
10.244.219.81   0.0.0.0         255.255.255.255 UH    0      0        0 caliaf3f4a2a80f
10.244.219.83   0.0.0.0         255.255.255.255 UH    0      0        0 cali9bef2516196
10.244.219.84   0.0.0.0         255.255.255.255 UH    0      0        0 cali9d477edda73
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
192.168.0.0     0.0.0.0         255.255.252.0   U     100    0        0 ens160


我们可以在上面看到子网10.244.219.64中的所有Ip都路由到工作程序节点0.0.0.0。如果我们登录到该工作节点列表,则将看到该特定节点上的网络接口,我们将看到预期的虚拟接口来自tunl0,另外每个pod都对应网络接口名称cali

4: caliaf3f4a2a80f@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1440 qdisc noqueue state UP group default 
    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::ecee:eeff:feee:eeee/64 scope link 
       valid_lft forever preferred_lft forever
6: cali9bef2516196@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1440 qdisc noqueue state UP group default 
    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 2
    inet6 fe80::ecee:eeff:feee:eeee/64 scope link 
       valid_lft forever preferred_lft forever
7: tunl0@NONE: <NOARP,UP,LOWER_UP> mtu 1440 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
    inet 10.244.219.64/32 brd 10.244.219.64 scope global tunl0
       valid_lft forever preferred_lft forever
8: cali9d477edda73@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1440 qdisc noqueue state UP group default 
    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 3
    inet6 fe80::ecee:eeff:feee:eeee/64 scope link 
       valid_lft forever preferred_lft forever

由于篇幅过长这篇文章不会深入calico网络的内容,如果你对网络更有兴趣,关注公众号,并设为星标,后面将能看到更多云原生干货知识。

回到正题。现在,让我们深入理解一下iptbales在kube-proxy流量是如何转发的,以及出现了哪些瓶颈,后续我们将大胆做个性能测试来验证没有kube-proxy是否会提升性能,为了更清晰熟悉iptables的在kube-proxy当中的使用的原理,我单独在master节点创建一个pod以及svc转通过nodeport转发出去,看一下实际流量的走向

[root@master ~]# kubectl create deploy nginx --image=nginx -o yaml --dry-run > nginx-test.yaml
[root@master ~]# kubectl create service nodeport nginx  --tcp=5678:80 --dry-run -o yaml > nginx-svc.yaml

运行之后我们会发现service关联上了对应的pod ip,当然如果你注意的话,我们会在宿主机多出一个新的cali的网络接口,当然你要知道这个pod并不是在master节点的,而serive它就充当了这么一个负载均衡器

[root@master ~]# kubectl get svc
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
kubernetes   ClusterIP   10.96.0.1      <none>        443/TCP          12d
nginx        NodePort    10.96.62.137   <none>        5678:30965/TCP   4s
[root@master ~]# kubectl get ep
NAME             ENDPOINTS           AGE
fuseim.pri-ifs   <none>              10d
kubernetes       192.168.2.10:6443   12d
nginx            10.244.167.174:80   9s

当kube-proxy在iptables的模式下使用(因为它是在kubernetes dafault默认的,除非你使用ipvs规则),将请求路由到服务继续为现有的服务工作,即使kube-proxy pod部署, Kube-proxy也会监听所有的K8S节点

在正常情况下,kube-proxy 绑定并侦听所有NodePorts端口,因此,如果你具有服务NodePort,则不必手动配置iptables规则。

如果你熟悉iptables,那么我们可以看到KUBE-SERVICES目标中有一个由创建的链kube-proxy。现在列出该链中的规则,请参见下面的示例:

[root@master ~]# iptables -t nat -L KUBE-SERVICES -n | column -t
Chain                      KUBE-SERVICES  (2   references)
target                     prot           opt  source          destination
KUBE-MARK-MASQ             tcp            --   !10.244.0.0/16  10.96.62.137  /*  default/nginx:5678-80         cluster  IP          */     tcp   dpt:5678
KUBE-SVC-VXA2I2E2TLP6KZO2  tcp            --   0.0.0.0/0       10.96.62.137  /*  default/nginx:5678-80         cluster  IP          */     tcp   dpt:5678
KUBE-MARK-MASQ             udp            --   !10.244.0.0/16  10.96.0.10    /*  kube-system/kube-dns:dns      cluster  IP          */     udp   dpt:53
KUBE-SVC-TCOU7JCQXEZGVUNU  udp            --   0.0.0.0/0       10.96.0.10    /*  kube-system/kube-dns:dns      cluster  IP          */     udp   dpt:53
KUBE-MARK-MASQ             tcp            --   !10.244.0.0/16  10.96.0.10    /*  kube-system/kube-dns:dns-tcp  cluster  IP          */     tcp   dpt:53
KUBE-SVC-ERIFXISQEP7F7OF4  tcp            --   0.0.0.0/0       10.96.0.10    /*  kube-system/kube-dns:dns-tcp  cluster  IP          */     tcp   dpt:53
KUBE-MARK-MASQ             tcp            --   !10.244.0.0/16  10.96.0.10    /*  kube-system/kube-dns:metrics  cluster  IP          */     tcp   dpt:9153
KUBE-SVC-JD5MR3NA4I4DYORP  tcp            --   0.0.0.0/0       10.96.0.10    /*  kube-system/kube-dns:metrics  cluster  IP          */     tcp   dpt:9153
KUBE-MARK-MASQ             tcp            --   !10.244.0.0/16  10.96.0.1     /*  default/kubernetes:https      cluster  IP          */     tcp   dpt:443
KUBE-SVC-NPX46M4PTMTKRN6Y  tcp            --   0.0.0.0/0       10.96.0.1     /*  default/kubernetes:https      cluster  IP          */     tcp   dpt:443
KUBE-NODEPORTS             all            --   0.0.0.0/0       0.0.0.0/0     /*  kubernetes                    service  nodeports;  NOTE:  this  must      be  the  last  rule  in  this  chain  */  ADDRTYPE  match  dst-type  LOCAL

如您所见,KUBE-SERVICES链中的一个目标是KUBE-NODEPORTS链。这会列出所有cluster ip的流量地址并关联对应的svc,刚才我们部署的nginx,正在iptables当中处理了流量

由于我们创建的服务的类型为NodePort,因此让我们在KUBE-NODEPORTS链中列出规则。

[root@master ~]# iptables -t nat -L KUBE-NODEPORTS -n | column -t
Chain                      KUBE-NODEPORTS  (1   references)
target                     prot            opt  source       destination
KUBE-MARK-MASQ             tcp             --   0.0.0.0/0    0.0.0.0/0    /*  default/nginx:5678-80  */  tcp  dpt:30965
KUBE-SVC-VXA2I2E2TLP6KZO2  tcp             --   0.0.0.0/0    0.0.0.0/0    /*  default/nginx:5678-80  */  tcp  dpt:30965

您应该看到输出显示目标是针对发往您的的数据包NodePort 30965。

然后验证kube-proxy正在侦听NodePort。

在正常情况下,kube-proxy绑定并侦听所有NodePorts端口以确保保留这些端口,并且其他任何进程都不能使用它们。您可以在上面的kubernetes节点上对此进行验证:

[root@master ~]# lsof -i:30965
COMMAND     PID USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
kube-prox 10122 root    8u  IPv6 90163732      0t0  TCP *:30965 (LISTEN)
[root@master ~]# ps -aef |grep -v grep |grep 10122
root     10122 10089  0 1月12 ?       00:09:11 /usr/local/bin/kube-proxy --config=/var/lib/kube-proxy/config.conf --hostname-override=master

或者通过netstat查看监听的端口

[root@master ~]# netstat -anpt |grep 30965
tcp6       0      0 :::30965                :::*                    LISTEN      10122/kube-proxy    
[root@master ~]# netstat -anpt |grep LISTEN
tcp        0      0 0.0.0.0:2049            0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:33830           0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:10248         0.0.0.0:*               LISTEN      2402/kubelet        
tcp        0      0 127.0.0.1:10249         0.0.0.0:*               LISTEN      10122/kube-proxy    
tcp        0      0 127.0.0.1:9099          0.0.0.0:*               LISTEN      12624/calico-node   
tcp        0      0 192.168.2.10:2379       0.0.0.0:*               LISTEN      8364/etcd           
tcp        0      0 127.0.0.1:2379          0.0.0.0:*               LISTEN      8364/etcd           
tcp        0      0 192.168.2.10:2380       0.0.0.0:*               LISTEN      8364/etcd           
tcp        0      0 127.0.0.1:2381          0.0.0.0:*               LISTEN      8364/etcd           
tcp        0      0 0.0.0.0:111             0.0.0.0:*               LISTEN      705/rpcbind         
tcp        0      0 0.0.0.0:20048           0.0.0.0:*               LISTEN      4530/rpc.mountd     
tcp        0      0 127.0.0.1:10257         0.0.0.0:*               LISTEN      8325/kube-controlle 
tcp        0      0 0.0.0.0:179             0.0.0.0:*               LISTEN      12809/bird          
tcp        0      0 127.0.0.1:10259         0.0.0.0:*               LISTEN      8281/kube-scheduler 
tcp        0      0 0.0.0.0:51348           0.0.0.0:*               LISTEN      28856/rpc.statd     
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      954/sshd            
tcp        0      0 127.0.0.1:44024         0.0.0.0:*               LISTEN      2402/kubelet        
tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN      1371/master         
tcp6       0      0 :::2049                 :::*                    LISTEN      -                   
tcp6       0      0 :::10250                :::*                    LISTEN      2402/kubelet        
tcp6       0      0 :::6443                 :::*                    LISTEN      8338/kube-apiserver 
tcp6       0      0 :::10251                :::*                    LISTEN      8281/kube-scheduler 
tcp6       0      0 :::10252                :::*                    LISTEN      8325/kube-controlle 
tcp6       0      0 :::54703                :::*                    LISTEN      28856/rpc.statd     
tcp6       0      0 :::111                  :::*                    LISTEN      705/rpcbind         
tcp6       0      0 :::20048                :::*                    LISTEN      4530/rpc.mountd     
tcp6       0      0 :::10256                :::*                    LISTEN      10122/kube-proxy    
tcp6       0      0 :::37075                :::*                    LISTEN      -                   
tcp6       0      0 :::30965                :::*                    LISTEN      10122/kube-proxy    
tcp6       0      0 :::22                   :::*                    LISTEN      954/sshd            
tcp6       0      0 ::1:25                  :::*                    LISTEN      1371/master         


您应该看到30965被kube-proxy正在监听。在iptables模式下,为kubernetes服务创建iptables规则,以确保对get的请求 被路由(并负载均衡)到适当的pod。

只要这些iptables规则存在,即使节点上的kube-proxy进程死亡,对服务的请求也将被路由到适当的pods。但是,新service的端点不能从这个节点工作,因为kube-proxy进程不会为它创建iptables规则。

如果你还对它疑惑🤔,好吧,这次我不会对它做其他的解释了,后面如果有新文章还会产出,另外我找到推特上来自一名Kubernetes, GKE, &谷歌云首席软件工程师发的帖子,他详细描述了 Kubernetes kube-proxy iptables规则流程图,希望它可以帮助到你,如果想获取的话,可以到公众号输入iptables即可获取

为什么要更换kube-proxy?

那么,为什么要替换kube-proxy呢?好吧,第一个原因只是为了一些个人实验:)。kube-proxy组件已被广泛使用,就像事实上的部署一样,因此没有真正的理由无故删除它。上面与iptables相关的入门文章中也暗示了另一个原因。让我们做一个简单的实验。

现在让我们做一个“愚蠢的”测试。我们将使用相同的选择器添加100个服务,这些服务指向我们当前使用不同服务名称的nginx部署

我们需要单独安装一个yq的解析yaml可以改动yaml文件的工具

如果你想学习可以参考这个开源工具并安装上 https://github.com/mikefarah/yq

[root@master ~]# for i in {1..100}; do yq w nginx-svc.yaml "metadata.name" "nginx-"$i | kubectl apply -n default  -f -; done 
service/nginx-1 created
service/nginx-2 created
service/nginx-3 created
service/nginx-4 created
...
100


现在,我们有100个service都指向同一个Pod。如果我们现在看一下iptables

如我们所见,kube-proxy为定义的每个新服务添加了iptables规则集。随着服务数量的增加,此列表将变得非常庞大,并且可能会影响性能,因为iptables处理是顺序的。规则在链中越向下,处理所需的时间就越长。

哈哈,甚至面试当中会问到这些,希望这次的演示可以帮你深刻的理解它。

回归正题,那么我们可以删除kube-proxy,不使用iptable换成其它的替换它吗?是,可以的。解决方案(或潜在解决方案之一)在于使用eBPF(扩展的Berkeley数据包过滤器)。

什么是eBPF?

全面而深入的技术理解超出了本实验的范围,甚至超出了我自己的技能范围,但以简单的术语来说,eBPF(扩展的Berkeley Packet Filter)是在Linux机器的内核中运行的虚拟机。它能够运行本地实时编译的“ bpf程序”,这些程序可以访问某些内核功能。换句话说,用户可以在运行时按需注入这些程序以在内核中运行。这些程序遵循bpf提供的特定指令集,并具有某些需要遵循的规则,并且将仅运行可以安全运行的程序。这与Linux模块不同,后者也在内核中运行,但是如果编写不正确,可能会导致内核出现问题。

我将把这些细节推送到有关BPF的大量文章上。但是,该虚拟机可以连接到诸如网络设备之类的任何内核子系统,并且BPF程序会响应这些子系统上的事件而执行。最古老,最受欢迎的Linux工具之一-tcpdump使用BPF。我很想说像智能nics等新技术都利用了BPF,但对我而言这只是一个疯狂的猜测。

使用eBPF用CNI驱动程序替换kube-proxy cilium项目利用eBPF实施其网络策略,还提供了kube-proxy替代产品。Calico项目还具有使用eBPF的技术预览,但是对于本实验,我们将仅使用Cilium。

Cilium kube-proxy免费安装

这实际上非常简单。关于内核版本,存在某些先决条件。内核版本至少应为4.19,但建议版本> v5.3。我们只需要遵循此处概述的步骤 https://docs.cilium.io/zh/v1.7/gettingstarted/kubeproxy-free/

如果你的版本过低会产生下面的报错

对于1.15和更低版本的K8S,我们首先在主机上删除kube-proxy的守护程序集并清空iptables,然后再添加其他节点

#kubectl -n kube-system delete ds kube-proxy
#iptables-restore <(iptables-save | grep -v KUBE)

对于1.16及更高版本,我们可以完全跳过kube-proxy

kubeadm init --pod-network-cidr=10.217.0.0/16 --skip-phases=addon/kube-proxy

直接使用helm,这将安装Cilium作为CNI插件,替换为BPF kube-proxy,以实现处理ClusterIP,NodePort,ExternalIPs和LoadBalancer类型的Kubernetes服务。

helm repo add cilium https://helm.cilium.io/

helm install cilium cilium/cilium --version 1.7.12 \
    --namespace kube-system \
    --set global.kubeProxyReplacement=strict \
    --set global.k8sServiceHost=API_SERVER_IP \
    --set global.k8sServicePort=API_SERVER_PORT

最后,作为最后一步,请验证Cilium是否已在所有节点上正确启动并准备就绪

如我们所见,没有kube-proxy pod。现在,我们可以像上面一样创建nginx部署和单个服务,并检查iptables。

#kubectl get pods -n benchmark -o wide               
NAME                  READY   STATUS    RESTARTS   AGE     IP       
nginx-8694799779-f8h82 1/1    Running   0          2d19h 101.96.1.18    
#kubectl get service nginx-svc  -o wide 
NAME        TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE     SELECTOR
nginx-svc   ClusterIP   101.67.9.101   <none>        80/TCP    2d19h   app=nginx


如我们所见,它为KUBE添加了一些基本规则,但没有KUBE-SERVICES存在。让我们像上面的测试一样添加100个服务,然后再次检查iptables。

完全一样。因此,我们摆脱了庞大的iptables规则,但是性能呢?让我们做一些测试。

简单的性能测试

为了测试这两个设置的性能,我们在两个设置上部署了一个运行ubuntu和apache bench的简单pod。我们确保apache工作台上的nodeSelector与运行nginx的节点不同,以便我们可以跨节点进行网络延迟。

此设置绝不是完全优化的生产设置,因此这里的结果是要注意的是,在不同的环境中情况可能有所不同,但是仍然可以在相同的基础上进行比较。部署如下所示

apiVersion: apps/v1
kind: Deployment
metadata:
   name: apachebench
spec:
  selector:
    matchLabels:
      app: apachebench
  template:
    metadata:
      labels:
        app: apachebench
    spec:
      nodeSelector:
        apptype: apachebench
      containers:
      - name: ubuntu
        image: ubuntu
        command: ["/bin/sleep""3650d"]

部署完成后,我们将得到以下内容

我们可以看到两个pod都在不同的节点上。现在,我们只需在ubuntu pod上安装apache bench,然后运行一个简单的ab测试,即可从单个连接发送100000个请求。

#kubectl exec -it apachebench-7db7b745c6-9cf9z /bin/bash -n benchmark
root@apachebench-7db7b745c6-9cf9z:/# ab -n 100000 -c 1 http://nginx-svc/
This is ApacheBench, Version 2.3 <$Revision: 1807734 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking nginx-svc (be patient)
Completed 10000 requests
Completed 20000 requests
Completed 30000 requests
Completed 40000 requests
Completed 50000 requests
Completed 60000 requests
Completed 70000 requests
Completed 80000 requests
Completed 90000 requests
Completed 100000 requests
Finished 100000 requests
Server Software:        nginx/1.17.10
Server Hostname:        nginx-svc
Server Port:            80
Document Path:          /
Document Length:        612 bytes
Concurrency Level:      1
Time taken for tests:   78.948 seconds
Complete requests:      100000
Failed requests:        0
Total transferred:      84600000 bytes
HTML transferred:       61200000 bytes
Requests per second:    1266.65 [#/sec] (mean)
Time per request:       0.789 [ms] (mean)
Time per request:       0.789 [ms] (mean, across all concurrent requests)
Transfer rate:          1046.47 [Kbytes/sec] received
Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       6
Processing:     0    0   0.1      0       4
Waiting:        0    0   0.1      0       4
Total:          0    1   0.1      1       7
Percentage of the requests served within a certain time (ms)
  50%      1
  66%      1
  75%      1
  80%      1
  90%      1
  95%      1
  98%      1
  99%      1
 100%      7 (longest request)
root@apachebench-7db7b745c6-9cf9z:/#


如果我们通过将集群中nginx服务的数量从1,100,1000,2000,3000,4000,5000增加到两个设置来运行此测试,并绘制比较图表,我们可以看到

正如我们看到的iptables(kube-proxy)和bpf设置开始时相对接近800微秒,但是随着服务数量的增加,iptables设置性能开始下降.bpf设置趋于或多或少保持恒定。现在很明显,当我们添加更多不同类型的负载时,这些负载甚至可能进一步不同,但是该图至少显示了在这种特定情况下,iptables的顺序处理如何在定义的大量规则上导致性能下降。

最终观察

我们已经看到了如何使用基于bpf的规则集摆脱基于kube-proxy和iptables的规则集。但是,这种性能下降在多大程度上真正影响了生产系统,这真的是必须做的吗?与往常一样,此类问题的答案是-这取决于:)。如果它是一个很小或中等的集群,并且没有很多服务或网络策略,那么kube-proxy版本可能会正常工作。实际上,它对许多kubernetes用户来说都是透明的。

但是,请想象一个大型集群,该集群在运行大量服务的多租户系统中具有100个租户。加上运行istio之类的副车代理的应用程序,其本身大量使用iptables。如果遇到iptables瓶颈,这种情况下所有租户的性能都会下降。因此,这种替代方法是一个有趣的探索途径。kube-proxy很有可能在某个时候开始在内部使用eBPF,并且所有这些更改都变得透明。

基于BPF的CNI驱动程序仍然相对较新,它们在等待时可能会有自己的优化,因此很难说是否已经准备好开箱即用,但这是值得期待的令人兴奋的领域。

参考文献

观看来自Facebook的精彩会议,了解他们在iptables上使用bpf防火墙的情况-http: vger.kernel.org/lpc_net2018_talks/ebpf-firewall-LPC.pdf和此处的视频https://www.youtube.com/watch ?v = XpBzEq1MwI8

为什么内核社区用BPF代替iptables?https://cilium.io/blog/2018/04/17/why-is-the-kernel-community-replacing-iptables

全面介绍eBPF 了解kubernetes网络 https://lwn.net/Articles/740157/

https://sysdig.com/blog/sysdig-and-falco-now-powered-by-ebpf/

https://medium.com/faun/kubernetes-without-kube-proxy-1c5d25786e18#3444

https://docs.cilium.io/en/v1.7/gettingstarted/kubeproxy-free/

https://serverfault.com/questions/1042705/kubernetes-and-iptables-forward-external-traffic-to-specific-nodeport

我等的船还不来
我等的人还不明白
寂寞默默沉没 沉入海 未来不再我还在

「——任贤齐 伤心太平洋」

【一个懂你的云原生知识共享平台】

「欢迎扫描下方二维码关注」


文章转载自云原生CTO,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论