שיטות מומלצות לשימוש ב-Terraform

כאן תוכלו לקרוא הנחיות והמלצות לפיתוח יעיל ב-Terraform, עם מספר חברי צוות ומקורות עבודה שונים.

המדריך הזה אינו מבוא ל-Terraform. אם אתם צריכים מבוא לשימוש ב-Terraform עם Google Cloud, תוכלו לקרוא את המאמר תחילת העבודה עם Terraform.

הנחיות כלליות לגבי הסגנון והמבנה

ההמלצות הבאות מתייחסות לסגנון ולמבנה הבסיסיים של ההגדרות של Terraform. ההמלצות האלו חלות על מודולים רב-פעמיים של Terraform ועל הגדרות ברמה הבסיסית (root).

שימוש במבנה הסטנדרטי של מודולים

  • המודולים של Terraform חייבים להתאים למבנה הסטנדרטי.
  • כל מודול צריך להתחיל בקובץ main.tf, שבו נמצאים המשאבים כברירת מחדל.
  • בכל מודול חשוב לכלול קובץ README.md בפורמט Markdown. בקובץ README.md יש לכלול תיעוד בסיסי לגבי המודול.
  • הדוגמאות צריכות להיות בתיקייה examples/, עם ספריית משנה נפרדת לכל דוגמה. לכל דוגמה יש לכלול קובץ README.md מפורט.
  • כדאי ליצור קבוצות לוגיות של משאבים עם הקבצים שלהם ולכל קבוצה לתת שם שמתאר אותה, למשל network.tf, ‏instances.tf או loadbalancer.tf.
    • מומלץ לא לתת לכל משאב קובץ משלו. עדיף לקבץ משאבים לפי המטרה המשותפת שלהם. לדוגמה, אפשר לשלב את המשאב google_dns_managed_zone עם google_dns_record_set בקובץ dns.tf.
  • בספריית הבסיס של המודול, צריך לכלול רק קובצי Terraform (‏*.tf) וקובצי מטא-נתונים של מאגרים (כמו README.md ו-CHANGELOG.md).
  • מסמכים נוספים צריכים להיות בספריית המשנה docs/.

שימוש במוסכמה למתן שמות

  • כשנותנים לאובייקטים שמות, צריך להשתמש בקווים תחתונים כדי להפריד בין מילים. השיטה הזו מאפשרת לשמור על עקביות עם המוסכמה למתן שמות לסוגי משאבים, לסוגים של מקורות נתונים ולערכים אחרים שמוגדרים מראש. המוסכמה הזו לא רלוונטית לשמות של ארגומנטים.

    מומלץ:

    resource "google_compute_instance" "web_server" {
      name = "web-server"
    }
    

    לא מומלץ:

    resource "google_compute_instance" "web-server" {
      name = "web-server"
    }
    
  • כדי לפשט את ההפניות למשאב שהוא יחיד מסוגו (לדוגמה, מאזן העומסים היחיד בכל המודול), יש לתת למשאב את השם main.

    • יותר קשה לזכור את השם some_google_resource.my_special_resource.id מאשר את השם some_google_resource.main.id.
  • כדי להבדיל בין משאבים מאותו סוג (למשל primary ו-secondary), צריך לתת למשאבים שמות עם משמעות.

  • השמות של המשאבים צריכים להיות ייחודיים.

  • כשנותנים שם למשאב, אין צורך לחזור שוב על הסוג שלו. למשל:

    מומלץ:

    resource "google_compute_global_address" "main" { ... }
    

    לא מומלץ:

    resource "google_compute_global_address" "main_global_address" { … }
    

שימוש זהיר במשתנים

  • יש להצהיר על כל המשתנים בקובץ variables.tf.
  • חשוב לתת למשתנים שמות תיאוריים שרלוונטיים לשימוש או למטרה שלהם.
    • שמות של משתני קלט, משתנים מקומיים ומשתני פלט שמייצגים ערכים מספריים כמו גודל הדיסק או גודל ה-RAM, חייבים לכלול את היחידות (למשל ram_size_gb). הסיבה לכך היא שלממשקי ה-API של Google Cloud אין יחידות סטנדרטיות, כך שמתן שמות עם יחידות מבהיר לאנשים שמנהלים את ההגדרות מהי יחידת הקלט שהם צריכים להזין.
    • למשתנים שמכילים יחידות אחסון, יש להשתמש בקידומות של יחידות בינאריות (חזקות של 1024) (kilo, ‏mega, ‏giga). לכל יחידות המידה האחרות, יש להשתמש ביחידות עשרוניות (חזקות של 1000). השימוש הזה בקידומות תואם לאופן השימוש ב-Google Cloud.
    • כדי לפשט את הלוגיקה של תנאים, יש לתת למשתנים בוליאניים שמות על דרך החיוב (לדוגמה, enable_external_access).
  • יש לספק תיאורים למשתנים. התיאורים נכללים אוטומטית בתיעוד האוטומטי שנוצר למודול ומפורסם. התיאורים מוסיפים הקשר נוסף ששמות תיאוריים לא יכולים לספק, ועוזרים למפתחים חדשים.
  • חשוב לתת למשתנים סוגים מוגדרים.
  • אפשר גם לספק ערכי ברירת מחדל לפי הצורך:
    • למשתנים שיש להם ערכים שאינם תלויים בסביבה (כמו גודל הדיסק), צריך לספק ערכי ברירת מחדל.
    • למשתנים שיש להם ערכים ספציפיים לסביבה (כמו project_id ), לא צריך לספק ערכי ברירת מחדל. כך המודול הקורא חייב לספק ערכים משמעותיים.
  • אפשר להשתמש בערכים ריקים כברירת מחדל למשתנים (כמו מחרוזות או רשימות ריקות) רק אם ממשק ה-API הבסיסי מאפשר העברת משתנים ריקים ולא דוחה אותם.
  • הפעילו שיקול דעת כשאתם משתמשים במשתנים. השתמשו בפרמטרים רק לערכים שחייבים להשתנות בכל מכונה או סביבה. כשאתם מחליטים אם לחשוף משתנה כלשהו, ודאו שיש לכם תרחיש קונקרטי לדוגמה שבו יהיה צורך לשנות את המשתנה הזה. אם הסיכוי שיהיה צורך במשתנה מסוים קטן, אל תחשפו אותו.
    • כשאתם מוסיפים משתנה עם ערך ברירת מחדל, חשוב לוודא שהמערכת תואמת לאחור.
    • כשאתם מסירים משתנה, חשוב לוודא שהמערכת אינה תואמת לאחור.
    • במקרים שבהם יש שימוש בליטרל במספר מקומות, אפשר להשתמש בערך מקומי בלי לחשוף אותו כמשתנה.

חשיפת פלטים

  • כל הפלטים צריכים להיות מסודרים בקובץ outputs.tf.
  • חשוב לספק תיאורים משמעותיים לכל הפלטים.
  • את תיאורי הפלט צריך לשמור בקובץ README.md. אפשר להשתמש בכלים כמו terraform-docs כדי לייצר אוטומטית תיאורים בזמן השמירה.
  • כדאי ליצור משתני פלט לכל הערכים השימושיים שהמודולים של הרמה הבסיסית (root) עשויים להתייחס אליהם או לשתף אותם. חשוב במיוחד לחשוף את כל הפלטים שיש סיכוי שישתמשו בהם כשמדובר במודולים של קוד פתוח או מודולים נפוצים במיוחד.
  • את משתני הפלט לא מעבירים ישירות דרך משתני הקלט, כדי שאפשר יהיה להוסיף אותם באופן תקין לתרשים התלות. כדי להבטיח שיווצרו יחסי תלות משתמעים, חשוב לוודא שמשתני הפלט מפנים למאפיינים של המשאבים. במקום להפנות אל משתנה הקלט של המכונה באופן ישיר, צריך להעביר את המאפיין, כפי שמוצג כאן:

    מומלץ:

    output "name" {
      description = "Name of instance"
      value       = google_compute_instance.main.name
    }
    

    לא מומלץ:

    output "name" {
      description = "Name of instance"
      value       = var.name
    }
    

שימוש במקורות נתונים

  • את מקורות הנתונים צריך לשמור קרוב למשאבים הרלוונטיים. לדוגמה, אם מאחזרים קובץ אימג' לצורך הפעלת מכונה, יש לשמור אותו לצד המכונה במקום לאסוף משאבי נתונים בקובץ משלהם.
  • אם כמות מקורות הנתונים גדולה, מומלץ להעביר אותם לקובץ data.tf ייעודי.
  • כדי לאחזר נתונים יחסיים לסביבה הנוכחית, צריך להשתמש באינטרפולציה על המשתנים או המשאבים.

הגבלת השימוש בסקריפטים מותאמים אישית

  • יש להשתמש בסקריפטים רק במידת הצורך. המצב של המשאבים שנוצרו באמצעות סקריפטים לא נכלל באחריות של Terraform ולא מנוהל על ידיה.
    • אם הדבר אפשרי, מומלץ להימנע מסקריפטים מותאמים אישית. יש להשתמש בהם רק כאשר המשאבים של Terraform לא תומכים בהתנהגות הרצויה.
    • לסקריפטים מותאמים אישית שנעשה בהם שימוש חייב להיות תיעוד ברור לגבי הסיבה שיצרו אותם ובאופן אידיאלי, כדאי שתהיה להם תוכנית להוצאה משימוש.
  • אפשר להפעיל סקריפטים מותאמים אישית באמצעות מנהל ההקצאות (provisioner) של Terraform, כולל מנהל ההקצאות המקומי.
  • סקריפטים מותאמים אישית שמופעלים על ידי Terraform צריכים להיות בספרייה scripts/.

שמירת הסקריפטים המסייעים בספרייה נפרדת

  • סקריפטים מסייעים שלא מופעלים על ידי Terraform צריכים להיות בספרייה helpers/.
  • את הסקריפטים המסייעים צריך לתעד בקובץ README.md עם הסברים ודוגמאות להפעלה.
  • אם הסקריפטים המסייעים מקבלים ארגומנטים, יש לספק בדיקת ארגומנטים ופלט --help.

שמירת הקבצים הסטטיים בספרייה נפרדת

  • קבצים סטטיים ש-Terraform מפנה אליהם, אבל לא מפעילה אותם (למשל, סקריפטים לטעינה בזמן ההפעלה שמעלים למכונות של Compute Engine) צריכים להיות מאורגנים בספרייה files/.
  • קטעי HereDocs ארוכים צריך לשמור בקבצים חיצוניים, בנפרד מה-HCL שלהם. אפשר להפנות אליהם באמצעות הפונקציה file().
  • לקבצים שנקראים באמצעות הפונקציה templatefile של Terraform, יש להשתמש בסיומת .tftpl.
    • תבניות חייבות להיות בספרייה templates/.

הגנה על משאבים עם שמירת מצב

למשאבים עם שמירת מצב, כמו מסדי נתונים, יש לוודא שההגנה מפני מחיקה פועלת. למשל:

resource "google_sql_database_instance" "main" {
  name = "primary-instance"
  settings {
    tier = "D0"
  }

  lifecycle {
    prevent_destroy = true
  }
}

שימוש בפורמט מובנה בקבצים

כל הקבצים של Terraform צריכים לעמוד בסטנדרטים של terraform fmt.

הגבלת המורכבות של ביטויים

  • יש להגביל את המורכבות של ביטויים שעברו אינטרפולציה. אם צריך להשתמש בכמה פונקציות בביטוי בודד, כדאי לפצל אותו למספר ביטויים באמצעות ערכים מקומיים.
  • לעולם אל תכללו יותר מפעולה תלת-ערכית אחת בשורה אחת. במקום זאת, השתמשו בכמה ערכים מקומיים כדי ליצור את הלוגיקה בהדרגה.

שימוש במשתנה count לערכים מותנים

כדי ליצור משאב באופן מותנה, יש להשתמש במטא-ארגומנט count. למשל:

variable "readers" {
  description = "..."
  type        = list
  default     = []
}

resource "resource_type" "reference_name" {
  // Do not create this resource if the list of readers is empty.
  count = length(var.readers) == 0 ? 0 : 1
  ...
}

חשוב לנהוג בזהירות כשמזינים ערכים למשתנה count של משאבים באמצעות משתנים שהגדירו המשתמשים. אם תספקו מאפיין של משאב למשתנה כזה (כמו project_id) והמשאב הזה עדיין לא קיים, Terraform לא תוכל ליצור תוכנית. במקום זאת, השגיאה value of count cannot be computed תופיע ב-Terraform. במקרים כאלה, כדאי להשתמש במשתנה enable_x נפרד כדי לחשב את הלוגיקה של התנאי.

שימוש במשתנה for_each לעותקים של משאבים

כדי ליצור כמה עותקים של משאב על סמך משאב קלט כלשהו, יש להשתמש במטא-ארגומנט for_each.

פרסום מודולים במרשם

מודולים לשימוש חוזר

למודולים שנועדו לשימוש חוזר, יש להשתמש בהנחיות הבאות בנוסף להנחיות הקודמות.

הפעלת ממשקי ה-API הנחוצים במודולים

במודולים של Terraform אפשר להפעיל את כל השירותים הנחוצים באמצעות המשאב google_project_service או באמצעות המודול project_services. יותר קל לבצע הדגמות כשמפעילים ממשקי API.

  • אם המודול מאפשר להפעיל את ממשק ה-API, חייבת להיות אפשרות להשבית את הפעלת ה-API על ידי חשיפת משתנה enable_apis שמוגדר כברירת מחדל לערך true.
  • אם המודול מאפשר להפעיל את ממשק ה-API, חייבת להיות אפשרות להגדיר את המאפיין disable_services_on_destroy עם הערך false, כי המאפיין הזה עלול לגרום לבעיות בעבודה עם מספר מופעים של המודול.

    למשל:

    module "project-services" {
      source  = "terraform-google-modules/project-factory/google//modules/project_services"
      version = "~> 12.0"
    
      project_id  = var.project_id
      enable_apis = var.enable_apis
    
      activate_apis = [
        "compute.googleapis.com",
        "pubsub.googleapis.com",
      ]
      disable_services_on_destroy = false
    }
    

הכללת קובץ בעלים

לכל המודולים המשותפים, צריך לכלול קובץ OWNERS (או CODEOWNERS ב-GitHub), כדי לתעד מי אחראי על המודול. הבעלים צריכים לאשר כל בקשת משיכה (pull request) כדי שהיא תמוזג.

פרסום גרסאות מתויגות

לפעמים צריך לבצע במודול שינוי תוכנה שעלול לגרום לכשל. לכן, צריך להעביר למשתמשים את המידע לגבי ההשפעה של השינוי, כדי שהם יוכלו להצמיד את ההגדרות שלהם לגרסה ספציפית.

חשוב לוודא שהמודולים המשותפים פועלים לפי SemVer בגרסה 2.0.0 כשגרסאות חדשות יתויגו או יתווספו לגרסה.

כשמוסיפים הפניה למודול, צריך להשתמש באילוץ על מספר הגרסה כדי להיצמד לגרסה הראשית. למשל:

module "gke" {
  source  = "terraform-google-modules/kubernetes-engine/google"
  version = "~> 20.0"
}

הימנעות מהצהרה על ספקים או על קצוות עורפיים

במודולים משותפים אסור להצהיר על ספקים או על קצוות עורפיים. במקום זאת, צריך להצהיר על ספקים ועל קצוות עורפיים במודולים ברמה הבסיסית.

במודולים משותפים, צריך להגדיר את הגרסאות המינימליות הנדרשות למודולים של ספקים בבלוק required_providers כך:

terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = ">= 4.0.0"
    }
  }
}

עליכם להניח שגרסאות חדשות של מודולים של ספקים יפעלו, אלא אם הוכח אחרת.

חשיפת תוויות באמצעות משתנה

כדי לאפשר גמישות בהוספת משאבים דרך הממשק של המודול, מומלץ לספק את המשתנה labels עם מיפוי ריק כברירת מחדל, כמו בדוגמה הבאה:

variable "labels" {
  description = "A map of labels to apply to contained resources."
  default     = {}
  type        = "map"
}

חשיפת משתני הפלט של כל המשאבים

משתנים ופלטים מאפשרים לכם להסיק יחסי תלות בין מודולים ומשאבים. ללא משתני פלט, המשתמשים לא יכולים לסדר את המודול בהתאם להגדרות שלהם ב-Terraform.

לכל משאב שמוגדר במודול משותף, יש לכלול לפחות משתנה פלט אחד שמפנה למשאב.

שימוש בתתי-מודולים בקוד ללוגיקה מורכבת

  • אתם יכולים להשתמש בתתי-מודולים בקוד כדי לחלק מודולים מורכבים של Terraform ליחידות קטנות יותר ולמנוע כפילויות של משאבים נפוצים.
  • המודולים בקוד צריכים להיות בתוך modules/$modulename.
  • יש להתייחס למודולים בתור קטעי קוד פרטיים, שלא נועדו לשימוש במודולים חיצוניים, אלא אם כן צוין אחרת במפורש במסמכים של המודול המשותף.
  • ב-Terraform אין מעקב אחרי משאבים שעברו ארגון מחדש של הקוד (Refactoring). אם אתם מתחילים עם כמה משאבים במודול של הרמה העליונה ולאחר מכן מעבירים אותם לתתי-מודולים, Terraform תנסה ליצור מחדש את כל המשאבים שעברו ארגון מחדש של הקוד. כדי למנוע את ההתנהגות הזו, יש להשתמש בבלוקים של moved כשמארגנים את הקוד מחדש.
  • משתני פלט שמוגדרים על ידי מודולים פנימיים לא נחשפים באופן אוטומטי. כדי לשתף משתני פלט ממודולים פנימיים, צריך לייצא אותם מחדש.

מודולים של Terraform ברמה הבסיסית (root)

הגדרות ברמה הבסיסית (מודולים של הרמה הבסיסית) הן ספריות העבודה שמהן אתם מריצים את ה-CLI של Terraform. חשוב לוודא שההגדרות ברמה הבסיסית הן בהתאם לסטנדרטים הבאים (ולהנחיות הקודמות של Terraform, במקרים הרלוונטיים). אם יש המלצות ספציפיות למודולים של הרמה הבסיסית, הן מחליפות את ההנחיות הכלליות.

צמצום מספר המשאבים בכל מודול של הרמה הבסיסית

חשוב למנוע מצב שבו הגדרה מסוימת ברמה הבסיסית תהיה גדולה מדי, ותכיל יותר מדי משאבים באותה ספרייה ובאותו מצב. כל המשאבים בהגדרה מסוימת ברמה הבסיסית מתעדכנים בכל פעם שמפעילים את Terraform. משאבים רבים מדי באותו מצב עלולים להאט את ההפעלה. כלל אצבע: אין לכלול יותר מ-100 משאבים באותו מצב (ורצוי רק כמה עשרות).

שימוש בספריות נפרדות לכל אפליקציה

כדי לנהל אפליקציות ופרויקטים באופן בלתי תלוי, יש לשמור את המשאבים של כל אפליקציה ושל כל פרויקט בספריות נפרדות ב-Terraform. כל שירות עשוי לייצג אפליקציה מסוימת או שירות משותף, כמו שירות לשיתוף רשתות. כל הקוד של שירות מסוים של Terraform צריך להיות בספרייה אחת (כולל ספריות המשנה).

פיצול אפליקציות לספריות משנה ספציפיות לסביבה

כשפורסים שירותים ב-Google Cloud, יש לפצל את ההגדרות של השירות ב-Terraform לשתי ספריות ברמה העליונה: הספרייה modules, שמכילה את ההגדרות בפועל של השירות, והספרייה environments, שמכילה את הגדרות הבסיס של כל סביבה.

-- SERVICE-DIRECTORY/
   -- OWNERS
   -- modules/
      -- <service-name>/
         -- main.tf
         -- variables.tf
         -- outputs.tf
         -- provider.tf
         -- README
      -- ...other…
   -- environments/
      -- dev/
         -- backend.tf
         -- main.tf

      -- qa/
         -- backend.tf
         -- main.tf

      -- prod/
         -- backend.tf
         -- main.tf

שימוש בספריות של הסביבה

כל ספרייה של סביבה (dev, ‏qa, ‏prod) מקבילה לסביבת עבודה מוגדרת ב-Terraform. פריסת גרסה של השירות תתבצע לסביבה הזו. חשוב להשתמש רק בסביבת העבודה המוגדרת. סביבות העבודה בלבד לא מספיקות כדי לייצג סביבות שונות.

כדי לשתף קוד בין סביבות, צריך להפנות למודולים. בדרך כלל מדובר במודול שירות שכולל את הגדרות הבסיס המשותפות של השירות ב-Terraform. במודולים של שירותים, מומלץ להזין את ערכי הקלט הנפוצים בתוך הקוד, ולהשתמש במשתנים רק לערכי קלט שהם ספציפיים לסביבה.

ספריית הסביבה הזו חייבת להכיל את הקבצים הבאים:

  • קובץ backend.tf עם הצהרה על מיקום המצב של הקצה העורפי של Terraform (בדרך כלל ב-Cloud Storage‏)
  • קובץ main.tf שמייצר את מודול השירות הזה

חשיפת משתני הפלט באמצעות שמירת המצב ביעד מרוחק

חשוב לייצא למשתני פלט מידע של מודול ברמה הבסיסית, שמודולים אחרים ברמה הבסיסית יכולים להיות תלויים בו. חשוב במיוחד לייצא מחדש את הפלט של תתי-מודולים שיכולים להועיל כששומרים את המצב ביעד מרוחק.

סביבות ואפליקציות אחרות של Terraform יכולות להתייחס רק למשתני פלט של מודולים ברמה הבסיסית.

כששומרים את הפלט ביעד מרוחק, אפשר להפנות לפלט של המודול ברמה הבסיסית. כדי לאפשר לאפליקציות תלויות אחרות להשתמש בפלט לצורך הגדרות, יש לייצא ליעד המרוחק נתונים שקשורים לנקודות הקצה (endpoints) של השירות.

במקרים מסוימים, למשל אם מפעילים מודול משותף מתוך הספריות של הסביבה, צריך לייצא מחדש את כל המודול הצאצא באופן הבא:

output "service" {
  value       = module.service
  description = "The service module outputs"
}

הצמדה לגרסאות משניות של ספקים

במודולים ברמה הבסיסית (root), יש להצהיר על כל ספק ולהצמיד לגרסה משנית שלו. כך אפשר לשדרג באופן אוטומטי לגרסאות תיקון חדשות, ועדיין לשמור על יעד יציב. כדי לשמור על עקביות תוכלו לקרוא לקובץ הגרסאות בשם versions.tf.

terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 4.0.0"
    }
  }
}

שמירת המשתנים בקובץ tfvars

למודולים ברמה הבסיסית (root) צריך לספק משתנים באמצעות קובץ מסוג .tfvars. מטעמי עקביות, יש לתת לקובצי המשתנים את השם terraform.tfvars.

אין לציין משתנים באמצעות קבצים חלופיים מסוג var-files או באמצעות האפשרות var='key=val' בשורת הפקודה. האפשרויות בשורת הפקודה הן פתרון זמני וקל לשכוח אותן. שימוש בקובץ להגדרת משתני ברירת המחדל הוא פתרון אמין וצפוי יותר.

הכנסת הקובץ .terraform.lock.hcl למערכת בקרת הגרסאות

במודולים ברמה הבסיסית (root), יש להכניס את קובץ נעילת התלות .terraform.lock.hcl למערכת בקרת הגרסאות. כך ניתן לעקוב אחרי השינויים בהגדרות הספקים.

תקשורת סביב ההגדרות

אחת מהבעיות הנפוצות כשמשתמשים ב-Terraform עם הגדרות שונות (שלפעמים מנהלים צוותים שונים), היא איך חולקים מידע. באופן כללי, אפשר לחלוק מידע על ההגדרות גם אם הן לא מאוחסנות באותה ספריית הגדרות (או אפילו באותו מאגר).

הדרך המומלצת לחלוק מידע בין הגדרות שונות של Terraform היא להפנות למודולים אחרים ברמה הבסיסית באמצעות שמירת המצב ביעד מרוחק. הקצוות העורפיים המועדפים לשמירת מצב הם Cloud Storage או Terraform Enterprise.

אם רוצים לשלוח שאילתות לגבי משאבים שלא מנוהלים על ידי Terraform, יש להשתמש במקורות נתונים מספק של Google. לדוגמה, אפשר לאחזר את חשבון השירות המוגדר כברירת מחדל ב-Compute Engine באמצעות מקור נתונים. אין להשתמש במקורות נתונים כדי לשלוח שאילתות על משאבים שמנוהלים על ידי הגדרות אחרות של Terraform. פעולה זו עלולה ליצור יחסי תלות מרומזים בין שמות ומבני נתונים של משאבים שפעולות רגילות של Terraform עלולות לקטוע בטעות.

שימוש במשאבים של Google Cloud

כאן נחזור על כמה מהשיטות המומלצות להקצאת משאבים ב-Google Cloud באמצעות Terraform, שמשולבות במודולים של Cloud Fundamentals Toolkit.

יצירת קובצי אימג' של מכונות וירטואליות

באופן כללי, מומלץ ליצור קובצי אימג' של מכונות וירטואליות באמצעות כלים כמו Packer. כך אפשר להפעיל את המכונות ב-Terraform באמצעות קובצי האימג' שהוכנו מראש.

אם אין קובצי אימג' שהוכנו מראש, אפשר למסור מכונות וירטואליות חדשות ב-Terraform לכלי לניהול ההגדרות באמצעות בלוק provisioner. מומלץ להימנע משיטה זו ולהשתמש בה רק כמוצא אחרון. כדי לנקות מצב ישן שמשויך למכונה, כלים לניהול הקצאות שמחייבים לוגיקת ניתוק צריכים להשתמש בבלוק של provisioner עם התנאי when = destroy.

על Terraform לספק לכלים של ניהול ההגדרות מידע לגבי ההגדרות של מכונות וירטואליות באמצעות המטא-נתונים של המכונה.

ניהול הזהויות והרשאות הגישה (IAM)

כשמקצים שיוכים של IAM באמצעות Terraform, אפשר להשתמש בכמה משאבים שונים:

  • google_*_iam_policy (לדוגמה, google_project_iam_policy)
  • google_*_iam_binding (לדוגמה, google_project_iam_binding)
  • google_*_iam_member (לדוגמה, google_project_iam_member)

google_*_iam_policy ו-google_*_iam_binding מייצרים שיוכים מוסמכים של IAM, שבהם המשאבים של Terraform משמשים כמקור האמין היחיד להרשאות שאפשר להקצות למשאב הרלוונטי.

אם ההרשאות משתנות מחוץ ל-Terraform, בפעם הבאה שתפעילו את Terraform, כל ההרשאות יוחלפו בהתאם למדיניות שהגדרתם. פעולה זו הגיונית אם המשאבים מנוהלים באופן מלא על ידי הגדרות ספציפיות של Terraform, אבל היא תגרום לכך שהגדרות של תפקידים שמנוהלות אוטומטית על ידי Google Cloud יוסרו, מה שעלול להפריע לפונקציונליות של שירותים מסוימים.

כדי למנוע זאת, מומלץ להשתמש במשאבים של google_*_iam_member ישירות או באמצעות המודול של IAM מ-Google.

ניהול הגרסאות

כמו בסוגים אחרים של קוד, אפשר לאחסן את קוד התשתית במערכת לניהול גרסאות כדי לשמור על ההיסטוריה ולחזור בקלות לגרסאות הקודמות.

שימוש בשיטת ברירת המחדל להסתעפות

לכל המאגרים שמכילים קוד של Terraform, יש להשתמש בשיטה הבאה כברירת מחדל:

  • ההסתעפות main היא ההסתעפות הראשית של הפיתוח והיא מייצגת את הקוד העדכני ביותר שעבר אישור. ההסתעפות mainהיא מוגנת.
  • הפיתוח מתרחש בהסתעפויות של פיתוח פיצ'רים ותיקוני באגים שמתפצלות מההסתעפות הראשית (main).
    • להסתעפויות של פיתוח פיצ'רים יש לתת את השם feature/$feature_name.
    • להסתעפויות של תיקוני באגים יש לתת את השם fix/$bugfix_name.
  • לאחר השלמת הפיתוח של הפיצ'ר או תיקון הבאג, יש למזג אותם חזרה להסתעפות main עם בקשת משיכה (pull request).
  • כדי למנוע קונפליקטים בין הסתעפויות, צריך לבצע rebase להסתעפויות לפני שממזגים ביניהן.

שימוש בהסתעפויות סביבתיות להגדרות ברמה הבסיסית (root)

במאגרים שכוללים הגדרות ברמה הבסיסית שפרוסות ישירות ב-Google Cloud, צריך אסטרטגיית השקה בטוחה. מומלץ ליצור הסתעפות נפרדת לכל סביבה. כך, אם רוצים לשנות את ההגדרות ב-Terraform, אפשר למזג את השינויים בין ההסתעפויות השונות.

הסתעפות נפרדת לכל סביבה

הפעלת הרשאות גישה נרחבות

חשוב להגדיר את קוד המקור ואת המאגרים של Terraform כך שתהיה גישה רחבה לארגונים של מהנדסי התוכנה, לבעלי התשתיות (לדוגמה, מהנדסי SRE) ולבעלי עניין בתשתית (לדוגמה, מפתחים). כך אפשר להבטיח שלבעלי עניין בתשתית תהיה הבנה טובה יותר של התשתית שהם תלויים בה.

חשוב לעודד בעלי עניין בתשתית לשלוח בקשות למיזוג כחלק מהתהליך של בקשת השינוי.

הגנה על סודות ואי חשיפתם

לעולם אין לשמור סודות למערכות לניהול גרסאות, כולל בהגדרות של Terraform. במקום זאת, אפשר להעלות אותם למערכת כמו Secret Manager ולהפנות אליהם באמצעות מקורות נתונים.

חשוב לזכור שערכים רגישים כאלה יכולים להגיע בסופו של דבר לקובצי מצב ועלולים גם להיחשף כפלט.

סידור המאגרים לפי תחומי צוותים

למרות שאפשר להשתמש בספריות נפרדות כדי לנהל תחומים לוגיים שונים של משאבים, התחומים הארגוניים והלוגיסטיקה קובעים את המבנה שלהמאגר. באופן כללי, כדאי לפעול לפי העיקרון העיצובי שקובע שהגדרות עם דרישות ניהול ואישור שונות יקובצו במאגרים שונים של מערכות לניהול גרסאות. כדי להמחיש את העיקרון הזה, ריכזנו כאן כמה הגדרות אפשריות של מאגרים:

  • מאגר מרכזי אחד: במודל הזה, צוות אחד של הפלטפורמה מנהל במרוכז את כל הקוד של Terraform. כדי שהשימוש במודל הזה יצליח, מומלץ לעבוד עם צוות תשתיות ייעודי, שיהיה אחראי על הניהול הכולל של הענן ויאשר את השינויים שצוותים אחרים יבקשו.

  • מאגרי צוות: במודל הזה, כל צוות יהיה אחראי על המאגר שלו ב-Terraform, וינהל את כל מה שקשור לתשתית שבבעלותו. לדוגמה, לצוות האבטחה יכול להיות מאגר שבו מנהלים את אמצעי בקרת האבטחה, בזמן שלצוותי האפליקציות יכול להיות מאגר משלהם ב-Terraform לפריסה ולניהול של אפליקציות.

    סידור המאגרים לפי התחומים השונים של הצוותים הוא המבנה הטוב ביותר לרוב התרחישים בארגון.

  • מאגרים מופרדים: במודל הזה, כל רכיב לוגי של Terraform מפוצל למאגר משלו. לדוגמה, יכול להיות מאגר ייעודי לרכיב Networking, ומאגר נפרד למאגר project factory המשמש ליצירה וניהול של פרויקטים. השיטה הזו מוצלחת בסביבות מבוזרות מאוד שבהן האחריות עוברת לעתים קרובות מצוות אחד לשני.

דוגמה למבנה של מאגר

אפשר לשלב את העקרונות האלה כדי לפצל את ההגדרות של Terraform בין סוגים שונים של מאגרים:

  • מאגר בסיסי
  • מאגרים ספציפיים לאפליקציות ולצוותים
מאגר בסיסי

מאגר בסיסי שמכיל רכיבים עיקריים חשובים, כמו תיקיות או הממשק הארגוני של IAM. הצוות המרכזי של הענן יכול לנהל את מאגר הנתונים הזה.

  • במאגר הזה יש לכלול ספרייה לכל רכיב ראשי (לדוגמה, תיקיות, רשתות וכו').
  • בספריות הרכיבים יש לכלול תיקייה נפרדת לכל סביבה (בהתאם להנחיות למבנה הספרייה שהזכרנו קודם).

מבנה המאגר הבסיסי

מאגרים ספציפיים לאפליקציות ולצוותים

מאגרים שהם ספציפיים לאפליקציות ולצוותים צריך לפרוס בנפרד לכל צוות. כך הם יוכלו לנהל בנפרד הגדרות שהן ייחודיות וספציפיות לאפליקציות ב-Terraform.

מבנה של מאגרים ספציפיים לאפליקציות ולצוותים

פעולות

כדי לשמור על תשתית מאובטחת חשוב ליצור תהליך יציב ומאובטח לעדכון Terraform.

התכנון קודם לכול

תמיד חשוב להכין מראש תוכנית להפעלות של Terraform, ולשמור אותה בקובץ פלט. אחרי שאחד מבעלי התשתית מאשר את התוכנית, מבצעים אותה. גם כשהמפתחים יוצרים אבטיפוס מקומי של שינויים, עליהם ליצור תוכנית ולבדוק אילו משאבים צריך להוסיף, לשנות ולכבות סופית לפני ביצוע התוכנית.

הטמעת צינור אוטומטי לעיבוד נתונים

כדי להבטיח שמירה על העקביות של ההקשר בזמן ההפעלה, יש להפעיל את Terraform באמצעות כלים אוטומטיים. אם מערכת build (כמו Jenkins) כבר נמצאת בשימוש ומאומצת באופן נרחב, השתמשו בה כדי להריץ את הפקודה terraform plan והפקודה terraform apply באופן אוטומטי. אם מערכת כזו לא קיימת, מומלץ להשתמש באחד משני הכלים Cloud Build או Terraform Cloud.

שימוש בפרטי הכניסה של חשבון השירות לאינטגרציה רציפה (CI)

כשמפעילים את Terraform מתוך מכונה בצינור עיבוד נתונים של CI/CD, המערכת צריכה לרשת את פרטי הכניסה של חשבון השירות מהשירות שמריץ את צינור עיבוד הנתונים. כשהדבר אפשרי, כדאי להפעיל צינורות לאינטגרציה רציפה (CI) ב-Google Cloud כי בסביבות Cloud Build,‏ Google Kubernetes Engine או Compute Engine תוכלו להשתמש בכלים כמו Workload Identity כדי להחדיר את פרטי הכניסה בלי להוריד את המפתחות של חשבון השירות. לצינורות עיבוד נתונים שפועלים במקומות אחרים, עדיף להשתמש באיחוד של Workload Identity כדי להשיג את פרטי הכניסה. את השימוש במפתחות של חשבון השירות שהורדתם יש לשמור כמוצא אחרון.

מניעת ייבוא של משאבים קיימים

במידת האפשר, יש להימנע מייבוא משאבים קיימים (באמצעות terraform import), כי הפעולה הזו עלולה להקשות על ההבנה המלאה של מקורות המידע וההגדרות של אותם משאבים שנוצרים באופן ידני. במקום זאת, אפשר ליצור משאבים חדשים באמצעות Terraform ולמחוק את המשאבים הישנים.

אם מחיקת משאבים ישנים מסובכת מדי, אפשר להשתמש בפקודת הייבוא terraform import עם אישור מפורש. אחרי שמייבאים משאבים ל-Terraform, יש לנהל אותם אך ורק באמצעות Terraform.

Google מספקת כלי שבעזרתו אפשר לייבא את המשאבים שלכם ב-Google Cloud למצב של Terraform. למידע נוסף, תוכלו לקרוא את המאמר ייבוא המשאבים של Google Cloud למצב של Terraform.

אי שינוי ידני של מצבים ב-Terraform

קובץ המצב של Terraform חיוני כדי לשמור על המיפוי בין ההגדרות של Terraform לבין המשאבים של Google Cloud. פגיעה בנתונים עלולה לגרום לבעיות חמורות בתשתית. כשצריך לשנות את קובץ המצב של Terraform יש להשתמש בפקודה terraform state.

בדיקה שוטפת של גרסאות מוצמדות

הצמדת הגרסאות מבטיחה יציבות, אבל מונעת הטמעה של תיקוני באגים ושיפורים נוספים בהגדרות. לכן, חשוב לבדוק באופן קבוע את ההצמדה של הגרסאות עבור Terraform, הספקים של Terraform והמודולים.

כדי לבצע את התהליך הזה אוטומטית, אפשר להשתמש בכלי כמו Dependabot.

שימוש ב-Application Default Credentials בזמן הרצה מקומית

כשמפתחים עוברים באופן אוטומטי על הגדרות מקומיות של Terraform, הם צריכים לבצע אימות באמצעות הפקודה gcloud auth application-default login כדי לייצר את פרטי הכניסה שמוגדרים כברירת מחדל באפליקציה. אין להוריד מפתחות לחשבון השירות, כי קשה יותר לנהל ולאבטח מפתחות שהורדו.

הגדרת משאבים חלופיים ל-Terraform

כדי להקל על הפיתוח המקומי, אתם יכולים להגדיר משאבים חלופיים ל-Terraform בפרופיל של מעטפת חלון הפקודות:

  • alias tf="terraform"
  • alias terrafrom="terraform"

אבטחה

כדי להשתמש ב-Terraform צריך גישה למידע רגיש בתשתית הענן. תוכלו להיעזר בשיטות המומלצות האלה כדי למזער את הסיכונים ולשפר את האבטחה הכוללת בענן.

שמירת המצב ביעד מרוחק

ללקוחות Google Cloud, מומלץ להשתמש בקצה העורפי לשמירת המצב ב-Cloud Storage. הגישה הזו נועלת את המצב ומאפשרת לצוותים לשתף פעולה. כמו כן, בגישה הזו יש הפרדה בין המצב וכל מידע נוסף שעשוי להיות רגיש לבין המערכת לבקרת הגרסאות.

חשוב לוודא שרק למערכת ה-build ולאדמינים עם הרשאות גבוהות יש גישה לקטגוריה שמשמשת לשמירת המצב ביעד המרוחק.

כדי למנוע הכנסת מצב בשלב הפיתוח למערכת בקרת הגרסאות, יש להשתמש ב-gitignore לקובצי מצב של Terraform.

הצפנת המצב

למרות שהקטגוריות של Google Cloud מוצפנות במנוחה, אפשר גם להשתמש במפתחות הצפנה באספקת הלקוח (CSEK) כדי להוסיף עוד שכבת הגנה. אפשר לעשות זאת באמצעות משתנה הסביבה GOOGLE_ENCRYPTION_KEY. גם אם לא שומרים סודות בקובץ המצב, תמיד חשוב להצפין את המצב כאמצעי אבטחה נוסף.

הימנעות משמירת סודות במצב

משאבים וספקי נתונים רבים ב-Terraform שומרים סודות כטקסט ללא הצפנה בקובץ המצב. אם אפשר, כדאי להימנע משמירת סודות בקובץ המצב. ריכזנו כמה דוגמאות לספקים ששומרים סודות כטקסט ללא הצפנה:

סימון פלטים עם מידע רגיש

במקום לנסות להצפין ערכים רגישים ידנית, כדאי להסתמך על יכולות הניהול המובנות של Terraform לקובצי מצב עם מידע רגיש. כשמייצאים ערכים רגישים כפלט, צריך לוודא שהערכים מסומנים בתור מידע רגיש.

הפרדת סמכויות

אם אי אפשר להריץ את Terraform ממערכת אוטומטית שאין למשתמשים גישה אליה, יש להפריד את ההרשאות והספריות כדי לשמור על עיקרון הפרדת הסמכויות. לדוגמה, פרויקט ברשת צריך להיות משויך לחשבון שירות של Terraform או למשתמש עם גישה שמוגבלת רק לפרויקט הזה.

הרצת בדיקות לפני ההטמעה

כשמריצים את Terraform בצינור עיבוד נתונים אוטומטי, יש להשתמש בכלי כמו gcloud terraform vet כדי לבדוק את פלט התוכנית מול המדיניות לפני ההטמעה. כך תוכלו לזהות בעיות רגרסיה באבטחה לפני שהן מתרחשות.

הרצת בדיקות שוטפות

אחרי ביצוע הפקודה terraform apply, צריך להריץ בדיקות אבטחה אוטומטיות. הבדיקות האלה יכולות לעזור למנוע בעיות אבטחה בתשתית. תוכלו להשתמש בכלים הבאים לבדיקות כאלה:

בדיקה

בדיקת המודולים וההגדרות של Terraform מתבססת לפעמים על תבניות ומוסכמות שונות מאלה של בדיקת הקוד של האפליקציה. בעוד שבדיקת הקוד של האפליקציה כרוכה בעיקר בבדיקת הלוגיקה העסקית של האפליקציה עצמה, בדיקה מלאה של קוד התשתית מחייבת לפרוס משאבים אמיתיים בענן, כדי למזער את הסיכון של כשלים בסביבת הייצור. יש כמה שיקולים שכדאי להביא בחשבון כשמריצים בדיקות ב-Terraform:

  • הרצת בדיקות ב-Terraform יוצרת, משנה ומכבה באופן סופי חלקים אמיתיים בתשתית, כך שהבדיקות עלולות להיות יקרות ולגזול זמן רב.
  • אי אפשר לבצע בדיקות יחידה (unit testing) של הארכיטקטורה מקצה לקצה. הגישה הטובה ביותר לבדיקות כאלה היא לחלק את הארכיטקטורה למודולים ולבדוק אותם בנפרד. היתרונות של הגישה הזו הם, בין השאר, שהבדיקות מתקצרות כך שאפשר לבצע פיתוח חוזר מהר יותר, שהעלות של כל בדיקה נמוכה יותר, ושיש פחות סיכוי לשגיאות בבדיקה בגלל גורמים שלא בשליטתכם.
  • מומלץ להימנע משימוש חוזר במצבים. בסיטואציות מסוימות, הבדיקות מתבצעות תוך שיתוף נתונים בין הגדרות, אבל באופן אידיאלי, כל בדיקה צריכה להיות עצמאית ולא כדאי להשתמש שוב באותו מצב בבדיקות שונות.

שימוש קודם כל בשיטות בדיקה פחות יקרות

אפשר לבדוק את Terraform בכמה שיטות. השיטות האלה הן (בסדר עולה לפי מחיר, זמן ריצה ועומק):

  • ניתוח סטטי: בדיקת התחביר והמבנה של ההגדרות ללא צורך לפרוס בכלל משאבים, באמצעות כלים כמו מהדרים (compilers), לינטרים (linters) והרצות בדיקה. כדי להריץ את הבדיקות האלה, אפשר להשתמש בפקודה terraform validate.
  • בדיקת השילוב של המודולים: כדי לוודא שהמודולים פועלים כמו שצריך, יש לבדוק כל מודול בנפרד. בבדיקת השילוב של המודולים, צריך לפרוס את המודול בסביבת הבדיקה ולוודא שהמשאבים שצופים שייווצרו אכן נוצרים. אפשר להיעזר ב-frameworks של בדיקות כדי להקל על כתיבת הבדיקות:
  • בדיקה מקצה לקצה: על ידי הרחבת הבדיקות לסביבה כולה אפשר לוודא שהמודולים השונים פועלים היטב ביחד. כשמשתמשים בשיטה הזו, צריך ליצור את הארכיטקטורה המלאה על ידי פריסת כל המודולים בסביבת בדיקה חדשה. באידיאל, סביבת הבדיקה צריכה להיות דומה ככל האפשר לסביבת הייצור. השיטה הזו אמנם יקרה, אבל היא מאפשרת לבדוק במידה הגבוהה ביותר של ביטחון שהשינויים לא פוגעים בסביבת הייצור.

התחלה בַּקטנה

חשוב לוודא שהבדיקות החוזרות נבנות זו על גבי זו. כדאי לשקול להתחיל מבדיקות קטנות ולאחר מכן להתקדם לבדיקות מורכבות יותר, בגישה של ניסוי וטעיה (fail fast).

רנדומיזציה של מזהי פרויקטים ושמות משאבים

כדי להימנע מקונפליקטים במתן שמות, יש לוודא שלכל פרויקט הגדרות יש מזהה פרויקט ייחודי וגלובלי וששמות המשאבים אינם חופפים בתוך כל פרויקט. לשם כך, מומלץ להשתמש במרחבי שמות (namespaces) למשאבים. ב-Terraform יש ספק רנדומיזציה (random provider) מובנה.

שימוש בסביבה נפרדת לבדיקה

משאבים רבים נוצרים ונמחקים במהלך הבדיקה. חשוב לוודא שהסביבה של הבדיקה מבודדת מפרויקטים בסביבת הפיתוח או בסביבת הייצור, כדי לא למחוק מהם משאבים בטעות כשמוחקים את משאבי הבדיקה. הגישה הכי טובה היא ליצור פרויקט או תיקייה חדשים לכל בדיקה. כדי להימנע מהגדרות שגויות, כדאי ליצור חשבונות שירות לכל בדיקה שמבצעים.

מחיקת משאבי הבדיקה

כדי לבדוק את קוד התשתית צריך לפרוס את המשאבים בפועל. כדי להימנע מחיובים, בסיום הבדיקה מומלץ למחוק את המשאבים שנוצרו.

כדי לכבות סופית את כל האובייקטים המרוחקים שמנוהלים על ידי הגדרה מסוימת, יש להשתמש בפקודה terraform destroy. חלק ממסגרות הבדיקה (frameworks) כוללות כבר שלב מובנה של מחיקת משאבים. לדוגמה, אם משתמשים ב-Terratest, אפשר להוסיף לבדיקה את הפקודה defer terraform.Destroy(t, terraformOptions). אם משתמשים ב-Kitchen-Terraform, אפשר למחוק את סביבת העבודה באמצעות הפקודה terraform kitchen delete WORKSPACE_NAME.

לאחר הרצת הפקודה terraform destroy, כדאי גם לבצע פעולות מחיקה נוספות כדי להסיר את המשאבים שלא הוסרו על ידי Terraform. כדי לעשות זאת, צריך למחוק את הפרויקטים ששימשו לביצוע הבדיקה או להשתמש בכלי כמו המודול project_cleanup.

קיצור זמני הבדיקות

כדי לקצר את זמני הבדיקות, אפשר להיעזר בשיטות הבאות:

  • הרצת בדיקות במקביל במסגרות בדיקה מסוימות (frameworks) אפשר לבצע מספר בדיקות ב-Terraform בו-זמנית.
    • לדוגמה, ב-Terratest אפשר להוסיף את השורה t.Parallel() אחרי ההגדרה של פונקציית הבדיקה.
  • בדיקה בשלבים מומלץ להפריד את הבדיקות להגדרות עצמאיות שאפשר לבדוק בנפרד. בגישה הזאת לא צריך לעבור את כל השלבים כשמבצעים את הבדיקה, ומקצרים את מחזור החזרה לפיתוח.
    • לדוגמה, ב-Kitchen-Terraform אפשר לפצל את הבדיקה לחבילות נפרדות. כשמבצעים בדיקה חוזרת, אפשר לבצע כל חבילה בנפרד.
    • באופן דומה, כשמשתמשים ב-Terratest, אפשר לעטוף כל שלב בבדיקה באמצעות stage(t, STAGE_NAME, CORRESPONDING_TESTFUNCTION). אפשר להגדיר משתני סביבה שמציינים אילו בדיקות להפעיל. לדוגמה, SKIPSTAGE_NAME="true".
    • התוכנית לבדיקת ה-framework תומכת בביצוע מדורג.

המאמרים הבאים