Fedora CoreOS + contenedor con Omada Controller

Introducción

CoreOS es una edición mínima del sistema operativo Fedora, inmutable, con actualizaciones automáticas, y enfocada a ejecutar servicios en contenedores. Aunque puede correr en máquinas físicas, está pensada para el despliegue automatizado de máquinas virtuales, tanto en local como en todo tipo de nubes.

Por todo ello, su instalación y configuración inicial, difieren bastante de cualquier distribución GNU/Linux habitual. Pero el resultado es un sistema robusto y optimizado, que merece la pena explorar.

Objetivo

En este artículo veremos como crear una máquina virtual con Fedora CoreOS, en la que desplegaremos un contenedor con el software Omada Controller que nos permitirá gestionar una infraestructura de red TP-Link.

Lo llevaremos a cabo en un entorno ya existente, que consta de un host físico Debian 13 (Trixie) con KVM/libvirt para virtualización, y una máquina de escritorio con Fedora Workstation 43.

Preparación del Ignition

La configuración de una nueva máquina con CoreOS se basa en el sistema Ignition, con lo que debemos proporcionarle un archivo JSON con un formato específico. Para generarlo, crearemos primero un archivo YAML que veremos a continuación, y utilizaremos la herramienta Butane para convertirlo al formato de Ignition.

Este sería el fichero omadactrl.bu que utilizaríamos en nuestro caso:

variant: fcos
version: 1.6.0
passwd:
  users:
    - name: core
      ssh_authorized_keys:
        - ssh-rsa XXXXXX  # Clave pública que utilizaremos para acceder vía SSH con el usuario "core"
storage:
  disks:
    - device: /dev/vdb
      wipe_table: true
      partitions:
        - number: 1
          label: containers  # Crea una partición en el disco /dev/vdb, que utilizaremos para albergar los contenedores que se ejecutarán en la máquina virtual
  filesystems:
    - path: /var/lib/containers
      device: /dev/vdb1
      format: xfs
      with_mount_unit: true  # Formatea la partición y habilita el montaje automático de la misma mediante un servicio de systemd
  files:
    - path: /etc/hostname
      mode: 0644
      contents:
        inline: omadactrl.home  # Establece el hostname
    - path: /etc/sysctl.d/10-disable-ipv6.conf
      contents:
        inline: |  # Deshabilita el protocolo IPv6 para todas las conexiones
          net.ipv6.conf.all.disable_ipv6=1
          net.ipv6.conf.default.disable_ipv6=1
    - path: /etc/NetworkManager/system-connections/enp1s0.nmconnection
      mode: 0600
      contents:
        inline: |
          [connection]
          id=enp1s0
          type=ethernet
          interface-name=enp1s0
          [ipv4]
          address1=192.168.0.100/24,192.168.0.1
          dns=192.168.0.1;
          dns-search=home
          may-fail=false
          method=manual
          [ipv6]
          method=disabled
    - path: /etc/hosts
      overwrite: false
      append:
        - inline: |  # Añade la entrada con la IP de la máquina en el /etc/hosts
            192.168.0.100 omadactrl.home omadactrl
    - path: /etc/vconsole.conf
      mode: 0644
      contents:
        inline: KEYMAP=es  # Establece el idioma del teclado
    - path: /etc/zincati/config.d/55-updates-strategy.toml
      contents:
        inline: |  # Define la ventana en la que se reiniciará de forma automática el servidor para aplicar las actualizaciones del sistema operativo, cuando las hubiere
          [updates]
          strategy = "periodic"
          [[updates.periodic.window]]
          days = [ "Sun" ]
          start_time = "04:00"
          length_minutes = 60
  links:
    - path: /etc/localtime
      target: ../usr/share/zoneinfo/Europe/Madrid  # Establece la zona horaria

Después realizamos la conversión ejecutando Butane en nuestra Workstation con Podman:

podman run --interactive --rm quay.io/coreos/butane:release --pretty --strict < omadactrl.bu > omadactrl.ign

Con esto ya tenemos el omadactrl.ign con el formato Ignition que necesitamos.

A continuación creamos una carpeta en nuestro host, para albergar el archivo .ign que acabamos de crear, y cualquier otro que vayamos a utilizar en un futuro.

sudo mkdir /var/lib/libvirt/ignition
sudo chown libvirt-qemu:libvirt-qemu /var/lib/libvirt/ignition

Una vez creado el omadactrl.ign, le cambiamos también el propietario.

sudo chown libvirt-qemu:libvirt-qemu /var/lib/libvirt/ignition/omadactrl.ign

En el caso de Debian, AppArmor no le va a permitir a libvirt abrir los archivos .ign en su ubicación, así que debemos indicarle esta excepción añadiendo la siguiente línea al final del archivo /etc/apparmor.d/local/abstractions/libvirt-qemu:

/var/lib/libvirt/ignition/*.ign rk,

Y después reiniciamos el servicio AppArmor.

sudo systemctl restart apparmor.service

Descarga de la imagen de CoreOS

Vamos a la web de descargas de CoreOS y localizamos la URL de la imagen estable para QEMU. Cuando la tengamos, la descargamos en nuestro host y la colocamos en su ubicación definitiva:

wget https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/43.20251024.3.0/x86_64/fedora-coreos-43.20251024.3.0-qemu.x86_64.qcow2.xz
unxz fedora-coreos-43.20251024.3.0-qemu.x86_64.qcow2.xz
sudo mv fedora-coreos-43.20251024.3.0-qemu.x86_64.qcow2 /var/lib/libvirt/images/omadactrl-vda.qcow2
sudo chown libvirt-qemu:libvirt-qemu /var/lib/libvirt/images/omadactrl-vda.qcow2
sudo chmod 600 /var/lib/libvirt/images/omadactrl-vda.qcow2

Arranque de la nueva máquina virtual

En los comandos indicados en la guía oficial de CoreOS, se utiliza la opción backing_store para dejar el qcow2 que hemos descargado como base, y hace un overlay sobre dicha imagen para la máquina virtual que estamos creando. Puede ser útil en escenarios en los que vayamos a desplegar muchas máquinas virtuales con la misma base, para lo que supone un ahorro importante de espacio, al compartir todas una única imagen de disco.

Pero en nuestro caso lo vamos a hacer de forma diferente, nuestra máquina virtual atacará directamente a la imagen de disco que hemos descargado, y quedará para uso exclusivo de la misma.

En primer lugar definiremos una serie de variables con los parámetros que le pasaremos al comando virt-install:

export IGNITION_CONFIG="/var/lib/libvirt/ignition/omadactrl.ign"
export DISK_A="/var/lib/libvirt/images/omadactrl-vda.qcow2"
export VM_NAME="omadactrl"
export VCPUS="2"
export RAM_MB="4096"
export DISK_B="/var/lib/libvirt/images/omadactrl-vdb.qcow2"
export DISK_B_GB="20"
export BRIDGE="br2"
export IGNITION_DEVICE_ARG=(--qemu-commandline="-fw_cfg name=opt/com.coreos/config,file=${IGNITION_CONFIG}")

Y luego lanzamos la creación de la máquina virtual:

sudo virt-install --connect="qemu:///system" --name="${VM_NAME}" --vcpus="${VCPUS}" --memory="${RAM_MB}" \
        --os-variant="fedora-coreos-stable" --import --graphics=none \
        --disk="${DISK_A}" \
        --disk="${DISK_B},size=${DISK_B_GB}" \
        --network bridge=${BRIDGE} "${IGNITION_DEVICE_ARG[@]}"

Cuando veamos que ha terminado el proceso, podemos salir de la consola de la máquina virtual pulsando Ctrl+5.

Por último, configuramos la máquina para que arranque automáticamente:

sudo virsh autostart omadactrl

Instalación del firewall

Ya nos podemos conectar a la nueva máquina con el usuario «core» e instalar firewalld:

sudo rpm-ostree install firewalld

Debemos tener en cuenta que estamos en un sistema inmutable, así que lo reiniciamos para que se aplique el cambio:

sudo systemctl reboot

Después podemos comprobar que efectivamente está ya activo el firewall:

sudo firewall-cmd --list-all

Configuramos los puertos que el controlador Omada va a necesitar abiertos en el firewall. Para ello creamos el archivo /etc/firewalld/services/omada.xml:

<?xml version="1.0" encoding="utf-8"?>
<service>
 <short>omada</short>
 <description>Omada Controller</description>
 <port protocol="tcp" port="8088" />
 <port protocol="tcp" port="8043" />
 <port protocol="tcp" port="8843" />
 <port protocol="tcp" port="29811-29816" />
 <port protocol="udp" port="29810" />
 <port protocol="udp" port="27001" />
</service>

Luego añadimos el nuevo servicio al firewall y comprobamos que queda activado:

sudo firewall-cmd --permanent --add-service=omada
sudo firewall-cmd --reload
sudo firewall-cmd --list-all
sudo firewall-cmd --info-service=omada

Preparación del entorno que ejecutará el contenedor

Vamos a ejecutar el contenedor siguiendo la filosofía «rootless», es decir, con un usuario básico sin privilegios, en lugar de que corra con el usuario «root».

En primer lugar creamos el nuevo usuario, y le habilitamos el lingering para que pueda seguir corriendo servicios a pesar de que no haya ninguna sesión iniciada con el mismo:

sudo useradd -u 1001 box
sudo loginctl enable-linger box

Luego creamos las carpetas que contendrán los datos del contenedor, y hacemos que el nuevo usuario sea su propietario:

sudo mkdir -p /var/lib/containers/omada/data
sudo mkdir -p /var/lib/containers/omada/logs
sudo chown -R box:box /var/lib/containers/omada

Cambiamos al nuevo usuario. Hacerlo de esta forma en lugar de utilizando el comando «su» nos facilitará las cosas a la hora de trabajar con servicios de systemd:

sudo machinectl shell box@.host

Habilitamos el servicio que se encargará de arrancar automáticamente los contenedores cuando se reinicie la máquina virtual:

systemctl --user enable podman-restart.service
systemctl --user start podman-restart.service
systemctl --user status podman-restart.service

Habilitamos el temporizador que comprobará periódicamente si existen nuevas versiones de las imágenes utilizadas en los contenedores, para proceder a su actualización de forma automática, si así lo establecemos:

systemctl --user enable podman-auto-update.timer
systemctl --user list-timers --all

Definición del contenedor

Esta es la parte que puede resultar más «chocante» para los que estamos acostumbrados a trabajar con contenedores Docker. Lo habitual cuando queremos desplegar una aplicación es encontrarnos con un docker-compose.yml de ejemplo, que adaptamos a nuestras necesidades.

Por un lado, CoreOS viene con Podman para la gestión de contenedores en lugar de Docker, y por otro, lo ideal es utilizar lo que llaman Quadlets, que es una manera de integrar la ejecución de contenedores como servicios de systemd.

En nuestro caso, utilizaríamos esta configuración si fuésemos a trabajar con Docker Compose:

services:
  omada-controller:
    container_name: omada
    image: mbentley/omada-controller:5.15
    restart: always
    ulimits:
      nofile:
        soft: 4096
        hard: 8192
    stop_grace_period: 60s
    network_mode: host
    environment:
      - PUID=1001
      - PGID=1001
      - TZ=Europe/Madrid
    volumes:
      - /var/lib/containers/omada/data:/opt/tplink/EAPController/data
      - /var/lib/containers/omada/logs:/opt/tplink/EAPController/logs

Pero como vamos a definir un quadlet, lo haremos creando el archivo ~/.config/containers/systemd/omada.container con el siguiente contenido (importante, seguimos con el usuario «box»):

[Unit]
Description=Omada Controller Container
Wants=network-online.target
After=network-online.target

[Container]
ContainerName=omada
Image=docker.io/mbentley/omada-controller:5.15
AutoUpdate=registry
User=1001
Group=1001
Environment=ROOTLESS=true
Environment=TZ=Europe/Madrid
Volume=/var/lib/containers/omada/data:/opt/tplink/EAPController/data:Z,U
Volume=/var/lib/containers/omada/logs:/opt/tplink/EAPController/logs:Z,U
Network=host
Ulimit=nofile=4096:8192
PodmanArgs=--stop-timeout 60

[Service]
Restart=always

[Install]
WantedBy=default.target

En la documentación de podman-systemd.unit tenemos mucha información para realizar la adaptación de Docker Compose a este tipo de servicios.

Recargamos la configuración de systemd y arrancamos el nuevo servicio:

systemctl --user daemon-reload
systemctl --user start omada.service

Con esto arrancará el contenedor y ya habríamos terminado.

Referencias

https://docs.fedoraproject.org/en-US/fedora-coreos
https://github.com/coreos/docs/issues/343
https://thelinuxitguy.wordpress.com/2025/01/21/the-ultimate-fedora-coreos-guide/
https://github.com/dave-atx/omada-podman-quadlet/blob/main/.config/containers/systemd/omada/omada.container
https://github.com/mbentley/docker-omada-controller?tab=readme-ov-file#quickstart-guide
https://github.com/containers/quadlet/blob/main/docs/ContainerSetup.md
https://mo8it.com/blog/quadlet/#updating-images
https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html
https://www.tp-link.com/us/support/faq/3281/
https://stackoverflow.com/questions/79173758/podman-volume-mount-permissions-issues
https://www.redhat.com/en/blog/debug-rootless-podman-mounted-volumes
https://www.nanosector.nl/posts/podman-rootless-autostart/

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *