Localnet UDN examples for Red Hat OpenShift Virtualization
Attach OpenShift Virtualization VMs to IBM Cloud VPC subnets using CUDN Localnet networks for VLAN-backed, namespace-isolated traffic.
This tutorial shows how to create an example three-tier application by using Virtual Private Cloud (VPC) subnets, Cluster User-Defined Networks (CUDNs), localnets, and namespaces on Red Hat OpenShift on IBM Cloud on IBM Cloud. The example demonstrates how to attach virtual servers that run on Red Hat OpenShift Virtualization directly to VPC subnets by using virtual local area network (VLAN)-backed localnet networks. For more information about network types, see Open Virtual Network (OVN) networking in Red Hat OpenShift for vSphere® administrators.
Overview
The example deploys a three-tier application with separate network segments for web, database, and application tiers. Each tier uses a dedicated VPC subnet and a localnet CUDN with a unique VLAN ID. The web and database tiers are placed in one
namespace (green), while the application tier is placed in a separate namespace (red) to demonstrate namespace-based isolation.
Network, namespace, and virtual server details
The following table summarizes the example network layout.
| Localnet CUDN | Namespace | VLAN ID | VPC subnet | Virtual servers |
|---|---|---|---|---|
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 |
Diagram of the final setup
Each code block in the guide can be copied to a file and applied with oc apply -f <file-name>.yml. Some code blocks might cross page boundaries in PDF and Word formats, and some long lines might wrap in those formats.
The guide uses ic as a shorthand for ibmcloud in IBM Cloud CLI commands. Define alias ic=ibmcloud in your shell, or substitute ibmcloud for ic in the commands that follow.
Before you begin
Make sure that you have the following items in place:
- An IBM Cloud account with permission to manage VPC and Red Hat OpenShift resources.
- The IBM Cloud CLI is installed.
- The
container-serviceandvpc-infrastructureplug-ins installed.
The container-service plug-in provides the ic ks commands that the guide uses to attach VLAN virtual network interfaces (VNIs) to the cluster. The vpc-infrastructure plug-in provides the ic is commands that the guide uses to create VPC subnets, virtual network interfaces (VNIs), and other infrastructure.
If you need to install the required CLI plug-ins, use the following commands.
ibmcloud plugin install container-service
ibmcloud plugin install vpc-infrastructure
Review network prerequisites
If you are using Red Hat OpenShift on IBM Cloud, review Red Hat OpenShift on IBM Cloud OVN UDN/CUDN network prerequisites before you begin. Red Hat OpenShift Virtualization Service includes these prerequisites by default.
Creating a namespace and CUDN Localnet secondary network for the green tier
Localnet networks can coexist with Layer 2 primary networks, Layer 2 secondary networks, and other localnets in a namespace. Each localnet requires a VLAN ID. Currently, you can use only one subnet per
VLAN.
The VLAN does not create a Layer 2 domain between workers. The VLAN is used inside each worker node, not inside the VPC.
Create the green namespace and the vlan20-prod and vlan21-db networks. Apply the manifest with 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
Keep the following considerations in mind:
- The subnet is not defined in the
Localnetconfiguration. - Do not use
VLAN 1. - Use VLANs in the range 2 - 500. VLANs 501 - 4094 are currently restricted.
Creating another namespace and CUDN Localnet secondary network for the red tier
Create the red namespace and the vlan22-app network. Apply the manifest with 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
Creating reserved IPs and VNIs in your VPC account
This step creates a reserved IP in each subnet, then creates the VNI using that reserved IP and assigns a MAC address to each virtual network interface (VNI).
Keep the following considerations in mind:
- The
VLAN IDis not attached in this step. - You must look up the security group to use in this step. Run
ic is sgsto list available security groups. - You can run this step in bulk.
- 256 VNIs per host is the cap. Keep usage less than 85% to allow for failover and migrations from other hosts.
Creating reserved IPs in each subnet
First, create a reserved IP in each subnet for your virtual servers:
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
Creating VNIs using the reserved IPs
Now create the VNIs using the reserved IP names from the previous step:
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
Attaching the VLAN VNI to the cluster
Get the ID for each VNI that you created in the previous step, and then use the IBM Cloud container-service plug-in to attach the VNI to the cluster. You need the cluster ID, which you can obtain by running ic ks cluster ls.
Keep the following considerations in mind:
- If a worker is over its VNI quota, the attached operation fails.
- VNIs can be attached to the cluster (also known as
floating) or to a particular worker.
Look up the VNI IDs.
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}'
Attach each VNI to the cluster with its matching VLAN ID. Replace the cluster ID and VNI IDs with the values from your environment.
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
Adding virtual servers to the namespaces
The following manifest is a bare YAML example for creating one virtual server. You can update this manifest for each namespace and network. You need the MAC address for each VNI that you created. Run ic is vnis to list them, and update the macAddress field in the template so that DHCP works correctly.
Use the following commands to find the MAC address for each VNI.
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}'
Example output for each VNI. Your values differ:
02:00:01:00:98:A0
02:00:02:00:98:A5
02:00:02:00:98:AA
Keep the following considerations in mind before you apply the manifests:
- Update the password in each manifest.
- You need a real SSH public key in the
ssh_authorized_keysfield to log in. - You cannot use SSH with the password.
- Replace every
macAddressvalue with the MAC address of the matching VNI that you looked up in the previous commands. ThemacAddressmust match the VNI so thatDHCPassigns the expected IP address.
Apply each of the following manifests with oc apply -f <file-name>.yml. Use the following links to jump directly to each manifest.
- Virtual server
plant-web00in thegreennamespace onvlan20-prod - Virtual server
plant-db00in thegreennamespace onvlan21-db - Virtual server
plant-app00in therednamespace onvlan22-app
Virtual server plant-web00 in the green namespace on 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
Virtual server plant-db00 in the green namespace on 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
Virtual server plant-app00 in the red namespace on 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
Testing the network setup
IBM recommends that you use a Linux® jump host in the same VPC as the cluster for these tests. From the jump host, use SSH to connect to each virtual server with the public key that you specified in the virtual server setup.
Configure SSH agent forwarding, or use a new key that is hosted on the jump host. Do not copy your private key between systems.
From one terminal session, use SSH to connect to the web tier virtual server:
ssh admin@198.18.0.20
From another terminal session, use SSH to connect to the database tier virtual server:
ssh admin@198.18.1.20
From another terminal session, use SSH to connect to the application tier virtual server:
ssh admin@198.18.2.20
Tests
Run the following tests to verify the network setup.
-
Start a simple HTTP server on each virtual server.
- From
198.18.0.20, rundarkhttpd /usr/share/doc/bash --daemon. - From
198.18.1.20, rundarkhttpd /usr/share/doc/bash --daemon. - From
198.18.2.20, rundarkhttpd /usr/share/doc/bash --daemon.
- From
-
From
198.18.0.20, runcurl -v http://198.18.1.20:8080. -
From
198.18.1.20, runcurl -v http://198.18.0.20:8080. -
From the jump virtual server, run
curl -v http://198.18.2.20:8080. -
From
198.18.1.20, start aniperf3server:iperf3 -s -p 9090. -
From
198.18.0.20, connect to theiperf3server in thegreennamespace:iperf3 -c 198.18.1.20 -p 9090. -
Ping from
198.18.1.20to198.18.0.20and vice versa. -
Rerun the preceding tasks by using
198.18.2.20and the jump virtual server's IP address.
Applying a multi-network policy and rerunning the tests
Apply a multi-network policy that allows only TCP port 8080 for the vlan20-prod network. Save the following manifest to a file named my-policy.yml. Then, run oc apply -f my-policy.yml to apply it.
---
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
Rerun the Tests against plant-web00 on vlan20-prod. TCP port 8080 still works, but other traffic, such as ping and the iperf3 test on port 9090, no longer work
on that network attachment.
Run oc delete -f my-policy.yml to remove the policy and restore connectivity. The tests pass again.