Convert2RHEL on Oracle Linux with Satellite

Convert2RHEL on Oracle Linux with Satellite

This began as a pet project a while back because I had a customer who was considering migrating from Oracle to RHEL but they were concerned about the amount of effort it would take to spin up new RHEL servers and migrate workloads. They were pleased to hear that it is possible to migrate in place using convert2rhel, and I threw together a quick demo for them which although not without it's hurdles was a lot easier to do "online" than it proved to be leveraging Satellite. Performing a RHEL conversion through the Satellite UI would mean that you could easily do batch migrations at scale, and open the door to doing conversions in disconnected environments.

Although convert2rhel officially supports Oracle Linux, our documentation is somewhat lacking when it comes to preparing Oracle specifically for conversion. Unlike the other officially supported distros (CentOS, Rocky and Alma) Oracle presents a few additional challenges, namely:

  • Oracle Linux by default uses their UEK (Unbreakable Enterprise Kernel) and must be changed out of the RHCK (Red Hat Compatible Kernel)
  • Oracle deprecated and ultimately removed subscription-manager from their repos starting with Oracle Linux 7
  • Oracle Linux contains some incompatible Intel Wifi firmware packages that must be removed

In order to make testing faster, I ended up creating custom VM templates in Openshift with all of the necessary changes applied to the base OS image, however this quickly became a series of chicken and egg problems where I needed to configure things in Satellite before I could then configure my Oracle Linux image.

After a lot of trial and error, and making (generally incorrect) assumptions about how the convert2rhel workflow should go on Satellite and how things should be configured I was finally successful in performing batch migrations of Oracle Linux to RHEL, using the Satellite UI in a reliably reproducible manner.


Preparing you Satellite server

For the sake of brevity, I'm going to assume you know your way around Satellite and it's basic functions, so I won't get too in the weeds. First, if you haven't already done so, you'll need to sync the Red Hat repos for the version you're working with - in my example I'm going to be working with RHEL 8.10, but the same concepts generally apply to RHEL 9. Then you're going to have to create several new products, content views and activation keys. At a minimum, you'll need to create products for Oracle Linux to include the BaseOS and AppStream repos.

Unlike most other distros, as far as I'm aware, there is only one official repo mirror for Oracle Linux located here:

https://yum.oracle.com/repo/OracleLinux/OL8

Be forewarned - the initial sync of the Oracle repos is going to take a very long time. It took over 8 hours on my Satellite server to pull down all the packages.

For the sake of completeness, I synced all of the Oracle repos, not just baseOS and AppStream

Subscription Manager For Oracle Linux 8

Then you're going to need to create a custom repo containing the subscription-manager package and its dependencies.

mkdir subscription-manager-8
cd subscription-manager-8
dnf download --resolve dnf-plugin-subscription-manager python3-cloud-what subscription-manager-rhsm-certificates python3-subscription-manager-rhsm subscription-manager python3-syspurpose
createrepo .

You will probably need to install createrepo_c first, as it is not generally included with a standard RHEL install.

Subscription Manager For Oracle Linux 9

mkdir subscription-manager-9
cd subscription-manager-9
dnf download --resolve libdnf-plugin-subscription-manager python3-cloud-what subscription-manager-rhsm-certificates python3-subscription-manager-rhsm subscription-manager
createrepo .

Once you have your repo structure created, you can drop it generally onto any web host (hint: you can just create a subdirectory under /var/www/html/pub/ on your Satellite server). I actually host mine in an nginx pod, but generally any web server will work.

Sample nginx.conf

user  nginx;
worker_processes  auto;
error_log  /var/log/nginx/error.log notice;
pid        /run/nginx.pid;
events {
    worker_connections  1024;
}
http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    access_log  /var/log/nginx/access.log  main;
    sendfile        on;
    #tcp_nopush     on;
    keepalive_timeout  65;
    #gzip  on;
    include /etc/nginx/conf.d/*.conf;
    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  _;
        root         /usr/share/nginx/html;
        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;
        location / {
                allow all;
                sendfile on;
                sendfile_max_chunk 1m;
                autoindex on;
                autoindex_exact_size off;
                autoindex_format html;
                autoindex_localtime on;
        }
          error_page 404 /404.html;
            location = /40x.html {
        }
          error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }
}

which is upped with a simple podman-compose.yml

version: '3.8'
services:
  nginx:
    image: nginx:latest
    ports:
      - "8880:80"
    volumes:
      - /pool0/podman/nginx/html:/usr/share/nginx/html
      - /pool0/podman/nginx/nginx.conf:/etc/nginx/nginx.conf

To make things easier on myself, I also added the .repo file in the repo making it easier to pull it down on my Oracle boxes, because although I also mirror this repo to a custom product on my Satellite server, you kinda need subscription-manager (the first 🐓:🥚 problem I encountered on this journey).

# example repo file
[subscription-manager]
name=Subscription Manager packages for Oracle
baseurl=https://zfs.cudanet.org/subscription-manager-8
enabled=1
gpgcheck=0
in case you're wondering why my repo host is called 'zfs', it's because that was initially the only thing I was mirroring and I was too lazy to change the DNS alias 🙃

Convert2RHEL repo

No hackery or creating any custom repos here. You can either add the convert2rhel repo directly to your Oracle boxes, eg;

curl -o /etc/yum.repos.d/convert2rhel.repo https://cdn-public.redhat.com/content/public/repofiles/convert2rhel-for-rhel-9-x86_64.repo

or, like me you can add it to your Satellite server. Just grab the URL from that repo file and create a new product + repo in Satellite


Preparing Oracle Linux for conversion

As I'd previously mentioned, there is a laundry list of tasks you have to perform on your Oracle Linux box before it is ready to be converted. First, we'll install subscription-manager since if this were a disconnected environment, that's where we'd have to begin in order to register the Oracle box to Satellite.

Download the repo file to /etc/yum.repos.d/ and install the package.

dnf -y install subscription-manager

Then you need to register your host. There are generally a few different ways to go about this. The old method was to first install your Satellite CA cert, update the system CA trust, then install the katello client package, eg;

curl -k https://satl01.cudanet.org/pub/katello-server-ca.crt > /etc/pki/ca-trust/source/anchors/katello-server-ca.crt
update-ca-trust
dnf -y install https://satl01.cudanet.org/pub/katello-ca-consumer-latest.noarch.rpm

There's a newer method in recent builds of Satellite that will do all of that in a one-liner under 'Register Host'. It's pretty slick, and can even configure REX (remote execution, aka; install the foreman-proxy service public SSH key for you). If you don't use the new method, make sure to run ssh-keygen -t rsa as root and add the foreman-proxy ssh key manually, eg;

ssh-keygen -t rsa
# follow the prompts, don't set a password
scp -r root@satl01:/var/lib/foreman-proxy/ssh/id_rsa_foreman_proxy.pub ./id_rsa_foreman_proxy.pub
cat ./id_rsa_foreman_proxy.pub >> ~/.ssh/authorized_keys

Then you can register your Oracle host to Satellite (assuming you've created a content view and an activation key for Oracle Linux. If you have not, do so now). We're going to have to create additional content views and activation keys later on, but for the purpose of preparing your Oracle host for convert2rhel, you need a content view with at a minimum, the Oracle BaseOS and AppStream repos, and the convert2rhel repo. Optionally you can include subscription-manager and get rid of the local repo file.

subscription-manager register --org=cudanet --activationkey=ol8_x86_64

You should now see your Oracle server in Satellite under hosts.

As we can see, it already has security errata that needs to be installed, so it's a good idea at this time to update your system.

dnf -y update

I'm lazy and like doing it through the Satellite UI. However, keep in mind that for any of the rest of this to work, you must have REX set up.

You could also do all of the above using Ansible, however that is not within the scope of this article.

Switch from booting UEK to RHCK

This step is pretty straightforward. You just modify your default boot option using grubby

# determine if you're using UEK, which is default for OL
grubby --default-kernel
uname -r
RHCK=$(grubby --info=ALL | grep ^kernel | grep -v uek | head -1 | awk -F '"' '{print $2}')
# set grub to boot RHCK, eg;
grubby --set-default $RHCK
reboot
# verify the system is booted into RHCK
uname -r
Notice it defaulted to the entry that doesn't have 'el8uek' in the name

Remove conflicting packages

The Intel wireless firmware packages that ship with Oracle are incompatible with RHEL and must be removed

dnf -y remove iwl* 

Install Convert2rhel

we're going to install the convert2rhel package, but we aren't quite ready to run it yet.

dnf -y install convert2rhel

At this time, you probably want to unregister the VM from Satellite. We're going to have to create another content view that will be used specifically for convert2rhel

Disable Secure Boot

If you have secure boot enabled, you'll need to disable it during the conversion. You can of course turn it back on after your server has been converted.

Optional: Create a template

Personally, I like easy and reproducible workflows. Depending on your virt platform the way to do this varies. Working with Openshift Virt, I find that the easiest way to create a template is to build it on a KVM host and then upload the disk image to Openshift, eg;

virtctl image-upload dv ol8 --size=64Gi --image-path=ol8-template.qcow2 --insecure

For whatever reason, after you've uploaded the disk image to a DataVolume, you'll also have to manually create a DataSource in order for the kubevirt template to actually see the disk as a boot source, eg;

apiVersion: cdi.kubevirt.io/v1beta1
kind: DataSource
metadata:
  annotations:
    operator-sdk/primary-resource: openshift-cnv/ssp-kubevirt-hyperconverged
    operator-sdk/primary-resource-type: SSP.ssp.kubevirt.io
  labels:
    app.kubernetes.io/component: storage
    app.kubernetes.io/managed-by: cdi-controller
    app.kubernetes.io/part-of: hyperconverged-cluster
    app.kubernetes.io/version: 4.18.2
    cdi.kubevirt.io/dataImportCron: centos-stream9-image-cron
    instancetype.kubevirt.io/default-instancetype: u1.medium
    instancetype.kubevirt.io/default-preference: ol8
    kubevirt.io/dynamic-credentials-support: "true"
  name: ol8
  namespace: virtualization
spec:
  source:
    pvc:
      name: ol8
      namespace: virtualization

Then you can create a custom VM template to use the image, eg;

kind: Template
apiVersion: template.openshift.io/v1
metadata:
  name: ol8-server-small
  namespace: virtualization
  labels:
    app.kubernetes.io/part-of: hyperconverged-cluster
    template.kubevirt.io/architecture: amd64
    flavor.template.kubevirt.io/small: 'true'
    template.kubevirt.io/version: v0.32.2
    app.kubernetes.io/version: 4.18.2
    template.kubevirt.io/type: base
    app.kubernetes.io/component: templating
    app.kubernetes.io/managed-by: ssp-operator
    os.template.kubevirt.io/ol8: 'true'
    template.kubevirt.io/default-os-variant: 'true'
    app.kubernetes.io/name: common-templates
    workload.template.kubevirt.io/server: 'true'
  annotations:
    template.kubevirt.io/provider: Oracle
    template.kubevirt.io/provider-url: 'https://www.oracle.com'
    template.kubevirt.io/containerdisks: |
      quay.io/containerdisks/centos-stream:8
    template.kubevirt.io/version: v1alpha1
    openshift.io/display-name: Oracle Linux 8 VM
    operator-sdk/primary-resource-type: SSP.ssp.kubevirt.io
    name.os.template.kubevirt.io/ol8: Oracle Linux 8 or higher
    defaults.template.kubevirt.io/disk: rootdisk
    template.kubevirt.io/editable: |
      /objects[0].spec.template.spec.domain.cpu.sockets
      /objects[0].spec.template.spec.domain.cpu.cores
      /objects[0].spec.template.spec.domain.cpu.threads
      /objects[0].spec.template.spec.domain.memory.guest
      /objects[0].spec.template.spec.domain.devices.disks
      /objects[0].spec.template.spec.volumes
      /objects[0].spec.template.spec.networks
    template.openshift.io/bindable: 'false'
    openshift.kubevirt.io/pronounceable-suffix-for-name-expression: 'true'
    operator-sdk/primary-resource: openshift-cnv/ssp-kubevirt-hyperconverged
    tags: 'hidden,kubevirt,virtualmachine,linux,centosstream'
    template.kubevirt.io/provider-support-level: Community
    description: Template for Oracle Linux 8 VM or newer. A PVC with the Oracle Linux disk image must be available.
    openshift.io/support-url: 'https://github.com/kubevirt/common-templates/issues'
    iconClass: icon-oracle
    openshift.io/provider-display-name: Oracle
objects:
  - apiVersion: kubevirt.io/v1
    kind: VirtualMachine
    metadata:
      annotations:
        vm.kubevirt.io/validations: |
          [
            {
              "name": "minimal-required-memory",
              "path": "jsonpath::.spec.domain.memory.guest",
              "rule": "integer",
              "message": "This VM requires more memory.",
              "min": 1610612736
            }
          ]
      labels:
        app: '${NAME}'
        kubevirt.io/dynamic-credentials-support: 'true'
        vm.kubevirt.io/template: ol8-server-small
        vm.kubevirt.io/template.revision: '1'
        vm.kubevirt.io/template.version: v0.32.2
      name: '${NAME}'
    spec:
      dataVolumeTemplates:
        - apiVersion: cdi.kubevirt.io/v1beta1
          kind: DataVolume
          metadata:
            name: '${NAME}'
          spec:
            sourceRef:
              kind: DataSource
              name: ol8
              namespace: virtualization
            storage:
              resources:
                requests:
                  storage: 70Gi
      runStrategy: Manual
      template:
        metadata:
          annotations:
            vm.kubevirt.io/flavor: small
            vm.kubevirt.io/os: ol8
            vm.kubevirt.io/workload: server
          labels:
            kubevirt.io/domain: '${NAME}'
            kubevirt.io/size: small
        spec:
          architecture: amd64
          domain:
            cpu:
              cores: 1
              sockets: 1
              threads: 1
            devices:
              disks:
                - disk:
                    bus: virtio
                  name: rootdisk
                - disk:
                    bus: virtio
                  name: cloudinitdisk
              interfaces:
                - bridge: {}
                  model: virtio
                  name: default
              rng: {}
            features:
              smm:
                enabled: true
            firmware:
              bootloader:
                efi: {}
            memory:
              guest: 2Gi
          networks:
            - multus:
                networkName: machine-net
              name: default
          terminationGracePeriodSeconds: 180
          volumes:
            - dataVolume:
                name: '${NAME}'
              name: rootdisk
            - cloudInitNoCloud:
                userData: |-
                  #cloud-config
                  user: <your-user>
                  password: <your-password>
                  chpasswd: { expire: False }
              name: cloudinitdisk
parameters:
  - name: NAME
    description: VM name
    generate: expression
    from: 'ol8-[a-z0-9]{16}'
  - name: DATA_SOURCE_NAME
    description: Name of the DataSource to clone
    value: ol8
  - name: DATA_SOURCE_NAMESPACE
    description: Namespace of the DataSource
    value: virtualization
  - name: CLOUD_USER_PASSWORD
    description: Randomized password for the cloud-init user centos
    generate: expression
    from: '[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}'

Configure Satellite for Convert2RHEL

With our VM template ready, now we need to configure a few more things on Satellite. This was actually the part that stumped me for a long time because I was making incorrect assumptions about the convert2rhel process. For instance, I assumed that you needed a content view that explicitly includes the RHEL and Oracle repos, when in reality the only way I was ever able to get this to work was to not use a content view at all and just create an activation key that pointed to Library > Default Organization View which effectively contains everything in your Satellite. Just select the necessary repos from the list (RHEL 8 and Oracle Linux 8 in this case) and disable everything else.

Install the Ansible Role on Satellite

Navigate to Configure > Ansible > Roles and click the 'Import from Satellite' button. Then select the redhat.satellite.convert2rhel from the list

Re-register your Oracle server

Re-register the Oracle server using the new activation key you created under Library > Default Organization View.

subscription-manager register --org=cudanet --activationkey=c2r-test

Then with your host re-registered to Satellite, click on the host and click the 3 dot menu under Content View Environment. Make sure to select Library and Default Organization View. (I'm sure that this step could be automated somehow but this is what worked for me while going through this process).

Run Convert2RHEL

At long last, we're finally ready to run convert2rhel. However, it's probably wise to run a Convert2RHEL Analyze job first as a sanity check.

Assuming it passes all of it's checks, you can proceed to run the conversion.

D'oh! Remember what I said about disabling secure boot temporarily? Yeah, VMs need a reboot for the setting to take. But other than that, everything checked out with no errors.

That's more like it. Now let's run the actual conversion. Schedule a new remote job on the host and select the actual Convert to RHEL job template this time.

Select your activation key, select Restart = Yes and set ELS = No.

Then click 'Run on Selected Hosts' to kick of the job. The actual conversion takes a lot longer than the analyze job, and it's going to sit on TASK [Start convert2rhel] the majority of the time with no further information. You can ssh into the box and run top to see what it's actually doing.

It took about 18 minutes total for the conversion job to run and reboot the host. YMMV.

If we go back to the hosts list, we can now see that the OS is now reporting itself as RHEL 8

Make sure to re-register the server using a RHEL activation key, eg;

subscription-manager register --org=cudanet --activationkey=rhel_8_x86_64 --force

Wrapping up

It was a long journey to get to the point where I could successfully convert an Oracle box to RHEL using only Satellite and more importantly, only Satellite hosted repos, thus making this process reproducible in disconnected environments. I probably made this harder on my self than necessary by stubbornly insisting on going with Oracle instead of one of the "easier" distros like CentOS

If you run into any snags along the way, it's probably best to run convert2rhel directly on the server CLI as it will provide a lot more debugging info until you get the process ironed out for your environment.

Now to tackle LEAPP 🤔. To be continued...