Saltar al contenido principal

Ansible y Contenedores 🐳

Hasta ahora hemos usado Ansible para gestionar servidores tradicionales. En este capítulo damos el salto al mundo de los contenedores: aprenderás a orquestar Docker y Kubernetes desde Ansible, sustituyendo scripts bash frágiles por playbooks idempotentes y reutilizables.

Video pendiente de grabación

Suscríbete al canal de YouTube para recibir la notificación.

🎯 ¿Por qué usar Ansible con contenedores?

La analogía: el contenedor y el estibador

Si Docker es la caja de mercancías estandarizada que viaja por todo el mundo, Ansible es el estibador que decide qué cajas se cargan en qué barco, en qué orden, y con qué etiquetas. Docker resuelve el "cómo se empaqueta una aplicación"; Ansible responde al "cómo desplegamos cientos de esas cajas en producción de forma repetible".

Cuándo Ansible aporta valor frente a Compose o kubectl

EscenarioHerramienta nativaAnsible aporta
Levantar un stack en localdocker compose up— (úsalo nativo)
Desplegar Docker en 50 hostsscripts bashInventario + idempotencia
Aplicar manifiestos K8s en CIkubectl applyPlantillas Jinja2 + Vault
Bootstrap de un clúster K3sbash + scpRoles reutilizables
Mezclar VMs y K8s en un mismo desplieguevariasUn solo playbook

Regla de oro: Ansible brilla cuando hay que coordinar la capa de infraestructura (instalar Docker, configurar el daemon, gestionar redes, certificados) junto con la capa de aplicación contenerizada. Para flujos puramente declarativos dentro del clúster, Helm o Kustomize son mejores.

🐳 Parte 1: Ansible + Docker

Instalación de la colección community.docker

Las funcionalidades de Docker en Ansible viven en una colección externa que hay que instalar primero:

ansible-galaxy collection install community.docker
pip install docker # SDK de Python requerido en el host destino

Módulos clave

MóduloPara qué sirve
community.docker.docker_imageConstruir, descargar o eliminar imágenes
community.docker.docker_containerCrear, arrancar, parar contenedores
community.docker.docker_networkGestionar redes Docker
community.docker.docker_volumeVolúmenes persistentes
community.docker.docker_compose_v2Orquestar stacks con Compose
community.docker.docker_loginAutenticarse en registries

Ejemplo: instalar Docker y desplegar Nginx

---
- name: Bootstrap Docker host y desplegar Nginx
hosts: webservers
become: true

tasks:
- name: Instalar dependencias
ansible.builtin.apt:
name:
- ca-certificates
- curl
- gnupg
state: present
update_cache: true

- name: Añadir repositorio oficial de Docker
ansible.builtin.deb822_repository:
name: docker
types: deb
uris: https://download.docker.com/linux/ubuntu
suites: "{{ ansible_distribution_release }}"
components: stable
signed_by: https://download.docker.com/linux/ubuntu/gpg

- name: Instalar Docker Engine
ansible.builtin.apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-compose-plugin
state: present
update_cache: true

- name: Asegurar que el servicio docker está activo
ansible.builtin.service:
name: docker
state: started
enabled: true

- name: Desplegar contenedor Nginx
community.docker.docker_container:
name: web
image: nginx:1.27-alpine
state: started
restart_policy: unless-stopped
ports:
- "80:80"
volumes:
- /srv/web/html:/usr/share/nginx/html:ro

Orquestar un stack Compose desde Ansible

- name: Levantar stack con Compose
community.docker.docker_compose_v2:
project_src: /opt/app
state: present
pull: always

Este patrón es ideal para bootstraping: Ansible prepara el host (firewall, certificados, variables de entorno renderizadas con Jinja2) y luego delega el ciclo de vida de los contenedores a Compose.

Construir imágenes en pipeline

- name: Construir y publicar imagen
community.docker.docker_image:
name: registry.local/notastack-api
tag: "{{ git_commit_sha }}"
source: build
build:
path: ./api
pull: true
push: true

⚓ Parte 2: Ansible + Kubernetes

Instalación de la colección kubernetes.core

ansible-galaxy collection install kubernetes.core
pip install kubernetes openshift pyyaml jsonpatch

Necesitas un kubeconfig válido en el host donde corre Ansible (normalmente el control node).

Módulos clave

MóduloPara qué sirve
kubernetes.core.k8sAplicar/borrar cualquier recurso (equivale a kubectl apply)
kubernetes.core.k8s_infoConsultar recursos existentes (equivale a kubectl get)
kubernetes.core.helmGestionar releases de Helm
kubernetes.core.helm_repositoryAñadir/quitar repositorios Helm
kubernetes.core.k8s_execEjecutar comandos dentro de pods
kubernetes.core.k8s_scaleEscalar deployments y statefulsets

Ejemplo: desplegar una aplicación en Kubernetes

---
- name: Desplegar NotaStack en K8s
hosts: localhost
gather_facts: false

vars:
app_namespace: notastack
app_image: registry.local/notastack-api:1.4.2

tasks:
- name: Crear namespace
kubernetes.core.k8s:
state: present
definition:
apiVersion: v1
kind: Namespace
metadata:
name: "{{ app_namespace }}"

- name: Aplicar manifiestos renderizados con Jinja2
kubernetes.core.k8s:
state: present
namespace: "{{ app_namespace }}"
template: "manifests/{{ item }}.yaml.j2"
loop:
- configmap
- deployment
- service
- ingress

- name: Esperar a que el deployment esté disponible
kubernetes.core.k8s_info:
kind: Deployment
namespace: "{{ app_namespace }}"
name: notastack-api
register: dep
until: >
dep.resources[0].status.availableReplicas | default(0) ==
dep.resources[0].spec.replicas
retries: 30
delay: 10

Plantilla Jinja2 de un Deployment

# manifests/deployment.yaml.j2
apiVersion: apps/v1
kind: Deployment
metadata:
name: notastack-api
spec:
replicas: {{ replicas | default(2) }}
selector:
matchLabels:
app: notastack-api
template:
metadata:
labels:
app: notastack-api
spec:
containers:
- name: api
image: {{ app_image }}
ports:
- containerPort: 8080
env:
- name: DB_HOST
value: "{{ db_host }}"

Este patrón —manifiestos K8s renderizados con Jinja2 desde Ansible— es muy potente: te da variables, condicionales y bucles que Helm sólo ofrece de forma limitada, y se integra con tu inventario de Ansible para distinguir entornos (dev/staging/prod).

Instalar charts con Helm desde Ansible

- name: Añadir repo de Bitnami
kubernetes.core.helm_repository:
name: bitnami
repo_url: https://charts.bitnami.com/bitnami

- name: Instalar PostgreSQL
kubernetes.core.helm:
name: db
chart_ref: bitnami/postgresql
release_namespace: notastack
create_namespace: true
values:
auth:
postgresPassword: "{{ pg_password }}"
primary:
persistence:
size: 20Gi

Bootstrap completo de un clúster K3s

Un caso de uso muy común: usar Ansible para levantar el clúster (no sólo desplegar dentro de él):

- name: Instalar K3s en el master
hosts: k3s_master
become: true
tasks:
- name: Descargar e instalar K3s
ansible.builtin.shell: |
curl -sfL https://get.k3s.io | sh -
args:
creates: /usr/local/bin/k3s

- name: Recoger token del nodo
ansible.builtin.slurp:
src: /var/lib/rancher/k3s/server/node-token
register: k3s_token

- name: Unir workers al clúster
hosts: k3s_workers
become: true
tasks:
- name: Instalar K3s agent
ansible.builtin.shell: |
curl -sfL https://get.k3s.io | \
K3S_URL=https://{{ hostvars[groups['k3s_master'][0]].ansible_host }}:6443 \
K3S_TOKEN={{ hostvars[groups['k3s_master'][0]]['k3s_token']['content'] | b64decode | trim }} \
sh -
args:
creates: /usr/local/bin/k3s

✅ Buenas prácticas

  • Usa Vault para tokens de registry, kubeconfigs y credenciales (lo vimos en el capítulo 7).
  • No reinventes Helm: si ya existe un chart oficial, instálalo desde Ansible en lugar de traducirlo a manifiestos sueltos.
  • Separa bootstrap (infra) de despliegue (app): Ansible es excelente para el primero; para el segundo, considera usar GitOps (ArgoCD/Flux) y limitar Ansible al pipeline.
  • Idempotencia: usa siempre state: present/absent en lugar de command: kubectl apply ....
  • Tags por entorno: estructura tu playbook con tags dev, staging, prod y combínalo con inventarios separados.

🧪 Reto del capítulo

  1. Crea un playbook que instale Docker en un host Linux limpio.
  2. Despliega un stack con Nginx + un backend Node + Redis usando docker_compose_v2.
  3. Bonus: levanta un clúster K3s de 1 master + 2 workers con Ansible y despliega el mismo stack como Deployments + Services.

📚 Recursos


Próximo capítulo: Ansible en DevOps y CI/CD — integramos todo lo aprendido en pipelines de Jenkins y GitHub Actions.