Red Hat OpenShift 虚拟化中的Localnet UDN示例

使用 CUDN Localnet 网络将 OpenShift 虚拟化虚拟机连接到 IBM Cloud VPC 子网,以实现基于 VLAN 且命名空间隔离的流量传输。

本教程将演示如何在 IBM Cloud 上的 Red Hat OpenShift on IBM Cloud 中,利用虚拟私有云(VPC)子网、集群用户定义网络(CUDN)、本地网和命名空间来创建一个三层应用程序示例。 本示例演示了如何利用基于虚拟局域网(VLAN)的 localnet 网络,将运行在 Red Hat OpenShift 虚拟化平台上的虚拟服务器直接连接到 VPC 子网。 有关网络类型的更多信息,请参阅 《 Red Hat OpenShift 》中面向 vSphere® 管理员的“开放虚拟网络(OVN)”网络配置部分。

概述

该示例部署了一个三层应用程序,其中Web层、数据库层和应用层各自位于不同的网络段中。 每个层级都使用一个专用的 VPC 子网和一个具有唯一 VLAN ID 的本地网 CUDN。 Web 层和数据库层被放置在一个命名空间中(green ),而应用程序层则被放置在另一个独立的命名空间中(red ),以此演示基于命名空间的隔离机制。

网络、命名空间和虚拟服务器的详细信息

下表总结了示例网络拓扑。

Localnet CUDN、命名空间和虚拟服务器布局示例
Localnet CUDN 名称空间 VLAN 标识 VPC 子网 虚拟服务器
vlan20-prod green 20 198.18.0.0/24 plant-web00, plant-web01
vlan21-db green 21 198.18.1.0/24 plant-db00, plant-db01
vlan22-app red 22 198.18.2.0/24 plant-app00, plant-app01

最终设置示意图

Localnet 三层应用程序架构
Localnet 三层应用程序架构

指南中的每个代码块都可以复制到文件中,并通过 oc apply -f <file-name>.yml 进行应用。 在 PDF 和 Word 格式中,某些代码块可能会跨越页面边界,而某些较长的行可能会在这些格式中换行。

本指南在 IBM Cloud 的 CLI 命令中,将 ic 作为 ibmcloud 的简写形式。 请在您的终端中定义 alias ic=ibmcloud,或者在后续命令中将 ic 替换为 ibmcloud。

准备工作

请确保已准备好以下各项:

  • 一个具有管理 VPC 和 Red Hat OpenShift 资源权限的 IBM Cloud 账户。
  • IBM Cloud 命令行界面已安装。
  • 已安装 container-service 和 vpc-infrastructure 插件。

container-service 插件提供了 ic ks 命令,本指南使用这些命令将VLAN虚拟网络接口(VNI)连接到集群。 vpc-infrastructure 插件提供了 ic is 命令,本指南将使用这些命令来创建VPC子网、虚拟网络接口(VNI)以及其他基础设施。

如果您需要安装所需的 CLI 插件,请使用以下命令。

ibmcloud plugin install container-service
ibmcloud plugin install vpc-infrastructure

检查网络先决条件

如果您使用的是 Red Hat OpenShift on IBM Cloud,请在开始之前查阅 Red Hat OpenShift on IBM Cloud 中的 OVN UDN/CUDN 网络先决条件。Red Hat OpenShift 虚拟化服务默认已包含这些先决条件。

为绿色层创建命名空间和 CUDN Localnet 二级网络

Localnet 网络可以在同一个命名空间内与 Layer 2 主网络、Layer 2 辅助网络以及其他 Localnet 网络共存。 每个本地网络都需要一个 VLAN ID。 目前,每个VLAN只能使用一个子网。

VLAN 不会在工作节点之间创建 Layer 2 域。 VLAN 用于每个工作节点内部,而非 VPC 内部。

创建 green 命名空间以及 vlan20-prod 和 vlan21-db 网络。 使用 oc apply -f step1.yml 应用该清单。

---
apiVersion: v1
kind: Namespace
metadata:
  name: green
---
apiVersion: k8s.ovn.org/v1
kind: ClusterUserDefinedNetwork
metadata:
  name: "vlan20-prod"
spec:
  namespaceSelector:
    matchExpressions:
    - key: kubernetes.io/metadata.name
      operator: In
      values:
      - "green"
  network:
    topology: Localnet
    localnet:
      role: "Secondary"
      physicalNetworkName: "vpc-vlans"
      ipam:
        mode: Disabled
      vlan:
        mode: Access
        access:
          id: 20
---
apiVersion: k8s.ovn.org/v1
kind: ClusterUserDefinedNetwork
metadata:
  name: "vlan21-db"
spec:
  namespaceSelector:
    matchExpressions:
    - key: kubernetes.io/metadata.name
      operator: In
      values:
      - "green"
  network:
    topology: Localnet
    localnet:
      role: "Secondary"
      physicalNetworkName: "vpc-vlans"
      ipam:
        mode: Disabled
      vlan:
        mode: Access
        access:
          id: 21

请牢记以下几点:

  • 在 Localnet 配置中未定义该子网。
  • 请勿使用 VLAN 1。
  • 请使用 2 到 500 之间的 VLAN 号。 VLAN 501 至 4094 目前处于受限状态。

为红色层创建另一个命名空间和 CUDN Localnet 二级网络

创建 red 命名空间和 vlan22-app 网络。 使用 oc apply -f step2.yml 应用该清单。

---
apiVersion: v1
kind: Namespace
metadata:
  name: red
---
apiVersion: k8s.ovn.org/v1
kind: ClusterUserDefinedNetwork
metadata:
  name: "vlan22-app"
spec:
  namespaceSelector:
    matchExpressions:
    - key: kubernetes.io/metadata.name
      operator: In
      values:
      - "red"
  network:
    topology: Localnet
    localnet:
      role: "Secondary"
      physicalNetworkName: "vpc-vlans"
      ipam:
        mode: Disabled
      vlan:
        mode: Access
        access:
          id: 22

在您的 VPC 账户中创建保留 IP 和 VNI

此步骤将在每个子网中创建一个保留 IP 地址,然后使用该保留 IP 地址创建 VNI,并为每个虚拟网络接口分配一个 MAC address (VNI )。

请牢记以下几点:

  • 在此步骤中,VLAN ID 未被连接。
  • 您必须查阅本步骤中要使用的安全组。 运行 ic is sgs 以列出可用安全组。
  • 您可以批量执行此步骤。
  • 每个主机最多支持 256 个 VNI。 请将使用率控制在85%以下,以便进行故障转移和从其他主机迁移。

在每个子网中创建保留 IP 地址

首先,在每个子网中为您的虚拟服务器创建一个保留 IP 地址:

ic is subnet-reserved-ip-create zone1-vm-prod --address 198.18.0.20 --name zone1-web00 --auto-delete false
ic is subnet-reserved-ip-create zone1-vm-db --address 198.18.1.20 --name zone1-db00 --auto-delete false
ic is subnet-reserved-ip-create zone1-vm-app --address 198.18.2.20 --name zone1-app00 --auto-delete false

使用保留的 IP 地址创建 VNI

现在,使用上一步中预留的 IP 名称创建 VNI:

ic is virtual-network-interface-create --name zone1-web00 --allow-ip-spoofing false --auto-delete false --protocol-state-filtering-mode disabled --enable-infrastructure-nat true --subnet zone1-vm-prod --vpc my-vpc --rip zone1-web00 --sgs handclap-roundworm-clique-eccentric --resource-group-name Default
ic is virtual-network-interface-create --name zone1-db00 --allow-ip-spoofing false --auto-delete false --protocol-state-filtering-mode disabled --enable-infrastructure-nat true --subnet zone1-vm-db --vpc my-vpc --rip zone1-db00 --sgs handclap-roundworm-clique-eccentric --resource-group-name Default
ic is virtual-network-interface-create --name zone1-app00 --allow-ip-spoofing false --auto-delete false --protocol-state-filtering-mode disabled --enable-infrastructure-nat true --subnet zone1-vm-app --vpc my-vpc --rip zone1-app00 --sgs handclap-roundworm-clique-eccentric --resource-group-name Default

将 VLAN VNI 绑定到集群

获取上一步中创建的每个 VNI 的 ID,然后使用 IBM Cloud container-service 插件将 VNI 连接到集群。 您需要集群 ID,可通过运行 ic ks cluster ls 获取。

请牢记以下几点:

  • 如果某个工作器的 VNI 配额已满,则关联的操作将失败。
  • VNI 可以绑定到集群(也称为“floating”),也可以绑定到特定的 worker。

查询 VNI ID。

ic is vni zone1-web00 | awk '$1 ~ /^ID/ {print $NF}'
ic is vni zone1-db00 | awk '$1 ~ /^ID/ {print $NF}'
ic is vni zone1-app00 | awk '$1 ~ /^ID/ {print $NF}'

将每个 VNI 与对应的 VLAN ID 关联到集群中。 请将集群 ID 和 VNI ID 替换为您环境中的实际值。

ic ks vni attach baremetal -c <CLUSTER_ID> --vni <VNI_ID_web00> --vlan 20
ic ks vni attach baremetal -c <CLUSTER_ID> --vni <VNI_ID_db00>  --vlan 21
ic ks vni attach baremetal -c <CLUSTER_ID> --vni <VNI_ID_app00> --vlan 22

将虚拟服务器添加到命名空间中

以下清单是一个简单的 YAML 示例,用于创建一个虚拟服务器。 您可以针对每个命名空间和网络更新此清单。 对于您创建的每个 VNI,都需要一个 MAC address。 运行 ic is vnis 命令以列出这些文件,并更新模板中的 macAddress 字段,以便 DHCP 能正常工作。

使用以下命令查找每个 VNI 的 MAC address。

ic is vni zone1-web00 | awk '/^Mac Address/ {print $NF}'
ic is vni zone1-db00  | awk '/^Mac Address/ {print $NF}'
ic is vni zone1-app00 | awk '/^Mac Address/ {print $NF}'

每个 VNI 的示例输出。 您的值不同:

02:00:01:00:98:A0
02:00:02:00:98:A5
02:00:02:00:98:AA

在应用清单之前,请牢记以下几点:

  • 在每个清单中更新密码。
  • 您需要在“ssh_authorized_keys”字段中输入一个真实的 SSH 公钥才能登录。
  • 您无法使用密码登录 SSH。
  • 将每个 macAddress 值替换为在前面的命令中查询到的、与之匹配的VNI的 MAC地址。 macAddress 必须与VNI匹配,这样 DHCP 才能分配预期的IP地址。

使用 oc apply -f <file-name>.yml 应用以下每个清单。 请使用以下链接直接跳转至各清单。

位于 green 命名空间中的虚拟服务器 plant-web00,地址为 vlan20-prod

# Bare YAML to stand up a Red Hat OpenShift on IBM Cloud virtual machine
---
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: "plant-web00"
  namespace: "green"
  annotations:
    description: "example vm plant-web00"
  labels:
    app: "plant-web00-green-server"
    kubevirt.io/dynamic-credentials-support: 'true'
    vm.kubevirt.io/template: "centos-stream9-server-small"
    vm.kubevirt.io/template.namespace: openshift
    vm.kubevirt.io/template.revision: '1'
    vm.kubevirt.io/template.version: v0.34.0
spec:
  dataVolumeTemplates:
    - apiVersion: cdi.kubevirt.io/v1beta1
      kind: DataVolume
      metadata:
        creationTimestamp: null
        name: "plant-web00-green-server"
      spec:
        sourceRef:
          kind: DataSource
          name: "centos-stream9"
          namespace: openshift-virtualization-os-images
        storage:
          resources:
            requests:
              storage: 30Gi
  runStrategy: RerunOnFailure
  template:
    metadata:
      annotations:
        vm.kubevirt.io/flavor: "small"
        vm.kubevirt.io/os: "centos-stream9"
        vm.kubevirt.io/workload: "server"
      labels:
        kubevirt.io/domain: example
        kubevirt.io/size: "small"
    spec:
      domain:
        cpu:
          cores: 1
          sockets: 1
          threads: 1
        devices:
          disks:
            - disk:
                bus: virtio
              name: rootdisk
            - disk:
                bus: virtio
              name: cloudinitdisk
          interfaces:
            - model: virtio
              name: default
              state: up
              bridge: {}
              macAddress: 02:00:01:00:98:A0 # <- replace with the MAC of VNI zone1-web00
          networkInterfaceMultiqueue: true
          rng: {}
        memory:
          guest: "2Gi"
      hostname: "plant-web00"
      networks:
        - name: default
          multus:
            networkName: vlan20-prod
      terminationGracePeriodSeconds: 180
      volumes:
        - dataVolume:
            name: "plant-web00-green-server"
          name: rootdisk
        - cloudInitNoCloud:
            userData: |-
              #cloud-config
              user: admin
              password: "aRandomPassword9292"
              ssh_authorized_keys:
                 - "ssh-ed25519 AAAA....BBBB....CCCCC.....DDDD put-your-key-here@example.local"
              chpasswd: { expire: False }
              runcmd:
                - [ dnf, install, -y, epel-release ]
                - [ dnf, install, -y, iperf3, nmap-ncat, darkhttpd ]
          name: cloudinitdisk

位于 green 命名空间中的虚拟服务器 plant-db00,地址为 vlan21-db

---
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: "plant-db00"
  namespace: "green"
  annotations:
    description: "example vm plant-db00"
  labels:
    app: "plant-db00-green-server"
    kubevirt.io/dynamic-credentials-support: 'true'
    vm.kubevirt.io/template: "centos-stream9-server-small"
    vm.kubevirt.io/template.namespace: openshift
    vm.kubevirt.io/template.revision: '1'
    vm.kubevirt.io/template.version: v0.34.0
spec:
  dataVolumeTemplates:
    - apiVersion: cdi.kubevirt.io/v1beta1
      kind: DataVolume
      metadata:
        creationTimestamp: null
        name: "plant-db00-green-server"
      spec:
        sourceRef:
          kind: DataSource
          name: "centos-stream9"
          namespace: openshift-virtualization-os-images
        storage:
          resources:
            requests:
              storage: 30Gi
  runStrategy: RerunOnFailure
  template:
    metadata:
      annotations:
        vm.kubevirt.io/flavor: "small"
        vm.kubevirt.io/os: "centos-stream9"
        vm.kubevirt.io/workload: "server"
      labels:
        kubevirt.io/domain: example
        kubevirt.io/size: "small"
    spec:
      domain:
        cpu:
          cores: 1
          sockets: 1
          threads: 1
        devices:
          disks:
            - disk:
                bus: virtio
              name: rootdisk
            - disk:
                bus: virtio
              name: cloudinitdisk
          interfaces:
            - model: virtio
              name: default
              state: up
              bridge: {}
              macAddress: 02:00:01:00:98:A5 # <- replace with the MAC of VNI zone1-db00
          networkInterfaceMultiqueue: true
          rng: {}
        memory:
          guest: "2Gi"
      hostname: "plant-db00"
      networks:
        - name: default
          multus:
            networkName: vlan21-db
      terminationGracePeriodSeconds: 180
      volumes:
        - dataVolume:
            name: "plant-db00-green-server"
          name: rootdisk
        - cloudInitNoCloud:
            userData: |-
              #cloud-config
              user: admin
              password: "aRandomPassword9292"
              ssh_authorized_keys:
                 - "ssh-ed25519 AAAA....BBBB....CCCCC.....DDDD put-your-key-here@example.local"
              chpasswd: { expire: False }
              runcmd:
                - [ dnf, install, -y, epel-release ]
                - [ dnf, install, -y, iperf3, nmap-ncat, darkhttpd ]
          name: cloudinitdisk

位于 red 命名空间中的虚拟服务器 plant-app00,地址为 vlan22-app

---
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: "plant-app00"
  namespace: "red"
  annotations:
    description: "example vm plant-app00"
  labels:
    app: "plant-app00-red-server"
    kubevirt.io/dynamic-credentials-support: 'true'
    vm.kubevirt.io/template: "centos-stream9-server-small"
    vm.kubevirt.io/template.namespace: openshift
    vm.kubevirt.io/template.revision: '1'
    vm.kubevirt.io/template.version: v0.34.0
spec:
  dataVolumeTemplates:
    - apiVersion: cdi.kubevirt.io/v1beta1
      kind: DataVolume
      metadata:
        creationTimestamp: null
        name: "plant-app00-red-server"
      spec:
        sourceRef:
          kind: DataSource
          name: "centos-stream9"
          namespace: openshift-virtualization-os-images
        storage:
          resources:
            requests:
              storage: 30Gi
  runStrategy: RerunOnFailure
  template:
    metadata:
      annotations:
        vm.kubevirt.io/flavor: "small"
        vm.kubevirt.io/os: "centos-stream9"
        vm.kubevirt.io/workload: "server"
      labels:
        kubevirt.io/domain: example
        kubevirt.io/size: "small"
    spec:
      domain:
        cpu:
          cores: 1
          sockets: 1
          threads: 1
        devices:
          disks:
            - disk:
                bus: virtio
              name: rootdisk
            - disk:
                bus: virtio
              name: cloudinitdisk
          interfaces:
            - model: virtio
              name: default
              state: up
              bridge: {}
              macAddress: 02:00:01:00:98:AA # <- replace with the MAC of VNI zone1-app00
          networkInterfaceMultiqueue: true
          rng: {}
        memory:
          guest: "2Gi"
      hostname: "plant-app00"
      networks:
        - name: default
          multus:
            networkName: vlan22-app
      terminationGracePeriodSeconds: 180
      volumes:
        - dataVolume:
            name: "plant-app00-red-server"
          name: rootdisk
        - cloudInitNoCloud:
            userData: |-
              #cloud-config
              user: admin
              password: "aRandomPassword9292"
              ssh_authorized_keys:
                 - "ssh-ed25519 AAAA....BBBB....CCCCC.....DDDD put-your-key-here@example.local"
              chpasswd: { expire: False }
              runcmd:
                - [ dnf, install, -y, epel-release ]
                - [ dnf, install, -y, iperf3, nmap-ncat, darkhttpd ]
          name: cloudinitdisk

测试网络设置

IBM 建议您在进行这些测试时,使用与集群位于同一 VPC 中的 Linux® 中转主机。 从跳转主机出发,使用 SSH 通过您在虚拟服务器设置中指定的公钥连接到每台虚拟服务器。

配置 SSH 代理转发,或者使用托管在跳转主机上的新密钥。 请勿在不同系统之间复制您的私钥。

在一个终端会话中,使用 SSH 连接到 Web 层虚拟服务器:

ssh admin@198.18.0.20

在另一个终端会话中,使用 SSH 连接到数据库层虚拟服务器:

ssh admin@198.18.1.20

在另一个终端会话中,使用 SSH 连接到应用层虚拟服务器:

ssh admin@198.18.2.20

测试

运行以下测试以验证网络配置。

  1. 在每台虚拟服务器上启动一个简单的 HTTP 服务器。

    • 在 198.18.0.20 上,运行 darkhttpd /usr/share/doc/bash --daemon。
    • 在 198.18.1.20 上,运行 darkhttpd /usr/share/doc/bash --daemon。
    • 在 198.18.2.20 上,运行 darkhttpd /usr/share/doc/bash --daemon。
  2. 在 198.18.0.20 上,运行 curl -v http://198.18.1.20:8080。

  3. 在 198.18.1.20 上,运行 curl -v http://198.18.0.20:8080。

  4. 在 jump 虚拟服务器上,运行 curl -v http://198.18.2.20:8080。

  5. 在 198.18.1.20 上,启动一个 iperf3 服务器:iperf3 -s -p 9090。

  6. 在 198.18.0.20 上,连接到 green 命名空间中的 iperf3 服务器:iperf3 -c 198.18.1.20 -p 9090。

  7. 从 198.18.1.20 向 198.18.0.20 发送 ping 请求,反之亦然。

  8. 使用 198.18.2.20 以及 jump 虚拟服务器的 IP 地址,重新运行上述任务。

应用多网络策略并重新运行测试

为 vlan20-prod 网络应用一项多网络策略,仅允许通过 TCP 的 8080 端口进行访问。 将以下清单保存为名为 my-policy.yml 的文件。 然后,运行 oc apply -f my-policy.yml 以应用该配置。

---
apiVersion: k8s.ovn.org/v1alpha1
kind: MultiNetworkPolicy
metadata:
  name: allow-8080-tcp
  namespace: green
  annotations:
    k8s.v1.cni.cncf.io/policy-for: vlan20-prod
spec:
  podSelector: {}
  ingress:
    - ports:
        - protocol: TCP
          port: 8080
  egress:
    - ports:
        - protocol: TCP
          port: 8080
  policyTypes:
    - Ingress
    - Egress

在 vlan20-prod 上针对 plant-web00 重新运行 测试。 TCP 8080 端口仍然可以正常工作,但其他流量(例如 ping 以及 9090 端口的 iperf3 测试)在该网络连接上已无法正常工作。

运行 oc delete -f my-policy.yml 以删除该策略并恢复连接。 测试再次通过了。