From fe5b55cf977a24ef1c288ce63ec82ac4cd732b69 Mon Sep 17 00:00:00 2001 From: Rico Lin Date: Sat, 1 Aug 2026 12:09:12 +0800 Subject: [PATCH] feat(production): support Nova consoles for bare metal Configure Nova compute-ironic VNC support, allow both console proxies to reach dynamic provider pods, and provide a digest-pinned noVNC assets override. Assisted-By: Codex Change-Id: Ibb0c07a168c94cf3207c9c092b0bdbab747d0bf8 Signed-off-by: Rico Lin --- .../templates/network-policy-console.yaml | 7 +++++ .../ironic/0002-add-novncproxy-support.patch | 11 ++++++-- ...a-ironic-vnc-console-5f403a83f03785d0.yaml | 12 +++++++++ roles/ironic/README.md | 5 ++++ roles/ironic/defaults/main.yml | 1 + .../ironic/tests/baremetal_console_test.yaml | 10 +++++++ roles/ironic/vars/main.yml | 5 ++++ roles/nova/README.md | 18 +++++++++++++ roles/nova/defaults/main.yml | 8 ++++++ roles/nova/tasks/main.yml | 25 ++++++++++++++++++ roles/nova/vars/main.yml | 26 ++++++++++++++++--- 11 files changed, 123 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/add-nova-ironic-vnc-console-5f403a83f03785d0.yaml diff --git a/charts/ironic/templates/network-policy-console.yaml b/charts/ironic/templates/network-policy-console.yaml index 2b94095443..f46142bc91 100644 --- a/charts/ironic/templates/network-policy-console.yaml +++ b/charts/ironic/templates/network-policy-console.yaml @@ -35,6 +35,13 @@ spec: matchLabels: application: ironic component: novncproxy + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ .Release.Namespace }} + podSelector: + matchLabels: + application: nova + component: novnc-proxy ports: - protocol: TCP port: 5900 diff --git a/charts/patches/ironic/0002-add-novncproxy-support.patch b/charts/patches/ironic/0002-add-novncproxy-support.patch index b0a1a8e69a..2b90f4de2a 100644 --- a/charts/patches/ironic/0002-add-novncproxy-support.patch +++ b/charts/patches/ironic/0002-add-novncproxy-support.patch @@ -335,10 +335,10 @@ index 0000000..4909ad9 +{{- end }} diff --git a/ironic/templates/network-policy-console.yaml b/ironic/templates/network-policy-console.yaml new file mode 100644 -index 0000000..8e128cf +index 0000000..b9b7b38 --- /dev/null +++ b/ironic/templates/network-policy-console.yaml -@@ -0,0 +1,41 @@ +@@ -0,0 +1,48 @@ +{{/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. @@ -376,6 +376,13 @@ index 0000000..8e128cf + matchLabels: + application: ironic + component: novncproxy ++ - namespaceSelector: ++ matchLabels: ++ kubernetes.io/metadata.name: {{ .Release.Namespace }} ++ podSelector: ++ matchLabels: ++ application: nova ++ component: novnc-proxy + ports: + - protocol: TCP + port: 5900 diff --git a/releasenotes/notes/add-nova-ironic-vnc-console-5f403a83f03785d0.yaml b/releasenotes/notes/add-nova-ironic-vnc-console-5f403a83f03785d0.yaml new file mode 100644 index 0000000000..26e9871973 --- /dev/null +++ b/releasenotes/notes/add-nova-ironic-vnc-console-5f403a83f03785d0.yaml @@ -0,0 +1,12 @@ +--- +features: + - | + Adds opt-in Nova remote console support for bare metal instances. The Nova + role can deploy ``nova-compute-ironic`` with the Ironic VNC state timeout, + select digest-pinned noVNC assets, and allow ``nova-novncproxy`` to reach + dynamic Ironic console provider Pods. +security: + - | + Enables the dynamic console provider NetworkPolicy by default whenever + Ironic graphical consoles are enabled. Both Ironic and Nova noVNC proxies + are admitted to the RFB backend port. diff --git a/roles/ironic/README.md b/roles/ironic/README.md index b1e89da4f3..be2c29d1fd 100644 --- a/roles/ironic/README.md +++ b/roles/ironic/README.md @@ -24,3 +24,8 @@ Set `atmosphere_image_overrides.ironic_novncproxy_assets` to a digest-pinned image when different browser assets are required. The replacement image must provide `/usr/share/novnc`; the chart copies these files without rewriting JavaScript. + +The console NetworkPolicy is enabled with graphical consoles by default and +allows RFB connections from both `ironic-novncproxy` and `nova-novncproxy` in +the OpenStack namespace. Set `ironic_vnc_network_policy_enabled: false` only +when policy enforcement is intentionally managed outside this role. diff --git a/roles/ironic/defaults/main.yml b/roles/ironic/defaults/main.yml index 8122214e4f..4f46a41890 100644 --- a/roles/ironic/defaults/main.yml +++ b/roles/ironic/defaults/main.yml @@ -35,6 +35,7 @@ ironic_vnc_public_url: "https://{{ openstack_helm_endpoints_ironic_novnc_host }} ironic_vnc_ingress_class_name: "{{ atmosphere_ingress_class_name }}" ironic_vnc_ingress_annotations: {} ironic_vnc_kubernetes_pod_timeout: 120 +ironic_vnc_network_policy_enabled: true # Optional driver/interface overrides. Keep hardware types unset to use # Ironic's defaults while allowing out-of-tree drivers to register additions. diff --git a/roles/ironic/tests/baremetal_console_test.yaml b/roles/ironic/tests/baremetal_console_test.yaml index d3c86c5337..6a2e8ad6b4 100644 --- a/roles/ironic/tests/baremetal_console_test.yaml +++ b/roles/ironic/tests/baremetal_console_test.yaml @@ -137,3 +137,13 @@ tests: equal: path: spec.ingress[0].from[0].podSelector.matchLabels.component value: novncproxy + - template: templates/network-policy-console.yaml + documentIndex: 0 + equal: + path: spec.ingress[0].from[1].podSelector.matchLabels.application + value: nova + - template: templates/network-policy-console.yaml + documentIndex: 0 + equal: + path: spec.ingress[0].from[1].podSelector.matchLabels.component + value: novnc-proxy diff --git a/roles/ironic/vars/main.yml b/roles/ironic/vars/main.yml index cfa5f497fa..b0c02a39be 100644 --- a/roles/ironic/vars/main.yml +++ b/roles/ironic/vars/main.yml @@ -116,6 +116,11 @@ _ironic_helm_values: conductor: 3 manifests: deployment_novncproxy: "{{ ironic_vnc_enabled | bool }}" + network_policy_console: >- + {{ + (ironic_vnc_enabled | bool) + and (ironic_vnc_network_policy_enabled | bool) + }} ingress_api: false service_ingress_api: false # NOTE(mnaser): We're managing all the networks via Ansible. diff --git a/roles/nova/README.md b/roles/nova/README.md index 5e9eee1b2d..ad5617dbf4 100644 --- a/roles/nova/README.md +++ b/roles/nova/README.md @@ -8,3 +8,21 @@ accepts a Go duration string such as `10m0s` and defaults to `5m0s`. ```yaml nova_helm_timeout: 10m0s ``` + +## Bare metal graphical consoles + +Nova can expose bare metal graphical consoles through its standard remote +console API and `nova-novncproxy`. Enable the Ironic compute service and its +VNC path with: + +```yaml +nova_ironic_vnc_enabled: true +``` + +The Nova image used by `nova-compute-ironic` must implement the Ironic +driver's VNC console methods. The role configures the Ironic console-state +timeout and retains the unauthenticated RFB backend scheme required between +`nova-novncproxy` and the ephemeral console provider. + +Set `nova_novnc_assets_image` to a digest-pinned image when different browser +assets are required. The image must provide `/usr/share/novnc`. diff --git a/roles/nova/defaults/main.yml b/roles/nova/defaults/main.yml index 1175f338eb..a4b6bc6ba8 100644 --- a/roles/nova/defaults/main.yml +++ b/roles/nova/defaults/main.yml @@ -25,6 +25,14 @@ nova_helm_values: {} # nova_helm_timeout: 10m0s nova_helm_timeout: 5m0s +# Enable Nova's standard remote console flow for bare metal instances. The +# nova-compute-ironic image must include Ironic VNC console support. +nova_ironic_vnc_enabled: false +nova_ironic_vnc_console_state_timeout: 60 + +# Optional digest-pinned noVNC browser assets shared by the Nova proxy. +nova_novnc_assets_image: null + # Private SSH key used for cold & live migration nova_ssh_key: "{{ undef(hint='You must specifiy an SSH key for Nova.') }}" diff --git a/roles/nova/tasks/main.yml b/roles/nova/tasks/main.yml index e3e7374874..4c43587f4d 100644 --- a/roles/nova/tasks/main.yml +++ b/roles/nova/tasks/main.yml @@ -44,6 +44,31 @@ ansible.builtin.set_fact: _nova_helm_values: "{{ _nova_helm_values | combine(_atmosphere_storage_config | vexxhost.atmosphere.storage_to_nova_helm_values, recursive=True) }}" +- name: Validate Nova bare metal VNC configuration + when: nova_ironic_vnc_enabled | bool + vars: + _nova_effective_helm_values: >- + {{ + _nova_helm_values + | combine(nova_helm_values, recursive=True) + }} + ansible.builtin.assert: + that: + - >- + 'none' in + _nova_effective_helm_values.conf.nova.vnc.auth_schemes.split(',') + - >- + _nova_effective_helm_values.manifests.statefulset_compute_ironic + | bool + - nova_ironic_vnc_console_state_timeout | int > 0 + - >- + not nova_novnc_assets_image + or '@sha256:' in nova_novnc_assets_image + fail_msg: >- + Nova bare metal VNC requires nova-compute-ironic, the none VNC + authentication scheme, and a positive console-state timeout. A custom + nova_novnc_assets_image must be digest-pinned. + - name: Deploy Helm chart run_once: true kubernetes.core.helm: diff --git a/roles/nova/vars/main.yml b/roles/nova/vars/main.yml index d8a4f4ee3c..a0ea1a452f 100644 --- a/roles/nova/vars/main.yml +++ b/roles/nova/vars/main.yml @@ -12,6 +12,21 @@ # License for the specific language governing permissions and limitations # under the License. +_nova_default_image_tags: >- + {{ + atmosphere_images + | vexxhost.atmosphere.openstack_helm_image_tags('nova') + }} + +_nova_vnc_image_tags: >- + {{ + { + 'nova_novncproxy_assets': nova_novnc_assets_image + } + if nova_novnc_assets_image + else {} + }} + _nova_helm_values: endpoints: "{{ openstack_helm_endpoints }}" labels: @@ -20,7 +35,11 @@ _nova_helm_values: node_selector_key: openstack-control-plane node_selector_value: enabled images: - tags: "{{ atmosphere_images | vexxhost.atmosphere.openstack_helm_image_tags('nova') }}" + tags: >- + {{ + _nova_default_image_tags + | combine(_nova_vnc_image_tags) + }} network: backend: - "{{ atmosphere_network_backend | default('openvswitch') }}" @@ -120,6 +139,8 @@ _nova_helm_values: nova_ironic: DEFAULT: force_config_drive: true + ironic: + vnc_console_state_timeout: "{{ nova_ironic_vnc_console_state_timeout }}" nova_api_uwsgi: uwsgi: chunked-input-limit: "4096000" @@ -163,8 +184,7 @@ _nova_helm_values: service_ingress_placement: false service_placement: false service_ingress_spiceproxy: false - # NOTE(mnaser): Enable this once we've got Ironic deployed. - statefulset_compute_ironic: false + statefulset_compute_ironic: "{{ nova_ironic_vnc_enabled | bool }}" _nova_novnc_ingress_annotations: nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"