简体中文 / [English]


A First Look at Infrastructure as Code (IaC) - Ansible and Terraform

This article is currently an experimental machine translation and may contain errors. If anything is unclear, please refer to the original Chinese version. I am continuously working to improve the translation.

I’ve heard about Infrastructure-as-Code (IaC) for quite a while, and recently got a great chance to try it out in practice—here’s a write-up to document my experience.

Background

My current infrastructure mainly consists of my HomeLab and some Serverless services on Alibaba Cloud. Both have been covered in previous blog posts and are now running stably, requiring no major changes.

However, I still need a few auxiliary services running on overseas VPS instances, primarily due to network connectivity and cost considerations. The VPS provider might occasionally change. Each time I migrate servers, the process becomes relatively cumbersome.

A quick and dirty approach would be writing a Docker Compose file or a bash script—just install Docker on the new VPS and you’re done. But Ansible seems like a more elegant solution, so I decided to give it a try.

Ansible - Agentless Remote Configuration of Linux Servers

Setting up a new Linux VPS isn’t particularly hard, but doing it repeatedly—whether for migrations or managing multiple machines—can get messy.

Ansible’s official documentation is quite clear and beginner-friendly. One read-through is enough to grasp the basics.

Using Ansible, the workflow looks like this:

  1. Install Ansible on your local machine.
  2. Define your inventory (VPS connection details) and write a playbook (desired state of the VPS).
  3. Run the playbook locally—Ansible uses SSH to connect and configure the VPS, bringing it to the defined state.

Here’s a quick look at what a playbook looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
---
- name: Install nginx package
package:
name:
- nginx
state: present

- name: Update nginx config
ansible.builtin.template:
src: templates/nginx.conf.j2
dest: /etc/nginx/sites-available/default
notify:
- Restart nginx

- name: Start nginx
ansible.builtin.systemd_service:
name: nginx
state: started
daemon_reload: true

This is a simple task list for setting up Nginx: Step 1 ensures Nginx is installed, Step 2 updates the config (and triggers a restart if needed), Step 3 makes sure Nginx is running. That’s it—simple and clean.

Compared to Docker Compose, Ansible is more flexible and powerful—it can directly manage system-level configurations, and can even be used to install and run Docker itself. The two tools are complementary, not mutually exclusive.

Compared to raw bash scripts, Ansible uses clean, readable YAML and is idempotent—running it multiple times won’t cause conflicts. It’s also much easier to maintain and modify than bash.

Ansible also has a registry called Galaxy, where you can reuse pre-built modules (Roles). For example, installing Docker and Docker Compose on a server can be as simple as:

1
2
3
4
5
6
7
8
9
---
- include_role:
name: geerlingguy.docker
- include_role:
name: geerlingguy.pip
vars:
pip_install_packages:
- name: docker
- name: docker-compose

Many common tasks are available as reusable roles, saving you from writing and maintaining boilerplate code—and they often work across different Linux distributions.

Now, if I need to migrate my server, I just update the inventory file and re-run the playbook. The new VPS will be fully provisioned automatically. This greatly improves efficiency, reduces human error, and gives me more confidence and flexibility when adjusting my infrastructure.

My current VPS playbook is already open-sourced. With IaC, you can leverage existing version control tools like Git for collaboration, audit trails, and history tracking.

Terraform - Define Cloud Infrastructure with Code

Ansible solves the problem of configuring Linux VPS instances, but many cloud services aren’t just virtual machines.

For example, my recently open-sourced project UptimeFlare uses Cloudflare Workers / KV / Pages. When I tried writing deployment instructions, I realized—it’s a pain. You have to log into the Cloudflare dashboard and click through dozens of settings. No way I’m writing that tedious step-by-step guide. I considered writing a bash script for one-click setup, but wrangler (Cloudflare’s CLI) lacks proper parameters and documentation—still requires manual intervention. Super annoying.

And once again, laziness became the mother of invention. Right after diving into Ansible, I discovered Terraform.

Terraform also uses code to define the desired state of your cloud infrastructure. It then ensures the actual state matches your definition.

Cloudflare even provides official Terraform documentation. Each cloud provider has its own Terraform provider and documentation. The language itself is simple—just follow the examples, and you’ll be up and running.

Here’s a code snippet for reference:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
terraform {
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 4"
}
}
}

provider "cloudflare" {
# read token from $CLOUDFLARE_API_TOKEN
}

variable "CLOUDFLARE_ACCOUNT_ID" {
# read account id from $TF_VAR_CLOUDFLARE_ACCOUNT_ID
type = string
}

resource "cloudflare_workers_kv_namespace" "uptimeflare_kv" {
account_id = var.CLOUDFLARE_ACCOUNT_ID
title = "uptimeflare_kv"
}

resource "cloudflare_worker_script" "uptimeflare" {
account_id = var.CLOUDFLARE_ACCOUNT_ID
name = "uptimeflare_worker"
content = file("worker/dist/index.js")
module = true
compatibility_date = "2023-11-08"

kv_namespace_binding {
name = "UPTIMEFLARE_STATE"
namespace_id = cloudflare_workers_kv_namespace.uptimeflare_kv.id
}
}

resource "cloudflare_worker_cron_trigger" "uptimeflare_worker_cron" {
account_id = var.CLOUDFLARE_ACCOUNT_ID
script_name = cloudflare_worker_script.uptimeflare.name
schedules = [
"*/2 * * * *", # every 2 minutes
]
}

resource "cloudflare_pages_project" "uptimeflare" {
account_id = var.CLOUDFLARE_ACCOUNT_ID
name = "uptimeflare"
production_branch = "main"

deployment_configs {
production {
kv_namespaces = {
UPTIMEFLARE_STATE = cloudflare_workers_kv_namespace.uptimeflare_kv.id
}
compatibility_date = "2023-11-08"
compatibility_flags = ["nodejs_compat"]
}
}
}

Essentially, each block defines a resource with parameters from the docs.

Then, just run terraform init and terraform plan locally to preview the changes.

Run terraform apply to apply them to your cloud environment. Any future changes? Just update the code and run terraform apply again.

In my use case, I no longer need to write long, tedious deployment guides. Users just provide an API token, and everything deploys automatically.

Summary

That’s my initial experience with IaC. Turns out, laziness really is the greatest driver of innovation. Ansible and Terraform are easy to get started with—simple configuration, lightweight, and perfect for individual developers. No need to deal with overly complex “enterprise-grade” solutions that feel like over-engineering. The benefits? Greater consistency, better scalability, and a huge reduction in manual, repetitive work. Efficiency win!

This article is licensed under the CC BY-NC-SA 4.0 license.

Author: lyc8503, Article link: https://blog.lyc8503.net/en/post/iac-explore-ansible-and-terraform/
If this article was helpful or interesting to you, consider buy me a coffee¬_¬
Feel free to comment in English below o/