SSD Nodes Learn Hosting plans →
Guías Matt ConnorPor Matt Connor · Actualizado 2026-08-21

Ansible --check y --diff: qué comprueban realmente

Entienda qué demuestran --check y --diff en Ansible y por qué un módulo sin soporte puede ocultar cambios, dando un resultado incorrecto antes de aplicar el playbook.

Qué hace el modo de comprobación de Ansible

El modo de comprobación de Ansible es una ejecución de prueba: ansible-playbook --check se conecta a cada host del play, pregunta a cada módulo si el estado actual ya coincide con el estado solicitado y muestra qué cambiaría sin escribir nada. Añada --diff para que también muestre el contenido anterior y posterior de los archivos que modificaría. Juntos responden a la pregunta que conviene hacerse antes de cada ejecución real: ¿qué está a punto de cambiar en estos servidores?

El modo de comprobación no simula el playbook. No existe ningún modelo del servidor. Simplemente se pide a cada módulo que lea en lugar de escribir. Un módulo que puede responder en modo de solo lectura informa de changed y continúa. Un módulo que no puede responder no hace nada ni informa de nada. La documentación de Ansible lo resume en una línea: "Los módulos que no admiten el modo de comprobación no informan de nada ni hacen nada". Esa limitación es la razón por la que una ejecución de prueba puede dar una respuesta incorrecta, por lo que la mayor parte de esta guía trata sobre ella.

Ejecute la simulación: --check y --diff

ansible-playbook -i inventory.ini site.yml --check --diff --limit web1

-C y -D son las formas abreviadas de las dos opciones. El --limit es deliberado. El diff de un host se puede leer. El diff de veinte hosts obliga a desplazarse demasiado.

Cuatro palabras de resultado contienen todo el informe.

  • ok: [web1] significa que el módulo comprobó el estado y que ya coincide. No cambiaría nada.
  • changed: [web1] significa que el módulo habría escrito algo. Con --diff, las líneas anteriores muestran qué.
  • skipping: [web1] significa que la tarea no se evaluó. Un when era falso o el módulo no puede ejecutarse en modo de comprobación.
  • fatal: [web1] significa que la tarea falló durante la comprobación. Lea el mensaje antes de asumir que el playbook está roto.

--diff muestra un diff unificado para los módulos de archivos. Las líneas eliminadas llevan - y las añadidas llevan +. El encabezado tiene líneas que comienzan por --- before y +++ after y que indican la ruta de destino. Los módulos que no escriben archivos muestran su propio antes y después. Por eso, ansible.builtin.user muestra los atributos que cambiaría en lugar del contenido del archivo.

Active diff de forma permanente en ansible.cfg para no olvidar nunca la opción:

[diff]
always = true
context = 5

Hay dos comprobaciones más económicas que conviene ejecutar antes del modo de comprobación. ansible-playbook site.yml --syntax-check analiza el YAML y la estructura del play sin contactar con ningún host. ansible-playbook site.yml --list-tasks muestra las tareas que se ejecutarían. Así puede descubrir que un rol que creía etiquetado no lo está. Ninguna de las dos establece conexiones, por lo que ambas son inmediatas.

El modo de comprobación sí establece conexiones. Abre una conexión SSH con cada host del patrón y recopila datos. Por eso, un host que está apagado hace que falle la simulación. Esto ya es una señal útil. También explica por qué importa decidir qué debe hacer un playbook con los hosts inaccesibles antes de incorporar una simulación a CI.

Por qué falla el modo de comprobación en un servidor nuevo

Este play es correcto. Ejecútelo con --check contra un servidor que todavía no tenga nginx y la mayor parte del play fallará.

- name: Install nginx
  ansible.builtin.apt:
    name: nginx
    state: present

- name: Write the site config
  ansible.builtin.template:
    src: site.conf.j2
    dest: /etc/nginx/conf.d/site.conf

- name: Start and enable nginx
  ansible.builtin.service:
    name: nginx
    state: started
    enabled: true

La tarea apt informa changed, y tiene razón: el paquete no está instalado, por lo que una ejecución real lo instalaría. El modo de comprobación no lo instaló. A continuación, la tarea template falla porque /etc/nginx/conf.d/ no existe en este host y ninguna tarea lo creó. La tarea service también falla porque no hay ninguna unidad nginx que pueda consultar. Ninguno de estos fallos es un error del playbook. La ejecución en seco no tenía disponible el estado que necesitaba. Esto es lo que indica la documentación cuando advierte que el modo de comprobación no puede generar resultados útiles para una tarea cuya entrada depende del cambio realizado por una tarea anterior.

Por tanto, la formulación correcta de la regla es la siguiente: el modo de comprobación es preciso frente a un host en el que el playbook ya ha convergido, y genera muchos errores frente a un host nuevo. Una ejecución con --check en la que todas las tareas informan ok es una afirmación válida sobre un host convergido, porque significa que no se realizaría ningún cambio. En un host completamente nuevo, --check indica principalmente que el host es nuevo. Cuando escriba su primer playbook de Ansible para un VPS, espere que la primera ejecución en seco muestre muchos errores y evalúe el playbook por la segunda.

Por qué se omiten las tareas de comandos y shell en el modo de comprobación

ansible.builtin.command y ansible.builtin.shell no saben qué hace su comando. No existe una forma de solo lectura para ejecutar un binario arbitrario, por lo que el módulo se niega a ejecutarlo en el modo de comprobación. El resultado de la tarea contiene skipped: true y el mensaje Command would have run if not in check mode, y la salida muestra skipping: [web1].

La documentación del módulo denomina «parcial» a su compatibilidad con el modo de comprobación, y la solución alternativa que indica es creates y removes. Asigne a la tarea una ruta creates para que el modo de comprobación pueda evaluar al menos la prueba del archivo:

- name: Extract the release bundle
  ansible.builtin.command: /usr/bin/tar xf /tmp/app.tar.gz -C /opt/app
  args:
    creates: /opt/app/bin/app

Si /opt/app/bin/app ya existe, el modo de comprobación informa de Would not run command since '/opt/app/bin/app' exists, que es una respuesta real. Si falta la ruta, obtiene Command would have run if not in check mode, que también es una respuesta real. Sin creates, esa tarea queda como un espacio vacío en la ejecución de prueba.

El efecto en cadena es peor que el espacio vacío. Una tarea omitida sigue registrando un resultado, pero ese resultado indica que la tarea se omitió y no tiene la clave stdout. La condición de la siguiente tarea falla al evaluarse, con un error similar a 'dict object' has no attribute 'stdout'. El playbook funciona en una ejecución real y falla en la ejecución de prueba. Este es el fallo más confuso de toda esta funcionalidad.

check_mode: false, y el único lugar al que pertenece

check_mode: false en una tarea significa «ejecutarla realmente, incluso con --check». Es la solución al problema de los comandos omitidos, y sólo es segura en una tarea que lee.

- name: Read the installed app version
  ansible.builtin.command: /usr/local/bin/app --version
  register: app_version
  check_mode: false
  changed_when: false

Esa tarea es correcta en ambos modos. Lee una versión y nunca escribe, changed_when: false evita que informe de un cambio que no realizó y check_mode: false hace que app_version.stdout exista durante una ejecución de prueba, de modo que las condiciones basadas en ella sigan evaluándose.

Lea literalmente la palabra clave antes de copiarla en otro lugar. Una tarea con check_mode: false escribe en los servidores durante ansible-playbook --check. Si la coloca en una tarea apt o template para que la ejecución de prueba parezca más limpia, dejará de ser una ejecución de prueba. Cuando una tarea que escribe no se pueda hacer segura, protéjala con una condición:

- name: Apply the database migration
  ansible.builtin.command: /usr/local/bin/app migrate --apply
  when: not ansible_check_mode

ansible_check_mode es una variable mágica que Ansible establece en true durante una ejecución de comprobación. También existe la palabra clave inversa. check_mode: true fija una tarea en modo de comprobación, incluso durante una ejecución real, y la convierte en una sonda de desviaciones: registre el resultado; un informe changed significa que el host ya no coincide con lo que solicita la tarea.

Por qué una tarea informa cambios en cada ejecución

Ejecute el playbook dos veces seguidas, sin hacer nada entre ambas ejecuciones. Todas las tareas deberían informar ok en la segunda ejecución. Si alguna tarea sigue informando changed, indica una de estas dos situaciones: el módulo no puede ver el estado que administra o la entrada que recibe no es estable. Ambas situaciones tienen solución y no son ruido que deba silenciarse.

  • command y shell sin creates, removes ni changed_when informan changed cada vez, porque el módulo no tiene forma de saber si ocurrió algo. Añada creates o establezca changed_when para una cadena presente en la salida.
  • ansible.builtin.file con state: touch informa changed en cada ejecución por diseño, porque tocar un archivo actualiza sus marcas de tiempo. Use state: file si sólo quería establecer el propietario o los permisos.
  • Un template cuya salida renderizada cambia vuelve a escribir el archivo en cada ejecución. Una marca de tiempo de ansible_date_time, una llamada a now() o una contraseña generada de nuevo en cada ejecución producen bytes diferentes, por lo que el módulo informa correctamente de un cambio. Quite el valor variable de la plantilla.
  • ansible.builtin.user con password: "{{ pw | password_hash('sha512') }}" cambia en cada ejecución porque password_hash elige un salt aleatorio cada vez que se invoca, por lo que el hash resultante nunca coincide con el que ya existe en /etc/shadow. Pase un salt explícito derivado de algo estable.
  • state: latest en un módulo de paquetes informa changed cuando hay una actualización disponible. Ese comportamiento es correcto. También explica por qué state: latest produce un playbook cuyo resultado no puede predecir. Use state: present y actualice de forma explícita.
  • ansible.builtin.unarchive apuntando a una URL sin creates vuelve a descargar y extraer el contenido. Proporcione una ruta creates.

--diff es la forma más rápida de distinguir estos casos. Si una tarea indica changed y la diferencia muestra bytes distintos, la entrada no es estable. Si indica changed y la diferencia no muestra nada, el módulo no puede expresar qué cambió. Esto suele significar una tarea command o una escritura que sólo modifica metadatos, como una marca de tiempo.

No use changed_when: false para silenciar una tarea ruidosa. Suprime el informe, por lo que notify nunca se activa y el handler que reinicia el servicio nunca se ejecuta. Corrija la tarea.

Reducir el alcance del incidente: --limit, --tags y --step

El modo de comprobación indica qué cambiaría. Estas opciones determinan cuántos equipos reciben el cambio a la vez.

--limit limita la ejecución a un subconjunto del inventario. Acepta los mismos patrones que hosts:, por lo que funcionan tanto --limit web1 como --limit 'webservers:!web3'. Ponga el patrón entre comillas. Un ! sin comillas en una sesión interactiva de bash activa la expansión del historial con el signo de exclamación, y el shell reescribe el comando antes de que Ansible lo reciba.

Confirme el patrón antes de confiar en él. ansible-playbook site.yml --limit 'webservers:!web3' --list-hosts muestra los hosts coincidentes y termina sin conectarse a ninguno. Un patrón sin coincidencias es seguro, porque Ansible no vuelve a usar todo el inventario. Muestra una advertencia indicando que no pudo encontrar coincidencias para el patrón de hosts y, después, termina con un error que indica que los hosts y --limit no coinciden con ningún host. Saber cómo define esos grupos el archivo de inventario es lo que permite predecir el resultado de un patrón.

--tags deploy ejecuta sólo las tareas etiquetadas, mientras que --skip-tags packages ejecuta todo lo demás. --list-tags muestra las etiquetas disponibles. Las etiquetas resultan útiles cuando un play supera el punto en el que está dispuesto a ejecutarlo completo. Esta es también una de las razones para dividir un playbook largo en roles.

--start-at-task "Write the site config" reanuda una ejecución fallida desde una tarea determinada. Úselo para recuperarse, pero tenga en cuenta el coste: omite todo lo anterior a esa tarea, incluidas las tareas que establecen facts o registran las variables que leen las tareas posteriores.

--step solicita confirmación antes de cada tarea y espera una respuesta: yes, no o continue. Es lento, pero es la herramienta adecuada la primera vez que ejecuta una operación destructiva, porque permite detenerse entre dos tareas en lugar de hacerlo después de veinte.

Aplicar el cambio de forma gradual

De forma predeterminada, Ansible ejecuta una tarea en todos los hosts del play antes de iniciar la siguiente. Es rápido, pero una tarea incorrecta llega a toda la flota en el mismo segundo. Cuando ha leído el error y ha pulsado Ctrl-C, el cambio ya se ha aplicado en todas partes.

serial divide el play en lotes. El play completo se ejecuta en el primer lote y después en el siguiente.

- name: Roll out the web tier
  hosts: webservers
  serial: [1, 5, "30%"]
  max_fail_percentage: 0
  tasks:
    - name: Deploy the release
      ansible.builtin.include_role:
        name: webapp

El primer lote contiene un host. Si funciona correctamente, el segundo lote contiene cinco hosts y cada lote posterior contiene el 30 por ciento de los hosts del play. max_fail_percentage: 0 finaliza el play en cuanto falla un host de un lote, por lo que una versión defectuosa se detiene en una sola máquina. any_errors_fatal: true es la variante más contundente: finaliza el play para todos cuando falla el primer host.

Ejecutar primero el play en un solo host no es una medida excesiva. La razón es concreta. Los grupos del inventario cambian con el tiempo. Un servidor añadido seis meses después que los demás puede ejecutar otra versión de la distribución, tener un servicio instalado manualmente o usar una disposición de discos diferente. El playbook es correcto para el grupo, pero incorrecto para ese host, y una ejecución de prueba en un host convergido no lo mostrará. Administrar una flota de servidores Linux consiste en gran medida en encontrar el host diferente antes de que lo haga el cambio.

Orden para ejecutar las tareas

  1. ansible-playbook site.yml --syntax-check detecta errores de YAML y de estructura sin realizar ninguna conexión de red.
  2. ansible-playbook site.yml --limit web1 --list-hosts demuestra que el patrón coincide con lo que espera.
  3. ansible-playbook site.yml --limit web1 --check --diff es la ejecución de prueba. Revise la diferencia.
  4. ansible-playbook site.yml --limit web1 --diff lo aplica a ese único host.
  5. Ejecute de nuevo el paso 4. Todo debería informar ok. Cualquier elemento que siga informando changed es una tarea que debe corregirse antes de aplicarla al resto de la flota.
  6. ansible-playbook site.yml --check --diff en todo el inventario devuelve ahora una respuesta útil, porque los hosts convergidos no muestran cambios y lo que queda es la diferencia real.

Una advertencia sobre el paso 3. --diff muestra el contenido de los archivos en el terminal y en el registro del trabajo de CI. Por tanto, si una plantilla genera una contraseña de base de datos, esa contraseña queda registrada. Establezca diff: false en esa tarea para suprimir la salida o no_log: true para ocultar todo el resultado. Mantenga el valor en un archivo cifrado de Ansible Vault, no en el repositorio.

FAQ

¿Cambia algo en el servidor ansible-playbook --check?

No, con una excepción que usted controla. En el modo de comprobación, se pide a cada módulo que informe en lugar de escribir. Los módulos que no pueden hacerlo no informan ni realizan cambios. La excepción es la palabra clave de tarea check_mode: false, que obliga a ejecutar esa tarea de forma real incluso durante una ejecución --check. Busque check_mode: false en sus playbooks y roles antes de confiar en una ejecución en seco. Confirme que cada coincidencia corresponda a una tarea que sólo lea el estado.

¿Cuál es la diferencia entre --check y --diff?

--check decide si algo se ejecuta de forma real. --diff decide cuántos detalles se muestran. --check por sí solo indica que un archivo cambiaría. --diff por sí solo aplica el cambio y muestra las líneas modificadas. Úselos juntos para obtener una ejecución en seco que se pueda leer. Mantenga --diff activado también en las ejecuciones reales. Para ello, configure always = true en [diff] dentro de ansible.cfg.

¿Por qué mi tarea de Ansible informa de cambios en cada ejecución?

Porque el módulo no puede ver el estado que administra o porque el valor que recibe cambia cada vez. command y shell informan siempre de changed, a menos que añada creates o changed_when. file con state: touch cambia por diseño. Una plantilla que genere una marca de tiempo o una contraseña nueva produce bytes diferentes en cada ejecución. Por tanto, el archivo se vuelve a escribir realmente. Ejecute el playbook dos veces seguidas. La tarea que siga marcada como changed en la segunda ejecución es la que debe corregir.

¿Por qué se omiten mis tareas command y shell durante una ejecución en seco?

Porque no existe una forma de solo lectura para ejecutar un comando arbitrario. En el modo de comprobación, el módulo command establece skipped: true con el mensaje Command would have run if not in check mode. Añada creates o removes para que el modo de comprobación pueda evaluar la prueba del archivo. Para una tarea que solo lea el estado, establezca check_mode: false junto con changed_when: false. Así, el resultado registrado seguirá existiendo durante la ejecución en seco y las condiciones basadas en él seguirán funcionando.

¿Por qué falla el modo de comprobación en un servidor nuevo, pero funciona en uno existente?

Porque el modo de comprobación no crea el estado del que dependen las tareas posteriores. Una ejecución en seco contra un host sin nginx informa de la instalación como changed. Después falla en la tarea que escribe en /etc/nginx/conf.d/, porque ese directorio nunca se creó. Este comportamiento es esperado. El modo de comprobación detecta desviaciones en hosts en los que el playbook ya ha convergido. No puede validar una primera ejecución. En un host nuevo, aplique el playbook a una máquina y revise la segunda ejecución.

#ansible#check-mode#idempotency#automation#safety