En este apunte describo como instalar Alpine Linux en una máquina virtual en mi servidor QEMU/KVM y cómo instalar Docker en ella. Necesitaba, para pruebas de concepto y servicios caseros, poder instalar contenedores sobre un servidor con Docker que ocupase “poquísimo”. ¿Se puede instalar un Host Docker encima de una Máquina Virtual?. La respuesta es un sí rotundo, de hecho es un lugar excelente para hacerlo, sobre todo en entornos de laboratorio, caseros, pequeños despliegues.
Introducción
Necesitaba montar microservicios encima de Docker (hacía tiempo que no (jugaba con Docker) dudé entre añadir Docker a mi servidor donde tengo KVM, dedicar un PC antiguo a Docker o pensar en algo más creativo…
Al final decidí optar por la tercera opción: montar máquinas virtuales dedicadas a contenedores Docker corriendo en mi servidor KVM. Mi potente y pequeño servidor Meerkat de System76 es donde tengo todas mis VM’s, pues bien, algunas de ellas van a soportar microservicio encima de Docker.
La segunda opción (añadir Docker a mi servidor donde tengo KVM) hubiese sido un infierno con el networking (openvswitch + docker switches + iptables), así que opté por aislar y contener problemas/troubleshooting de Docker en VM’s dedicadas. Así que mi servidor de VM’s queda como sigue:
- Hardware: Meerkat de System76
- Software: Pop!_OS, Ubuntu Server LTS.
- Networking: Open vSwtich
- QEMU/KVM con Hypervisor
- Varios Guest’s con máquinas virtuales corriendo Linux (Ubuntu Server LTS) y servicios.
- Varios Guest’s appliances como Umbrella o vWLC de Cisco.
- Varios Guest’s con máquinas virtuales corriendo Alpine Linux con Docker y contenedores para servicios (git, nodered, …).
¿Donde ejecutar Docker?
Lo primero que necesitaba decidir es sobre qué SO iba a montar Docker, teniendo en cuenta que:
- Voy a correr Docker dentro de una Máquina Virtual en mi Servidor QEMU/KVM.
- El Sistema Operativo
Guest
solo va a tener Docker, no necesito una distribución enorme. - Busco algo pequeño, fácil de mantener y robusto
Entre las diferentes opciones que he visto por ahí he optado por Alpine Linux
Máquina virtual con Alpine Linux
Veamos un ejemplo creando una una VM l
- Descargo Alpine Linux desde Downloads > VIRTUAL > Slimmed down kernel. Optimized for virtual systems, x86_64 (solo 52MB), es la versión más compacta posible.
luis@sol:~/kvm/base$ wget https://dl-cdn.alpinelinux.org/alpine/v3.15/releases/x86_64/alpine-virt-3.15.3-x86_64.iso
luis@sol:~/kvm/base$ wget https://dl-cdn.alpinelinux.org/alpine/v3.15/releases/x86_64/alpine-virt-3.15.3-x86_64.iso.sha256
luis@sol:~/kvm/base$ sha256sum -c alpine-virt-3.15.3-x86_64.iso.sha256
alpine-virt-3.15.3-x86_64.iso: La suma coincide
- Creo un puerto
estático
en mi switch virtual (más info aquí: Open vSwitch y KVM).
luis@sol:~/kvm/base$ sudo ovs-vsctl list-br
solbr
luis@sol:~/kvm/base$ sudo ovs-vsctl list-ports solbr
eth0
:
v100vnet12 (miro cual es el último que tenía dado de alta)
:
luis@sol:~$ sudo ovs-vsctl add-port solbr v100vnet13 tag=100 -- set Interface v100vnet13 type=internal
- Creo el directorio donde ubicaré el fichero de la máquina virtual
luis@sol:~/kvm$ mkdir docker
- Creo una máquina virtual desde
virt-manager
con 1GB de RAM, 1 CPU, disco de 4GB y una NIC virtio, usando la imagen:alpine-virt-3.15.3-x86_64.iso
, la llamodocker.tudominio.com
y en la configuración de red uso el interfaz que acabo de crearv100vnet13
.
luis@sol:~$ virt-manager

- Arranco la VM y entro en el setup de Alpine (más info en esta guía).
luis@sol:~/kvm/gitea-traefik-docker$ virsh console docker.tudominio.com
localhost login: root
Welcome to Alpine!
:
localhost:~#
:
# export SWAP_SIZE=0
# setup-alpine
Select keyboard layout: [none] es
Select variant (or 'abort'): es
Enter system hostname (fully qualified form, e.g. 'foo.example.org') [localhost] docker
Available interfaces are: eth0.
Which one do you want to initialize? (or '?' or 'done') [eth0]
Ip address for eth0? (or 'dhcp', 'none', '?') [dhcp] 192.168.100.225/24
Gateway? (or 'none') [none] 192.168.100.1
Do you want to do any manual network configuration? (y/n) [n] n
DNS domain name? (e.g 'bar.com') tudominio.com
DNS nameserver(s)? 192.168.100.224
Changing password for root
Which timezone are you in? ('?' for list) [UTC] Europe/Madrid
HTTP/FTP proxy URL? (e.g. 'http://proxy:8080', or 'none') [none]
Enter mirror number (1-71) or URL to add (or r/f/e/done) [1]
Which SSH server? ('openssh', 'dropbear' or 'none') [openssh]
Which disk(s) would you like to use? (or '?' for help or 'none') [none] vda
How would you like to use it? ('sys', 'data', 'crypt', 'lvm' or '?' for help) [?] sys
WARNING: Erase the above disk(s) and continue? (y/n) [n] y
Installation is complete. Please reboot.
docker:~# reboot
- Hago login como root e instalo unas cuantas herramientas útiles.
docker:~# apk add iproute2 nano tzdata
docker:~# cp /usr/share/zoneinfo/Europe/Madrid /etc/localtime
docker:~# echo "Europe/Madrid" > /etc/timezone
docker:~# apk del tzdata
- Creo mi usuario
luis
y configuro ssh
docker:~# addgroup -g 1000 luis
docker:~# adduser -h /home/luis -s /bin/ash -G luis --u 1000 luis
docker:~# adduser luis wheel
docker:~# su - luis
docker:~$
docker:~$ ssh-keygen -t rsa -b 2048 -C "luis@docker.tudominio.com"
:
docker:~$ exit
- Creo
authorized_keys
(apunte sobre SSH en linux) - Modifico SSH para que trabaje solo con clave pública/privada
docker:~$ su -
Password:
docker:~# cat /etc/ssh/sshd_config
# Config LuisPa
Port 22
PubkeyAuthentication yes
PasswordAuthentication no
AuthenticationMethods publickey
AllowAgentForwarding yes
AllowTcpForwarding yes
GatewayPorts yes
AddressFamily inet
PrintMotd no
Subsystem sftp /usr/lib64/misc/sftp-server
AcceptEnv LANG LC_*
docker:~# service sshd restart
- Creo el fichero
/etc/nanorc
(fuente aquí) para el editornano
- Acelero el tiempo de boot a unos 5 segundos
docker:~# cat /boot/extlinux.conf
# Generated by update-extlinux 6.04_pre1-r9
#DEFAULT menu.c32 # Comento esta línea
DEFAULT virt # Añadida, virt = nombre más abajo
PROMPT 0
MENU TITLE Alpine/Linux Boot Menu
MENU HIDDEN
MENU AUTOBOOT Alpine will be booted automatically in # seconds.
TIMEOUT 30
LABEL virt
MENU LABEL Linux virt
LINUX vmlinuz-virt
INITRD initramfs-virt
APPEND root=UUID=bff03f67-29ee-4525-96d9-3096a1799fc7 modules=sd-mod,usb-storage,ext4 quiet rootfstype=ext4
MENU SEPARATOR
Instalación de Docker y Docker Compose
- Habilito el Community repository
git:~# cat /etc/apk/repositories
#/media/cdrom/apks
http://dl-cdn.alpinelinux.org/alpine/v3.15/main
http://dl-cdn.alpinelinux.org/alpine/v3.15/community <== Descomento esta línea
- Añado la siguiente línea al fichero
/etc/sudoers
# User rules for luis
luis ALL=(ALL) NOPASSWD:ALL
- Creo un par de scripts de apoyo
nodered:~# cat > /usr/bin/e
#!/bin/ash
/usr/bin/nano "${*}"
nodered:~# chmod 755 /usr/bin/e
:
nodered:~# cat > /usr/bin/confcat
#!/bin/ash
# By LuisPa 1998
# confcat: quita las lineas con comentarios, muy util como sustituto
# a "cat" para ver contenido sin los comentarios.
#
grep -vh '^[[:space:]]*#' "$@" | grep -v '^//' | grep -v '^;' | grep -v '^$' | grep -v '^!' | grep -v '^--'
nodered:~# chmod 755 /usr/bin/confcat
:
nodered:~# cat > /usr/bin/s
#!/bin/ash
/usr/bin/sudo -i
nodered:~# chmod 755 /usr/bin/s
- Actualizo el sistema e instalo herramientas muy útiles además de docker y docker-compose
docker:~# apk update
docker:~# apk upgrade --available
docker:~# apk add bash-completion procps util-linux
docker:~# apk add readline findutils sed coreutils sudo
docker:~# apk add docker docker-bash-completion docker-compose docker-compose-bash-completion docker-cli-compose
docker:~# rc-update add docker boot
docker:~# service docker start
- Añado mi usuario al grupo docker y hago un reboot…
git:~# addgroup luis docker
git:~# reboot -f
- Pruebo Docker (con la última imagen de alpine, que ocupa poquísimo…)
docker:~$ docker pull alpine:latest
docker:~$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
alpine latest 76c8fb57b6fc 3 days ago 5.57MB
docker:~$ docker create -t -i --name myalpine alpine:latest
5f1fefa539848f9e0fe995bf2e9c426def69ca48bfacc51bdb509197939c041e
docker:~$ docker start myalpine
/ # exit
docker:~$ docker exec -it myalpine /bin/ash
docker:~$ docker stop myalpine
docker:~$ docker rm myalpine
- Que no te sorprenda que
docker stop myalpine
tarde un rato en pararse, aquí tienes la explicacion.