ในโลกของ Cloud Native การเก็บรหัสผ่านหรือ API Key ไว้ในโค้ดแบบ Hardcoded หรือแม้แต่ใส่ตรง ๆ เป็น Environment Variables บน Console เริ่มไม่ปลอดภัยและจัดการยากขึ้นเรื่อย ๆ
วันนี้ผมจะมาแชร์วิธีจัดการ Secret ด้วย GCP Secret Manager โดยใช้ Terraform เป็นตัวควบคุมจากจุดเดียว ช่วยลดเวลาการตั้งค่าผ่านหน้า Console ทำซ้ำได้ง่าย และเหมาะกับการทำงานเป็นทีมมากขึ้นครับ
🏗️ 3 ผสานการสร้าง Secret ด้วย Terraform
การจัดการ Secret ที่ดี ไม่ใช่แค่เอาค่าไปเก็บ แต่ต้องคำนึงถึง Lifecycle และ สิทธิ์การเข้าถึง ให้ถูกต้อง โดยสามารถแบ่งออกเป็น 3 ส่วนหลักดังนี้
1. The Container (การสร้างกล่องเก็บ Secret)
เริ่มจากการสร้าง “กล่องเก็บ Secret” หรือชื่อของตัวแปรใน Secret Manager ก่อน โดยใช้ resource google_secret_manager_secret
resource "google_secret_manager_secret" "app_secret" {
secret_id = "my-service-secrets" # ชื่อที่จะปรากฏบน GCP Console
project = var.project_id # Project ID ของคุณ
replication {
auto {} # ให้ GCP ช่วยสำรองข้อมูล (Replication) อัตโนมัติ
}
}2. The Permission (การแจกสิทธิ์)
มีกล่องแล้ว แต่คำถามคือ ใครมีสิทธิ์เปิดดูได้บ้าง? สำหรับ Cloud Run เราจำเป็นต้องให้ Service Account ของแอป มี Role เป็น
Secret Manager Secret Accessor
resource "google_secret_manager_secret_iam_member" "app_accessor" {
project = var.project_id
secret_id = google_secret_manager_secret.app_secret.secret_id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${var.app_service_account_email}"
}3. The Data (การใส่ข้อมูล)
เมื่อมีกล่องและสิทธิ์พร้อมแล้ว ขั้นต่อไปคือการใส่ Value ของ Secret เข้าไป ซึ่งตรงนี้ถือเป็น จุดตัดสินใจที่สำคัญ เพราะเรามี 2 แนวทางให้เลือก
3.1 Fully Terraform
ให้ Terraform เป็นคนอัปโหลดค่า Secret จากเครื่องเราเข้าไปเลย เหมาะกับค่าที่ไม่สำคัญมาก หรือเป็นค่า Demo / Test
# 1. สร้างตัว Secret (กล่อง)
resource "google_secret_manager_secret" "app_secret" {
secret_id = "my-app-env"
project = "my-project-id"
replication {
auto {}
}
}
# 2. ใส่ข้อมูล (Version) โดยอ่านจากไฟล์ .env ตรงๆ
resource "google_secret_manager_secret_version" "app_secret_version" {
secret = google_secret_manager_secret.app_secret.id
# ใช้ฟังก์ชัน file() เพื่ออ่านเนื้อหาในไฟล์ส่งเข้าไปเป็นข้อมูลลับ
secret_data = file("${path.module}/secrets/.env")
}⚠️ ข้อควรระวัง
Security Risk: ข้อมูลในไฟล์ .env จะถูกเก็บไว้ในไฟล์ terraform.tfstate เป็นแบบ Plain Text (ตัวอักษรอ่านออก) หากใครเข้าถึงไฟล์ state ได้ ก็จะเห็นรหัสผ่านทั้งหมดทันที
Sensitive Suppression: ใน Terraform เวอร์ชันใหม่ๆ มักจะแนะนำให้กำหนดค่าตัวแปรเป็น sensitive = true เพื่อไม่ให้รหัสผ่านโชว์ขึ้นมาบนหน้าจอตอนรัน terraform plan ครับ
3.2 Hybrid Approach (แนะนำ ✅)
ใช้ Terraform สร้างเฉพาะ กล่อง และ สิทธิ์
ส่วน Value ของ Secret ใส่ผ่าน gcloud CLI หรือ Cloud Console เอง
เพื่อป้องกันไม่ให้ข้อมูลลับไปค้างอยู่ในไฟล์ terraform.tfstate
วิธีใส่ข้อมูลผ่าน Cloud Console (หน้าเว็บ)
หากต้องการอัปเดตค่า Secret ด้วยตัวเองผ่านหน้าเว็บ สามารถทำได้ตามขั้นตอนนี้
- ไปที่ GCP Console
- ค้นหาคำว่า Secret Manager หรือเลือกจากเมนู Security
- เลือก Secret ที่ Terraform สร้างไว้ (เช่น backoffice-service-dev)
- คลิกปุ่ม + CREATE VERSION
- วางค่า Secret (เช่น จากไฟล์ .env หรือรหัสผ่าน)
- กด CREATE เป็นอันเสร็จสิ้น
ระบบจะใช้ค่าใน Version ล่าสุด (Latest) เสมอ หาก Cloud Run ถูกตั้งค่าให้ดึงแบบนั้น
💻 วิธีใช้ gcloud CLI (สำหรับสาย Command Line)
ถ้าไม่อยากเปิดเว็บ แต่ยังต้องการความปลอดภัย สามารถใช้ gcloud รันจากเครื่องได้โดยตรง (ค่าที่อ่านมาจากไฟล์ไม่ค้างใน history)
gcloud secrets versions add [ชื่อ-SECRET-ของคุณ] \
--data-file=".env" \
--project=[PROJECT-ID]🔥 ทำไมวิธี 3.2 ถึงเป็นที่นิยม?
- Separation of Concerns
ทีม Infra ดูแล Terraform
ทีม Dev ถือและจัดการรหัสผ่าน - No State Leak
Secret จะไม่ไปโผล่ใน terraform.tfstate หรือ Git Repository - Audit Log
GCP บันทึกครบว่า ใคร แก้ เมื่อไหร่
ตอบโจทย์เรื่อง Security และ Compliance มาก
🎯 เทคนิคเด็ด: Mount Secret เป็นไฟล์ใน Cloud Run
วิธีที่ปลอดภัยที่สุดไม่ใช่การดึง Secret มาเป็น Environment Variable (เพราะอาจหลุดไปใน Log ได้)
แต่คือการ Mount Secret เป็นไฟล์ เข้าไปใน Container โดยตรง แอปอ่านค่าจากไฟล์แทน
ตัวอย่างการตั้งค่าใน Terraform:
template {
volumes {
name = "conf-volume"
secret {
secret = "my-service-secrets"
items {
version = "latest"
path = ".env" # ชื่อไฟล์ที่จะไปปรากฏในเครื่อง
}
}
}
containers {
volume_mounts {
name = "conf-volume"
mount_path = "/app/configs" # โฟลเดอร์ปลายทาง
}
}
}ถ้าต้องจัดการแค่ไม่กี่ service การทำผ่าน Console ก็อาจเพียงพอ แต่เมื่อจำนวน service เพิ่มขึ้น และต้องทำซ้ำบ่อย ๆ วิธีนี้จะกลายเป็นตัวช่วยที่ทรงพลังมาก
หวังว่าเทคนิคนี้จะช่วยให้การทำงานของคุณง่ายขึ้น เป็นระบบขึ้น และขอให้มีความสุขกับการทำงานครับ 🚀


