1
2
3
4
5
作者:李晓辉

微信联系:lxh_chat

联系邮箱: 939958092@qq.com

简单回顾Kubernetes SDN

Kubernetes 会给每个容器(Pod)自动分配一个在同一个子网里的 IP 地址。这就意味着,即使这些容器运行在不同的节点上,或者属于不同的 Kubernetes 命名空间,它们也能够轻松地互相通信。这就好像是给每个容器都发了一张“通行证”,让它们可以在集群的网络里自由穿梭。

Kubernetes 是通过一个叫软件定义网络(SDN)的东西来实现这个功能的。SDN 就像是一个智能的网络管理员,能够通过编程的方式来控制网络流量和网络资源。这有点像 VMware 的 vSwitch 软件,不过更灵活、更强大。

Red Hat OpenShift 使用了一个叫 Cluster Network Operator(CNO)的东西来管理这个 SDN。CNO 就像是一个指挥官,它会调用符合容器网络接口(CNI)规范的插件来配置容器网络接口。简单来说,CNO 就是那个“幕后英雄”,它确保所有的网络配置都能正确无误地完成。

Red Hat OpenShift 提供了几种插件提供商,比如 OVN-Kubernetes 和 Kuryr。默认情况下,安装时会选择 OVN-Kubernetes 提供商,它会在每个节点上运行 Open vSwitch(OVS)插件。这个插件就像是网络的“交通警察”,确保数据包能够顺利地到达目的地。

用service 暴露虚拟机和Pod

接下来,我们来看看怎么通过服务(Services)来暴露容器和虚拟机。下面这个图展示了 Kubernetes 的 SDN 是如何把所有的容器(Pods)连接到一个共享网络里的。这就像是把所有的容器都放在同一个虚拟网络派对里,它们可以轻松地互相发送消息,不管它们在集群的哪个角落。

pod-sdn

容器在集群里可是个“流浪汉”,它们会不断地被创建和销毁。比如,当你更新应用版本的时候,Kubernetes 会把旧的容器干掉,然后创建新的容器来运行新版本。再比如,当某个节点需要维护的时候,Kubernetes 会把那个节点上的容器都销毁,然后在其他节点上重新创建新的容器。这就像是在玩“ musical chairs”(抢椅子游戏),容器们不断地在节点之间跳来跳去。

因为 Kubernetes 给每个容器都分配了一个独一无二的 IP 地址,所以要想找到这些容器,可不是件容易的事。这就像是在一个不断变化的迷宫里找朋友,每次都得重新找一遍。

别担心,Kubernetes 的服务(Services)就是来解决这个问题的。服务就像是一个“固定联络点”,它给其他容器提供了一个单一且固定的 IP 地址,不管那些容器跑到哪里去了。虚拟机(VMs)也是在容器里运行的,所以它们也可以用服务来互相通信。

服务是怎么做到的呢?它用了一种叫做“标签”(labels)的东西。你可以给容器贴上标签,然后服务就会用这些标签来找到对应的容器。这就像是给容器们贴上了一个“名字标签”,服务就可以通过这个标签来找到它们。当容器被创建或者销毁的时候,服务会自动更新这些“名字标签”,并且把流量均匀地分配到各个容器上。这就像是有一个超级智能的交通指挥官,确保所有的流量都能顺利到达目的地。

Kubernetes 用了一个子网来管理容器,另一个子网来管理服务。这就像是有两个不同的“邮局”,一个负责容器的邮件,另一个负责服务的邮件。当你通过服务的 IP 地址发送请求的时候,Kubernetes 会自动把请求转发到正确的容器上。这就像是你只需要把信寄到“邮局”,然后“邮局”会帮你把信送到正确的人手里。

pod-service-sdn

想象一下,你有一个应用,它有三个容器在运行 API。这些容器可能不在同一个节点上。你可以创建一个服务,比如叫 service1,它会在这些容器之间平衡负载。你还可以创建另一个服务,比如叫 service2,它可以把请求转发到一个虚拟机上。这就像是你有一个超级智能的助手,它会帮你把请求送到正确的地方,不管那个地方在哪里。

嘿,朋友们!今天咱们继续聊 Kubernetes 的那些事儿,这次重点说说怎么查看集群的网络配置。

查看集群的网络配置

在安装 Kubernetes 集群的时候,你可以配置每个网络的地址范围。这个地址范围就像是给每个网络分配了一块“地盘”,所有的容器和服务都会在这个“地盘”里分配 IP 地址。

如果你想查看你的集群正在使用哪些地址范围,可以运行一个简单的命令:

1
oc get network/cluster -o yaml

假设你运行了上面的命令,输出可能看起来像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: config.openshift.io/v1  # API 版本号,指定 Kubernetes API 的版本
kind: Network # 资源类型,表示这是一个 Network 类型的资源
metadata: # 资源的元数据
name: cluster # 资源的名称,这里是集群的网络配置
spec: # 网络的具体配置
clusterNetwork: # 容器网络的配置
- cidr: 10.8.0.0/14 # 容器网络的地址范围,所有容器的 IP 地址都会在这个范围内分配
hostPrefix: 23 # 每个节点上容器的子网前缀,每个节点上的容器会分配到一个 /23 的子网
externalIP: # 外部 IP 的配置
policy: {} # 外部 IP 的策略,这里为空表示没有特别的外部 IP 配置
networkType: OVNKubernetes # 使用的网络插件类型,这里是 OVN-Kubernetes
serviceNetwork: # 服务网络的配置
- 172.30.0.0/16 # 服务网络的地址范围,所有服务的 IP 地址都会在这个范围内分配

Kubernetes 能提供好几种服务,让虚拟机(VM)能被集群外的设备访问。具体来说,Kubernetes 支持以下几种服务类型:

  • ClusterIP 类型的服务:它会给服务分配一个内部 IP 地址,不过这个地址只能在集群内部用,外边的设备想访问可不行。
  • NodePort 类型的服务:它会在集群的每个节点上开放一个端口,外部设备可以通过节点的 IP 地址加上这个端口来访问服务。
  • LoadBalancer 类型的服务:它会给服务分配一个外部可访问的 IP 地址,外部设备可以直接通过这个 IP 地址来访问服务。
  • ExternalName 类型的服务:它会将服务映射到一个外部的域名,外部设备可以通过这个域名来访问服务。

再看看 Red Hat OpenShift,它也很牛,提供了一种叫路由(route)的机制,能让服务在集群外被访问。这路由机制的详细情况,咱们以后再慢慢唠。

通过 DNS 记录来访问服务

首先,Kubernetes 内部有个 DNS 服务,它能响应集群内部应用程序的查询请求,帮它们找到服务的 IP 地址。这个 DNS 服务是由 DNS Operator 来管理的,它会部署并运行一个 DNS 服务器,这个服务器会自动监控服务,创建和更新 DNS 记录。DNS Operator 管理的域名是 svc.cluster.local,它会按照 servicename.namespace.svc.cluster.local 的格式来创建记录。而且,它还会自动在 Pod 里创建 /etc/resolv.conf 配置文件,这样 Pod 就能直接进行域名解析,不用再做其他配置了。

比如说,你想获取 prod2 命名空间里 backend 服务的 IP 地址,就可以通过查询 backend.prod2.svc.cluster.local 这个记录来实现,就像这样:

1
2
[lxh@host ~]# getent hosts backend.prod2.svc.cluster.local
172.30.123.204 backend.prod2.svc.cluster.local

为虚拟机(VM)创建服务

接下来,说说怎么为虚拟机(VM)创建服务。在 Kubernetes 里,虚拟机是运行在 virt-launcher Pod 里面的。这些 Pod 会在 Pod SDN 上获得一个 IP 地址,你可以用这个地址来创建一个 Kubernetes 服务,通过服务 SDN 上的一个固定 IP 地址来访问虚拟机,这个服务的 IP 地址和虚拟机的 IP 地址是不一样的。

virt-launcher 进程在 Pod 里运行,它会运行一个动态主机配置协议(DHCP)服务器,给虚拟机分配 IP 地址和 DNS 配置。virt-launcher Pod 会把进入的流量重定向到虚拟机,并且把出去的流量路由到目的地。

你可以创建一个 Kubernetes 服务,把虚拟机暴露在集群内部。一旦虚拟机被暴露,集群里的 Pod 和其他虚拟机就能访问虚拟机上运行的应用程序了。

最后,说说怎么为服务准备标签。如果你想暴露一个虚拟机,就得给 VirtualMachine 资源添加一个标签,然后创建一个服务,这个服务的标签选择器要和你添加到虚拟机上的标签一致。

如果你想通过 Web 控制台给虚拟机添加标签,就去 Virtualization → VirtualMachines,选中虚拟机,然后去 YAML 标签那里操作。

webconsole-yaml

要是你想给虚拟机加标签,一定要在 .spec.template.metadata.labels 这个路径下的标签部分加哦。这样操作能确保标签被正确设置到 virt-launcher Pod 上。为啥要强调这个呢?因为虚拟机的资源配置里有不少标签部分,但只有这个位置的标签才是 Kubernetes 用来识别服务相关 Pod 的关键。其他地方的标签,虽然也有用,但和这个服务识别没啥关系。

要是你想在命令行里给虚拟机加标签,那就用 oc edit vm vmname 命令来编辑虚拟机,然后保存修改就行啦。

比如你想给名为 backendvm 的虚拟机加标签,操作就像这样:

1
[lxh@host ~]$ oc edit vm backendvm

然后你会看到类似这样的虚拟机资源配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
...output omitted...
labels:
app: backendvm
...output omitted...
name: backendvm
spec:
dataVolumeTemplates:
...output omitted...
template:
metadata:
creationTimestamp: null
labels:
tier: backend
...output omitted...

不过得注意,当你编辑虚拟机资源清单的时候,Kubernetes 不会自动把你的修改同步到 VirtualMachineInstance 和 virt-launcher Pod 资源上。要是想让这些修改生效,你可以重启虚拟机,这样 Kubernetes 就会根据最新的虚拟机资源清单重新创建 VirtualMachineInstance 和 virt-launcher Pod 资源。

要是你想从 OpenShift 的 Web 控制台重启虚拟机,就去 Virtualization → VirtualMachines,选中虚拟机,然后点击 Actions → Restart,或者直接用重启图标。

restart-vm

除了用 Web 控制台,你还能用 virtctl 客户端从命令行重启虚拟机呢。比如你想重启名为 backendvm 的虚拟机,就用下面这个命令:

1
2
[lxh@host ~]$ virtctl restart backendvm
VM backendvm was scheduled to restart

重启之后,你可以用 oc get vm 命令看看虚拟机是不是已经运行起来了:

1
2
3
[lxh@host ~]$ oc get vm
NAME AGE PHASE IP NODENAME READY
backendvm 23s Running 10.11.0.22 worker02 True

要是你想手动给 virt-launcher Pod 设置 tier: backend 标签,直接在命令行里用 oc label pod 命令就行。先用 oc get pods 找到 Pod 的名字:

1
2
3
[lxh@host ~]$ oc get pods
NAME READY STATUS RESTARTS AGE
virt-launcher-backendvm-frxbj 1/1 Running 0 4m34s

然后给它加上标签:

1
2
[lxh@host ~]$ oc label pod virt-launcher-backendvm-frxbj tier=backend
pod/virt-launcher-backendvm-frxbj labeled

要是你更习惯用 Web 控制台来操作,就去 Workloads → Pods,选中对应的 virt-launcher Pod,然后在 Details 标签页里手动设置 tier: backend 标签。

edit-label

在 Labels 部分,点击 Edit 按钮,然后就能添加标签啦。

add-label

说到这儿,我得提醒你一句,虽然你可以给 virt-launcher Pod 设置标签,但你还是得在虚拟机资源级别定义相同的标签。为啥呢?因为当你重启虚拟机的时候,Kubernetes 会销毁 VMI 和 virt-launcher Pod 资源,然后根据虚拟机资源重新创建它们。要是你没有在虚拟机资源上关联标签,那么你之前给 virt-launcher Pod 设置的标签就会丢失。

所以,一定要记得在虚拟机资源上也设置好标签哦,这样即使重启虚拟机,标签也能一直保持有效。

要是你想从 Web 控制台创建服务,操作也很简单。直接去 Networking → Services,然后点击 Create Service。接下来,就可以用 YAML 编辑器来声明服务啦。

svc-manifest

你创建好服务后,Web上就会显示IP地址了

你要是从命令行创建服务,可以用下面这个清单文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
name: backend # 你创建的服务的名字,DNS Operator 会为这个名字创建记录
namespace: prod2 # 宿主 VM 的命名空间
spec:
type: ClusterIP
selector:
tier: backend # 与你在 VirtualMachine 资源中定义的标签匹配
ports:
- protocol: TCP # 服务监听 80/TCP 端口并将请求转发到后端 VM 的 8080 端口
port: 80
targetPort: 8080

oc create -f service_file.yaml 命令来创建服务:

1
2
[lxh@host ~]$ oc create -f service_file.yaml
service/backend created

创建服务后,可以用 oc get svc 命令确认服务创建成功并查看分配的 IP 地址:

1
2
3
[lxh@host ~]$ oc get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
backend ClusterIP 172.30.123.204 <none> 80/TCP 2m57s

virtctl 工具也提供了从命令行创建服务的方法。可以用 virtctl expose vmi vmi-name 命令来创建服务,该命令会使用 VM 的所有标签来创建服务的选择器:

1
2
[lxh@host ~]$ virtctl expose vmi backendvm --name backend \
--type ClusterIP --port 80 --target-port 8080

对于每个匹配选择器的 Pod,Kubernetes 会自动创建一个端点资源。一个 VMI 必须匹配选择器使用的所有标签,服务才会包含该 VMI。虽然使用 VM 的所有标签来创建服务很方便,但可能并不理想。如果 VMI 上的标签发生变化,那么该 VMI 就不再包含在服务中了。

要确认服务指向 VM,可以比较端点资源中的 IP 地址与 VirtualMachineInstance 资源中的 IP 地址:

1
2
3
4
5
6
7
[lxh@host ~]$ oc get endpoints
NAME ENDPOINTS AGE
backend 10.128.3.135:8080 3m13s

[lxh@host ~]$ oc get vmi
NAME AGE PHASE IP NODENAME READY
backendvm 13m Running 10.128.3.135 prodnode1 True

创建服务后,DNS Operator 会添加 backend.prod2.svc.cluster.local 记录,地址为 172.30.123.204。然后,你可以通过 backend.prod2.svc.cluster.local DNS 名称从另一个 Pod 或另一个 VM 访问 VM 上运行的应用程序。由于 DNS Operator 在 Pod 上部署的 /etc/resolv.conf 文件定义了 svc.cluster.localprod2.svc.cluster.local 搜索域,你也可以使用 backend.prod2backend 短名称来访问应用程序。

下面这个例子启动了一个临时测试 Pod,并对服务名称进行 DNS 查询:

1
2
3
4
5
6
7
8
9
10
11
[lxh@host ~]$ oc run mytestnet -it --rm --image=rhel8/toolbox
If you don't see a command prompt, try pressing enter.

[user@mytestnet /]# getent hosts backend.prod2.svc.cluster.local
172.30.123.204 backend.prod2.svc.cluster.local

[user@mytestnet /]# getent hosts backend.prod2
172.30.123.204 backend.prod2.svc.cluster.local

[user@mytestnet /]# getent hosts backend
172.30.123.204 backend.prod2.svc.cluster.local

为虚拟机配置无头服务(headless)

服务为稳定 IP 地址提供 DNS 名称,还为连接到服务的所有 Pod 提供负载均衡。但是,如果客户端要直接连接到单个 Pod,可以使用无头服务。无头服务创建一个 DNS 条目,将 DNS 名称与 Pod 的 IP 地址绑定。无头服务允许客户端直接连接到它偏好的任意 Pod。不会分配集群 IP;kube-proxy 不处理服务;平台也不会进行负载均衡或代理。

要创建无头服务,将清单中的 spec.clusterIP 字段设置为 None

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Service
metadata:
name: mysubdomain # 服务名称必须与 VirtualMachine 资源中的 spec.subdomain 属性匹配,DNS Operator 会为该服务名称创建记录
namespace: prod2 # 宿主 VM 的命名空间
spec:
type: ClusterIP
selector:
tier: backend # 与你在 VirtualMachine 资源中定义的标签匹配
clusterIP: None # 指定无头服务
ports: # 服务暴露的端口列表必须至少定义一个端口
- protocol: TCP
port: 1234
targetPort: 1234