Optimizaciones Raspberry Pi

Recopilación de algunas mejoras de rendimiento para diferentes escenarios. Incluye todas las versiones de Debian hasta Bookworm y para cualquier Raspberry que use alguna de estas versiones.

Hasta la versión de Debian Bookworm el archivo de configuración config.txt estaba en /boot/config.txt ahora lo podéis encontrar en /boot/firmware/config.txt. Lo mismo sucede con el fichero de configuración del kernel cmdline.txt pasa de estar en /boot/cmdline.txt a estar en /boot/firmware/cmdline.txt.

También se incluyen algunas recomendaciones, sobre todo para procesos de larga duración.

Rendimiento

En esta sección incluyo algunas mejoras que se pueden usar para mejorar el rendimiento. Hay que tener cuidado con algunas de ellas así que es bueno evaluar cada una para ver si proporciona el rendimiento esperado.

Reducir memoria dedicada a la gráfica

Para aumentar la memoria disponible del sistema se puede reducir la cantidad de memoria dedicada a la parte gráfica, esto no suele ser un problema ya que muchas veces Raspberry no se usa para aplicaciones gráficas.

Esto lo hacemos a través de la utilidad raspi-config, vamos a la opción Performance options -> GPU Memory e indicamos como valor 16 que es el mínimo permitido.

En Bookworm esto ha cambiado y ahora tenemos que editar manualmente el fichero config.txt. Añadimos esta línea en el fichero, por ejemplo antes de las opciones de audio (buscar dtparam), aquí podemos indicar el valor que queramos, no se recomiendan valores muy bajos (menores a 64MB), por ejemplo el mínimo para una Raspberry Pi 1 sería 16MB. Después de aplicar este cambio reiniciamos:

# Set memory split (add this line to set GPU memory allocation)
gpu_mem=16

Overclock

Existe también la opción de hacer overclocking a la Raspberry Pi a través de la herramienta raspi-config, vamos a la opción Performance options -> Overclock y elegimos una de las opciones para aumentar la velocidad del procesador, estas opciones son delicadas y no es recomendable usarla sobre todo porque Raspberry no tiene ningún sistema de refrigeración adicional.

Aumentar archivo swap

En general el tamaño del archivo swap es adecuado pero para ciertos procesos muy intensivos o Raspberry limitadas se puede aumentar para evitar que se quede colgada, la contrapartida es que esto producirá un mayor aumento de E/S por lo que el tiempo de vida de la tarjeta SD se puede ver reducida. Se puede aumentar de la siguiente forma:

Primero paramos el uso del archivo swap.

sudo dphys-swapfile swapoff

Ahora modificamos el tamaño del fichero dphys-swapfile, por ejemplo:

sudo nano /etc/dphys-swapfile

Vamos a elegir un tamaño de 256MB así que modificamos la siguiente línea.

CONF_SWAPSIZE=256

Y ahora podemos volver a habilitar el uso del fichero swap, primero lo inicializamos y luego lo arrancamos.

sudo dphys-swapfile setup
sudo dphys-swapfile swapon

Si queremos ver la memoria swap asignada así como la que está en uso podemos usar el comando top o htop o free -h.

Usando top la línea que tiene MiB Swap se refiere a la memoria swap, total es toda la que disponemos, free es la que actualmente no está en uso y used es la que está en uso, en avail Mem podemos ver la suma tanto de la memoria RAM como de la swap, haciendo un seguimiento de estos valores podemos tunear para escenarios concretos.

Usando zswap

zswap es un sistema de compresión de memoria RAM esto proporciona más memoria pero también un aumento del uso del procesador en el proceso de compresión, descompresión. Es útil en algunos escenarios donde los procesos consumen mucha memoria. Diría por las pruebas que he hecho que permite que ciertos procesos terminen y no se cuelguen pero no aumenta esencialmente la velocidad de los mismos. Los pasos serían:

Tenemos que añadir zswap.enabled = 1 al archivo /boot/cmdline.txt (/boot/firmware/cmdline.txt en bookworm) para habilitar zswap.

Otra opción sería usar zram, aquí y aquí un listado de ventajas e inconvenientes.

Habilitando z3fold & lz4 para zswap

Instalamos primero el paquete lz4, que es el compresor de menoría y z3fold es la forma en que se guardan estas páginas de memoria.

sudo apt-get install lz4

Después tenemos que comprobar si está habilitado initramfs, para eso editamos este fichero /etc/default/raspberrypi-kernel y comprobamos que está descomentada esta línea (No necesario para la version bookworm).

INITRD=Yes

En caso de que estuviese comentada posiblemente no tendríamos creado initramfs si quisiéramos verificarlo ejecutamos.

sudo update-initramfs -uv

Y la propia salida nos indicará si existe o no, en caso de que no exista ejecutamos el siguiente comando:

sudo update-initramfs -c -k $(uname -r)

Editamos el fichero /etc/initramfs-tools/modules y añadimos las siguientes líneas:

lz4
lz4_compress
z3fold

Y ejecutamos el siguiente comando para actualizar con los nuevos módulos:

update-initramfs -uv

Si da un error de hard link realmente es un aviso y no es un problema. Esta salida nos debería indicar también el nombre de la imagen, algo así como -este fichero se puede encontrar en /boot-:
‘/boot/initrd.img-6.6.20+rpt-rpi-v6‘ -> ‘/boot/firmware/initramfs’

(Este paso lo podemos omitir en bookworm) Después editamos config.txt y añadimos al final del fichero dentro de la sección general, es decir, ninguna sección que contenga un hardware concreto como [pi4] [pi3] [all]... siendo el valor el nombre de la imagen que nos ha sacado el comando anterior (no es necesario poner un «=»).

initramfs initrd.img-6.6.20+rpt-rpi-v6

Ahora editamos cmdline.txt y añadimos lo siguiente:

zswap.compressor=lz4 zswap.zpool=z3fold

Y reiniciamos, después para verificar que todo ha ido correcto ejecutamos:

grep -R . /sys/module/zswap/parameters

Que nos debería sacar como salida…

/sys/module/zswap/parameters/same_filled_pages_enabled:Y
/sys/module/zswap/parameters/enabled:Y
/sys/module/zswap/parameters/max_pool_percent:20
/sys/module/zswap/parameters/compressor:lz4
/sys/module/zswap/parameters/zpool:z3fold
/sys/module/zswap/parameters/accept_threshold_percent:90

En este punto la configuración del archivo config.txt auto_initramfs=1 se puede quitar por redundante pero tampoco afectará si se deja, esto asegura que al menos un initramfs se cargará al inicio, de hecho en Bookworm es la configuración recomendada y no usar la mencionada anteriormente.

Más información sobre las ventajas aquí y detalles de configuración aquí:

Usando ZRAM

A veces puede suceder que zswap no nos de el resultado esperado, podemos probar en ese caso con zram, para eso seguimos estos pasos:

sudo wget -O /usr/bin/zram.sh https://raw.githubusercontent.com/Bash-Projects/rpi_zram/master/zram.sh

Damos permisos para ejecutar el script que nos acabamos de descargar:

sudo chmod +x /usr/bin/zram.sh

Ahora lo vamos a programar para que se ejecute 50 segundos después del arranque, así que lanzamos crontab.

sudo crontab -e

y programamos la ejecución del script:

@reboot ( sleep 50 ; sudo /usr/bin/zram.sh &)

El comando lo podemos lanzar directamente para ver el resultado o podemos reiniciar y esperar 50 segundos para ver el resultado, podemos verificar el aumento de memoria usando:

free -h

para ver como ha aumentado la RAM y para poder ver el aumento en la swap usamos:

swapon -s

Recomendaciones

Recomendaciones para ciertas tareas que se suelen hacer con Raspberry de forma habitual.

Procesos de larga duración

Muchas veces tenemos que lanzar procesos en Raspberry que pueden tardar mucho tiempo, sobre todo en un sistema como Raspberry de capacidades limitadas, y queremos asegurarnos que el proceso termine aunque no vayamos a estar delante del terminal, aquí indico algunas formas de abordar este problema.

Usando comandos

A veces hay procesos que llevan mucho tiempo -por ejemplo compilar un contenedor-, la mejor opción es dejar ejecutando el comando build en el terminal pero a veces esta compilación se lanza desde una conexión ssh y al cerrar se puede terminar el proceso, para evitar esto se puede usar el comando nohup, por ejemplo:

nohup docker image build --tag user/imagen -f Dockerfile .

También se puede ejecutar en segundo plano añadiendo & al final del comando.

Si el comando ya se estaba ejecutando podemos usar el comando disown para conseguir el mismo efecto, en este caso primero pasamos el comando a segundo plano usando Ctrl+Z y usando el comando jobs veremos como el comando está detenido, ahora lo volvemos a arrancar pero esta vez en segundo plano usando:

bg %1

siendo 1 el número del trabajo y a continuación lo desasociamos del terminal usando:

disown -h %1

Si solo tenemos un trabajo podemos omitir %1 en los comandos ya que actuarán sobre el último proceso que se haya lanzado.

Con cualquiera de estos dos métodos el proceso seguirá ejecutándose aunque salgamos del terminal.

Un problema adicional es no saber que está haciendo el proceso, podemos guardar un log redireccionando a texto con > pero en ese caso no veremos por pantalla lo que está pasando para esto tenemos el comando tee que nos permite ver por pantalla y a la vez emitir a fichero lo que está pasando.

Este comando es un resumen de lo explicado, lanza el proceso en segundo plano y permite ver la salida por pantalla a la vez que se guarda en un fichero.

nohup docker image build --tag user/image -f Dockerfile . | tee out.txt &

Si cerramos la sesión y volvemos a conectar más adelante la forma cómoda de ver que está haciendo el trabajo sería usando el comando tail con el fichero que hemos indicado, usando el parámetro -f podemos ir viendo las actualizaciones a medida que el proceso escriba.

tail -f out.txt

Usando screen

Otra opción posiblemente más cómoda sea usando screen, permite tener una sesión de la que se pueda salir sin cerrar el proceso, tiene el problema que no es un paquete instalado por defecto así que primero lo instalamos.

sudo apt-get install screen

Para lanzarlo simplemente usamos el comando screen

screen

Aceptamos el mensaje y veremos un shell en el que podemos trabajar, algunos comandos útiles de screen.

# Listado de las sesiones activas
screen -ls
# Reanudar sesión, si solo hay una activa
screen -r
# Reanudar sesión, donde ID es el número que aparece antes del punto
screen -r ID

También tenemos unos atajos útiles:

Ctrl+A,D Desconectamos temporalmente de la sesión (esto NO interrumpe los procesos activos).

Ctrl+A,A Permite cambiar entre diferentes sesiones activas.

Capturar errores en comandos encadenados

Muchas veces en encadenamos comandos con && y puede ser que fallé alguno de esos comandos pero realmente no se dará la ejecución como fallida a no ser que falle el último, si queremos que cualquier comando produzca un error en toda la ejecución podemos usar pipefail:

set -eo pipefail

Referencias

Enabling & Increasing Raspberry Pi Swap – Neblio
ZRAM

Optimizaciones Raspberry Pi

Kodi y plataformas de streaming (Netflix, HBO, Amazon, Disney+)

Actualizado a 13 de Octubre de 2022

Es habitual ver centros multimedia en Raspberry Pi, lo que no es tan habitual es montarlos en una imagen Docker, con las mejoras que ha habido en hardware en Raspberri ya es posible tener varios servicios a la vez en una sola Rpi, por ejemplo servidores de vídeo, audio etc..

En este caso se van a detallar los pasos para tener una imagen Docker con Kodi (v19.x) en la última versión Debian disponible en este momento (Bullseye).

Esta imagen Docker contendrá los plugins de HBO, Netflix, Amazon y Disney+ (aunque es posible que se puedan configurar otros).

Configuración Host

Antes de empezar con la imagen Docker necesitaremos una Raspberry operativa, como versión de Raspberry Pi OS podemos usar cualquiera (incluso Lite) una vez instalado Raspberry Pi OS actualizamos el sistema con:

sudo apt-get update
sudo apt-get upgrade

También hace falta instalar el paquete lirc.

apt-get install lirc

Y tendremos también que instalar Docker siguiendo estos pasos sino está ya instalado, en ese mismo enlace se dan algunas recomendaciones sobre rendimiento que pueden ser útiles.

Vídeo

Hay que hacer una pequeña modificación en el fichero /boot/config.txt para que el vídeo se reproduzca correctamente, tenemos que tener esto en el fichero:

[all]
dtoverlay=vc4-fkms-v3d

Y después solo queda reiniciar. A partir de aquí es posible que queramos hacer algunas tareas adicionales, por ejemplo igual queremos añadir como biblioteca de vídeo o audio un USB conectado o crear carpetas de unidad de red para acceder a ficheros remotos.

Docker

Aquí tenemos la imagen Docker, solo hay un parámetro para build que indica que salida de audio se va a utilizar, 1 para HDMI y 0 para la salida Jack.

FROM balenalib/raspberrypi3-debian:bullseye

ARG DEBIAN_FRONTEND=noninteractive
# 1 for HDMI, 0 for headphones
ARG     AUDIO=1

RUN	apt-get update				&& \
	apt-get -y purge openssl		&& \
	apt-get -y --purge autoremove		&& \
	apt-get dist-upgrade			&& \
	apt-get install	-y			\			
	uuid-dev		\
	upower			\
	alsa-base		\
	alsa-utils		\
	alsa-tools		\
	dbus-x11		\
	libraspberrypi0		\
	xterm			\
	xinput			\
	xinput-calibrator	\
	evemu-tools		\	
	libnspr4		\
	libwidevinecdm0		\
	libc6			\
	avahi-daemon		\
	libnss-mdns		\
	nano			\
	lirc			\
	lirc-compat-remotes	\
	bluez			\
	dumb-init		\
# Required for AirPlay Mirroring
	cmake					\
        libavahi-compat-libdnssd-dev		\
        libplist-dev				\
        libgstreamer1.0-dev			\
        libx264-dev				\
        libjpeg-dev				\
        libgstreamer-plugins-base1.0-dev	\
        libgstreamer-plugins-bad1.0-dev		\
        gstreamer1.0-plugins-ugly		\
        gstreamer1.0-tools			\
        gstreamer1.0-gl				\
        gstreamer1.0-gtk3			\
        git					\
        git-svn					\
        libssl-dev				\
# Python required for Netflix, Amazon...
	python3-pip		\
	build-essential		&& \
# Install some Python packages
	pip install setuptools pycryptodome pycryptodomex wheel pycrypto				&& \
	apt-get -y --purge autoremove									&& \
	rm -rf /var/lib/apt/lists/*

# besides kodi, we will install a few extra packages:
#  - ca-certificates              allows Kodi to properly establish HTTPS connections
#  - kodi-eventclients-kodi-send  allows us to shut down Kodi gracefully upon container termination
#  - kodi-game-libretro           allows Kodi to utilize Libretro cores as game add-ons
#  - kodi-game-libretro-*         Libretro cores
#  - kodi-inputstream-*           input stream add-ons
#  - kodi-peripheral-*            enables the use of gamepads, joysticks, game controllers, etc.
#  - kodi-pvr-*                   PVR add-ons
#  - kodi-screensaver-*           additional screensavers
#  - lirc,lirc-compat-remotes     enables the use of IR Remotes
#  - locales                      additional spoken language support (via x11docker --lang option)
#  - pulseaudio                   in case the user prefers PulseAudio instead of ALSA
#  - tzdata                       necessary for timezone selection
RUN packages="                                               \
    fbset                                                    \
    ca-certificates                                          \
    kodi                                                     \
    kodi-eventclients-kodi-send                              \
    kodi-inputstream-adaptive                                \
    kodi-inputstream-rtmp                                    \
    kodi-peripheral-joystick                                 \
    kodi-pvr-argustv                                         \
    kodi-pvr-dvblink                                         \
    kodi-pvr-dvbviewer                                       \
    kodi-pvr-filmon                                          \
    kodi-pvr-hdhomerun                                       \
    kodi-pvr-hts                                             \
    kodi-pvr-iptvsimple                                      \
    kodi-pvr-mediaportal-tvserver                            \
    kodi-pvr-mythtv                                          \
    kodi-pvr-nextpvr                                         \
    kodi-pvr-njoy                                            \
    kodi-pvr-pctv                                            \
    kodi-pvr-sledovanitv-cz                                  \
    kodi-pvr-stalker                                         \
    kodi-pvr-teleboy                                         \
    kodi-pvr-vbox                                            \
    kodi-pvr-vdr-vnsi                                        \
#    kodi-pvr-vuplus                                          \ # Not yet available
    kodi-pvr-wmc                                             \
    kodi-pvr-zattoo                                          \
    kodi-screensaver-biogenesis                              \
    kodi-screensaver-matrixtrails                            \
    kodi-screensaver-pyro                                    \
    kodi-screensaver-stars                                   \
    lirc                                                     \
    lirc-compat-remotes                                      \
    locales                                                  \
    libnss3                                                  \
    tzdata"                                               && \
                                                             \
    apt-get update                                        && \
    apt-get install -y $packages                          


# Audio settings
RUN     echo            \
"pcm.!default {         \n\
  type asym             \n\
  playback.pcm {        \n\
  type plug             \n\
  slave.pcm "output"    \n\
}                       \n\
capture.pcm {           \n\
  type plug             \n\
  slave.pcm "input"     \n\
  }                     \n\
}                       \n\
pcm.output {            \n\
  type hw               \n\
  card $AUDIO           \n\
}                       \n\
ctl.!default {          \n\
  type hw               \n\
  card $AUDIO           \n\
}" >> /etc/asound.conf

# Adding user
ENV	KODI_ID=1000
RUN     adduser \
	--uid $KODI_ID \
	--disabled-password \
	--gecos '' \
	--ingroup sudo \
	kodi 
RUN     echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
RUN	usermod -a -G audio,video,input,dialout,plugdev,netdev,users,cdrom,tty kodi

# Install RpiPlay
WORKDIR /opt/vc
RUN     git svn clone https://github.com/raspberrypi/firmware/trunk/opt/vc/include
RUN     git svn clone https://github.com/raspberrypi/firmware/trunk/opt/vc/src
RUN	git svn clone https://github.com/raspberrypi/firmware/trunk/opt/vc/lib

WORKDIR /repos
COPY    rpiplay.tar.gz .
RUN     tar -zxvf rpiplay.tar.gz
RUN	ls -la
WORKDIR /repos/rpiplay/build
RUN	ls -la
RUN	cmake ..
RUN	make -j 4
WORKDIR /
RUN	rm -rf /repos
RUN	rm -rf /rpiplay.tar.gz

# Plugins

WORKDIR	/plugins
ADD	--chown=kodi:sudo https://github.com/castagnait/repository.castagnait/raw/kodi/repository.castagnait-2.0.0.zip .
ADD	--chown=kodi:sudo https://k.slyguy.xyz/repository.slyguy.zip .
ADD	--chown=kodi:sudo https://github.com/Sandmann79/xbmc/releases/download/Repository/repository.sandmann79.plugins-1.0.4.zip .

# Configuration
EXPOSE	8080 9777/udp

VOLUME	/home/kodi
USER	kodi

COPY entrypoint.sh /usr/local/bin
CMD ["/usr/local/bin/entrypoint.sh"]

También usaremos el siguiente fichero Docker compose:

version: "3.7"
services:
  rpi-kodi:
    image: joursain/rpi-kodi:buster
    build:
      context: .
      dockerfile: Dockerfile
    container_name: "kodi"
    user: kodi
    network_mode: host
    restart: "no"
    privileged: true
    devices:
      - /dev/fb0:/dev/fb0
      - /dev/vchiq:/dev/vchiq
      - /dev/tty0:/dev/tty0
      - /dev/tty2:/dev/tty2
      - /dev/input:/dev/input
      - /dev/snd:/dev/snd
      - /dev/shm:/dev/shm
    volumes:
      - /var/run/dbus:/var/run/dbus
      - /mnt/media/kodi:/home/kodi
      - "/etc/timezone:/etc/timezone:ro"
      - "/etc/localtime:/etc/localtime:ro"
      - "/usr/bin/tvservice:/usr/bin/tvservice:ro"
    tmpfs:
      - /tmp
    environment:
      - DBUS_SYSTEM_BUS_ADDRESS=unix:path=/var/run/dbus/system_bus_socket

Para arrancar lo haremos de la forma habitual (opcionalmente podemos añadir -d si queremos que arranque en segundo plano)

docker-compose up [-d]

Veremos la salida (si no hemos seleccionado -d) de la ejecución y en cuanto haya terminado aparecerá Kodi en pantalla. Esta imagen permite el uso de teclado así que en cuanto esté operativa podremos usar el teclado y escuchar sonidos a medida que se seleccionen diferentes opciones, esto verificará que el sonido está bien configurado.

La carpeta home del usuario kodi (/home/kodi) que contiene la configuración de Kodi está compartida en un volumen por si hiciera falta hacer una copia de seguridad de la configuración o para añadir algún plugin externo se puede dejar en esta carpeta para poder instalarlo desde Kodi.

Al arrancar posiblemente saldrán varias ventanas relacionadas con plugins pidiendo activación, no hace falta hacerlo en un primer momento y se puede hacer más adelante.

Servicios

Es posible configurar algunos servicios de Kodi como por ejemplo UPnP o AirPlay de la siguiente forma:

  1. Vamos a configuración (el icono de la rueda)
  2. Icono System
  3. Services
  4. Aquí podemos habilitar UPnP/DLNA o AirPlay

En la parte de abajo hay una opción para poder ampliar las opciones de configuración (el nivel máximo es Expert)

Sonido

La imagen Docker por defecto al arrancar ya tiene el sonido habilitado, la configuración que deberíamos ver es la que está en:

  1. Vamos a configuración (el icono de la rueda)
  2. Icono System
  3. Opción audio
  4. Audio output device
  5. Marcamos Default (bcm2835 Headphones bcm2835 Headphones) ()

Como detalle si se están compartiendo los altavoces entre Raspberry y otro dispositivo es posible que la imagen arranque sin sonido, solo un dispositivo puede estar habilitado a la vez.

Plugins

Ahora vamos a configurar los diferentes plugins, la imagen Docker los coloca en la carpeta /plugins, antes de instalarlos hay que realizar algunas acciones en la imagen Docker.

  1. Vamos a configuración (el icono de la rueda).
  2. Add-ons.
  3. My add-ons.
  4. VideoPlayer InputStream.
  5. InputStream Adaptive.
  6. Enable.

Ahora tenemos que habilitar la opción de orígenes desconocidos para poder instalar los plugins

  1. Vamos a configuración (el icono de la rueda).
  2. System.
  3. Add-ons.
  4. Habilitamos Unknown sources y confirmamos el mensaje.

Para instalar los diferentes plugins tendremos que seguir estos pasos.

  1. Vamos a configuración (el icono de la rueda).
  2. Add-ons.
  3. Install from zip file (igual tenemos que usar los .. para subir a un nivel superior y poder ver la opción) y confirmamos el mensaje que saldrá, este mensaje solo nos indica que estos plugins no se actualizan automáticamente y habrá que hacerlo manualmente.
  4. Nos movemos a la carpeta /plugins (Root filesystem/plugins).
  5. Aquí se encuentran todos los plugins que iremos instalando.

Instalación

Todos los plugins se instalan de la misma forma, el procedimiento es:

  1. Desde la carpeta plugins del paso anterior buscamos el plugin que queramos instalar, habrá los siguientes:
    1. CastagnaIT para Netflix
    2. SlyGuy para HBO y Disney+
    3. Sandman para Amazon
  2. Una vez seleccionado e instalado, vamos a la opción Install From Repository (al mismo nivel que Install from zip File) y veremos varios repositorios, seleccionamos por el mismo nombre que antes el que interese.
  3. Si solicita componentes adicionales los instalamos también.

Al ejecutar algunos plugins es posible que solicite instalar Widevine CDM, es necesario instalarlo para que funcione el vídeo. Este proceso puede llevar un rato. Es posible también que falle la instalación o de un error al abrir el vídeo, en ese caso hay que apagar el contenedor y volverlo a levantar.

Configuración

Ahora que están los plugins instalados vamos a configurarlos, simplemente hay que acceder a ellos a través de:

  1. Vamos a configuración (el icono de la rueda)
  2. Add-ons
  3. My addons / Video add ons / (plug-in de vídeo)

Básicamente todos los plugins tienen el mismo sistema, algunos pedirán los datos de la cuenta al principio y otros tendrán una sección especial. Cada plugin tiene varias opciones de configuración, por ejemplo idioma o subtítulos, merece la pena dedicarle un rato.

Televisión

Para poder ver la televisión tenemos que ir al apartado de Add-Ons en Sistema y luego en My-AddOns y finalmente instalamos PVR IPTV Simple Client, en la parte de Configure donde dice M3U playlist URL ponemos https://www.tdtchannels.com/lists/tv.m3u8 o https://www.tdtchannels.com/lists/tvradio.m3u8 si también queremos radio.

Ahora solo tenemos que ir al icono de TV que tenemos en la página principal en un lateral y esperar a que cargue los canales, saldrán muchos así que usar el buscador es una buena opción.

Notas

El código fuente aquí.

Como dato adicional se puede usar este mismo docker compose para cambiar Plex por Kodi.

No parece que de buen resultado usar zswap aquí, produce algunos errores al menos en Rpi3, es mejor usar zram.

De igual forma no podemos tener la memoría gráfica por debajo de 160, así que lo dejamos en 256GB.

Bonus

Esta imagen Docker admite AirPlay y Bluetooth, también el uso de infrarrojos si hay uno conectado aunque habría que incluirlo en docker-compose añadiendo – dev/lirc:/dev/lirc o – dev/lirc0:/dev/lirc0

Referencias

https://forum.kodi.tv/showthread.php?tid=365714
https://mundokodi.com/addon-hbo-max-en-kodi/
https://support.zoom.us/hc/en-us/articles/203680359-Protocols-for-Apple-screen-mirroring-AirPlay-
https://forums.raspberrypi.com/viewtopic.php?t=295008
https://github.com/CodaFog/kodi-rpi
https://github.com/rimago/rpi-kodi
https://kodi.wiki/view/AirPlay
https://www.genbeta.com/multimedia/como-ver-netflix-kodi-que-puede-ser-mejor-que-hacerlo-sus-aplicaciones-oficiales
https://www-genbeta-com.cdn.ampproject.org/v/s/www.genbeta.com/multimedia/netflix-hbo-pluto-tv-twitch-youtube-todas-grandes-plataformas-que-puedes-ver-kodi-como-hacerlo/amp?amp_gsa=1&amp_js_v=a6&usqp=mq331AQIKAGwASCAAgM%3D#amp_tf=De%20%251%24s&aoh=16301408042642&csi=0&referrer=https%3A%2F%2Fwww.google.com&ampshare=https%3A%2F%2Fwww.genbeta.com%2Fmultimedia%2Fnetflix-hbo-pluto-tv-twitch-youtube-todas-grandes-plataformas-que-puedes-ver-kodi-como-hacerlo
https://www.balena.io/docs/learn/more/masterclasses/services-masterclass/#5-running-systemd-in-a-service
https://wiki.ubuntu.com/LircHowto
https://www.linuxquestions.org/questions/linux-from-scratch-13/blfs-svn-dbus-won%27t-start-886338/
https://forums.balena.io/t/dbus-failed-to-connect-to-socket-host-run-dbus-system/3107
https://www.balena.io/docs/learn/develop/runtime/#dbus-communication-with-host-os

Kodi y plataformas de streaming (Netflix, HBO, Amazon, Disney+)

Docker Raspberry: Servidor multimedia con Compose

Actualizado el 19 de Octubre de 2021

Con la llegada de Raspberry surgieron muchas distribuciones multimedia como Kodi, OSMC, OpenELEC,… todas tienen en común que vienen preparadas para ser utilizadas sin ninguna instalación y con su propia interfaz gráfica y proporcionan plugins para ampliar la funcionalidad, por ejemplo emisoras de radio o clientes Torrent.

Estas distribuciones al estar dedicadas en exclusiva a funciones multimedia no permiten realizar otras acciones. Puede ser que no estemos todo el tiempo usándola de este modo y queramos darle más usos, sobre todo con los últimos modelos que son más potentes así que una alternativa es usar contenedores Docker, en este caso se van a crear tres contenedores:

  • Transmission: Un cliente torrent personalizable.
  • Mopidy: Servidor de audio compatible con Spotify.
  • Plex: Un servidor de streaming de video.
Docker

Dockerfile

Todos los contenedores se pueden ejecutar por separado usando los siguientes ficheros Dockerfile o usando el fichero compose que se indica al final. El código fuente está aquí. Vamos a suponer que las carpetas locales están bajo el raíz /mnt/media

Docker Mopidy

El fichero Docker para Mopidy está basado en este artículo, tiene consejos y recomendaciones sobre la configuración de sonido, la variable de entorno AUDIO maneja la salida de audio. Hay que tener en cuenta que si los altavoces están compartidos, por ejemplo tanto un ordenador como una Raspberry están conectados solo uno puede estar usándolo a la vez y el otro dispositivo tendrá que cambiar su salida de audio para liberar el canal.

# 0. Official armv32 image
FROM debian:latest

LABEL "guru.raraavis.creator"="blog@raraavis.guru"
LABEL "guru.raraavis.version"="1.0.0"
LABEL "guru.raraavis.release-date"="20/03/2021"
LABEL "guru.raraavis.description"="Raspberry Pi 3 image with Mopidy"

# 1. Environment vars
ARG DEBIAN_FRONTEND=noninteractive
ARG TZ=Europe/Madrid
ENV TZ=$TZ
        # Use 1 for headphones, 0 for HDMI
ARG AUDIO=1

# 2. Update system and install necessary packages
RUN     apt-get update && \
        apt-get install -y \
                wget \
                lsb-release \
                gnupg2 \
                gnupg \
                gnupg1 \
                python3-dev \
                python3-pip \
                libffi-dev \
                sudo \
                gosu \
                dumb-init && \
                cron && \
                rm -rf /var/lib/apt/lists/* && \
                rm -rf /tmp/*

# 3. Add mopidy repository
RUN     wget -q -O - https://apt.mopidy.com/mopidy.gpg | apt-key add -
RUN     DEBIAN_CODENAME=`lsb_release -sc` && \
        wget -vO /etc/apt/sources.list.d/mopidy.list "https://apt.mopidy.com/$DEBIAN_CODENAME.list"

# 4. Install pip packages and Mopidy
RUN     apt-get update && \
        apt-get install -y \
                mopidy \
                libspotify-dev && \
        rm -rf /var/lib/apt/lists/* && \
        rm -rf /tmp/*
RUN     python3 -m pip install \
                pyspotify \
                Mopidy-Local \
                Mopidy-Iris \
                Mopidy-Mobile \
                Mopidy-Spotify \
                Mopidy-Mopify

# 5. Configure user
RUN     usermod -d /mnt/mopidy mopidy
RUN     echo "mopidy ALL=NOPASSWD: /usr/local/lib/python3.7/dist-packages/mopidy_iris/system.sh" >> /etc/sudoers

# 6. Create folders
WORKDIR /mnt/mopidy
RUN     mkdir /mnt/mopidy/data
RUN     mkdir /mnt/mopidy/cache
COPY    scripts/ scripts/

# 7. Configuration local scan
RUN     (crontab -l 2>/dev/null || true; echo "0 */1 * * * mopidy --config  /mnt/mopidy/config/mopidy.conf local scan") | crontab -
RUN     ln -sf /mnt/mopidy/config/mopidy.conf /etc/mopidy/mopidy.conf

# 8. Audio settings
RUN     echo            \
"pcm.!default {         \n\
  type asym             \n\
  playback.pcm {        \n\
  type plug             \n\
  slave.pcm "output"    \n\
}                       \n\
capture.pcm {           \n\
  type plug             \n\
  slave.pcm "input"     \n\
  }                     \n\
}                       \n\
pcm.output {            \n\
  type hw               \n\
  card $AUDIO           \n\
}                       \n\
ctl.!default {          \n\
  type hw               \n\
  card $AUDIO           \n\
}" >> /etc/asound.conf


# 9. Container
EXPOSE  6680
VOLUME  /mnt/mopidy/config
VOLUME  /mnt/mopidy/local

ENTRYPOINT ["/usr/bin/dumb-init","--"]
CMD ["/bin/sh", "scripts/entry_point.sh", "mopidy", "--config", "/mnt/mopidy/config/mopidy.conf"]

Instalamos algunos paquetes de sistema necesarios para poder instalar Mopidy, el paquete curl se instala para poder usar el comando HEALTHCHECK en el fichero docker-compose.yml

Instalamos Mopidy usando el repositorio oficial, en este caso usamos Debian porque el repositorio está orientado a esta distribución de tal forma que al usar el comando lsb_release podemos parametrizar el acceso al repositorio.

Una vez instalado Mopidy instalamos algunos plugins (se podrían instalar más). Usamos el usuario creado mopidy para arrancar el servicio y configuramos las carpetas donde guardar la configuración (/config) y la carpeta de archivos locales (/local) para el plugin local.

Como el plugin local tiene que escanear si hay cambios en las carpetas añadimos una tarea cron que se ejecutará cada hora para refrescar carpetas. Este mismo comando se puede ejecutar desde la opción Start local scan para eso añadimos al usuario mopidy al fichero sudoers para que pueda lanzar el comando. Este comando lanzado a través de la web utilizar el fichero de configuración por defecto así que creamos un enlace simbólico al que vamos a usar.

En este caso el usuario mopidy no hace falta que tenga derechos de root por seguridad, aunque es verdad que sería necesario para actualizar algunos plugins en este caso es más práctico actualizar el contenedor.

También se usa el paquete dump-init, el proceso mopidy no está orientado a ser usado como proceso padre así que es difícil gestionar su ciclo de vida si sucede algo y es necesario reiniciarlo, por eso gestionamos el contenedor a través de dumb-init que es el proceso padre del contenedor y el que arranca el proceso de mopidy.

Para obtener un id de cliente y un secret de Spotify podemos ir aquí.

Compilación y ejecución

Para compilar podemos usar:
docker build –tag user/mopidy -f Dockerfile.mopidy .

Para ejecutar podemos usar:
docker container run \
–init \
–publish 6680:6680 \
–device /dev/snd \
–volume /mnt/media/mopidy/config:/mnt/mopidy/config \
–volume /mnt/media/mopidy/local:/mnt/mopidy/local \
–env RA_UUID=1000 \
–env RA_GUID=1000 \
–env RA_SRVC=mopidy \
–env RA_FLDR=/mnt/mopidy \
–detach \
user/mopidy

Un detalle importante es usar –device /dev/snd para que el dispositivo de sonido sea utilizado dentro del contenedor. Una vez que arranquemos el contenedor tendremos que hacer login en Spotify y puede que también refrescar el token y la lista de reproducción.

Migración

Si ya tenemos una instalación de Mopidy podemos coger el fichero de configuración buscándolo con:
sudo find / -name mopidy*
Normalmente estará en el home del usuario en la carpeta .config, por ejemplo /home/pi/.config/mopidy/mopidy.conf

Docker Transmission

El fichero Docker para Transmission está basado en este artículo aunque también existe una variante de linuxserver.

# 0. Official armv32 image
FROM ubuntu:latest

LABEL "guru.raraavis.creator"="blog@raraavis.guru"
LABEL "guru.raraavis.version"="1.0.0"
LABEL "guru.raraavis.release-date"="20/03/2021"
LABEL "guru.raraavis.description"="Raspberry Pi 3 image with Transmission"

# 1. Environment vars
ARG DEBIAN_FRONTEND=noninteractive
ARG TZ=Europe/Madrid
ENV TZ=$TZ

# 2. Update system and install necessary packages
RUN     apt-get update && \
        apt-get install -y \
                gosu \
                software-properties-common \
                gnupg2 \
                gnupg \
                gnupg1 && \
                rm -rf /var/lib/apt/lists/* && \
                rm -rf /tmp/*

# 3. Add transmission repository and install Transmission
RUN     add-apt-repository ppa:transmissionbt/ppa && \
        apt-get update && \
        apt-get install -y \
                transmission-cli \
                transmission-common \
                transmission-daemon && \
                rm -rf /var/lib/apt/lists/* && \
                rm -rf /tmp/*

# 4. Configure user
RUN     usermod -d /mnt/transmission debian-transmission

# 5. Create folder
WORKDIR /mnt/transmission
COPY    scripts/ scripts/

# 6. Container
EXPOSE  9091
VOLUME  /mnt/transmission/config
VOLUME  /mnt/transmission/watch
VOLUME  /mnt/transmission/download
VOLUME  /mnt/transmission/temp

ENTRYPOINT ["/bin/sh", "scripts/entry_point.sh"]
CMD ["/usr/bin/transmission-daemon", "-g", "/mnt/transmission/config", "-f", "-x", "/mnt/transmission/config/trans.PID"]

Para evitar un error parecido a este:
UDP Failed to set receive buffer: requested 4194304, got 425984 (tr-udp.c:84)

Tenemos que editar algunos valores del kernel en el host estos valores no se pueden modificar en el contenedor al ser valores compartidos. Hay varias formas de editar estos valores, una forma sería la siguiente:

Primero editamos el fichero sysctl.conf y añadimos lo siguiente (hay varias formas de editar sysctl).

net.core.rmem_max = 16777216
net.core.wmem_max = 4194304

Y refrescamos los valores con sysctl -p

Tenemos también varios volúmenes configurados:

  • /mnt/transmission/config para la configuración
  • /mnt/transmission/watch la carpeta watch donde buscar nuevos ficheros torrent para descargar
  • /mnt/transmission/download carpeta de descargas
  • /mnt/transmission/temp la carpeta temporal

Se usa un script especial para arrancar el contenedor y configurar los permisos, los detalles aquí.

Compilación y ejecución

Para compilar podemos usar:
docker build –tag user/transmission -f Dockerfile.transmission .

Para ejecutar podemos usar:
docker container run –init -p 9091:9091 -p 51413:51413 \
–volume /mnt/media/transmission/config:/mnt/transmission/config \
–volume /mnt/media/transmission/download:/mnt/transmission/download \
–volume /mnt/media/transmission/temp:/mnt/transmission/temp \
–volume /mnt/media/transmission/watch:/mnt/transmission/watch \
–env RA_UUID=1000 \
–env RA_GUID=1000 \
–env RA_SRVC=debian-transmission \
–env RA_FLDR=/mnt/transmission
\
–detach user/transmission

Migración

La carpeta de Transmission tiene no solo la configuración en el fichero settings.json también otras carpetas donde se guardan los ficheros torrent descargados y las estadísticas, se puede encontrar con sudo find / -name settings.json y normalmente estará ubicada en /var/lib/transmission-daemon
Hay que copiar todo este contenido dentro de la carpeta /mnt/transmission/config y actualizar el fichero settings.json para que apunte a las carpetas del contenedor (/mnt/transmission/download, ….)

Docker Plex

En este caso se ha usado un contenedor ya existente de Plex suministrado por linuxserver.

Compilación y ejecución

En este caso no hace falta compilar al ser una imagen sacada del registro de contenedores. Para ejecutar podemos ver los detalles aquí.

Migración

Si tenemos cuenta en Plex con hacer login se puede recuperar gran parte de la configuración, la carpeta con el resto de la información se puede sacar de aquí /var/lib/plexmediaserver/Library/Application Support/Plex Media Server tal y como se cuenta aquí.

Docker Compose

Ahora todos los contenedores se arrancan a la vez usando este fichero:

version: "3"
services:  
  mopidy:    
    container_name: mopidy    
    image: raraavis/mopidy    
    build:      
      context: ..
      dockerfile: media/mopidy/Dockerfile.mopidy
    ports:      
      - 6680:6680    
    volumes:      
      - /mnt/media/mopidy/config:/mnt/mopidy/config
      - /mnt/media/mopidy/local:/mnt/mopidy/local
    devices:
      - /dev/snd
    environment:      
      - RA_UUID=1000      
      - RA_GUID=1000      
      - RA_SRVC=mopidy
      - RA_FLDR=/mnt/mopidy
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:6680 || bash -c 'kill -s 15 -1 && (sleep 10; kill -s 9 -1)'"]      
      interval: 30m
      timeout: 30s
      retries: 3
      start_period: 30s
    restart: unless-stopped
  transmission:    
    container_name: transmission    
    image: raraavis/transmission    
    build:      
      context: ..
      dockerfile: media/transmission/Dockerfile.transmission
    ports:      
      - 9091:9091      
      - 51413:51413    
    volumes:      
      - /mnt/media/transmission/config:/mnt/transmission/config
      - /mnt/media/temp:/mnt/transmission/temp
      - /mnt/media/download:/mnt/transmission/download
      - /mnt/media/watch:/mnt/transmission/watch
    environment:      
      - RA_UUID=1000      
      - RA_GUID=1000      
      - RA_SRVC=debian-transmission
      - RA_FLDR=/mnt/transmission
  plex:    
    container_name: plex    
    image: ghcr.io/linuxserver/plex    
    container_name: plex    
    network_mode: host    
    environment:      
      - PUID=105      
      - PGID=106      
      - VERSION=docker      
    #- PLEX_CLAIM= see .env file
    volumes:      
      - /mnt/media/plex/config:/config      
     #- /mnt/media/plex/tv:/tv      
      - /mnt/media/movies:/movies    
  restart: unless-stopped

Algunos aspectos a destacar son que el servidor Mopidy usa un health check para verificar si sigue activo, esto es debido a un bug reportado a Mopidy, el comando lo que hace es verificar si el servidor sigue activo y sino lo está mata el contenedor para que se reinicie usando el comando restart, más información aquí.

Para rellenar PLEX_CLAIM puede usarse esta página, esta variable está en un fichero de configuración separado .env otra opción sería usar secrets pero esto condiciona a la creación de un swarm.

Para docker transmission se pueden configurar varias carpetas pero si algunas de ellas está anidadas dentro de otras pueden ocultarse entre ellas de tal forma que no verá el contenedor los ficheros que dejemos, esto no es algo que suceda usando la línea de comandos de docker. La forma de evitarlo es indicar solo la carpeta raíz.

La estructura de carpetas es la misma que aparece en el código fuente.

Una vez que las imágenes han sido creadas y subidas al repositorio se puede comentar la sección de build.

Compose: compilación y ejecución

El fichero está configurado para compilar los ficheros dockerfile indicados usando docker-compose build el nombre de la imagen será el indicado en image.

Para ejecutar simplemente usamos docker-compose up y podremos ir viendo los mensajes o si lo queremos en segundo plano agregamos la opción –detach y quedaría docker-compose up –detach

Puede pasar que algunos valores del contenedor se mantengan entre ejecuciones y veamos mensajes como:
WARNING: Service «transmission» is using volume «/watch» from the previous container. Host mapping «/watch» has no effect. Remove the existing containers (with docker-compose rm transmission) to use the host volume mapping.

Solo hay que fijarse en el mensaje y ejecutar la instrucción que indica docker-compose rm transmission

Posible arquitectura

Se puede plantear lo siguiente:

  • Los contenedores Docker se colocan en la misma Rpi usando Docker compose.
  • Las carpetas de descarga apuntan a un almacenamiento externo, para saber como hacerlo se puede ver aquí.
  • La carpeta watch se puede crear como un recurso compartido vía Samba para poder dejar los ficheros torrent desde cualquier otra máquina y que se vayan descargando sin tener que hacerlo a través del cliente web.
  • Haría falta un cliente Plex que puede ser una SmartTv, u otra Raspberry Pi con esta distribución.
  • Opcionalmente se puede colocar por delante el proxy Apache como se indica más abajo.

(Opcional) Proxy Apache

Es posible usar un servidor Apache que sirva de proxy para poder acceder a los servicios de forma más cómoda usando una URL con nombre en lugar de a través de la IP y del puerto, en cualquier caso tiene que haber un DNS que pueda resolver los nombres.

Tenemos que habilitar algunos módulos, para permitir que funcionen los websockets cors.

a2enmod headers
a2enmod proxy
a2enmod proxy_http
a2enmod proxy_wstunnel
a2enmod headers

Vamos a suponer que la IP del contenedor es 192.168.1.2, y que el dominio es rpi.local, hay que crear los siguientes ficheros de configuración de apache y colocarlos en la carpeta /etc/apache2/sites-available

001-transmission.conf

<VirtualHost *:80>
      ServerName torrent.rpi.local
      ServerAlias torrent
      ProxyPreserveHost On
      ProxyPass / http://192.168.1.2:9091/
      ProxyPassReverse / http://192.168.1.2:9091/
      CustomLog /var/log/apache2/transmission.log combined
      ErrorLog /var/log/apache2/transmission.error.log
</VirtualHost>

Para habilitar el sitio ejecutamos:

sudo a2ensite 001-transmission.conf

002-mopidy.conf

<VirtualHost *:80>
      ServerName spotify.rpi.local
      ServerAlias spotify
      ProxyPreserveHost On
      ProxyPass / http://192.168.1.2:6680/
      ProxyPassReverse / http://192.168.1.2:6680/
      CustomLog /var/log/apache2/mopidy.log combined
      ErrorLog /var/log/apache2/mopidy.error.log
</VirtualHost>

Para habilitar el sitio ejecutamos:

sudo a2ensite 002-mopidy.conf

003-plex.conf

<VirtualHost *:80>
      ServerName plex.rpi.local
      ServerAlias plex
      ProxyPreserveHost On
      ProxyPass / http://192.168.1.2:32400/
      ProxyPassReverse / http://192.168.1.2:32400/
      CustomLog /var/log/apache2/plex.log combined
      ErrorLog /var/log/apache2/plex.error.log
</VirtualHost>

Para habilitar el sitio ejecutamos este comando para los diferentes virtual host, por ejemplo:

sudo a2ensite 003-plex.conf

Docker Raspberry: Servidor multimedia con Compose

Cambiando permisos en volúmenes Docker

Actualizado el 24 de Marzo de 2021

A veces es necesario que tanto el contenedor como el host accedan a la misma carpeta ya sea local al host o una carpeta externa, esto se puede configurar a través de los volúmenes Docker, para que ambos puedan tener los mismos permisos es necesario que compartan el mismo id de usuario o de grupo, esto es importante porque el nombre de usuario no servirá.

Normalmente esto se soluciona creando un usuario o grupo en el contenedor con un id específico y usando ese id para establecer los permisos en el host. El problema es que a veces el usuario es creado automáticamente por un paquete, como un usuario de servicio, y no se puede conocer por anticipado.

Una solución sencilla es arrancar el contenedor y ejecutar dentro de este el comando id por ejemplo docker exec #contenedor id -u que devolverá el id de usuario que se está usando en el contenedor docker.

Puede ser que esta opción no sea viable porque el contenedor necesite acceder a esa carpeta para arrancar o simplemente no interesa esta acción manual al ejecutarse Docker en un entorno automatizado (DevOps).

Permisos Docker

Script

Una posible solución es usar este script basado en la imagen base de linuxserver.

#!/bin/sh
RA_SRVC=${RA_SRVC:-$(id -un)}

if [ "$RA_SRVC" = $(id -un) ]; then
  echo "Executing as self: $RA_SRVC"
  echo "You will need to be root or sudo"
  $@
else
  RA_SRVC=${RA_SRVC:-$(id -un)}
  uid=$(id -u $RA_SRVC)
  gid=$(id -g $RA_SRVC)
  RA_UUID=${RA_UUID:-$uid}
  RA_GUID=${RA_GUID:-$gid}
  RA_FLDR=${RA_FLDR:-$(pwd)}
  echo "  Starting $RA_SRVC uid=$(id -u $RA_SRVC) gid=$(id -g $RA_SRVC)
  Setting user id:  $RA_UUID $(gosu root usermod -o -u $RA_UUID $RA_SRVC)
  -------------------------------------
  User:     $RA_SRVC $(id -u $RA_SRVC)
  Folder:   $RA_FLDR
  -------------------------------------
  Setting permissions on $RA_FLDR $(gosu root chown -R $RA_UUID:$RA_GUID $RA_FLDR)
  Running $@
  "
  exec gosu $RA_SRVC $@
fi

Usamos las siguientes variables de entorno:

  • RA_SRVC: el usuario que ejecutará el contenedor Docker, si no se indica se supondrá el usuario actual del contenedor Docker.
  • RA_UUID: el nuevo id de usuario.
  • RA_GUID: el id de grupo que se usará para cambiar los permisos.
  • RA_FLDR: la carpeta en la que se establecerá el nuevo owner.

La idea del script es establecer un nuevo id de usuario (RA_UUID) al usuario que va a ejecutar Docker (RA_SRVC) estas variables de entorno se pueden pasar a Docker vía línea de comandos o a través de un fichero docker-compose, después se usa el comando chown para cambiar el propietario en la carpeta indicada (RA_FLDR) al usuario (RA_UUID) y grupo indicado (RA_GUID).

Si no se indica un usuario se entiende que es el establecido en Docker via comando USER o el indicado en docker-compose usando user. Este usuario (RA_SRVC) tiene que ser otro diferente al que arranca el contenedor ya que no se puede cambiar el id así mismo sin reiniciar.

La única opción si se intenta cambiar el id del usuario que está activo en Docker sería hacer un login para refrescar el id del usuario, por ejemplo a través del comando su, lo que implicaría pasar la contraseña al script para poder hacerlo o reiniciar el contenedor pero dado que Docker no conserva el estado no sería útil.

Se está usando gosu en lugar de sudo porque es la opción recomendada para contenedores Docker, para detalles del uso de gosu ver aquí, sino queremos usar gosu porque no se puede instalar la opción comentada también funcionaría sudo -u «$RA_SRVC» $

Hay que tener en cuenta que si el volumen a compartir es una unidad de red entonces en /etc/fstab se indica el usuario que se establecerá como owner al montar la carpeta.

Uso

Una forma típica de uso es usando el comando ENTRYPOINT de Docker, la diferencia con CMD es que ENTRYPOINT no se puede sobreescribir, por ejemplo:

ENTRYPOINT [«/bin/sh», «/entry_point.sh»]
CMD [«/usr/bin/transmission-daemon», «-f», «-x», «/tmp/trans.PID»]

Un ejemplo más completo aquí.

Mejoras

En la opción sudo es necesario que el usuario pueda ejecutar sudo para poder establecer los permisos y lanzar el comando que ejecutará el contenedor, esto se podría cambiar quitando sudo antes de lanzar el comando si el usuario puede lanzar el comando sin usar sudo, también se puede usar s6-setuidguid.

Si se ejecuta el contenedor con el usuario por defecto root se pueden establecer los permisos usando también root en el host aunque puede que no sea la opción más segura.

Notas al pie

Detalles sobre instrucciones bash aquí.
Monta tu propio logo en ascii-art.
El comando cat es para mostrar el logo.

Cambiando permisos en volúmenes Docker

Docker Raspberry: Jupyter kernels

Actualizado el 29 de Octubre de 2022

Docker

Jupyter tiene un sistema basado en kernels, que son procesos de ejecución independientes en un lenguaje de programación concreto, de tal forma que en Jupyter pueden convivir kernels ejecutándose en el mismo o en varios lenguajes.

En Python es posible crear entornos virtuales con configuraciones concretas de versiones de Python y paquetes, podemos usar esta idea junto con los kernels de Jupyter para crear diferentes kernels de Python.

Por ejemplo, vamos a suponer para este caso que queremos crear cuatro entornos de Python, cada una de ellas orientado a un propósito diferente con diferentes paquetes.

  • Un entorno con Python 3.9 orientado a IA pero usando la versión de TensorFlow lite que es la recomendada para Raspberry PI incluyendo los paquetes más habituales en ML (torch, pandas, keras, scikit, numpy, …) y algunos paquetes de utilidad (split-folder, BeautifulSoup4, …)
  • Un segundo entorno similar al anterior pero con la versión de TensorFlow completa compilada para Raspberry PI junto con algunos paquetes adicionales como keras-tuner.
  • Un tercer entorno similar al anterior pero orientado a Reinforcement Learning (gym, keras-rl, etc…)
  • Un cuarto entorno de ámbito más matemático (plotly, sympy, …)

En general los comandos que se usarán a continuación están pensados para Python >= 3.3, más adelante se mostrará una configuración para Docker.

Configuración

La configuración incluye dos pasos, la creación del entorno virtual en Python y la creación del kernel asociado al mismo, en este ejemplo vamos a lanzar los comandos directamente desde la consola de Jupyter.

Entorno virtual

Si no lo estuviera ya instalamos el paquete venv para poder crear los entornos, esto hay que hacerlo para cada uno de las versiones de Python que queramos usar:

apt-get install python3-venv

Si usamos la consola de Jupyter es más útil arrancar el shell bash, simplemente ejecutando bash.

Hay que tener en cuenta que si queremos crear los entornos de Python con una versión específica, por ejemplo por compatibilidad de librerías tendremos que usar el binario exacto de esa versión, por ejemplo python3.7 o python3.8, etc…

Nos movemos a la carpeta donde queramos crear el entorno para el primer escenario, para crear el entorno virtual usamos:

python3 -m venv env

En general los ficheros .gitignore están configurados para ignorar la carpeta env por lo que usar este nombre es la opción recomendada.

Esto lo hacemos para todos los demás entornos, nos movemos a la carpeta donde queramos crear este entorno y ejecutamos el mismo comando que antes para que nos cree una carpeta env igual que antes.

Ahora si queremos activar un entornos concreto, dentro de la carpeta env correspondiente ejecutamos:

source env/bin/activate

El entorno se activa para cualquier código Python que se ejecute a partir de ese momento dentro de esa carpeta por lo que se puede crear un entorno y usarlo para varios proyectos.

Si intentamos lanzar Python veremos que se ejecuta con la versión que hemos configurado para esa carpeta, por ejemplo si queremos ver la versión podemos usar:

python3 --version

Cuando queramos desactivar un entorno hacemos:

deactivate

Para eliminar el entorno virtual simplemente tenemos que borrar la carpeta que se ha creado.

rm -r env

Kernel

Para cada uno de los kernels que vamos a crear tendremos que hacer los siguientes pasos, esto habrá que hacerlo dentro de la carpeta de cada entorno y habiendo activado previamente el entorno.

Instalamos el paquete ipykernel que nos proporcionará el kernel de Python para Jupyter, este paquete a su vez requiere del paquete wheel.

python3 -m pip install --user wheel
python3 -m pip install --user ipykernel

Con esta opción –user instalaríamos los paquetes dentro de la carpeta del usuario que es la opción recomendada.

Puede ser que tengamos un error al ejecutar desde el entorno de Jupyter (ERROR: Can not perform a ‘–user’ install. User site-packages are not visible in this virtualenv) en ese caso se puede ejecutar el mismo comando sin la opción –user.

Una vez hecho esto nos movemos a la carpeta de uno de ellos, en concreto vamos a empezar por el primer escenario y vamos a instalar el kernel correspondiente.

python3 -m ipykernel install --user --display-name='Python AI' --name=envpyai

–name se refiere al nombre del kernel que veremos al usar el comando list y –display-name se refiere al nombre que veremos en Jupyter Notebook, usamos –user para instalar el kernel en la carpeta del usuario y no tener problemas de permisos, si quisiéramos instalarlo a nivel global para todos los usuarios tendríamos que ejecutarlo como root.

El comando anterior emitirá una respuesta similar a la siguiente para indicar que todo ha ido bien, en la ruta que se muestra hay un archivo llamado kernel.json que contiene toda la configuración del kernel.

Installed kernelspec env in /home/jupyter/.local/share/jupyter/kernels/env

Ahora hacemos lo mismo para el resto de configuraciones, nos movemos a la carpeta y activamos el entorno correspondiente antes de ejecutar el comando anterior.

Hay que tener en cuenta que al ser entornos virtuales vienen vacíos por lo que habrá que instalar todos los paquetes que sean necesarios para poder desarrollar.

Para ver los kernel instalados podemos usar este comando, como detalle no se pueden tener dos kernel con el mismo nombre.

jupyter kernelspec list

SI queremos eliminar algún kernel, por ejemplo la versión para Python AI haríamos:

jupyter kernelspec uninstall envpyai

Verificación

Sí todo ha ido bien veremos algo como:

Si abrimos una consola de Python veremos la versión directamente, y para verificarla en un notebook podemos ejecutar el siguiente código:

import sys
print(sys.version)

No hay que usar el siguiente código que mostrará la versión global de Python.

!python3 --version

Desde la parte superior derecha o desde el menú podremos cambiar de kernel.

Dockerfile

Este sería un ejemplo de Dockerfile que resume todos los conceptos anteriores, puede que en Docker tener los entornos virtuales no tenga sentido, pero por comodidad vamos a hacerlo en este caso.

En este caso como estamos heredando de la imagen de Jupyter creada previamente cambiamos al usuario root ya que vamos a instalar algunos paquetes.

Como cada comando RUN se ejecuta de forma aislada de los demás una forma es emular lo que haría el entorno virtual de Python para poder activarlos, otra forma es activar los entornos usando el mismo comando que es lo que se ha hecho en este caso.

FROM joursain/rpi-jupyterlab:bullseye
LABEL "guru.raraavis.creator"="blog@raraavis.guru"
LABEL "guru.raraavis.version"="1.3.0"
LABEL "guru.raraavis.release-date"="23/05/2022"
LABEL "guru.raraavis.description"="Raspberry Pi image with JupyterLab Kernels"

# 0. Prepare system
# 0.1 Required for Matplotlib
USER    root
RUN     apt-get update -y && apt-get install -y \
                libopenjp2-7 \
                libjpeg62-turbo \
                libtiff5 \
                libhdf5-dev \
                libpng-dev \
                libavcodec-dev \
                libavformat-dev \
                libswscale-dev \
                libgtk-3-dev \
                unrar-free \
                # Required for nbconvert (to PDF)
                pandoc \
                libxslt1.1 \
                texlive-xetex \
                # Required for RL
                ffmpeg \
                mediainfo \
                xvfb \
                openmpi-bin \
                openmpi-common \
                libxcb1 \
                libopenmpi3 \
                libpomp-dev \
                libomp5 \
                libopenblas-dev \
                libopenmpi-dev && \
        rm -rf /var/lib/apt/lists/* && \
        rm -rf /tmp/*

# 1. Create virtual environments
WORKDIR /home/jupyter/ai
COPY --chown=jupyter    wheels/tensorflow-2.8.0-cp39-cp39-linux_aarch64.whl .
COPY --chown=jupyter    wheels/torch-1.11.0a0+gitbc2c6ed-cp39-cp39-linux_aarch64.whl .
COPY --chown=jupyter    wheels/ale_py-0.7.5+8f3bc3b-cp39-cp39-linux_aarch64.whl .

RUN     chown -R jupyter:jupyter /home/jupyter/ai
USER    jupyter

RUN     python3 -m venv envailite
RUN     source envailite/bin/activate && \
        python3 -m pip install --upgrade pip setuptools wheel && \
        python3 -m pip install --upgrade        \
                typing-extensions               \
                ipykernel                       \
                dask[complete]                  \
                keras                           \
                numpy                           \
                scipy                           \
                scikit-learn                    \
                scikit-image                    \
                pandas                          \
                matplotlib                      \
                split-folders                   \
                opencv-contrib-python==4.5.5.62 \
                theano                          \
                seaborn                         \
                scikit-fuzzy                    \
                mlflow                          \
                pyforest                        \
                html5lib                        \
                BeautifulSoup4                  \
                scrapy                          \
                requests                        \
                bokeh                           \
                plotly                          \
                lxml                            \
                graphviz                        \
                split-folders                   \
                nltk                            && \
        python3 -m pip install --extra-index-url https://google-coral.github.io/py-repo/ tflite_runtime && \
        python3 -m pip install torch-1.11.0a0+gitbc2c6ed-cp39-cp39-linux_aarch64.whl && \
        python3 -m ipykernel install --user --display-name='Python AI Lite' --name=envpyailite && \
        deactivate


RUN     python3 -m venv envai
RUN     source envai/bin/activate && \
        python3 -m pip install --upgrade pip setuptools wheel && \
        python3 -m pip install --upgrade        \
                typing-extensions               \
                ipykernel                       \
                dask[complete]                  \
                keras                           \
                numpy                           \
                scipy                           \
                scikit-learn                    \
                scikit-image                    \
                pandas                          \
                matplotlib                      \
                split-folders                   \
                opencv-contrib-python==4.5.5.62 \
                theano                          \
                seaborn                         \
                scikit-fuzzy                    \
                mlflow                          \
                pyforest                        \
                html5lib                        \
                BeautifulSoup4                  \
                scrapy                          \
                requests                        \
                bokeh                           \
                plotly                          \
                sympy                           \
                lxml                            \
                graphviz                        \
                h5py                            \
                kaggle                          \
                split-folders                   \
                keras-tuner                     \
                nltk                            && \
        python3 -m pip install  \
                torch-1.11.0a0+gitbc2c6ed-cp39-cp39-linux_aarch64.whl \
                tensorflow-2.8.0-cp39-cp39-linux_aarch64.whl && \
        python3 -m ipykernel install --user --display-name='Python AI' --name=envpyai && \
        deactivate

RUN     python3 -m venv envpyrl
RUN     source envpyrl/bin/activate && \
        python3 -m pip install --upgrade pip setuptools wheel && \
        python3 -m pip install  \
                torch-1.11.0a0+gitbc2c6ed-cp39-cp39-linux_aarch64.whl \
                tensorflow-2.8.0-cp39-cp39-linux_aarch64.whl && \
        python3 -m pip install --upgrade        \
                typing-extensions               \
                ipykernel                       \
                dask[complete]                  \
                keras                           \
                numpy                           \
                scipy                           \
                scikit-learn                    \
                scikit-image                    \
                pandas                          \
                matplotlib                      \
                split-folders                   \
                opencv-contrib-python==4.5.5.62 \
                theano                          \
                seaborn                         \
                scikit-fuzzy                    \
                mlflow                          \
                pyforest                        \
                html5lib                        \
                BeautifulSoup4                  \
                scrapy                          \
                requests                        \
                bokeh                           \
                plotly                          \
                sympy                           \
                lxml                            \
                graphviz                        \
                nltk                            \
                gym                             \
                h5py                            \
                pillow                          \
                keras-rl2                       \
                pyvirtualdisplay                \
                keras-tuner                     && \
        python3 -m pip install  \
                ale_py-0.7.5+8f3bc3b-cp39-cp39-linux_aarch64.whl \
                torch-1.11.0a0+gitbc2c6ed-cp39-cp39-linux_aarch64.whl \
                tensorflow-2.8.0-cp39-cp39-linux_aarch64.whl && \
        python3 -m ipykernel install --user --display-name='Python RL' --name=envpyrl && \
        deactivate


RUN     python3 -m venv envmaths
RUN     source envmaths/bin/activate && \
        python3 -m pip install --upgrade pip setuptools wheel && \
        python3 -m pip install --upgrade        \
                typing-extensions               \
                ipykernel                       \
                keras                           \
                numpy                           \
                scipy                           \
                scikit-learn                    \
                pandas                          \
                matplotlib                      \
                opencv-contrib-python==4.5.5.62 \
                theano                          \
                seaborn                         \
                scikit-fuzzy                    \
                requests                        \
                bokeh                           \
                plotly                          \
                sympy                           \
                lxml                            && \
        python3 -m ipykernel install --user --display-name='Python Maths' --name=envmaths && \
        deactivate

RUN     rm torch-1.11.0a0+gitbc2c6ed-cp39-cp39-linux_aarch64.whl
RUN     rm tensorflow-2.8.0-cp39-cp39-linux_aarch64.whl

Compilación

La compilación no tiene ninguna novedad, en este caso el proceso es un poco más largo.

docker image build --tag user/image_name -f Dockerfile.ai .

Ejecución

La ejecución tampoco tiene modificaciones.

docker container run --init -p 8888:8888 --detach --volume /home/pi/jupyter:/home/jupyter/notebooks id_imagen

Referencias

https://janakiev.com/blog/jupyter-virtual-envs/
https://queirozf.com/entries/jupyter-kernels-how-to-add-change-remove

Docker Raspberry: Jupyter kernels

Docker Raspberry: Jupyter TensorFlow

Docker

Es normal que al usar Jupyter se use Tensorflow, siguiendo con el ejemplo de Docker anterior voy a poner dos ejemplos, uno para usar una configuración de Docker con la última versión de TensorFlow y otra para tener dos versiones de TensorFlow, la correspondiente a la rama 1.x y otra con la versión 2.x.

TensorFlow 1.x y 2.x

El siguiente DockerFile está basado en el anterior así que solo se comentarán las diferencias.

FROM ubuntu:latest

# 1. Environment vars
ARG DEBIAN_FRONTEND=noninteractive
ARG TZ=Europe/Madrid
ENV TZ=$TZ
ENV NODE_OPTIONS=--max-old-space-size=768

# 2. Install packages
# 2.1 Update system and install Jupyter
RUN apt-get update && apt-get -y upgrade && \
        apt-get install -y --no-install-recommends libhdf5-dev && \
        apt-get install -y --no-install-recommends tzdata && \
        apt-get install -y --no-install-recommends libzbar-dev libzbar0 && \
        apt-get install -y --no-install-recommends build-essential python3-pip python3-dev python3-venv && \
        apt-get install -y --no-install-recommends git && \
        apt-get install -y --no-install-recommends software-properties-common && \
        python3 -m pip install --upgrade virtualenv && \
        python3 -m pip install --upgrade wheel && \
        python3 -m pip install --upgrade ipykernel && \
        python3 -m pip install --upgrade pip && \
        python3 -m pip install --upgrade setuptools && \
# 2.2 Build git extension
        python3 -m pip install jupyter && \
        apt-get install -y --no-install-recommends nodejs && \
        apt-get install -y --no-install-recommends npm && \
        python3 -m pip install jupyterlab && \
        python3 -m pip install --upgrade jupyterlab-git && \
        jupyter lab build --minimize=False && \
# 2.3 Clean
        apt-get clean && \
        rm -rf /var/lib/apt/lists/* && \
        rm -rf /tmp/*

# 3 Install TensorFlow
RUN python3 -m pip install --no-cache-dir --force-reinstall grpcio
# 3.1 Install Tensorflow 1 for Python 3.7
RUN add-apt-repository ppa:deadsnakes/ppa
RUN apt-get install -y python3.7 python3.7-dev python3.7-venv
RUN python3.7 -m pip install --upgrade pip && python3.7 -m pip install setuptools
COPY tensorflow-1.14.0-cp37-none-linux_armv7l.whl ./
RUN python3.7 -m pip install tensorflow-1.14.0-cp37-none-linux_armv7l.whl
# 3.2 Install Tensorflow 2 for Python 3.8
COPY tensorflow-2.4.0rc0-cp38-none-linux_armv7l.whl ./
RUN python3 -m pip install tensorflow-2.4.0rc0-cp38-none-linux_armv7l.whl

# 4. Add Jupyter user
RUN adduser --disabled-password --gecos '' jupyter
RUN adduser jupyter sudo
RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
USER jupyter
WORKDIR /home/jupyter/
RUN chmod a+rwx /home/jupyter/

# 5. Folder to download git files
RUN mkdir /home/jupyter/notebooks

# 6. Execute jupyter
CMD ["jupyter", "lab", "--port=8888", "--no-browser", "--ip=0.0.0.0", "--allow-root", "--notebook-dir=/home/jupyter/notebooks", "--NotebookApp.token=''", "--NotebookApp.password]

Hay que instalar algunos paquetes nuevos como wheel, virtualenv o ipykernel para poder usar varias versiones de Python en Jupyter a través del uso de kernels, esto se comentará más adelante para configuraciones con varias versiones de TensorFlow.

Instalamos libhdf5-dev y actualizamos el paquete setuptools para evitar un error durante la instalación del paquete grpcio y para evitar posibles errores en la creación de entornos virtuales.

El paquete software-properties-common es necesario para poder agregar repositorios personalizados que harán falta más adelante.

Install TensorFlow (3)

Este código ejecuta las dos versiones de Python, la correspondiente a la 1.14 y 2.0rc4 en el caso de grpcio lo instalamos previamente ya que va a ser usado por ambas versiones de TensorFlow y además necesita una configuración especial para no producir un error durante la instalación.

Install TensorFlow 1 para Python 3.7 (3.1)

La versión Ubuntu de RaspberryPi Buster incorpora la versión 3.8 de Python pero la última versión disponible de TensorFlow 1.x (1.4) solo funciona con Python 3.7 que no se puede instalar a través de los repositorios oficiales de Ubuntu, por lo que configuramos un repositorio alternativo (3.1) donde se encuentran versiones de Python para varias versiones de Ubuntu.

Instalamos los mismos paquetes Python que hemos instalado para la versión 3.8 pero en este caso para la versión 3.7 usando el repositorio anterior y copiamos el paquete de TensorFlow 1.4 (3.1) para poder instalarlo, el paquete lo podemos compilar o cogerlo de aquí.

Install Tensorflow 2 for Python 3.8

En el caso de TensorFlow 2 solo tenemos que copiar (3.2) la versión correspondiente que podemos copiarla del mismo repositorio anterior o compilarla como se describe aquí.

TensorFlow 2.x

La configuración de Docker sería muy similar a la anterior, simplemente quitamos la parte relacionada con TensorFlow 1.x

FROM ubuntu:latest

# 1. Environment vars
ARG DEBIAN_FRONTEND=noninteractive
ARG TZ=Europe/Madrid
ENV TZ=$TZ
ENV NODE_OPTIONS=--max-old-space-size=768

# 2. Install packages
# 2.1 Update system and install Jupyter
RUN apt-get update && apt-get -y upgrade && \
        apt-get install -y --no-install-recommends libhdf5-dev && \
        apt-get install -y --no-install-recommends tzdata && \
        apt-get install -y --no-install-recommends libzbar-dev libzbar0 && \
        apt-get install -y --no-install-recommends build-essential python3-pip python3-dev python3-venv && \
        apt-get install -y --no-install-recommends git && \
        apt-get install -y --no-install-recommends software-properties-common && \
        python3 -m pip install --upgrade virtualenv && \
        python3 -m pip install --upgrade wheel && \
        python3 -m pip install --upgrade ipykernel && \
        python3 -m pip install --upgrade pip && \
        python3 -m pip install --upgrade setuptools && \
# 2.2 Build git extension
        python3 -m pip install jupyter && \
        apt-get install -y --no-install-recommends nodejs && \
        apt-get install -y --no-install-recommends npm && \
        python3 -m pip install jupyterlab && \
        python3 -m pip install --upgrade jupyterlab-git && \
        jupyter lab build --minimize=False && \
# 2.3 Clean
        apt-get clean && \
        rm -rf /var/lib/apt/lists/* && \
        rm -rf /tmp/*

# 3 Install TensorFlow
RUN python3 -m pip install --no-cache-dir --force-reinstall grpcio
COPY tensorflow-2.4.0rc0-cp38-none-linux_armv7l.whl ./
RUN python3 -m pip install tensorflow-2.4.0rc0-cp38-none-linux_armv7l.whl

# 4. Add Jupyter user
RUN adduser --disabled-password --gecos '' jupyter
RUN adduser jupyter sudo
RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
USER jupyter
WORKDIR /home/jupyter/
RUN chmod a+rwx /home/jupyter/

# 5. Folder to download git files
RUN mkdir /home/jupyter/notebooks

# 6. Execute jupyter
CMD ["jupyter", "lab", "--port=8888", "--no-browser", "--ip=0.0.0.0", "--allow-root", "--notebook-dir=/home/jupyter/notebooks", "--NotebookApp.token=''", "--NotebookApp.password=''"]

Compilación

La compilación no tiene ninguna novedad, en este caso el proceso es mucho más largo y para la configuración con dos versiones de TensorFlow puede llevar entre 8-10 horas en una Raspberry Pi 3.

docker image prune -f
docker image build --tag user/image_name .

Ejecución

La ejecución tampoco tiene modificaciones.

docker container run --init -p 8888:8888 --detach --volume /home/pi/jupyter:/home/jupyter/notebooks id_imagen
Docker Raspberry: Jupyter TensorFlow

Docker en Raspberry Pi

Actualizado el 27 de Junio de 2024

Introducción

Es posible usar Docker con Raspberry Pi, puede parecer que en un dispositivo de prestaciones limitadas no tenga mucho sentido pero cada vez es más común verlo para dispositivos IoT y los dispositivos Raspberry han mejorado mucho con el tiempo sobre todo a partir de Raspberry Pi 4.

Aquí coloco algunas notas de como configurar Docker y algunas posibles mejoras de rendimiento.

Preparando el sistema

Lo primero es actualizar el sistema y reiniciar.

sudo apt-get update
sudo apt-get upgrade

Instalando Docker

Instalamos Docker usando el script proporcionado por ellos.

curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

Después agregamos al usuario pi al grupo docker haciendo:

 sudo usermod -aG docker pi

Una vez agregado si estamos con el usuario tendremos que volver a iniciar sesión o reiniciar para que los cambios de pertenencia al nuevo grupo se hagan efectivos.

Reiniciamos con sudo reboot now y verificamos la versión Docker con docker version.
Probamos también a verificar el sistema usando este comando que descargará una imagen Docker y la ejecutará, se verá un mensaje como Hello from Docker!

docker run --rm hello-world

Instalando Docker-Compose

Ahora instalamos Docker Compose que nos hará falta para gestionar varios contenedores a la vez y la comunicación entre ellos, primero instalamos pip, el gestor de paquetes de Python, cualquier distribución de Raspberry ya viene con Python instalado así que no hará falta instalarlo.

sudo curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py && sudo python3 get-pip.py

Si tenemos algún error podemos probar a ejecutar este comando y después volvemos a ejecutar el comando anterior para actualizar pip.

sudo apt-get install python3-pip

Es importante que en el fichero /etc/pip.conf tengamos agregado el repositorio de piwheels

[global]
extra-index-url=https://www.piwheels.org/simple

Es posible también que tengamos que instalar distutils

sudo apt-get install python3-distutils

Ahora instalamos Docker Compose

python3 -m pip install docker-compose

Si nos da este error: command ‘arm-linux-gnueabihf-gcc’ failed with exit status 1 podemos probar lo siguiente y repetir el comando anterior:

sudo apt-get install libzbar-dev libzbar0

Si nos da un error relacionado con python.h instalamos este paquete:

sudo apt-get install python3-dev

Si tenemos un error relacionado con ffi.h instalamos este paquete:

apt install libffi-dev

Finalmente para verificar que docker-compose se ha instalado bien se puede usar:

docker-compose --version

Rendimiento

Usando ZRAM

A veces puede suceder que zswap no nos de el resultado esperado, podemos probar en ese caso con zram, para eso seguimos estos pasos:

sudo wget -O /usr/bin/zram.sh https://raw.githubusercontent.com/Bash-Projects/rpi_zram/master/zram.sh

Damos permisos para ejecutar el script que nos acabamos de descargar:

sudo chmod +x /usr/bin/zram.sh

Ahora lo vamos a programar para que se ejecute 50 segundos después del arranque, así que lanzamos crontab.

sudo crontab -e

y programamos la ejecución del script:

@reboot ( sleep 50 ; sudo /usr/bin/zram.sh &)

El comando lo podemos lanzar directamente para ver el resultado o podemos reiniciar y esperar 50 segundos para ver el resultado, podemos verificar el aumento de memoria usando:

free -h

para ver como ha aumentado la RAM y para poder ver el aumento en la swap usamos:

swapon -s

Recomendaciones

Una serie de recomendaciones y problemas frecuentes.

Usar gosu en lugar de sudo

Las imágenes Docker normalmente se ejecutan como root pero a veces hace falta ejecutar algunos comandos como un usuario concreto dentro del entry point en este caso la mejor opción es usar gosu en lugar de sudo.

Problemas

Job for docker.service failed because the control process exited with error code.
See «systemctl status docker.service» and «journalctl -xe» for details.

Si el servicio da errores al arrancar podemos usar journalctl -xe y si vemos un error como The process’ exit code is ‘exited’ and its exit status is 2, podemos intentar lo siguiente:

rm -rf /var/lib/docker
sudo curl -sSL https://get.docker.com | sh
service docker restart

Es decir, borramos la carpeta, volvemos a instalar y reiniciamos el servicio, es posible que este error indique la tarjeta SD está fallando.

Errores al subir imágenes

Si al hacer push da un error como file integrity checksum failed for «usr/lib/arm-linux-gnueabihf/libicui18n.so.66.1» ejecutamos este comando:

docker system prune -a

Si aún así continuamos con el error no nos quedará otra que borrar la imagen y volverla a crear con docker build.

At least one invalid signature was encountered

Si durante la creación de imágenes obtenemos un error «At least one invalid signature was encountered» al instalar paquetes via apt-get es necesario instalar la versión actualizada de libseccomp2 la opción recomendada es descargar l paquete (en este caso via wget) e instalarlo desde aquí aquí.
Es posible que la versión del paquete haya cambiado y no se encuentre en el servidor, en ese caso se puede acceder a más mirrors aquí.

wget  http://ftp.us.debian.org/debian/pool/main/libs/libseccomp/libseccomp2_2.4.4-1+b1_armhf.deb
sudo apt-get install ./libseccomp2_2.4.4-1+b1_armhf.deb

E: Release file for xxx is not valid yet (invalid for another 2h 45min 28s). Updates for this repository will not be applied.

Esto es por un problema con la hora del sistema, posiblemente el sistema no tiene la hora correcta, recordar que Raspberry no incluye un reloj de hardware, se puede solucionar configurando la zona regional a través de raspi-config pero si eso no funciona una forma cómoda es instalando el cliente de ntp:

sudo apt-get install ntpdate

Después solo nos queda ejecutarlo indicando el servidor de tiempo que queramos usar:

sudo ntpdate time.windows.com
Docker en Raspberry Pi

Compilando Tensorflow 2 para Raspberry Pi

Actualizado el 31 de Octubre de 2020

Introducción

Raspberry instala por defecto TensorFlow 1.x, existen varias opciones para instalar TensorFlow 2, una de ellas es acceder a la página de TensorFlow y descargarse la versión 2 para Python 3.5, en la parte inferior veremos el enlace actualmente con esta dirección:

https://storage.googleapis.com/tensorflow/raspberrypi/tensorflow-2.3.0-cp35-none-linux_armv6l.whl
https://storage.googleapis.com/tensorflow/raspberrypi/tensorflow-2.3.0-cp35-none-linux_armv7l.whl

Ambos paquetes instalarán tensorflow 2.3 para una versión de Python 3.5 el primero para una Raspberry 0 o 1 (arquitectura ARMv6) y el segundo para una Raspberry 2 o 3 (ARMv7).

Puede ser que junto con TensorFlow 2 queramos usar una versión de Python superior (actualmente está disponible la 3.7) en ese caso la mejor opción será crear la versión desde el propio código fuente. Este proceso se puede realizar desde la propia Raspberry pero el proceso es bastante largo debido a las limitaciones de la Raspberry así que vamos a apoyarnos en otra máquina (escritorio, portátil,…) para agilizar el proceso y también tenerlo preparado para futuras versiones.

El proceso se puede realizar de varias formas, en este caso nos apoyaremos en Windows Linux Subsytem.

Windows Linux Subsystem

Lo primero será instalar Windows Linux Subsystem, siguiendo los pasos que se dan en la página se puede preparar Windows 10 para ejecutar Linux sin problemas. Vamos a elegir como distribución a instalar Ubuntu 20.04 LTS.

Una vez arrancado y creado un usuario Linux seguimos los siguientes pasos:

Descargar el código fuente

El código fuente de TensorFlow lo podemos descargar de su repositorio oficial, vamos a trabajar con las ramas release en concreto para este caso la 2.4 que es la última disponible:

git clone https://github.com/tensorflow/tensorflow.git
git checkout r2.4

Compilación del código

TensorFlow 2 se compila a través de Docker, si estamos usando Windows 10 podemos habilitar la integración con WSL, la información completa esta aquí, pero con los siguientes pasos podemos verificar rápidamente si todo está correcto.

  1. Dentro de Settings -> General
    Habilitar Use the WSL 2 based engine (habilitado por defecto). Apply & restart.
  2. Verificar que WSL se está ejecutando en modo 2, ejecutamos este comando en una línea de comandos (cmd): wsl -l -v
    Tenemos que ver que la versión es la 2.
  3. En Settings -> Resources -> WSL Integration habilitamos Enable integration with my default WSL distro y marcamos todas las imágenes en las que queramos habilitar WSL 2. Después Apply & restart.
    Sino vemos la opción de WSL Integration es que estamos ejecutando Docker con contenedores Windows, tenemos que usar contenedores Linux esta opción la tenemos en el menú contextual de Docker Switch to Linux containers.

Ahora nos movemos a la carpeta donde lanzaremos la compilación, que típicamente estará en tensorflow/tensorflow/tools/ci_build (hay dos carpetas tensorflow, la de descarga del código y otra propia del repositorio).

En este caso tenemos que cambiar un par de detalles en el archivo Dockerfile.pi-python37 tenemos que quitar las librerías de Python 2.7, esto hará que no sea compatible con Python 2.7 pero no es un problema ya que Python 2 está en desuso y además la compilación será más rápida, también asociaremos la versión de Python de la imagen Docker con la 3.7, normalmente se hace con update-alternatives pero en este caso lo vamos a simplificar modificando el enlace simbólico.

El siguiente código lo vamos a colocar justo después de la instalación de Python.

...
# The following line installs the Python 3.7 cross-compilation toolchain.
RUN /install/install_pi_python3x_toolchain.sh "3.7"

# Code to remove Python 2.7 and update symbolic link
RUN ln -sf /usr/bin/python3.7 /usr/bin/python
RUN apt-get remove -y libpython2.7-dev

RUN /install/install_bazel.sh
...

Ahora lanzamos este comando y comenzará la compilación, este comando (ci_build.sh) acepta dos parámetros.

  • El primero sirve para indicar en que maquina Docker se ejecutará (PI-PYTHON37), hay otros valores para compilar para otras máquinas Raspberry, si se quiere compilar para todas se puede usar el parámetro PI_ONE.
  • El segundo parámetro (build_raspberry_pi.sh) indica el comando que se ejecutará dentro de la máquina virtual.
./ci_build.sh PI-PYTHON37 \
    tensorflow/tools/ci_build/pi/build_raspberry_pi.sh

Durante la compilación nos irá sacando información del proceso, el tiempo que tarda depende de la máquina en particular, en este caso concreto en una máquina i7-6700K@4Ghz con 16GB RAM y en un disco SSD tarda alrededor de 30′-45′.

El resultado de la compilación lo tendréis en la carpeta tensorflow/output-artifacts y el paquete a instalar tendrá este nombre:
tensorflow-2.4.0rc0-cp37-none-linux_armv7l.whl

Vemos que hemos compilado una release candidate #0 de la versión 2.4 (2.4.0rc0) para una versión de Python 3.7 (cp37) y una arquitectura ARMv7 (armv7l) el none simplemente indica que no está compilado pensando en optimizaciones para GPU (Raspberry no soporta CUDA) o versiones especiales de Python (las versiones m).

Si tarda más de 60′ es posible que el proceso no termine, en ese caso la mejor opción es detener el proceso (Ctrl+Z) buscar el trabajo parado y coger el id (jobs -l) y matar el proceso (kill -9 #id).

Después de esto probamos a volver a lanzar el comando pero antes es muy recomendable reiniciar Docker, volver a abrir la máquina Ubuntu y hacer una limpieza de ficheros con git clean -fxd desde el raíz del repositorio.

Instalación

Ahora solo tenemos que copiar el fichero a la Raspberry, la forma más cómoda posiblemente sea usando el comando scp, por ejemplo:

scp tensorflow-2.4.0rc0-cp37-none-linux_armv7l.whl pi@192.168.1.5:/home/pi

En este ejemplo estamos copiando el fichero a una máquina remota 192.168.1.5 en la carpeta /home/pi y para acceder a esa máquina usamos el usuario pi, al conectar nos pedirá contraseña.

Una vez en la máquina Raspberry nos movemos a la carpeta donde hayamos copiado el fichero (/home/pi) primero vamos a actualizar pip, después actualizaremos setuptools y finalmente instalaremos el paquete TensorFlow 2:

sudo python3 -m pip install --upgrade pip
sudo pip3 install --upgrade setuptools
sudo pip3 install tensorflow-2.4.0rc0-cp37-none-linux_armv7l.whl

También hace falta tener instalado la librería ATLAS

sudo apt-get install libatlas-base-dev

La instalación tarda un poco pero nos irá información del proceso, al terminar para verificar que está bien instalado podemos usar:

sudo python3 -c 'import tensorflow as tf; print(tf.__version__)'

Y deberíamos ver…


2.4.0-rc0

Bonus

Es posible también coger paquetes precompilados desde varios orígenes de terceros y evitar tener que hacer este proceso, pero al no ser repositorios oficiales no se puede asegurar el funcionamiento del mismo.

Otra opción sería usar TensorFlow lite una versión más apropiada para dispositivos de menores prestaciones.

Problemas

Durante el desarrollo de este artículo me he encontrado varios problemas, los enumero aquí para la posteridad (muchos de ellos relacionados con la rama master).

Cannot add PPA: ‘ppa:~openjdk-r/ubuntu/ppa’.
ERROR: ‘~openjdk-r’ user or team does not exist.
Es un problema de certificados, ejecutamos:

sudo apt-get install --reinstall ca-certificates

wget: unable to resolve host address ‘xxx’
Es un problema con los DNS, se puede editar el fichero resolv.conf (sudo nano /etc/resolv.conf) y añadir la siguiente línea para configurar el DNS de Google.

nameserver 8.8.8.8

sudo: pip3: command not found
Hay que instalar el gestor de paquetes de Python pip, en este caso estamos obligados a modificar la imagen de TensorFlow, en el fichero DockerFile que corresponda (en este caso Dockerfile.pi-python37) añadimos esta línea encima de la línea que haya fallado, por ejemplo en este caso ha fallado la instalación del paquete auditwheel

...
RUN /install/install_buildifier.sh
RUN apt install -y python3-pip
RUN pip install --upgrade pip
RUN /install/install_auditwheel.sh
...

ERROR: An error occurred during the fetch of repository ‘local_config_python’:

Problem getting numpy include path.

ModuleNotFoundError: No module named ‘numpy’

Hay que hacer dos cosas, por un lado es un problema con la versión de Python, por defecto la versión de Python es la 3.5 cuando tendría que ser la 3.7 y además también hay que instalar numpy

Referencias

https://github.com/tensorflow/tensorflow/issues/26947
http://www.itworkman.com/97618.html
https://devblogs.microsoft.com/commandline/a-guide-to-invoking-wsl/
https://github.com/tensorflow/tensorflow/issues/39340

Compilando Tensorflow 2 para Raspberry Pi

Jupyter notebook en Raspberry Pi con Docker

Actualizado el 31 de Octubre de 2021

Docker

Introducción

Últimamente he estado usando Azure notebooks para mis notas en Jupyter pero parece que finalmente Microsoft ha decidido abandonarlo y da varias alternativas para la migración, pero ninguna me ha terminado de convencer, ya sea por estar atado a otro componente de Azure o por ser de pago, así que decidí incluir Jupyter dentro de una de mis Raspberry.

Uno de los problemas es la mala compatibilidad de Jupyter con Git, apenas existen extensiones que sean fiables así que decidí usar JupyterLab con su extensión de Git que es bastante completa.

Finalmente continuando con lo que parece que será la senda a seguir en un futuro cercano he optado a hacerlo en Docker, principalmente porque así lo puedo mover cómodamente entre mis Raspberry sin tener que instalarlo todo de nuevo (bueno, para lo que se supone que es Docker).

Estos son los pasos a seguir, en este caso use una Raspberry Pi 3 con 1 GB de RAM para comprobar el rendimiento, aunque Docker tiene más sentido en una Raspberry Pi 4 con al menos 4GB de RAM.

Configuración del sistema

Es recomendable leer este enlace antes de seguir, da instrucciones e ideas para instalar Docker.

DockerFile

Este es el fichero Dockerfile necesario para crear la imagen, vamos a usar como imagen base la versión ARM de Debian, posiblemente con Alpine el tamaño de la imagen sería menor (actualmente 1.2GB) pero puede haber algunos problemas de rendimiento.

# 0. Image and labels
FROM debian:latest
LABEL "guru.raraavis.creator"="blog@raraavis.guru"
LABEL "guru.raraavis.version"="1.1.0"
LABEL "guru.raraavis.release-date"="31/10/2021"
LABEL "guru.raraavis.description"="Raspberry Pi image with JupyterLab"

# 1. Environment vars
ARG DEBIAN_FRONTEND=noninteractive
ARG NODE_OPTIONS=--max-old-space-size=768
ARG TZ=Europe/Madrid
ENV NODE_VERSION=16
ENV JUPYTER_ID=1000
ENV TZ=$TZ

# 2. Install packages
# 2.1 Update system and install necessary packages
RUN     apt-get update && \
        apt-get install -y --no-install-recommends \
        python3-dev \
        python3-venv \
        libatlas-base-dev \
        libblas-dev \
        liblapack-dev \
        python-dev \
        gfortran \
        tzdata \
        software-properties-common \
        sudo \
        gosu \
        bzip2 \
        git \
        dumb-init \
        curl && \
        curl -sL https://deb.nodesource.com/setup_$NODE_VERSION.x | bash - && \
        apt-get install -y --no-install-recommends nodejs && \
        rm -rf /var/lib/apt/lists/* && \
        rm -rf /tmp/*
# 2.2 Install pip
ADD     https://bootstrap.pypa.io/get-pip.py get-pip.py
RUN     python3 get-pip.py
RUN     python3 -m pip config --global set global.extra-index-url https://www.piwheels.org/simple
# 2.3 Install pip packages and Jupyter
RUN     python3 -m pip install --upgrade \
        conda \
        virtualenv \
        ipykernel \
        jupyter \
        jupyterlab \
        jupyterlab-git \
        jupyter_contrib_nbextensions \
        autopep8

RUN     jupyter lab build
RUN     jupyter contrib nbextension install --system

# 3. Add jupyter user
RUN     adduser --uid $JUPYTER_ID --disabled-password --gecos '' jupyter
RUN     adduser jupyter sudo
RUN     echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers

# 4. Install Miniconda
WORKDIR /home/jupyter
ADD --chown=jupyter     http://repo.continuum.io/miniconda/Miniconda3-latest-Linux-armv7l.sh Miniconda3.sh
RUN     chmod 755 Miniconda3.sh
RUN     md5sum Miniconda3.sh
RUN     ./Miniconda3.sh -b -p /home/jupyter/miniconda3
RUN     ./miniconda3/bin/conda config --add channels rpi
RUN     rm ./Miniconda3.sh

# 5. Configure container
RUN     mkdir -p /home/jupyter/notebooks
RUN     chown -R jupyter:jupyter /home/jupyter
ONBUILD SHELL   ["/bin/bash","-c"]
VOLUME /home/jupyter/notebooks
EXPOSE 8888
USER jupyter

# 6. Execute jupyter
CMD ["jupyter", "lab", "--port=8888", "--no-browser", "--ip=0.0.0.0", "--allow-root", "--notebook-dir=/mnt/jupyter/notebooks", "--ServerApp.token=", "--ServerApp.password="]

Variables de entorno (1)

El paquete tzdata cuando se instala necesita especificar una zona horaria, así que establecemos la variable de entorno TZ, si se establece en tiempo de ejecución indicará la zona horaria a usar para configurar el sistema, se puede volver a configurar usando dpkg-reconfigure, tzdata también necesitará establecer DEBIAN_FRONTEND=noninteractive para evitar preguntas durante la instalación.
Enlazamos ARG con ENV, ya que ARG solo se puede usar en tiempo de compilación la enlazamos con ENV para que se pueda establecer también en tiempo de ejecución.
NODE_OPTIONS se usará más adelante para establecer parámetros en NodeJs, principalmente en el uso de la memoria, ya que a no ser que la Raspberry tenga más de 2GB fallará durante la creación de la extensión de Git, para una RPI4 se podría eliminar o ampliar este parámetro.
Existe también la opción de parametrizar que versión de Node se quiere instalar -necesario para JupyterLab- por defecto la 16 que es la última disponible actualmente.
La variable JUPYTER_ID se usa para indicar que id de usuario tendrá el usuario Jupyter que luego crearemos, esto es muy útil para poder establecer permisos en el host y evitar algunos problemas.

Instalar paquetes (2)

El proceso de instalación consta de un solo comando RUN para evitar crear capas intermedias. También crea e instala la extensión de Git, hay que tener en cuenta que no existe una forma oficial de generar la extensión y luego instalarla, de ser así una opción habría sido crearla en un host y luego copiarla en otro.
También se incluyen varias librerías de sistema, muchas de ellas son necesarias para instalar ciertos paquetes Python (principalmente librerías científicas) así que es bueno tenerlas ya instaladas.

Actualizar el sistema e instalar Jupyter (2.1)

Los comandos básicos para actualizar el sistema e instalar Jupyter con sus dependencias, se usa –no-install-recommends y finalmente se elimina de la cache los paquetes para aligerar el tamaño del fichero Docker.

Instalar pip (2.2)

Aquí descargamos e instalamos pip, también añadimos el repositorio de paquetes Python piwheels, este repositorio contiene muchos de los paquetes Python preparados para Raspberry lo que ahorra mucho tiempo de compilación e instalación, también indicamos que este repositorio sea global para que cualquier usuario o entorno virtual se beneficie del mismo.

Instalar librerías Python y Jupyter (2.3)

Aquí se instalan todos los paquetes Python necesarios para JupyterLab incluyendo algunos que son útiles -como virtualenv- extensiones a JupyterLab -como la propia de Git– y finalmente el propio Jupyterlab, después solo hay que llamar al comando build. Aprovechamos también para instalar algunas extensiones útiles para JupyterLab.

Agregar usuario Jupyter (3)

Ahora creamos el usuario jupyter que usaremos a partir de ahora, lo configuramos sudo y configuramos la carpeta de trabajo.

Instalar Miniconda (4)

En este paso vamos a instalar Miniconda para el usuario Jupyter, se hace una descarga e instalación desatendida dentro de la carpeta que hemos creado previamente. Miniconda instala principalmente el gestor de paquetes Conda y el resto de paquetes habría que instalarlos por separado, hay que tener en cuenta que el soporte de Conda para Raspberry no es muy bueno.
Finalmente se agrega el canal (repositorio) de rpi a conda para poder descargar paquetes orientados a Raspberry Pi (similar a como hemos hecho antes para pip).
Al instalar Conda de manera desatendida no se incluyen los binarios de Conda en el PATH por lo tanto para usar los binarios de conda hay que ubicarse en la carpeta bin de la carpeta miniconda como se ve al usar el comando conda config, esta opción la he preferido para no tener que usar la versión de Python de Conda que generalmente es inferior a la instalada en el sistema.

Contenedor (5)

Establecemos una carpeta de trabajo donde crearemos los notebooks y la usaremos también para clonar repositorios, establecemos los permisos adecuados para el usuario Jupyter.

También usamos ONBUILD para indicar que las imágenes que hereden de esta usarán bash como shell, esto es importante porque sino algunos comandos como source muy usados para crear entornos virtuales no estarán disponibles.

Añadimos la configuración de los puertos y la carpeta que se usará como volumen para mapear con el host que es donde residen los cuadernos.

Ejecutar Jupyter

Establecemos el comando que arrancará Jupyter, algunas consideraciones:

  • –no-browser: para evitar abrir el navegador al arrancar.
  • –allow-root: permite ejecutar Jupyter como root.
  • –notebook-dir: carpeta raíz de Jupyter, es decir de la que leerá los ficheros, que a su vez es la misma que usamos para Git.
  • –NotebookApp.token y NotebookApp.password lo establecemos con cadenas vacías para indicar que no queremos usar autenticación, esto para un entorno particular como suele ser el de una Raspberry es muy adecuado, en entornos compartidos quizás tenga más sentido no usar estos valores y establecerlos a través del archivo de configuración.

Compilación

Ejecutamos con el siguiente comando para compilar, previamente hacemos una limpieza para evitar posibles errores (errores GPG con las claves de los orígenes de los paquetes por ejemplo). Es posible que de algún error al compilar por falta de memoría, quitar algunos servicios o programas activos para ganar memoria y reintentar puede ayudar.

docker image prune -f
docker image build --tag user/image_name .

Ejecución

Una vez terminada la compilación con el siguiente comando arrancamos la imagen:

docker container run --init --publish 8888:8888 --detach --volume /home/pi/jupyter:/home/jupyter/notebooks user/image_name

Estamos mapeando el puerto 8888 al mismo en puerto en el host y también estamos mapeando la carpeta en Rasperry como un volumen en el host en la carpeta home del usuario pi, en id_imagen indicamos el id de la imagen a ejecutar. El parámetro –init es un valor recomendado para Jupyter que le proporciona más estabilidad.
Se pueden establecer también las variables de entorno con –env para JUPYTER_ID, NODE_VERSION o TZ o se pueden dejar los valores por defecto.

Push & Pull

Para subir la imagen solo tenemos que ejecutar el siguiente comando e introducir nuestras credenciales de Docker.

docker login

Para subir la imagen usamos la información del repositorio que hemos creado, normalmente usamos como user/name lo mismo que hayamos especificado en el tag que hemos creado con el comando docker build.

docker push user/image_name

Y para descargarlo en otra máquina simplemente hacemos:

docker pull user/image_name

Aunque si lo invocamos directamente con docker run tendrá el mismo efecto.

Configurar Git

Si arrancamos un navegador y vamos a la dirección de la Raspberry podremos ver el entorno de JupyterLab, vamos a configurar Git desde aquí.

La mayoría de las tareas básicas se pueden hacer desde los menús pero para comandos más avanzados podemos abrir un terminal y lanzar comandos desde ahí o para verificar que todo ha sido correcto, por ejemplo vamos a configurar Git con nuestro usuario e email, (nota, pegar se realiza con mayus+Insert).

git config user.email "coyote@acme.com"
git config user.name "El Coyote"

Para evitar que nos pregunte usuario y contraseña para los commit vamos a configurarlo también, desde el terminal hacemos:

git config credential.helper store
git pull

Introducimos el usuario y contraseña cuando lo pregunte, con esto quedarán almacenadas y no lo preguntará para el resto de acciones que hagamos desde la consola o desde la interfaz.

Ahora para clonar el repositorio usamos la interfaz, desde el menú Git -> Clone a Repository introducimos la URL del repo, nos preguntará por unas credenciales que tendremos que tener previamente configuradas en el servidor Git.

Esto nos descargará el código en la carpeta que hemos indicado, ahora podemos probar a crear cualquier archivo y realizar un commit, en la parte de la izquierda pulsemos el icono de Git y veremos los archivos modificados, seleccionamos los que queremos subir y en la parte inferior escribimos un resumen y una descripción opcional, a continuación pulsamos Commit, como ya hemos configurado el nombre y el correo en Git el commit no dará ningún error, sino que saldrá una ventana pidiendo esos datos.

Para hacer Push hay dos formas, se puede hacer directamente desde esta misma ventana usando el icono o desde el menú Git -> Push to remote.

Desde esta misma ventana también podemos cambiar de ramas o ver el historial.

(Opcional) Proxy Apache

Podemos usar Apache como proxy para acceder y así configurar estas URL por algo más fácil de recordar, para ello en Apache creamos un virtual host que nos haga de proxy con el servidor de JupyterLab, creamos un fichero en la carpeta /etc/apache2/sites-available/005-jupyterlab.conf con el siguiente contenido, obviamente deberemos tener un sistema DNS que nos resuelva correctamente el nombre jupyterlab, vamos a suponer que el servidor está en la dirección 192.168.1.4

Tenemos que habilitar algunos módulos, para permitir que funcionen los websockets y cors.

a2enmod headers
a2enmod proxy
a2enmod proxy_http
a2enmod proxy_wstunnel
a2enmod headers

Y ahora configuramos el servidor virtual

<VirtualHost spotify:80>
  ServerName jupyterlab.domain
  SererAlias jupyterlab
  Header set Access-Control-Allow-Origin "*"
  ProxyPreserveHost On
  ProxyPass / http://192.168.1.4:8888/
  ProxyPassReverse / http://192.168.1.4:8888/
  <Location "terminals/websocket">
      ProxyPass "ws://192.168.1.4:8888/terminals/websocket"
  </Location>
  <Location "/api/kernels/">
      ProxyPass "ws://192.168.1.4:8888/api/kernels/"
  </Location>
  CustomLog /var/log/apache2/jupyterlab.log combined
  ErrorLog /var/log/apache2/jupyterlab.error.log
</VirtualHost>

Para habilitar el sitio ejecutamos:

sudo a2ensite 005-jupyterlab.conf

Referencias

Configurar tzdata en Docker
Jupyter Notebook Dockerfile

Jupyter notebook en Raspberry Pi con Docker

Azure kubernetes

Pasos rápidos para crear un servicio kubernetes

  1. Creamos un recurso kubernetes service, a tener en cuenta
    1. Crear un grupo de recursos propios, facilita la gestión y la limpieza (usaremos kubernetes)
    2. Elegimos un nombre, se usará también como DNS (usaremos dummy)
    3. Elegir el tamaño de nodo adecuado, con la restricción de que tenga al menos 2 cores y 4GB de RAM (B2s estándar)
    4. Elegir el número de nodos
  2. Usamos la herramienta Azure CLI
    1. az login
      Logearse dentro de la plataforma
    2. az aks get-credentials –resource-group kubernetes –name dummy
      Actualiza el contexto kubernetes y se conecta al cluster, usando la herramienta de docker desktop se pueden ver todos los contextos de kubernetes activos.
    3. Se puede verificar usando el comando kubectl get nodes donde se pueden ver los nodos que habían sido creados
  3. A partir de este punto se pueden usar los comandos de kubernetes necesarios para desplegar como kubectl create

Creación de disco

Es muy común que se quiera crear un volumen de datos asociado a un cluster kubernetes, un ejemplo sería este:

kind: StorageClass 
apiVersion: storage.k8s.io/v1beta1
metadata: 
    name: azure-disk
provisioner: kubernetes.io/azure-disk 
parameters: 
    storageaccounttype: Standard_LRS 
    kind: Managed

En este caso no hace falta crear un volumen persistente porque Azure Disks soporta aprovisionamiento dinámico. Este es un ejemplo para una base de datos, hay que establecer la contraseña con:
kubectl create secret generic
mssql –from-literal=SA_PASSWORD=»Password_123″

apiVersion: v1 
kind: PersistentVolumeClaim 
metadata: 
    name: azure-volume-claim 
spec: 
    storageClassName: azure-disk 
    accessModes: 
        - ReadWriteOnce 
    resources: 
        requests: 
            storage: 8Gi
spec: 
    containers: 
        - name: myapp-database 
          image: mcr.microsoft.com/mssql/server 
          ports: 
              - containerPort: 1433 
          env: 
              - name: "ACCEPT_EULA" 
                value: "Y" 
              - name: "SA_PASSWORD" 
                valueFrom: 
                    secretKeyRef: 
                        name: mssql 
                        key: SA_PASSWORD 
              - name: "MSSQL_PID"
                value: "Express" 
          volumeMounts: 
              - name: mssqldb 
                mountPath: /var/opt/mssql 
    volumes: 
    - name: mssqldb 
          persistentVolumeClaim: 
              claimName: azure-volume-claim

Podemos crear también un balanceador para acceder a la base de datos.

apiVersion: v1 
kind: Service 
metadata: 
    name: myapp-db-svc 
spec: 
    selector: 
        app: myapp-db 
    ports: 
        - protocol: TCP 
          port: 1433 
          targetPort: 1433 
    type: LoadBalancer

Este script habilitará una IP externa que se puede usar para acceder a la base de datos, cuando no se necesite acceder se puede eliminar este balanceador.

Azure kubernetes