The Boundaries of Operations Automation: A Comparative Analysis of Ansible and Terraform

0. Introduction: Why You Need “Tools” Instead of “Common Commands”

Abstract: Infrastructure as Code (Terraform) and Configuration Management/Orchestration (Ansible) transform “human repetitive labor” into reproducible code. However, when automation encounters the real world—state drift, hardware, emergency changes, approvals, sensitive information, and third-party dependencies—”boundaries” are exposed. This article explains these gaps, common pitfalls, and practical models in a teaching + hands-on manner, providing directly runnable examples (including simulated console commands and execution results). The goal is for you to implement this method in your team pipeline, achieving both automation and safety.

1. Essential Differences: Terraform vs Ansible

  • Terraform: Declarative, focuses on the existence state of resources (create/update/destroy), maintains global/remote state, suitable for infrastructure lifecycle management (networks, load balancers, cloud instances, DNS, managed DBs, etc.).
  • Ansible: Imperative or idempotent (ideally), focuses on configuration and runtime state (software installation, service configuration, file content, executing one-time scripts), suitable for machine configuration/application deployment/operations scripts.

In summary: Terraform manages the existence of the “house” and “fence”, while Ansible manages “what’s inside the house” and “how to clean it”.

2. Common “Boundaries” and Symptoms in the Real World

Below are real issues you will encounter in production, and why relying solely on Ansible or Terraform makes it difficult to solve them:

State Drift

  • • For example: Operations personnel manually changed configurations on running machines; Terraform state still believes the resource has not changed.
  • • Result: terraform apply cannot detect or unwillingly changes the running state service, causing interruptions.

Sensitive Information Leakage and State

  • • Terraform state and provider outputs may contain plaintext credentials. If the team does not encrypt/access control, it will leak.

Non-reconstructability of External/Stateful Services

  • • Some resources (e.g., legacy databases, hardware devices, proprietary switch ACLs) cannot be casually destroyed and rebuilt; Terraform’s inertia is to “declare based on resources”.

Long-running Tasks and Partial Failures

  • • For example, patching or migrating large databases; a failed step requires manual inspection, unsuitable for full automation retry.

Network/Hardware/Physical Dependencies

  • • On-premises switches, UPS, cabinets, fiber optics, etc., are not covered by cloud providers, and Terraform’s capabilities are weak.

Approvals, Compliance, and Manual Interventions

  • • Compliance requirements (change orders, approval chains) need to insert manual steps outside the automation process.

Uncertainty of Third-party and Closed-source APIs

  • • Provider bugs, API limitations, and rate limits can lead to discrepancies between plans and actual outcomes.

3. Practical Models: Bridging the Gaps with Practices and Architectures

Below is a highly practical model aimed at placing Terraform and Ansible in the correct responsibility boundaries and seamlessly integrating with real processes.

Model A — Responsibility Division

  • • Terraform: Networks, VPCs, subnets, load balancers, cloud hosts (empty images), managed database instances, DNS, IAM, S3, etc.; outputs IP/hostname, ssh-key, userdata, etc., to upstream.
  • • Packer (optional): Builds golden images (including basic dependencies), reducing initial configuration time and drift.
  • • Ansible: Configuration, application deployment, runtime tasks, patching, one-time migration scripts.
  • • CI/CD: Executes terraform plan and uses the artifact (plan) as PR results; after manual approval, applies in a controlled environment using apply.
  • • Secrets: HashiCorp Vault / AWS Secrets Manager / SOPS + KMS; Terraform state exists in a controlled backend (S3 + Dynamo lock / Terraform Cloud).

Model B — Secure Connection Points

  1. 1. Terraform outputs JSON -> dynamic inventory for Ansible use
  • terraform output -json > tf-output.json, then a small script writes the instance IP to inventory.ini, or use Ansible EC2 dynamic inventory plugin.
  • 2. Avoid using Terraform provisioners (local-exec/remote-exec) for configuration
    • • Provisioners are convenient but can cause unpredictable states in failure scenarios. Recommended: Use userdata/cloud-init or Packer to build images, then manage with Ansible.
  • 3. Blue-green/rolling replacements
    • • For stateful services, first perform blue-green or rolling updates; do not directly replace single-node databases.
  • 4. Detection and Repair (drift)
    • • Regularly run terraform plan + ansible --check to report differences; for non-destructive issues, automatic remediation (e.g., packages); for destructive issues, initiate a work order.
  • 5. Approval & Manual Intervention Points
    • • CI posts plan output back to PR; Require reviewers -> Merge triggers apply (with multiple webhooks + logs).

    4. Practical Example

    Scenario: Create an EC2 instance in AWS (example) using Terraform, then use Ansible to install Nginx and place a static page. The example focuses on demonstrating the “correct boundaries”: Terraform manages resources, Ansible manages configurations.

    4.1 Terraform: Simple main.tf

    # main.tf (example)
    provider "aws" {
      region = "us-east-1"
    }
    
    resource "aws_key_pair" "deploy" {
      key_name   = "demo-key"
      public_key = file("~/.ssh/id_rsa.pub")
    }
    
    resource "aws_security_group" "ssh_http" {
      name = "sg-ssh-http-demo"
      ingress {
        from_port = 22
        to_port   = 22
        protocol  = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
      }
      ingress {
        from_port = 80
        to_port   = 80
        protocol  = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
      }
    }
    
    resource "aws_instance" "web" {
      ami           = "ami-0c94855ba95c71c99" # Example: Ubuntu 18.04 (please adjust according to real account)
      instance_type = "t3.micro"
      key_name      = aws_key_pair.deploy.key_name
      vpc_security_group_ids = [aws_security_group.ssh_http.id]
    
      tags = { Name = "demo-web" }
    
      # cloud-init for initial boot, only prepares for ssh availability
      user_data = <<-EOF
                  #cloud-config
                  ssh_authorized_keys:
                    - ${file("~/.ssh/id_rsa.pub")}
                  EOF
    }
    
    output "web_public_ip" {
      value = aws_instance.web.public_ip
    }

    4.2 Terraform Operations

    $ terraform init
    Initializing the backend...
    Initializing provider plugins...
    Terraform has been successfully initialized!
    
    $ terraform plan -out=tfplan.binary
    Refreshing provider state...
    Plan: 1 to add, 0 to change, 0 to destroy.
    
    $ terraform apply "tfplan.binary"
    aws_key_pair.deploy: Creating...
    aws_security_group.ssh_http: Creating...
    aws_instance.web: Creating...
    ... (omitted)
    Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
    
    Outputs:
    web_public_ip = "3.125.78.99"

    Note: Here, web_public_ip will be used for Ansible inventory (next section).

    4.3 Passing Terraform Output to Ansible (Generating Simple Inventory)

    Save tf-output.json:

    $ terraform output -json > tf-output.json
    $ cat tf-output.json
    {
      "web_public_ip": {
        "sensitive": false,
        "type": "string",
        "value": "3.125.78.99"
      }
    }

    Generate inventory.ini:

    $ python3 - <<'PY'
    import json
    j=json.load(open('tf-output.json'))
    ip = j['web_public_ip']['value']
    with open('inventory.ini','w') as f:
        f.write('[web]\n')
        f.write(f'{ip} ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/id_rsa\n')
    print('inventory.ini written')
    PY
    
    $ cat inventory.ini
    [web]
    3.125.78.99 ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/id_rsa

    4.4 Ansible: site.yml

    # site.yml
    - hosts: web
      gather_facts: yes
      become: yes
    
      tasks:
        - name: Ensure apt cache is up to date
          apt:
            update_cache: yes
          tags: packages
    
        - name: Install nginx
          apt:
            name: nginx
            state: latest
          notify: restart nginx
          tags: packages
    
        - name: Deploy index.html
          copy:
            dest: /var/www/html/index.html
            content: |
              <html><body><h1>Deployed by Ansible</h1></body></html>
          tags: deploy
    
      handlers:
        - name: restart nginx
          service:
            name: nginx
            state: restarted

    4.5 Ansible Execution

    $ ansible-playbook -i inventory.ini site.yml
    
    PLAY [web] ******************************************************************
    
    TASK [Gathering Facts] ********************************************************
    ok: [3.125.78.99]
    
    TASK [Ensure apt cache is up to date] *****************************************
    changed: [3.125.78.99]
    
    TASK [Install nginx] **********************************************************
    changed: [3.125.78.99]
    
    TASK [Deploy index.html] ******************************************************
    changed: [3.125.78.99]
    
    RUNNING HANDLER [restart nginx] **********************************************
    changed: [3.125.78.99]
    
    PLAY RECAP ********************************************************************
    3.125.78.99               : ok=5    changed=4    unreachable=0    failed=0

    Note: If Ansible reports an error (e.g., unreachable/failed), we do not automatically retry destroying or modifying instances at the Terraform level. We should log the failure in CI logs and create a work order/manual follow-up (as misconfiguration may lead to data loss).

    5. Testing, Rollback, and Exception Handling Models

    5.1 Terraform’s “Plan-Approve-Apply” Workflow

    1. 1. Execute terraform plan in the PR, and use the plan output as an artifact (or use terraform show -json plan).
    2. 2. Reviewer reviews the plan against changes; after approval, trigger terraform apply (executed in a controlled runner, avoiding local direct apply).
    3. 3. Use a state backend (S3 + Dynamo/consul or Terraform Cloud) to ensure locking and audit logging.

    GitHub Actions:

    # .github/workflows/terraform.yml (snippet)
    on: [pull_request]
    
    jobs:
      plan:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - name: terraform init
            run: terraform init
          - name: terraform plan
            run: terraform plan -out=tfplan
          - name: upload plan
            uses: actions/upload-artifact@v4
            with:
              name: tfplan
              path: tfplan

    The apply step should only be executed in a protected branch or triggered manually.

    5.2 Rollback Strategies

    • Non-destructive changes: Try to make replacement changes as rolling updates or blue-green, avoiding destroy followed by create.
    • Database/Stateful: Always establish backups -> verify backups -> then execute destructive changes. Use migration scripts (idempotent) in conjunction with Ansible.
    • Short-term rollback: Use AMI rollback or DNS quick switch to the old cluster in case of failure.

    5.3 Drift Detection and Automatic Repair

    • • Periodically run: terraform plan -detailed-exitcode (0: no changes, 2: changes) and alert.
    • • For non-destructive differences, automatically repair with Ansible (e.g., package version mismatch), and initiate change orders for destructive changes.

    6. Common Pitfalls and Response Manual

    Variable Spelling Errors

    Some people easily misspell variable results logic when writing playbooks:

    - hosts: dbservers
      vars:
        mysql_port: 3306
      tasks:
        - name: Start MySQL
          service:
            name: mysqld
            state: started
            port: "{{ mysql_prt }}"

    Execution:

    fatal: [db01]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'mysql_prt' is undefined"}

    Missing an o resulted in MySQL not starting at all. Such small mistakes cannot be helped by automation tools and can amplify errors instead.

    State File Conflicts

    When multiple people collaborate without configuring remote state, the results can be dangerous:

    $ terraform apply
    Error acquiring the state lock
    Error: Error locking state: Error acquiring the state lock: ConditionalCheckFailedException: The conditional request failed

    The state file was modified simultaneously by two engineers, ultimately requiring manual intervention to unlock.

    $ terraform force-unlock LOCK_ID
    Terraform state has been successfully unlocked!

    This situation indicates that Terraform is not always “autopilot”; sometimes you still need to pull the handbrake.

    Conclusion

    Automation does not completely remove people from the process; rather, it frees people’s energy from repetitive labor to focus on judgment, design, and auditing. Terraform and Ansible each have their roles, and when you place them in the right positions, supplemented by CI, auditing, and drills, automation transitions from an ideal to reality: no longer a “fantasy universal key”, but a reliable productivity tool for the team.

    Old Yang’s Time

    Let me clarify, in daily life, everyone calls me Bo Ge, which has nothing to do with seniority, mainly because I am older. It’s just a nickname.

    I call my younger colleagues born in the 2000s with “Ge” as well, like Zhang Ge, Li Ge.

    However, this title feels a bit inappropriate when the “golden master” calls it during offline events.

    For example, last time a planning director from a certain group said at a company meeting: “Today we are happy! Please welcome Bo Ge from the IT operations and maintenance technology circle to say a few words.”

    This atmosphere and this title feel a bit mismatched in the internet industry!

    Every time I encounter this situation, I want to respond: “Meeting you all is fate, thank you for your kindness, no need to say anything, it’s all in the wine. I drink, you all feel free!”

    So from now on, let’s call me Old Yang, which is both down-to-earth and low-key. I think it’s quite nice.

    Operations X Files series articles:

    From Alarm to CTO: An 11-hour Life-and-Death Race of a P0 Incident

    Enterprise-level Kubernetes Cluster Security Hardening Complete Guide (with one-click check script)

    Don’t leave after reading. Cultivation lies in likes, shares, and views. Accumulate merits in this life to earn blessings in the next.

    Click to read the original text or open the address to collect and analyze global VPS projects in real-time.

    vps.top365app.com

    Old Yang AI’s account: 98dev

    Leave a Comment