แก้ไข Terraform Code แต่ Apply ไม่ได้ อาจเพราะ DependencyViolationError

Nopnithi Khaokaew (Game)
3 min readNov 18, 2022

--

DependencyViolationError บน Terraform คืออะไร? เกิดขึ้นได้ยังไง? และมีวิธีแก้ไขหรือป้องกันได้อย่างไรบ้าง? ลองมาดูครับ

หัวข้อที่พูดถึง

  1. DependencyViolation Error คืออะไร?
  2. ตัวอย่างการเกิด DependencyViolation Error
  3. วิธีป้องกัน 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.
  1. สร้าง security group A ใหม่โดยเปลี่ยน name แล้วเรียบร้อย
  2. แก้ไข security group B ให้ reference ไปหา security group A ใหม่แทน
  3. ลบ security group A เก่าทิ้ง

ทีนี้ก็เป็นเรื่องของประสบการณ์แล้วครับ หวังว่าจะนำเทคนิคนี้ไปใช้ในการเขียน Terraform นะครับ หรือใครเจออะไรก็มาแชร์กันได้ครับ

--

--

Nopnithi Khaokaew (Game)
Nopnithi Khaokaew (Game)

Written by Nopnithi Khaokaew (Game)

Cloud Solutions Architect & Hobbyist Developer | 8x AWS/Azure Certified, CKA, CKAD, CKS, 2x HashiCorp Certified (Terraform, Vault), etc.

No responses yet