Terraform
Նկարագրիր սերվերները, DNS-ը և cloud ռեսուրսները կոդով, վերանայիր plan-ը, հետո կիրառիր այն։
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-ներից, ոչ թե կոդից։
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-ն։
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։
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-ներ պարունակել, օրինակ՝ բազայի գաղտնաբառեր, ուստի գաղտնագրիր այն և սահմանափակիր մուտքը։
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։
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-ը, ոչ թե փոխարինում։
Լավագույն պրակտիկաներ
- Առաջին օրվանից օգտագործիր remote state՝ versioning-ով, գաղտնագրմամբ և locking-ով։
- Ամրագրիր Terraform-ի և provider-ների տարբերակները և commit արա dependency lock ֆայլը։
- Plan-ը աշխատեցրու CI-ում յուրաքանչյուր pull request-ի վրա, իսկ apply արա միայն CI-ից՝ review-ից հետո։
- State-ը բաժանիր ըստ միջավայրի և ըստ մեծ բաղադրիչի՝ blast radius-ը սահմանափակելու համար։
- Երբեք secret-ներ մի hardcode արա. փոխանցիր դրանք environment variable-ներով կամ secret manager-ով։
- Կարևոր ռեսուրսները պաշտպանիր prevent_destroy-ով և plan-ում վերանայիր յուրաքանչյուր destroy։
- 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-ն օգտագործիր միայն թաքնված կախվածությունների համար, որոնք ոչ մի հղում չի արտահայտում։
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։
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-ը այն երբեք չի տեսնում։
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-ի վրա կիրառում են կազմակերպության կանոնները։
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."
}
}
Իրական իրավիճակներ
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-ի։
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-երը։
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 արված փոփոխությունները։
Շարունակեք սովորել
Պրոդուկտի գաղափար ունե՞ք։Եկեք ստեղծենք այն։
Պատմեք ձեր գաղափարի, պրոդուկտի կամ թիմի մասին։ Սովորաբար պատասխանում եմ մեկ աշխատանքային օրվա ընթացքում։