Thao Tác State: import Block, state mv, state rm
State đã an toàn trên S3, nhưng làm việc với hạ tầng thật sinh ra những tình huống mà chỉ apply không giải được. Bạn có một bucket dựng tay từ trước và giờ muốn Terraform quản lý nó. Bạn đổi tên một resource trong code và không muốn Terraform xóa-tạo-lại. Bạn muốn ngừng quản lý một resource nhưng giữ nó tồn tại. Ba việc này đều thao tác trực tiếp lên state, và bài này làm cả ba trên một bucket thật.
Mục tiêu
Nắm ba thao tác state hay dùng: import block để nhận quản lý resource có sẵn (kèm tự sinh cấu hình), state mv để đổi tên/di chuyển resource trong state, và state rm để gỡ resource khỏi quản lý mà không đụng hạ tầng thật.
Bài toán: hạ tầng có sẵn, chưa do Terraform quản lý
Rất hiếm khi bạn bắt đầu một dự án với hạ tầng trống trơn. Thường đã có sẵn thứ gì đó dựng tay: một bucket, một VPC, một security group ai đó tạo từ console năm ngoái. Terraform không biết chúng tồn tại vì chúng không có trong state. Muốn đưa chúng vào quản lý mà không xóa đi dựng lại (việc không thể với hạ tầng đang chạy), ta dùng import.
Để mô phỏng, tạo một bucket bằng AWS CLI, coi như nó là di sản có sẵn:
$ aws s3api create-bucket --bucket tf-series-bai7-preexisting-1779678443 \
--region ap-southeast-1 \
--create-bucket-configuration LocationConstraint=ap-southeast-1
import block: khai báo thay vì gõ lệnh
Trước Terraform 1.5, import là một lệnh thủ công (terraform import địa_chỉ id) và bạn phải tự viết resource block khớp với hạ tầng thật, sai một li là apply đòi sửa. Từ 1.5 có config-driven import: khai báo ý định bằng một import block ngay trong cấu hình, để Terraform lo phần còn lại.
import {
to = aws_s3_bucket.adopted
id = "tf-series-bai7-preexisting-1779678443"
}
to là địa chỉ resource sẽ mang trong cấu hình, id là định danh thật trên AWS (với bucket S3 thì id chính là tên bucket). Để ý: ta chưa viết block resource "aws_s3_bucket" "adopted" nào cả. Đó là chỗ tính năng sinh cấu hình tự động vào cuộc.
Tự sinh cấu hình với -generate-config-out
Theo tài liệu, "bạn có thể chỉ viết import block rồi chạy terraform plan với cờ generate-config-out để Terraform sinh ra các resource block". Chạy:
$ terraform plan -generate-config-out=generated.tf
aws_s3_bucket.adopted: Preparing import... [id=tf-series-bai7-preexisting-1779678443]
aws_s3_bucket.adopted: Refreshing state... [id=tf-series-bai7-preexisting-1779678443]
# aws_s3_bucket.adopted will be imported
# (config will be generated)
resource "aws_s3_bucket" "adopted" {
arn = "arn:aws:s3:::tf-series-bai7-preexisting-1779678443"
bucket = "tf-series-bai7-preexisting-1779678443"
...
}
Terraform đọc resource thật trên AWS và ghi cấu hình tương ứng ra file generated.tf:
# __generated__ by Terraform
# Please review these resources and move them into your main configuration files.
# __generated__ by Terraform from "tf-series-bai7-preexisting-1779678443"
resource "aws_s3_bucket" "adopted" {
bucket = "tf-series-bai7-preexisting-1779678443"
bucket_namespace = "global"
force_destroy = false
object_lock_enabled = false
region = "ap-southeast-1"
tags = {}
tags_all = {}
}
Dòng comment đầu file nhắc đúng việc cần làm: xem lại rồi gộp vào cấu hình chính. Cấu hình sinh tự động không phải lúc nào cũng hoàn hảo (có thể thừa trường mặc định, thiếu cấu trúc bạn muốn), nên nó là điểm khởi đầu để bạn dọn chứ không phải bản cuối. Nhưng với hạ tầng phức tạp, được Terraform viết hộ bản nháp nhanh hơn nhiều so với gõ tay từ đầu.
apply: thực hiện import
Có cấu hình rồi, apply đưa resource vào state:
$ terraform apply -auto-approve
aws_s3_bucket.adopted: Importing... [id=tf-series-bai7-preexisting-1779678443]
aws_s3_bucket.adopted: Import complete [id=tf-series-bai7-preexisting-1779678443]
Apply complete! Resources: 1 imported, 0 added, 0 changed, 0 destroyed.
$ terraform state list
aws_s3_bucket.adopted
Dòng tổng kết ghi 1 imported, 0 added — không tạo gì mới, chỉ đưa bucket có sẵn vào quản lý. Từ giờ Terraform coi bucket này như resource của mình. Sau khi import xong, có thể xóa import block đi (nó đã làm xong việc); để lại cũng không sao, Terraform thấy resource đã trong state thì bỏ qua. Với nhiều resource cần import một lượt, bạn viết nhiều import block rồi import hàng loạt trong một lần apply.
state mv: đổi tên không phá hạ tầng
Giả sử bạn nhận ra tên adopted không hợp, muốn đổi thành data. Nếu chỉ sửa tên trong code rồi apply, Terraform sẽ thấy adopted biến mất và data xuất hiện, hiểu nhầm là "xóa cái cũ, tạo cái mới" — với bucket đang chứa dữ liệu thì đó là thảm họa. state mv đổi ánh xạ trong state để cùng một resource thật giờ mang địa chỉ mới, không xóa-tạo gì.
$ terraform state mv aws_s3_bucket.adopted aws_s3_bucket.data
Move "aws_s3_bucket.adopted" to "aws_s3_bucket.data"
Successfully moved 1 object(s).
$ terraform state list
aws_s3_bucket.data
Tài liệu mô tả lệnh này "đổi binding trong state để object remote có sẵn gắn với instance resource mới". Dùng nó khi đổi tên resource, hoặc khi gom resource vào module (Part IV). Lưu ý: state mv chỉ sửa state, bạn vẫn phải đổi tên trong code cho khớp. Bài 16 sẽ giới thiệu moved block — cách khai báo việc đổi tên ngay trong cấu hình, sạch hơn gõ lệnh state mv tay.
state rm: ngừng quản lý, giữ nguyên hạ tầng
Đôi khi bạn muốn Terraform quên một resource đi mà không xóa nó — ví dụ tách resource đó sang một cấu hình khác quản lý. state rm gỡ nó khỏi state:
$ terraform state rm aws_s3_bucket.data
Removed aws_s3_bucket.data
Successfully removed 1 resource instance(s).
$ terraform state list
$ # rỗng — Terraform không còn quản lý gì
Bucket thật thì sao? Vẫn còn nguyên:
$ aws s3api head-bucket --bucket tf-series-bai7-preexisting-1779678443
{
"BucketArn": "arn:aws:s3:::tf-series-bai7-preexisting-1779678443",
"BucketRegion": "ap-southeast-1",
"AccessPointAlias": false
}
Đây là khác biệt cốt lõi giữa state rm và destroy: destroy xóa hạ tầng thật, state rm chỉ cắt sợi dây giữa Terraform và resource. Sau state rm, resource trở thành "không ai quản lý" từ góc nhìn Terraform — bạn tự chịu trách nhiệm với nó (import lại nơi khác, hoặc xóa tay). Bài 16 cũng có removed block, cách khai báo việc gỡ-khỏi-state này trong cấu hình thay cho lệnh tay.
🧹 Dọn dẹp
Vì đã state rm nên Terraform không còn quản lý bucket — terraform destroy sẽ không đụng tới nó. Phải xóa tay:
$ aws s3 rb s3://tf-series-bai7-preexisting-1779678443 --force
remove_bucket: tf-series-bai7-preexisting-1779678443
Đây cũng là một bài học thực tế: state rm để lại resource mồ côi mà bạn dễ quên, tốn tiền âm thầm. Dùng nó có chủ đích, và ghi nhớ mình vừa tách cái gì ra.
Tổng kết
Ba thao tác state cho ba nhu cầu khác nhau. import block (1.5) đưa hạ tầng có sẵn vào quản lý bằng cách khai báo to/id, kết hợp plan -generate-config-out để Terraform viết hộ bản nháp cấu hình. state mv đổi địa chỉ một resource trong state mà không xóa-tạo lại, dùng khi đổi tên hoặc gom vào module. state rm cắt resource khỏi quản lý nhưng giữ nó tồn tại thật, dễ để lại tài nguyên mồ côi nên phải cẩn thận. Cả ba thao tác đụng thẳng vào state, nên luôn làm khi state đã được khóa và sao lưu (versioning ở bài 6 giúp ích lúc này).
State chứa secret là chuyện ta đã nhắc nhiều lần mà chưa xử lý. Bài 8 khép Part II bằng đúng vấn đề đó: làm sao truyền mật khẩu, khóa API vào Terraform mà không để chúng rơi vào state hay plan — dùng sensitive, và mạnh hơn là ephemeral resources cùng write-only arguments, những thứ mới có ở các bản Terraform gần đây.