最近两年一直在使用 kubeadm 部署 Kubernetes 集群,总体来说配合一些自己小脚本还有一些自动化工具还算是方便;但是全容器化稳定性确实担忧,也遇到过莫名其妙的证书过期错误,最后重启大法解决这种问题;所以也在探索比较方便的二进制部署方式,比如这个 k0s。
一、k0s 介绍
The Simple, Solid & Certified Kubernetes Distribution.
k0s 可以认为是一个下游的 Kubernetes 发行版,与原生 Kubernetes 相比,k0s 并未阉割大量 Kubernetes 功能;k0s 主要阉割部分基本上只有树内 Cloud provider,其他的都与原生 Kubernetes 相同。
k0s 自行编译 Kubernetes 源码生成 Kubernetes 二进制文件,然后在安装后将二进制文件释放到宿主机再启动;这种情况下所有功能几乎与原生 Kubernetes 没有差异。
二、k0sctl 使用
k0sctl 是 k0s 为了方便快速部署集群所提供的工具,有点类似于 kubeadm,但是其扩展性要比 kubeadm 好得多。在多节点的情况下,k0sctl 通过 SSH 链接目标主机然后按照步骤释放文件并启动 Kubernetes 相关服务,从而完成集群初始化。
2.1、k0sctl 安装集群
安装过程中会自动下载相关镜像,需要保证所有节点可以扶墙,如何离线安装后面讲解。安装前保证目标机器的 hostname 为非域名形式,否则可能会出现一些问题。以下是一个简单的启动集群示例:
首先安装 k0sctl:
#安装k0sctl
wgethttps://github.com/k0sproject/k0sctl/releases/download/v0.9.0/k0sctl-linux-x64
chmod+xk0sctl-linux-x64
mvk0sctl-linux-x64/usr/local/bin/k0sctl
然后编写 k0sctl.yaml 配置文件:
apiVersion:k0sctl.k0sproject.io/v1beta1
kind:Cluster
metadata:
name:k0s-cluster
spec:
hosts:
-ssh:
address:10.0.0.11
user:root
port:22
keyPath:/Users/bleem/.ssh/id_rsa
role:controller+worker
-ssh:
address:10.0.0.12
user:root
port:22
keyPath:/Users/bleem/.ssh/id_rsa
role:controller+worker
-ssh:
address:10.0.0.13
user:root
port:22
keyPath:/Users/bleem/.ssh/id_rsa
role:controller+worker
-ssh:
address:10.0.0.14
user:root
port:22
keyPath:/Users/bleem/.ssh/id_rsa
role:worker
-ssh:
address:10.0.0.15
user:root
port:22
keyPath:/Users/bleem/.ssh/id_rsa
role:worker
k0s:
version:1.21.2+k0s.1
config:
apiVersion:k0s.k0sproject.io/v1beta1
kind:Cluster
metadata:
name:k0s
spec:
api:
address:10.0.0.11
port:6443
k0sApiPort:9443
sans:
-10.0.0.11
-10.0.0.12
-10.0.0.13
storage:
type:etcd
etcd:
peerAddress:10.0.0.11
network:
kubeProxy:
disabled:false
mode:ipvs
最后执行 apply 命令安装即可,安装前确保你的操作机器可以 SSH 免密登陆所有目标机器:
➜tmpk0sctlapply-cbak.yaml
⠀⣿⣿⡇⠀⠀⢀⣴⣾⣿⠟⠁⢸⣿⣿⣿⣿⣿⣿⣿⡿⠛⠁⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀█████████████████████
⠀⣿⣿⡇⣠⣶⣿⡿⠋⠀⠀⠀⢸⣿⡇⠀⠀⠀⣠⠀⠀⢀⣠⡆⢸⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀█████████
⠀⣿⣿⣿⣿⣟⠋⠀⠀⠀⠀⠀⢸⣿⡇⠀⢰⣾⣿⠀⠀⣿⣿⡇⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀█████████
⠀⣿⣿⡏⠻⣿⣷⣤⡀⠀⠀⠀⠸⠛⠁⠀⠸⠋⠁⠀⠀⣿⣿⡇⠈⠉⠉⠉⠉⠉⠉⠉⠉⢹⣿⣿⠀█████████
⠀⣿⣿⡇⠀⠀⠙⢿⣿⣦⣀⠀⠀⠀⣠⣶⣶⣶⣶⣶⣶⣿⣿⡇⢰⣶⣶⣶⣶⣶⣶⣶⣶⣾⣿⣿⠀██████████████████████
k0sctl0.0.0Copyright2021,k0sctlauthors.
Anonymizedtelemetryofusagewillbesenttotheauthors.
Bycontinuingtousek0sctlyouagreetotheseterms:
https://k0sproject.io/licenses/eula
INFO==>Runningphase:Connecttohosts
INFO[ssh]10.0.0.15connected
INFO[ssh]10.0.0.11connected
INFO[ssh]10.0.0.12connected
INFO[ssh]10.0.0.14connected
INFO[ssh]10.0.0.13connected
INFO==>Runningphase:Detecthostoperatingsystems
INFO[ssh]10.0.0.11isrunningUbuntu20.04.2LTS
INFO[ssh]10.0.0.12isrunningUbuntu20.04.2LTS
INFO[ssh]10.0.0.14isrunningUbuntu20.04.2LTS
INFO[ssh]10.0.0.13isrunningUbuntu20.04.2LTS
INFO[ssh]10.0.0.15isrunningUbuntu20.04.2LTS
INFO==>Runningphase:Preparehosts
INFO==>Runningphase:Gatherhostfacts
INFO[ssh]10.0.0.11discoveredens33asprivateinterface
INFO[ssh]10.0.0.13discoveredens33asprivateinterface
INFO[ssh]10.0.0.12discoveredens33asprivateinterface
INFO==>Runningphase:Downloadk0sonhosts
INFO[ssh]10.0.0.11downloadingk0s1.21.2+k0s.1
INFO[ssh]10.0.0.13downloadingk0s1.21.2+k0s.1
INFO[ssh]10.0.0.12downloadingk0s1.21.2+k0s.1
INFO[ssh]10.0.0.15downloadingk0s1.21.2+k0s.1
INFO[ssh]10.0.0.14downloadingk0s1.21.2+k0s.1
......
稍等片刻后带有三个 Master 和两个 Node 的集群将安装完成:
#注意:目标机器 hostname 不应当为域名形式,这里的样例是已经修复了这个问题
k1.node➜~k0skubectlgetnode-owide
NAMESTATUSROLESAGEVERSIONINTERNAL-IPEXTERNAL-IPOS-IMAGEKERNEL-VERSIONCONTAINER-RUNTIME
k1.nodeReady10mv1.21.2+k0s10.0.0.11Ubuntu20.04.2LTS5.4.0-77-genericcontainerd://1.4.6
k2.nodeReady10mv1.21.2+k0s10.0.0.12Ubuntu20.04.2LTS5.4.0-77-genericcontainerd://1.4.6
k3.nodeReady10mv1.21.2+k0s10.0.0.13Ubuntu20.04.2LTS5.4.0-77-genericcontainerd://1.4.6
k4.nodeReady10mv1.21.2+k0s10.0.0.14Ubuntu20.04.2LTS5.4.0-77-genericcontainerd://1.4.6
k5.nodeReady10mv1.21.2+k0s10.0.0.15Ubuntu20.04.2LTS5.4.0-77-genericcontainerd://1.4.6
2.2、k0sctl 的扩展方式
与 kubeadm 不同,k0sctl 几乎提供了所有安装细节的可定制化选项,其通过三种行为来完成扩展:
-
文件上传:k0sctl 允许定义在安装前的文件上传,在安装之前 k0sctl 会把已经定义的相关文件全部上传到目标主机,包括不限于 k0s 本身二进制文件、离线镜像包、其他安装文件、其他辅助脚本等。
-
Manifests 与 Helm:当将特定的文件上传到 Master 节点的 /var/lib/k0s/manifests 目录时,k0s 在安装过程中会自动应用这些配置,类似 kubelet 的 static pod 一样,只不过 k0s 允许全部资源(包括不限于 Deployment、DaemonSet、namespace 等);同样也可以直接在 k0sctl.yaml 添加 Helm 配置,k0s 也会以同样的方式帮你管理。
-
辅助脚本:可以在每个主机下配置 hooks 选项来实现执行一些特定的脚本(文档里没有,需要看源码),以便在特定情况下做点骚操作。
2.3、k0sctl 使用离线镜像包
基于上面的扩展,k0s 还方便的帮我们集成了离线镜像包的自动导入,我们只需要定义一个文件上传,将镜像包上传到 /var/lib/k0s/images/ 目录后,k0s 会自定将其倒入到 containerd 中而无需我们手动干预:
apiVersion:k0sctl.k0sproject.io/v1beta1
kind:Cluster
metadata:
name:k0s-cluster
spec:
hosts:
-ssh:
address:10.0.0.11
user:root
port:22
keyPath:/Users/bleem/.ssh/id_rsa
role:controller+worker
#files配置将会在安装前将相关文件上传到目标主机
files:
-name:image-bundle
src:/Users/bleem/tmp/bundle_file
#在该目录下的image压缩包将会被自动导入到containerd中
dstDir:/var/lib/k0s/images/
perm:0755
......
关于 image 压缩包(bundle_file)如何下载以及自己自定义问题请参考官方 Airgap install[1] 文档。
2.4、切换 CNI 插件
默认情况下 k0s 内部集成了两个 CNI 插件:Calico 和 kube-router;如果我们使用其他的 CNI 插件例如 Flannel,我们只需要将默认的 CNI 插件设置为 custom,然后将 Flannel 的部署 yaml 上传到一台 Master 的 /var/lib/k0s/manifests 目录即可,k0s 会自动帮我门执行 apply -f xxxx.yaml 这种操作。
下面是切换到 Flannel 的样例,需要注意的是 Flannel 官方镜像不会帮你安装 CNI 的二进制文件,我们需要借助文件上传自己安装(CNI GitHub 插件下载地址[2]):
apiVersion:k0sctl.k0sproject.io/v1beta1
kind:Cluster
metadata:
name:k0s-cluster
spec:
hosts:
-ssh:
address:10.0.0.11
user:root
port:22
keyPath:/Users/bleem/.ssh/id_rsa
role:controller+worker
files:
#将Flannel的yaml放到Manifests里(需要单独创建一个目录)
-name:flannel
src:/Users/bleem/tmp/kube-flannel.yaml
dstDir:/var/lib/k0s/manifests/flannel
perm:0644
#自己安装一下CNI插件
-name:cni-plugins
src:/Users/bleem/tmp/cni-plugins/*
dstDir:/opt/cni/bin/
perm:0755
k0s:
version:v1.21.2+k0s.1
config:
apiVersion:k0s.k0sproject.io/v1beta1
kind:Cluster
metadata:
name:k0s
spec:
api:
address:10.0.0.11
port:6443
k0sApiPort:9443
sans:
-10.0.0.11
-10.0.0.12
-10.0.0.13
storage:
type:etcd
network:
podCIDR:10.244.0.0/16
serviceCIDR:10.96.0.0/12
#这里指定CNI为custom自定义类型,这样
#k0s就不会安装Calico/kube-router了
provider:custom
2.5、上传 k0s 二进制文件
除了普通文件、镜像压缩包等,默认情况下 k0sctl 在安装集群时还会在目标机器上下载 k0s 二进制文件;当然在离线环境下这一步也可以通过一个简单的配置来实现离线上传:
apiVersion:k0sctl.k0sproject.io/v1beta1
kind:Cluster
metadata:
name:k0s-cluster
spec:
hosts:
-ssh:
address:10.0.0.11
user:root
port:22
keyPath:/Users/bleem/.ssh/id_rsa
role:controller+worker
#声明需要上传二进制文件
uploadBinary:true
#指定二进制文件位置
k0sBinaryPath:/Users/bleem/tmp/k0s
files:
-name:flannel
src:/Users/bleem/tmp/kube-flannel.yaml
dstDir:/var/lib/k0s/manifests/flannel
perm:0644
......
2.6、更换镜像版本
默认情况下 k0s 版本号与 Kubernetes 保持一致,但是如果期望某个组件使用特定的版本,则可以直接配置这些内置组件的镜像名称:
apiVersion:k0sctl.k0sproject.io/v1beta1
kind:Cluster
metadata:
name:k0s-cluster
spec:
hosts:
-ssh:
address:10.0.0.11
user:root
port:22
keyPath:/Users/bleem/.ssh/id_rsa
role:controller+worker
uploadBinary:true
k0sBinaryPath:/Users/bleem/tmp/k0s
files:
-name:flannel
src:/Users/bleem/tmp/kube-flannel.yaml
dstDir:/var/lib/k0s/manifests/flannel
perm:0644
......
k0s:
version:v1.21.2+k0s.1
config:
apiVersion:k0s.k0sproject.io/v1beta1
kind:Cluster
metadata:
name:k0s
spec:
api:
address:10.0.0.11
port:6443
k0sApiPort:9443
sans:
-10.0.0.11
-10.0.0.12
-10.0.0.13
#指定内部组件的镜像使用的版本
images:
#konnectivity:
#image:us.gcr.io/k8s-artifacts-prod/kas-network-proxy/proxy-agent
#version:v0.0.21
#metricsserver:
#image:gcr.io/k8s-staging-metrics-server/metrics-server
#version:v0.3.7
kubeproxy:
image:k8s.gcr.io/kube-proxy
version:v1.21.3
#coredns:
#image:docker.io/coredns/coredns
#version:1.7.0
#calico:
#cni:
#image:docker.io/calico/cni
#version:v3.18.1
#node:
#image:docker.io/calico/node
#version:v3.18.1
#kubecontrollers:
#image:docker.io/calico/kube-controllers
#version:v3.18.1
#kuberouter:
#cni:
#image:docker.io/cloudnativelabs/kube-router
#version:v1.2.1
#cniInstaller:
#image:quay.io/k0sproject/cni-node
#version:0.1.0
default_pull_policy:IfNotPresent
#default_pull_policy:Never
2.7、调整 Master 组件参数
熟悉 Kubernetes 的应该清楚,Master 上三大组件:apiserver、controller、scheduler 管控整个集群;在 k0sctl 安装集群的过程中也允许自定义这些组件的参数,这些调整通过修改使用的 k0sctl.yaml 配置文件完成。
spec.api.extraArgs:用于自定义 kube-apiserver 的自定义参数(KV map)
spec.scheduler.extraArgs:用于自定义 kube-scheduler 的自定义参数(KV map)
spec.controllerManager.extraArgs:用于自定义 kube-controller-manager 自定义参数(KV map)
spec.workerProfiles:用于覆盖 kubelet-config.yaml 中的配置,该配置最终将于默认的 kubelet-config.yaml 合并
除此之外在 Host 配置中还有一个 InstallFlags 配置用于传递 k0s 安装时的其他配置选项。
三、k0s HA 搭建
其实上面的第二部分主要都是介绍 k0sctl 一些基础功能,为的就是给下面这部分 HA 生产级部署做铺垫。
就目前来说,k0s HA 仅支持独立负载均衡器的 HA 架构;即外部需要有一个高可用的 4 层负载均衡器,其他所有 Node 节点链接这个负载均衡器实现 Master 的高可用。在使用 k0sctl 命令搭建 HA 集群时很简单,只需要添加一个外部负载均衡器地址即可;以下是一个完整的,全离线状态下的 HA 集群搭建配置。
3.1、外部负载均衡器
在搭建之前我们假设已经有一个外部的高可用的 4 层负载均衡器,且负载均衡器已经负载了以下端口:
6443(for Kubernetes API):负载均衡器 6443 负载所有 Master 节点的 6443
9443(for controller join API):负载均衡器 9443 负载所有 Master 节点的 9443
8132(for Konnectivity agent):负载均衡器 8132 负载所有 Master 节点的 8132
8133(for Konnectivity server):负载均衡器 8133 负载所有 Master 节点的 8133
以下为一个 Nginx 4 层代理的样例:
error_logsyslog:server=unix:/dev/lognotice;
worker_processesauto;
events{
multi_accepton;
useepoll;
worker_connections1024;
}
stream{
upstreamkube_apiserver{
least_conn;
server10.0.0.11:6443;
server10.0.0.12:6443;
server10.0.0.13:6443;
}
upstreamkonnectivity_agent{
least_conn;
server10.0.0.11:8132;
server10.0.0.12:8132;
server10.0.0.13:8132;
}
upstreamkonnectivity_server{
least_conn;
server10.0.0.11:8133;
server10.0.0.12:8133;
server10.0.0.13:8133;
}
upstreamcontroller_join_api{
least_conn;
server10.0.0.11:9443;
server10.0.0.12:9443;
server10.0.0.13:9443;
}
server{
listen0.0.0.0:6443;
proxy_passkube_apiserver;
proxy_timeout10m;
proxy_connect_timeout1s;
}
server{
listen0.0.0.0:8132;
proxy_passkonnectivity_agent;
proxy_timeout10m;
proxy_connect_timeout1s;
}
server{
listen0.0.0.0:8133;
proxy_passkonnectivity_server;
proxy_timeout10m;
proxy_connect_timeout1s;
}
server{
listen0.0.0.0:9443;
proxy_passcontroller_join_api;
proxy_timeout10m;
proxy_connect_timeout1s;
}
}
3.2、搭建 HA 集群
以下为 k0sctl 的 HA + 离线部署样例配置:
apiVersion:k0sctl.k0sproject.io/v1beta1
kind:Cluster
metadata:
name:k0s-cluster
spec:
hosts:
-ssh:
address:10.0.0.11
user:root
port:22
keyPath:/Users/bleem/.ssh/id_rsa
#role支持的值
#'controller'单Master
#'worker'单Worker
#'controller+worker'Master和Worker都运行
role:controller+worker
#从本地上传k0sbin文件,不要在目标机器下载
uploadBinary:true
k0sBinaryPath:/Users/bleem/tmp/k0s
#上传其他文件
files:
#上传Flannel配置,使用自定的Flannel替换内置的Calico
-name:flannel
src:/Users/bleem/tmp/kube-flannel.yaml
dstDir:/var/lib/k0s/manifests/flannel
perm:0644
#上传打包好的image镜像包,k0s会自动导入到containerd
-name:image-bundle
src:/Users/bleem/tmp/bundle_file
dstDir:/var/lib/k0s/images/
perm:0755
#使用Flannel后每个机器要上传对应的CNI插件
-name:cni-plugins
src:/Users/bleem/tmp/cni-plugins/*
dstDir:/opt/cni/bin/
perm:0755
-ssh:
address:10.0.0.12
user:root
port:22
keyPath:/Users/bleem/.ssh/id_rsa
role:controller+worker
uploadBinary:true
k0sBinaryPath:/Users/bleem/tmp/k0s
files:
-name:image-bundle
src:/Users/bleem/tmp/bundle_file
dstDir:/var/lib/k0s/images/
perm:0755
-name:cni-plugins
src:/Users/bleem/tmp/cni-plugins/*
dstDir:/opt/cni/bin/
perm:0755
-ssh:
address:10.0.0.13
user:root
port:22
keyPath:/Users/bleem/.ssh/id_rsa
role:controller+worker
uploadBinary:true
k0sBinaryPath:/Users/bleem/tmp/k0s
files:
-name:image-bundle
src:/Users/bleem/tmp/bundle_file
dstDir:/var/lib/k0s/images/
perm:0755
-name:cni-plugins
src:/Users/bleem/tmp/cni-plugins/*
dstDir:/opt/cni/bin/
perm:0755
-ssh:
address:10.0.0.14
user:root
port:22
keyPath:/Users/bleem/.ssh/id_rsa
role:worker
uploadBinary:true
k0sBinaryPath:/Users/bleem/tmp/k0s
files:
-name:image-bundle
src:/Users/bleem/tmp/bundle_file
dstDir:/var/lib/k0s/images/
perm:0755
-name:cni-plugins
src:/Users/bleem/tmp/cni-plugins/*
dstDir:/opt/cni/bin/
perm:0755
-ssh:
address:10.0.0.15
user:root
port:22
keyPath:/Users/bleem/.ssh/id_rsa
role:worker
uploadBinary:true
k0sBinaryPath:/Users/bleem/tmp/k0s
files:
-name:image-bundle
src:/Users/bleem/tmp/bundle_file
dstDir:/var/lib/k0s/images/
perm:0755
-name:cni-plugins
src:/Users/bleem/tmp/cni-plugins/*
dstDir:/opt/cni/bin/
perm:0755
k0s:
version:v1.21.2+k0s.1
config:
apiVersion:k0s.k0sproject.io/v1beta1
kind:Cluster
metadata:
name:k0s
spec:
api:
#此处填写外部的负载均衡器地址,所有kubelet会链接这个地址
externalAddress:10.0.0.20
#不要忘了为外部负载均衡器添加API证书的SAN
sans:
-10.0.0.11
-10.0.0.12
-10.0.0.13
-10.0.0.20
#存储类型使用etcd,etcd集群由k0s自动管理
storage:
type:etcd
network:
podCIDR:10.244.0.0/16
serviceCIDR:10.96.0.0/12
#网络插件使用custom,然后让Flannel接管
provider:custom
kubeProxy:
disabled:false
#开启kubelet的ipvs模式
mode:ipvs
#不发送任何匿名统计信息
telemetry:
enabled:false
images:
default_pull_policy:IfNotPresent
最后只需要执行 k0sctl apply -c k0sctl.yaml 稍等几分钟集群就搭建好了,安装过程中可以看到相关文件的上传流程:
3.3、证书续签和管理
kubeadm 集群默认证书有效期是一年,到期要通过 kubeadm 重新签署;k0s 集群也差不多一样,但是不同的是 k0s 集群更加暴力;只要 CA(默认 10年)不丢,k0s 每次重启都强行重新生成一年有效期的证书,所以在 HA 的环境下,快到期时重启一下 k0s 服务就行。
k0sctl 安装完的集群默认只有一个 k0scontroller.service 服务,Master、Node 上所有服务都由这个服务启动,所以到期之前 systemctl restart k0scontroller.service 一下就行。
四、集群备份和恢复
k0sctl 提供了集群备份和恢复功能,默认情况下只需要执行 k0sctl backup 即可完成集群备份,该命令会在当前目录下生成一个 k0s_backup_TIMESTAMP.tar.gz 备份文件。
需要恢复集群时使用 k0sctl apply --restore-from k0s_backup_TIMESTAMP.tar.gz 命令进行恢复即可;需要注意的是恢复命令等同于在新机器重新安装集群,所以有一定风险。
经过连续两天的测试,感觉这个备份恢复功能并不算靠谱,还是推荐使用 Velero 备份集群。
五、其他高级功能
5.1、etcd 替换
在小规模集群场景下可能并不需要特别完善的 etcd 作为存储,k0s 借助于 kine 库可以实现使用 SQLite 或 MySQL 等传统数据库作为集群存储;如果想要切换存储只需要调整 k0sctl.yaml 配置即可:
apiVersion:k0s.k0sproject.io/v1beta1
kind:Cluster
metadata:
name:k0s
spec:
storage:
type:kine
kine:
dataSource:"sqlite:///var/lib/k0s/db/state.db?more=rwc&_journal=WAL&cache=shared"
5.2、集群用户管理
使用 k0sctl 搭建的集群通过 k0s 命令可以很方便的为集群添加用户,以下是添加样例:
k0skubeconfigcreate--groups"system:masters"testUser>k0s.config
5.3、Containerd 配置
在不做配置的情况下 k0s 集群使用默认的 Containerd 配置,如果需要自己定义特殊配置,可以在安装时通过文件上传方式将 Containerd 配置文件上传到 /etc/k0s/containerd.toml 位置,该配置将会被 k0s 启动的 Containerd 读取并使用。
六、总结
k0s 是个不错的项目,对于二进制宿主机部署 Kubernetes 集群很方便,由于其直接采用 Kubernetes 二进制文件启动,所以基本没有功能阉割,而 k0sctl 又为自动化安装提供了良好的扩展性,所以值得一试。不过目前来说 k0s 在细节部分还有一定瑕疵,比如 konnectivity 服务在安装时无法选择性关闭等;k0s 综合来说是个不错的工具,也推荐看看源码,里面很多设计很新颖也比较利于了解集群引导过程。
相关链接:
https://docs.k0sproject.io/v1.21.2+k0s.1/airgap-install/
https://github.com/containernetworking/plugins/releases
原文链接:https://mritd.com/2021/07/29/test-the-k0s-cluster/
-
机器
+关注
关注
0文章
780浏览量
40710 -
源码
+关注
关注
8文章
638浏览量
29184 -
负载均衡器
+关注
关注
0文章
18浏览量
2579
原文标题:K0S 使用实战介绍
文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论