এই পর্বে যা শিখবেন
- HCL পড়া, version constraint লেখা আর lock file-এর কাজ বোঝা
- Resource address, argument, attribute, data source আর dependency graph চেনা
- Variable, tfvars, local আর output ব্যবহার, আর কোন মান জেতে তা বলতে পারা
fmt,validate,plan,apply,destroyনিরাপদ ক্রমে চালানো, আর saved plan-এর সুবিধা- Plan-এর প্রতিটি চিহ্ন পড়া, আর কোন বদল replace ডেকে আনে তা ধরা
for_eachকেনcount-এর চেয়ে নিরাপদ, আরlifecycle-এর তিনটি নিয়ম কখন লাগে
১ · Terraform আসলে কী করে
আগের পর্বে আপনি IaC-এর ধারণা পেয়েছেন: server বা network হাতে না বানিয়ে ফাইলে লেখা। সে ধারণাকে কাজে নামানোর সবচেয়ে প্রচলিত হাতিয়ারগুলোর একটি Terraform. এটি declarative ধরনের: আপনি বলেন শেষে কী থাকবে, পথ বের করে সে নিজে।
একজন ঠিকাদারের কথা ভাবুন। আপনি বাড়ির নকশা দিলেন। তাঁর কাছে আছে একটি হিসাবের খাতা, যাতে লেখা আগে কী কী বানানো হয়েছে। তিনি নকশা আর খাতা মিলিয়ে কাজের তালিকা বানান: এই দেয়াল নতুন, ওই জানালা বদল। আপনি তালিকা দেখে সই করলে মিস্ত্রিরা কাজে নামেন। রাজমিস্ত্রি আর বিদ্যুৎ মিস্ত্রি আলাদা লোক, কিন্তু ঠিকাদার একজনই। Terraform-এ নকশা হলো .tf ফাইল, খাতা হলো state, তালিকা হলো plan, আর মিস্ত্রি হলো provider.
Config থেকে আসল resource পর্যন্ত
Config and state go into Terraform Core; providers call the APIs; results return to state
HashiCorp ২০১৪ সালে Terraform প্রকাশ করে; প্রতিষ্ঠাতাদের একজন Mitchell Hashimoto. শুরুতে লাইসেন্স ছিল MPL 2.0. আগস্ট ২০২৩-এ তা বদলে BUSL হয়, আর তার জবাবে Linux Foundation-এর অধীনে আসে fork OpenTofu. এই পর্বের ধারণা দুটোতেই প্রায় একই, যদিও নতুন feature-এ পার্থক্য তৈরি হতে পারে।
আউটপুট সম্পর্কে: এই পর্বের সব আউটপুট একটি আসল run থেকে নেওয়া: Terraform ১.১৭, hashicorp/kubernetes ৩.৪, hashicorp/local ২.৯, আর local single-node cluster (Kubernetes ১.৩৪). কোনো cloud account লাগেনি। আপনার run-এ id, hash, uid আর সময় আলাদা হবে। লম্বা অংশ … দিয়ে ছোট করা। Cloud provider-এর plan এখানে চালানো হয়নি।
কোন কাজে Terraform, কোন কাজে নয়
Terraform-এর শক্তি হলো জিনিসের জন্ম থেকে মৃত্যু পর্যন্ত হিসাব রাখা। তবে সব কাজের জন্য এটি সেরা নয়। সাধারণ ভাগটি এমন, যদিও দলভেদে আলাদা হয়:
| কাজ | প্রচলিত হাতিয়ার | কারণ |
|---|---|---|
| VPC, VM, database, DNS, IAM বানানো | Terraform, OpenTofu, CloudFormation, Bicep | Cloud resource-এর পুরো জীবন হিসাবে থাকে |
| Cluster-এর ভেতরে app deploy | kubectl, Helm, GitOps tool | Rollout আর ঘনঘন release সেখানেই সহজ |
| Server-এর ভেতরে package আর config | Ansible, machine image | পর্ব ৫৬-এর বিষয় |
| একবারের ছোট পরীক্ষা | Console বা CLI | পরে মুছে ফেললে কোড লেখার দরকার নেই |
এই পর্বে আমরা Kubernetes provider ব্যবহার করছি একটি বিনা খরচের পরীক্ষাগার হিসেবে, কারণ এতে আসল API, আসল state আর আসল replace দেখা যায়। এর মানে এই নয় যে production-এ সব app Terraform দিয়েই deploy করতে হবে।
২ · HCL, provider, init আর lock file
Terraform-এর ফাইল লেখা হয় HCL ভাষায়। এর গঠন খুব ছোট। একটি block-এর থাকে ধরন, কিছু label আর { }-এর ভেতরে argument. যেমন resource "local_file" "report" { … }-এ ধরন resource, label দুটি resource type আর আপনার দেওয়া নাম। ফোল্ডারের সব .tf ফাইল মিলে একটি config হয়, তাই ফাইলের নাম শুধু গুছিয়ে রাখার জন্য।
Argument-এর ডান দিকে থাকে একটি expression, অর্থাৎ এমন কিছু যার মান বের করা যায়। পুরো পর্বে এই কয়েক ধরনই বারবার আসবে:
| লেখা | অর্থ |
|---|---|
"bazar", 3, true | সরাসরি মান: string, number, bool |
["dhaka", "sylhet"] | List: ক্রম আছে, গোনা হয় ০ থেকে |
{ app = "bazar" } | Map: নাম আর মানের জোড়া |
"${var.app_name}-web" | String-এর ভেতরে মান বসানো (interpolation) |
var.x, local.x, data.type.name.attr | অন্য জায়গার মান পড়া (reference) |
length(var.branches) | Built-in function; নিজের function লেখা যায় না |
var.enabled ? 1 : 0 | শর্ত: সত্য হলে ১, নইলে ০ |
# comment | মন্তব্য, Terraform এড়িয়ে যায় |
আমরা একটি কাল্পনিক অনলাইন বাজার অ্যাপ bazar-এর ছোট config বানাব। প্রথম ফাইলে terraform block জানায় কোন Terraform আর কোন provider লাগবে।
terraform {
required_version = ">= 1.9.0"
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 3.4"
}
local = {
source = "hashicorp/local"
version = "~> 2.9"
}
}
}
provider "kubernetes" {
config_path = "~/.kube/config"
config_context = "docker-desktop"
}Provider হলো আলাদা একটি প্রোগ্রাম, যে একটি নির্দিষ্ট API-র ভাষা জানে। provider block তাকে বলে কোথায় যুক্ত হতে হবে। Credential এখানে লিখবেন না; environment বা role থেকে নেওয়াই নিরাপদ, যা পর্ব ৩৩-এর বিষয়।
Version constraint
Version constraint বলে দেয় কোন version চলবে। সবচেয়ে কাজের চিহ্নটি ~>: শুধু ডান দিকের শেষ অংশটি বাড়তে পারবে।
| লেখা | যা চলবে | কখন |
|---|---|---|
= 3.4.0 | শুধু 3.4.0 | খুব কড়া নিয়ন্ত্রণ দরকার হলে |
>= 3.4 | 3.4 বা নতুন, 4.0-ও | Module-এ নিচের সীমা বলতে |
~> 3.4 | >= 3.4 এবং < 4.0 | Minor আপডেট চলবে |
~> 3.4.0 | >= 3.4.0 এবং < 3.5.0 | শুধু patch চলবে |
>= 3.4, < 3.6 | 3.4.x আর 3.5.x | পরিষ্কার নিচ ও ওপরের সীমা |
সীমা ভাঙলে Terraform কাজই শুরু করে না। আমরা ইচ্ছে করে required_version = ">= 2.0.0" লিখে চালালাম।
$ terraform init
Initializing the backend...
Error: Unsupported Terraform Core version
on v.tf line 2, in terraform:
2: required_version = ">= 2.0.0"
This configuration does not support Terraform version 1.17.1. To proceed,
either choose another supported Terraform version or update this version
constraint. …terraform init আর lock file
terraform init ফোল্ডারটি কাজের জন্য তৈরি করে: constraint মেনে provider নামায়, আর যে version বেছে নিল তা লিখে রাখে।
$ terraform init
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/kubernetes versions matching "~> 3.4"...
- Finding hashicorp/local versions matching "~> 2.9"...
- Installing hashicorp/kubernetes v3.4.0...
- Installed hashicorp/kubernetes v3.4.0 (signed by HashiCorp)
- Installing hashicorp/local v2.9.1...
- Installed hashicorp/local v2.9.1 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!
…
$ cat .terraform.lock.hcl
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/kubernetes" {
version = "3.4.0"
constraints = "~> 3.4"
hashes = [
"h1:iIaKddGgcDoeG/TxxYcIOOnXNm4vkrLoLow3STxfYIs=",
"zh:02a3156701f8eb7c43acaaf4ac136ddfe55cd86a703a5b2b54c679bf4fe25c02",
…Constraint হলো “কোন সীমার মধ্যে চলবে”, আর lock file হলো “ঠিক কোনটি বেছে নেওয়া হলো”। এটি অন্য ecosystem-এর lockfile-এর মতোই। hashes নামানো ফাইলটি যাচাই করে। তাই .terraform.lock.hcl Git-এ রাখুন, কিন্তু .terraform/ ফোল্ডার নয়, কারণ তাতে শুধু নামানো plugin থাকে।
Lock করা version আর নতুন constraint না মিললে কী হয়? আমরা constraint বদলে ~> 2.8.0 করলাম, যদিও lock file-এ আছে 2.9.1.
$ terraform init
…
Error: Failed to query available provider packages
Could not retrieve the list of available versions for provider
hashicorp/local: locked provider registry.terraform.io/hashicorp/local 2.9.1
does not match configured version constraint ~> 2.8.0; must use terraform
init -upgrade to allow selection of new versionsঠিক কাজটিই হলো: version নিজে থেকে বদলায় না। বদলাতে চাইলে terraform init -upgrade চালিয়ে lock file-এর diff review করতে হয়।
| ফাইল | Git-এ? | কারণ |
|---|---|---|
*.tf | হ্যাঁ | এটিই আপনার কোড |
.terraform.lock.hcl | হ্যাঁ | সবার একই provider থাকে |
.terraform/ | না | নামানো plugin, আবার নামানো যায় |
*.tfstate, *.tfstate.backup | না | গোপন মান থাকতে পারে |
Saved plan (tfplan) | না | গোপন মান থাকতে পারে |
*.tfvars | গোপন মান থাকলে না | Password Git-এ গেলে বদলাতে হয় |
৩ · Resource, address, argument আর attribute
Resource হলো Terraform-এর পরিচালনায় থাকা একটি জিনিস: একটি VM, একটি bucket বা একটি ConfigMap. নিচের ফাইলে একটি data source, কিছু locals, দুটি Kubernetes resource আর একটি ফাইল আছে।
data "kubernetes_namespace_v1" "team" {
metadata {
name = "abcl-tb"
}
}
locals {
namespace = data.kubernetes_namespace_v1.team.metadata[0].name
labels = {
app = var.app_name
managed-by = "terraform"
}
}
resource "kubernetes_config_map_v1" "settings" {
metadata {
name = "${var.app_name}-settings"
namespace = local.namespace
labels = local.labels
}
data = {
LOG_LEVEL = var.log_level
}
}
resource "kubernetes_deployment_v1" "web" {
metadata {
name = "${var.app_name}-web"
namespace = local.namespace
labels = local.labels
}
spec {
replicas = var.replicas
selector {
match_labels = local.labels
}
template {
metadata {
labels = local.labels
}
spec {
container {
name = "web"
image = "nginx:1.27-alpine"
env_from {
config_map_ref {
name = kubernetes_config_map_v1.settings.metadata[0].name
}
}
resources {
requests = { cpu = "10m", memory = "16Mi" }
limits = { cpu = "100m", memory = "64Mi" }
}
}
}
}
}
}
resource "local_file" "report" {
filename = "${path.module}/report.txt"
content = "settings uid: ${kubernetes_config_map_v1.settings.metadata[0].uid}\n"
}প্রতিটি resource-এর একটি address থাকে: type.name, যেমন kubernetes_config_map_v1.settings. এটি config-এর ভেতরের নাম। Cluster-এ জিনিসটির নাম bazar-settings. দুটি আলাদা জিনিস, আর এই পার্থক্য পরে কাজে লাগবে।
| বিষয় | Argument | Attribute |
|---|---|---|
| কে ঠিক করে | আপনি লেখেন | Provider বা API জানায় |
| উদাহরণ | metadata.name, data | metadata[0].uid, id |
| কখন জানা যায় | লেখার সময় | প্রায়ই apply-এর পরে |
| Plan-এ দেখায় | মানটি | (known after apply) |
সব argument আবার attribute হিসেবেও পড়া যায়। আর metadata[0]-এর [0] কেন? কারণ nested block ভেতরে একটি list হিসেবে থাকে, যদিও তাতে একটিই block. known after apply মানে API এখনো মানটি বানায়নি। বানানোর পর state-এ যা জমা হয়, তা দেখুন।
$ terraform state list
data.kubernetes_namespace_v1.team
kubernetes_config_map_v1.settings
kubernetes_deployment_v1.web
local_file.report
$ terraform state show kubernetes_config_map_v1.settings
# kubernetes_config_map_v1.settings:
resource "kubernetes_config_map_v1" "settings" {
data = {
"LOG_LEVEL" = "info"
}
id = "abcl-tb/bazar-settings"
immutable = false
metadata {
generate_name = null
generation = 0
labels = {
"app" = "bazar"
"managed-by" = "terraform"
}
name = "bazar-settings"
namespace = "abcl-tb"
resource_version = "18539"
uid = "6523526f-59f7-4f35-a771-ddcbcedd0d6c"
}
}আপনি uid লেখেননি, Kubernetes বানিয়েছে। State সেটি মনে রেখেছে, তাই অন্য resource ব্যবহার করতে পারে। State-এর পুরো গল্প পর্ব ৫৩-এ। আপাতত একটি কথা: এই ফাইলে গোপন মান সাধারণ লেখায় থাকতে পারে, তাই *.tfstate Git-এ রাখবেন না।
Data source: শুধু পড়া
data block কিছু বানায় না, অন্যের বানানো জিনিসের তথ্য পড়ে। ওপরে namespace abcl-tb আগেই kubectl দিয়ে তৈরি ছিল, তাই Terraform তার মালিক নয়। Plan-এ দেখা যায় data.kubernetes_namespace_v1.team: Read complete after 0s [id=abcl-tb], আর destroy চালালেও namespace-টি থাকে। Cloud-এ একই কৌশলে সর্বশেষ machine image বা অন্য দলের VPC-র id খোঁজা হয়। সুবিধা হলো মান হাতে লিখতে হয় না, আর ঝুঁকি হলো “সর্বশেষ” কিছু পড়লে মান নিজে থেকে বদলাতে পারে, তাই পরের plan-এ অপ্রত্যাশিত বদল আসতে পারে।
৪ · Reference, dependency graph আর cloud-এর একই নকশা
আপনি কোথাও ক্রম লেখেননি। তবু Terraform জানে ConfigMap আগে, Deployment পরে। কারণ Deployment-এ লেখা আছে kubernetes_config_map_v1.settings.metadata[0].name. এই reference-ই একটি implicit dependency. সব মিলিয়ে তৈরি হয় dependency graph, যা নিজেই দেখা যায়।
$ terraform graph
digraph G {
rankdir = "RL";
node [shape = rect, fontname = "sans-serif"];
"data.kubernetes_namespace_v1.team" [label="data.kubernetes_namespace_v1.team"];
"kubernetes_config_map_v1.settings" [label="kubernetes_config_map_v1.settings"];
"kubernetes_deployment_v1.web" [label="kubernetes_deployment_v1.web"];
"local_file.report" [label="local_file.report"];
"kubernetes_config_map_v1.settings" -> "data.kubernetes_namespace_v1.team";
"kubernetes_deployment_v1.web" -> "kubernetes_config_map_v1.settings";
"local_file.report" -> "kubernetes_config_map_v1.settings";
}এটি DOT ভাষায় লেখা। A -> B মানে A নির্ভর করে B-এর ওপর, অর্থাৎ B আগে। Terraform graph-টির topological sort করে কাজ সাজায়, আর যাদের মধ্যে সম্পর্ক নেই তাদের একসাথে চালায়; ডিফল্ট সীমা ১০টি (-parallelism). Destroy হয় উল্টো ক্রমে। terraform graph -type=plan দিলে variable, local আর provider-সহ পুরো graph মেলে।
কে কার আগে
Arrows show creation order, the reverse of DOT's depends-on arrows
depends_on: সম্পর্ক যখন কোডে দেখা যায় না
কখনো একটি জিনিস আরেকটির ওপর নির্ভর করে, অথচ কোনো attribute নেয় না। যেমন rollout শেষে একটি নোট লেখা। তখন depends_on লাগে।
resource "local_file" "release_note" {
filename = "${path.module}/release.txt"
content = "web rollout finished\n"
depends_on = [kubernetes_deployment_v1.web]
}$ terraform apply -auto-approve
…
kubernetes_config_map_v1.settings: Creating...
kubernetes_config_map_v1.settings: Creation complete after 0s [id=abcl-tb/bazar-config-v2]
local_file.report: Creating...
local_file.report: Creation complete after 0s [id=879646a5fbcd5f724b1e6eda0721cf68c2edd43c]
kubernetes_deployment_v1.web: Creating...
kubernetes_deployment_v1.web: Creation complete after 2s [id=abcl-tb/bazar-web]
local_file.release_note: Creating...
local_file.release_note: Creation complete after 0s [id=0f62f7c476fe14dc3358807cec29ff55214f0e5a]
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.Report ফাইল আগেই হয়ে গেল, কারণ সে Deployment-এর ওপর নির্ভর করে না। কিন্তু release_note Deployment শেষ না হওয়া পর্যন্ত অপেক্ষা করল। terraform graph-এও নতুন লাইন এলো: "local_file.release_note" -> "kubernetes_deployment_v1.web";. এই run-এ ConfigMap-এর নাম bazar-config-v2, কারণ এটি ৭ নম্বর অংশের নাম-বদলের পরে চালানো। নিয়ম: reference দিয়ে সম্ভব হলে depends_on লিখবেন না। এটি কম স্পষ্ট, আর বেশি ব্যবহারে plan-এ অকারণে (known after apply) বাড়তে পারে।
Cloud-এও একই নকশা
নিচের নমুনায় একটি VPC, একটি subnet আর একটি VM. এটি চালানো হয়নি, কারণ এই পর্বে কোনো cloud account ব্যবহার হয়নি। শুধু গঠনটি দেখুন: VPC-র id attribute subnet নেয়, subnet-এর id নেয় VM. কাজেই ক্রম আবার নিজে থেকেই তৈরি।
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
provider "aws" {
region = "ap-south-1"
}
variable "image_id" {
type = string
default = "ami-0123456789abcdef0" # fake id, use a real image
}
resource "aws_vpc" "main" {
cidr_block = "10.20.0.0/16"
}
resource "aws_subnet" "app" {
vpc_id = aws_vpc.main.id
cidr_block = "10.20.1.0/24"
}
resource "aws_instance" "web" {
ami = var.image_id
instance_type = "t3.micro"
subnet_id = aws_subnet.app.id
}| ধারণা | AWS | Azure | Google Cloud |
|---|---|---|---|
| Provider | aws | azurerm | google |
| Network | aws_vpc | azurerm_virtual_network | google_compute_network |
| Subnet | aws_subnet | azurerm_subnet | google_compute_subnetwork |
| VM | aws_instance | azurerm_linux_virtual_machine | google_compute_instance |
Terraform multi-cloud মানে “এক কোড সব cloud-এ” নয়। ভাষা আর workflow এক, কিন্তু resource type ও argument প্রতিটি provider-এর আলাদা। আর cloud-এ এই ফাইল apply করলে আসল টাকা খরচ হতে পারে। নিজে চালালে provider-এর দাম দেখে নিন, budget alert রাখুন, আর শেষে terraform destroy করুন।
৫ · Variable, tfvars, local আর output
একই config dev আর production-এ চালাতে মান বাইরে রাখতে হয়। Input variable হলো config-এর দরজা।
variable "app_name" {
type = string
description = "Short name of the app"
default = "bazar"
}
variable "replicas" {
type = number
default = 1
validation {
condition = var.replicas >= 1 && var.replicas <= 6
error_message = "replicas must be between 1 and 6."
}
}
variable "log_level" {
type = string
default = "info"
}output "deployment_name" {
description = "Name of the web Deployment"
value = kubernetes_deployment_v1.web.metadata[0].name
}
output "settings_uid" {
value = kubernetes_config_map_v1.settings.metadata[0].uid
}- Type:
string,number,bool, আর গঠনযুক্তlist(string),set(string),map(number),object({ … }). - Default: না থাকলে variable-টি বাধ্যতামূলক।
- Local:
localsblock-এর নাম দেওয়া হিসাব, যা ফাইলের ভেতরেই থাকে। বাইরে থেকে বদলানো যায় না। ওপরেরlocal.labelsএকবার লিখে তিন জায়গায় ব্যবহার হয়েছে। - Output: apply শেষে দেখানো মান। পরে module বা অন্য tool এগুলো পড়ে, যা পর্ব ৫৪-এ আসবে।
মান না দিলে, ভুল ধরনের মান দিলে বা validation ভাঙলে কী আসে, তিনটিই আসল run থেকে দেখুন। প্রথমটি default-হীন একটি পরীক্ষামূলক variable tier দিয়ে।
$ terraform plan -input=false
Error: No value for required variable
on main.tf line 21:
21: variable "tier" {
The root module input variable "tier" is not set, and has no default value.
Use a -var or -var-file command line argument to provide a value for this
variable.
$ terraform plan -var replicas=three
…
Error: Invalid value for input variable
on main.tf line 6:
6: variable "replicas" {
Unsuitable value for var.replicas set using -var="replicas=...": a number is
required.
$ terraform plan -var replicas=9
…
Error: Invalid value for variable
on main.tf line 6:
6: variable "replicas" {
├────────────────
│ var.replicas is 9
replicas must be between 1 and 6.
This was checked by the validation rule at main.tf:10,3-13.sensitive কতটুকু লুকায়
Variable বা output-এ sensitive = true দিলে Terraform মানটি পর্দায় দেখায় না। কিন্তু এটি encryption নয়। নিচে একটি নকল token দিয়ে পরীক্ষা করা হলো।
variable "api_token" {
type = string
sensitive = true
default = "fake-token-123"
}
output "token_for_tool" {
value = var.api_token
sensitive = true
}$ terraform output
token_for_tool = <sensitive>
$ grep -n 'fake-token' terraform.tfstate
8: "value": "fake-token-123",পর্দায় <sensitive>, অথচ state ফাইলে হুবহু মান। আসল গোপন মান তাই কখনো default-এ নয়, আর state-এর জায়গাটিও সুরক্ষিত হতে হয়। পরের পর্বে এই বিষয়ে আবার আসছে।
কোন মান জেতে
একই variable-এর মান অনেক জায়গা থেকে আসতে পারে। Terraform একটি নির্দিষ্ট ক্রমে পড়ে, আর পরে যেটি আসে সেটিই জেতে। tfvars ফাইল হলো শুধু name = value লেখা ফাইল।
| ক্রম (নিচের জন জেতে) | উৎস | আসল run-এ region_name |
|---|---|---|
| ১ | default | from-default |
| ২ | TF_VAR_region_name environment | from-env |
| ৩ | terraform.tfvars | from-tfvars |
| ৪ | terraform.tfvars.json | from-tfvars-json |
| ৫ | *.auto.tfvars, নামের বর্ণক্রমে | from-b-auto (a-কে হারিয়ে) |
| ৬ | -var আর -var-file, যে ক্রমে লেখা | শেষে লেখা জন জেতে |
নিচের ছোট simulator একই নিয়ম মানে। এর ৭টি ফল আসল Terraform-এ মিলিয়ে দেখা হয়েছে। CASES বদলে নিজে পরীক্ষা করুন।
// Which value does one variable get? Later in this order wins.
// rank: default 0, env 1, terraform.tfvars 2, .tfvars.json 3,
// *.auto.tfvars 4 (sorted by file name), command line 5.
const RANK = { default: 0, env: 1, tfvars: 2, tfvarsJson: 3, auto: 4, cli: 5 };
function resolve(sources) {
const sorted = sources
.map((s, i) => ({ ...s, i }))
.sort((a, b) => RANK[a.kind] - RANK[b.kind]
|| (a.kind === "auto" ? a.file.localeCompare(b.file) : a.i - b.i));
return sorted[sorted.length - 1];
}
const d = { kind: "default", value: "from-default" };
const env = { kind: "env", value: "from-env" };
const tf = { kind: "tfvars", value: "from-tfvars" };
const js = { kind: "tfvarsJson", value: "from-tfvars-json" };
const a = { kind: "auto", file: "a.auto.tfvars", value: "from-a-auto" };
const b = { kind: "auto", file: "b.auto.tfvars", value: "from-b-auto" };
const cli = { kind: "cli", value: "from-cli" };
const file = { kind: "cli", value: "from-prod-file" };
const CASES = [
["default only", [d]],
["env added", [d, env]],
["env + tfvars", [d, env, tf]],
["env + tfvars + json", [d, env, tf, js]],
["two auto files", [d, env, tf, js, b, a]],
["-var-file then -var", [d, env, b, file, cli]],
["-var then -var-file", [d, env, b, cli, file]],
];
for (const [name, sources] of CASES) {
console.log(name.padEnd(24) + resolve(sources).value);
}ফল: default only → from-default থেকে শুরু করে two auto files → from-b-auto পর্যন্ত প্রতিটি ধাপে মান বদলায়। শেষ দুই লাইনে -var-file then -var দেয় from-cli, আর -var then -var-file দেয় from-prod-file. কারণ command line-এ জয় নির্ভর করে লেখার ক্রমের ওপর। সবচেয়ে বেশি ভুল হয় এখানেই: CI-র environment variable কাজ করছে না মনে হয়, অথচ পাশের একটি *.auto.tfvars তাকে চুপচাপ হারিয়ে দিয়েছে।
৬ · মূল workflow: fmt, validate, plan, apply, destroy
কাজের ক্রম সব জায়গায় একই। প্রথমে দুটি বিনা ঝুঁকির কমান্ড: terraform fmt লেখা গুছিয়ে দেয়, আর terraform validate API না ডেকেই ফাইলের ভুল ধরে।
$ terraform fmt -diff
main.tf
--- old/main.tf
+++ new/main.tf
@@ -1,4 +1,4 @@
resource "local_file" "note" {
-filename="${path.module}/note.txt"
- content = "hello"
+ filename = "${path.module}/note.txt"
+ content = "hello"
}
$ terraform validate # filename-এর বানান ভুল করে লেখা হয়েছে
Error: Missing required argument
…
Error: Unsupported argument
on main.tf line 2, in resource "local_file" "note":
2: filenam = "${path.module}/note.txt"
An argument named "filenam" is not expected here. Did you mean "filename"?CI-তে terraform fmt -check ভুল বিন্যাসে exit code 3 দেয়, তাই pipeline থেমে যায়। এবার পুরো পথটি ধাপে ধাপে। “পরে” চেপে প্রতিটি ধাপে ফাইল আর আউটপুট লক্ষ করুন।
| .terraform/ | |
| .terraform.lock.hcl | |
| tfplan | |
| state-এ resource | |
| cluster-এ bazar-web |
versions.tf variables.tf main.tf outputs.tf
- Installed hashicorp/kubernetes v3.4.0 (signed by HashiCorp)
- Installed hashicorp/local v2.9.1 (signed by HashiCorp)
Terraform has been successfully initialized!
Success! The configuration is valid.
data.kubernetes_namespace_v1.team: Read complete after 0s [id=abcl-tb]
…
# kubernetes_config_map_v1.settings will be created
# kubernetes_deployment_v1.web will be created
# local_file.report will be created
…
Plan: 3 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ deployment_name = "bazar-web"
+ settings_uid = (known after apply)
Saved the plan to: tfplan
To perform exactly these actions, run the following command to apply:
terraform apply "tfplan"
…
kubernetes_config_map_v1.settings: Creation complete after 0s [id=abcl-tb/bazar-settings]
local_file.report: Creation complete after 0s [id=c21bf8d4c14a…]
kubernetes_deployment_v1.web: Creation complete after 3s [id=abcl-tb/bazar-web]
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
Outputs:
deployment_name = "bazar-web"
settings_uid = "6523526f-59f7-4f35-a771-ddcbcedd0d6c"
No changes. Your infrastructure matches the configuration.
- শুরু। ফোল্ডারে শুধু চারটি
.tfফাইল। State নেই, plugin নেই, cluster-এbazar-webনেই। - init. Provider নেমে
.terraform/-এ বসল, আর lock file তৈরি হলো। আসল resource এখনো একটিও হয়নি। - validate. Syntax, argument-এর নাম আর type মিলল। Cluster-এ কোনো ডাক যায়নি।
- plan. এখানে plan আসল API পড়ে: data source পড়ল, config আর খালি state মিলিয়ে বলল ৩টি নতুন জিনিস লাগবে। কিছুই বানায়নি। এটি একরকম dry run.
- plan -out. একই plan ফাইলে জমা হলো। Review করে ঠিক এই ফাইলটিই apply করা হবে।
- apply. Apply-এ graph-এর ক্রমে কাজ হলো: ConfigMap আগে, তারপর বাকি দুটি একসাথে। Deployment ৩ সেকেন্ড নিল, কারণ provider rollout শেষের অপেক্ষা করে। State-এ এখন ৩টি resource.
- আবার plan.
No changes.এটিই idempotent আচরণ: কাঙ্ক্ষিত অবস্থায় পৌঁছে গেলে আর কিছু করার নেই।
Plan-এর ভেতরে তিন দিকের তুলনা
দ্বিতীয়বার plan চালালে আউটপুটের শুরুতে এমন লাইন আসে: kubernetes_config_map_v1.settings: Refreshing state... [id=abcl-tb/bazar-settings]. অর্থাৎ Terraform শুধু ফাইল আর state মেলায় না, আসল API-কেও জিজ্ঞেস করে জিনিসটি এখন কেমন আছে। তিনটি জিনিস মেলে:
- Config: আপনি কী চান।
- State: Terraform শেষবার কী জেনেছিল।
- আসল অবস্থা: API এখন কী বলছে।
কেউ হাতে কিছু বদলালে এই তুলনাতেই drift ধরা পড়ে, যা পর্ব ৫৩-এ দেখবেন। CI-তে আরেকটি সুবিধা -detailed-exitcode. আসল run-এ নতুন config-এ terraform plan -detailed-exitcode exit code ২ দিল, আর apply-এর পরে দিল ০. নিয়মটি: ০ মানে কোনো বদল নেই, ১ মানে error, ২ মানে বদল আছে। এভাবে pipeline বুঝতে পারে review লাগবে কি না।
Saved plan কেন
খালি terraform apply নতুন করে plan বানায়, তাই আপনি যা দেখেছিলেন আর যা চলল, দুটি আলাদা হতে পারে। Saved plan এই ফাঁক বন্ধ করে। আর মাঝখানে state বদলালে Terraform পুরোনো plan চালাতে রাজি হয় না। নিচে একটি plan জমা রেখে মাঝখানে আরেকটি apply চালানো হলো।
$ terraform plan -out=up.tfplan
…
Plan: 0 to add, 1 to change, 0 to destroy.
$ terraform apply -auto-approve -var replicas=1
…
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
$ terraform apply up.tfplan
Error: Saved plan is stale
The given plan file can no longer be applied because the state was changed by
another operation after the plan was created.terraform apply tfplan আর নিশ্চিতকরণ চায় না। Plan ফাইলে গোপন মানও থাকতে পারে, তাই এটিকে state-এর মতোই সাবধানে রাখুন, Git-এ নয়। CI-তে plan আর apply একই পরিবেশে একই provider version-এ চালাতে হয়।
Plan কোনো গ্যারান্টি নয়
Plan একটি পূর্বাভাস। Apply-এর সময় API অন্য কিছু বলতেই পারে। নিচে Terraform দুটি ConfigMap বানাতে চায়, কিন্তু দ্বিতীয় নামের একটি ConfigMap আমরা আগেই kubectl দিয়ে বানিয়ে রেখেছিলাম।
$ terraform apply -auto-approve
…
Plan: 2 to add, 0 to change, 0 to destroy.
kubernetes_config_map_v1.first: Creating...
kubernetes_config_map_v1.first: Creation complete after 0s [id=abcl-tb/demo-first]
kubernetes_config_map_v1.second: Creating...
Error: configmaps "demo-second" already exists
with kubernetes_config_map_v1.second,
on main.tf line 8, in resource "kubernetes_config_map_v1" "second":
8: resource "kubernetes_config_map_v1" "second" {
$ terraform state list
kubernetes_config_map_v1.firstPlan 2 to add বলেছিল, কারণ state-এ কিছু ছিল না। Terraform জানে শুধু নিজের state-এর জিনিস। আর দেখুন: first তৈরি হয়ে state-এই রয়ে গেল। Apply কোনো transaction নয়, rollback হয় না। কারণটি ঠিক করে আবার apply করলে সে বাকিটুকু শেষ করে।
শেষ ধাপ terraform destroy, যা state-এর সব resource মুছে ফেলে। Test environment গোছাতে এটি দারুণ, production-এ ভয়ংকর। আগে terraform plan -destroy চালিয়ে তালিকা দেখে নিন।
৭ · Plan পড়া: চিহ্ন, update আর replace
প্রতিটি resource-এর বাঁ পাশে একটি চিহ্ন থাকে। এই চিহ্ন পড়াই Terraform ব্যবহারের সবচেয়ে গুরুত্বপূর্ণ দক্ষতা।
| চিহ্ন | Terraform-এর ভাষায় | অর্থ | ঝুঁকি |
|---|---|---|---|
+ | create | নতুন বানাবে | কম, তবে খরচ বাড়তে পারে |
~ | update in-place | একই জিনিস রেখে বদলাবে | মাঝারি, restart হতে পারে |
- | destroy | মুছে ফেলবে | বেশি |
-/+ | destroy and then create replacement | আগে মুছবে, পরে বানাবে | বেশি, মাঝে কিছু থাকে না |
+/- | create replacement and then destroy | আগে বানাবে, পরে মুছবে | কম, তবে নাম আলাদা হতে হয় |
<= | read | Data source পড়বে apply-এর সময় | নেই |
শেষ লাইনের হিসাবে একটি ফাঁদ আছে: replace গোনা হয় ১টি add আর ১টি destroy হিসেবে। তাই Plan: 2 to add, 1 to change, 2 to destroy.-এর অর্থ হতে পারে, দুটি জিনিস মুছে নতুন হবে। নিচের reader-এ নিজের plan-এর শিরোনাম লাইন বসিয়ে দেখুন। নমুনাটি ছোট করা, আর WATCH-এ আপনার মূল্যবান resource-এর নাম দিন।
// Reads the action symbol of each resource line in a plan.
// Paste header lines from a plan; the sample is shortened.
const planText = `
+ resource "kubernetes_config_map_v1" "ledger" {
~ resource "kubernetes_deployment_v1" "web" {
-/+ resource "kubernetes_config_map_v1" "settings" {
+/- resource "kubernetes_service_v1" "api" {
- resource "local_file" "release_note" {
<= data "kubernetes_namespace_v1" "team" {
`;
const MEAN = {
"+": ["create", 1, 0, 0],
"~": ["update in-place", 0, 1, 0],
"-": ["destroy", 0, 0, 1],
"-/+": ["destroy, then create", 1, 0, 1],
"+/-": ["create, then destroy", 1, 0, 1],
"<=": ["read only", 0, 0, 0],
};
const WATCH = ["ledger", "database", "volume"]; // never lose these
let add = 0, change = 0, destroy = 0;
for (const line of planText.split("\n")) {
const m = line.match(/^\s*(\S+)\s+(?:resource|data)\s+"(\S+)"\s+"(\S+)"/);
if (!m || !MEAN[m[1]]) continue;
const [text, a, c, d] = MEAN[m[1]];
add += a; change += c; destroy += d;
const risky = d > 0 && WATCH.some(w => m[3].includes(w));
console.log(m[1].padEnd(4) + (m[2] + "." + m[3]).padEnd(42) + text +
(risky ? " << STOP, stateful" : ""));
}
console.log("Plan: " + add + " to add, " + change + " to change, " + destroy + " to destroy.");ফল শেষ হয় Plan: 3 to add, 1 to change, 3 to destroy. লাইনে: + ১টি, দুই replace মিলে ২টি add; - ১টি, দুই replace মিলে ২টি destroy. <= কোনো সংখ্যায় যোগ হয় না। ledger ধরনের নাম মুছতে গেলে এখানে << STOP, stateful ছাপা হবে; -/+ লাইনে settings-এর বদলে নামটি বসিয়ে দেখুন। বাস্তবে সাধারণ <= খুব কম দেখা যায়, কারণ বেশিরভাগ data source plan-এর সময়ই পড়া হয়ে যায়।
Plan পড়ার রুটিন
লম্বা plan ওপর থেকে পড়লে চোখ ক্লান্ত হয়, আর আসল লাইনটিই চোখ এড়ায়। এই ক্রম মানুন:
- শেষ লাইন আগে:
Plan: …লাইনের সংখ্যা কি আপনার edit-এর সাথে মানায়? এক লাইন বদলে12 to destroyএলে সেখানেই থামুন। # … will be destroyedআরmust be replaced: এই শিরোনামগুলো আগে খুঁজুন। কোনোটি ডেটা রাখে কি না দেখুন।# forces replacement: কোন argument দায়ী, তা বুঝুন। অনেক সময় সামান্য বদলে এটি এড়ানো যায়।~লাইনের->: বাঁ দিকে পুরোনো, ডান দিকে নতুন। আপনি যা চাননি এমন কিছু আছে কি?- Outputs: কোনো গুরুত্বপূর্ণ ঠিকানা বা id বদলাচ্ছে কি না, যা অন্য দল ব্যবহার করে।
ছোট edit, দুই রকম ফল
কোন argument জায়গায় বদলানো যায়, আর কোনটি যায় না, তা ঠিক করে API আর provider. Kubernetes-এ একটি object-এর metadata.name বদলানো যায় না, তাই provider সেটিকে “নতুন করে বানাতে হবে” বলে চিহ্নিত করে। Plan-এ এটি দেখা যায় # forces replacement মন্তব্যে। দুটি ছোট edit পাশাপাশি দেখুন।
| Cluster-এ নাম | |
| uid | |
| ConfigMap-এ LOG_LEVEL | |
| চলমান Pod-এর env |
- শুরুর অবস্থা। ConfigMap-এর
uid6523526f…, আর Pod-এর env-এinfo. Plan বলছে কিছু করার নেই। - Edit ১:
log_levelহলোdebug. শুধুdataবদলাচ্ছে, যা জায়গায় বদলানো যায়। তাই চিহ্ন~, আর ১টি change. এখনো কিছু বদলায়নি। - Apply-এর ফল।
uidএকই, অর্থাৎ একই object. কিন্তু Pod-এর env এখনোinfo! Environment variable শুধু Pod শুরুর সময় পড়া হয়, আর Deployment-এর template বদলায়নি। Terraform সত্যি বলেছে, তবু app-এ বদল আসেনি। - Edit ২: ConfigMap-এর নাম বদল।
nameবদলানো যায় না, তাই-/+. ঢেউ ছড়াল: নতুনuid-এর জন্য report ফাইলও replace, আর Deployment update. মোট2 to add, 1 to change, 2 to destroy. - Apply-এর ফল। নতুন
uid7c1e61f0…: এটি একেবারে নতুন object. Deployment-এর template বদলানোয় Pod নতুন হলো, আর এবার envdebug. তবে ক্রম খেয়াল করুন: পুরোনো ConfigMap আগে মুছল, Deployment বদলাল সবার শেষে। create_before_destroy-সহ আরেকবার। নাম হলোbazar-config-v2, আর চিহ্ন+/-. নতুনটি আগে তৈরি, Deployment তাতে সরে গেল, পুরোনোটি (deposed object) মুছল সবার শেষে। মাঝখানে Pod-এর জন্য ConfigMap-হীন কোনো সময় থাকে না।
আসল plan-এর ওপরের অংশটি হুবহু এমন। ধাপ ৫-এর Pod-এর মানটি আলাদা একটি পুনরাবৃত্ত run-এ kubectl exec দিয়ে মিলিয়ে নেওয়া।
$ terraform plan
…
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
~ update in-place
-/+ destroy and then create replacement
Terraform will perform the following actions:
# kubernetes_config_map_v1.settings must be replaced
-/+ resource "kubernetes_config_map_v1" "settings" {
- binary_data = {} -> null
~ id = "abcl-tb/bazar-settings" -> (known after apply)
- immutable = false -> null
# (1 unchanged attribute hidden)
~ metadata {
- annotations = {} -> null
~ generation = 0 -> (known after apply)
~ name = "bazar-settings" -> "bazar-config" # forces replacement
~ resource_version = "18680" -> (known after apply)
~ uid = "6523526f-59f7-4f35-a771-ddcbcedd0d6c" -> (known after apply)
# (3 unchanged attributes hidden)
}
}
…
Plan: 2 to add, 1 to change, 2 to destroy.ConfigMap-এর ফাঁদ: ধাপ ৩-এর সমস্যা Terraform-এর ভুল নয়। Kubernetes-এর ধরনই এমন, যা পর্ব ৪৭-এ আছে। সমাধান: config-এর hash Pod template-এর annotation-এ রাখা, অথবা ধাপ ৬-এর মতো নতুন নামে ConfigMap বানানো। Cloud-এ একই ধরনের ফাঁদ: database-এর subnet বদলালে বা VM-এর image বদলালে অনেক সময় replace আসে, যা provider-ভেদে আলাদা। তাই forces replacement লেখাটি খুঁজে পড়ার অভ্যাস করুন।
৮ · একাধিক কপি: count বনাম for_each
তিনটি শাখার জন্য তিনটি ConfigMap লাগলে তিনবার লিখবেন না। count দেয় সংখ্যা দিয়ে কপি, আর for_each দেয় নাম দিয়ে কপি। একই list দিয়ে দুটিই বানাই।
variable "branches" {
type = list(string)
default = ["dhaka", "khulna", "sylhet"]
}
resource "kubernetes_config_map_v1" "by_count" {
count = length(var.branches)
metadata {
name = "c-${var.branches[count.index]}"
namespace = "abcl-tb"
}
data = {
BRANCH = var.branches[count.index]
}
}
resource "kubernetes_config_map_v1" "by_each" {
for_each = toset(var.branches)
metadata {
name = "e-${each.key}"
namespace = "abcl-tb"
}
data = {
BRANCH = each.value
}
}ক্লাসের রোল নম্বর ভাবুন। রোল ২-এর ছাত্র চলে গেলে যদি সবার রোল এক ঘর করে এগিয়ে আসে, তাহলে খাতায় রোল ৩-এর সব নম্বর এখন ভুল লোকের নামে। count ঠিক এই রোল নম্বর। for_each-এ প্রত্যেকের নিজের নাম, তাই একজন চলে গেলে বাকিদের খাতা একই থাকে।
এবার list-এর মাঝখান থেকে khulna বাদ দিলাম। ছোট্ট এক শব্দের edit, কিন্তু ফল দেখুন।
| by_count[0] | |
| by_count[1] | |
| by_count[2] | |
| by_each["dhaka"] | |
| by_each["khulna"] | |
| by_each["sylhet"] |
- শুরু। ছয়টি ConfigMap, প্রতি শাখায় দুটি করে।
count-এর address-এ সংখ্যা,for_each-এর address-এ নাম। - Edit. List থেকে
khulnaবাদ। এখনsylhet-এর অবস্থান ২ থেকে সরে ১ হলো। আমাদের কাছে এটি একটি মোছা, কিন্তু Terraform দেখে শুধু address. count-এর হিসাব।[0]মিলল।[1]-এর নাম ছিলc-khulna, এখন চাইc-sylhet, আর নাম বদলানো যায় না, তাই replace.[2]সীমার বাইরে, তাই destroy. Sylhet-এর কিছুই বদলায়নি, তবু তার ConfigMap ভাঙা পড়বে।for_each-এর হিসাব। Keykhulnaনেই, তাই শুধু সেটি মুছবে।dhakaআরsylhetছোঁয়াই হবে না।- Apply. এই plan-এ দুই ধরনের ফল একসাথে:
countথেকে ১ add ও ২ destroy, আরfor_eachথেকে ১ destroy. মোট1 to add, 3 to destroy. - শেষ অবস্থা। দুই দিকের ফল একই দেখাচ্ছে: dhaka আর sylhet. তবে
count-এরc-sylhetএকটি নতুন object. ConfigMap-এর বদলে এটি database হলে ডেটা হারাত।
Map দিয়ে for_each: প্রতিটি কপির নিজের মান
for_each-এর সবচেয়ে কাজের রূপ হলো map. তখন each.key হলো পরিচয়, আর each.value হলো সেই কপির নিজের মান। toset() লেগেছিল কারণ for_each সরাসরি list নেয় না, শুধু map বা set নেয়।
variable "branch_replicas" {
type = map(number)
default = {
dhaka = 3
khulna = 1
}
}
resource "local_file" "branch" {
for_each = var.branch_replicas
filename = "${path.module}/${each.key}.txt"
content = "replicas=${each.value}\n"
}
output "branch_files" {
value = [for f in local_file.branch : f.filename]
}$ terraform apply -auto-approve
…
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
branch_files = [
"./dhaka.txt",
"./khulna.txt",
]
$ terraform state list
local_file.branch["dhaka"]
local_file.branch["khulna"]Output-এর [for f in … : f.filename] হলো for expression: একটি সংগ্রহ থেকে নতুন list বানায়। এটি for_each নয়, resource-ও বানায় না।
নিচের simulator একই হিসাব করে। after বদলে দেখুন: শেষের item মুছলে count-এও ক্ষতি নেই, কিন্তু প্রথম item মুছলে সবচেয়ে বেশি।
// count vs for_each: which instances does a list edit touch?
// Edit the two lists and run again.
const before = ["dhaka", "khulna", "sylhet"];
const after = ["dhaka", "sylhet"];
function countPlan(a, b) {
const lines = [];
for (let i = 0; i < Math.max(a.length, b.length); i++) {
const addr = "by_count[" + i + "]";
if (i >= b.length) lines.push(["-", addr, a[i] + " (index out of range)"]);
else if (i >= a.length) lines.push(["+", addr, b[i]]);
else if (a[i] !== b[i]) lines.push(["-/+", addr, a[i] + " -> " + b[i]]);
}
return lines;
}
function eachPlan(a, b) {
const lines = [];
for (const k of a) if (!b.includes(k)) lines.push(["-", 'by_each["' + k + '"]', k]);
for (const k of b) if (!a.includes(k)) lines.push(["+", 'by_each["' + k + '"]', k]);
return lines;
}
function show(title, lines) {
let add = 0, del = 0;
console.log(title);
for (const [sym, addr, note] of lines) {
console.log(" " + sym.padEnd(4) + addr.padEnd(22) + note);
if (sym !== "-") add++;
if (sym !== "+") del++;
}
console.log(" Plan: " + add + " to add, 0 to change, " + del + " to destroy.\n");
}
show("count (identity = position)", countPlan(before, after));
show("for_each (identity = key)", eachPlan(before, after));ফল আসল run-এর সাথে মেলে: count-এ Plan: 1 to add, 0 to change, 2 to destroy., for_each-এ Plan: 0 to add, 0 to change, 1 to destroy.. সহজ নিয়ম: কপিগুলো হুবহু একরকম হলে, বা শুধু “আছে অথবা নেই” বোঝাতে (count = var.enabled ? 1 : 0), count ঠিক আছে। প্রতিটির নিজের পরিচয় থাকলে for_each নিন। আগের config-এ count থাকলে moved block (Terraform ১.১ থেকে) দিয়ে address বদলানো যায়, resource না ভেঙেই।
৯ · lifecycle: Terraform-এর আচরণে সীমা টানা
lifecycle block একটি resource-এর জন্য বিশেষ নিয়ম দেয়। তিনটি argument সবচেয়ে কাজের।
create_before_destroy
আগের stepper-এর ৬ নম্বর ধাপ দেখুন। create_before_destroy দিলে replace-এর ক্রম উল্টে যায়। আসল run-এ শেষ লাইনটি এমন: kubernetes_config_map_v1.settings (deposed object 2c9a5c5f): Destroying... [id=abcl-tb/bazar-config]. Deposed object মানে নতুনটি এসে যাওয়ায় পদ হারানো পুরোনো জিনিস। শর্ত একটিই: নতুন আর পুরোনো একসাথে থাকতে হবে। তাই দুটির নাম একই হলে সংঘর্ষ হয়, যেমন Kubernetes-এ already exists. Load balancer-এর পেছনের VM বা certificate বদলানোর মতো কাজে এটি downtime কমায়।
resource "kubernetes_config_map_v1" "ledger" {
metadata {
name = "${var.app_name}-ledger"
namespace = local.namespace
}
data = {
OWNER = "finance"
}
lifecycle {
prevent_destroy = true
}
}prevent_destroy
prevent_destroy হলো জরুরি brake. Database বা গুরুত্বপূর্ণ bucket-এ এটি দিন। আসল run-এ terraform destroy চালালাম।
$ terraform destroy -auto-approve
…
Plan: 0 to add, 0 to change, 5 to destroy.
…
Error: Instance cannot be destroyed
on main.tf line 80:
80: resource "kubernetes_config_map_v1" "ledger" {
Resource kubernetes_config_map_v1.ledger has lifecycle.prevent_destroy set,
but the plan calls for this resource to be destroyed. To avoid this error and
continue with the plan, either disable lifecycle.prevent_destroy or reduce
the scope of the plan using the -target option.
$ terraform plan # ledger block পুরোটাই ফাইল থেকে মুছে
…
# kubernetes_config_map_v1.ledger will be destroyed
# (because kubernetes_config_map_v1.ledger is not in configuration)
…
Plan: 0 to add, 0 to change, 1 to destroy.
$ terraform destroy -auto-approve
…
local_file.report: Destruction complete after 0s
local_file.release_note: Destruction complete after 0s
kubernetes_config_map_v1.ledger: Destruction complete after 0s
kubernetes_deployment_v1.web: Destruction complete after 0s
kubernetes_config_map_v1.settings: Destruction complete after 0s
Destroy complete! Resources: 5 destroyed.এক resource-এর বাধায় পুরো destroy থেমে গেল, কিছুই মোছেনি। নাম বদলে replace ডাকলেও একই error এসেছে। কিন্তু খেয়াল করুন শেষ অংশ: block মুছে দিলে পাহারাও মুছে যায়, কারণ নিয়মটি থাকে config-এ। Cluster বা cloud console থেকে মোছাও এটি আটকায় না। আসল সুরক্ষার জন্য provider-এর deletion protection আর backup-ও লাগে। আর destroy-এর ক্রম দেখুন: graph-এর উল্টো, settings সবার শেষে।
ignore_changes
কিছু মান Terraform-এর বাইরে বদলায়। যেমন autoscaler Deployment-এর replica বাড়ায়। আসল run-এ kubectl scale --replicas=2 চালানোর পর plan বলল ~ replicas = "2" -> "1", অর্থাৎ পরের apply autoscaler-এর কাজ উল্টে দেবে। Deployment-এ lifecycle { ignore_changes = [spec[0].replicas] } যোগ করলে একই অবস্থায় plan বলল:
$ terraform plan
…
No changes. Your infrastructure matches the configuration.
$ kubectl get deploy bazar-web
NAME READY UP-TO-DATE AVAILABLE AGE
bazar-web 1/2 2 1 45signore_changes মানে ওই attribute-এর drift ইচ্ছে করে দেখবে না। ফলে config-এ মানটি বদলালেও সেটি আর প্রয়োগ হবে না, আর ভুল বদলও আড়ালে থাকবে। তাই এটি শুধু নির্দিষ্ট attribute-এ, কারণ লিখে রেখে ব্যবহার করুন। ignore_changes = all প্রায় সব সময়ই সমস্যা লুকানোর চিহ্ন।
আরও গভীরে: Core আর provider আলাদা প্রক্রিয়া, যারা gRPC-র মাধ্যমে কথা বলে। Plan-এর সময় Core provider-কে “এই পুরোনো আর এই নতুন মান থেকে কী হবে” জিজ্ঞেস করে, আর provider-ই জানায় কোন attribute replace দাবি করে। তাই একই ভাষার নিচে আচরণ provider-ভেদে আলাদা। প্রতিটি resource-এর নিয়ম পড়তে হয় Terraform Registry-তে। Lock file-এর h1: আর zh: hash দুটি আলাদা পদ্ধতির; নানা OS-এ দলের কাজ হলে terraform providers lock -platform=… দিয়ে সব platform-এর hash যোগ করা হয়।
পরীক্ষা ও ইন্টারভিউতে:
terraform initকী করে? Provider ও module নামায়, backend ঠিক করে, lock file লেখে।- Argument আর attribute-এর পার্থক্য? Argument আপনি দেন, attribute provider ফেরত দেয়।
~> 3.4-এর অর্থ? 3.4 বা তার ওপরে, তবে 4.0-এর নিচে।for_eachকেনcount-এর চেয়ে নিরাপদ? Instance-এর পরিচয় অবস্থান নয়, স্থায়ী key, তাই মাঝের item মুছলে বাকিরা নড়ে না।-/+আর+/--এর পার্থক্য? প্রথমটি আগে মোছে, দ্বিতীয়টি (create_before_destroy) আগে বানায়।
১০ · AI-এর যুগে Terraform
HCL লিখতে AI খুব দ্রুত। কিন্তু AI আপনার state দেখে না, আর আপনার provider version-ও জানে না। HCL দেখতে ঠিক হলেও ফল হতে পারে একটি database মুছে যাওয়া। তাই AI-এর কোডের যাচাই হয় plan দিয়ে, আত্মবিশ্বাসী উত্তর দিয়ে নয়।
| AI-এর সাধারণ ভুল | যেভাবে ধরবেন |
|---|---|
| নেই এমন argument বানায়, বা পুরোনো resource নাম দেয় | terraform validate, Registry-র docs |
required_providers-এ version দেয় না | Constraint আর lock file আছে কি না দেখা |
Security group-এ 0.0.0.0/0, IAM-এ * অনুমতি, public bucket | Plan-এর diff পড়া, policy check (পর্ব ৫৫) |
Password সরাসরি default বা .tfvars-এ লেখে | Secret manager, সতর্ক ভাবে state রাখা |
পরিচয়-সহ জিনিসে count ব্যবহার করে | for_each-এ বদলে নেওয়া |
সমস্যা ঢাকতে ignore_changes = all বা -target বলে | মূল কারণ খোঁজা |
terraform destroy বা -auto-approve কোনো সতর্কতা ছাড়াই বলে | নিজে plan পড়া, production-এ কখনো অন্ধভাবে নয় |
| খরচের কথা ভাবে না | Instance size দেখা, budget alert রাখা |
Plan-এর আউটপুট দিয়ে AI-কে review করাতে পারেন। আগে id, account আর গোপন মান সরিয়ে নিন। State ফাইল কখনো পেস্ট করবেন না।
AI-এর লেখা HCL হাতে পেলে
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।
প্র ০১Script দিয়ে সরাসরি কাজ না করে plan আর apply আলাদা করা হলো কেন?
ধরুন একটি bash script cloud CLI ডেকে server বানায়। Script চালানো আর ফল পাওয়া একই মুহূর্তের কাজ। ভুল বুঝতে পারলে ততক্ষণে কিছু ঘটে গেছে। আর দ্বিতীয়বার চালালে সে আবার নতুন server বানাবে, কারণ সে আগের কথা মনে রাখে না।
Plan এই দুই সমস্যাকে আলাদা করে। প্রথমত, এটি “কী ঘটবে” প্রশ্নটিকে “ঘটানো” থেকে আলাদা করে। ফলে একজন মানুষ, অথবা একটি policy tool, কাজ শুরুর আগেই তালিকাটি দেখতে পারে। Pull request-এ plan-এর আউটপুট জুড়ে দেওয়ার রীতি এখান থেকেই। দ্বিতীয়ত, config আর state মিলিয়ে দেখার ফলে একই কাজ বারবার চালানো নিরাপদ হয়, যা idempotent আচরণ।
ফেব্রুয়ারি ২০১৭-এ Amazon S3-এর বড় outage-এর প্রকাশিত ব্যাখ্যায় বলা হয়েছিল, একটি নির্ধারিত কমান্ডে ভুল ইনপুটের কারণে ইচ্ছার চেয়ে বেশি server সরে যায়। পরে টুলটিতে গতি ও সীমার সুরক্ষা যোগ করা হয়। ঘটনাটি Terraform-এর ছিল না, তবে শিক্ষা একই: শক্তিশালী কমান্ডের সামনে একটি পূর্বদর্শন আর একটি সীমা থাকা দরকার।
তবে plan সর্বশক্তিমান নয়। আপনি এই পর্বেই দেখেছেন plan 2 to add বলার পরও apply ব্যর্থ হয়েছে, কারণ Terraform-এর জানা জগৎ শুধু state আর API-র উত্তর। অন্য কেউ মাঝখানে কিছু বদলালে plan পুরোনো হয়ে যায়। তাই plan-কে নিশ্চিত ভবিষ্যদ্বাণী না ভেবে একটি সুশিক্ষিত অনুমান ভাবাই ঠিক। এজন্যই saved plan আর stale check, আর এজন্যই পর্ব ৫৩-এর locking আসে। একই ভাবনা অন্য জায়গাতেও আছে: AWS CloudFormation-এর change set, আর kubectl diff কমান্ডে।
মূল উপলব্ধি: Plan-এর আসল মূল্য স্বয়ংক্রিয়তায় নয়, মানুষের সিদ্ধান্তের একটি বিন্দু তৈরি করায়। ঝুঁকিপূর্ণ কাজকে “দেখা” আর “করা” দুই ভাগে ভাঙলে ভুল ধরার একটি সুযোগ তৈরি হয়।
প্র ০২Terraform-এর লাইসেন্স বদল আর OpenTofu: একজন সাধারণ ব্যবহারকারীর জন্য এর মানে কী?
আগস্ট ২০২৩-এ HashiCorp তাদের প্রধান পণ্যগুলোর লাইসেন্স MPL 2.0 থেকে Business Source License (BUSL 1.1)-এ নেয়। BUSL ওপেন সোর্স সংজ্ঞায় পড়ে না: কোড পড়া ও ব্যবহার করা যায়, কিন্তু HashiCorp-এর সাথে প্রতিযোগিতা করে এমন পণ্যে ব্যবহারে সীমা আছে। নির্দিষ্ট সময় পরে কোড আবার উদার লাইসেন্সে যায় বলে শর্ত আছে।
কারণ হিসেবে বলা হয়েছিল, অন্য কোম্পানি তাদের কোডের ওপর ভিত্তি করে প্রতিযোগী সেবা বিক্রি করছে। Terraform-এর ওপর ব্যবসা গড়া কয়েকটি কোম্পানি ও community-র একটি অংশ এতে আপত্তি জানায়, এবং শেষ MPL সংস্করণ থেকে একটি fork শুরু করে, যা Linux Foundation-এর অধীনে OpenTofu নামে আসে। পরে IBM HashiCorp-কে কিনে নেয়; চুক্তির ঘোষণা ২০২৪ সালে, সম্পন্ন হয় ২০২৫ সালে।
একজন সাধারণ ব্যবহারকারী যিনি নিজের infrastructure চালাতে Terraform ব্যবহার করেন, তাঁর জন্য দৈনন্দিন কাজে সাধারণত বাধা নেই। প্রশ্নটি বেশি জরুরি তাঁদের জন্য, যাঁরা Terraform-কে ঘিরে পণ্য বিক্রি করেন। নিশ্চিত হতে হলে নিজের প্রতিষ্ঠানের আইনি পরামর্শ নেওয়াই ঠিক; এটি আইনি মতামত নয়।
দুটি tool-এর ভাষা ও workflow-এর বড় অংশ একই থাকলেও সময়ের সাথে পার্থক্য বাড়ে। যেমন OpenTofu নিজে থেকে state encryption যোগ করেছে। Provider-গুলো সাধারণত দুটিতেই চলে, তবে নতুন feature মিলিয়ে দেখতে হয়। কোনো দল চাইলে পরীক্ষার environment-এ আগে দেখে নিতে পারে যে একই config-এর plan দুটিতে একই আসে কি না।
মূল উপলব্ধি: Infrastructure tool বেছে নেওয়া মানে তার লাইসেন্স, মালিকানা আর ভবিষ্যৎ বেছে নেওয়া। Vendor lock-in শুধু cloud-এ নয়, tool-এও হয়। তাই ধারণাগুলো শিখুন, কারণ ধারণা tool-এর চেয়ে বেশি দিন টেকে।
প্র ০৩Terraform ফাইলের ক্রম না দেখে graph বানায় কেন? এতে লাভ আর দাম কী?
Script ওপর থেকে নিচে চলে, তাই ক্রমের দায়িত্ব লেখকের। একশো resource হলে কে কার আগে, তা মাথায় রাখা কঠিন। আর একটি লাইন ভুল জায়গায় বসলেই সব ভাঙে। Terraform প্রতিটি reference থেকে একটি directed acyclic graph বানায়, ফাইলের ক্রমের কোনো মূল্যই থাকে না। আপনি resource-গুলো যে কোনো ফাইলে, যে কোনো ক্রমে লিখতে পারেন।
এর তিনটি বড় লাভ। প্রথমত, সমান্তরাল কাজ: যাদের মধ্যে সম্পর্ক নেই তারা একসাথে চলে। এই পর্বের apply-তে report আর Deployment কারও অপেক্ষা করেনি। শত শত resource-এ সময়ের পার্থক্য বিশাল, যদিও খুব বেশি সমান্তরাল কাজ চাইলে API-র rate limit-এ আটকে যেতে পারে, আর তখন -parallelism কমাতে হয়। দ্বিতীয়ত, উল্টো destroy: একই graph উল্টে নিলেই মোছার সঠিক ক্রম, আলাদা করে লিখতে হয় না। তৃতীয়ত, পরিবর্তনের ঢেউ বোঝা: ConfigMap-এর নতুন uid report ফাইলকেও replace করাল, কারণ graph জানে কে কার মান ব্যবহার করে।
দামও আছে। Graph-এ বৃত্ত থাকলে অর্থাৎ A চায় B-কে, B চায় A-কে, Terraform Cycle error দেয়। Security group-এর পারস্পরিক নিয়মে এমন প্রায়ই হয়, যার সমাধান নিয়মগুলোকে আলাদা resource-এ নেওয়া। আরেকটি দাম: graph শুধু কোডে যা আছে তা জানে। একটি IAM policy বসার পরও তা ছড়াতে কয়েক সেকেন্ড লাগতে পারে, আর সেই সময়টি graph-এ নেই। এমন লুকানো নির্ভরতার জন্যই depends_on. আর graph দেখে বোঝা যায় না কাজটি নিরাপদ কি না, শুধু বোঝা যায় কোনটির পরে কোনটি হবে।
একই ধারণা build tool-এ (Make, Bazel) আর DSA-র topological sort-এ পাবেন।
মূল উপলব্ধি: ক্রম যখন তথ্য থেকে বের করা যায়, তখন তা হাতে না লেখাই ভালো। মানুষের কাজ সম্পর্ক ঘোষণা করা, ক্রম বের করা যন্ত্রের কাজ।
প্র ০৪count-এর index shift সমস্যাটি কি শুধু Terraform-এর? এর পেছনের সাধারণ নীতিটি কী?
না। এটি একটি সাধারণ প্রকৌশল-নীতির উদাহরণ: কোনো জিনিসের পরিচয় তার অবস্থান দিয়ে দেবেন না। count-এ একটি instance-এর পরিচয় তার index. Index বদলালে Terraform-এর চোখে সে অন্য জিনিস।
একই ভুল অন্য জায়গাতেও দেখা যায়। Database-এ সারির নম্বরকে primary key বানালে একটি সারি মুছলে বাকিদের foreign key ভেঙে যায়, তাই স্থায়ী id ব্যবহার হয়। React-এ list-এর key হিসেবে array index দিলে item মোছার পর input-এর মান ভুল সারিতে চলে যায়। আবার hash(key) mod N দিয়ে cache ভাগ করলে একটি server কমলে প্রায় সব key জায়গা বদলায়, আর consistent hashing ঠিক এই সরে যাওয়া কমায়।
Terraform-এ হিসাবটি সরল। nটি item-এর list-এ অবস্থান k (০ থেকে গোনা) মুছলে n-k-1টি replace হয়, আর শেষ index-টি destroy. পাঁচটির মধ্যে দ্বিতীয়টি (k=1) মুছলে ৩টি replace আর ১টি destroy. for_each-এ সব ক্ষেত্রেই ১টি destroy. আর যদি প্রতিটি replace একটি database হয়, তাহলে ক্ষতির মাত্রা blast radius অনেক বড়।
for_each-এরও নিয়ম আছে। তার key plan-এর সময়েই জানা থাকতে হয়। তাই apply-এর পরে জানা যায় এমন মান, যেমন একটি নতুন resource-এর id, key হিসেবে দিলে error আসে। আর key বদলালে আবার replace হয়, তাই key হওয়া উচিত স্থায়ী নাম, এমন কিছু নয় যা মাসে মাসে বদলায়। পুরোনো config-কে count থেকে for_each-এ নিতে হলে moved block দিয়ে পুরোনো address-কে নতুন address-এর সাথে জুড়ে দিতে হয়; নইলে Terraform ভাববে একটি মুছে আরেকটি বানাতে হবে। Plan দেখে নিশ্চিত হবেন যে সেখানে কোনো destroy নেই।
মূল উপলব্ধি: পরিচয় স্থায়ী হওয়া চাই, অবস্থান নয়। নতুন কোনো সিস্টেম দেখলে জিজ্ঞেস করুন: এখানে একটি জিনিসকে কী দিয়ে চেনা হয়, আর সেটি কখন বদলাতে পারে?
প্র ০৫Apply মাঝপথে ব্যর্থ হলে Terraform rollback করে না কেন? Database-এর transaction-এর মতো হলো না কেন?
Database-এ transaction সম্ভব, কারণ সব ডেটা একটি সিস্টেমের হাতে, আর সে একসাথে commit বা abort করতে পারে। Terraform-এর অবস্থা আলাদা। সে ডাকে নানা API: Kubernetes, ফাইল সিস্টেম, একাধিক cloud. এদের মধ্যে কোনো সাধারণ commit নেই। প্রতিটি call নিজেই একটি সম্পন্ন ঘটনা।
Rollback করতে গেলে সমস্যা আরও বাড়ে। প্রথমত, অনেক কাজ উল্টানো যায় না: একটি মোছা disk ফেরত আসে না। দ্বিতীয়ত, উল্টানোর কাজটিও ব্যর্থ হতে পারে, তখন অবস্থা আরও অজানা। তৃতীয়ত, সময়: database replace করতে কয়েক মিনিট লাগে, আর স্বয়ংক্রিয় rollback আরেকটি অপরিকল্পিত পরিবর্তন।
তাই Terraform অন্য পথ নিয়েছে: এগিয়ে যাওয়া, আর যা হলো তা ঠিকঠাক লিখে রাখা। এই পর্বের ব্যর্থ apply-তে first state-এ রয়ে গেল। বাধাটি সরালে পরের apply শুধু বাকিটুকু করবে। এটি reconcile ধারণা: কাঙ্ক্ষিত অবস্থা ঘোষিত, আর tool বারবার চেষ্টা করে সেখানে পৌঁছায়। Kubernetes controller-এর সাথে মিল আছে, যদিও Terraform নিজে থেকে লুপে চলে না, কেউ চালালে তবেই চলে।
পরিচিত উল্টো উদাহরণ AWS CloudFormation: ডিফল্টে সে ব্যর্থ stack update-এ পুরোনো অবস্থায় ফিরে যাওয়ার চেষ্টা করে। দুটি পদ্ধতিরই দাম আছে, আর rollback নিজেও আটকে যেতে পারে।
বাস্তব নিয়ম: বড় বদলকে ছোট ছোট apply-তে ভাগ করুন, যাতে ব্যর্থতার পরিধি ছোট থাকে। আর গুরুত্বপূর্ণ কাজের আগে backup আছে কি না দেখে নিন, কারণ Terraform নিজে আপনার ডেটা ফেরত দেবে না। Apply থেমে গেলে আতঙ্কে state হাতে না ঘেঁটে আগে terraform plan চালান।
মূল উপলব্ধি: Distributed system-এ “সব অথবা কিছুই না” প্রায় অসম্ভব। তাই ভালো tool ব্যর্থতা লুকায় না, বরং ব্যর্থতার পর আবার চেষ্টা করা নিরাপদ রাখে।
অনুশীলন
নিজে আগে উত্তর ভাবুন। হাতে-কলমের কাজটি শুধু নিজের কম্পিউটারে চলে, কোনো cloud account লাগে না।
-
হিসাব করুন: Registry-তে একটি provider-এর version আছে 3.3.9, 3.4.0, 3.4.7, 3.9.2, 4.0.0. (ক)
~> 3.4, (খ)~> 3.4.0, (গ)>= 3.4, < 3.6-এ কোনগুলো চলবে, আর lock file না থাকলে কোনটি নামবে? (ঘ) পাঁচ item-এর["a","b","c","d","e"]list থেকে"b"মুছলেcountআরfor_each-এর plan-এর শেষ লাইন কী হবে, যদি নাম বদলালে replace হয়?(ক) 3.4.0, 3.4.7, 3.9.2; নামবে 3.9.2. (খ) 3.4.0, 3.4.7; নামবে 3.4.7. (গ) 3.4.0, 3.4.7; নামবে 3.4.7.
(ঘ)
count:[1]b→c,[2]c→d,[3]d→e, এই ৩টি replace, আর[4]destroy. তাইPlan: 3 to add, 0 to change, 4 to destroy.for_each:Plan: 0 to add, 0 to change, 1 to destroy.এটিindex_shift.js-এ list দুটি বসিয়ে মিলিয়ে নিতে পারেন। -
ফল আগে বলুন: Variable
tier-এরdefault = "a".TF_VAR_tier=bসেট করা।terraform.tfvars-এ"c",z.auto.tfvars-এ"d",m.auto.tfvars-এ"e", আরqa.tfvars-এ"f". (ক)terraform plan(খ)terraform plan -var tier=g -var-file=qa.tfvars(গ)terraform plan -var-file=qa.tfvars -var tier=g-এtier-এর মান কত? এরপর (ঘ): একটিlocal_file-এরcontentবদলে plan চালালে চিহ্ন কী হবে, আর এই resource-এ~কি আশা করা যায়?(ক)
"d": দুই auto ফাইলের মধ্যে নামের ক্রমেzপরে আসে।qa.tfvarsনিজে থেকে পড়া হয় না, কারণ নামে.autoনেই। (খ)"f": command line-এ শেষে লেখা জন জেতে। (গ)"g".(ঘ)
-/+, পাশে# forces replacement.local_file-এ~আশা করবেন না, কারণ এই resource-এরcontentবদল replace হিসেবেই আসে, যা এই পর্বের report ফাইলের plan-এ দেখা গেছে। -
Review করুন: একজন সহকর্মীর PR-এ আছে:
required_providers-এ version নেই;.terraform.lock.hcl.gitignore-এ; তিনটি databasecount = length(var.dbs)দিয়ে বানানো;variable "db_password" { default = "Secret123" }; আর CI চালায়terraform apply -auto-approve, কোনো plan review ছাড়া। কী কী সমস্যা, আর কীভাবে ঠিক করবেন?- Version নেই, lock নেই: দুই জনের দুই provider version আসতে পারে।
~>constraint দিন, lock file Git-এ রাখুন। count-এ database: মাঝের একটি বাদ দিলে পরেরগুলো replace, অর্থাৎ ডেটা হারানোর ঝুঁকি।for_eachনিন, আরprevent_destroyও provider-এর deletion protection যোগ করুন।- Hardcoded password: Git-এ গোপন মান গেল, আর সেটি বদলাতে হবে। Secret manager থেকে আনুন,
sensitive = trueদিন, আর মনে রাখুন state-এ মান তবুও থাকতে পারে। -auto-approve, review নেই: PR-এplan -outদেখান, অনুমোদনের পর সেই ফাইল apply করুন।
- Version নেই, lock নেই: দুই জনের দুই provider version আসতে পারে।
-
হাতে-কলমে (ঐচ্ছিক, খরচ নেই): শুধু
hashicorp/localprovider দিয়ে একটি ফোল্ডারেlocal_fileবানান।init,plan,applyচালান।contentবদলে আবার plan পড়ুন। শেষেterraform destroyকরুন। Lock file আর state ফাইলে কী আছে দেখুন।এই পর্বের আসল run-এ যা হয়েছে, আপনারও একই হওয়ার কথা: প্রথম plan-এ
Plan: 1 to add, 0 to change, 0 to destroy..contentবদলালে-/+, আর# forces replacement. Destroy-এর পরে ফাইলটি থাকে না, আর state-এ কোনো resource থাকে না। শুরুতেইfilenamলিখেterraform validateচালিয়ে দেখলে “Did you mean” বার্তাও পাবেন। এতে কোনো cloud account বা টাকা লাগে না; শেষে ফোল্ডারটি মুছে দিলেই পরিষ্কার।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমনতুন Terraform ফোল্ডারে নিরাপদ কাজের ক্রম সাজান (▲▼ চেপে সরান)
terraform init: provider নামানো, lock file তৈরিterraform fmtআরterraform validate: ফাইল যাচাইterraform plan -out=tfplan: পরিবর্তনের তালিকা জমা- মানুষের review:
forces replacementআর destroy খোঁজা terraform apply tfplan: ঠিক সেই plan চালানো- আবার
terraform plan:No changes.মিলিয়ে দেখা
Provider-এর constraint version = "~> 3.4". নিচের কোন version চলবে?
- 3.3.9
- 3.9.2
- 4.0.0
- 4.1.0
Terraform plan-এ একটি resource-এর পাশে -/+ আর একটি argument-এর পাশে # forces replacement লেখা। এর অর্থ কী?
- Resource-টি একই থেকে জায়গায় বদলাবে
- নতুনটি আগে বানাবে, পুরোনোটি পরে মুছবে
- পুরোনোটি আগে মুছবে, তারপর নতুনটি বানাবে
- কোনো পরিবর্তন হবে না, শুধু সতর্কবার্তা
Terraform-এ count = length(var.names) আর names = ["a", "b", "c"] দিয়ে তিনটি resource তৈরি। প্রতিটির নাম names[count.index], আর নাম বদলালে replace হয়। List থেকে "b" মুছলে plan-এর শেষ লাইন কোনটি?
- Plan: 0 to add, 0 to change, 1 to destroy.
- Plan: 1 to add, 0 to change, 2 to destroy.
- Plan: 0 to add, 1 to change, 1 to destroy.
- Plan: 3 to add, 0 to change, 3 to destroy.
একটি Terraform variable-এর default আছে, TF_VAR_ environment variable আছে, terraform.tfvars-এ মান আছে, আর command line-এ -var দেওয়া। কোন মান ব্যবহার হবে?
- default
- TF_VAR_ environment variable
- terraform.tfvars
- -var-এর মান
terraform apply tfplan চালাতে গিয়ে এলো Error: Saved plan is stale. সবচেয়ে সম্ভাব্য কারণ কী?
- Plan বানানোর পরে অন্য কোনো কাজে state বদলে গেছে
- HCL ফাইলে syntax ভুল আছে
- Provider নামানো হয়নি
- Plan ফাইলের সময়সীমা এক ঘণ্টা পার হয়েছে
একটি Terraform resource-এ prevent_destroy = true আছে। কেউ পুরো resource block-টি ফাইল থেকে মুছে দিয়ে plan চালাল। কী হবে?
- Error আসবে: Instance cannot be destroyed
- কিছুই হবে না, resource-টি আগের মতো থাকবে
- Plan সেটিকে destroy হিসেবে দেখাবে
- Terraform block-টি আবার ফাইলে লিখে দেবে
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Config + state → plan → provider → API. Core হিসাব করে, provider কাজ করে, state মনে রাখে।
- Constraint বলে সীমা, lock file বলে ঠিক কোনটি। Lock file Git-এ, state আর plan ফাইল নয়।
- Reference থেকে graph আসে;
depends_onশুধু লুকানো সম্পর্কের জন্য। - চিহ্ন পড়ুন:
+,~,-,-/+,+/-.forces replacementদেখলেই থামুন। - পরিচয় থাকলে
for_each, আরprevent_destroyblock মুছলে পাহারাও শেষ। Apply-এর পরে app নিজে দেখুন।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৫৩ · Terraform state — backend, lock, import আর driftপরবর্তী পর্বState-এর ভেতরের গঠন, দলের জন্য remote backend আর locking.
- ক্লাউড কম্পিউটিং ও DevOps · কোর্স হোম৮১টি পর্বপুরো কোর্সের রোডম্যাপ।
- পর্ব ৪৭ · ConfigMap, Secret আর storageআগের পর্বConfigMap বদলালে Pod কেন পুরোনো মান রাখে।
- পর্ব ৪৪ · Deploymentআগের পর্বPod template বদলালে rollout কীভাবে হয়।
- Continuous Delivery ও DevOpsপাশের ট্র্যাকPlan review কীভাবে delivery pipeline-এ বসে।