Ansible vs Puppet: Choosing the Right Configuration Management Tool

David Childs

A comprehensive comparison of Ansible and Puppet for configuration management, with real-world use cases and migration strategies.

After years of managing infrastructure with both Ansible and Puppet, I've learned that each tool excels in different scenarios. This guide will help you choose the right tool for your infrastructure needs and avoid common pitfalls.

Architecture Differences

Ansible: Agentless Simplicity

  • Push-based model: Control node pushes configurations
  • No agents required: Uses SSH/WinRM for communication
  • YAML playbooks: Human-readable configuration
  • Procedural approach: Step-by-step execution

Puppet: Agent-Based Control

  • Pull-based model: Agents pull configurations
  • Master-agent architecture: Centralized management
  • Puppet DSL: Domain-specific language
  • Declarative approach: Describe desired state

Getting Started Comparison

Ansible Quick Start

# inventory.yml
webservers:
  hosts:
    web1.example.com:
    web2.example.com:

# playbook.yml
- name: Configure Web Servers
  hosts: webservers
  become: yes
  tasks:
    - name: Install Nginx
      package:
        name: nginx
        state: present
    
    - name: Start Nginx
      service:
        name: nginx
        state: started
        enabled: yes
    
    - name: Deploy config
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify: restart nginx
  
  handlers:
    - name: restart nginx
      service:
        name: nginx
        state: restarted

Puppet Quick Start

# site.pp
node 'web1.example.com', 'web2.example.com' {
  class { 'nginx':
    ensure => present,
    enable => true,
  }
  
  file { '/etc/nginx/nginx.conf':
    ensure  => file,
    source  => 'puppet:///modules/nginx/nginx.conf',
    require => Package['nginx'],
    notify  => Service['nginx'],
  }
  
  service { 'nginx':
    ensure  => running,
    enable  => true,
    require => Package['nginx'],
  }
}

Use Case Analysis

When to Choose Ansible

Ideal for:

  • Rapid deployment and provisioning
  • Multi-cloud orchestration
  • Ad-hoc tasks and one-time configurations
  • Small to medium environments
  • Teams with limited configuration management experience

Real-world example:

# Multi-cloud deployment
- name: Deploy to AWS
  hosts: localhost
  tasks:
    - amazon.aws.ec2_instance:
        name: "web-{{ item }}"
        instance_type: t3.micro
        image_id: ami-12345678
        count: 1
      loop: "{{ range(1, 4) | list }}"

- name: Deploy to Azure
  hosts: localhost
  tasks:
    - azure.azcollection.azure_rm_virtualmachine:
        name: "web-{{ item }}"
        vm_size: Standard_B1s
        image: UbuntuLTS
      loop: "{{ range(1, 4) | list }}"

When to Choose Puppet

Ideal for:

  • Large-scale enterprise environments
  • Strict compliance requirements
  • Complex dependency management
  • Long-term configuration drift prevention
  • Environments requiring detailed reporting

Real-world example:

# Compliance-focused configuration
class security::baseline {
  # CIS Benchmark implementation
  file { '/etc/ssh/sshd_config':
    ensure  => file,
    owner   => 'root',
    group   => 'root',
    mode    => '0600',
    content => template('security/sshd_config.erb'),
  }
  
  augeas { 'kernel.parameters':
    context => '/files/etc/sysctl.conf',
    changes => [
      'set net.ipv4.tcp_syncookies 1',
      'set net.ipv4.conf.all.rp_filter 1',
      'set net.ipv4.conf.default.rp_filter 1',
    ],
  }
  
  # Automatic compliance reporting
  @@nagios_service { "compliance_${hostname}":
    ensure              => present,
    host_name          => $hostname,
    service_description => 'Security Compliance',
    check_command      => 'check_compliance',
  }
}

Performance and Scalability

Ansible Performance

# Optimize with strategies
- name: Parallel execution
  hosts: all
  strategy: free
  tasks:
    - name: Update packages
      package:
        name: "*"
        state: latest
      async: 3600
      poll: 0
      register: update_job

# Fact caching for performance
[defaults]
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_cache
fact_caching_timeout = 3600

Puppet Performance

# Puppet server tuning
# /etc/puppetlabs/puppetserver/conf.d/puppetserver.conf
jruby-puppet: {
  max-active-instances: 4
  max-requests-per-instance: 10000
}

# Client-side caching
[agent]
usecacheonfailure = true
report = true
runinterval = 30m

Learning Curve and Team Adoption

Ansible Learning Path

  1. Week 1: Basic playbooks and inventory
  2. Week 2: Variables, templates, and handlers
  3. Week 3: Roles and Galaxy
  4. Week 4: Advanced features (vault, AWX/Tower)

Puppet Learning Path

  1. Week 1: Resources and manifests
  2. Week 2: Classes and modules
  3. Week 3: Hiera and data separation
  4. Week 4: PuppetDB and reporting
  5. Week 5-6: Advanced patterns and Puppet Enterprise

Integration Capabilities

Ansible Integrations

# CI/CD Integration
- name: Jenkins Pipeline
  hosts: localhost
  tasks:
    - uri:
        url: "http://jenkins:8080/job/deploy/build"
        method: POST
        user: "{{ jenkins_user }}"
        password: "{{ jenkins_token }}"
        
# Secret Management
- name: Retrieve from Vault
  hashivault_read:
    path: secret/data/app
    key: database_password
  register: db_password

Puppet Integrations

# Kubernetes Integration
kubernetes_deployment { 'frontend':
  ensure => present,
  metadata => {
    namespace => 'production',
  },
  spec => {
    replicas => 3,
    selector => {
      matchLabels => {
        app => 'frontend',
      },
    },
  },
}

# ServiceNow CMDB
node default {
  $cmdb_data = lookup('cmdb::configuration', Hash, 'deep')
  class { 'application':
    config => $cmdb_data,
  }
}

Cost Comparison

Ansible Costs

  • Ansible Core: Free and open source
  • Red Hat Ansible Automation Platform:
    • Standard: ~$10,000/year (100 nodes)
    • Premium: ~$14,000/year (100 nodes)
  • Training: $3,000-5,000 per person
  • Implementation: 2-4 weeks typical

Puppet Costs

  • Puppet Open Source: Free
  • Puppet Enterprise:
    • Standard: ~$120/node/year
    • Premium: ~$199/node/year
  • Training: $3,500-6,000 per person
  • Implementation: 4-8 weeks typical

Migration Strategies

From Puppet to Ansible

#!/usr/bin/env python3
# Convert Puppet manifests to Ansible playbooks
import yaml
import re

def convert_puppet_to_ansible(puppet_manifest):
    playbook = {
        'name': 'Converted from Puppet',
        'hosts': 'all',
        'tasks': []
    }
    
    # Parse package resources
    packages = re.findall(r"package\s*{\s*'([^']+)'.*?ensure\s*=>\s*(\w+)", 
                          puppet_manifest, re.DOTALL)
    for pkg, state in packages:
        playbook['tasks'].append({
            'name': f'Install {pkg}',
            'package': {
                'name': pkg,
                'state': 'present' if state == 'installed' else state
            }
        })
    
    return yaml.dump([playbook])

From Ansible to Puppet

# Convert Ansible playbooks to Puppet manifests
require 'yaml'

def convert_ansible_to_puppet(playbook_path)
  playbook = YAML.load_file(playbook_path)
  manifest = ""
  
  playbook['tasks'].each do |task|
    if task['package']
      manifest += <<-PUPPET
package { '#{task['package']['name']}':
  ensure => #{task['package']['state']},
}
      PUPPET
    end
  end
  
  manifest
end

Testing and Validation

Ansible Testing

# Molecule testing
molecule:
  - name: default
    driver:
      name: docker
    platforms:
      - name: ubuntu
        image: ubuntu:20.04
    verifier:
      name: ansible
      
# Ansible-lint
ansible-lint playbook.yml --exclude .github

Puppet Testing

# RSpec testing
require 'spec_helper'

describe 'nginx' do
  context 'with default parameters' do
    it { is_expected.to compile }
    it { is_expected.to contain_package('nginx') }
    it { is_expected.to contain_service('nginx').with_ensure('running') }
  end
end

Common Pitfalls and Solutions

Ansible Pitfalls

  1. Idempotency issues: Always use modules instead of shell commands
  2. Variable precedence confusion: Document your variable hierarchy
  3. Slow execution: Use mitogen or pipelining for speed

Puppet Pitfalls

  1. Catalog compilation errors: Test in development first
  2. Certificate management: Automate certificate signing
  3. Resource ordering: Use proper relationships (require, notify)

Making the Decision

Choose Ansible if you:

  • Need quick implementation
  • Prefer agentless architecture
  • Have dynamic, cloud-based infrastructure
  • Want easier learning curve
  • Focus on orchestration over configuration

Choose Puppet if you:

  • Manage large, stable infrastructure
  • Require detailed compliance reporting
  • Need strong Windows support
  • Want declarative configuration
  • Have dedicated DevOps team

Conclusion

Both Ansible and Puppet are excellent tools that solve configuration management differently. Ansible's simplicity and flexibility make it perfect for modern, dynamic infrastructures. Puppet's robustness and reporting capabilities excel in large, compliance-focused enterprises.

Many organizations successfully use both: Ansible for orchestration and deployment, Puppet for configuration enforcement and compliance. Choose based on your specific needs, team skills, and infrastructure requirements.

Share this article

DC

David Childs

Consulting Systems Engineer with over 10 years of experience building scalable infrastructure and helping organizations optimize their technology stack.

Related Articles