Բոլոր դասերը
DevOps և ամպԱռաջադեմ11 րոպե ընթերցումԱրժե իմանալ

Terraform

Նկարագրիր սերվերները, DNS-ը և cloud ռեսուրսները կոդով, վերանայիր plan-ը, հետո կիրառիր այն։

  • Infrastructure as code
  • State
  • Modules
  • Plan / apply
Քայլ 1/6
Infrastructure as code՝ ուզածդ սերվերներն ու database-ը նկարագրում ես main.tf ֆայլում ու պահում Git-ում։

30 վայրկյանում

Terraform-ը թույլ է տալիս ենթակառուցվածքդ (սերվերներ, բազաներ, DNS գրառումներ, bucket-ներ, թույլտվություններ) գրել որպես կոդի ֆայլեր։ Այն համեմատում է այդ կոդը իրականում գոյություն ունեցողի հետ և ցույց է տալիս plan՝ թե կոնկրետ ինչ է ստեղծելու, փոխելու կամ ջնջելու։ Հաստատելուց հետո այն փոփոխություններն անում է յուրաքանչյուր provider-ի API-ի միջոցով։ Ենթակառուցվածքը դառնում է վերանայելի, կրկնելի և փաստաթղթավորված Git-ում։

Իմ ամենօրյա stack-ում չէ․ գիտեմ հիմնական գաղափարները և մանրամասները կսովորեմ, երբ նախագիծը դա պահանջի։

Ինչու է սա կարևոր

  • Infrastructure as code-ը կարևոր է, որովհետև վեբ կոնսոլում սեղմելը կրկնելի չէ, վերանայելի չէ և մեկ ամսում մոռացվում է։
  • Թիմին այն պետք է, երբ կառավարում է մի բուռ ռեսուրսից ավելին, մի քանի միջավայր, կամ երբ մի քանի մարդ է փոխում ենթակառուցվածքը։
  • Plan քայլը անվտանգության ցանց է. տեսնում ես, որ փոփոխությունը ջնջելու է բազան, մինչև դա տեղի ունենա, ոչ թե հետո։
  • Իմ ներկայիս սերվերները բավական քիչ են՝ ձեռքով կառավարելու համար՝ փաստաթղթավորված runbook-ներով. Terraform-ը բնական հաջորդ քայլն է, երբ միջավայրերը շատանան կամ պետք լինի դրանք արագ ու նույնությամբ վերակառուցել։

Ինչպես է աշխատում

01Գաղափարը՝ infrastructure as code

Infrastructure as code նշանակում է, որ քո սերվերները, ցանցերը և DNS-ը սահմանված են տեքստային ֆայլերում, պահվում են Git-ում և փոխվում են pull request-ներով, ինչպես հավելվածի կոդը։ Terraform-ը օգտագործում է դեկլարատիվ լեզու՝ HCL. նկարագրում ես ցանկալի վերջնական վիճակը (այս չափի մեկ սերվեր, մեկ DNS գրառում, որը նայում է դրան), ոչ թե այնտեղ հասնելու քայլերը։ Քայլերը Terraform-ն ինքն է հաշվարկում։ Օգուտները շոշափելի են։ Staging միջավայրը կարող է production-ի ճշգրիտ պատճենը լինել։ Թիմի նոր անդամը կարդում է կոդը՝ ենթակառուցվածքը հասկանալու համար։ Փոփոխությունները վերանայվում են մինչև տեղի ունենալը, իսկ Git-ի պատմությունը ցույց է տալիս, թե ով ինչ է փոխել և ինչու։ Աղետից վերականգնումը արագանում է, որովհետև ենթակառուցվածքը կարող ես վերստեղծել կոդից, թեև տվյալները դեռ պետք է գան backup-ներից։

  • Դեկլարատիվ՝ նկարագրիր ինչը, ոչ թե ինչպեսը։
  • Տարբերակավորված և վերանայվող, ինչպես հավելվածի կոդը։
  • Terraform-ը ստեղծում է ենթակառուցվածքը, իսկ Ansible-ի նման գործիքները կարգավորում են այն, ինչ աշխատում է սերվերների ներսում։

02Provider-ներ և ռեսուրսներ

Terraform-ն ինքնին ոչինչ չգիտի AWS-ի կամ DigitalOcean-ի մասին։ Provider-ները plugin-ներ են, որոնք քո կոդը թարգմանում են մեկ պլատֆորմի API կանչերի. կան provider-ներ AWS-ի, Google Cloud-ի, Azure-ի, DigitalOcean-ի, Hetzner-ի, Cloudflare-ի, GitHub-ի, Kubernetes-ի և հարյուրավոր այլ պլատֆորմների համար։ Հայտարարում ես, թե որ provider-ներն են պետք, և ամրագրում դրանց տարբերակները, հետո terraform init-ը ներբեռնում է դրանք և ճշգրիտ տարբերակները գրանցում lock ֆայլում, որը commit ես անում։ Resource բլոկը նկարագրում է կառավարվող մեկ օբյեկտ՝ սերվեր, bucket, DNS գրառում։ Data բլոկը կարդում է արդեն գոյություն ունեցող ինչ-որ բան՝ առանց այն կառավարելու։ Variable-ները կոդը դարձնում են վերաօգտագործելի, output-ները բացահայտում են օգտակար արժեքներ, օրինակ՝ IP հասցեն, իսկ credentials-ը գալիս են environment variable-ներից, ոչ թե կոդից։

hcl
terraform {
  required_version = ">= 1.10"
  required_providers {
    digitalocean = { source = "digitalocean/digitalocean", version = "~> 2.0" }
    cloudflare   = { source = "cloudflare/cloudflare", version = "~> 5.0" }
  }
}
 
# tokens come from DIGITALOCEAN_TOKEN and CLOUDFLARE_API_TOKEN env vars
provider "digitalocean" {}
provider "cloudflare" {}
 
variable "ssh_key_fingerprint" { type = string }
variable "cloudflare_zone_id" { type = string }

03Օրինակ՝ սերվեր և դրա DNS գրառումը

Ահա իրական ենթակառուցվածքի ամբողջական, փոքր կտոր՝ Ubuntu սերվեր և DNS գրառում, որը hostname-ը ուղղում է դրան։ Ուշադրություն դարձրու DNS գրառման ներսում digitalocean_droplet.web.ipv4_address հղմանը։ Terraform-ը կարդում է այս հղումները՝ կախվածությունների գրաֆ կառուցելու համար, ուստի գիտի, որ սերվերը պետք է գոյություն ունենա, նախքան գրառումը ստեղծելը, և կարող է անկախ ռեսուրսները ստեղծել զուգահեռ։ Եթե հետո փոխես սերվերի չափը, plan-ը ցույց կտա in-place թարմացում, իսկ եթե փոխես image-ը, ցույց կտա, որ սերվերը պետք է ջնջվի ու վերստեղծվի. հենց այդպիսի անակնկալներ բացահայտելու համար է plan-ը։ Output-ը apply-ից հետո տպում է նոր IP-ն։

hcl
resource "digitalocean_droplet" "web" {
  name     = "web-1"
  image    = "ubuntu-24-04-x64"
  region   = "fra1"
  size     = "s-1vcpu-2gb"
  ssh_keys = [var.ssh_key_fingerprint]
}
 
resource "cloudflare_dns_record" "app" {
  zone_id = var.cloudflare_zone_id
  name    = "app.example.com"
  type    = "A"
  content = digitalocean_droplet.web.ipv4_address
  ttl     = 1
  proxied = true
}
 
output "server_ip" { value = digitalocean_droplet.web.ipv4_address }

04Plan և apply

Ամենօրյա workflow-ն ունի ֆիքսված ռիթմ։ terraform init-ը պատրաստում է աշխատանքային թղթապանակը և ներբեռնում provider-ները։ terraform fmt-ը և validate-ը բռնում են ոճի ու շարահյուսության խնդիրները։ terraform plan-ը համեմատում է երեք բան՝ քո կոդը, state ֆայլը և իրական ռեսուրսները, հետո տպում է յուրաքանչյուր գործողություն նշանով՝ պլյուս՝ ստեղծման, տիլդա՝ in-place թարմացման, մինուս՝ ջնջման և մինուս-պլյուս՝ փոխարինման համար։ Plan-ը ուշադիր կարդա, հատկապես ցանկացած destroy։ Plan-ը ֆայլում պահելը և հենց այդ ֆայլը կիրառելը երաշխավորում է, որ աշխատում է հենց այն, ինչ վերանայել ես։ Թիմում CI-ը plan է աշխատեցնում յուրաքանչյուր pull request-ի վրա և արդյունքը հրապարակում review-ի համար, իսկ apply-ը աշխատում է merge-ից հետո՝ CI-ից, ոչ թե ինչ-որ մեկի լափթոփից։ Թանկարժեք ռեսուրսների, օրինակ՝ բազաների վրա օգտագործիր prevent_destroy։

bash
terraform init
terraform fmt -check
terraform validate
terraform plan -out=tfplan
terraform apply tfplan
 
# inspect and troubleshoot
terraform state list
terraform output server_ip

05State, remote state և locking

Terraform-ը պահում է state ֆայլ՝ քարտեզ քո կոդի յուրաքանչյուր ռեսուրսից դեպի իրական օբյեկտը, որը կառավարում է, իր ID-ով ու ատրիբուտներով։ Առանց state-ի Terraform-ը չի կարող իմանալ, որ քո կոդի web-ը provider-ի մոտ 12345 սերվերն է։ Լռելյայն state-ը լոկալ ֆայլ է, որը կոտրվում է, հենց որ երկու մարդ կամ CI job է ներգրավվում։ Պահիր այն հեռակա՝ S3 bucket-ում կամ համատեղելի պահոցում, Terraform Cloud-ում կամ այլ backend-ում, versioning-ը միացված, որպեսզի կարողանաս վերականգնել վատ գրառումը։ Locking-ը թույլ չի տալիս երկու apply միաժամանակ աշխատել և վնասել state-ը։ S3 backend-ն այժմ աջակցում է native locking use_lockfile-ով. հին DynamoDB աղյուսակի մեթոդը deprecated է։ State-ը կարող է secret-ներ պարունակել, օրինակ՝ բազայի գաղտնաբառեր, ուստի գաղտնագրիր այն և սահմանափակիր մուտքը։

hcl
terraform {
  backend "s3" {
    bucket       = "example-terraform-state"
    key          = "prod/terraform.tfstate"
    region       = "eu-central-1"
    encrypt      = true
    use_lockfile = true
  }
}

06Module-ներ, drift և գործիքների լանդշաֆտը

Module-ը Terraform կոդի թղթապանակ է input-ներով ու output-ներով, ինչպես ֆունկցիան։ Գրիր web-server module մեկ անգամ և կանչիր այն staging-ի ու production-ի համար տարբեր չափերով։ Module-ները պահիր փոքր և կենտրոնացված, ընդհանուրների տարբերակները ամրագրիր, իսկ միջավայրերը պահիր առանձին state ֆայլերում, որպեսզի staging-ում սխալը չկարողանա դիպչել production-ին։ Drift-ը այն է, երբ իրականությունն այլևս չի համապատասխանում կոդին, սովորաբար որովհետև ինչ-որ մեկը կոնսոլում ինչ-որ բան ձեռքով է փոխել։ terraform plan-ը բացահայտում է այն. հետո կա՛մ թարմացնում ես կոդը՝ համապատասխանեցնելու համար, կա՛մ apply ես անում՝ հայտարարված վիճակը վերականգնելու համար։ Ինչ վերաբերում է գործիքներին. HashiCorp-ը 2023-ին Terraform-ը տեղափոխեց Business Source License-ի և այժմ IBM-ի մաս է։ OpenTofu-ն open-source fork-ն է, մեծապես համատեղելի, լրացուցիչ հնարավորություններով, օրինակ՝ ներկառուցված state encryption-ով։ Pulumi-ն HCL-ի փոխարեն օգտագործում է TypeScript, Python կամ Go։

hcl
module "web_staging" {
  source = "./modules/web-server"
  name   = "web-staging"
  size   = "s-1vcpu-1gb"
}
 
module "web_prod" {
  source = "./modules/web-server"
  name   = "web-prod"
  size   = "s-2vcpu-4gb"
}

Օգտագործեք, երբ

  • Մի քանի միջավայր (staging, production), որոնք կառուցվածքով պետք է նույնական մնան։
  • Թիմեր, որտեղ մեկից ավելի մարդ է փոխում ենթակառուցվածքը, և փոփոխություններին review է պետք։
  • Cloud կառուցվածքներ բազմաթիվ փոխկապակցված ռեսուրսներով՝ ցանցեր, IAM, բազաներ, DNS, CDN։
  • Երբ պետք է ենթակառուցվածքը արագ վերակառուցել աղետից հետո կամ նոր ռեգիոնում։

Մի՛ օգտագործեք, երբ

  • Մեկ կամ երկու երկարակյաց սերվեր, որոնք հազվադեպ են փոխվում. փաստաթղթավորված runbook-ը կարող է բավական լինել։
  • Սերվերների ներսում ծրագրերի կարգավորում (փաթեթներ, ֆայլեր, ծառայություններ). դա Ansible-ի, cloud-init-ի կամ Docker image-ների գործն է։
  • Հավելվածի հաճախակի deploy-ներ. կոդի համար օգտագործիր CI/CD, իսկ Terraform-ը՝ դրա տակի պլատֆորմի համար։

Այլընտրանքներ

  • OpenTofu

    Terraform-ի open-source fork, նախագծերի մեծ մասի համար ուղղակի փոխարինող, ավելացնում է state encryption։

  • Pulumi

    Infrastructure as code TypeScript-ով, Python-ով, Go-ով կամ C#-ով՝ իրական ցիկլերով ու թեստերով։

  • AWS CloudFormation / CDK

    AWS-ի սեփական IaC. խորը ինտեգրացիա AWS-ի հետ, բայց միայն AWS։

  • Ansible

    Կոնֆիգուրացիայի կառավարում SSH-ով. լրացնում է Terraform-ը, ոչ թե փոխարինում։

Լավագույն պրակտիկաներ

  1. Առաջին օրվանից օգտագործիր remote state՝ versioning-ով, գաղտնագրմամբ և locking-ով։
  2. Ամրագրիր Terraform-ի և provider-ների տարբերակները և commit արա dependency lock ֆայլը։
  3. Plan-ը աշխատեցրու CI-ում յուրաքանչյուր pull request-ի վրա, իսկ apply արա միայն CI-ից՝ review-ից հետո։
  4. State-ը բաժանիր ըստ միջավայրի և ըստ մեծ բաղադրիչի՝ blast radius-ը սահմանափակելու համար։
  5. Երբեք secret-ներ մի hardcode արա. փոխանցիր դրանք environment variable-ներով կամ secret manager-ով։
  6. Կարևոր ռեսուրսները պաշտպանիր prevent_destroy-ով և plan-ում վերանայիր յուրաքանչյուր destroy։
  7. Plan-ը աշխատեցրու ժամանակացույցով՝ drift-ը վաղ հայտնաբերելու համար։

Տարածված սխալներ

  • terraform.tfstate-ը Git-ում commit անել՝ բացահայտելով secret-ներ և առաջացնելով կոնֆլիկտներ։
  • Apply անել առանց plan-ը կարդալու և բազան փոխարինել՝ այն թարմացնելու փոխարեն։
  • Ռեսուրսները կոնսոլում ձեռքով փոխել, հետո զարմանալ, երբ apply-ը դրանք հետ է բերում։
  • Մեկ հսկա state ամեն ինչի համար, ուստի փոքր փոփոխությունը պահանջում է դանդաղ ու ռիսկային plan։
  • Կորցնել state ֆայլը և ստիպված լինել յուրաքանչյուր ռեսուրս ձեռքով import անել։

Խորը վերլուծություն

07Ինչպես է իրականում աշխատում plan-ը․ գրաֆը, անհայտները և փոխարինման հերթականությունը

Terraform-ը քո կոնֆիգուրացիան վերածում է կախվածությունների գրաֆի։ Յուրաքանչյուր resource, data source, variable և output գագաթ է, իսկ յուրաքանչյուր հղում ստեղծում է կող։ Plan-ի ժամանակ այն provider-ից թարմացնում է յուրաքանչյուր կառավարվող resource, համեմատում է թարմացված state-ը կոնֆիգուրացիայի հետ և provider-ին հարցնում, թե ինչ գործողություն է պետք։ Այն արժեքները, որոնք գոյություն ունեն միայն ստեղծումից հետո, օրինակ՝ նոր սերվերի IP-ն, երևում են որպես known after apply և հոսում են առաջ որպես անհայտներ, այդ պատճառով որոշ plan-եր ցույց են տալիս փոփոխությունների շղթա։ Terraform-ը գրաֆով անցնում է զուգահեռ՝ լռելյայն միաժամանակ տասը գործողություն, որը կարգավորվում է -parallelism-ով։ Երբ արգումենտը հնարավոր չէ թարմացնել տեղում, provider-ը այն նշում է որպես forcing replacement, և լռելյայն Terraform-ը նախ ոչնչացնում է հին օբյեկտը, հետո ստեղծում նորը։ create_before_destroy-ը շրջում է հերթականությունը՝ downtime-ից խուսափելու համար, եթե միայն անունները չեն բախվում։ depends_on-ն օգտագործիր միայն թաքնված կախվածությունների համար, որոնք ոչ մի հղում չի արտահայտում։

hcl
resource "aws_launch_template" "web" {
  name_prefix   = "web-"            # unique names allow create-before-destroy
  image_id      = var.ami_id
  instance_type = "t4g.small"
 
  lifecycle {
    create_before_destroy = true
    ignore_changes        = [tags["LastScannedAt"]]   # set by another tool
  }
}

08count-ն ընդդեմ for_each-ի և refactoring՝ առանց ոչնչացնելու

count-ը ստեղծում է instance-ների ցուցակ, որոնց դիմում են index-ով՝ server[0], server[1], server[2]։ Հեռացրու մուտքային ցուցակի միջին տարրը, և բոլոր հաջորդ index-ները կտեղաշարժվեն, ուստի Terraform-ը կպլանավորի ոչնչացնել և վերստեղծել սերվերներ, որոնք իրականում չեն փոխվել։ for_each-ը ստեղծում է map, որին դիմում են key-ով՝ server["api"], server["worker"], ուստի մեկ key-ի հեռացումն ազդում է միայն այդ instance-ի վրա․ նախընտրիր այն, երբ տարրերն ունեն բնական ինքնություն։ Resource-ի անվանափոխումը կամ module-ի մեջ տեղափոխելը նույնպես փոխում է դրա հասցեն, և առանց օգնության Terraform-ը կոչնչացներ հին հասցեն և կստեղծեր նորը։ moved block-ը անվանափոխումը հայտարարում է կոդում, ուստի plan-ը ցույց է տալիս տեղափոխություն՝ առանց փոփոխությունների, և թիմակիցներն ու CI-ը դա ավտոմատ վերցնում են, ինչը terraform state mv-ն չի կարող անել։ Resource-ը կառավարելը դադարեցնելու համար՝ առանց այն ոչնչացնելու, օգտագործիր removed block՝ lifecycle destroy = false-ով։ Գոյություն ունեցող resource-ը «որդեգրելու» համար օգտագործիր import block։

hcl
resource "digitalocean_droplet" "app" {
  for_each = { api = "s-2vcpu-4gb", worker = "s-1vcpu-2gb" }
  name     = "app-${each.key}"
  size     = each.value
  image    = "ubuntu-24-04-x64"
  region   = "fra1"
}
 
moved {
  from = digitalocean_droplet.web
  to   = digitalocean_droplet.app["api"]
}
 
import {
  to = cloudflare_dns_record.legacy
  id = "zone_id/record_id"
}

09Գաղտնիքներ․ sensitive, ephemeral և write-only արժեքներ

Variable-ը կամ output-ը sensitive = true նշելը միայն թաքցնում է այն plan-ի արդյունքում և log-երում․ արժեքը միևնույն է գրվում է state ֆայլում բաց տեքստով։ Այդ պատճառով state-ը պետք է գաղտնագրված լինի, և հասանելիությունը՝ վերահսկվի։ Նոր տարբերակները ավելի հեռուն են գնում։ Terraform 1.10-ը ավելացրեց ephemeral input variable-ներ, output-ներ և ephemeral resource-ներ՝ արժեքներ, որոնք գոյություն ունեն գործարկման ընթացքում, բայց երբեք չեն գրվում plan կամ state ֆայլերում, օրինակ՝ secret manager-ից վերցված գաղտնաբառը։ Terraform 1.11-ը ավելացրեց write-only արգումենտներ՝ _wo վերջածանցով, որոնք թույլ են տալիս կառավարվող resource-ներին ընդունել այդ ephemeral արժեքները․ provider-ը գաղտնիքն ուղարկում է API-ին, բայց երբեք չի պահում։ Քանի որ Terraform-ը չի տեսնում պահված արժեքը, այն rotation անելու համար մեծացնում ես ուղեկից version արգումենտը։ Ավելի պարզ տարբերակ, որտեղ պլատֆորմն աջակցում է, ծառայությանը թույլ տալն է ինքը գեներացնի և կառավարի գաղտնիքը, ինչպես RDS-ը կարող է manage_master_user_password-ով, այնպես որ Terraform-ը այն երբեք չի տեսնում։

hcl
ephemeral "random_password" "db" {
  length = 32
}
 
resource "aws_db_instance" "main" {
  identifier          = "app-prod"
  engine              = "postgres"
  instance_class      = "db.t4g.medium"
  allocated_storage   = 50
  username            = "app"
  password_wo         = ephemeral.random_password.db.result
  password_wo_version = 1          # bump to rotate
}

10Պաշտպանիչ ցանկապատեր․ validation, check-եր, թեստեր և policy as code

Terraform-ն ունի ներկառուցված ստուգումների մի քանի շերտ։ Variable-ի validation block-երը մերժում են սխալ մուտքը դեռ plan-ից առաջ, օրինակ՝ անհայտ միջավայրի անուն կամ instance-ի չափ, որը թույլատրված ցուցակում չէ։ Resource-ի lifecycle-ի ներսում precondition-ները և postcondition-ները ձևակերպում են ենթադրություններ, որոնք պետք է ճիշտ լինեն, օրինակ՝ որ ընտրված image-ը ճիշտ ճարտարապետության համար է։ Check block-երը գործարկում են assertion-ներ apply-ից հետո, օրինակ՝ HTTP request նոր endpoint-ին, և հաղորդում են warning-ներ՝ առանց արգելափակելու։ terraform test հրամանը, .tftest.hcl-ով ավարտվող ֆայլերով, plan կամ apply է անում թեստային մուտքերով և ստուգում արդյունքները․ այսպես են module-ների հեղինակները բռնում regression-ները։ Terraform-ից դուրս tflint-ը գտնում է provider-ին հատուկ սխալներ, Checkov-ի կամ Trivy-ի նման scanner-ները նշում են անապահով կարգավորումներ, օրինակ՝ բաց security group-եր, իսկ policy engine-ները, ինչպիսիք են OPA-ն Conftest-ի հետ կամ Sentinel-ը HCP Terraform-ում, apply-ից առաջ plan-ի վրա կիրառում են կազմակերպության կանոնները։

hcl
variable "environment" {
  type = string
  validation {
    condition     = contains(["staging", "prod"], var.environment)
    error_message = "environment must be staging or prod."
  }
}
 
check "site_up" {
  data "http" "health" { url = "https://app.example.com/healthz" }
  assert {
    condition     = data.http.health.status_code == 200
    error_message = "Health check did not return 200."
  }
}

Իրական իրավիճակներ

01

State-ը lock է արված, բայց ոչ ոք ոչինչ չի գործարկում

Խնդիր

CI job-ը չեղարկվել է apply-ի կեսին։ Հիմա ամեն նոր plan ձախողվում է սխալով, որ state-ը lock է արված, և ցույց է տալիս lock ID, user և մեկ ժամ առաջվա ժամանակ։

Ինչպես լուծել

Նախ համոզվիր, որ գործարկումն իսկապես կանգ է առել․ ստուգիր CI համակարգը և հարցրու lock-ում նշված մարդուն։ Երբեք unlock մի արա, քանի դեռ apply-ը կարող է շարունակել գրել, որովհետև երկու գրող փչացնում են state-ը։ Հետո գործարկիր terraform force-unlock՝ սխալի հաղորդագրության lock ID-ով։ Քանի որ apply-ը ընդհատվել է, որոշ resource-ներ կարող են գոյություն ունենալ՝ ամբողջությամբ գրանցված չլինելով, ուստի գործարկիր terraform plan և ուշադիր կարդա այն․ այն ամենը, ինչ ստեղծվել է, բայց state-ում չկա, կարելի է import անել։ Եթե ինչ-որ բան սխալ է թվում, ստուգիր state bucket-ի տարբերակների պատմությունը։ Կանխելու համար CI-ը կարգավորիր այնպես, որ apply job-երը երբեք ավտոմատ չչեղարկվեն՝ օգտագործելով concurrency group առանց cancel-in-progress-ի։

02

Plan-ը ուզում է փոխարինել production բազան

Խնդիր

Փոքր pull request-ը, որը փոխում էր բազայի կարգավորումները, տալիս է plan, ըստ որի production բազան կոչնչացվի և կվերստեղծվի։ Reviewer-ը քիչ էր մնում հաստատեր այն։

Ինչպես լուծել

Կանգ առ և plan-ում գտիր forces replacement նշված տողը․ այն անվանում է պատասխանատու արգումենտը, հաճախ՝ engine-ի տարբերակը, storage-ի տեսակը, identifier-ը, subnet group-ը կամ availability zone-ը։ Որոշիր՝ արդյոք այդ փոփոխությունն իսկապես պետք է։ Հաճախ՝ ոչ, և լուծումը այն ետ շրջելն է կամ փոխելը աջակցվող in-place ճանապարհով, օրինակ՝ engine-ի minor upgrade-ով։ Եթե արժեքը փոխել է մեկ այլ գործիք կամ console-ը, ignore_changes-ը կարող է տեղին լինել։ Եթե փոխարինումն իսկապես անհրաժեշտ է, պլանավորիր այն որպես migration՝ snapshot-ով և downtime-ի պատուհանով, ոչ թե որպես սովորական apply։ Բազային ավելացրու lifecycle prevent_destroy և CI policy, որը արգելափակում է կրիտիկական resource-ներ ջնջող plan-երը։

03

Console-ով կառուցված ենթակառուցվածքը Terraform-ի տակ բերելը

Խնդիր

Ընկերությունը երկու տարի աշխատել է console-ում ձեռքով ստեղծված resource-ների վրա․ VPC, սերվերներ, բազա, DNS zone-եր։ Նրանք ուզում են Terraform՝ առանց որևէ բան վերակառուցելու կամ downtime առաջացնելու։

Ինչպես լուծել

Գույքագրիր այն, ինչ կա, և որոշիր՝ ինչը կառավարել, սկսելով ամենաքիչ ռիսկային շերտից, օրինակ՝ DNS-ից։ Գրիր import block-եր, որոնք յուրաքանչյուր գոյություն ունեցող ID կապում են resource-ի հասցեի հետ, ապա գործարկիր terraform plan -generate-config-out=generated.tf, որն իրական resource-ներից կազմում է կոնֆիգուրացիայի սևագիր։ Մաքրիր գեներացված կոդը․ hard-coded ID-ները փոխարինիր հղումներով, առանձնացրու variable-ներ և խմբավորիր module-ներում։ Կրկնիր, մինչև terraform plan-ը ցույց տա, որ փոփոխություններ չկան, ինչն ապացուցում է, որ կոդն ու իրականությունը համընկնում են, ապա apply արա՝ import-ները state-ում գրանցելու համար։ Կրկնիր շերտ առ շերտ՝ սկզբից ունենալով remote state և CI։ Վերջում production-ում հանիր console-ով գրելու հասանելիությունը, որպեսզի կոդը մնա ճշմարտության աղբյուրը։

Արագ ուղեցույց

Դիտել drift-ը՝ առանց որևէ բան փոխելու
terraform plan -refresh-only
Ազատել կախված lock-ը (ստուգելուց հետո)
terraform force-unlock <LOCK_ID>
Գեներացնել config import block-երի համար
terraform plan -generate-config-out=generated.tf
Ստիպել մեկ resource-ի վերստեղծումը
terraform plan -replace=digitalocean_droplet.web
Lock անել provider-ները բոլոր CI և laptop պլատֆորմների համար
terraform providers lock -platform=linux_amd64 -platform=darwin_arm64
Ցույց տալ մեկ resource-ի state-ը
terraform state show digitalocean_droplet.web
Փորձարկել արտահայտությունները ինտերակտիվ
terraform console
Գործարկել module-ների թեստերը
terraform test
Թարմացնել provider-ները սահմանափակումների շրջանակում
terraform init -upgrade
Նկարել կախվածությունների գրաֆը
terraform graph | dot -Tsvg > graph.svg

Հարցեր և պատասխաններ

Q1Ի՞նչ է Terraform state-ը, և ինչո՞ւ այն պետք է remote լինի։

State-ը Terraform-ի գրառումն է այն մասին, թե որ իրական ռեսուրսն է համապատասխանում կոդի յուրաքանչյուր ռեսուրսին՝ իր ատրիբուտներով ու կախվածություններով։ Terraform-ը օգտագործում է այն plan-ը հաշվարկելու և այն բաներին հետևելու համար, որոնք provider-ի API-ն հեշտությամբ չի կարող նկարագրել։ Լոկալ state ֆայլը աշխատում է միայն մեկ մարդու համար։ Remote state-ը, օրինակ՝ versioning-ով S3 bucket-ում կամ Terraform Cloud-ում, թիմին ու CI-ին տալիս է ճշմարտության մեկ աղբյուր, սխալներից վերականգնվելու պատմություն և locking, որպեսզի երկու apply չկարողանան միաժամանակ աշխատել։ Քանի որ state-ը կարող է զգայուն արժեքներ պահել, օրինակ՝ գաղտնաբառեր, այն պետք է գաղտնագրված և մուտքով վերահսկվող լինի և երբեք չcommit արվի Git-ում։

Q2Ո՞րն է տարբերությունը terraform plan-ի և terraform apply-ի միջև։

Plan-ը read-only է։ Այն թարմացնում է իրական ռեսուրսների հայտնի վիճակը, համեմատում կոնֆիգուրացիայի հետ և տպում անհրաժեշտ գործողությունները՝ ստեղծում, in-place թարմացում, փոխարինում կամ ջնջում։ Ոչինչ չի փոխվում։ Apply-ը կատարում է այդ գործողությունները provider-ների API-ների միջոցով և թարմացնում state ֆայլը։ Եթե apply աշխատեցնես առանց պահված plan-ի, այն կհաշվարկի նոր plan և հաստատում կխնդրի։ Թիմում անվտանգ մոդելը հետևյալն է՝ plan-ը աշխատեցնել CI-ում pull request-ի վրա, վերանայել այն կոդի հետ, պահել և merge-ից հետո apply անել հենց այդ պահված plan-ը, որպեսզի վերանայված և կատարված փոփոխությունները երաշխավորված նույնական լինեն։

Q3Ի՞նչ է drift-ը, և ինչպե՞ս ես աշխատում դրա հետ։

Drift-ը ցանկացած տարբերություն է իրական ենթակառուցվածքի և այն բանի միջև, ինչ նկարագրում են Terraform-ի կոնֆիգուրացիան ու state-ը. սովորաբար դրա պատճառը կոնսոլում ձեռքով փոփոխություններն են, մեկ այլ գործիք կամ provider-ի կողմից լռելյայն արժեքների փոփոխությունը։ terraform plan-ը ցույց է տալիս այն որպես փոփոխություններ, որոնք Terraform-ն ուզում է անել։ Հետո որոշում ես. եթե ձեռքով փոփոխությունը ճիշտ էր, թարմացրու կոդը, որպեսզի plan-ը դատարկ դառնա, եթե ոչ՝ apply արա՝ հայտարարված վիճակը վերականգնելու համար։ Drift-ը նվազեցնելու համար production-ում սահմանափակիր կոնսոլում գրելու մուտքը, բոլոր փոփոխություններն արա pull request-ներով և CI-ում աշխատեցրու ժամանակացույցով plan, որը ծանուցում է, երբ դատարկ չէ։ Որոշ թիմեր օգտագործում են refresh-only plan-եր՝ drift-ը վերանայելու համար նախքան գործելը։

Q4Ի՞նչ կլինի, եթե կորցնես state ֆայլը։

Terraform-ն այլևս չի իմանում, թե որ իրական ռեսուրսներն է կառավարում։ Apply աշխատեցնելը կփորձի ամեն ինչ նորից ստեղծել՝ առաջացնելով կրկնօրինակներ կամ անունների կոնֆլիկտներ։ Վերականգնումը նշանակում է state-ը վերականգնել backup-ից, ահա թե ինչու է կարևոր versioning-ով remote state-ը, կամ վերակառուցել այն import-ով։ Ժամանակակից Terraform-ը աջակցում է import բլոկներ կոնֆիգուրացիայում, ուստի հայտարարում ես, թե գոյություն ունեցող որ ռեսուրսի ID-ն որ հասցեին է համապատասխանում, վերանայում plan-ում և apply անում։ Մեծ կառուցվածքների համար դա դանդաղ է, ուստի իրական պատասխանը կանխարգելումն է՝ versioning-ով, գաղտնագրված remote state, սահմանափակ մուտք, locking և state ֆայլերը երբեք ձեռքով չխմբագրել, բացառությամբ terraform state հրամանների միջոցով։

Q5Terraform, OpenTofu, թե՞ Pulumi. ինչպե՞ս ընտրել։

Terraform-ն ունի ամենամեծ էկոհամակարգը, փաստաթղթավորումը և աշխատուժի շուկան, իսկ HCL-ը ընթեռնելի է operations-ի մարդկանց համար։ 2023-ից այն օգտագործում է Business Source License, որը ընկերությունների մեծ մասի համար նորմալ է, բայց սահմանափակում է մրցակից արտադրանքներ կառուցելը, իսկ HashiCorp-ը այժմ IBM-ի մաս է։ OpenTofu-ն open-source fork-ն է Linux Foundation-ի ներքո, համատեղելի Terraform-ի կոդի ու provider-ների մեծ մասի հետ, և ավելացնում է այնպիսի հնարավորություններ, ինչպիսին է client-side state encryption-ը. այն հարմար է թիմերի համար, որոնք ուզում են բաց լիցենզիա։ Pulumi-ն թույլ է տալիս ենթակառուցվածքը գրել TypeScript-ով, Python-ով կամ Go-ով, ինչը ծրագրավորողներին դուր է գալիս ցիկլերի, աբստրակցիաների և unit թեստերի համար։ Ես կընտրեի՝ ելնելով լիցենզիայի պահանջներից, թիմի հմտություններից և առկա կոդից։

Q6Terraform, թե՞ Ansible։

Դրանք լուծում են տարբեր շերտեր և լավ են աշխատում միասին։ Terraform-ը API-ների միջոցով ստեղծում է ենթակառուցվածք՝ սերվերներ, ցանցեր, load balancer-ներ, DNS, բազաներ, IAM։ Այն դեկլարատիվ է և հետևում է state-ին, ուստի գիտի, թե ինչ փոխել կամ ջնջել։ Ansible-ը SSH-ով կարգավորում է այն, ինչ աշխատում է մեքենաների ներսում՝ փաթեթներ, օգտատերեր, կոնֆիգ ֆայլեր, ծառայություններ, և հիմնականում procedural է ու stateless։ Տարածված հոսքն այսպիսին է՝ Terraform-ը ստեղծում է սերվերը և դրա DNS գրառումը, հետո Ansible-ը կամ cloud-init-ը hardening և կարգավորում են անում։ Կոնտեյներների դեպքում Ansible-ի աշխատանքի մեծ մասը տեղափոխվում է Docker image-ների մեջ, և Terraform-ը գումարած կոնտեյներային պլատֆորմը ծածկում են կարիքների մեծ մասը՝ Ansible-ին թողնելով սերվերի բազային կարգավորումը։

Սենիոր մակարդակի հարցեր

S1Ինչպե՞ս կառուցել Terraform-ը մի քանի միջավայրի և թիմի համար։

State-ը բաժանիր ըստ միջավայրի և ըստ կոմպոնենտի, որպեսզի staging-ում սխալը չկարողանա դիպչել production-ին, և ամեն plan մնա փոքր ու արագ։ Տարածված կառուցվածքն է՝ վերօգտագործելի module-ները մեկ տեղում, ապա root թղթապանակ յուրաքանչյուր միջավայրի և կոմպոնենտի համար, օրինակ՝ prod/network, prod/database, prod/app, յուրաքանչյուրն իր backend key-ով, որոնք կանչում են ընդհանուր module-ները միջավայրին հատուկ արժեքներով։ Կոմպոնենտները արժեքներ են փոխանակում output-ներով, որոնք կարդացվում են terraform_remote_state-ով, կամ, ավելի լավ, data source-ներով կամ parameter store-ով՝ կապը պահելով բացահայտ։ Workspace-ները հարմար են նույնական պատճենների համար, օրինակ՝ կարճաժամկետ preview միջավայրերի, բայց հեշտ է դրանք սխալ օգտագործել prod-ն ընդդեմ staging-ի համար, որովհետև սխալ ընտրված workspace-ը անտեսանելի է։ Terragrunt-ի կամ HCP Terraform stacks-ի նման գործիքները նվազեցնում են կրկնությունը։ Փոխզիջումը շատ փոքր state-երն են, ինչը նշանակում է ավելի շատ համակարգում խաչվող փոփոխությունների համար, բայց շատ ավելի փոքր blast radius։

S2Ինչպե՞ս անվտանգ գործարկել Terraform-ը CI-ում։

Plan-եր ամեն pull request-ի վրա, apply-ներ միայն main branch-ից, և ընդհանուր միջավայրերի համար՝ երբեք laptop-ներից։ CI-ը նույնականացրու OIDC-ով՝ կարճաժամկետ cloud role-երով․ հիմնականում կարդալու role plan-ի համար և ավելի ուժեղ role apply-ի համար, որը հասանելի է միայն պաշտպանված main branch-ին կամ environment-ին։ Pull request-ի վրա գործարկիր fmt, validate, tflint, security scanner և plan, և plan-ը հրապարակիր որպես comment, որպեսզի reviewer-ները կարդան իրական փոփոխությունները, ոչ թե միայն կոդը։ Merge-ից հետո apply արա այդ commit-ի պահված plan-ը, կամ նորից plan արա և պահանջիր հաստատում, եթե plan-ը փոխվել է։ Գործարկումները յուրաքանչյուր state-ի համար հերթականացրու concurrency group-ով և երբեք մի չեղարկիր ընթացող apply-ը։ Ավելացրու պլանավորված drift plan։ HCP Terraform-ի, Spacelift-ի կամ Atlantis-ի նման պլատֆորմները սրա մեծ մասը տալիս են «տուփից դուրս»։

S3Provider-ի նոր major տարբերակը վերանվանում է resource-ները։ Ինչպե՞ս թարմացնել։

Վերաբերվիր դրան որպես նախագծի, ոչ թե տարբերակի պարզ բարձրացման։ Կարդա upgrade guide-ը, որը թվարկում է վերանվանված resource-ները, փոխված արգումենտները և հեռացված լռելյայն արժեքները․ օրինակ՝ Cloudflare provider-ի 5-րդ տարբերակը վերանվանեց մի քանի resource և փոխեց բազմաթիվ ատրիբուտների ձևը։ Թարմացրու առանձին branch-ում terraform init -upgrade-ով և ուղղիր կոնֆիգուրացիան, մինչև validate-ն անցնի։ Վերանվանված resource տեսակների համար օգտագործիր moved block-եր, որտեղ provider-ն աջակցում է տեսակների միջև տեղափոխումը, կամ removed block-ով հեռացրու հին հասցեն state-ից և նույն օբյեկտը import արա նոր հասցեով։ Նպատակը plan է, որը ցույց չի տալիս իրական ենթակառուցվածքի ոչ մի փոխարինում։ Արա դա ըստ state-ի՝ սկսելով ամենաքիչ կրիտիկական միջավայրից, և lock ֆայլի փոփոխությունը պահիր նույն pull request-ում։ Major տարբերակները pessimistic operator-ով ամրագրելը երաշխավորում է, որ նման թարմացումները տեղի են ունենում միայն գիտակցաբար։

Բառարան

Provider
Plugin, որը Terraform-ի resource-ները թարգմանում է մեկ պլատֆորմի API կանչերի, օրինակ՝ AWS-ի կամ Cloudflare-ի։
Resource address
Resource-ի եզակի ուղին կոնֆիգուրացիայում, օրինակ՝ module.web.aws_instance.app["api"]։
State
Terraform-ի գրառումը, որը յուրաքանչյուր resource-ի հասցե կապում է իր կառավարած իրական օբյեկտի և դրա ատրիբուտների հետ։
Backend
Այն տեղը, որտեղ state-ը պահվում և lock է արվում, օրինակ՝ լոկալ ֆայլ, S3 bucket կամ HCP Terraform։
State lock
Նշիչ, որը թույլ չի տալիս Terraform-ի երկու գործարկումների միաժամանակ գրել նույն state-ը։
Drift
Տարբերություններ իրական ենթակառուցվածքի և կոնֆիգուրացիայով ու state-ով նկարագրվածի միջև, հաճախ՝ ձեռքով փոփոխությունների պատճառով։
Module
Terraform կոդի վերօգտագործելի թղթապանակ՝ մուտքային variable-ներով և output-ներով, որը կանչվում է ֆունկցիայի պես։
moved block
Կոնֆիգուրացիա, որը Terraform-ին ասում է, որ resource-ը փոխել է հասցեն, որպեսզի այն state-ում տեղափոխվի, ոչ թե վերստեղծվի։
Ephemeral value
Արժեք, որը հասանելի է գործարկման ընթացքում, բայց երբեք չի գրվում plan կամ state ֆայլերում․ օգտագործվում է գաղտնիքների համար։
Saved plan
-out-ով ֆայլում գրված plan, որպեսզի apply-ը կատարի հենց review արված փոփոխությունները։

Պրոդուկտի գաղափար ունե՞ք։Եկեք ստեղծենք այն։

Պատմեք ձեր գաղափարի, պրոդուկտի կամ թիմի մասին։ Սովորաբար պատասխանում եմ մեկ աշխատանքային օրվա ընթացքում։