K8S部署Traefik与Ingress、IngressRoute教程
前言:
关键字:Kubernetes、K8S、Traefik、IngerssRoute、Ingress;
本博文为原创博文,欢迎转载,但请务必注明出处;
为什么有本篇博文?因为博主有时间~~但真正的原因不是这个,是因为博主最近在搞K8S,其中Ingress这个K8S定义的资源的使用,卡了老子好几天!!!而看我博客的人都知道,博主是不会写那些完全没营养的文章的,之所以写这个,是因为博主有理由相信这个东西会卡住很多使用K8S的读者!先说说这个Ingress是什么,网上说得太复杂了,很简单,就是一个定义“反向代理”工具七层转发的“转发规则”的K8S资源!这个“反向代理”指的是什么?就是NGINX、TRAEFIK等等这些软件;这个“转发规则”指的是什么?就是按域名转发、按URI转发、转到那里的端口等等的规则!
上面说了,Ingress只是反向代理软件的代理规则定义,那要如何使用Ingress呢?那就需要K8S入面说的“Ingress Controller”[Ingress控制器]这个东西,那这个东西是又什么呢?“Ingress Controller”就是NGINX、TRAEFIK等等这些反向代理软件,但这些代理软件与平时我们使用的有点不同,这些反向代理软件都作了专门为在K8S上使用,而作了特殊的处理,可以理解为:NGINX专用于K8S的特别版,TRAEFIK专用于K8S的特别版……
好了,现在我们明白了,要使用Ingress这个K8S资源,那意味着我们必须在K8S上安装“Ingress Controller”这个东西[K8S默认不会安装这个];好了,现在我们为了使用Ingress这个资源,我们要选择使用那一个“Ingress Controller”;按照目前K8S的“Ingress Controller”控制器的流行度,博主只能说,TRAEFIK排行第一!!!忘记说了,要使用“Ingress Controller”,需要在K8S集群中运行对应的容器;
OK!上面说到TRAEFIK这个反向代理程序,怎么说呢?博主认为,官方文档简直反人类,博主在上面转了几天,就是没看出个所以来……当然,期间,博主是百度、GOOGLE、K8S官方文档、TRAEFIK官方文档四管齐下!!!好家伙!!!竟然没有能说清楚这个东西的文章!!!百度、CSND出来的东西,是人看的吗?过时不说,至少你也应该说清楚那是在做什么吧?或者能跑起来?而且那个编排,博客真的没心情看啊~~~
博主说了很多废话是不是?没办法,还是要说的;另外,博主还先说了,虽然博主在这篇博文中说清楚博文标题的内容,但是,如果你对K8S的资源不熟悉,你是无法看懂这篇博文的,毕竟博主不可能把每个配置项都解释一遍,但博客会让你真正的使用起“TRAEFIK”+“Ingress”/“IngressRoute”这个两个组合~~怎么又来了个“IngressRoute”?博主之后的文章会说明的~~~另外,博主是有想过提供给大家完整的YAML文件的,但是,文章会过时啊,官方文档会变啊,提供的内容是有可能失效啊,所以博主决定教的是方法~~
这里是我写完博文后加的,这不是给人看的,博主是LZ……第二节与第三节说明是一样的……
一、要求
你要有一个自己的K8S集群进行测试;博主是不会在本篇博文中教K8S搭建的,如果你连搭建K8S也存在问题,那你很可能不适合看本篇博文;本博文的测试环境如下图:
注意:直接复制本博客任意博文中的配置/代码,"空格"字符的前面将可能产生不可见的字符"M-BM-",从而造成文件不可用,这是字符编码的问题~若必需使用复制粘贴方式,请务必手动替换掉配置项中的所有空格!!!或尝试使用以下命令过滤掉所有"M-BM-"字符~
1 2 3 4 |
sed 's/\xc2\xa0/ /g' [文件名称] # LINUX下可使用以下命令查看不可见字符的情况 cat -A [文件名称] |
二、Traekif部署与使用[使用Ingress]
这个标题为什么是这么样的???因为"TRAEFIK"+"Ingress"方式与"TRAEFIK"+"IngressRoute"方式,对TRAEFIK的部署要求是不同的;现在请打开TRAEFIK的官方网页[ https://doc.traefik.io/traefik/routing/providers/kubernetes-ingress/ ],找到下图中的“RBAC”位置,复制其中的YAML代码:
官方安装方法,没什么好说的,就像你WINDOW安装EXE一样,完全不需要问为什么~~~LINUX执行以下命令:
1 |
touch 11_RBAC.yaml && vim 11_RBAC.yaml |
粘贴官方的YAML代码[复制官方的!!官方可能会更新!!],如下;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: traefik-ingress-controller rules: - apiGroups: - "" resources: - services - endpoints - secrets verbs: - get - list - watch - apiGroups: - extensions - networking.k8s.io resources: - ingresses - ingressclasses verbs: - get - list - watch - apiGroups: - extensions resources: - ingresses/status verbs: - update --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: traefik-ingress-controller roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: traefik-ingress-controller subjects: - kind: ServiceAccount name: traefik-ingress-controller namespace: default |
在上一步完成后,找到下图中的“Traefik”位置,作用为部署"traefik"POD/容器[注意:在同一WEB页面,别去了其它页面];LINUX执行以下命令:
1 |
touch 12_Traefik.yaml && vim 12_Traefik.yaml |
复制其中“Traefik”的官方YAML代码[复制官方的!!官方可能会更新!!];注意:本处只需要复制"ServiceAccount"资源与"Deployment"资源,并按照下面的配置文件中的提示进行修改、删除及增加;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
apiVersion: v1 kind: ServiceAccount metadata: name: traefik-ingress-controller --- kind: Deployment # [修改] 修改本处为"DaemonSet"资源类型 apiVersion: apps/v1 metadata: name: traefik labels: app: traefik spec: replicas: 1 # [删除] 删除本项["DaemonSet"资源没有此配置] selector: matchLabels: app: traefik template: metadata: labels: app: traefik spec: serviceAccountName: traefik-ingress-controller containers: - name: traefik image: traefik:v2.3 args: - --log.level=INFO # [增加] Traefik的日志级别 - --api # [增加] 允许访问API接口 - --api.insecure # [增加] 允许以HTTP访问API接口 - --entrypoints.web.address=:80 - --entrypoints.websecure.Address=:443 # [增加] 定义HTTPS的接收端口 - --providers.kubernetesingress ports: - name: web containerPort: 80 hostPort: 80 # [增加] 暴露Traefik容器的80端口至节点[HTTP转发] - name: websecure # [增加] 增加HTTPS转发的支持[选用] containerPort: 443 # [增加] Traefik容器上使用的端口[对应上面的配置][选用] hostPort: 443 # [增加] 暴露Traefik容器的443端口至节点[HTTPS转发] - name: admin # [增加] 实际上加不加也可以 containerPort: 8080 # [增加] 这是Traefik的DashBoard访问端口 hostPort: 8080 # [增加] 暴露Tracfik容器的8080端口至节点[尽量别使用,可以后期通用转发实现访问][选用] |
说明:官方文档是以"Deployment"资源的方式部署TRAEFIK容器的[反向代理],但无论如何,现实情况下,一般是不会这样使用的,无论是学习者的测试,还是生产环境中使用;实际上,无论在"TRAEFIK"+"Ingress"方式还是在"TRAEFIK"+"IngressRoute"方式下,我们都是希望TRAEFIK成为K8S集群中的边缘反向代理来使用,要不然,Igress的意义就变得不大了。而且,以"Deploymet"的方式部署,POD的位置是不确定的,而作为边缘反向代理,我们希望TRAEFIK的POD的位置是固定的,所以我们需要"DeamonSet"的方式去部署TRAEFIK。当然,博主并不是说"Deployment"的方式不行,事实上如果结合公有云的"Ingress"服务,这是可行的,但是问题是,我们为什么一定非要使用公有云的"Ingress"服务?而且,谁会在测试阶段特别去公有云搞个"Ingress"?而且这可是至少还需要三台公有云的VPS做成K8S然后测试的啊!!所以说,官方文档这样写,可真是一个大坑!!!
好了,上面说了,我们是希望TRAEFIK成为K8S集群的边缘反向代理来使用的,所以现在作为读者,你们应该可以理解上面的增加的"hostPort: xxx"的配置是为什么了吧?我们需要在节点上暴露端口以进行访问!!其实上面YAML中的说明,博主感觉已经对那些"增加、删除、修改"项的作用说得够清楚了,也有解释至此;
现在,通过以下命令来在K8S集群中启用上面写好的YAML配置文件吧~在完成部署后,你便能以"http://NODE_IP:8080"的方式,在浏览器上面访问TRAEFIK的DashBoard后台了~~读者可以试试以各个节点的IP:8080的方式访问,基于"DaemonSet"资源的使用方式,只要非K8S-MASTER主节点均可以访问;
1 |
kubectl apply -f 11_RBAC.yaml && kubectl apply -f 12_Traefik.yaml |
是的,现在我们已经可以通过"http://NODE_IP:8080"的方式去访问TRAEFIK的后台了,但现实中,我们真会这样使用吗?并不会是吧?首先,这会占用掉所有运行TRAEFIK节点的"8080"端口["DaemonSet"],其次,我明明已经有TRAEFIK作为K8S集群的边缘反向代理了,也已经为其暴露了"80"端口了,同时,我们还想以域名的方式去访问;这时,"Ingress"资源出场了!现在,我们以域名"treafik.domain.local",从"80"端口访问TRAEFIK的后台为目标开始进行配置;
先想想我们需要做什么?1、我们需要为TRAEFIK的DashBoard后台定义一个"Service"资源[指向TRAEFIK-"POD"的"8080"端口];2、我们需要为前面创建的"Service"资源创建一个对应的转发规则["Ingress"资源];OK,LINUX上操作以下命令:
1 |
touch 13_SvcIngress.yaml && vim 13_SvcIngress.yaml |
复制粘贴以下YAML代码[这个不是官方的,官方没有]:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
apiVersion: v1 kind: Service metadata: name: traefik spec: ports: - protocol: TCP name: traefik port: 8080 selector: app: traefik --- apiVersion: extensions/v1beta1 kind: Ingress metadata: name: traefik spec: rules: - host: traefik.domain.local # 域名定义 http: paths: - path: / backend: serviceName: traefik # "Service"资源的定义名称 servicePort: 8080 # "Service"资源的端口"service.spec.ports.port" |
以上YAML文件,我们创建了一个"Service"资源[指向TRAEFIK容器的"8080"端口],一个"Ingress"资源[设定了前面创建的"Service"资源的七层转发规则];在K8S集群中执行以下命令:
1 |
kubectl apply -f 13_SvcIngress.yaml |
由于我们使用的是虚构域名,所以我们需要在"hosts"文件中作对应的域名解释;以下假设你的其中一个节点IP为"192.168.100.31"[注意:你可以使用任何节点],要使用WINDOW中的浏览器访问"traefik.domain.local"[对应"13_SvcIngress.yaml"文件中"Ingress"资源中的配置];现在,在WINDOW的CMD运行以下命令[或者直接在"运行"中执行]:
1 2 |
notepad c:\windows\system32\drivers\etc\hosts # 注:LINUX中为"vim /etc/hosts" |
输入以下域名解释并保存:
1 |
192.168.100.31 traefik.domain.local |
现在你可以WINDOW的浏览器中输入"http://traefik.domain.locl"来访问TRAEFIK的DashBoard后台了;并且,你现在可以删除"12_Traefik.yaml"文件中的"hostPort: 8080"配置,为节点省出"8080"端口了;
至此,K8S的"Traefik"+"Ingress"的部署与使用说明至此结束~~
三、Traekif部署与使用[使用IngressRoute]
什么是"IngressRoute"?简单来说,"IngressRoute"就是TRAEFIK官方做的"Ingress",只不过是一个是K8S官方做的,一个是TRAEFIK官方做的,这两个K8S"资源"是同一个东西,也许TRAEFIK觉得K8S做的"Ingress"太垃圾了吧?既然TRAEFIC官方做了"IngressRoute"资源,那应该是更好的,但就TRAEFIK的那个官方文档,博主现在的建议是,还是不要在生产中使用了,除非你觉得你能碾压TRAEFIK的那个官方文档~但无论如何,这可能是未来的趋势,所以,博主这里顺便也一起写了~~
现在请打开TRAEFIK的官方网页[ https://doc.traefik.io/traefik/routing/providers/kubernetes-crd/ ],找到下图中的“Resource Definition”位置,同前,官方安装方法,作用为安装TRAEFIK的"资源"定义,LINUX执行以下命令:
1 |
touch 01_Defindition.yaml && vim 01_Defindition.yaml |
粘贴YAML代码[复制官方的!!官方可能会更新!!];
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: ingressroutes.traefik.containo.us spec: group: traefik.containo.us version: v1alpha1 names: kind: IngressRoute plural: ingressroutes singular: ingressroute scope: Namespaced --- apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: middlewares.traefik.containo.us spec: group: traefik.containo.us version: v1alpha1 names: kind: Middleware plural: middlewares singular: middleware scope: Namespaced --- apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: ingressroutetcps.traefik.containo.us spec: group: traefik.containo.us version: v1alpha1 names: kind: IngressRouteTCP plural: ingressroutetcps singular: ingressroutetcp scope: Namespaced --- apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: ingressrouteudps.traefik.containo.us spec: group: traefik.containo.us version: v1alpha1 names: kind: IngressRouteUDP plural: ingressrouteudps singular: ingressrouteudp scope: Namespaced --- apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: tlsoptions.traefik.containo.us spec: group: traefik.containo.us version: v1alpha1 names: kind: TLSOption plural: tlsoptions singular: tlsoption scope: Namespaced --- apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: tlsstores.traefik.containo.us spec: group: traefik.containo.us version: v1alpha1 names: kind: TLSStore plural: tlsstores singular: tlsstore scope: Namespaced --- apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: traefikservices.traefik.containo.us spec: group: traefik.containo.us version: v1alpha1 names: kind: TraefikService plural: traefikservices singular: traefikservice scope: Namespaced |
在上一步完成后,找到下图中的“RBAC”位置[注意:在同一WEB页面,别去了其它页面];
1 |
touch 02_RBAC.yaml && vim 02_RBAC.yaml |
粘贴YAML代码,作用为部署"RABC"认证[复制官方的!!官方可能会更新!!];
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: traefik-ingress-controller rules: - apiGroups: - "" resources: - services - endpoints - secrets verbs: - get - list - watch - apiGroups: - extensions - networking.k8s.io resources: - ingresses - ingressclasses verbs: - get - list - watch - apiGroups: - extensions resources: - ingresses/status verbs: - update - apiGroups: - traefik.containo.us resources: - middlewares - ingressroutes - traefikservices - ingressroutetcps - ingressrouteudps - tlsoptions - tlsstores verbs: - get - list - watch --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: traefik-ingress-controller roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: traefik-ingress-controller subjects: - kind: ServiceAccount name: traefik-ingress-controller namespace: default |
在上一步完成后,找到下图中的“Traefik”位置,作用为部署"traefik"容器[注意:在同一WEB页面,别去了其它页面];LINUX执行以下命令:
1 |
touch 03_Traefik.yaml && vim 03_Traefik.yaml |
复制其中“Traefik”的官方YAML代码;注意:本处只需要复制"ServiceAccount"资源与"Deployment"资源,并按照下面的配置文件中的提示进行修改、删除及增加;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
apiVersion: v1 kind: ServiceAccount metadata: name: traefik-ingress-controller --- kind: Deployment # [修改] 修改本处为"DaemonSet"资源类型 apiVersion: apps/v1 metadata: name: traefik labels: app: traefik spec: replicas: 1 # [删除] 删除本项["DaemonSet"资源没有此配置] selector: matchLabels: app: traefik template: metadata: labels: app: traefik spec: serviceAccountName: traefik-ingress-controller containers: - name: traefik image: traefik:v2.3 args: - --log.level=DEBUG # [修改] Traefik的日志级别,定义为"INFO"即可[选用] - --api - --api.insecure - --entrypoints.web.address=:80 # [说明] 定义HTTP的接收端口 - --entrypoints.websecure.Address=:443 # [增加] 定义HTTPS的接收端口 - --entrypoints.tcpep.address=:8000 # [说明] 定义TCP的接收端口 - --entrypoints.udpep.address=:9000/udp # [说明] 定义UDP的接收端口 - --providers.kubernetescrd ports: - name: web containerPort: 80 hostPort: 80 # [增加] 暴露Traefik容器的80端口至节点[HTTP转发] - name: websecure # [增加] 增加HTTPS转发的支持[选用] containerPort: 443 # [增加] Traefik容器上使用的端口[对应上面的配置][选用] hostPort: 443 # [增加] 暴露Traefik容器的443端口至节点[HTTPS转发] - name: admin # [说明] 这是Traefik的DashBoard访问端口 containerPort: 8080 hostPort: 8080 # [增加] 暴露Tracfik容器的8080端口至节点[尽量别使用,可以后期通用转发实现访问][选用] - name: tcpep containerPort: 8000 hostPort: 8000 # [增加] 暴露Tracfik容器的8000端口至节点[TCP转发] - name: udpep containerPort: 9000 hostPort: 9000 # [增加] 暴露Tracfik容器的9000端口至节点[UDP转发] |
说明:现在,通过以下命令来在K8S集群中启用上面写好的YAML配置文件吧~在完成部署后,你便能以"http://NODE_IP:8080"的方式,在浏览器上面访问TRAEFIK的DashBoard后台了~~读者可以试试以各个节点的IP:8080的方式访问,基于"DaemonSet"资源的使用方式,只要非K8S-MASTER主节点均可以访问;
1 |
kubectl apply -f 01_Defindition.yaml && kubectl apply -f 02_RBAC.yaml && kubectl apply -f 03_Traefik.yaml |
是的,现在我们已经可以通过"http://NODE_IP:8080"的方式去访问TRAEFIK的后台了,但现实中,我们真会这样使用吗?并不会是吧?首先,这会占用掉所有运行TRAEFIK节点的"8080"端口["DaemonSet"],其次,我明明已经有TRAEFIK作为K8S集群的边缘反向代理了,也已经为其暴露了"80"端口了,同时,我们还想以域名的方式去访问;这次,"IngressRoute"资源出场了!现在,我们以域名"treafik.domain.local",从"80"端口访问TRAEFIK的后台为目标开始进行配置;
先想想我们需要做什么?1、我们需要为TRAEFIK的DashBoard后台定义一个"Service"资源[指向TRAEFIK容器的"8080"端口];2、我们需要为前面创建的"Service"资源创建一个对应的规则["IngressRoute"资源];OK,LINUX上操作以下命令:
1 |
touch 04_SvcIngressRoute.yaml && vim 04_SvcIngressRoute.yaml |
复制粘贴以下YAML代码[不是官方的,官方没有此代码]:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
apiVersion: v1 kind: Service metadata: name: traefik spec: ports: - protocol: TCP name: traefik port: 8080 selector: app: traefik --- apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: traefik namespace: default spec: entryPoints: - web routes: - match: Host(`traefik.domain.local`) kind: Rule services: - name: traefik port: 8080 |
以上YAML文件,我们创建了一个"Service"资源[指向TRAEFIK容器的"8080"端口],一个"IngressRoute"资源[设定了前面创建的"Service"资源的七层转发规则];在K8S集群中执行以下命令:
1 |
kubectl apply -f 04_SvcIngressRoute.yaml |
由于我们使用的是虚构域名,所以我们需要在"hosts"文件中作对应的域名解释;以下假设你的其中一个节点IP为"192.168.100.31"[注意:你可以使用任何节点],使用WINDOW中的浏览器访问"traefik.domain.local";在WINDOW的CMD运行以下命令[或者直接在"运行"中执行]:
1 2 |
notepad c:\windows\system32\drivers\etc\hosts # 注:LINUX中为"vim /etc/hosts" |
输入以下域名解释并保存:
1 |
192.168.100.31 traefik.domain.local |
现在你可以WINDOW的浏览器中输入"http://traefik.domain.locl"来访问TRAEFIK的DashBoard后台了;并且,你现在可以删除"12_Traefik.yaml"文件中的"hostPort: 8080"配置,为节点省出"8080"端口了;
至此,你应该了解K8S的"IngressRoute"资源是如何使用的了~~
四、"Ingress"/"IngressRoute"的作用
"Ingress"/"IngressRoute"在实际生产环境中到底有什么作用呢?本处做一个实验,并且以"Ingress"+"traefik"的部署方式为例[即"二、Traekif部署与使用[使用Ingress]"];
现在返回去看看第二节的"hosts"文件域名解释的截图,除了定义了"traefik.domin.local"外,博主还定义了一个"ingress.domin.local"的域名,如果你之前尝试过访问这个域名,你会得到一个"错误"的应答,这是必然的,因为我们并没有对这个域名进行了路由规则的定义;现在,假设我们想使用"ingress.domain.local"去访问TRAEFIK的后台,并且不想再使用"traefik.domina.local"这一域名去访问了,那怎么做?
执行以下命令:"kubectl edit ingress traefik",修改"host: traefik.domain.local" --> "host: ingress.domain.local",保存并退出;
现在你可以通过浏览器中输入"http://ingress.domain.local"来访问TRAEFIK的DashBoard后台了[记得配置"hosts"文件];并且,如果你访问原来的"traefik.domain.local",会得到一个错误的应答;
作用:通过上面的实验,"Ingress"资源的作用出来了,"Ingress"资源能实时的将配置规则写入至边缘反向代理中,而作为管理者的我们,并不需要担心边缘反向代理[POD]中的配置;实际上,"Ingress"资源的作用并不至此,有兴趣的读者自行参照K8S官方对"Ingress"资源的解释;而TRAEFIK官方的"IngressRoute"资源,其功能则更为强大,可惜的是文档写得太烂,从手有点困难~~
结、后记
博主在写完这篇博文后,感慨,这是给人看的吗?就你还有资格吐糟网上那些文章?????别写博客了……《完》
LZ 写的很用心
2022-09-27 下午10:26