Ngừng Sao Chép Thủ Công Hạ Tầng: Hướng Dẫn Thực Tế Về Terragrunt

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

Nỗi lo thực tế khi sử dụng Terraform thuần túy

Terraform mang lại trải nghiệm tuyệt vời khi mới bắt đầu. Bạn viết một vài khối tài nguyên, chạy terraform apply, và chứng kiến hạ tầng của mình xuất hiện trên cloud. Nhưng giai đoạn “trăng mật” sẽ kết thúc khi dự án mở rộng. Một khi bạn chuyển từ một sandbox duy nhất sang quản lý các môi trường Development, Staging và Production, bạn sẽ vấp phải rào cản của việc lặp lại thủ công.

Đột nhiên, bạn phải sao chép và dán cùng một cấu hình backend, các khối provider và các lần gọi module vào hàng tá thư mục. Nếu bạn quản lý năm môi trường với mười module mỗi loại, bạn sẽ có 50 tệp backend.tf cần bảo trì.

Sự trùng lặp này là một cơn ác mộng về bảo trì. Nếu bạn cần cập nhật phiên bản provider hoặc một tag chung, bạn phải nhớ thay đổi nó trong mọi thư mục. Chỉ cần quên một chỗ, các môi trường của bạn sẽ bị sai lệch (drift), dẫn đến những lỗi kiểu “chạy tốt ở Dev” gây ám ảnh cho các bản phát hành production.

So sánh hai thế giới

Để hiểu tại sao Terragrunt trở thành một công cụ thiết yếu trong hệ sinh thái Infrastructure as Code hiện đại, hãy xem cách nó thay đổi cấu trúc mã nguồn so với Terraform tiêu chuẩn.

Thiết lập Terraform tiêu chuẩn

Trong một thiết lập thuần túy điển hình, cấu trúc thư mục của bạn thường trông như thế này:

infrastructure/
├── dev/
│   ├── main.tf
│   ├── variables.tf
│   └── backend.tf
├── prod/
│   ├── main.tf
│   ├── variables.tf
│   └── backend.tf
└── modules/
    └── vpc/

Bạn có thấy sự dư thừa không? Các tệp dev/backend.tfprod/backend.tf hầu như giống hệt nhau, ngoại trừ một key S3 bucket duy nhất. Các tệp main.tf thường chỉ là các lớp bao (wrapper) gọi cùng một module với các biến hơi khác nhau. Điều này vi phạm nguyên tắc DRY (Don’t Repeat Yourself) và dễ dẫn đến sai sót của con người.

Giải pháp thay thế từ Terragrunt

Terragrunt hoạt động như một lớp bao (wrapper) thông minh và mỏng cho Terraform. Thay vì định nghĩa backend và provider trong mọi thư mục con, bạn chỉ cần định nghĩa chúng một lần trong tệp cấu hình gốc. Các thư mục môi trường của bạn sau đó chỉ chứa một tệp terragrunt.hcl. Tệp này chỉ đơn giản tham chiếu đến logic chung và cung cấp các đầu vào (input) cụ thể. Terragrunt xử lý các tác vụ tẻ nhạt như tạo cấu hình backend và truyền biến một cách linh hoạt.

Sự đánh đổi: Terragrunt có xứng đáng không?

Thêm một công cụ vào stack của bạn không bao giờ là miễn phí. Điều quan trọng là phải cân nhắc giữa lợi ích vận hành và lớp trừu tượng bổ sung, tương tự như khi kiểm thử Infrastructure as Code cho môi trường production.

Lợi ích

  • Logic Backend DRY: Bạn định nghĩa backend S3 hoặc GCS một lần. Terragrunt tự động tạo bucket nếu thiếu và quản lý state key dựa trên cấu trúc thư mục của bạn.
  • Tập trung hóa Provider: Định nghĩa provider AWS hoặc Azure trong một tệp duy nhất và để mọi module kế thừa các cài đặt đó.
  • Quản lý phụ thuộc thông minh: Module App của bạn có cần VPC ID không? Terragrunt cho phép bạn định nghĩa các phụ thuộc (dependencies) một cách rõ ràng. Nó đảm bảo tài nguyên được tạo đúng thứ tự mọi lúc.
  • Thực thi hàng loạt: Các lệnh như terragrunt run-all plan cho phép bạn xem trước thay đổi trên hơn 20 module cùng lúc. Điều này có thể giúp kỹ sư DevOps tiết kiệm 30 phút điều hướng thủ công, hoặc kết hợp với việc ước tính chi phí cloud tự động trong mỗi chu kỳ triển khai.

Nhược điểm

  • Chi phí vận hành công cụ: Thêm một file thực thi (binary) cần cài đặt và cập nhật trong pipeline CI/CD của bạn (như việc tập trung hóa GitHub Actions hoặc GitLab CI).
  • Rào cản học tập ban đầu: Các thành viên mới trong nhóm phải học cả Terraform và cú pháp Terragrunt HCL, điều này có thể gây bối rối lúc đầu.
  • Các lớp trừu tượng: Khi triển khai thất bại, bạn phải kiểm tra xem lỗi nằm ở Terraform module hay ở logic điều phối của Terragrunt.

Cấu trúc dự án chuyên nghiệp

Trong môi trường production, tôi khuyên bạn nên tách biệt “mã hạ tầng” (các module tái sử dụng) khỏi “cấu hình thực tế” (các lần triển khai thực tế). Điều này giữ cho logic của bạn sạch sẽ và việc triển khai có thể dự đoán được.

my-repo/
├── modules/             # Các module Terraform tái sử dụng (Cái gì)
│   ├── vpc/
│   └── ec2/
└── live/                # Cấu hình cụ thể theo môi trường (Ở đâu)
    ├── terragrunt.hcl   # Cấu hình gốc
    ├── dev/
    │   └── vpc/
    │       └── terragrunt.hcl
    └── prod/
        └── vpc/
            └── terragrunt.hcl

Cách triển khai Terragrunt ngay hôm nay

Hãy cùng đi qua một ví dụ thực tế để quản lý VPC trên hai môi trường mà không gặp phải tình trạng phình mã nguồn thường thấy.

Bước 1: Cấu hình gốc

Tạo tệp live/terragrunt.hcl để xử lý remote state. Terragrunt sử dụng tệp này để tự động tạo backend.tf cho mọi thư mục con.

# live/terragrunt.hcl
remote_state {
  backend = "s3"
  generate = {
    path      = "backend.tf"
    if_exists = "overwrite_terragrunt"
  }
  config = {
    bucket = "my-company-terraform-state"
    key = "${path_relative_to_include()}/terraform.tfstate"
    region = "us-east-1"
    encrypt = true
    dynamodb_table = "terraform-lock-table"
  }
}

Hàm path_relative_to_include() thực sự là một cứu cánh. Nếu bạn đang làm việc trong live/dev/vpc, nó sẽ tự động đặt S3 key của bạn thành dev/vpc/terraform.tfstate. Không còn lỗi đường dẫn thủ công nữa.

Bước 2: Định nghĩa môi trường của bạn

Tệp live/dev/vpc/terragrunt.hcl của bạn giờ đây cực kỳ gọn nhẹ. Nó chỉ cần trỏ đến module và cung cấp các đầu vào (input).

# live/dev/vpc/terragrunt.hcl
include "root" {
  path = find_in_parent_folders()
}

terraform {
  source = "../../../modules/vpc"
}

inputs = {
  env           = "dev"
  vpc_cidr      = "10.0.0.0/16"
  enable_nat_gw = false
}

Bước 3: Kết nối các thành phần bằng Dependencies

Đây là nơi Terragrunt vượt trội hơn Terraform thuần túy. Nếu instance EC2 của bạn cần VPC ID, bạn không cần phải viết cứng (hardcode) hoặc sử dụng các data source phức tạp.

# live/dev/ec2/terragrunt.hcl
dependency "vpc" {
  config_path = "../vpc"
}

inputs = {
  vpc_id    = dependency.vpc.outputs.vpc_id
  subnet_id = dependency.vpc.outputs.public_subnets[0]
  instance_type = "t3.micro"
}

Terragrunt lấy các output từ module VPC và đưa chúng vào module EC2. Nếu VPC chưa được triển khai, Terragrunt sẽ thông báo cho bạn hoặc tự động triển khai nó nếu bạn sử dụng lệnh run-all.

Mở rộng IaC mà không mất đi sự tỉnh táo

Trong giai đoạn đầu sự nghiệp, tôi từng nghĩ Terraform tiêu chuẩn là đủ cho mọi tác vụ. Tuy nhiên, khi số lượng microservices tăng lên, phương pháp “sao chép-dán” đã dẫn đến vài lần hệ thống ngừng hoạt động ở production do sai lệch subnet ID. Chuyển sang Terragrunt đã loại bỏ hoàn toàn loại lỗi này bằng cách thực thi một nguồn chân lý (source of truth) duy nhất.

Nếu bạn là một phần của nhóm hoặc đang quản lý nhiều hơn một môi trường, Terragrunt không chỉ là một tiện ích “có thì tốt”. Nó là một công cụ thiết yếu để giữ cho mã nguồn sạch sẽ và việc triển khai có thể dự đoán được. Hãy bắt đầu bằng cách di chuyển cấu hình backend trước. Bạn sẽ nhận thấy sự khác biệt trong quy trình làm việc hàng ngày của mình gần như ngay lập tức.

Share: