Linux – Arranque en modo sólo lectura (ro) en systemd

por | 3 diciembre, 2015

Existen varias estrategias para hacer que un sistema con systemd se inicie en modo sólo lectura. Puede ser algo no tan trivial como parece debido a su esencia paralelista y a que puede no haber un orden único de ejecución de los procesos, tras probar varios métodos, finalmente opté por una solución sencilla y que me funciona en CentOS7.

En systemd se utiliza el archivo /etc/fstab para generar units de tipo mount. Simplemente editando dicho archivo es posible indicar que la partición que contiene el punto de montaje «/» se haga como sólo lectura. Ejemplo de fstab:

/dev/mapper/centos-root /                       ext3    defaults,ro        1 1
/dev/sda1               /boot                   ext3    defaults,ro        1

tmpfs                   /dev/shm                tmpfs   defaults        0 0
sysfs                   /sys                    sysfs   defaults        0 0
proc                    /proc                   proc    defaults        0 0

Parece sencillo, pero no lo es tanto. Esta estrategia, propia de sistemas embebidos, requiere una adecuación de los directorios necesarios en escritura del sistema. Además de /proc y /sys, los cuales ya son volátiles, necesitamos que el home del root, /tmp  y /var se vean escribibles tras el arranque se sistema operativo. Una manera de hacerlo es habiendo comprimido antes dichos directorios y descomprimiéndolos sobre unidades RAMdisk en el arranque. Pero además necesitamos que dicha descompresión se haga en un punto suficientemente temprano en el arranque, ya que si no, algunos servicios fallarán al no poder leer y escribir en el tmp y var. Para ello usaremos un servicio que llamaré setupRamdisk.service que se inicie con el sysinit.service y ejecute un script que haga lo necesario.

Primero, un script de preparación de estos directorios en RAMdisk puede ser el siguiente:

# Creación y adecuación de la unidad RAM
mknod /dev/ram0 b 1 2
mkdir /RAMDISK
mount /dev/ram0 -t tmpfs /RAMDISK
mkfs.ext3 -F -m0 -q /dev/ram0

# Movemos el contenido de RW a disco RAM
mv /tmp /RAMDISK
mv /var /RAMDISK
mv /root /RAMDISK

# Creamos los enlaces hacia la unidad RAM
ln -s /RAMDISK/tmp /tmp
ln -s /RAMDISK/var /var
ln -s /RAMDISK/root /root

# Enlaces extras necesarios para el /VAR
ln -sf /run/lock /RAMDISK/var/lock
ln -sf /run/ /RAMDISK/var/run

# Comprimimos el home del root y el /var
tar -czf /tmpvar.tar.gz /RAMDISK/var
tar -czf /root.tar.gz /RAMDISK/root

# Copiamos nuestros ficheros personalizados en sistema:
cp -f fstab /etc/fstab
cp -f setupRamdisk /usr/bin/
cp -f setupRamdisk.service /etc/systemd/system/
cp -f systemd-random-seed.service /usr/lib/systemd/system

# Esto equivale al systemctl enable setupRamdisk.service:
ln -s /etc/systemd/system/setupRamdisk.service /etc/systemd/system/sysinit.target.wants/setupRamdisk.service

El script que se ejecutará en el arranque para la restauración de los directorios en RAM puede ser el siguiente:

# Preparar la unidad RAM
mknod /dev/ram0 b 1 2
mount /dev/ram0 -t tmpfs /RAMDISK/
mkfs.ext3 -F -m0 -q /dev/ram0

# Crear los directorios de volcado
mkdir /RAMDISK/etc
mkdir /RAMDISK/root
mkdir /RAMDISK/tmp

# Descompresión de los contenidos en R/W
tar -xzpf /tmpvar.tar.gz
tar -xzpf /root.tar.gz

# Adecuamos algunos permisos
chmod 1777 /RAMDISK/tmp
chmod 755 /RAMDISK/var/lock
chmod 1777 /RAMDISK/var/tmp

Finalmente el servicio setupRamdisk.service que será llamado desde sysinit.service:

[Unit]
   Description=RAMDisk folders init
   DefaultDependencies=no
   Wants=local-fs-pre.target
   Before=local-fs-pre.target shutdown.target systemd-random-seed.service
   Conflicts=shutdown.target

[Service]
   Type=oneshot
   ExecStart=/usr/bin/setupRamdisk

[Install]
   WantedBy=sysinit.target

Como vemos, además de esto, se ha tenido que tocar el servicio systemd-random-seed.service. Este servicio es llamado desde sysinit.service mediante un «wants» pero en su definición se especifica Before=sysinit.target y cuando se ejecuta, el /tmp sobre el que escribe aún no estaba disponible. Tenemos que decirle explícitamente que espere, añadiendo nuestro servicio setupRamdisk.service en la directiva After:

[Unit]
Description=Load/Save Random Seed
Documentation=man:systemd-random-seed.service(8) man:random(4)
DefaultDependencies=no
RequiresMountsFor=/var/lib/systemd/random-seed
Conflicts=shutdown.target
After=systemd-readahead-collect.service systemd-readahead-replay.service systemd-remount-fs.service setupRamdisk.service
Before=sysinit.target shutdown.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/lib/systemd/systemd-random-seed load
ExecStop=/usr/lib/systemd/systemd-random-seed sabe

Todo esto funcionó, y obtuve un sistema arrancable desde flash en sólo lectura y completamente funcional. Para la instalación de nuevos paquetes o acceso de lectura/escritura sólo hay que ejecutar «mount -o rw,remount /«. Pero puede haber estrategias más elegantes, como el uso de systemd-tmpfiles, el cual no he probado y promete ser más intuitivo.

 

 

 

 

 

 

 

 

 

 


 

 

 

 

 

 

 

 

 

 

Deja un comentario

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