---
title: Deploying hosted control planes on OpenShift Virtualization in a disconnected environment
---

# Deploying hosted control planes on OpenShift Virtualization in a disconnected environment {#hcp-deploy-dc-virt}

When you deploy hosted control planes in a disconnected environment, some of the steps differ depending on whether you use bare metal or OpenShift Virtualization.

To get started, you must meet the following requirements:

- You have a disconnected OpenShift Container Platform environment serving as your management cluster.
- You have an internal registry to mirror images on. For more information, see "About disconnected installation mirroring".

> [!NOTE]
> A known limitation exists for hosted clusters on an OpenShift Container Platform management cluster that is version 4.21 or later. To avoid issues, you must mirror the 4.20.10 release payload from the `quay.io/openshift-release-dev/ocp-release:4.20.10-multi` image to the target mirror registry. This temporary limitation is expected to be resolved in a later release.

**Additional resources**
{._additional-resources}

- [About disconnected installation mirroring](/openshift-docs-markdown/disconnected/index#installing-mirroring-disconnected-about)

## Configuring image mirroring for hosted control planes in a disconnected environment {#hcp-dc-image-mirror_hcp-deploy-dc-virt}

Image mirroring is the process of fetching images from external registries, such as `registry.redhat.com` or `quay.io`, and storing them in your private registry.

In the following procedure, the `oc-mirror` tool is used, which is a binary that uses the `ImageSetConfiguration` object. In the file, you can specify the following information:

- The OpenShift Container Platform versions to mirror. The versions are in `quay.io`.
- The additional Operators to mirror. Select packages individually.
- The extra images that you want to add to the repository.

**Prerequisites**

- Ensure that the registry server is running before you start the mirroring process.

**Procedure**

1. Ensure that your `${HOME}/.docker/config.json` file is updated with the registries that you are going to mirror from and with the private registry that you plan to push the images to.
2. By using the following example, create an `ImageSetConfiguration` object to use for mirroring. Replace values as needed to match your environment:

   ```yaml
   apiVersion: mirror.openshift.io/v2alpha1
   kind: ImageSetConfiguration
   mirror:
     platform:
       channels:
       - name: candidate-4.22
         minVersion: <4.x.y-build>
         maxVersion: <4.x.y-build>
         type: ocp
       kubeVirtContainer: true
       graph: true
     operators:
     - catalog: registry.redhat.io/redhat/redhat-operator-index:v4.22
       packages:
       - name: lvms-operator
       - name: local-storage-operator
       - name: odf-csi-addons-operator
       - name: odf-operator
       - name: mcg-operator
       - name: ocs-operator
       - name: metallb-operator
       - name: kubevirt-hyperconverged
   ```

   - `mirror.platform.channels.minVersion` specifies the supported OpenShift Container Platform version you want to use.
   - `mirror.platform.channels.maxVersion` specifies the supported (product-title) version you want to use.
   - `kubeVirtContainer` specifies whether you want to also mirror to the container disk image for the Red Hat Enterprise Linux CoreOS (RHCOS) boot image for the KubeVirt provider. This flag is optional. It is available with oc-mirror v2 only.
   - `mirror.operators.packages.name: kubevirt-hyperconverged` must be included for deployments that use the KubeVirt provider.
3. Start the mirroring process by entering the following command:

   ```terminal
   $ oc-mirror --v2 --config imagesetconfig.yaml \
     --workspace file://mirror-file docker://<registry>
   ```

   After the mirroring process is finished, you have a new folder named `mirror-file`, which contains the `ImageDigestMirrorSet` (IDMS), `ImageTagMirrorSet` (ITMS), and the catalog sources to apply on the hosted cluster.
4. Mirror the nightly or CI versions of OpenShift Container Platform by configuring the `imagesetconfig.yaml` file as follows:

   ```yaml
   apiVersion: mirror.openshift.io/v2alpha1
   kind: ImageSetConfiguration
   mirror:
     platform:
       graph: true
       release: registry.ci.openshift.org/ocp/release:<4.x.y-build>
       kubeVirtContainer: true
   # ...
   ```

   - `mirror.platform.release` specifies the supported OpenShift Container Platform version you want to use.
   - `mirror.platform.kubeVirtContainer` specifies that you want to also mirror the container disk image for the Red Hat Enterprise Linux CoreOS (RHCOS) boot image for the KubeVirt provider. This flag is available with oc-mirror v2 only.
5. If you have a partially disconnected environment, mirror the images from the image set configuration to a registry by entering the following command:

   ```terminal
   $ oc mirror -c imagesetconfig.yaml \
     --workspace file://<file_path> docker://<mirror_registry_url> --v2
   ```

   For more information, see "Mirroring an image set in a partially disconnected environment".
6. If you have a fully disconnected environment, complete the following steps:

   1. Mirror the images from the specified image set configuration to the disk by entering the following command:

      ```terminal
      $ oc mirror -c imagesetconfig.yaml file://<file_path> --v2
      ```

      For more information, see "Mirroring an image set in a fully disconnected environment".
   2. Process the image set file on the disk and mirror the contents to a target mirror registry by entering the following command:

      ```terminal
      $ oc mirror -c imagesetconfig.yaml \
        --from file://<file_path> docker://<mirror_registry_url> --v2
      ```
7. Mirror the latest multicluster engine Operator images by following the steps in "Install on disconnected networks".

**Additional resources**
{._additional-resources}

- [Mirroring an image set in a partially disconnected environment](/openshift-docs-markdown/disconnected/about-installing-oc-mirror-v2#oc-mirror-workflows-partially-disconnected-v2_about-installing-oc-mirror-v2)
- [Mirroring an image set in a fully disconnected environment](/openshift-docs-markdown/disconnected/about-installing-oc-mirror-v2#oc-mirror-workflows-fully-disconnected-v2_about-installing-oc-mirror-v2)
- [Install on disconnected networks](https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_management_for_kubernetes/2.16/html/clusters/cluster_mce_overview#install-on-disconnected-networks)

## Applying objects in the management cluster {#hcp-dc-apply-objects_hcp-deploy-dc-virt}

After the mirroring process is complete, you must apply two objects required for mirroring in the management cluster.

You apply the following objects:

- `ImageContentSourcePolicy` (ICSP) or `ImageDigestMirrorSet` (IDMS)
- Catalog sources

When you use the `oc-mirror` tool, the output artifacts are in a folder named `oc-mirror-workspace/results-XXXXXX/`.

The `oc mirror` mirroring file initiates a `MachineConfig` change that does not restart your nodes but restarts the kubelet on each of them. After the nodes are marked as `READY`, you need to apply the newly generated catalog sources.

The catalog sources initiate actions in the `openshift-marketplace` Operator, such as downloading the catalog image and processing it to retrieve all the `PackageManifests` that are included in that image.

**Procedure**

1. To check the new sources, run the following command by using the new `CatalogSource` as a source:

   ```terminal
   $ oc get packagemanifest
   ```
2. To apply the artifacts, complete the following steps:

   1. Create the ICSP or IDMS artifacts by entering the following command:

      ```terminal
      $ oc apply -f oc-mirror-workspace/results-XXXXXX/imageContentSourcePolicy.yaml
      ```
   2. Wait for the nodes to become ready, and then enter the following command:

      ```terminal
      $ oc apply -f catalogSource-XXXXXXXX-index.yaml
      ```
3. Mirror the OLM catalogs and configure the hosted cluster to point to the mirror.

   When you use the `management` (default) OLMCatalogPlacement mode, the image stream that is used for OLM catalogs is not automatically amended with override information from the ICSP on the management cluster.

   1. If the OLM catalogs are properly mirrored to an internal registry by using the original name and tag, add the `hypershift.openshift.io/olm-catalogs-is-registry-overrides` annotation to the `HostedCluster` resource. The format is `"sr1=dr1,sr2=dr2"`, where the source registry string is a key and the destination registry is a value.
   2. To bypass the OLM catalog image stream mechanism, use the following four annotations on the `HostedCluster` resource to directly specify the addresses of the four images to use for OLM Operator catalogs:

      - `hypershift.openshift.io/certified-operators-catalog-image`
      - `hypershift.openshift.io/community-operators-catalog-image`
      - `hypershift.openshift.io/redhat-marketplace-catalog-image`
      - `hypershift.openshift.io/redhat-operators-catalog-image`

        In this case, the image stream is not created, and you must update the value of the annotations when the internal mirror is refreshed to pull in Operator updates.

**Next steps**

Deploy the multicluster engine Operator by completing the steps in "Deploying multicluster engine Operator for a disconnected installation of hosted control planes".

**Additional resources**
{._additional-resources}

- [Mirroring images for a disconnected installation by using the oc-mirror plugin v2](/openshift-docs-markdown/disconnected/about-installing-oc-mirror-v2#about-installing-oc-mirror-v2)

## Deploying multicluster engine Operator for a disconnected installation of hosted control planes {#hcp-dc-mce-virt_hcp-deploy-dc-virt}

The multicluster engine for Kubernetes Operator is crucial in deploying clusters across providers. Ensure that you have multicluster engine Operator installed and configured for your deployment.

If you do not have multicluster engine Operator installed, review the following documentation to understand the prerequisites and steps to install it:

- "About cluster lifecycle with multicluster engine operator"
- "Installing and upgrading multicluster engine operator"

**Additional resources**
{._additional-resources}

- [About cluster lifecycle with multicluster engine operator](https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_management_for_kubernetes/latest/html/clusters/cluster_mce_overview#mce-intro)
- [Installing and upgrading multicluster engine operator](https://docs.redhat.com/en/documentation/red_hat_advanced_cluster_management_for_kubernetes/latest/html/clusters/cluster_mce_overview#mce-install-intro)

## TLS certificates for a disconnected installation of hosted control planes {#hcp-dc-tls-virt_hcp-deploy-dc-virt}

To ensure proper function in a disconnected deployment, you need to configure the registry CA certificates in the management cluster and the compute nodes for the hosted cluster.

### Adding the registry CA to the management cluster {#hcp-dc-tls-mgmt_hcp-deploy-dc-virt}

To ensure proper function in a disconnected deployment, you need to configure the registry CA certificates in the management cluster.

To add the registry CA to the management cluster, complete the following steps.

**Procedure**

1. Create a config map that resembles the following example:

   ```yaml
   apiVersion: v1
   kind: ConfigMap
   metadata:
     name: <config_map_name>
     namespace: <config_map_namespace>
   data:
     <registry_name>..<port>: |
       -----BEGIN CERTIFICATE-----
       -----END CERTIFICATE-----
     <registry_name>..<port>: |
       -----BEGIN CERTIFICATE-----
       -----END CERTIFICATE-----
     <registry_name>..<port>: |
       -----BEGIN CERTIFICATE-----
       -----END CERTIFICATE-----
   ```

   - `metadata.name` specifies the name of the config map.
   - `metadata.namespace` specifies the namespace for the config map.
   - `data` specifies the registry names and the registry certificate content. Replace `<port>` with the port where the registry server is running; for example, `5000`. Ensure that the data in the config map is defined by using `|` only instead of other methods, such as `| -`. If you use other methods, issues can occur when the pod reads the certificates.
2. Patch the cluster-wide object, `image.config.openshift.io` to include the following specification:

   ```yaml
   spec:
     additionalTrustedCA:
       name: registry-config
   ```

   As a result of this patch, the control plane nodes can retrieve images from the private registry and the HyperShift Operator can extract the OpenShift Container Platform payload for hosted cluster deployments.

   The process to patch the object might take several minutes to be completed.

### Adding the registry CA to the compute nodes for the hosted cluster {#hcp-dc-tls-hosted_hcp-deploy-dc-virt}

To ensure that the data plane compute nodes in the hosted cluster can retrieve images from the private registry, you must add the registry certificate authority (CA) to the compute nodes.

**Procedure**

1. In the `hc.spec.additionalTrustBundle` file, add the following specification:

   ```yaml
   spec:
     additionalTrustBundle:
       name: user-ca-bundle
   ```

   The `user-ca-bundle` entry is a config map that you create in the next step.
2. In the same namespace where the `HostedCluster` object is created, create the `user-ca-bundle` config map. The config map resembles the following example:

   ```yaml
   apiVersion: v1
   data:
     ca-bundle.crt: |
       // Registry1 CA
       -----BEGIN CERTIFICATE-----
       -----END CERTIFICATE-----

       // Registry2 CA
       -----BEGIN CERTIFICATE-----
       -----END CERTIFICATE-----

       // Registry3 CA
       -----BEGIN CERTIFICATE-----
       -----END CERTIFICATE-----

   kind: ConfigMap
   metadata:
     name: user-ca-bundle
     namespace: <hosted_cluster_namespace>
   ```

   Specify the namespace where the `HostedCluster` object is created.

## Creating a hosted cluster on OpenShift Virtualization in a disconnected environment {#hcp-dc-virt-hosted_hcp-deploy-dc-virt}

As part of the process to deploy hosted control planes on OpenShift Virtualization in a disconnected environment, you need to create a hosted cluster. A hosted cluster is an OpenShift Container Platform cluster with its control plane and API endpoint hosted on a management cluster. The hosted cluster includes the control plane and its corresponding data plane.

### Prerequisites to deploy hosted control planes on OpenShift Virtualization {#hcp-virt-prereqs_hcp-deploy-dc-virt}

To create an OpenShift Container Platform cluster on OpenShift Virtualization, you must meet several prerequisites.

- You have administrator access to an OpenShift Container Platform cluster, version 4.14 or later, specified in the `KUBECONFIG` environment variable.
- The OpenShift Container Platform management cluster must have wildcard DNS routes enabled, as shown in the following command:

  ```terminal
  $ oc patch ingresscontroller -n openshift-ingress-operator default \
    --type=json \
    -p '[{ "op": "add", "path": "/spec/routeAdmission", "value": {wildcardPolicy: "WildcardsAllowed"}}]'
  ```
- The OpenShift Container Platform management cluster has OpenShift Virtualization, version 4.14 or later, installed on it. For more information, see "Installing OpenShift Virtualization using the web console".
- The OpenShift Container Platform management cluster is on-premise bare metal.
- The OpenShift Container Platform management cluster must be configured with `OVNKubernetes` as the default pod network Container Network Interface (CNI). Live migration is supported for nodes only if the CNI is OVN-Kubernetes.
- The OpenShift Container Platform management cluster has a default storage class. For more information, see "Postinstallation storage configuration". The following example shows how to set a default storage class:

  ```terminal
  $ oc patch storageclass ocs-storagecluster-ceph-rbd \
    -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
  ```
- When you configure storage for hosted control planes, consider the recommended etcd practices. To ensure that you meet the latency requirements, dedicate a fast storage device to all hosted control plane etcd instances that run on each control-plane node. You can use LVM storage to configure a local storage class for hosted etcd pods. For more information, see "Recommended etcd practices" and "Persistent storage using Logical Volume Manager storage".
- On the OpenShift Container Platform cluster that hosts the OpenShift Virtualization virtual machines, you must use a `ReadWriteMany` (RWX) storage class so that live migration can be enabled.
- You have a valid pull secret file for the `quay.io/openshift-release-dev` repository. For more information, see "Install OpenShift on any x86_64 platform with user-provisioned infrastructure".
- You have installed the hosted control plane command-line interface.
- You have configured a load balancer. For more information, see "Configuring MetalLB".
- For optimal network performance, you are using a network maximum transmission unit (MTU) of 9000 or greater on the OpenShift Container Platform cluster that hosts the KubeVirt virtual machines. If you use a lower MTU setting, network latency and the throughput of the hosted pods are affected. Enable multiqueue on node pools only when the MTU is 9000 or greater.

  > [!IMPORTANT]
  > You cannot change the MTU value for your cluster as a postinstallation task.
- The multicluster engine Operator has at least one managed OpenShift Container Platform cluster. The `local-cluster` is automatically imported. For more information about the `local-cluster`, see "Advanced configuration" in the multicluster engine Operator documentation. You can check the status of your hub cluster by running the following command:

  ```terminal
  $ oc get managedclusters local-cluster
  ```
- Ensure each hosted cluster has a cluster-wide unique name.
- Do not use `clusters` as a hosted cluster name.
- Do not create a hosted cluster in the namespace of a multicluster engine Operator managed cluster.

### Creating a hosted cluster with the KubeVirt platform by using the CLI {#hcp-virt-create-hc-cli_hcp-deploy-dc-virt}

To create a hosted cluster on OpenShift Virtualization, you can use the hosted control plane command-line interface (CLI), `hcp`.

> [!IMPORTANT]
> Avoid storing all hosted cluster information in a shared namespace. If you create a hosted cluster in a shared namespace and then back up and restore the hosted cluster, you might unintentionally change other hosted clusters. Either store hosted cluster information in a separate namespace or set up your hosted cluster to back up and restore resources based on labels.

**Procedure**

1. Create a hosted cluster with the KubeVirt platform by entering the following command:

   ```terminal
   $ hcp create cluster kubevirt \
     --name <hosted_cluster_name> \
     --node-pool-replicas <node_pool_replica_count> \
     --pull-secret <path_to_pull_secret> \
     --memory <value_for_memory> \
     --cores <value_for_cpu> \
     --etcd-storage-class=<etcd_storage_class> \
     --arch <architecture_of_the_nodepool> \
     --release-image <ocp_release_image_for_the_cluster> \
     --image-content-sources <path_to_image_content_sources_file> \
     --disable-cluster-capabilities=<capability> \
     --enable-cluster-capabilities=<capability> \
     --additional-trust-bundle <path_to_ca_bundle_file>
   ```

   - `--name` defines the name of your hosted cluster, for example, `my-hosted-cluster`.
   - `--node-pool-replicas` defines the node pool replica count, for example, `3`. You must specify the replica count as `0` or greater to create the same number of replicas. Otherwise, no node pools are created.
   - `--pull-secret` defines the path to your pull secret, for example, `/user/name/pullsecret`.
   - `--memory` defines a value for memory, for example, `6Gi`.
   - `--cores` defines a value for CPU, for example, `2`.
   - `--etcd-storage-class` defines the etcd storage class name, for example, `lvm-storageclass`.
   - `--arch` defines the architecture of the node pool, for example, `s390x`. The default is `amd64`.
   - `--release-image` defines the OpenShift Container Platform release image for the cluster, for example, `quay.io/openshift-release-dev/ocp-release:4.20.14-multi`. You can use the `--release-image` flag to set up the hosted cluster with a specific OpenShift Container Platform release.
   - `--image-content-sources` specifies the path to a file with image content sources.
   - `--disable-cluster-capabilities` specifies that you want to disable optional capabilities. This flag is optional. For more information, see "Capabilities for hosted clusters".
   - `--enable-cluster-capabilities` specifies that you want to enable optional capabilities in the hosted cluster. This flag is optional. For more information, see "Capabilities for hosted clusters".
   - `--additional-trust-bundle` specifies the path to a file with user CA bundle.

   A default node pool is created for the cluster with a specific number of virtual machine worker replicas according to the `--node-pool-replicas` flag.
2. After a few moments, verify that the hosted control plane pods are running by entering the following command:

   ```terminal
   $ oc -n clusters-<hosted-cluster-name> get pods
   ```

   ```terminal {title="Example output"}
   NAME                                                  READY   STATUS    RESTARTS   AGE
   capi-provider-5cc7b74f47-n5gkr                        1/1     Running   0          3m
   catalog-operator-5f799567b7-fd6jw                     2/2     Running   0          69s
   certified-operators-catalog-784b9899f9-mrp6p          1/1     Running   0          66s
   cluster-api-6bbc867966-l4dwl                          1/1     Running   0          66s
   .
   .
   .
   redhat-operators-catalog-9d5fd4d44-z8qqk              1/1     Running   0          66s
   ```

   A hosted cluster that has worker nodes that are backed by KubeVirt virtual machines typically takes 10-15 minutes to be fully provisioned.

**Verification**

- To check the status of the hosted cluster, see the corresponding `HostedCluster` resource by entering the following command:

  ```terminal
  $ oc get --namespace clusters hostedclusters
  ```

  See the following example output, which illustrates a fully provisioned `HostedCluster` object:

  ```
  NAMESPACE   NAME                VERSION     KUBECONFIG                 PROGRESS    AVAILABLE   PROGRESSING   MESSAGE
  clusters    my-hosted-cluster   <4.x.0>     example-admin-kubeconfig   Completed   True        False         The hosted control plane is available
  ```

  Replace `<4.x.0>` with the supported OpenShift Container Platform version that you want to use.

**Additional resources**
{._additional-resources}

- [Labeling management cluster nodes](/openshift-docs-markdown/hosted_control_planes/hcp-prepare/hcp-distribute-workloads#hcp-labels-taints_hcp-distribute-workloads)

## Configuring the default ingress and DNS for hosted control planes on OpenShift Virtualization {#hcp-virt-ingress-dns_hcp-deploy-dc-virt}

Every OpenShift Container Platform cluster includes a default application Ingress Controller, which must have an wildcard DNS record associated with it.

By default, hosted clusters that are created by using OpenShift Virtualization automatically become a subdomain of the OpenShift Container Platform cluster that the virtual machines run on.

For example, your OpenShift Container Platform cluster might have the following default ingress DNS entry:

```terminal
*.apps.mgmt-cluster.example.com
```

As a result, a hosted cluster that is named `guest` and that runs on that underlying OpenShift Container Platform cluster has the following default ingress:

```terminal
*.apps.guest.apps.mgmt-cluster.example.com
```

**Procedure**

- For the default ingress DNS to work properly, the cluster that hosts the virtual machines must allow wildcard DNS routes. You can configure this behavior by entering the following command:

  ```terminal
  $ oc patch ingresscontroller -n openshift-ingress-operator default \
    --type=json \
    -p '[{ "op": "add", "path": "/spec/routeAdmission", "value": {wildcardPolicy: "WildcardsAllowed"}}]'
  ```

  > [!NOTE]
  > When you use the default hosted cluster ingress, connectivity is limited to HTTPS traffic over port 443. Plain HTTP traffic over port 80 is rejected. This limitation applies to only the default ingress behavior.

## Customize ingress and DNS behavior {#hcp-dc-virt-ingress-dns-custom_hcp-deploy-dc-virt}

If you do not want to use the default ingress and DNS behavior, you can configure a hosted cluster on OpenShift Virtualization with a unique base domain at creation time.

This option requires manual configuration steps during creation and involves three main steps: cluster creation, load balancer creation, and wildcard DNS configuration.

## Creating a hosted cluster that specifies the base domain {#hcp-virt-hc-base-domain_hcp-deploy-dc-virt}

If you do not want to use the default ingress and DNS behavior, you can configure a KubeVirt hosted cluster with a unique base domain at creation time.

**Procedure**

1. Create the cluster by entering the following command:

   ```terminal
   $ hcp create cluster kubevirt \
     --name <hosted_cluster_name> \
     --node-pool-replicas <worker_count> \
     --pull-secret <path_to_pull_secret> \
     --memory <value_for_memory> \
     --cores <value_for_cpu> \
     --base-domain <basedomain> \
     --arch <architecture_of_the_nodepool> \
     --release-image <ocp_release_image_for_the_cluster> \
     --image-content-sources <path_to_image_content_sources_file> \
     --additional-trust-bundle <path_to_ca_bundle_file>
   ```

   - `--name` specifies the name of your hosted cluster.
   - `--node-pool-replicas` specifies the worker count, for example, `2`.
   - `--pull-secret` specifies the path to your pull secret, for example, `/user/name/pullsecret`.
   - `--memory` specifies a value for memory, for example, `6Gi`.
   - `--cores` specifies a value for CPU, for example, `2`.
   - `--base-domain` specifies the base domain, for example, `hypershift.lab`.
   - `--arch` specifies the architecture of the node pool, for example, `s390x`. The default is `amd64`.
   - `--release-image` specifies the ocp release image for the cluster, for example, `quay.io/openshift-release-dev/ocp-release:4.20.14-multi`.
   - `--image-content-sources` specifies the path to a file with image content sources.
   - `--additional-trust-bundle` specifies the path to a file with user CA bundle.

     As a result, the hosted cluster has an ingress wildcard that is configured for the cluster name and the base domain, for example, `.apps.example.hypershift.lab`. The hosted cluster remains in `Partial` status because after you create a hosted cluster with unique base domain, you must configure the required DNS records and load balancer.

**Verification**

1. View the status of your hosted cluster by entering the following command:

   ```terminal
   $ oc get --namespace clusters hostedclusters
   ```

   ```terminal {title="Example output"}
   NAME            VERSION   KUBECONFIG                       PROGRESS   AVAILABLE   PROGRESSING   MESSAGE
   example                   example-admin-kubeconfig         Partial    True        False         The hosted control plane is available
   ```
2. Access the cluster by entering the following commands:

   ```terminal
   $ hcp create kubeconfig --name <hosted_cluster_name> \
     > <hosted_cluster_name>-kubeconfig
   ```

   ```terminal
   $ oc --kubeconfig <hosted_cluster_name>-kubeconfig get co
   ```

   ```terminal {title="Example output"}
   NAME                                       VERSION   AVAILABLE   PROGRESSING   DEGRADED   SINCE   MESSAGE
   console                                    <4.x.0>     False       False         False      30m     RouteHealthAvailable: failed to GET route (https://console-openshift-console.apps.example.hypershift.lab): Get "https://console-openshift-console.apps.example.hypershift.lab": dial tcp: lookup console-openshift-console.apps.example.hypershift.lab on 172.31.0.10:53: no such host
   ingress                                    <4.x.0>     True        False         True       28m     The "default" ingress controller reports Degraded=True: DegradedConditions: One or more other status conditions indicate a degraded state: CanaryChecksSucceeding=False (CanaryChecksRepetitiveFailures: Canary route checks for the default ingress controller are failing)
   ```

   Replace `<4.x.0>` with the supported OpenShift Container Platform version that you want to use.
3. To fix any errors in the output, complete the steps in "Setting up the load balancer" and "Setting up a wildcard DNS".

   > [!NOTE]
   > If your hosted cluster is on bare metal, you might need MetalLB to set up load balancer services. For more information, see "Configuring MetalLB".

## Setting up the load balancer {#hcp-virt-load-balancer_hcp-deploy-dc-virt}

Set up the load balancer service that routes ingress traffic to the KubeVirt VMs and assigns a wildcard DNS entry to the load balancer IP address.

**Procedure**

1. A `NodePort` service that exposes the hosted cluster ingress already exists. You can export the node ports and create the load balancer service that targets those ports.

   1. Get the HTTP node port by entering the following command:

      ```terminal
      $ oc --kubeconfig <hosted_cluster_name>-kubeconfig get services \
        -n openshift-ingress router-nodeport-default \
        -o jsonpath='{.spec.ports[?(@.name=="http")].nodePort}'
      ```

      Note the HTTP node port value to use in the next step.
   2. Get the HTTPS node port by entering the following command:

      ```terminal
      $ oc --kubeconfig <hosted_cluster_name>-kubeconfig get services \
        -n openshift-ingress router-nodeport-default \
        -o jsonpath='{.spec.ports[?(@.name=="https")].nodePort}'
      ```

      Note the HTTPS node port value to use in the next step.
2. Enter the following information in a YAML file:

   ```yaml
   apiVersion: v1
   kind: Service
   metadata:
     labels:
       app: <hosted_cluster_name>
     name: <hosted_cluster_name>-apps
     namespace: clusters-<hosted_cluster_name>
   spec:
     ports:
     - name: https-443
       port: 443
       protocol: TCP
       targetPort: <https_node_port>
     - name: http-80
       port: 80
       protocol: TCP
       targetPort: <http_node_port>
     selector:
       kubevirt.io: virt-launcher
     type: LoadBalancer
   ```

   where:

   - `<https_node_port>` specifies the HTTPS node port value that you noted in the previous step.
   - `<http_node_port>` specifies the HTTP node port value that you noted in the previous step.
3. Create the load balancer service by running the following command:

   ```terminal
   $ oc create -f <file_name>.yaml
   ```

## Setting up a wildcard DNS {#hcp-virt-wildcard-dns_hcp-deploy-dc-virt}

If you are customizing the ingress and DNS for your hosted cluster, you need to set up a wildcard DNS record or CNAME that references the external IP of the load balancer service.

**Procedure**

1. Get the external IP address by entering the following command:

   ```terminal
   $ oc -n clusters-<hosted_cluster_name> get service <hosted-cluster-name>-apps \
     -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
   ```

   ```terminal {title="Example output"}
   192.168.20.30
   ```
2. Configure a wildcard DNS entry that references the external IP address. View the following example DNS entry:

   ```terminal
   *.apps.<hosted_cluster_name\>.<base_domain\>.
   ```

   The DNS entry must be able to route inside and outside of the cluster.

   ```terminal {title="DNS resolutions example"}
   dig +short test.apps.example.hypershift.lab

   192.168.20.30
   ```

**Verification**

- Check that hosted cluster status has moved from `Partial` to `Completed` by entering the following command:

  ```terminal
  $ oc get --namespace clusters hostedclusters
  ```

  ```terminal {title="Example output"}
  NAME            VERSION   KUBECONFIG                       PROGRESS    AVAILABLE   PROGRESSING   MESSAGE
  example         <4.x.0>     example-admin-kubeconfig         Completed   True        False         The hosted control plane is available
  ```

  Replace `<4.x.0>` with the supported OpenShift Container Platform version that you want to use.

## Deployment finalization {#hcp-dc-finish_hcp-deploy-dc-virt}

You can monitor the deployment of a hosted cluster from two perspectives: the control plane and the data plane.

### Monitoring the control plane {#hcp-monitor-cp_hcp-deploy-dc-virt}

While the deployment proceeds, you can monitor the control plane.

You can gather information about the following artifacts:

- The HyperShift Operator
- The `HostedControlPlane` pod
- The bare-metal hosts
- The agents
- The `InfraEnv` resource
- The `HostedCluster` and `NodePool` resources

**Procedure**

1. Enter the following command to export the `kubeconfig` file for the deployment:

   ```terminal
   $ export KUBECONFIG=/root/.kcli/clusters/hub-ipv4/auth/kubeconfig
   ```
2. Enter the following command to monitor the deployment:

   ```terminal
   $ watch "oc get pod -n hypershift;echo;echo;\
     oc get pod -n clusters-hosted-ipv4;echo;echo;\
     oc get bmh -A;echo;echo;\
     oc get agent -A;echo;echo;\
     oc get infraenv -A;echo;echo;\
     oc get hostedcluster -A;echo;echo;\
     oc get nodepool -A;echo;echo;"
   ```

### Monitoring the data plane {#hcp-monitor-dp_hcp-deploy-dc-virt}

While the deployment proceeds, you can monitor the data plane.

You can gather information about the following artifacts:

- The cluster version
- The nodes, specifically, about whether the nodes joined the cluster
- The cluster Operators

**Procedure**

1. Enter the following command to get the `kubeconfig` secret:

   ```
   $ oc get secret -n clusters-hosted-ipv4 admin-kubeconfig \
     -o jsonpath='{.data.kubeconfig}' | base64 -d > /root/hc_admin_kubeconfig.yaml
   ```
2. Enter the following command to export the `kubeconfig` file for the deployment:

   ```
   $ export KUBECONFIG=/root/hc_admin_kubeconfig.yaml
   ```
3. Enter the following command to monitor the deployment:

   ```
   $ watch "oc get clusterversion,nodes,co"
   ```
