06/03/2022 - Week 2 - GCE multi LocalSSD with Terraform
Bắt đầu với một bài toán cơ bản, viết một module hỗ trợ việc tạo Commpute resource trong GCP. Khá đơn giản, code minh họa như sau:
resource "google_compute_instance" "this" {
project = var.project_id
count = var.enabled ? 1 : 0
name = "${local.name}-${count.index}"
machine_type = var.machine_type
zone = var.zone
deletion_protection = var.deletion_protection
min_cpu_platform = var.min_cpu_platform
description = var.description
allow_stopping_for_update = var.allow_stopping
boot_disk {
auto_delete = var.auto_delete
initialize_params {
image = var.machine_image
size = var.disk_size_gb
type = var.disk_type
labels = merge({ "managed_by" = "terraform" }, local.disk_labels)
}
}
labels = merge({ "managed_by" = "terraform" }, local.default_labels)
network_interface {
subnetwork = var.subnetwork
subnetwork_project = var.subnetwork_project
network_ip = var.network_ip
}
lifecycle {
create_before_destroy = "true"
ignore_changes = [attached_disk]
}
scheduling {
preemptible = var.preemptible
automatic_restart = !var.preemptible
on_host_maintenance = local.on_host_maintenance
}
tags = var.tags
service_account {
email = "[email protected]"
scopes = ["cloud-platform"]
}
}
variables.tf
#
# General
#
variable "project_id" {
description = "The ID of the project in which the resource belongs"
}
variable "product" {
description = "The product of this resource"
type = string
}
variable "env" {
description = "Environment of this resource"
type = string
default = "prod"
}
# sql/kafka/postgres/redis
variable "service" {
description = "The service name of this resource"
type = string
}
# eg:
# Redis is a service, but we can use redis as a caching or database
variable "role" {
description = "The Role of service"
type = string
}
local.tf
resource "random_string" "this" {
length = 5
special = true
override_special = "/@£$"
lower = true
min_lower = 5
}
locals {
host_ip = element(split(".", var.network_ip), 3)
net_ip = element(split(".", var.private_ip), 2)
name = "${var.product}-${var.env}-${var.service}-${var.role}-${random_string.this.id}-${local.net_ip}-${local.host_ip}"
on_host_maintenance = (
var.preemptible ? "TERMINATE" : var.on_host_maintenance
)
default_labels = {
project_id = var.project_id
env = var.env
gcp_service = "compute"
product = var.product
service = var.service
role = var.role
}
disk_labels = {
project_id = var.project_id
env = var.env
gcp_service = "disk"
product = var.product
service = var.service
role = var.role
}
}
Lưu ý code trên chỉ là code minh hoạ, nên có thể thiếu một số variables và có bug, tuy nhiên ý tưởng chính là tạo được GCE resource, enforce resource đó có đầy đủ labels, và naming của resource cũng được enforce theo một convention cố định. Chuyện này đem lại 2 lợi ích:
- Quản lý chi phí dễ dàng dựa trên labeling
- Dễ dàng phân biệt được resource thuộc loại gì, môi trường nào, chạy dịch vụ gì khi nhìn trên Google console
Sau khi tạo được GCE resource rồi, tui bắt đầu suy nghĩ đến việc làm thế nào để attach LocalSSD vào resource này hoặc tạo GCE hỗ trợ gắn LocalSSD. Lục trong google_compute_instance thì thấy có vài ý như sau:
Có một đoạn mã minh họa
// Local SSD disk
scratch_disk {
interface = "SCSI"
}
Phần giải thích về argument
này như sau:
scratch_disk
- (Optional) Scratch disks to attach to the instance. This can be specified multiple times for multiple scratch disks. Thescratch_disk
block supports:
interface
- (Required) The disk interface to use for attaching this disk; either SCSI or NVME.
Ngoài ví dụ minh họa, trong phần giải thích không nhắc gì tới việc đây là thuộc tính của LocalSSD. Nếu thử gắn code vào module, chạy terraform apply
thì sẽ thấy compute được tạo có một LocalSSD với kích thước theo mô tả của GCP là 375GB.
Trong đoạn giải thích phía trên có nói có thể gắn nhiều LocalSSD bằng cách lặp lại nhiều lần, tức là nếu muốn có 2 LocalSSD, thì code sẽ như sau:
resource "google_compute_instance" "this" {
...
scratch_disk {
interface = "NVME"
}
scratch_disk {
interface = "NVME"
}
...
}
Ở nhiều lần đọc document, tui đã bỏ qua chi tiết multiple times for multiple scratch disks và khá là loay hoay trong việc suy nghĩ làm sao để attach nhiều LocalSSD, mặc dù cách đơn giản là lặp lại nhiều lần scratch_disk { interface = "NVME" }
Tới đây, coi như giải quyết được chuyện làm thế nào để tạo GCE với LocalSSD, mặc dù cái docs thì hơi xu cà na tí.
Tuy nhiên, nó lại phát sinh ra một bài toán khác. Vì đây là code module, nên khi xài tui sẽ xài kiểu như vầy:
module "gh-postgresql" {
source = "./modules/gce/"
env = "tst"
product = "gh"
service = "postgresql"
role = "primary"
description = "Self manage PostgreSQL service for gh product"
enable = true
machine_image = "ubuntu-os-cloud/ubuntu-2004-lts"
machine_type = "n2-highcpu-2"
tags = ["postgresql", "ssh", "gh"]
}
module "gh-web" {
source = "./modules/gce/"
env = "tst"
product = "gh"
service = "web"
role = "http"
description = "HTTP web for gh product"
enable = true
machine_image = "ubuntu-os-cloud/ubuntu-2004-lts"
machine_type = "n2-highcpu-2"
tags = ["web", "ssh", "gh"]
}
Vấn đề ở đây là không phải GCE resource nào tui cũng muốn có LocalSSD, và không phải GCE nào tui cũng muốn 2 LocalSSD. Tui muốn bật tắt được nó và input vào số lượng tui muốn dựa vào nhu cầu. Nếu hardcode số lượng scratch_disk
trong code module thì sẽ không thể nào truyền argument ở module được.
Suy nghĩ đầu tiên là hay thử với cú pháp for_each
, tuy nhiên thì for_each
phù hợp với việc tạo nhiều resource, ví dụ tạo nhiều GCE thì có thể dùng for_each
thay cho count
=> bỏ, không dùng được
Suy nghĩ tiếp theo là dynamic
block, đọc thử qua docs thì nghĩ mãi và thử một vài ví dụ vẫn không chạy được.
Do không nghĩ ra cách, tui quyết định đọc issues của tất cả các module tương tự như terraform-google-vm và đọc luôn provider repo (lưu ý là đọc tất cả những gì thấy liên quan bao gồm cả closed issue, các comment, link comment). Và search luôn trên cả Github với từ khóa scratch_disk
xem có gì liên quan không (nhưng search của Github không ngon lắm), sau đó search tiếp Sourcegraph và cuối cùng cũng ra kết quả.
Đầu tiên, từ issue 7293, code minh họa như sau:
dynamic "scratch_disk" {
for_each = range(var.local_disk_count)
content {
interface = "NVME"
}
}
Khai báo biến như sau:
variable "local_ssd_count" {
description = "Number of LocalSSD attach to this instance"
type = number
default = 1
}
Bằng việc khai báo
local_ssd_count
, tui có thể bật tắt hoặc tùy chọn số lượng LocalSSD mong muốn, tới đây coi như giải quyết được bài toán rồi
Tuy nhiên có thể thấy interface
sẽ luôn luôn là NVME
, thực ra cũng chẳng ảnh hưởng lắm, vì SCSI
hay NVME
thì giá cả không thay đổi, và nếu giá không thay đổi thì tội gì chọn SCSI
. Nhưng vẫn có thể làm tốt hơn từ một ví dụ tui lấy được trên Sourcegraph. Code minh họa như sau:
dynamic "scratch_disk" {
for_each = [
for i in range(0, var.scratch_disks.count) : var.scratch_disks.interface
]
iterator = config
content {
interface = config.value
}
}
Khai báo biến như sau:
variable "scratch_disks" {
description = "Scratch disks configuration."
type = object({
count = number
interface = string
})
default = {
count = 0
interface = "NVME"
}
}
Khi sử dụng trong code module, chỉ cần pass giá trị như sau:
module "gh-postgresql" {
source = "./modules/gce/"
env = "tst"
product = "gh"
service = "postgresql"
role = "primary"
description = "Self manage PostgreSQL service for gh product"
enable = true
machine_image = "ubuntu-os-cloud/ubuntu-2004-lts"
machine_type = "n2-highcpu-2"
tags = ["postgresql", "ssh", "gh"]
scratch_disks = { count = 2, interface = "NVME" }
}
module "gh-web" {
source = "./modules/gce/"
env = "tst"
product = "gh"
service = "web"
role = "http"
description = "HTTP web for gh product"
enable = true
machine_image = "ubuntu-os-cloud/ubuntu-2004-lts"
machine_type = "n2-highcpu-2"
tags = ["web", "ssh", "gh"]
scratch_disks = { count = 0, interface = "NVME" }
}
Vậy là xong, mất 2 ngày để giải quyết vấn đề này 🥺, trong đầu chỉ có một suy nghĩ là syntax của Terraform thật “kỳ lạ”, và khá khó để biết là nó phù hợp với vấn đề gì hoặc khi gặp một vấn đề, không biết bắt đầu với keyword gì. Thực ra, tui vẫn chưa hiểu là cú pháp đó nó làm gì đâu =)) nhưng thôi, để coi sau, cứ biết là nó chạy đúng nhu cầu đã. :v