OpenWrt como host de contenedor Docker
OpenWrt puede ser un host Docker en x86-64, Aarch64 y otras arquitecturas compatibles.
Hay dos formas de utilizar Docker como host: instalar Docker Community Edition o utilizar herramientas nativas OpenWrt que admitan la especificación del contenedor Docker.
Probablemente, primero necesitará configurar el almacenamiento como lugar para almacenar los contenedores y los datos.
Además, en la mayoría de los casos ejecutará el contenedor como un usuario específico y le dará acceso a alguna carpeta fuera del contenedor, donde podrá almacenar su configuración y los datos. Por lo tanto, probablemente necesitará crear nuevos usuarios y grupos para aplicaciones o servicios del sistema, crear las carpetas para la configuración y los datos, y luego cambiar el propietario de estas carpetas al usuario con el que ejecutará el contenedor.
Instalar Docker Community Edition
- Instala el paquete docker-ce para herramientas de línea de comandos
- Instala el paquete luci-app-dockerman para obtener un panel de control para contenedores en Luci
La carpeta predeterminada para Docker en la interfaz Dockerman de Luci es /opt/docker, por lo que es deseable montar su almacenamiento en /opt o cambiar la carpeta en Docker > Overview > Docker Root Dir y luego reiniciar el servicio Dockerd.
Añadir una imagen
Para agregar una imagen, búsquela en Docker Hub y luego copie el nombre de imagen desde el cuadro de texto Docker Pull Command. Por ejemplo, si el texto es docker pull linuxserver/transmission, copie linuxserver/transmission.
En Luci, vaya a Docker > Images y pegue ese texto en el cuadro Pull Image, luego haga clic en Pull. La página mostrará el progreso de la descarga.
Para extracciones de contenedores más largas, Luci puede agotar el tiempo de espera, por lo que deberá usar la línea de comando. Por ejemplo, las imágenes del controlador unifi incluyen el entorno de ejecución de Java y se acercan a los 500 MB, por lo que puede acceder mediante SSH e ingresar: docker pull linuxserver/unifi-controller.
Luego, en Luci, vaya a Docker > Containers > Add. En la página del nuevo contenedor, seleccione la imagen de docker en el menú Docker Image, y luego configure todos los demás parámetros (normalmente los parámetros disponibles/útiles se describen en la descripción del contenedor en Docker Hub), luego presione Enviar (Submit) para crear el contenedor.
Configurar el demonio del motor Docker CE
La configuración está ubicada en /etc/config/dockerd
.
data_root
es una carpeta donde almacenar imágenes y contenedores. Esta también es montada por un docker. Puede que desee cambiarla a un disco USB. Su sistema de archivos no puede ser fat o ntfs. De manera predeterminada es/opt/docker/
log_level
predeterminadowarn
.hosts
es un detector de API. De manera predeterminada se utiliza un socket UNIX/var/run/docker.sock
.iptables
Habilita las reglas de iptables. Predeterminado1
bip
puente de red IP. Predeterminado172.18.0.1/24
fixed_cidr
Asigna IPs desde un rango. Predeterminado172.17.0.0/16
fixed_cidr_v6
igual que fixed_cidr para IPv6. Predeterminado 'fc00:1::/80'ipv6
Habilita redes IPv6. Predeterminado1
ip
Predeterminado::ffff:0.0.0.0
dns
Servidores DNS. Predeterminado172.17.0.1
registry_mirrors
URL de un registro. Predeterminadohttps://hub.docker.com
Las siguientes configuraciones requieren reiniciar (restart) docker para que surta efecto completo. Una recarga (reload) solo tendrá efecto parcial o ningún efecto:
- bip
- blocked_interfaces
- extra_iptables_args
- device
Usar herramientas nativas OpenWrt
El sistema Procd init ahora admite la especificación de tiempo de ejecución de Open Container Initiative, ampliando su capacidad de contenedores delgados (“ujail”).
La herramienta de línea de comandos uxc maneja las operaciones básicas en contenedores según lo definido por la especificación.
Esto permite usarlo como reemplazo directo del 'runc' (o 'crun') de Docker en hosts OpenWrt con un espacio significativamente reducido.
Información detallada pero posiblemente desactualizada disponible en https://gitlab.com/prpl-foundation/prplos/prplos/-/wikis/uxc
instalar paquetes
Para 20.0x instale lo siguiente:
opkg install kmod-veth uxc ujail-console
Para nuevas instantáneas:
opkg install kmod-veth uxc procd-ujail procd-ujail-console
crear par veth (virtual ethernet) para el contenedor
uci batch <<EOF set network.veth0=device set network.veth0.type='veth' set network.veth0.name='vhost0' set network.veth0.peer_name='virt0' add_list network.lan.ifname='vhost0' set network.virt0=interface set network.virt0.ifname='virt0' set network.virt0.proto='none' # set proto='none' assuming DHCP client inside container # use 'static' otherwise and also set ipaddr, gateway and dns set network.virt0.jail='container1' set network.virt0.jail_ifname='host0' commit network EOF
crear un paquete de tiempo de ejecución de OCI
Para crear un paquete de tiempo de ejecución de OCI, necesario para uxc, siga estos pasos.
Primero compile una imagen de contenedor.
docker build -t container1 .
Tenga en cuenta la identificación de la imagen que se imprime al final y úsela después de @ en el siguiente comando.
skopeo copy containers-storage:[overlay@$HOME/.local/share/containers/storage+/run/user/1000/containers]@b0897a4ee285938413663f4c7b2b06d21e45c4358cebb04093ac9de9de118bf2 oci:container1:latest sudo umoci unpack --image container1 container1-bundle sudo rsync -aH container1-bundle root@192.168.0.1:/mnt/sda3/debian
Esto es bastante engorroso. Si alguien conoce una manera mejor, actualice esta página.
importar un contenedor de tiempo de ejecución OCI
(asumiendo el paquete de tiempo de ejecución OCI con config.json en /mnt/sda3/debian)
uxc create container1 /mnt/sda3/debian true uxc start container1 uxc list uxc state container
Si el contenedor usa una consola stdio, puede conectarla usando
ujail-console -c container1
(no hay búfer, por lo que si desea ver el registro de inicio completo de un contenedor, asegúrese de conectar una consola después de la llamada 'create' pero antes de iniciarlo)
Podman
https://podman.io/ es otra alternativa a Docker y es compatible con los comandos del cliente Docker. A continuación se muestra un ejemplo de configuración utilizando podman para crear un contenedor de servidor web con proxy.
Instalar paquetes necesarios:
opkg install conmon crun catatonit netavark podman external-protocol
Si desea utilizar contenedores 'rootless', necesita paquetes adicionales, como slirp4netns. También hay un servidor DNS autoritario para netavark disponible como paquete aardvark-dns. Esta guía excluye su configuración actual.
Red
Comencemos revisando la configuración de nuestra red de contenedores. /etc/containers/networks/podman.json:
{ "name": "podman", "id": "5ef894788befd4d42498314b6e66282ca730aa2e1e82f9b9597bf4d1725ca074", "driver": "bridge", "network_interface": "podman0", "created": "2023-02-20T08:56:34.652030952Z", "subnets": [ { "subnet": "10.129.0.0/24", "gateway": "10.129.0.1" } ], "ipv6_enabled": false, "internal": false, "dns_enabled": true, "ipam_options": { "driver": "host-local" } }
Tengo una red bastante grande (10.0.0.0/9), por eso la subred rara: puede elegir la suya propia, pero este es un ejemplo de configuración basado en mi configuración. En este archivo usted define su red llamada podman. Puede tener múltiples redes.
Cortafuegos/Zona
A continuación, nos aseguramos de que el reenvío de puertos internos/cortafuegos de podman esté deshabilitado, verifique /etc/config/netavark:
config firewall option driver 'none'
Hacemos esto para usar el propio cortafuegos de openwrt, ya que cuando se usa podman/netavark, las reglas se pierden cada vez que se recarga el cortafuegos, incluso cuando inicia su primera red de contenedores, cuando aparece la interfaz podman0.
A continuación es momento de configurar la red y el cortafuegos en el lado de openwrt, agregue esto a /etc/config/network:
config interface 'podman' option proto 'external' option device 'podman0'
Y también queremos usar cortafuegos, en esta configuración permitimos el acceso desde lan a podman, pero no al revés, también otorgamos acceso desde wan y hacia wan. /etc/config/firewall:
config zone option name 'podman' option input 'REJECT' option output 'ACCEPT' option forward 'ACCEPT' option mtu_fix '1' list network 'podman' config forwarding option src 'lan' option dest 'podman' config forwarding option src 'podman' option dest 'wan' config forwarding option src 'wan' option dest 'podman'
Ahora que hemos bloqueado el acceso a la LAN, a nuestros contenedores les falta acceso al DNS, a menos que los configuremos para usar otra cosa, como 8.8.8.8; por lo tanto, hacemos una excepción, los contenedores pueden conectarse a la LAN, pero solo en el puerto 53 ( DNS):
config rule option name 'Allow-Podman-DNS' option src 'podman' option dest_port '53' option target 'ACCEPT'
Ahora la configuración inicial de la red está completa.
Servicio
A continuación, nos aseguramos de que el servicio podman se inicie al arrancar. Esto es opcional, pero si desea seguir esta guía, le resultará útil más adelante. El servicio Podman no crea/inicia/etc. ningún contenedor, solo inicia el servicio en segundo plano y crea un socket Unix utilizado para la comunicación. Este socket está ubicado en /var/run/podman/podman.sock; este socket acepta comunicación similar/compatible como Docker. Un paquete llamado podman-docker-compatibility generalmente solo contiene un enlace a este socket a la ruta estándar del socket de Docker y un script contenedor de Docker que reenvía su línea de comando al comando Podman. En realidad, el servicio se inicia cuando usa podman, pero queremos asegurarnos de que aparezca como instancia de openwrt y, para una configuración más avanzada, esto es útil.
/etc/init.d/podman enable
Después de reiniciar, puede comenzar a usar la configuración de podman. La guía continúa, creamos un servidor web y un proxy caddy como proyectos de ejemplo y manejamos el reenvío del tráfico hacia ellos. Almacenaremos los datos de nuestro contenedor en /srv.
Almacenamiento del contenedor (opcional)
Tengo mucho espacio en disco disponible, así que configuro mi Graphroot en el disco duro, en lugar de almacenar datos del contenedor en la RAM, como /tmp o /var, que son los valores predeterminados. Primero haga un camino adecuado:
mkdir -p /srv/.podman/storage
Y edite su /etc/containers/storage.conf:
graphroot = "/srv/.podman/storage"
el valor predeterminado era: “/var/lib/containers/storage”
Sin configurar Graphroot, su configuración también funciona, es solo que tengo mucho espacio en disco en mis configuraciones, prefiero almacenarlas en el disco duro en lugar de en la memoria, como /tmp o /var.
Almacenamiento local de imagen (opcional)
Mi configuración compila mis contenedores en cada arranque y quiero acelerar ese proceso, por lo que quiero que las imágenes para el proxy Caddy y el servidor web nginx se almacenen en un almacenamiento permanente. Pero tenga cuidado, cada vez que desee realizar cambios en estas imágenes, deberá reiniciar este proceso completamente desde el principio, primero restableciendo la configuración additionalimagestores a su valor predeterminado, eliminar archivos de imágenes físicas y, por supuesto, antes de todo esto, debe eliminar los contenedores que usan estas imágenes, junto con las imágenes del propio sistema de Podman y, finalmente, extraiga nuevas imágenes y restaure la configuración additionalimagestores en la ruta de su imagen. Además, como el servicio de Podman carece de la función de parada, también son necesarios algunos reinicios. ¿Complicado? Sí, lo es, pero al final podría dar sus frutos, ya que no es necesario extraer las imágenes después de cada reinicio. Para empezar, reinicie su computadora y asegúrese de que no se creen, inicien o incluso existan contenedores, esto ayudará a evitar problemas.
Crear directorio /srv/.podman/images:
mkdir -p /srv/.podman/images
Luego revise su /etc/containers/storage.conf:
additionalimagestores = []
Si tiene que cambiar esta línea, debe reiniciar el servicio Podman para que esto surta efecto. Debería encontrarse la siguiente línea, es la configuración predeterminada. Con esta línea, no tenemos imágenes almacenadas localmente en almacén permanente. A continuación emita los siguientes comandos:
podman --root /srv/.podman/images images podman --root /srv/.podman/images pull docker.io/me/my_caddy_image:latest podman --root /srv/.podman/images pull docker.io/me/my_nginx_image:latest
reemplace las URL de imágenes con las suyas propias, el primer comando configura las imágenes de ruta como almacén de imágenes local y los siguientes comandos llevan sus imágenes al almacén de imágenes local. También puede extraer su imagen de pausa, por ejemplo: k8s.gcr.io/pause:3.5
Después de esto, realice cambios en su /etc/containers/storage.conf:
additionalimagestores = [ "/srv/.podman/images" ]
y reiniciar. Ahora, al crear contenedores que utilizan imágenes almacenadas localmente, no es necesario extraerlas de Internet, están disponibles al instante. Si utiliza imágenes que no están almacenadas localmente, funcionan bien; simplemente son extraídos de internet. Esta es una operación única; Los cambios, eliminaciones y adiciones de imágenes almacenadas localmente no se pueden modificar muy fácilmente. Para reiniciar el proceso; elimine contenedores usando imágenes almacenadas localmente, elimine estas imágenes de podman (podman image rm <imageid>), cambie sus additionalimagestores nuevamente a [], deshabilite el servicio podman y asegúrese de que el servicio podman no comience con la creación /inicio de contenedores durante el arranque. Luego elimine todos los archivos, de /srv/.podman/images:
rm -rf /srv/.podman/images
reinicie nuevamente y comience esto nuevamente desde el inicio de esta sección de la guía.
Pod Empezaremos creando un pod. El pod puede contener varios contenedores y comparten algunos atributos, como la dirección IP. Mientras intentamos crear una configuración de servidor web, queremos que la dirección IP sea siempre la misma para este pod. He creado un script /srv/create.sh para construir este pod:
#!/bin/sh podman pod create \ --replace \ --name servers \ --hostname srv \ --ip 10.129.0.2 podman pod start servers
Esto crea, o reemplaza, si existe, servidores con nombres de pod, le asigna un nombre de host srv (no importante) y una dirección IP estática 10.129.0.2.
Contenedores Todas las configuraciones y los datos exportados estáticamente también se encuentran en /srv. En /srv/caddy tengo todo lo necesario para construir mi contenedor caddy, como las configuraciones y cualquier contenedor caddy que necesite. También tengo un script de compilación allí, /srv/caddy/create.sh:
#!/bin/sh podman create \ --name caddy \ --pod servers \ --replace \ --systemd false \ --label app=caddy \ --volume /srv/caddy/conf/:/etc/caddy/:Z,rw \ --volume /srv/caddy/htdocs/:/var/htdocs/:z,rw \ --volume /srv/caddy/logs/:/var/log/:z,rw \ --volume /dev/log:/dev/log:Z,rw \ --mount="type=bind,src=/etc/acme/domain.tld_ecc/domain.tld.cer,dst=/etc/caddy/ssl/server.pem,ro=true,idmap=uids=0-82-1;gids=0-82-1" \ --mount="type=bind,src=/etc/acme/domain.tld_ecc/domain.tld.key,dst=/etc/caddy/ssl/server.key,ro=true,idmap=uids=0-82-1;gids=0-82-1" \ docker.io/me/my_caddy_image:latest podman start caddy
En esta guía no reviso la configuración de Caddy, la busco en los documentos de Caddy. Mi caddy está configurado para ejecutarse como usuario www:www-data, que en esa configuración son uid 82 y gid 82, acme se usa para obtener certificados, pero el usuario www(82) no puede leer archivos de propiedad raíz, por lo que usamos idmapping para asignar estos 2 archivos para el usuario www:www-data. Hay varias formas de hacer esto, este es solo un enfoque. También puede configurar un sistema que modifique esos archivos para que estén disponibles para que todos los lean, o al menos para el usuario y/o el grupo 82. O cópielos localmente y cópielos estáticamente en esa ubicación.
Y tengo un script similar para nginx:
#!/bin/sh podman create \ --name nginx \ --pod servers \ --replace \ --systemd false \ --label app=nginx \ --volume /srv/nginx/conf/:/etc/nginx/:Z,rw \ --volume /srv/nginx/logs/:/var/log/nginx/:Z,rw \ --volume /srv/nginx/htdocs/:/var/htdocs/:z,rw \ --volume /dev/log:/dev/log:Z,rw \ docker.io/me/my_nginx_image:latest podman start nginx
Ahora, después de haber configurado correctamente su caddy y nginx, deberíamos tener un servidor funcionando correctamente. Necesitamos configurar redirecciones desde wan.
Exponer a wan
Ahora que tenemos caddy sirviendo en 10.129.0.2, puertos 80 y 443, editamos /etc/config/firewall nuevamente:
config redirect option name 'Allow-HTTP' option src 'wan' option dest 'podman' option src_dport '80' option dest_ip '10.129.0.2' option dest_port '80' option proto 'tcp' option reflection '0' option target 'DNAT' option enabled '1' config redirect option name 'Allow-HTTPS' option src 'wan' option dest 'podman' option src_dport '443' option dest_ip '10.129.0.2' option dest_port '443' option proto 'tcp' option reflection '0' option target 'DNAT' option enabled '1'
Automatización
Finalmente, queremos que nuestro pod y contenedores se construyan y se inicien durante el arranque, también tenemos acme manejando nuestros certificados, por lo que queremos reiniciar caddy cuando se renuevan los certificados.
Agregué el directorio /srv/scripts y agregué allí el archivo restart_caddy.sh:
#!/bin/sh /etc/init.d/podman enabled || exit logger -t acme -p daemon.info "SSL certificates renewed, restarting container servers:caddy" podman stop caddy sleep 1 podman start caddy
Y luego el resto lo maneja /etc/rc.local:
# Put your custom commands here that should be executed once # the system init finished. By default this file does nothing. add_podman_trigger() { local counter=10 local running=0 [ -x "/etc/init.d/acme" ] || exit /etc/init.d/acme enabled || exit while [ "$counter" -gt 0 ]; do [ "$(service podman status)" = "running" ] && { running=1 counter=0 } || { sleep 1 counter=$(($counter-1)) } done [ "$running" -eq 1 ] && { ubus call service set '{ "name": "podman", "triggers": [[ "acme.renew", [[ "run_script", "/srv/scripts/restart_caddy.sh" ]], 2000 ]], "data": {}}' logger -t podman -p daemon.info "podman: added service trigger for acme.renew event to restart servers:caddy" } } start_podman_services() { /etc/init.d/podman enabled && { [ -f /tmp/.podman_created ] || { touch /tmp/.podman_created sleep 1 /srv/create.sh sleep 2 /srv/caddy/create.sh sleep 2 /srv/nginx/create.sh add_podman_trigger & } } }
Es por eso que iniciar el servicio podman con /etc/init.d/podman resulta útil; podemos ignorar todos los contenedores relacionados durante el arranque, simplemente deshabilitando el servicio, ya que no se inicia nada relacionado con podman si el servicio está deshabilitado. Esto construye nuestro pod y ambos contenedores y luego agrega un activador para que el servicio podman reinicie caddy cuando se renuevan los certificados SSL. Hay una rutina que verifica si el servicio podman se ha iniciado, porque el disparador debe agregarse DESPUÉS de que se haya iniciado el servicio podman.