Infrastructure automation with Terraform (IaC) and Ansible (configuration management) has matured through 2018-2019. The two tools address different problems and are often used together.
Terraform for resource provisioning
Terraform provisions infrastructure resources: cloud VMs, databases, networking, and managed services. The declarative model (describe the desired state, Terraform computes the plan to reach it) makes infrastructure changes reviewable and repeatable. The provider ecosystem covers AWS, Azure, GCP, Kubernetes, and hundreds of third-party services. Terraform is not designed for configuring software on running machines, that is Ansible's domain.
Ansible for configuration management
Ansible configures software on existing infrastructure: installing packages, configuring application settings, managing service startup, and deploying application code to VMs. Ansible playbooks are idempotent: running the same playbook multiple times produces the same result. The agentless model (Ansible pushes commands via SSH) avoids the agent installation requirement of Chef and Puppet.
The combination pattern
The production pattern that works: Terraform provisions the infrastructure (VMs, networking, cloud services), Ansible configures the software on the VMs (runtime versions, application configuration, security hardening). Terraform's output (IP addresses, DNS names, credentials) feeds into Ansible inventory. This separates infrastructure provisioning (rare, risky) from software configuration (more frequent, lower risk).
Packer for immutable images
Packer builds machine images with all software pre-installed and configured, ready to boot in a known state. The Packer workflow: start from a base OS image, run Ansible to install and configure software, produce an AMI or Azure VHD. The resulting image contains everything needed for the application to run. Deployment replaces old instances with new instances booted from the new image, no configuration management at runtime.