Introduction
ServiceNow is the dominant IT Service Management (ITSM) platform in enterprise environments. Integrating Ansible with ServiceNow creates closed-loop automation: incidents trigger remediation playbooks, change requests auto-execute approved changes, and the CMDB stays in sync with actual infrastructure state. The servicenow.itsm collection provides native Ansible modules for the full ServiceNow API.
Install the ServiceNow Collection
ansible-galaxy collection install servicenow.itsm
# Verify
ansible-galaxy collection list servicenow.itsm
Connection Setup
# group_vars/all.yml
servicenow_instance: "{{ vault_snow_instance }}" # e.g., dev12345.service-now.com
servicenow_username: "{{ vault_snow_username }}"
servicenow_password: "{{ vault_snow_password }}"
# Or use environment variables
# SN_HOST=https://dev12345.service-now.com
# SN_USERNAME=admin
# SN_PASSWORD=secret
Create Incidents Automatically
---
- name: Auto-create incident from monitoring alert
hosts: localhost
gather_facts: false
vars:
alert_host: "{{ alert_hostname | default('web-server-01') }}"
alert_message: "{{ alert_msg | default('Service unresponsive') }}"
tasks:
- name: Create incident in ServiceNow
servicenow.itsm.incident:
state: new
caller: "ansible-automation"
short_description: "[AUTO] {{ alert_message }} on {{ alert_host }}"
description: |
Automated incident created by Ansible monitoring integration.
Host: {{ alert_host }}
Alert: {{ alert_message }}
Time: {{ ansible_date_time.iso8601 | default(lookup('pipe', 'date -u +%Y-%m-%dT%H:%M:%SZ')) }}
Playbook: {{ ansible_play_name }}
impact: high
urgency: high
category: "Software"
subcategory: "Operating System"
assignment_group: "Linux Support"
register: incident
- name: Display incident number
ansible.builtin.debug:
msg: "Created incident: {{ incident.record.number }}"
- name: Attempt automated remediation
ansible.builtin.include_role:
name: "remediate_{{ alert_type | default('service_restart') }}"
vars:
target_host: "{{ alert_host }}"
register: remediation
ignore_errors: true
- name: Update incident with remediation result
servicenow.itsm.incident:
number: "{{ incident.record.number }}"
state: "{{ 'resolved' if remediation is succeeded else 'in_progress' }}"
close_code: "{{ 'Solved (Permanently)' if remediation is succeeded else omit }}"
close_notes: "{{ 'Auto-remediated by Ansible' if remediation is succeeded else omit }}"
work_notes: |
Automated remediation {{ 'succeeded' if remediation is succeeded else 'failed' }}.
{% if remediation is failed %}
Manual intervention required. Escalating to on-call engineer.
{% endif %}
Manage Change Requests
- name: Create and execute change request
hosts: localhost
gather_facts: false
tasks:
- name: Create change request
servicenow.itsm.change_request:
state: new
type: standard
short_description: "Patch {{ target_servers | length }} servers — {{ patch_date }}"
description: |
Standard patching window for {{ environment }} servers.
Servers: {{ target_servers | join(', ') }}
Maintenance window: {{ patch_date }} {{ patch_time }}
category: "Software"
priority: "3 - Moderate"
assignment_group: "Linux Support"
risk: moderate
impact: "3 - Low"
register: change
- name: Display change number
ansible.builtin.debug:
msg: "Change request: {{ change.record.number }}"
- name: Wait for approval
servicenow.itsm.change_request_info:
number: "{{ change.record.number }}"
register: change_status
until: change_status.records[0].state == "scheduled"
retries: 720
delay: 60
when: wait_for_approval | default(false)
- name: Move to implement
servicenow.itsm.change_request:
number: "{{ change.record.number }}"
state: implement
- name: Execute patching
ansible.builtin.include_role:
name: patching
vars:
servers: "{{ target_servers }}"
register: patch_result
- name: Close change request
servicenow.itsm.change_request:
number: "{{ change.record.number }}"
state: "{{ 'closed' if patch_result is succeeded else 'canceled' }}"
close_code: "{{ 'successful' if patch_result is succeeded else 'unsuccessful' }}"
close_notes: |
Patching {{ 'completed successfully' if patch_result is succeeded else 'failed' }}.
Servers patched: {{ target_servers | length }}
Sync CMDB with Inventory
- name: Sync infrastructure to ServiceNow CMDB
hosts: all
gather_facts: true
tasks:
- name: Update CMDB configuration item
servicenow.itsm.configuration_item:
name: "{{ inventory_hostname }}"
sys_class_name: cmdb_ci_linux_server
ip_address: "{{ ansible_default_ipv4.address }}"
os: "{{ ansible_distribution }} {{ ansible_distribution_version }}"
os_version: "{{ ansible_kernel }}"
cpu_count: "{{ ansible_processor_vcpus }}"
ram: "{{ ansible_memtotal_mb }}"
serial_number: "{{ ansible_product_serial | default('N/A') }}"
environment: "{{ env | default('production') }}"
operational_status: operational
attributes:
ansible_managed: "true"
last_scan: "{{ ansible_date_time.iso8601 }}"
uptime_days: "{{ ansible_uptime_seconds | int // 86400 }}"
delegate_to: localhost
- name: Discover and register new CIs
hosts: localhost
gather_facts: false
tasks:
- name: Query CMDB for existing servers
servicenow.itsm.configuration_item_info:
query:
- sys_class_name: = cmdb_ci_linux_server
operational_status: = operational
register: cmdb_servers
- name: Find servers in Ansible but not CMDB
ansible.builtin.set_fact:
unregistered: "{{ groups['all'] | difference(cmdb_servers.records | map(attribute='name') | list) }}"
- name: Report unregistered servers
ansible.builtin.debug:
msg: "Servers in Ansible inventory but NOT in CMDB: {{ unregistered }}"
when: unregistered | length > 0
ServiceNow Dynamic Inventory
# servicenow_inventory.yml
plugin: servicenow.itsm.now
instance:
host: "{{ lookup('env', 'SN_HOST') }}"
username: "{{ lookup('env', 'SN_USERNAME') }}"
password: "{{ lookup('env', 'SN_PASSWORD') }}"
table: cmdb_ci_linux_server
fields: [name, ip_address, os, environment, operational_status]
query:
- operational_status: = operational
- environment: = production
columns:
ip_address: ansible_host
os: os_type
groups:
linux: "os_type == 'Linux'"
web_servers: "'web' in name"
db_servers: "'db' in name"
compose:
ansible_host: ip_address
# Use ServiceNow as inventory source
ansible-inventory -i servicenow_inventory.yml --list
ansible-playbook -i servicenow_inventory.yml site.yml
Closed-Loop Remediation Workflow
- name: Closed-loop monitoring → SNOW → remediation
hosts: localhost
gather_facts: false
tasks:
- name: Query open incidents assigned to automation
servicenow.itsm.incident_info:
query:
- state: = in_progress
assignment_group: = "Ansible Automation"
register: open_incidents
- name: Process each incident
ansible.builtin.include_tasks: remediate-incident.yml
loop: "{{ open_incidents.records }}"
loop_control:
loop_var: incident
label: "{{ incident.number }}"
# remediate-incident.yml
- name: Parse incident category
ansible.builtin.set_fact:
remediation_playbook: >-
{{ remediation_map[incident.category + '/' + incident.subcategory]
| default('escalate') }}
- name: Run remediation
ansible.builtin.command: >
ansible-playbook playbooks/{{ remediation_playbook }}.yml
-e "incident_number={{ incident.number }}"
-e "target_host={{ incident.cmdb_ci.display_value }}"
register: result
when: remediation_playbook != 'escalate'
ignore_errors: true
- name: Update incident
servicenow.itsm.incident:
number: "{{ incident.number }}"
work_notes: >
Ansible remediation {{ 'completed' if result is succeeded else 'failed' }}
using playbook: {{ remediation_playbook }}
Related Articles
Conclusion
Ansible + ServiceNow integration creates powerful enterprise automation. The servicenow.itsm collection handles incidents (auto-create from alerts, auto-resolve after remediation), change requests (create, wait for approval, execute, close), and CMDB sync (keep configuration items in sync with actual infrastructure). The dynamic inventory plugin lets you use ServiceNow as your source of truth. The ultimate pattern: closed-loop automation where monitoring triggers an incident, Ansible remediates it, and the incident closes automatically — with full audit trail in ServiceNow.