En este laboratorio voy a preparar varios sistemas Linux en Docker para utilizarlos posteriormente como nodos administrados mediante Ansible.
La idea es tener un entorno pequeño pero variado:
debian1 Debian 13 172.30.10.11
debian2 Debian 13 172.30.10.12
rocky1 Rocky Linux 9 172.30.10.21
rocky2 Rocky Linux 9 172.30.10.22
alpine1 Alpine 3.24 172.30.10.31
mysql1 MySQL 8.4 172.30.10.40
Todos los nodos estarán conectados a la red Docker ansible-lab, con acceso SSH mediante el usuario ansible.
%%{init: {
"theme": "base",
"themeVariables": {
"fontFamily": "sans-serif",
"fontSize": "13px",
"edgeLabelBackground": "#ffffff"
}
} }%%
flowchart TD
classDef host fill:#2b3e50,stroke:#1a252f,stroke-width:2px,color:#fff,rx:8,ry:8;
classDef net fill:#34495e,stroke:#2c3e50,stroke-width:2px,color:#fff,rx:8,ry:8;
classDef debian fill:#2980b9,stroke:#1f618d,stroke-width:2px,color:#fff,rx:8,ry:8;
classDef rocky fill:#16a085,stroke:#117a65,stroke-width:2px,color:#fff,rx:8,ry:8;
classDef alpine fill:#d35400,stroke:#ba4a00,stroke-width:2px,color:#fff,rx:8,ry:8;
classDef db fill:#8e44ad,stroke:#7d3c98,stroke-width:2px,color:#fff,rx:8,ry:8;
M["Manjaro<br/><b>Nodo de control</b>"]:::host
subgraph LAB ["Red Docker: ansible-lab (172.30.10.0/24)"]
NET["Docker Bridge<br/>172.30.10.1"]:::net
subgraph NODES ["Contenedores Gestionados"]
direction LR
subgraph DEB ["Debian 13"]
direction TB
D1["debian1<br/>172.30.10.11"]:::debian
D2["debian2<br/>172.30.10.12"]:::debian
end
subgraph RCK ["Rocky Linux 9"]
direction TB
R1["rocky1<br/>172.30.10.21"]:::rocky
R2["rocky2<br/>172.30.10.22"]:::rocky
end
subgraph ALP ["Alpine 3.24"]
direction TB
A1["alpine1<br/>172.30.10.31"]:::alpine
end
subgraph DAT ["Base de Datos"]
direction TB
DB["mysql1<br/>172.30.10.40"]:::db
end
end
end
M ==> NET
NET --- D1
NET --- D2
NET --- R1
NET --- R2
NET --- A1
NET --- DB
linkStyle default stroke:#7f8c8d,stroke-width:1.5px;
1. Creo la estructura del proyecto
Empiezo creando las carpetas:
mkdir -p ansible-docker-lab/{ssh,images/{debian,rocky,alpine,mysql}}
cd ansible-docker-lab
La estructura será:
ansible-docker-lab/
├── compose.yml
├── .env
├── ssh/
│ └── ansible_lab.pub
└── images/
├── debian/
│ └── Dockerfile.systemd
├── rocky/
│ └── Dockerfile.systemd
├── alpine/
│ └── Dockerfile
└── mysql/
├── Dockerfile
└── entrypoint-lab.sh
Creo los archivos:
touch compose.yml
touch .env
touch images/debian/Dockerfile.systemd
touch images/rocky/Dockerfile.systemd
touch images/alpine/Dockerfile
touch images/mysql/Dockerfile
touch images/mysql/entrypoint-lab.sh
2. Creo la clave SSH
Genero una clave específica para el laboratorio:
ssh-keygen -t ed25519 \
-f ~/.ssh/ansible_lab_ed25519 \
-C "ansible-docker-lab"
Copio solo la clave pública:
cp ~/.ssh/ansible_lab_ed25519.pub ssh/ansible_lab.pub
La clave privada se queda únicamente en mi equipo.
3. Creo el Dockerfile de Debian
Debian y Rocky van a ejecutar systemd dentro del contenedor. Esto permite gestionar servicios de una forma muy parecida a una máquina Linux normal.
Edito:
vim images/debian/Dockerfile.systemd
y creo una imagen basada en Debian 13.
Primero instalo los componentes principales:
FROM debian:trixie
ENV container=docker
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y \
systemd \
systemd-sysv \
dbus \
openssh-server \
sudo \
python3 \
python3-apt \
procps \
iproute2 \
iputils-ping \
curl \
vim && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
Después creo el usuario que utilizará Ansible:
RUN useradd --create-home --shell /bin/bash ansible && \
echo "ansible ALL=(ALL) NOPASSWD:ALL" \
> /etc/sudoers.d/ansible && \
chmod 0440 /etc/sudoers.d/ansible
Instalo la clave pública:
RUN mkdir -p /home/ansible/.ssh
COPY ssh/ansible_lab.pub /home/ansible/.ssh/authorized_keys
RUN chown -R ansible:ansible /home/ansible/.ssh && \
chmod 700 /home/ansible/.ssh && \
chmod 600 /home/ansible/.ssh/authorized_keys
Configuro SSH para usar autenticación por clave:
RUN printf '%s\n' \
'PermitRootLogin no' \
'PasswordAuthentication no' \
'PubkeyAuthentication yes' \
> /etc/ssh/sshd_config.d/ansible-lab.conf
Finalmente preparo systemd y dejo que sea el proceso principal:
RUN systemctl enable ssh
RUN systemctl mask systemd-modules-load.service
STOPSIGNAL SIGRTMIN+3
CMD ["/lib/systemd/systemd"]
La idea del Dockerfile es sencilla: parto de Debian, añado SSH, Python y sudo, creo el usuario ansible y convierto el contenedor en un pequeño servidor administrable.
4. Creo el Dockerfile de Rocky Linux
Creo:
vim images/rocky/Dockerfile.systemd
La estructura es prácticamente la misma, pero utilizando las herramientas propias de Rocky Linux.
FROM rockylinux:9
ENV container=docker
RUN dnf -y install \
systemd \
openssh-server \
sudo \
python3 \
python3-dnf \
procps-ng \
iproute \
iputils \
curl-minimal \
vim-minimal && \
dnf clean all
Creo el usuario:
RUN useradd --create-home --shell /bin/bash ansible && \
echo "ansible ALL=(ALL) NOPASSWD:ALL" \
> /etc/sudoers.d/ansible && \
chmod 0440 /etc/sudoers.d/ansible
Instalo la clave SSH:
RUN mkdir -p /home/ansible/.ssh
COPY ssh/ansible_lab.pub /home/ansible/.ssh/authorized_keys
RUN chown -R ansible:ansible /home/ansible/.ssh && \
chmod 700 /home/ansible/.ssh && \
chmod 600 /home/ansible/.ssh/authorized_keys
Habilito SSH:
RUN systemctl enable sshd
Y arranco el contenedor con systemd:
STOPSIGNAL SIGRTMIN+3
CMD ["/usr/lib/systemd/systemd"]
La principal diferencia frente a Debian es el uso de dnf y del servicio sshd.
5. Creo el Dockerfile de Alpine
Alpine no utiliza systemd, sino OpenRC, por lo que su imagen es más sencilla.
Creo:
vim images/alpine/Dockerfile
Parto de Alpine 3.24:
FROM alpine:3.24
RUN apk add --no-cache \
openrc \
openssh-server \
openssh-server-common-openrc \
sudo \
python3 \
bash \
procps \
iproute2 \
iputils \
curl
Creo el usuario:
RUN adduser -D -s /bin/sh ansible && \
passwd -d ansible && \
echo "ansible ALL=(ALL) NOPASSWD:ALL" \
> /etc/sudoers.d/ansible && \
chmod 0440 /etc/sudoers.d/ansible
Añado la clave pública:
RUN mkdir -p /home/ansible/.ssh
COPY ssh/ansible_lab.pub /home/ansible/.ssh/authorized_keys
RUN chown -R ansible:ansible /home/ansible/.ssh && \
chmod 700 /home/ansible/.ssh && \
chmod 600 /home/ansible/.ssh/authorized_keys
Habilito SSH con OpenRC:
RUN rc-update add sshd default
Y preparo el arranque:
RUN mkdir -p /usr/local/sbin && \
printf '%s\n' \
'#!/bin/sh' \
'ssh-keygen -A' \
'exec /sbin/init' \
> /usr/local/sbin/ansible-lab-init && \
chmod 0755 /usr/local/sbin/ansible-lab-init
CMD ["/usr/local/sbin/ansible-lab-init"]
Este nodo no necesita privileged ni configuración especial de cgroups.
6. Creo el Dockerfile de MySQL
Para MySQL parto de la imagen oficial:
vim images/mysql/Dockerfile
FROM mysql:8.4
Instalo lo necesario para administrarlo mediante Ansible:
RUN microdnf install -y \
openssh-server \
sudo \
python3 \
shadow-utils \
passwd \
procps-ng \
iproute && \
microdnf clean all
Creo el usuario ansible e instalo la clave exactamente igual que en el resto de nodos.
En este caso no quiero sustituir el arranque normal de MySQL, así que creo:
vim images/mysql/entrypoint-lab.sh
con:
#!/bin/bash
set -e
ssh-keygen -A
mkdir -p /run/sshd /usr/sbin/sshd
exec /usr/local/bin/docker-entrypoint.sh "$@"
Le doy permisos:
chmod +x images/mysql/entrypoint-lab.sh
Y desde el Dockerfile lo utilizo como wrapper:
COPY images/mysql/entrypoint-lab.sh \
/usr/local/bin/entrypoint-lab.sh
RUN chmod 0755 /usr/local/bin/entrypoint-lab.sh
ENTRYPOINT ["/usr/local/bin/entrypoint-lab.sh"]
CMD ["mysqld"]
Así primero arranco SSH y después dejo que la imagen oficial continúe arrancando MySQL normalmente.
7. Construyo las imágenes
Desde la raíz del proyecto:
docker build \
-t ansible-lab-debian:13-systemd \
-f images/debian/Dockerfile.systemd .
docker build \
-t ansible-lab-rocky:9-systemd \
-f images/rocky/Dockerfile.systemd .
docker build \
-t ansible-lab-alpine:3.24 \
-f images/alpine/Dockerfile .
docker build \
-t ansible-lab-mysql:8.4 \
-f images/mysql/Dockerfile .
Compruebo el resultado:
docker image ls | grep ansible-lab
8. Creo las variables de MySQL
En .env:
MYSQL_ROOT_PASSWORD=ansible-lab-root
MYSQL_DATABASE=ansible_lab
Si publico el proyecto en GitHub, añado .env a .gitignore.
9. Creo Docker Compose
En compose.yml defino los seis nodos:
services:
debian1:
image: ansible-lab-debian:13-systemd
container_name: ansible-debian1
hostname: debian1
privileged: true
cgroup: private
restart: "no"
networks:
ansible_lab:
ipv4_address: 172.30.10.11
debian2:
image: ansible-lab-debian:13-systemd
container_name: ansible-debian2
hostname: debian2
privileged: true
cgroup: private
restart: "no"
networks:
ansible_lab:
ipv4_address: 172.30.10.12
rocky1:
image: ansible-lab-rocky:9-systemd
container_name: ansible-rocky1
hostname: rocky1
privileged: true
cgroup: private
restart: "no"
networks:
ansible_lab:
ipv4_address: 172.30.10.21
rocky2:
image: ansible-lab-rocky:9-systemd
container_name: ansible-rocky2
hostname: rocky2
privileged: true
cgroup: private
restart: "no"
networks:
ansible_lab:
ipv4_address: 172.30.10.22
alpine1:
image: ansible-lab-alpine:3.24
container_name: ansible-alpine1
hostname: alpine1
restart: "no"
networks:
ansible_lab:
ipv4_address: 172.30.10.31
mysql1:
image: ansible-lab-mysql:8.4
container_name: ansible-mysql1
hostname: mysql1
restart: "no"
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
volumes:
- mysql-data:/var/lib/mysql
networks:
ansible_lab:
ipv4_address: 172.30.10.40
networks:
ansible_lab:
name: ansible-lab
driver: bridge
ipam:
config:
- subnet: 172.30.10.0/24
gateway: 172.30.10.1
volumes:
mysql-data:
name: ansible-lab-mysql-data
Compruebo que Compose interpreta correctamente el archivo:
docker compose config
10. Arranco el laboratorio
docker compose up -d
Compruebo los contenedores:
docker compose ps
Y la red:
docker network inspect ansible-lab
Debo tener:
172.30.10.11 debian1
172.30.10.12 debian2
172.30.10.21 rocky1
172.30.10.22 rocky2
172.30.10.31 alpine1
172.30.10.40 mysql1
11. Compruebo SSH
Pruebo uno de los nodos:
ssh -i ~/.ssh/ansible_lab_ed25519 \
ansible@172.30.10.11
Dentro compruebo:
hostname
whoami
sudo whoami
python3 --version
Después puedo repetir la prueba con el resto de direcciones.
Para Debian y Rocky también compruebo:
systemctl is-system-running
y espero:
running
En Alpine:
rc-service sshd status
Una vez comprobado SSH, Python y sudo en todos los nodos, el laboratorio está preparado para empezar a utilizar Ansible.
Para apagarlo:
docker compose down
Para volver a levantarlo:
docker compose up -d
A partir de aquí el siguiente paso será instalar Ansible en Manjaro, crear el inventario y comprobar todos los hosts con:
ansible all -m ping