แก้ไข Terraform Code แต่ Apply ไม่ได้ อาจเพราะ DependencyViolationError
DependencyViolationError บน Terraform คืออะไร? เกิดขึ้นได้ยังไง? และมีวิธีแก้ไขหรือป้องกันได้อย่างไรบ้าง? ลองมาดูครับ
หัวข้อที่พูดถึง
- DependencyViolation Error คืออะไร?
- ตัวอย่างการเกิด DependencyViolation Error
- วิธีป้องกัน Error deleting xxx: DependencyViolation
DependencyViolation Error คืออะไร?
หลายคนที่ใช้ Terraform ในการสร้าง resource บน cloud บางครั้งอาจจะเคยเจอกับ error ประมาณนี้เวลาที่มีการแก้ไข Terraform code แล้ว terraform apply
Error: Error deleting xxx: DependencyViolation: ...
สาเหตุเกิดจาก Terraform พยายามจะลบ resource A แต่ลบไม่ได้ เพราะมี resource อื่น reference มาหา resource A อยู่ (เรียกว่ามันผูกกันอยู่แล้วกัน) ทีนี้มันก็จะค้างไปจนกระทั่งถึง timeout แล้วจึงโชว์ error ขึ้นมา
ตัวอย่างการเกิด DependencyViolation Error
จากรูปด้านบน ผมจะลองสร้างสถานการณ์ให้เกิด DependencyViolation error ขึ้น โดยสร้าง AWS security group 2 ตัว และให้ security group B มี rule ที่ allow SSH ให้ security group A เข้ามาได้
เริ่มจากสร้าง Security Group A (a1) ขึ้นมา
resource "aws_security_group" "a" {
name = "a1"
tags = {
Name = "a1"
Terraform = "true"
}
timeouts {
delete = "30s"
}
}
ทีนี้แก้ไข name ใน Security Group A (a1 เป็น a2)
resource "aws_security_group" "a" {
name = "a2"
tags = {
Name = "a2"
Terraform = "true"
}
timeouts {
delete = "30s"
}
}
จะได้
aws_security_group.a: Destroying... [id=sg-0afbd74866b3b4644]
aws_security_group.a: Destruction complete after 1s
aws_security_group.a: Creating...
aws_security_group.a: Creation complete after 1s [id=sg-0d0c00b3f7ee16b00]
Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
จะเห็นว่า Terraform มันลบ security group A (a1) ก่อน จากนั้นค่อยสร้าง security group A (a2) ขึ้นมาใหม่ เพราะ argument name
ไม่สามารถแก้ไข ทำให้ Terraform ต้องลบแล้วสร้างขึ้นใหม่ (resource บางอย่างบน cloud สร้างแล้วแก้ไขได้ แต่บางอย่างทำไม่ได้)
คราวนี้ผมจะสร้างปัญหาขึ้นโดยจะสร้าง security group B (b1) และ reference ไปหา security group A (a2) และอย่างที่หลายคนรู้คือเราสามารถใช้ security group ตัวอื่นเป็น source ใน rule ได้เพื่อ allow หรือ deny traffic จาก security group นั้น
resource "aws_security_group" "a" {
name = "a2"
tags = {
Name = "a2"
Terraform = "true"
}
timeouts {
delete = "30s"
}
}
resource "aws_security_group" "b" {
name = "b1"
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
security_groups = [aws_security_group.a.id] # reference หา SG A
}
tags = {
Name = "b1"
Terraform = "true"
}
}
และเมื่อผม terraform apply
ไปก็ไม่ได้ติดปัญหาอะไรครับ จนกระทั่ง…
วันดีคืนดีผมมีเหตุให้ต้องแก้ไข security group A ขึ้นมา (ซึ่งดันเป็นการแก้ไขที่ต้องใช้การลบ resource แล้วสร้างขึ้นใหม่)
resource "aws_security_group" "a" {
name = "a3"
tags = {
Name = "a3"
Terraform = "true"
}
timeouts {
delete = "30s"
}
}
การแก้ name
ทำให้ Terraform จะต้องลบ security group A ก่อน จากนั้นจะสร้างตัวใหม่ขึ้นมาแทน และ DependencyViolation error ก็เกิดขึ้นครับ
Plan: 1 to add, 1 to change, 1 to destroy.
aws_security_group.a: Destroying... [id=sg-0d0c00b3f7ee16b00]
aws_security_group.a: Still destroying... [id=sg-0d0c00b3f7ee16b00, 10s elapsed]
aws_security_group.a: Still destroying... [id=sg-0d0c00b3f7ee16b00, 20s elapsed]
aws_security_group.a: Still destroying... [id=sg-0d0c00b3f7ee16b00, 30s elapsed]
╷
│ Error: Error deleting security group: DependencyViolation: resource sg-0d0c00b3f7ee16b00 has a dependent object
│ status code: 400, request id: 31bf51a4-3735-4a19-9cdb-2bc2d13b9bc7
เพราะอะไร? เพราะมันพยายามลบ security group A แต่ไม่สามารถทำได้ เนื่องจากถูก reference มาจาก security group B อยู่ และมันก็จะรอไปจนกระทั่งถึง timeout (ผมปรับเหลือ 30 วินาที) ก่อนที่จะ raise error ขึ้นมาตาม output ด้านบน
วิธีป้องกัน Error deleting xxx: DependencyViolation
เราจะใช้ lifecycle
meta-argument มาช่วย
lifecycle {
create_before_destroy = true
}
วิธีการคือหากมีเหตุสุ่มเสี่ยงที่จะต้องลบ resource ที่ผูกกันอยู่แบบนี้ด้วยความไม่ตั้งใจ (โดยเฉพาะพวก security group หรือ auto scaling group) เราจะสร้าง resource ตัวใหม่ขึ้นมาแทน แล้วค่อยลบตัวเก่า ถ้ายังไม่เข้าใจลองดูตัวอย่างประกอบครับ
resource "aws_security_group" "a" {
name_prefix = "a2-"
tags = {
Name = "a2"
Terraform = "true"
}
timeouts {
delete = "30s"
}
lifecycle {
create_before_destroy = true
}
}
resource "aws_security_group" "b" {
name = "b1"
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
security_groups = [aws_security_group.a.id]
}
tags = {
Name = "b1"
Terraform = "true"
}
}
จาก code ด้านบน Terraform จะสร้าง security group A (a3) ตัวใหม่ขึ้นมาก่อน จากนั้นไปแก้ security group B (b1) ให้ย้ายมา reference หา security group A (a3) ตัวใหม่ จากนั้นค่อยลบ security group A (a2) ตัวเก่าทิ้ง
และผมเปลี่ยน argument name
เป็น name_prefix
ด้วย ซึ่งเป็นการเติม suffix เข้ามาในชื่อของ security group จากที่ควรจะเป็น a2
กลายเป็น a2-xxxxxxxxxxx
ตรงนี้จะช่วยป้องกันชื่อซ้ำกันระหว่าง resource เก่าและใหม่
และเมื่อลอง terraform apply
จะได้ output ประมาณนี้ ซึ่งไม่เจอปัญหาเดิมแล้ว
Plan: 1 to add, 1 to change, 1 to destroy.
aws_security_group.a: Creating...
aws_security_group.a: Creation complete after 1s [id=sg-01a10acff559fbb2d]
aws_security_group.b: Modifying... [id=sg-04ae37cf73a17dfca]
aws_security_group.b: Modifications complete after 0s [id=sg-04ae37cf73a17dfca]
aws_security_group.a (deposed object 1f5af6a0): Destroying... [id=sg-0aaf4c6ee7c858c17]
aws_security_group.a: Destruction complete after 1s
Apply complete! Resources: 1 added, 1 changed, 1 destroyed.
- สร้าง security group A ใหม่โดยเปลี่ยน
name
แล้วเรียบร้อย - แก้ไข security group B ให้ reference ไปหา security group A ใหม่แทน
- ลบ security group A เก่าทิ้ง
ทีนี้ก็เป็นเรื่องของประสบการณ์แล้วครับ หวังว่าจะนำเทคนิคนี้ไปใช้ในการเขียน Terraform นะครับ หรือใครเจออะไรก็มาแชร์กันได้ครับ