เหตุที่ต้อง debug ดูรายละเอียดการทำงาน Terraform Provider

อยากใช้ Terraform Provider ตัวที่ชื่อว่า ansible/ansible แต่เรียกใช้งานได้รอบเดียว พอเรียกอีกครั้ง ยังไงก็ใช้ไม่ได้

author image
drs

Technology evangelistic advocacy

Posted on 2023-12-22 00:51:47 +0700

Terraform vs Ansible

Terraform กับ Ansible เป็นเครื่องมือสาย Infrastructure as Code ที่มีความโดดเต่นทั้ง 2 ตัว แต่ว่าแต่ละตัวต่างมีข้อดีในคนละมุม ส่วนตัวผมเอง ก็ใช้งานทั้ง 2 ตัวพร้อม ๆ กัน โดยให้ Terraform ทำงานในช่วง provisioning และให้ Ansible ทำงานในช่วง configuration ผมเลือกให้ Terraform เป็นตัวที่ไปเรียกใช้งาน Ansible หลังจากที่ทำการ provision ทรัพยากรต่าง ๆ เสร็จเรียบร้อยแล้ว ซึ่งท่าปกติที่ใช้ให้เครื่องมือทั้ง 2 ตัวนี้ทำงานร่วมกัน ก็คือเลือกใช้ local-remote provisioner อธิบายสั้น ๆ ก็คือ ให้ Terraform ไปเรียกใช้คำสั่งที่ระบุในกรณีนี้ก็จะเป็นคำสั่ง ansible-playbook ที่เครื่องที่เรียกใช้งาน terraform  

เมื่อ 6-7 เดือนที่แล้ว เห็นว่ามีบทความ ประกาศออกมาว่ามี Ansible Provider สำหรับ Terraform ให้เรียกใช้งานได้ และ provider ตัวนี้ถูกพัฒนาโดย community ที่มี Ansible เป็นเจ้าภาพ (ที่ผมเข้าใจอย่างนี้เพราะว่า Repository ของ provider อยู่ที่ github ของ Ansible)
ความดีงามของ Ansible provider ก็คือสามารถทำงานได้ทั้ง 2 มุม

  1. ในมุมที่ Terraform ไปเรียกใช้งาน Ansible ก็จะทำเราเรียกใช้งาน Ansible Playbook มองว่าเป็น resource ชนิดหนึ่งใน Terraform เลย
  2. ในมุมที่ Terraform ทำหน้าที่สร้าง inventory ของ resource ที่ Terraform สร้าง แล้วให้ Ansible เอาข้อมูล State file ไปใช้งานเป็น inventory ได้ เช่น ในกรณีที่ให้ Terraform ไปสร้าง EC2 ใน AWS หลังที่สร้างเสร็จเรียบร้อย เราสามารเรียกใช้งาน Ansible Provider ให้เขียนข้อมูล IP Address ที่ได้รับมาจาก AWS หลังจากสร้างเสร็จมาเขียนลงใน State file เพื่อให้เป็น Inventory ได้

เจอ Bug ใน Ansible Provider

ผมเป็นคนหนึ่งที่ตื่นเต้นกับการมาของ Ansible Provider และเริ่มทดสอบตั้งแต่ช่วงแรก ๆ ที่ได้ทราบ ... ผลก็คือ ใช้งานได้ไม่กี่ครั้ง แล้วก็ใช้งานไม่ได้อีกเลย มันเกิดอะไรขึ้น !!! ผมใช้เวลาอยู่หลายสัปดาห์ กว่าจะได้คำตอบ แต่ในบทความนี้ผมกระโดดข้ามไปถึงตอนที่ผมหาคำตอบได้แล้ว และสรุปวิธีการสร้างให้เกิดปัญหาประมาณนี้ เพื่อพาผู้อ่านเดินทางไปหาวิธีการที่ผมใช้ในการหาคำตอบ

  1. ใน Terraform Configuration มีการเรียกใช้งาน provider 2 ตัวคือ DigitalOcean และ Ansible เพื่อทดสองสร้าง ​Droplet (Virtual Machine) ใน DigitalOcean และเรียกใช้งาน Ansible Playbook หลังจากที่สร้าง Droplet เสร็จ ตามตัวอย่าง Terraform Configuration ก็จะประมาณนี้

  2. terraform {
      required_providers {
        ansible = {
          source  = "ansible/ansible"
        }
        digitalocean = {
          source = "digitalocean/digitalocean"
        }
      }
    }
    
    resource "digitalocean_droplet" "web" {
    [... truncated output ...]
    }
    
    resource "ansible_playbook" "playbook" {
      playbook   = "myplaybook.yaml"
      name       = digitalocean_droplet.web.ipv4_address
    }
    

    ถ้า Ansible Playbook ไม่มีข้อผิดพลาดอะไร ทุกอย่างก็จะทำงานได้อย่างปกติ

  3. ผมแก้ไข Ansible Playbook แต่พลาด!!! เขียนผิดโดยไม่รู้ตัว หลังจากแก้ไขเสร็จ ก็เรียก terraform apply ... ก็จะพบว่า error

  4. ansible_playbook.playbook: Creating...
    ╷
    │ Error: Plugin did not respond
    │
    │   with ansible_playbook.playbook,
    │   on main.tf line 45, in resource "ansible_playbook" "playbook":
    │   45: resource "ansible_playbook" "playbook" {
    │
    │ The plugin encountered an error, and failed to respond to the plugin.(*GRPCProvider).ApplyResourceChange call. The plugin logs may contain more details.
    ╵
    

    จาก error ดูเหมือนจะไม่ได้บอกอะไรที่พอจะเป็นประโยชน์เลย ดูไม่รู้เลยว่า จะไปแก้ไขที่ไหนดี ผมก็พยายามไล่เรียงส่วนต่าง ๆ ทั้งใน Terraform Configuration ทั้งใน Ansible Playbook จนกระทั่งมั่นใจว่า ไม่มีอะไรผิดพลาดอีกแล้ว ... แต่จะเรียก terraform apply อีกกี่ครั้งก็ไม่สำเร็จ

Debugging Terraform

ผมใช้เวลาหลายวันสาละวนอยู่กับความพยายามในการแก้ปัญหา แบบติดกับดักความคิดของตัวเอง พยายามไปไล่เรียงดู Terrform Configuration และ Ansible Playbook แก้โน่นนี่นั่น ลองไปเรื่อย โดยละเลยข้อความสำคัญใน error ที่ได้มา The plugin logs may contain more details. ... เครื่องหมายคำถามอันโต ๆ ที่โผล่ขึ้นมาคือ "logs ของ plugin อยู่ที่ไหน ถ้าไม่มีต้องทำยังไง" ... สุดท้ายจากเอกสารของ HashiCorp ก็ให้คำตอบกับเราว่าการดูรายละเอียดการทำงานของ Terraform ไม่ยากเลย แค่กำหนดให้มี environment variable ก็สามารถดูรายละเอียดการทำงานของ Terraform ได้แล้ว ตัวแปรที่ต้องกำหนด ก็คือ

  • TF_LOG เป็นการกำหนดรายละเอียดของการทำงานที่จะให้แสดงผลออกมาแบ่งเป็นลำดับรายละเอียดจากมากไปหาน้อยดังนี้ TRACE, DEBUG, INFO, WARN และ ERROR
  • TF_LOG_PATH ในกรณีที่ต้องการให้รายละเอียดของการทำงานเก็บในรูปแบบของไฟล์ สามารถกำหนดชื่อและ path ในการจัดเก็บได้ที่ตัวแปรนี้

ในที่สุดก็ทราบสาเหตุที่ใช้งานไม่ได้

เริ่มจาก กำหนดค่าตัวแปล TF_LOG และ TF_LOG_PATH ก่อนเรียกคำสั่ง terraform apply ... หลังจากที่คำสั่ง terraform ทำงานเสร็จ จะพบ log ตามชื่อที่กำหนดไว้

> export TF_LOG="DEBUG"
> export TF_LOG_PATH="./tf.log"
> terraform apply --auto-approve
[... truncated output ...]
> ls -la tf.log
-rw-r--r--@ 1 drs  staff  38383 Dec 23 11:07 tf.log

ในรายละเอียดของ tf.log ผมพบข้อความอยู่ช่วงหนึ่งซึ่งเป็นชื่อที่ ansible_playbook ใน Ansible Provider กำลังทำงานอยู่ และพบความแปลก ๆ ที่แสดงออกมาใน log ก็คือ ตัว ansible_playbook ให้ข้อความออกมาว่า ใช้ไฟล์ทึ่ขึ้นต้นด้วย .inventory และลงท้ายด้วย .ini ทุกไฟล์ใน temporary path มาใช้เป็น inventory ของ Ansible Playbook และนี่ก็เป็นสาเหตุที่เกิดข้อผิดพลาดในการทำงาน

> cat tf.log
[... truncated output ...]
2023-12-23T11:06:59.208+0700 [INFO]  Starting apply for ansible_playbook.playbook
2023-12-23T11:06:59.209+0700 [DEBUG] ansible_playbook.playbook: applying the planned Create change
2023-12-23T11:06:59.209+0700 [DEBUG] provider.terraform-provider-ansible_v1.1.0: 2023/12/23 11:06:59 [DEBUG] setting computed for "args" from ComputedKeys
2023-12-23T11:06:59.209+0700 [DEBUG] provider.terraform-provider-ansible_v1.1.0: 2023/12/23 11:06:59 [ANSIBLE ARGS]:
2023-12-23T11:06:59.209+0700 [DEBUG] provider.terraform-provider-ansible_v1.1.0: 2023/12/23 11:06:59 [-e hostname=128.199.157.138 myplaybook.yaml]
2023-12-23T11:06:59.209+0700 [DEBUG] provider.terraform-provider-ansible_v1.1.0: 2023/12/23 11:06:59 LOG [ansible-playbook]: playbook = myplaybook.yaml
2023-12-23T11:06:59.209+0700 [DEBUG] provider.terraform-provider-ansible_v1.1.0: 2023/12/23 11:06:59 Inventory /var/folders/z4/lzyb9r096d3dhx_zyz_x7ywr0000gn/T/.inventory-752087978.ini was created
2023-12-23T11:06:59.209+0700 [DEBUG] provider.terraform-provider-ansible_v1.1.0: 2023/12/23 11:06:59 Temp Inventory File: /var/folders/z4/lzyb9r096d3dhx_zyz_x7ywr0000gn/T/.inventory-752087978.ini
2023-12-23T11:06:59.209+0700 [DEBUG] provider.terraform-provider-ansible_v1.1.0: 2023/12/23 11:06:59 [TEMP DIR]: /var/folders/z4/lzyb9r096d3dhx_zyz_x7ywr0000gn/T/
2023-12-23T11:06:59.210+0700 [DEBUG] provider.terraform-provider-ansible_v1.1.0: 2023/12/23 11:06:59 [INVENTORIES]:
2023-12-23T11:06:59.210+0700 [DEBUG] provider.terraform-provider-ansible_v1.1.0: 2023/12/23 11:06:59 [/var/folders/z4/lzyb9r096d3dhx_zyz_x7ywr0000gn/T/.inventory-2047090903.ini /var/folders/z4/lzyb9r096d3dhx_zyz_x7ywr0000gn/T/.inventory-2118709326.ini /var/folders/z4/lzyb9r096d3dhx_zyz_x7ywr0000gn/T/.inventory-2625742638.ini /var/folders/z4/lzyb9r096d3dhx_zyz_x7ywr0000gn/T/.inventory-3985336357.ini /var/folders/z4/lzyb9r096d3dhx_zyz_x7ywr0000gn/T/.inventory-752087978.ini /var/folders/z4/lzyb9r096d3dhx_zyz_x7ywr0000gn/T/.inventory-963862841.ini /var/folders/z4/lzyb9r096d3dhx_zyz_x7ywr0000gn/T/.inventory-977766482.ini]
2023-12-23T11:07:09.791+0700 [DEBUG] provider.terraform-provider-ansible_v1.1.0: 2023/12/23 11:07:09 ERROR [ansible-playbook]: couldn't run ansible-playbook
2023-12-23T11:07:09.791+0700 [DEBUG] provider.terraform-provider-ansible_v1.1.0: myplaybook.yaml! There may be an error within your playbook.
2023-12-23T11:07:09.791+0700 [DEBUG] provider.terraform-provider-ansible_v1.1.0: exit status 4
[... truncated output ...]

ตามไปไล่ล่าต้นตอของสาเหตุ

โดยปกติข้อมูลของ provider ใน https://registry.terraform.io/ ก็จะมี source code ให้ดูได้อยู่แล้ว ผมก็เลยตามไปที่ repository ของ Ansible Provider ไปไล่เรียงดู source code ในนั้นกับน้องเติ้ง อีกคนที่ช่วยกันหา ... ในที่สุดน้องเติ้งไปพบต้นตอของหาเหตุอยู่ที่ branch ที่มี tag v1.1.0 ที่ไฟล์ provider/resource_playbook.go ใน func resourcePlaybookUpdate พบว่า มีการเรียก function providerutils.GetAllInventories ในบรรทัดที่ 474 ซึ่งเป็นการไปอ่านรายชื่อไฟล์ที่ขึ้นต้นด้วย .inventory- ทั้งหมดใน temp directory มาใช้งานเป็น inventory

เราก็ยังไปเจออีกว่า ใน main branch เราไม่พบการเรียก function นี้ใน provider/resource_playbook.go

tag v1.1.0/terraform-provider-ansible/provider/resource_playbook.go

[... truncated outpout ...]
    inventoryFileNamePrefix := ".inventory-"

	if tempInventoryFile == "" {
		tempInventoryFile = providerutils.BuildPlaybookInventory(inventoryFileNamePrefix+"*.ini", name, -1, groups)
		if err := data.Set("temp_inventory_file", tempInventoryFile); err != nil {
			log.Fatal("ERROR [ansible-playbook]: couldn't set 'temp_inventory_file'!")
		}
	}

	log.Printf("Temp Inventory File: %s", tempInventoryFile)

	// Get all available temp inventories and pass them as args
	inventories := providerutils.GetAllInventories(inventoryFileNamePrefix)

	log.Print("[INVENTORIES]:")
	log.Print(inventories)
[... truncated outpout ...]

นั่นแปลว่า ในรุ่นถัดไปของ Ansible Provider จะไม่มีปัญหานี้แล้ว แต่ที่น่าแปลกใจก็คือในวันที่เขียนเรื่องนี้ 23 ธันวาตม 2566 ผ่านไป 7 เดือนแล้วก็ยังไม่มีรุ่นถัดไปที่แก้ปัญหานี้ออกมา

แนวทางการแก้ไขปัญหา

ทุกปัญหาย่อมมีทางออก ผมก็เลยนั่งคิดว่า จะแก้ปัญหาชั่วคราวระหว่างรอ version ใหม่อย่างไรดี คำตอบที่ได้ก็คือ

  • ลบไฟล์ .inventory-* ใน temp directory ทุกครั้งก็เรียกคำสั่ง terraform apply
  • หยิบเอา source code ใน main branch มา compile Ansible provider มาใช้เองก่อนชั่วคราว (more detail in the next article)

Reference:



cover

Share on

Tags

Human knowledge belongs to the world

a line from the movie "Antitrust"