三十二、kubernetes集群的网络实现
Kubernetes集群的网络实现
CNI介绍及集群网络选型
容器网络接口(Container Network Interface),实现kubernetes集群的Pod网络通信及管理。包括:
- CNI Plugin负责给容器配置网络,它包括两个基本的接口: 配置网络: AddNetwork(net NetworkConfig, rt RuntimeConf) (types.Result, error) 清理网络: DelNetwork(net NetworkConfig, rt RuntimeConf) error
- IPAM Plugin负责给容器分配IP地址,主要实现包括host-local和dhcp。
以上两种插件的支持,使得k8s的网络可以支持各式各样的管理模式,当前在业界也出现了大量的支持方案,其中比较流行的比如flannel、calico等。
kubernetes配置了cni网络插件后,其容器网络创建流程为:
kubelet先创建pause容器生成对应的network namespace
调用网络driver,因为配置的是CNI,所以会调用CNI相关代码,识别CNI的配置目录为/etc/cni/net.d
CNI driver根据配置调用具体的CNI插件,二进制调用,可执行文件目录为/opt/cni/bin,项目
CNI插件给pause容器配置正确的网络,pod中其他的容器都是用pause的网络
可以在此查看社区中的CNI实现,https://github.com/containernetworking/cni
通用类型:flannel、calico等,部署使用简单
其他:根据具体的网络环境及网络需求选择,比如
- 公有云机器,可以选择厂商与网络插件的定制Backend,如AWS、阿里、腾讯针对flannel均有自己的插件,也有AWS ECS CNI
- 私有云厂商,比如Vmware NSX-T等
- 网络性能等,MacVlan
Flannel网络模型实现剖析
flannel的网络有多种实现:
- udp
- vxlan
- host-gw
- ...
不特殊指定的话,默认会使用vxlan技术作为Backend,可以通过如下查看:
$ kubectl -n kube-system exec kube-flannel-ds-amd64-cb7hs cat /etc/kube-flannel/net-conf.json
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "vxlan"
}
}
vxlan介绍及点对点通信的实现
VXLAN 全称是虚拟可扩展的局域网( Virtual eXtensible Local Area Network),它是一种 overlay 技术,通过三层的网络来搭建虚拟的二层网络。
它创建在原来的 IP 网络(三层)上,只要是三层可达(能够通过 IP 互相通信)的网络就能部署 vxlan。在每个端点上都有一个 vtep 负责 vxlan 协议报文的封包和解包,也就是在虚拟报文上封装 vtep 通信的报文头部。物理网络上可以创建多个 vxlan 网络,这些 vxlan 网络可以认为是一个隧道,不同节点的虚拟机能够通过隧道直连。每个 vxlan 网络由唯一的 VNI 标识,不同的 vxlan 可以不相互影响。
- VTEP(VXLAN Tunnel Endpoints):vxlan 网络的边缘设备,用来进行 vxlan 报文的处理(封包和解包)。vtep 可以是网络设备(比如交换机),也可以是一台机器(比如虚拟化集群中的宿主机)
- VNI(VXLAN Network Identifier):VNI 是每个 vxlan 的标识,一共有 2^24 = 16,777,216,一般每个 VNI 对应一个租户,也就是说使用 vxlan 搭建的公有云可以理论上可以支撑千万级别的租户
演示:在k8s-slave1和k8s-slave2两台机器间,利用vxlan的点对点能力,实现虚拟二层网络的通信
172.21.51.55
节点:
# 创建vTEP设备,对端指向172.21.52.84节点,指定VNI及underlay网络使用的网卡
$ ip link add vxlan20 type vxlan id 20 remote 172.21.52.84 dstport 4789 dev eth0
$ ip -d link show vxlan20
# 启动设备
$ ip link set vxlan20 up
# 设置ip地址
$ ip addr add 10.0.51.55/24 dev vxlan20
172.21.52.84
节点:
# 创建VTEP设备,对端指向k8s-slave1节点,指定VNI及underlay网络使用的网卡
$ ip link add vxlan20 type vxlan id 20 remote 172.21.51.55 dstport 4789 dev eth0
# 启动设备
$ ip link set vxlan20 up
# 设置ip地址
$ ip addr add 10.0.52.84/24 dev vxlan20
在172.21.51.55
节点:
$ ping 10.0.52.84
# 走vtep封包解包
$ ip route add 10.0.52.0/24 dev vxlan20
# 在172.21.52.84机器
$ ip route add 10.0.51.0/24 dev vxlan20
# 再次ping
$ ping 10.0.52.84
#
$ tcpdump -i vxlan20 icmp
隧道是一个逻辑上的概念,在 vxlan 模型中并没有具体的物理实体想对应。隧道可以看做是一种虚拟通道,vxlan 通信双方(图中的虚拟机)认为自己是在直接通信,并不知道底层网络的存在。从整体来说,每个 vxlan 网络像是为通信的虚拟机搭建了一个单独的通信通道,也就是隧道。
实现的过程:
虚拟机的报文通过 vtep 添加上 vxlan 以及外部的报文层,然后发送出去,对方 vtep 收到之后拆除 vxlan 头部然后根据 VNI 把原始报文发送到目的虚拟机。
# 查看172.21.51.55主机路由
$ route -n
10.0.51.0 0.0.0.0 255.255.255.0 U 0 0 0 vxlan20
10.0.52.0 0.0.0.0 255.255.255.0 U 0 0 0 vxlan20
# 到了vxlan的设备后,
$ ip -d link show vxlan20
vxlan id 20 remote 172.21.51.55 dev eth0 srcport 0 0 dstport 4789 ...
# 查看fdb地址表,主要由MAC地址、VLAN号、端口号和一些标志域等信息组成,vtep 对端地址为 172.21.51.55,换句话说,如果接收到的报文添加上 vxlan 头部之后都会发到 172.21.51.55
$ bridge fdb show dev vxlan20
00:00:00:00:00:00 dst 172.21.52.84 via eth0 self permanent
a6:61:05:84:20:c6 dst 172.21.52.84 self
在172.21.52.84
机器抓包,查看vxlan封装后的包:
# 在172.21.52.84机器执行
$ tcpdump -i eth0 host 172.21.51.55 -w vxlan.cap
# 在172.21.51.55机器执行
$ ping 10.0.52.84
使用wireshark分析ICMP类型的数据包
清理:
$ ip link del vxlan20
跨主机容器网络的通信
思考:容器网络模式下,vxlan设备该接在哪里?
基本的保证:目的容器的流量要通过vtep设备进行转发!
演示:利用vxlan实现跨主机容器网络通信
为了不影响已有的网络,因此创建一个新的网桥,创建容器接入到新的网桥来演示效果
在172.21.51.55
节点:
# 创建新网桥,指定cidr段
$ docker network create --subnet 172.18.1.0/24 network-luffy
# 查看网桥
$ brctl show
# 新建容器,接入到新网桥
$ docker run -d --name vxlan-test --net network-luffy --ip 172.18.1.2 nginx:alpine
$ docker exec vxlan-test ifconfig
在172.21.52.84
节点:
# 创建新网桥,指定cidr段
$ docker network create --subnet 172.18.2.0/24 network-luffy
# 新建容器,接入到新网桥
$ docker run -d --name vxlan-test --net network-luffy --ip 172.18.2.2 nginx:alpine
此时执行ping测试:
$ docker exec vxlan-test ping 172.18.2.2
分析:数据到了网桥后,出不去。结合前面的示例,因此应该将流量由vtep设备转发,联想到网桥的特性,接入到桥中的端口,会由网桥负责转发数据,因此,相当于所有容器发出的数据都会经过到vxlan的端口,vxlan将流量转到对端的vtep端点,再次由网桥负责转到容器中。
172.21.51.55
节点:
# 删除旧的vtep
$ ip link del vxlan20
# 新建vtep
$ ip link add vxlan_docker type vxlan id 100 remote 172.21.52.84 dstport 4789 dev eth0
$ ip link set vxlan_docker up
# 不用设置ip,因为目标是可以转发容器的数据即可
$ brctl show
br-0fdb78d3b486 8000.02421452871b no vethfffdd2f
# 接入到网桥中
$ brctl addif br-0fdb78d3b486 vxlan_docker
# 设置路由转发
$ ip route add 172.18.2.0/24 dev br-0fdb78d3b486
172.21.52.84
节点:
# 删除旧的vtep
$ ip link del vxlan20
# 新建vtep
$ ip link add vxlan_docker type vxlan id 100 remote 172.21.51.55 dstport 4789 dev eth0
$ ip link set vxlan_docker up
# 不用设置ip,因为目标是可以转发容器的数据即可
# 接入到网桥中
$ brctl show
$ brctl addif br-c6660fe2dc53 vxlan_docker
# 设置路由转发
$ ip route add 172.18.1.0/24 dev br-c6660fe2dc53
再次执行ping测试:
$ docker exec vxlan-test ping 172.18.2.2
Flannel的vxlan实现精讲
思考:k8s集群的网络环境和手动实现的跨主机的容器通信有哪些差别?
- CNI要求,集群中的每个Pod都必须分配唯一的Pod IP
- k8s集群内的通信不是vxlan点对点通信,因为集群内的所有节点之间都需要互联
- 没法创建点对点的vxlan模型
- 集群节点动态添加
flannel如何为每个节点分配Pod地址段:
$ kubectl -n kube-system exec kube-flannel-ds-amd64-cb7hs cat /etc/kube-flannel/net-conf.json
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "vxlan"
}
}
#查看节点的pod ip
[root@k8s-master bin]# kd get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE
myblog-5d9ff54d4b-4rftt 1/1 Running 1 33h 10.244.2.19 k8s-slave2
myblog-5d9ff54d4b-n447p 1/1 Running 1 33h 10.244.1.32 k8s-slave1
#查看k8s-slave1主机分配的地址段
$ cat /run/flannel/subnet.env
FLANNEL_NETWORK=10.244.0.0/16
FLANNEL_SUBNET=10.244.1.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=true
# kubelet启动容器的时候就可以按照本机的网段配置来为pod设置IP地址
vtep的设备在哪:
$ ip -d link show flannel.1
# 没有remote ip,非点对点
Pod的流量如何转到vtep设备中
$ brctl show cni0
# 每个Pod都会使用Veth pair来实现流量转到cni0网桥
$ route -n
10.244.0.0 10.244.0.0 255.255.255.0 UG 0 0 0 flannel.1
10.244.1.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0
10.244.2.0 10.244.2.0 255.255.255.0 UG 0 0 0 flannel.1
vtep封包的时候,如何拿到目的vetp端的IP及MAC信息
--kube-subnet-mgr, 所有节点的flanneld都可以感知到节点的添加和删除操作,就可以动态的更新本机的转发配置,
同时通过ARP的方式可以获取fdb表的mac地址
演示跨主机Pod通信的流量详细过程:
$ kubectl -n luffy get po -o wide
myblog-5d9ff54d4b-4rftt 1/1 Running 1 25h 10.244.2.19 k8s-slave2
myblog-5d9ff54d4b-n447p 1/1 Running 1 25h 10.244.1.32 k8s-slave1
$ kubectl -n luffy exec myblog-5d9ff54d4b-n447p -- ping 10.244.2.19 -c 2
PING 10.244.2.19 (10.244.2.19) 56(84) bytes of data.
64 bytes from 10.244.2.19: icmp_seq=1 ttl=62 time=0.480 ms
64 bytes from 10.244.2.19: icmp_seq=2 ttl=62 time=1.44 ms
--- 10.244.2.19 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 0.480/0.961/1.443/0.482 ms
# 查看路由
$ kubectl -n luffy exec myblog-5d9ff54d4b-n447p -- route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.244.1.1 0.0.0.0 UG 0 0 0 eth0
10.244.0.0 10.244.1.1 255.255.0.0 UG 0 0 0 eth0
10.244.1.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
# 查看k8s-slave1 的veth pair 和网桥
$ brctl show
bridge name bridge id STP enabled interfaces
cni0 8000.6a9a0b341d88 no veth048cc253
veth76f8e4ce
vetha4c972e1
# 流量到了cni0后,查看slave1节点的route
$ route -n
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.136.2 0.0.0.0 UG 100 0 0 eth0
10.0.136.0 0.0.0.0 255.255.255.0 U 0 0 0 vxlan20
10.244.0.0 10.244.0.0 255.255.255.0 UG 0 0 0 flannel.1
10.244.1.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0
10.244.2.0 10.244.2.0 255.255.255.0 UG 0 0 0 flannel.1
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
192.168.136.0 0.0.0.0 255.255.255.0 U 100 0 0 eth0
# 流量转发到了flannel.1网卡,查看该网卡,其实是vtep设备
$ ip -d link show flannel.1
4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default
link/ether 8a:2a:89:4d:b0:31 brd ff:ff:ff:ff:ff:ff promiscuity 0
vxlan id 1 local 172.21.51.68 dev eth0 srcport 0 0 dstport 8472 nolearning ageing 300 noudpcsum noudp6zerocsumtx noudp6zerocsumrx addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
# 该转发到哪里,通过etcd查询数据,然后本地缓存,流量不用走多播发送
$ bridge fdb show dev flannel.1
4a:4d:9d:3a:c5:f0 dst 172.21.51.68 self permanent
76:e7:98:9f:5b:e9 dst 172.21.51.67 self permanent
# 对端的vtep设备接收到请求后做解包,取出源payload内容,查看k8s-slave2的路由
$ route -n
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 172.21.50.140 0.0.0.0 UG 0 0 0 eth0
10.244.0.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0
10.244.1.0 10.244.1.0 255.255.255.0 UG 0 0 0 flannel.1
10.244.2.0 10.244.2.0 255.255.255.0 UG 0 0 0 flannel.1
169.254.0.0 0.0.0.0 255.255.0.0 U 1002 0 0 eth0
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
172.21.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0
#根据路由规则转发到cni0网桥,然后由网桥转到具体的Pod中
实际的请求图:
- k8s-slave1 节点中的 pod-a(10.244.2.19)当中的 IP 包通过 pod-a 内的路由表被发送到eth0,进一步通过veth pair转到宿主机中的网桥
cni0
- 到达
cni0
当中的 IP 包通过匹配节点 k8s-slave1 的路由表发现通往 10.244.2.19 的 IP 包应该交给flannel.1
接口 flannel.1
作为一个 VTEP 设备,收到报文后将按照VTEP
的配置进行封包,第一次会发送ARP请求,知道10.244.2.19的vtep设备是k8s-slave2机器,IP地址是172.21.51.67,拿到MAC 地址进行 VXLAN 封包。- 通过节点 k8s-slave2 跟 k8s-slave1之间的网络连接,VXLAN 包到达 k8s-slave2 的 eth0 接口
- 通过端口 8472,VXLAN 包被转发给 VTEP 设备
flannel.1
进行解包 - 解封装后的 IP 包匹配节点 k8s-slave2 当中的路由表(10.244.2.0),内核将 IP 包转发给
cni0
cni0
将 IP 包转发给连接在cni0
上的 pod-b
利用host-gw模式提升集群网络性能
vxlan模式适用于三层可达的网络环境,对集群的网络要求很宽松,但是同时由于会通过VTEP设备进行额外封包和解包,因此给性能带来了额外的开销。
网络插件的目的其实就是将本机的cni0网桥的流量送到目的主机的cni0网桥。实际上有很多集群是部署在同一二层网络环境下的,可以直接利用二层的主机当作流量转发的网关。这样的话,可以不用进行封包解包,直接通过路由表去转发流量。
为什么三层可达的网络不直接利用网关转发流量?
内核当中的路由规则,网关必须在跟主机当中至少一个 IP 处于同一网段。
由于k8s集群内部各节点均需要实现Pod互通,因此,也就意味着host-gw模式需要整个集群节点都在同一二层网络内。
修改flannel的网络后端:
$ kubectl edit cm kube-flannel-cfg -n kube-system
...
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "host-gw"
}
}
kind: ConfigMap
...
重建Flannel的Pod
$ kubectl -n kube-system get po |grep flannel
kube-flannel-ds-amd64-5dgb8 1/1 Running 0 15m
kube-flannel-ds-amd64-c2gdc 1/1 Running 0 14m
kube-flannel-ds-amd64-t2jdd 1/1 Running 0 15m
$ kubectl -n kube-system delete po kube-flannel-ds-amd64-5dgb8 kube-flannel-ds-amd64-c2gdc kube-flannel-ds-amd64-t2jdd
# 等待Pod新启动后,查看日志,出现Backend type: host-gw字样
$ kubectl -n kube-system logs -f kube-flannel-ds-amd64-4hjdw
I0704 01:18:11.916374 1 kube.go:126] Waiting 10m0s for node controller to sync
I0704 01:18:11.916579 1 kube.go:309] Starting kube subnet manager
I0704 01:18:12.917339 1 kube.go:133] Node controller sync successful
I0704 01:18:12.917848 1 main.go:247] Installing signal handlers
I0704 01:18:12.918569 1 main.go:386] Found network config - Backend type: host-gw
I0704 01:18:13.017841 1 main.go:317] Wrote subnet file to /run/flannel/subnet.env
查看节点路由表:
$ route -n
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.136.2 0.0.0.0 UG 100 0 0 eth0
10.244.0.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0
10.244.1.0 172.21.51.68 255.255.255.0 UG 0 0 0 eth0
10.244.2.0 172.21.51.55 255.255.255.0 UG 0 0 0 eth0
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
192.168.136.0 0.0.0.0 255.255.255.0 U 100 0 0 eth0
- k8s-slave1 节点中的 pod-a(10.244.2.19)当中的 IP 包通过 pod-a 内的路由表被发送到eth0,进一步通过veth pair转到宿主机中的网桥
cni0
- 到达
cni0
当中的 IP 包通过匹配节点 k8s-slave1 的路由表发现通往 10.244.2.19 的 IP 包应该使用172.21.51.55这个网关进行转发 - 包到达k8s-slave2节点(172.21.51.55)节点的eth0网卡,根据该节点的路由规则,转发给cni0网卡
cni0
将 IP 包转发给连接在cni0
上的 pod-b
三十二、kubernetes集群的网络实现的更多相关文章
- k8s教程:Kubernetes集群使用网络存储NFS
NFS存储 NFS即网络文件系统Network File System,它是一种分布式文件系统协议,最初是由Sun MicroSystems公司开发的类Unix操作系统之上的一款经典网络存储方案,其功 ...
- Docker系列(十):Kubernetes集群入门
kubenetes安装 官网:https://github.com/kubernetes/kubernetes/blob/release-1.0/docs/getting-started- guide ...
- 三十六.MHA集群概述 、 部署MHA集群 测试配置
1.准备MHA集群环境 准备6台虚拟机,并按照本节规划配置好IP参数 在这些虚拟机之间实现SSH免密登录 在相应节点上安装好MHA相关的软件包 使用6台RHEL 7虚拟机,如图-1所示.准备集群环 ...
- Kubernetes集群部署--kubernetes1.10.1
参考博客:https://mritd.me/2018/04/19/set-up-kubernetes-1.10.1-cluster-by-hyperkube/ 一.环境 (1)系统环境 IP 操作系统 ...
- 二进制文件方式安装kubernetes集群
所有操作全部用root使用者进行,高可用一般建议大于等于3台的奇数,我们使用3台master来做高可用 练习环境说明: 参考GitHub master: kube-apiserver,kube-con ...
- 灵雀云开源网络插件Kube-OVN 1.4.0 版发布!支持跨集群容器网络、NetworkPolicy 日志
从 1.4 开始 Kube-OVN 支持将多个 Kubernetes 集群容器网络打通,不同集群之间的 Pod 可以通过 Pod IP 直接互相通信.本版本还支持 ACL 日志,可以记录因 Netwo ...
- 使用Minikube部署本地Kubernetes集群(二十八)
前言 使用Minikube部署本地k8s集群相对比较简单,非常推荐将其用于本地k8s开发环境,唯一麻烦点的仅仅是网络问题. 在本篇教程中,我们使用了国内的镜像来完成本地k8s集群的搭建.如果搭建过程中 ...
- 【Kubernetes学习之二】Kubernetes集群安装
环境 centos 7 Kubernetes有三种安装方式:yum.二进制.kubeadm,这里演示kubeadm. 一.准备工作1.软件版本 软件 版本 kubernetes v1.15.3 Cen ...
- CentOS 7.5 使用 yum 安装 Kubernetes 集群(二)
一.安装方式介绍 1.yum 安装 目前CentOS官方已经把Kubernetes源放入到自己的默认 extras 仓库里面,使用 yum 安装,好处是简单,坏处也很明显,需要官方更新 yum 源才能 ...
随机推荐
- Taurus.MVC 微服务框架 入门开发教程:项目部署:2、让Kestrel支持绑定多个域名转发,替代Ngnix使用。
系列目录: 本系列分为项目集成.项目部署.架构演进三个方向,后续会根据情况调整文章目录. 本系列第一篇:Taurus.MVC V3.0.3 微服务开源框架发布:让.NET 架构在大并发的演进过程更简单 ...
- oracle 怎么查看用户对应的表空间
oracle 怎么查看用户对应的表空间? 查询用户: 查看数据库里面所有用户,前提是你是有 dba 权限的帐号,如 sys,system: select * from dba_users; 查看你能管 ...
- ESP8266 NONOS SDK学习
一.概况 1.存储 ESP8266 带有 160 KB 的 RAM,其中 64 KB 为 iRAM,96 KB 为 dRAM.iRAM 进一步 分成两块:32 KB iRAM 块运行标有 IRAM_A ...
- Lua 支持虚函数的解决方案
概述 lua本身没有提供类似C++虚函数机制,调用的父类方法调用虚函数可能会出现问题. 问题分析 分析这段代码和输出 local Gun = {} -- 示例,实际应用还要考虑构造,虚表等情况 fun ...
- Hadoop阶段学习总结
第一部分:HDFS相关问题 一.描述一下HDFS的数据写入流程 首先由客户端想NameNode服务发起写数据请求,NameNode接收到请求后会进行基本验证,验证内容包括对请求上传的路径进行合法验 ...
- C++ "链链"不忘@必有回响之单链表
1. 前言 数组和链表是数据结构的基石,是逻辑上可描述.物理结构真实存在的具体数据结构.其它的数据结构往往在此基础上赋予不同的数据操作语义,如栈先进后出,队列先进先出-- 数组中的所有数据存储在一片连 ...
- keycloak~资源的远程授权
17.1远程资源授权准备 17.1.1认证和访问流程图 参考:http://www.zyiz.net/tech/detail-141309.html 17.1.2为用户指定角色 可以使用ROLE_US ...
- Elasticsearch 快照生命周期管理 (SLM) 实战指南
文章转载自:https://mp.weixin.qq.com/s/PSfgPJc4dKN2pOZd0Y02wA 1.Elasticsearch 保证高可用性的方式 Elasticsearch 保证集群 ...
- kubeadm 使用 Calico CNI 以及外部 etcd 部署 kubernetes v1.23.1 高可用集群
文章转载自:https://mp.weixin.qq.com/s/2sWHt6SeCf7GGam0LJEkkA 一.环境准备 使用服务器 Centos 8.4 镜像,默认操作系统版本 4.18.0-3 ...
- CentOS无法识别NTFS格式U盘完美解决方案
问题描述:CentOS上无法识别NTFS格式的U盘 解决方案: # 进入yum目录 cd /etc/yum.repos.d # 下载阿里的epel wget http://mirrors.aliyun ...