searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

Ingress金丝雀发布介绍

2024-09-11 09:53:27
3
0

创建一个测试用namespace

apiVersion: v1
kind: Namespace
metadata:
  name: test-ingress
[root@192 test-ingress]# kubectl apply -f create-namespace.yaml 
namespace/test-ingress created
[root@192 test-ingress]# 
[root@192 test-ingress]# 
[root@192 test-ingress]# kubectl get namespace
NAME                   STATUS   AGE
default                Active   3d17h
istio-system           Active   3d17h
kube-node-lease        Active   3d17h
kube-public            Active   3d17h
kube-system            Active   3d17h
kubernetes-dashboard   Active   3d17h
test-ingress           Active   10s

安装nginx-ingress

参考官方文档:

https://kubernetes.github.io/ingress-nginx/deploy/#minikube

不同环境有不同的安装方式,这里以minikube环境为例

The ingress controller can be installed through minikube's addons system:

minikube addons enable ingress

检查安装成功

##查看ingress的访问入口,注意ingress是默认安装到ingress-nginx命名空间的
[root@192 test-ingress]# kubectl get services -n ingress-nginx
NAME                                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             NodePort    10.99.151.210   <none>        80:31359/TCP,443:32466/TCP   17m
ingress-nginx-controller-admission   ClusterIP   10.108.13.155   <none>        443/TCP                      17m

##可以看出是以nodeport方式暴露这个nginx的,我们可以看下我们的node地址
[root@192 test-ingress]# kubectl get nodes -o wide
NAME       STATUS   ROLES           AGE     VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION          CONTAINER-RUNTIME
minikube   Ready    control-plane   3d18h   v1.28.3   192.168.49.2   <none>        Ubuntu 22.04.4 LTS   5.14.0-503.el9.x86_64   docker://26.1.1

#测试下80端口,能访问说明成功
[root@192 test-ingress]# curl http://192.168.49.2:80
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx</center>
</body>
</html>

 

创建app-v1(Deployment)、svc-v1(Service)、ingress-v1(Ingress)

[root@192 test-ingress]# kubectl apply -f create-v1-all.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: app-v1
  name: app-v1
  namespace: test-ingress
spec:
  replicas: 1
  selector:
    matchLabels:
      app: app-v1
  template:
    metadata:
      labels:
        app: app-v1
    spec:
      containers:
      - image: nginx:alpine
        name: app-v1
        # 此处修改nginx主页显示为 this is app-v1
        command: ["/bin/sh","-c","echo 'this is app-v1'>/usr/share/nginx/html/index.html;nginx -g 'daemon off;'"]
---
apiVersion: v1
kind: Service
metadata:
  name: svc-v1
  namespace: test-ingress
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: app-v1
  type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-v1
  namespace: test-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
    - host: "www.test.com"
      http:
        paths:
          - path: "/"
            pathType: Prefix
            backend:
              service:
                # 指定服务的名称和端口
                name: svc-v1
                port:
                  number: 80

注意注解 kubernetes.io/ingress.class: "nginx"需要进行填入,表示使用nginx-ingress

验证访问ingress->app-v1

[root@192 test-ingress]# kubectl get ingress -n test-ingress
NAME         CLASS    HOSTS          ADDRESS        PORTS   AGE
ingress-v1   <none>   www.test.com   192.168.49.2   80      30m

[root@192 test-ingress]# kubectl get ingress -n test-ingress -o yaml
apiVersion: v1
items:
- apiVersion: networking.k8s.io/v1
  kind: Ingress
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"networking.k8s.io/v1","kind":"Ingress","metadata":{"annotations":{"kubernetes.io/ingress.class":"nginx"},"name":"ingress-v1","namespace":"test-ingress"},"spec":{"rules":[{"host":"www.test.com","http":{"paths":[{"backend":{"service":{"name":"svc-v1","port":{"number":80}}},"path":"/","pathType":"Prefix"}]}}]}}
      kubernetes.io/ingress.class: nginx
    creationTimestamp: "2024-09-09T02:22:37Z"
    generation: 1
    name: ingress-v1
    namespace: test-ingress
    resourceVersion: "92470"
    uid: 8129d9c3-8615-45cd-838b-cfd49725a282
  spec:
    rules:
    - host: www.test.com
      http:
        paths:
        - backend:
            service:
              name: svc-v1
              port:
                number: 80
          path: /
          pathType: Prefix
  status:
    loadBalancer:
      ingress:
      - ip: 192.168.49.2
kind: List
metadata:
  resourceVersion: ""
[root@192 test-ingress]# curl -H "Host:www.test.com" http://192.168.49.2/
this is app-v1
[root@192 test-ingress]# 

 

创建app-v2、svc-v2、ingress-v2

[root@192 test-ingress]# kubectl apply -f create-v2-all.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: app-v2
  name: app-v2
  namespace: test-ingress
spec:
  replicas: 1
  selector:
    matchLabels:
      app: app-v2
  template:
    metadata:
      labels:
        app: app-v2
    spec:
      containers:
      - image: nginx:alpine
        name: app-v2
        # 此处修改nginx主页显示为 this is app-v2
        command: ["/bin/sh","-c","echo 'this is app-v2'>/usr/share/nginx/html/index.html;nginx -g 'daemon off;'"]
---
apiVersion: v1
kind: Service
metadata:
  name: svc-v2
  namespace: test-ingress
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: app-v2
  type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-v2
  namespace: test-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
  rules:
    - host: "www.test.com"
      http:
        paths:
          - path: "/"
            pathType: Prefix
            backend:
              service:
                name: svc-v2
                port:
                  number: 80

nginx.ingress.kubernetes.io/canary: "true" #启动金丝雀

nginx.ingress.kubernetes.io/canary-weight: "10" #百分之10流量

验证访问流量

[root@192 test-ingress]# kubectl get ingress -n test-ingress
NAME         CLASS    HOSTS          ADDRESS        PORTS   AGE
ingress-v1   <none>   www.test.com   192.168.49.2   80      47m
ingress-v2   <none>   www.test.com   192.168.49.2   80      52s


[root@192 test-ingress]# for i in $(seq 1 20);do curl -H "Host: www.test.com" http://192.168.49.2;done
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v2
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v2
this is app-v1
this is app-v1
this is app-v1
##可以看出流量有大概10%的到了v2版本

验证不符合预期-回滚操作

删掉v2对应的ingress,再访问流量就回到原来的v1了

[root@192 test-ingress]# kubectl delete -f create-v2-all.yaml 
deployment.apps "app-v2" deleted
service "svc-v2" deleted
ingress.networking.k8s.io "ingress-v2" deleted
[root@192 test-ingress]# 
[root@192 test-ingress]# 
[root@192 test-ingress]# for i in $(seq 1 20);do curl -H "Host: www.test.com" http://192.168.49.2;done
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1

 

验证符合预期-慢慢放大v2版本流量

这里就不演示逐步加到app-v2的pod数量,以应对更多的流量

[root@192 test-ingress]# kubectl apply -f v2-ingress100.yaml 
ingress.networking.k8s.io/ingress-v2 configured
[root@192 test-ingress]# 
[root@192 test-ingress]# 
[root@192 test-ingress]# for i in $(seq 1 20);do curl -H "Host: www.test.com" http://192.168.49.2;done
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2

我们可以调整v2-ingress100.yaml 这个文件的流量比例,慢慢放大v2版本的流量

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-v2
  namespace: test-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "100"
spec:
  rules:
    - host: "www.test.com"
      http:
        paths:
          - path: "/"
            pathType: Prefix
            backend:
              service:
                name: svc-v2
                port:
                  number: 80

通常金丝雀规则验证完,我们会删掉金丝雀规则,直接让原非金丝雀流量也转到v2版本里去

我们可以创建一个新的ingress-v2-release规则,将所有流量转到svc-v2里去,例如如下。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-v2-release
  namespace: test-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
    - host: "www.test.com"
      http:
        paths:
          - path: "/"
            pathType: Prefix
            backend:
              service:
                # 指定服务的名称和端口
                name: svc-v2
                port:
                  number: 80

然后再删除ingress-v1和ingress-v2,以及svc-v1和app-v1,只保留svc-v2和app-v2

 

根据Header做金丝雀

[root@192 test-ingress]# kubectl apply -f v2-ingress-header-forward.yaml 
ingress.networking.k8s.io/ingress-v2 configured

[root@192 test-ingress]# kubectl get ingress -n test-ingress
NAME         CLASS    HOSTS          ADDRESS        PORTS   AGE
ingress-v1   <none>   www.test.com   192.168.49.2   80      69m
ingress-v2   <none>   www.test.com   192.168.49.2   80      8m37s

[root@192 test-ingress]# curl -H "Host: www.test.com"  http://192.168.49.2
this is app-v1
[root@192 test-ingress]# curl -H "Host: www.test.com" -H "version: v2" http://192.168.49.2
this is app-v2

以下yaml文件指定了根据header来做金丝雀

nginx.ingress.kubernetes.io/canary: "true"

nginx.ingress.kubernetes.io/canary-by-header: "version"

nginx.ingress.kubernetes.io/canary-by-header-value: "v2"

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-v2
  namespace: test-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-by-header: "version"
    nginx.ingress.kubernetes.io/canary-by-header-value: "v2"
spec:
  rules:
    - host: "www.test.com"
      http:
        paths:
          - path: "/"
            pathType: Prefix
            backend:
              service:
                name: svc-v2
                port:
                  number: 80

0条评论
作者已关闭评论
q****n
20文章数
0粉丝数
q****n
20 文章 | 0 粉丝
q****n
20文章数
0粉丝数
q****n
20 文章 | 0 粉丝
原创

Ingress金丝雀发布介绍

2024-09-11 09:53:27
3
0

创建一个测试用namespace

apiVersion: v1
kind: Namespace
metadata:
  name: test-ingress
[root@192 test-ingress]# kubectl apply -f create-namespace.yaml 
namespace/test-ingress created
[root@192 test-ingress]# 
[root@192 test-ingress]# 
[root@192 test-ingress]# kubectl get namespace
NAME                   STATUS   AGE
default                Active   3d17h
istio-system           Active   3d17h
kube-node-lease        Active   3d17h
kube-public            Active   3d17h
kube-system            Active   3d17h
kubernetes-dashboard   Active   3d17h
test-ingress           Active   10s

安装nginx-ingress

参考官方文档:

https://kubernetes.github.io/ingress-nginx/deploy/#minikube

不同环境有不同的安装方式,这里以minikube环境为例

The ingress controller can be installed through minikube's addons system:

minikube addons enable ingress

检查安装成功

##查看ingress的访问入口,注意ingress是默认安装到ingress-nginx命名空间的
[root@192 test-ingress]# kubectl get services -n ingress-nginx
NAME                                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             NodePort    10.99.151.210   <none>        80:31359/TCP,443:32466/TCP   17m
ingress-nginx-controller-admission   ClusterIP   10.108.13.155   <none>        443/TCP                      17m

##可以看出是以nodeport方式暴露这个nginx的,我们可以看下我们的node地址
[root@192 test-ingress]# kubectl get nodes -o wide
NAME       STATUS   ROLES           AGE     VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION          CONTAINER-RUNTIME
minikube   Ready    control-plane   3d18h   v1.28.3   192.168.49.2   <none>        Ubuntu 22.04.4 LTS   5.14.0-503.el9.x86_64   docker://26.1.1

#测试下80端口,能访问说明成功
[root@192 test-ingress]# curl http://192.168.49.2:80
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx</center>
</body>
</html>

 

创建app-v1(Deployment)、svc-v1(Service)、ingress-v1(Ingress)

[root@192 test-ingress]# kubectl apply -f create-v1-all.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: app-v1
  name: app-v1
  namespace: test-ingress
spec:
  replicas: 1
  selector:
    matchLabels:
      app: app-v1
  template:
    metadata:
      labels:
        app: app-v1
    spec:
      containers:
      - image: nginx:alpine
        name: app-v1
        # 此处修改nginx主页显示为 this is app-v1
        command: ["/bin/sh","-c","echo 'this is app-v1'>/usr/share/nginx/html/index.html;nginx -g 'daemon off;'"]
---
apiVersion: v1
kind: Service
metadata:
  name: svc-v1
  namespace: test-ingress
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: app-v1
  type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-v1
  namespace: test-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
    - host: "www.test.com"
      http:
        paths:
          - path: "/"
            pathType: Prefix
            backend:
              service:
                # 指定服务的名称和端口
                name: svc-v1
                port:
                  number: 80

注意注解 kubernetes.io/ingress.class: "nginx"需要进行填入,表示使用nginx-ingress

验证访问ingress->app-v1

[root@192 test-ingress]# kubectl get ingress -n test-ingress
NAME         CLASS    HOSTS          ADDRESS        PORTS   AGE
ingress-v1   <none>   www.test.com   192.168.49.2   80      30m

[root@192 test-ingress]# kubectl get ingress -n test-ingress -o yaml
apiVersion: v1
items:
- apiVersion: networking.k8s.io/v1
  kind: Ingress
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"networking.k8s.io/v1","kind":"Ingress","metadata":{"annotations":{"kubernetes.io/ingress.class":"nginx"},"name":"ingress-v1","namespace":"test-ingress"},"spec":{"rules":[{"host":"www.test.com","http":{"paths":[{"backend":{"service":{"name":"svc-v1","port":{"number":80}}},"path":"/","pathType":"Prefix"}]}}]}}
      kubernetes.io/ingress.class: nginx
    creationTimestamp: "2024-09-09T02:22:37Z"
    generation: 1
    name: ingress-v1
    namespace: test-ingress
    resourceVersion: "92470"
    uid: 8129d9c3-8615-45cd-838b-cfd49725a282
  spec:
    rules:
    - host: www.test.com
      http:
        paths:
        - backend:
            service:
              name: svc-v1
              port:
                number: 80
          path: /
          pathType: Prefix
  status:
    loadBalancer:
      ingress:
      - ip: 192.168.49.2
kind: List
metadata:
  resourceVersion: ""
[root@192 test-ingress]# curl -H "Host:www.test.com" http://192.168.49.2/
this is app-v1
[root@192 test-ingress]# 

 

创建app-v2、svc-v2、ingress-v2

[root@192 test-ingress]# kubectl apply -f create-v2-all.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: app-v2
  name: app-v2
  namespace: test-ingress
spec:
  replicas: 1
  selector:
    matchLabels:
      app: app-v2
  template:
    metadata:
      labels:
        app: app-v2
    spec:
      containers:
      - image: nginx:alpine
        name: app-v2
        # 此处修改nginx主页显示为 this is app-v2
        command: ["/bin/sh","-c","echo 'this is app-v2'>/usr/share/nginx/html/index.html;nginx -g 'daemon off;'"]
---
apiVersion: v1
kind: Service
metadata:
  name: svc-v2
  namespace: test-ingress
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: app-v2
  type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-v2
  namespace: test-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
  rules:
    - host: "www.test.com"
      http:
        paths:
          - path: "/"
            pathType: Prefix
            backend:
              service:
                name: svc-v2
                port:
                  number: 80

nginx.ingress.kubernetes.io/canary: "true" #启动金丝雀

nginx.ingress.kubernetes.io/canary-weight: "10" #百分之10流量

验证访问流量

[root@192 test-ingress]# kubectl get ingress -n test-ingress
NAME         CLASS    HOSTS          ADDRESS        PORTS   AGE
ingress-v1   <none>   www.test.com   192.168.49.2   80      47m
ingress-v2   <none>   www.test.com   192.168.49.2   80      52s


[root@192 test-ingress]# for i in $(seq 1 20);do curl -H "Host: www.test.com" http://192.168.49.2;done
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v2
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v2
this is app-v1
this is app-v1
this is app-v1
##可以看出流量有大概10%的到了v2版本

验证不符合预期-回滚操作

删掉v2对应的ingress,再访问流量就回到原来的v1了

[root@192 test-ingress]# kubectl delete -f create-v2-all.yaml 
deployment.apps "app-v2" deleted
service "svc-v2" deleted
ingress.networking.k8s.io "ingress-v2" deleted
[root@192 test-ingress]# 
[root@192 test-ingress]# 
[root@192 test-ingress]# for i in $(seq 1 20);do curl -H "Host: www.test.com" http://192.168.49.2;done
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1
this is app-v1

 

验证符合预期-慢慢放大v2版本流量

这里就不演示逐步加到app-v2的pod数量,以应对更多的流量

[root@192 test-ingress]# kubectl apply -f v2-ingress100.yaml 
ingress.networking.k8s.io/ingress-v2 configured
[root@192 test-ingress]# 
[root@192 test-ingress]# 
[root@192 test-ingress]# for i in $(seq 1 20);do curl -H "Host: www.test.com" http://192.168.49.2;done
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2
this is app-v2

我们可以调整v2-ingress100.yaml 这个文件的流量比例,慢慢放大v2版本的流量

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-v2
  namespace: test-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "100"
spec:
  rules:
    - host: "www.test.com"
      http:
        paths:
          - path: "/"
            pathType: Prefix
            backend:
              service:
                name: svc-v2
                port:
                  number: 80

通常金丝雀规则验证完,我们会删掉金丝雀规则,直接让原非金丝雀流量也转到v2版本里去

我们可以创建一个新的ingress-v2-release规则,将所有流量转到svc-v2里去,例如如下。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-v2-release
  namespace: test-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
    - host: "www.test.com"
      http:
        paths:
          - path: "/"
            pathType: Prefix
            backend:
              service:
                # 指定服务的名称和端口
                name: svc-v2
                port:
                  number: 80

然后再删除ingress-v1和ingress-v2,以及svc-v1和app-v1,只保留svc-v2和app-v2

 

根据Header做金丝雀

[root@192 test-ingress]# kubectl apply -f v2-ingress-header-forward.yaml 
ingress.networking.k8s.io/ingress-v2 configured

[root@192 test-ingress]# kubectl get ingress -n test-ingress
NAME         CLASS    HOSTS          ADDRESS        PORTS   AGE
ingress-v1   <none>   www.test.com   192.168.49.2   80      69m
ingress-v2   <none>   www.test.com   192.168.49.2   80      8m37s

[root@192 test-ingress]# curl -H "Host: www.test.com"  http://192.168.49.2
this is app-v1
[root@192 test-ingress]# curl -H "Host: www.test.com" -H "version: v2" http://192.168.49.2
this is app-v2

以下yaml文件指定了根据header来做金丝雀

nginx.ingress.kubernetes.io/canary: "true"

nginx.ingress.kubernetes.io/canary-by-header: "version"

nginx.ingress.kubernetes.io/canary-by-header-value: "v2"

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-v2
  namespace: test-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-by-header: "version"
    nginx.ingress.kubernetes.io/canary-by-header-value: "v2"
spec:
  rules:
    - host: "www.test.com"
      http:
        paths:
          - path: "/"
            pathType: Prefix
            backend:
              service:
                name: svc-v2
                port:
                  number: 80

文章来自个人专栏
云技术专栏
20 文章 | 1 订阅
0条评论
作者已关闭评论
作者已关闭评论
0
0