সরাসরি পর্বে যান
পর্ব ৫১ · ৮১-এর মধ্যে · মডিউল ৯

Infrastructure as Code — declarative, idempotent আর drift

Infrastructure as Code — Declarative, Idempotent and Drift

Taught in Bangla, this lesson shows why ClickOps breaks down in larger teams and what IaC changes, running a script and a Terraform config twice to compare imperative and declarative behaviour, idempotence, desired, recorded and real state, dependency graphs, drift, replacement, a Git and review workflow, blast radius, plan-reading habits, and a map of IaC tools and their limits.

এই পর্বে যা শিখবেন

  • ClickOps কেন বড় দলে ভেঙে পড়ে, আর IaC ঠিক কী বদলায়
  • একটি script দুইবার আর একটি Terraform config দুইবার চালিয়ে declarative আর idempotent-এর আসল অর্থ
  • Desired, recorded আর real state-এর পার্থক্য, আর plan কীভাবে তৈরি হয়
  • Drift ধরা, replace চেনা, আর dependency graph-এর ক্রম বোঝা
  • Git, review আর ছোট blast radius দিয়ে নিরাপদ workflow, আর tool বাছাইয়ের মানচিত্র

আউটপুট সম্পর্কে: এই পর্বের Terraform transcript-গুলো আসল run: Terraform ১.১৭, hashicorp/local ২.৯ আর hashicorp/kubernetes ৩.৪ provider, আর একটি local single-node cluster (Kubernetes ১.৩৪), namespace abcl-ia. কোনো cloud account ব্যবহার হয়নি, তাই cloud provider-এর plan এখানে চালানো হয়নি। আপনার run-এ id, hash আর সময় আলাদা হবে। লম্বা আউটপুট ছোট করা হয়েছে, আর বাদ দেওয়া অংশে … আছে। Browser-এ Terraform চলে না, তাই transcript পড়ার জন্য, আর simulator নিজে বদলে চালানোর জন্য।

১ · ClickOps: হাতে বানানোর তিনটি দাম

একটি কাল্পনিক অনলাইন বাজার অ্যাপের কথা ভাবুন। ছয় মাস আগে রিফাত console খুলে server, firewall আর database বানিয়েছেন। সব ঠিকই চলছে। এখন একই রকম একটি staging environment দরকার, আর রিফাত চাকরি ছেড়ে গেছেন। Firewall-এ কোন port খোলা ছিল? Database-এর কোন setting বদলানো হয়েছিল? কেউ জানে না।

Web console-এ ক্লিক করে infrastructure বানানোকে মজা করে বলা হয় ClickOps. শেখার জন্য, বা একটি নতুন service চেখে দেখার জন্য console চমৎকার। সমস্যা শুরু হয় যখন ক্লিকই production বানানোর একমাত্র পথ হয়ে যায়। দাম তিনটি:

রেকর্ড নেই

কী আছে, কেন আছে, কে কবে বদলেছে, তা কোনো এক জায়গায় পড়া যায় না। Audit log ঘটনা দেখায়, কিন্তু উদ্দেশ্য দেখায় না।

No record

পর্যালোচনা নেই

একজন মানুষের একটি ক্লিক সরাসরি production-এ যায়। Application code-এর মতো কেউ আগে দেখে না।

No review

পুনরাবৃত্তি নেই

Staging আর production ধীরে ধীরে আলাদা হয়ে যায়। দুর্যোগের পরে আবার বানাতে গেলে স্মৃতির ওপর ভরসা করতে হয়।

No repeat

দাদি মাপ ছাড়াই অসাধারণ বিরিয়ানি রাঁধেন। কিন্তু রেসিপি তাঁর মনে। তিনি না থাকলে স্বাদটাও হারিয়ে যায়। রান্নাঘরের রেসিপি খাতায় লিখে রাখলে যে কেউ একই স্বাদ আনতে পারে, কেউ ভুল ধরিয়ে দিতে পারে, আর একশো জনের বিয়েবাড়ির জন্য একই রান্না বাড়ানো যায়। IaC হলো infrastructure-এর রেসিপি খাতা।

মূল ধারণা

Infrastructure as Code মানে server, network, database আর অনুমতির কাঙ্ক্ষিত রূপ টেক্সট ফাইলে লেখা, সেই ফাইল version control-এ রাখা, পর্যালোচনা করা, আর একটি tool দিয়ে আসল জগতে বসানো। ফলে infrastructure-এ application code-এর সব সুবিধা আসে: ইতিহাস, diff, review, পরীক্ষা আর পুনরায় বানানো। Console, CLI আর API-র পার্থক্য পর্ব ০৬-এ দেখেছেন। সব IaC tool শেষে সেই API-ই ডাকে।

CFEngine থেকে Terraform: দুই দশকের পথ

IaC-র ভাবনা cloud-এর চেয়ে পুরোনো। প্রথম প্রশ্ন ছিল, একশোটি Unix machine-কে একই রকম রাখব কীভাবে? Cloud আসার পরে প্রশ্ন বদলাল: machine-টাই যদি API ডেকে বানানো যায়, তাহলে পুরো data center কোডে লেখা যাবে না কেন?

বছরToolকী নতুন আনল
১৯৯৩CFEngine (Mark Burgess)Machine-কে নির্দিষ্ট অবস্থায় টেনে আনার ধারণা, যাকে বলা হয় convergence
২০০৫Puppet (Luke Kanies)নিজস্ব declarative ভাষায় server-এর configuration
২০০৯Chef (Adam Jacob)Ruby-তে লেখা recipe, ডেভেলপারদের পরিচিত ধাঁচ
২০১১AWS CloudFormationTemplate ফাইল (শুরুতে JSON, পরে YAML-ও) থেকে পুরো cloud resource-এর দল বানানো
২০১২Ansible (Michael DeHaan)Agent ছাড়া, SSH দিয়ে YAML playbook
২০১৪Terraform (HashiCorp)এক ভাষায় বহু provider, আর apply-এর আগে আলাদা plan

এখান থেকে দুটি পরিবার চিনে নিন। Configuration management (Puppet, Chef, Ansible) একটি চলমান server-এর ভেতরে package আর ফাইল ঠিক রাখে। Provisioning tool (CloudFormation, Terraform) server, network আর database-এর মতো জিনিসটাই বানায়। সীমারেখা পুরোপুরি কঠিন নয়, Ansible দিয়েও cloud resource বানানো যায়। এই মডিউলের বেশিরভাগ অংশ provisioning নিয়ে। Configuration management আসবে পর্ব ৫৬-এ।

২০২৩ সালের আগস্টে HashiCorp Terraform-এর licence open-source MPL ২.০ থেকে BUSL ১.১-এ বদলায়। BUSL source দেখার সুযোগ রাখে, কিন্তু প্রতিযোগী পণ্যে ব্যবহারে সীমা বসায়। এর জবাবে সম্প্রদায় শেষ MPL সংস্করণ থেকে একটি fork বানায়, যার নাম OpenTofu. এটি Linux Foundation-এর অধীনে চলে, আর MPL licence-ই রেখেছে। এই কোর্সে Terraform দিয়ে শেখানো হচ্ছে, তবে ধারণাগুলো দুটিতেই খাটে।

২ · Imperative বনাম declarative: একই কাজ দুইবার

মতিঝিল যাবেন, CNG-তে উঠলেন। চালককে দুইভাবে বলা যায়। “সোজা যান, দ্বিতীয় মোড়ে বামে, তারপর ডানে।” এটি imperative: আপনি ধাপ বলছেন। অথবা “মতিঝিল শাপলা চত্বর।” এটি declarative: আপনি গন্তব্য বলছেন, পথ চালকের দায়িত্ব। আপনি যদি আগে থেকেই শাপলা চত্বরে থাকেন? প্রথম ধরনের নির্দেশ মানলে অন্য কোথাও চলে যাবেন। দ্বিতীয় ধরনে চালক বলবেন, “এসেই তো গেছি।”

নিচের script-এ লক্ষ্য একটাই: site ফোল্ডার আর একটি config লাইন। প্রথম function ধাপ চালায়। দ্বিতীয়টি আগে দেখে, পার্থক্য থাকলেই কেবল কিছু করে। দুটিই দুইবার চলবে। চালিয়ে দেখুন:

#!/usr/bin/env bash
# One goal: folder + one config line. Each style runs twice.
set -u
cd "$(mktemp -d)"
WANT="port=8080"

imperative() {            # a list of steps
  mkdir site 2>&1
  echo "$WANT" >> site/app.conf
  echo "  lines in file: $(wc -l < site/app.conf)"
}

converge() {              # check first, act only on a difference
  if [ -d site2 ]; then echo "  folder: ok"
  else mkdir site2; echo "  folder: created"; fi

  if [ "$(cat site2/app.conf 2>/dev/null)" = "$WANT" ]; then
    echo "  file: ok, no change"
  else
    echo "$WANT" > site2/app.conf; echo "  file: written"
  fi
  echo "  lines in file: $(wc -l < site2/app.conf)"
}

echo "== imperative, run 1"; imperative
echo "== imperative, run 2"; imperative
echo "== converge, run 1";   converge
echo "== converge, run 2";   converge

Imperative-এর দ্বিতীয় run-এ দুটি সমস্যা: mkdir বলে File exists, আর ফাইলে একই লাইন দুইবার ঢোকে (lines in file: 2). set -e থাকলে script মাঝপথেই থেমে যেত। Converge-এর দ্বিতীয় run বলে file: ok, no change, আর ফাইলে লাইন একটিই থাকে। এই গুণের নাম idempotency: একবার করা আর একাধিকবার করার ফল একই। লক্ষ্য করুন, converge নিজে declarative tool নয়, বরং হাতে লেখা একটি idempotent script. তুলনা আর সিদ্ধান্তের কোড আমাদের লিখতে হয়েছে। Declarative tool ঠিক এই অংশটি আপনার হয়ে করে।

এবার একই লক্ষ্য Terraform-এ। এখানে কোনো ধাপ নেই, শুধু শেষ ফলের বর্ণনা:

terraform {
  required_version = ">= 1.6"
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.5"
    }
  }
}

resource "local_file" "app_conf" {
  filename = "${path.module}/site/app.conf"
  content  = "port=8080\n"
}
$ terraform apply -auto-approve
…
  # local_file.app_conf will be created
  + resource "local_file" "app_conf" {
      + content              = <<-EOT
            port=8080
        EOT
      …
      + filename             = "./site/app.conf"
      …
    }

Plan: 1 to add, 0 to change, 0 to destroy.
local_file.app_conf: Creating...
local_file.app_conf: Creation complete after 0s [id=b652360b0632ca0f81fbcbad647a5761d4a68b53]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

$ terraform apply -auto-approve
local_file.app_conf: Refreshing state... [id=b652360b0632ca0f81fbcbad647a5761d4a68b53]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration
and found no differences, so no changes are needed.

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

দ্বিতীয় run-এ Terraform প্রথমে আসল ফাইল পড়েছে (Refreshing state), তারপর তুলনা করেছে, আর কিছুই করেনি। Script-এ এই তুলনা আপনাকে লিখতে হতো। -auto-approve এখানে শুধু শেখার জন্য। এটি প্রশ্ন না করেই apply করে, তাই নিজের হাতে production-এ কখনো ব্যবহার করবেন না।

প্রশ্নImperativeDeclarative
আপনি কী লেখেনধাপের ক্রমশেষ ফল
আগে থেকে জিনিসটি থাকলেনিজে সামলাতে হয়Tool দেখে, কিছু করে না
মুছতে চাইলেআলাদা script লিখতে হয়Code থেকে লাইনটি সরালেই হয়
কী হবে আগে দেখাকঠিনPlan দেখায়
Custom যুক্তি, data migrationসহজকঠিন বা অস্বাভাবিক

Declarative tool-ও আপনার ভুলে idempotent থাকে না। ধরুন local_file-এর content-এ timestamp() লিখলেন। এই function প্রতি run-এ নতুন মান দেয়। আসল run-এ দ্বিতীয় plan বলেছে # local_file.stamp must be replaced আর Plan: 1 to add, 0 to change, 1 to destroy. Code কোনো বদল ছাড়াই প্রতিবার একটি পরিবর্তন দেখালে আগে এমন অস্থির মান খুঁজুন।

৩ · এক জিনিসের তিন রূপ: desired, recorded আর real

একটি গুদামঘরের কথা ভাবুন। মালিকের তালিকায় লেখা, তাকে ৫০ বস্তা চাল থাকবে। হিসাবের খাতায় লেখা, গত সপ্তাহে ৫০ বস্তা তোলা হয়েছে। আর আসল তাকে আছে ৪৮ বস্তা। তিনটি আলাদা তথ্য, আর তিনটি নিয়েই কাজ করতে হয়। IaC-তেও তাই:

Desired state

আপনার code-এ যা লেখা। “এমন হওয়া উচিত।” মালিকের তালিকা।

Git-এ থাকে

Recorded state

গত apply-এর পরে tool যা লিখে রেখেছে। Terraform-এ এটি state ফাইল। হিসাবের খাতা।

Backend-এ থাকে

Real state

এই মুহূর্তে cloud বা cluster-এ যা সত্যিই আছে। আসল তাক।

Provider-এর API জানে

Kubernetes-এর desired state আর reconciliation loop পর্ব ৪২-এ দেখেছেন। সেখানে কথা ছিল দুটি অবস্থার। Terraform-এ তৃতীয়টি কেন লাগে? কারণ তিনটি:

  • নাম থেকে আসল জিনিস: code-এ শুধু local_file.app_conf লেখা। State জানে, এই নামের সাথে কোন আসল id বাঁধা।
  • মোছা: code থেকে একটি resource সরালে code তো আর তার কথা জানে না। State-এ নাম আছে, code-এ নেই, এই অমিল থেকেই Terraform বোঝে জিনিসটি মুছতে হবে।
  • মেটাডেটা: dependency-র ক্রম আর হাজার resource-এর হিসাব রাখা, যাতে প্রতিবার সব কিছু নতুন করে জানতে না হয়।

Terraform-এর কাজের ধারা তাই এমন: state-এ যা আছে, তার আসল অবস্থা API থেকে পড়ে (এটি refresh), তারপর সেটি code-এর সাথে তুলনা করে। State ফাইলে সব attribute-এর মান থাকে, password-ও। তাই একে গোপন তথ্য হিসেবে সামলাতে হয়। Backend, lock আর import-এর বিস্তারিত পর্ব ৫৩-এ।

Plan আসলে একটি diff

Plan হলো কাঙ্ক্ষিত আর আসল অবস্থার পার্থক্যের তালিকা। Git-এর diff যেমন লাইনের পার্থক্য দেখায়, plan দেখায় infrastructure-এর পার্থক্য। প্রতিটি resource-এর জন্য ফল পাঁচটির একটি:

চিহ্নঅর্থকখনঝুঁকি
+createCode-এ আছে, আসলে নেইকম, তবে খরচ বাড়ে
~update in-placeদুটিতেই আছে, কিছু মান আলাদামাঝারি
-destroyআসলে আছে, code-এ নেইবেশি
-/+replace: মুছে তারপর নতুনযে মান জায়গায় বদলানো যায় নাসবচেয়ে বেশি
কোনো চিহ্ন নেইno-opসব মিলে গেছেনেই

এই পুরো ধারণাটি কয়েক লাইনে লেখা যায়। নিচের খেলনা engine-এ known হলো refresh-এর পরের অবস্থা। চালান, তারপর known-এ deploy/web-এর replicas 1 করে দেখুন, config/old মুছে দেখুন, বা forcesNew ফাঁকা করে দেখুন:

// A toy plan engine. Change desired or known, then run again.
// desired = your code. known = state after refresh (what really exists).
const desired = {
  "config/site": { greeting: "Salam" },
  "deploy/web":  { replicas: 1, image: "nginx:1.27" },
  "db/main":     { engine: "postgres", size: "small" },
  "service/web": { port: 80 },
};
const known = {
  "config/site": { greeting: "Salam" },
  "deploy/web":  { replicas: 3, image: "nginx:1.27" },
  "db/main":     { engine: "mysql", size: "small" },
  "config/old":  { greeting: "Hi" },
};
// Changing these fields cannot be done in place.
const forcesNew = ["engine"];

function plan(want, have) {
  const count = { add: 0, change: 0, destroy: 0 };
  for (const name of Object.keys({ ...have, ...want }).sort()) {
    const w = want[name], h = have[name];
    if (!h) { count.add++; console.log("  +   " + name + "  create"); continue; }
    if (!w) { count.destroy++; console.log("  -   " + name + "  delete"); continue; }
    const diffs = Object.keys(w).filter((k) => w[k] !== h[k]);
    const text = diffs.map((k) => k + ": " + h[k] + " -> " + w[k]).join(", ");
    if (diffs.length === 0) console.log("  =   " + name + "  no-op");
    else if (diffs.some((k) => forcesNew.includes(k))) {
      count.add++; count.destroy++;
      console.log("  -/+ " + name + "  replace (" + text + ")");
    } else { count.change++; console.log("  ~   " + name + "  update (" + text + ")"); }
  }
  console.log(`Plan: ${count.add} to add, ${count.change} to change, ${count.destroy} to destroy.`);
}
plan(desired, known);

আউটপুটের শেষ লাইন: Plan: 2 to add, 1 to change, 2 to destroy. Resource আছে পাঁচটি, কিন্তু যোগফল ৫ নয়। db/main-এর replace একবার add আর একবার destroy-তে গোনা হয়েছে। তাই 2 to add মানে service/web আর নতুন db/main, আর 2 to destroy মানে config/old আর পুরোনো db/main. Summary লাইনে destroy সংখ্যাটি প্রত্যাশার চেয়ে বেশি হলে থামুন। এটি একটি database-ও হতে পারে। আসল Terraform-ও একইভাবে গোনে, যা পরে replace-এর আসল আউটপুটে দেখবেন। আসল plan অবশ্য আরও অনেক কিছু দেখে: nested block, provider-এর schema, আর কোন মান apply-এর পরেই জানা যাবে (known after apply).

৪ · Dependency graph: কে আগে, কে পরে

বাড়ি বানাতে আগে ভিত, তারপর দেয়াল, তারপর ছাদ। ভাঙতে হলে উল্টো ক্রম। Declarative ফাইলে কিন্তু ক্রম লেখা হয় না। তাহলে tool ক্রম জানে কীভাবে? উত্তর: একটি resource যখন অন্যটির মান ব্যবহার করে, সেখান থেকেই একটি dependency graph তৈরি হয়। নিচের config-এ Deployment-এর config_map_ref ConfigMap-এর নাম পড়ছে:

resource "kubernetes_config_map_v1" "site" {
  metadata {
    name      = "site-config"
    namespace = "abcl-ia"
  }
  data = {
    GREETING = "Salam"
  }
}

resource "kubernetes_deployment_v1" "web" {
  metadata {
    name      = "web"
    namespace = "abcl-ia"
  }
  spec {
    replicas = 1
    selector {
      match_labels = { app = "web" }
    }
    template {
      metadata {
        labels = { app = "web" }
      }
      spec {
        container {
          name  = "web"
          image = "nginx:1.27-alpine"
          env_from {
            config_map_ref {
              name = kubernetes_config_map_v1.site.metadata[0].name
            }
          }
          resources {
            requests = { cpu = "10m", memory = "16Mi" }
            limits   = { memory = "48Mi" }
          }
        }
      }
    }
  }
}
$ terraform graph
digraph G {
  rankdir = "RL";
  node [shape = rect, fontname = "sans-serif"];
  "kubernetes_config_map_v1.site" [label="kubernetes_config_map_v1.site"];
  "kubernetes_deployment_v1.web" [label="kubernetes_deployment_v1.web"];
  "kubernetes_deployment_v1.web" -> "kubernetes_config_map_v1.site";
}

$ terraform apply tfplan
kubernetes_config_map_v1.site: Creating...
kubernetes_config_map_v1.site: Creation complete after 0s [id=abcl-ia/site-config]
kubernetes_deployment_v1.web: Creating...
kubernetes_deployment_v1.web: Creation complete after 4s [id=abcl-ia/web]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

$ terraform destroy -auto-approve
…
Plan: 0 to add, 0 to change, 2 to destroy.
kubernetes_deployment_v1.web: Destroying... [id=abcl-ia/web]
kubernetes_deployment_v1.web: Destruction complete after 0s
kubernetes_config_map_v1.site: Destroying... [id=abcl-ia/site-config-v3]
kubernetes_config_map_v1.site: Destruction complete after 0s

Destroy complete! Resources: 2 destroyed.

Graph-এর একমাত্র তিরটি বলছে, web নির্ভর করে site-এর ওপর। তাই তৈরির সময় ConfigMap আগে, মোছার সময় Deployment আগে। এখানে site-config-v3 নামটি এসেছে, কারণ এই destroy চলেছে পরের পরীক্ষাগুলোর শেষে। ফাইলে resource দুটি কোন ক্রমে লেখা, তাতে কিছু আসে যায় না।

একই ধারণা একটু বড় graph-এ চালিয়ে দেখুন। প্রতিটি key-এর পাশে লেখা, সে কার ওপর নির্ভর করে। এবার network-এর তালিকায় "balancer" বসিয়ে চালান। একটি বৃত্ত তৈরি হবে:

const needs = {
  network:  [],
  subnet:   ["network"],
  firewall: ["network"],
  database: ["subnet", "firewall"],
  server:   ["subnet", "firewall", "database"],
  balancer: ["subnet", "server"],
};

function levels(graph) {
  const level = {}, visiting = new Set();
  function visit(name, path) {
    if (level[name] !== undefined) return level[name];
    if (visiting.has(name)) throw new Error("Cycle: " + [...path, name].join(" -> "));
    if (!graph[name]) throw new Error("Unknown resource: " + name);
    visiting.add(name);
    const below = graph[name].map((d) => visit(d, [...path, name]));
    visiting.delete(name);
    return (level[name] = 1 + Math.max(0, ...below));
  }
  Object.keys(graph).forEach((n) => visit(n, []));
  return level;
}

try {
  const level = levels(needs);
  const depth = Math.max(...Object.values(level));
  const at = (d) => Object.keys(level).filter((n) => level[n] === d).sort().join(", ");
  console.log("CREATE (same line = can run in parallel)");
  for (let d = 1; d <= depth; d++) console.log("  step " + d + ": " + at(d));
  console.log("DESTROY (the same steps, reversed)");
  for (let d = depth; d >= 1; d--) console.log("  step " + (depth - d + 1) + ": " + at(d));
} catch (e) {
  console.log("Error: " + e.message);
}

তৈরির step 2-তে আছে firewall, subnet: এরা একে অপরের ওপর নির্ভর করে না, তাই একসাথে চলতে পারে। আসল Terraform-ও graph ধরে সমান্তরালে কাজ করে, আর -parallelism দিয়ে সীমা বদলানো যায়। মোছার ক্রম হুবহু উল্টো, balancer আগে, network শেষে। বৃত্তের পরীক্ষায় আউটপুট হবে Error: Cycle: network -> balancer -> subnet -> network. আসল Terraform-ও বৃত্ত পেলে Cycle error দিয়ে থামে, কারণ কোনটি আগে, তার উত্তরই নেই।

কিছু নির্ভরতা code-এ দেখা যায় না। ধরুন একটি server বুট হওয়ার সময় একটি bucket থেকে ফাইল নামায়, অথচ config-এ bucket-এর নাম সরাসরি লেখা, কোনো reference নেই। Graph-এ তখন তির থাকবে না। এমন ক্ষেত্রেই depends_on দিয়ে নির্ভরতা নিজে বলে দিতে হয়।

৫ · Drift: code বলে এক, আসল অবস্থা আরেক

রাত দুইটায় traffic বেড়ে গেল। On-call engineer দ্রুত kubectl scale দিয়ে replica বাড়ালেন। ঠিক কাজই করলেন। কিন্তু code-এ এখনো replicas = 1. কাঙ্ক্ষিত আর আসল অবস্থার এই অমিলই drift. এটি আসল run-এ করে দেখা যাক:

$ kubectl scale deployment web --replicas=3 -n abcl-ia
deployment.apps/web scaled

$ terraform plan -detailed-exitcode
kubernetes_config_map_v1.site: Refreshing state... [id=abcl-ia/site-config]
kubernetes_deployment_v1.web: Refreshing state... [id=abcl-ia/web]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # kubernetes_deployment_v1.web will be updated in-place
  ~ resource "kubernetes_deployment_v1" "web" {
        id               = "abcl-ia/web"
        # (1 unchanged attribute hidden)

      ~ spec {
          ~ replicas                  = "3" -> "1"
            # (4 unchanged attributes hidden)

            # (3 unchanged blocks hidden)
        }

        # (1 unchanged block hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.
…
$ echo $?
2

"3" -> "1" পড়ুন “এখন আছে ৩, হবে ১”। Plan drift ধরেছে, আর এই আউটপুটে সে code-কেই সত্য ধরে নিয়েছে। -detailed-exitcode দিলে exit code হয় 0 (পার্থক্য নেই), 1 (error) বা 2 (পার্থক্য আছে)। তাই রাতের schedule-করা একটি job দিয়েই drift ধরা যায়। আর plan নিজে কিছু বদলায়নি: এই সময়ে state ফাইলে তখনও replicas = "1" ছিল, আর cluster-এ ৩টি replica চলছিল।

নিচের ডায়াগ্রামে তিন জায়গার মান ধাপে ধাপে দেখুন। প্রশ্নটি হলো, এখন apply করা কি নিরাপদ?

Deployment web-এর replicas
Code (desired)
State (recorded)
Cluster (real)
  1. সব মিলছে। তিন জায়গাতেই ১। Plan বলছে No changes.
  2. হাতে বদল। kubectl scale শুধু cluster বদলাল, এখন ৩। Code আর state জানে না, আর Terraform নিজে থেকে কিছু দেখছেও না। Kubernetes-এর controller-এর মতো সে অবিরাম চলে না, শুধু চালালেই চলে।
  3. Drift ধরা পড়ল। Schedule-করা terraform plan refresh করে আসল ৩ দেখল। Exit code 2. State ফাইল এখনো ১, কারণ plan state বদলায় না।
  4. থামুন, সিদ্ধান্ত নিন। এখন apply করলে replica ৩ থেকে ১-এ নামবে, ঠিক যখন traffic বেশি। Tool জানে কী আলাদা, কিন্তু জানে না কেন আলাদা। কোনটি ঠিক, সেটি মানুষের সিদ্ধান্ত। এখানে ঠিক হলো ৩।
  5. Code-কে আসলের কাছে আনা। একটি pull request-এ replicas = 3 করা হলো। এখন code ৩, cluster ৩। Plan আবার বলছে No changes., যদিও পুরোনো state-এ ১ লেখা।
  6. Merge-এর পরে apply. Resource বদলাতে হয়নি, 0 changed, কিন্তু refresh-এর ফল state-এ লেখা হলো। তিন জায়গাতেই এখন ৩।

Drift মেটানোর পথ তাই একটি নয়:

পথকখনকীভাবে
ফিরিয়ে দেওয়া (revert)হাতের বদলটি ভুল বা অননুমোদিতterraform apply. আসল run-এ ফল ছিল 1 changed, আর cluster ১-এ ফিরল
গ্রহণ করা (adopt)বদলটি ঠিক ছিলCode বদলে PR, তারপর apply
শুধু খাতা হালনাগাদবাইরের বদল মেনে নিয়ে state ঠিক করতে চানterraform plan -refresh-only দেখে তারপর apply
ইচ্ছাকৃতভাবে উপেক্ষাঅন্য কেউ মানটি চালায়, যেমন HPAlifecycle-এ ignore_changes

Plan সব কিছু দেখে না। Terraform শুধু সেই জিনিস পরীক্ষা করে, যা তার state-এ আছে। আমরা হাতে আলাদা একটি ConfigMap manual-note বানিয়ে plan চালিয়েছিলাম। ফল: No changes. অর্থাৎ code-এর বাইরে হাতে বানানো জিনিস এই drift পরীক্ষায় ধরা পড়ে না। অন্যদিকে Deployment-এ হাতে বসানো label আর environment variable, আর হাতে মোছা ConfigMap, তিনটিই আসল run-এ plan ধরেছে। মোছা ConfigMap-এর ক্ষেত্রে plan বলেছে will be created.

কোন field drift হিসেবে ধরা পড়বে, তা provider আর resource-এর ওপর নির্ভর করে। যেমন local_file-এ ফাইল হাতে বদলালে আসল run-এ ~ update আসেনি, এসেছে # local_file.app_conf will be created: provider পুরো জিনিসটিকেই “নেই” ধরে নিয়েছে। তাই ধরে নেবেন না, পরীক্ষা করে দেখুন।

৬ · Replace: বদল নয়, মুছে নতুন বানানো

সব মান জায়গায় বদলানো যায় না। লঞ্চের রং নতুন করা যায়, কিন্তু কাঠামো বদলাতে হলে নতুন লঞ্চই বানাতে হয়। Cloud-এও তাই। Kubernetes-এ object-এর নাম, আর cloud-এ VM-এর availability zone বা database-এর কিছু setting সাধারণত এমন। Provider-এর schema বলে দেয় কোন field replacement ঘটাবে। ConfigMap-এর নাম বদলে আসল plan:

$ 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.site must be replaced
-/+ resource "kubernetes_config_map_v1" "site" {
      …
      ~ metadata {
          …
          ~ name             = "site-config" -> "site-config-v2" # forces replacement
          …
        }
    }

  # kubernetes_deployment_v1.web will be updated in-place
  ~ resource "kubernetes_deployment_v1" "web" {
      …
                          ~ config_map_ref {
                              ~ name     = "site-config" -> "site-config-v2"
      …
    }

Plan: 1 to add, 1 to change, 1 to destroy.

দুটি শব্দ খুঁজে নিতে শিখুন: must be replaced আর # forces replacement. দ্বিতীয়টি বলে দেয়, ঠিক কোন লাইনটির জন্য replace হচ্ছে। ধাপগুলো দেখুন:

আসল cluster-এ কী আছে
site-config
site-config-v2
web যে নাম চায়
  1. শুরু। web-এর Pod site-config থেকে environment পড়ে। সব ঠিক আছে।
  2. Code-এ নাম বদল। নাম জায়গায় বদলানো যায় না, তাই plan-এ -/+. Deployment-এ reference থাকায় সেও ~ update-এ এসেছে। মোট ৩টি কাজ।
  3. আগে মোছা। Default ক্রমে পুরোনোটি আগে মোছে। এই মুহূর্তটি বিপজ্জনক: Deployment এখনো মুছে যাওয়া নাম চায়। চলমান Pod-এর environment আগেই বসানো, তাই সে চলতে থাকে, কিন্তু এই সময়ে নতুন Pod শুরু হলে সাধারণত CreateContainerConfigError পায়।
  4. নতুন তৈরি। site-config-v2 এলো। আসল run-এ মাঝের ফাঁক এক সেকেন্ডেরও কম ছিল। Cloud-এর একটি load balancer বা database-এ এই ফাঁক মিনিট বা ঘণ্টাও হতে পারে।
  5. Deployment বদল। নতুন নাম পেয়ে Deployment নতুন Pod চালায়। ফল: Apply complete! Resources: 1 added, 1 changed, 1 destroyed.
  6. ভালো ক্রম। create_before_destroy = true দিলে চিহ্ন হয় +/-. আসল run-এ ক্রম ছিল: Creating, Modifying, তারপর deposed object মোছা। ফাঁক নেই, তবে নতুন আর পুরোনো কিছুক্ষণ একসাথে থাকে, তাই নাম আলাদা হতে হয়।
resource "local_file" "keep" {
  filename = "${path.module}/keep.txt"
  content  = "important\n"
  lifecycle {
    prevent_destroy = true
  }
}

prevent_destroy থাকলে এই resource মোছার plan আটকে যায়। আসল run-এ error ছিল Error: Instance cannot be destroyed. Database-এর মতো জিনিসে এটি একটি সস্তা সিটবেল্ট। তবে resource block-টাই code থেকে সরালে এই রক্ষাও চলে যায়। পুরো রক্ষা নয়, তাই সাথে backup আর provider-এর deletion protection রাখুন।

৭ · Code থেকে cloud: Git, review আর apply

IaC-র আসল শক্তি আসে Git-এ রাখলে। Code পরিবর্তন একটি pull request হয়। সেখানে CI plan চালায়, মানুষ plan পড়ে, আর merge-এর পরে apply চলে। Code review-এর অভ্যাসটিই infrastructure-এ আসে।

IaC-র পুরো পথ

Code → plan → review → apply → cloud, with state

Code থেকে cloud পর্যন্ত IaC-র পথ, state-সহ CodeGit-এ .tf ফাইল, desired state Planপার্থক্য হিসাব, কিছুই বদলায় না Reviewমানুষ plan পড়েন, অনুমোদন Applyঅনুমোদিত plan চালানো CloudReal state, provider API Staterecordedstate হাতে বদলdrift-এর উৎস পড়ে লেখে refresh
ছবি ৫১.১ — Plan দুই দিক থেকে পড়ে: state আর আসল cloud. Apply-ই শুধু cloud আর state বদলায়।

এবার একটি ছোট বদল পুরো পথে হাঁটিয়ে দেখি: web-এর replica ১ থেকে ২।

  1. Branchcode বদল
  2. PR + CIplan তৈরি
  3. Reviewplan পড়া
  4. Merge, applyবদল হলো
  5. যাচাইNo changes
Git diffএক লাইনের বদল
$ git switch -c scale-web
  spec {
-    replicas = 1
+    replicas = 2
CI jobনমুনা ধাপ
terraform fmt -check
terraform validate
terraform plan -out=tfplan
PR-এর commentনমুনা আউটপুট
  # kubernetes_deployment_v1.web will be updated in-place
          ~ replicas = "1" -> "2"

Plan: 0 to add, 1 to change, 0 to destroy.
Merge-এর পরেসেই plan-ই চলে
terraform apply tfplan
পুরোনো planআসল run-এর error
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.
পরের planআসল run-এর একই বার্তা
No changes. Your infrastructure matches the configuration.
  1. Branch-এ বদল। সরাসরি production নয়, একটি branch. Diff মাত্র এক লাইন, আর সেটিই ইতিহাসে থাকবে: কে, কবে, কেন।
  2. Pull request আর CI. PR খুলতেই CI format আর syntax পরীক্ষা করে plan বানায়। -out=tfplan plan-টি একটি ফাইলে সেভ করে। CI-তে দীর্ঘস্থায়ী key-এর বদলে স্বল্পমেয়াদি পরিচয় ব্যবহার করা ভালো, যা পর্ব ৬১-এ আসবে।
  3. Review. Reviewer code-এর সাথে plan-ও পড়েন। ১টি change, ০টি destroy, প্রত্যাশামতোই। এখানে একটি অপ্রত্যাশিত -/+ দেখা গেলে merge আটকে যেত।
  4. Merge আর apply. terraform apply tfplan নতুন করে কিছু ভাবে না, অনুমোদিত সেই plan-ই চালায়। মাঝখানে ভিন্ন কিছু ঢোকার পথ বন্ধ।
  5. মাঝখানে অন্য কেউ apply করলে? State বদলে গেলে সেভ করা plan আর চলে না। আসল run-এ আমরা এটি ঘটিয়েছিলাম, আর Terraform সরাসরি আটকে দিয়েছে। এটি একটি সুরক্ষা, সমস্যা নয়। নতুন plan বানিয়ে আবার পড়তে হবে।
  6. যাচাই। Apply-এর পরে আবার plan চালালে No changes. আসা উচিত। না এলে আগে কারণ খুঁজুন, কারণ কোনো মান হয়তো প্রতি run-এ বদলাচ্ছে।

কয়েকটি নিয়ম এই পথকে নিরাপদ রাখে। .tf ফাইল আর .terraform.lock.hcl Git-এ থাকে। *.tfstate আর .terraform/ ফোল্ডার থাকে না, কারণ state-এ গোপন মান থাকতে পারে। Secret code-এ লেখা হয় না (পর্ব ৩৩)। আর production apply করার অধিকার মানুষের হাতে না রেখে pipeline-কে দিলে হাতে বদলের পথও কমে। এই Git-কেন্দ্রিক ভাবনার পরের রূপ GitOps, পর্ব ৬০-এ।

৮ · Blast radius আর plan পড়ার অভ্যাস

IaC ভুলকেও দ্রুত আর বড় করে। হাতে একটি server মুছতে কয়েকটি ক্লিক লাগে। এক লাইনের ভুল code শত resource ছুঁতে পারে। একটি stack (যে resource-গুলো একসাথে একটি state বা একটি apply-এ চলে) যত বড়, তার blast radius-ও তত বড়।

পুরো বাড়ির বিদ্যুৎ যদি একটি মাত্র সার্কিট ব্রেকারে চলে, তাহলে রান্নাঘরের একটি ত্রুটিতে সব ঘর অন্ধকার। আলাদা ব্রেকার থাকলে ক্ষতি একটি ঘরেই থামে। Stack ভাগ করা ঠিক সেই কাজ।

ধরুন network, database, app আর DNS সব একটি stack-এ, আর dev আর prod-ও একসাথে। তখন app-এর ছোট বদলের plan-এ network-ও পড়া হয়, lock-এ সবাই আটকে থাকে, আর একটি ভুল সবকিছুকে ছুঁতে পারে। সাধারণ নিয়ম:

  • Environment আলাদা: dev আর prod-এর state ও অনুমতি আলাদা (পর্ব ৫৪)।
  • বদলের গতি অনুযায়ী ভাগ: network আর database কম বদলায়, app বেশি বদলায়। তাই stack আলাদা।
  • মালিকানা অনুযায়ী ভাগ: যে দল চালায়, stack তার।
  • সীমিত অনুমতি: প্রতিটি stack-এর পরিচয়ে শুধু দরকারি অনুমতি, অর্থাৎ least privilege.

তবে খুব বেশি ভাগ করলে আবার stack-এর মধ্যে নির্ভরতা সামলানো কঠিন হয়, আর একটি কাজের জন্য অনেক PR লাগে। ভারসাম্যই আসল। Plan পড়ার অভ্যাসটি প্রতিবার এই ক্রমে করুন:

  1. শেষ লাইন আগে: Plan: x to add, y to change, z to destroy. সংখ্যাগুলো কি আপনার বদলের সাথে মেলে?
  2. সতর্ক-শব্দ খুঁজুন: destroy, must be replaced, # forces replacement.
  3. বিপজ্জনক ধরন দেখুন: database, storage, DNS, IAM, firewall.
  4. অপরিচিত পরিবর্তন খুঁজুন: আপনি যা বদলাননি, তা plan-এ এলে সেটি drift বা version বদলের চিহ্ন হতে পারে।
  5. known after apply বুঝুন: সেখানে মান এখনো অজানা, তাই plan পুরো নিশ্চয়তা দেয় না।
  6. সন্দেহ হলে থামুন: Plan বোঝা না গেলে apply নয়।

৯ · Tool-এর মানচিত্র আর IaC-র সীমা

নাম অনেক, কিন্তু প্রশ্ন কয়েকটি: কোন ভাষায় লিখবেন, কোন cloud-এ চলবে, আর state কে রাখবে?

Toolলেখার ভাষাকোথায় কাজ করেState কোথায়
TerraformHCLবহু cloud ও service, provider-এর মাধ্যমেState ফাইল, backend-এ
OpenTofuHCLTerraform-এর মতোই, Linux Foundation-এর forkState ফাইল, backend-এ
CloudFormationYAML, JSONAWSAWS নিজে stack হিসেবে রাখে
BicepBicep, যা ARM JSON-এ রূপান্তর হয়Azureআলাদা state ফাইল নেই, Azure-এর অবস্থাই ভিত্তি
PulumiPython, TypeScript, Go, C#, Javaবহু cloudনিজস্ব state, backend-এ
AWS CDKTypeScript, Python, Java, C#, GoAWS, CloudFormation-এ রূপান্তর হয়েCloudFormation stack
CrossplaneKubernetes YAML, CRDKubernetes থেকে বহু cloudCluster-এর object, অবিরাম reconcile

Google Cloud-এ Terraform খুবই প্রচলিত, আর লেখার সময়ে Google-এর Infrastructure Manager সেবাটিও Terraform-ভিত্তিক। Tool-এর সুবিধা আর licence বদলাতে থাকে, তাই বাছাইয়ের আগে সেই tool-এর বর্তমান ডকুমেন্টেশন দেখুন।

কীভাবে বাছবেন

একটি মাত্র cloud আর সেই cloud-এর নিজস্ব গভীর সুবিধা দরকার হলে native tool সহজ হতে পারে। একাধিক cloud বা SaaS, অথবা বড় কমিউনিটি মডিউল দরকার হলে Terraform বা OpenTofu. দলে general-purpose ভাষার দক্ষতা বেশি, আর লুপ বা পরীক্ষা জটিল হলে Pulumi বা CDK. Kubernetes-কেন্দ্রিক platform-এ অবিরাম reconcile চাইলে Crossplane. একটি দলে এক ধরনের কাজে একটি tool রাখুন, নইলে state-ই ভাগ হয়ে যায়।

IaC যা সমাধান করে না

IaC জাদু নয়। এই সীমাগুলো জানা থাকলে প্রত্যাশা ঠিক থাকে:

  • ভুল নকশা: ভুল architecture-কে IaC দ্রুত আর হুবহু বারবার বানায়।
  • ডেটা: Code database আবার বানাতে পারে, ভেতরের ডেটা নয়। সে জন্য পর্ব ২৪-এর backup লাগে।
  • নিরাপত্তা: 0.0.0.0/0 লেখা থাকলে tool ঠিক সেটিই বসাবে। Policy আর scan আসবে পর্ব ৫৫-এ।
  • চলমান system-এর ভেতরের কাজ: schema migration, data backfill আর ধীরে ধীরে rollout-এর জন্য আলাদা পদ্ধতি লাগে।
  • Plan-এর সীমা: Plan বলে কী বদলাবে, কিন্তু বদলের পরে app ঠিক চলবে কি না, তা বলে না। Quota বা অনুমতির সমস্যাও অনেক সময় apply-এর সময়েই ধরা পড়ে।
  • মাঝপথের ব্যর্থতা: Terraform-এর apply কোনো transaction নয়। অর্ধেক কাজের পরে থামলে যা হয়েছে, তা থেকে যায়। আবার plan করে এগোতে হয়।
  • মানুষের শৃঙ্খলা: সবাই console-এ হাত দিলে যত ভালো code-ই হোক, drift থামবে না।

আরও গভীরে: Terraform আগে state থেকে অবস্থা নিয়ে provider-এর ReadResource দিয়ে refresh করে, তারপর code-এর মান আর PlanResourceChange-এর প্রস্তাব মিলিয়ে প্রতিটি resource-এর জন্য ফল ঠিক করে। Tool-গুলোর মৌলিক ভাগ হলো দুটি: Terraform-এর মতো “চালালে চলে”, আর Kubernetes বা Crossplane-এর মতো “অবিরাম চলে”। Mark Burgess-এর convergence আর পরে Promise Theory-র কাজে এই ভাবনার তাত্ত্বিক শিকড় আছে। পড়ার জন্য ভালো বই Kief Morris-এর Infrastructure as Code (O'Reilly, প্রথম সংস্করণ ২০১৬)।

পরীক্ষা ও ইন্টারভিউতে:

  • Declarative আর imperative-এর পার্থক্য? প্রথমটিতে শেষ ফল, দ্বিতীয়টিতে ধাপ লেখা হয়; declarative tool পার্থক্য হিসাব করে নিজে ধাপ ঠিক করে।
  • Idempotency কী? একই কাজ বারবার করলেও ফল একবার করার সমান থাকা; দ্বিতীয় apply-এ No changes.
  • Terraform state কেন লাগে? Code-এর নামের সাথে আসল resource মেলাতে, আর code থেকে সরানো জিনিস চিনতে।
  • Drift কীভাবে ধরবেন? Schedule-করা terraform plan -detailed-exitcode; exit code 2 মানে পার্থক্য আছে।
  • -/+ আর +/--এর পার্থক্য? প্রথমটি আগে মোছে, দ্বিতীয়টি আগে বানায়, যা create_before_destroy দিয়ে হয়।

১০ · AI-এর যুগে IaC

AI কয়েক সেকেন্ডে শত লাইন HCL লিখে দিতে পারে, আর দেখতে তা নিখুঁত লাগে। কিন্তু AI আপনার state, আসল account বা বিল দেখতে পায় না। IaC-তে ভুলের ফল একটি resource-এ থামে না, তাই এখানে যাচাই আরও জরুরি।

AI-এর সাধারণ ভুলফলযাচাই
Argument বা resource type বানিয়ে লেখাvalidate-এ error, বা ভুল আচরণProvider-এর ডকুমেন্টেশন, version মিলিয়ে
IAM-এ *, firewall-এ 0.0.0.0/0অকারণে বড় অনুমতিপ্রতিটি অনুমতির কারণ জিজ্ঞেস করা
Storage public, secret কোডে লেখাডেটা বা key ফাঁসScan আর হাতে পড়া
Provider version না বাঁধা, image-এ latestপরে হঠাৎ আচরণ বদলVersion pin আর lock file
নাম বদলের পরামর্শ, যা replace ঘটায়Database মুছে নতুনPlan-এ # forces replacement খোঁজা
সমস্যা মেটাতে terraform destroy বা state মোছাআসল জিনিস হারানো, বা হিসাব হারানোনিজে না বুঝে কখনো না চালানো
খরচ উপেক্ষাবড় instance, বাড়তি NAT বা diskPricing page আর budget alert

নিচের prompt-এ code নয়, plan পড়তে সাহায্য চাওয়া হয়েছে। State ফাইল আর secret কখনো paste করবেন না, আর plan-এ গোপন বা account-এর তথ্য থাকলে আগে মুছে নিন।

তুমি একজন সতর্ক Terraform reviewer. নিচে একটি plan-এর আউটপুট দিলাম (গোপন মান মুছে দেওয়া আছে)। আমার উদ্দেশ্য ছিল শুধু [আপনার উদ্দেশ্য লিখুন]। [plan আউটপুট পেস্ট করুন] ১. কোন কোন resource মুছবে বা replace হবে, আর কোন লাইনের কারণে? ২. আমার উদ্দেশ্যের বাইরে কোনো পরিবর্তন আছে কি? ৩. ডেটা হারানো বা downtime-এর ঝুঁকি কোথায়? ৪. কোনো provider argument নিয়ে নিশ্চিত না হলে সরাসরি বলো, আন্দাজে বলো না। ৫. কোনো destructive কমান্ড চালাতে বলো না।

AI-এর লেখা IaC হাতে পেলে — যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।

প্র ০১Terraform-এর state ফাইল আছে, Bicep-এর নেই। আলাদা state রাখা কি ভালো নকশা, না একটি বোঝা?

দুটোই, আর এখানেই নকশার আসল trade-off.

কেন Terraform-এর state লাগে: Terraform বহু ধরনের API-র ওপর কাজ করে। সব API “এই project-এর সব জিনিস দাও” ধরনের নির্ভরযোগ্য উত্তর দেয় না, আর সব জিনিসে মালিকের চিহ্নও বসানো যায় না। তাই সে নিজের খাতা রাখে: কোন নাম কোন আসল id. এই খাতা থাকায় code থেকে সরানো resource চেনা যায়, dependency-র তথ্য টিকে থাকে, আর বড় সংগ্রহে হিসাব রাখা সহজ হয়।

দাম: State একটি নতুন গুরুত্বপূর্ণ জিনিস। হারালে Terraform ভুলে যায় সে কী বানিয়েছিল। দুজন একসাথে লিখলে নষ্ট হতে পারে, তাই lock লাগে। ভেতরে গোপন মান থাকতে পারে, তাই encryption আর অনুমতি লাগে। আর খাতা আসল অবস্থার সাথে না মিললে refresh, import আর state mv-এর মতো কাজ শিখতে হয়।

Bicep আর ARM-এর পথ: Azure Resource Manager নিজেই সব resource-এর একটি কেন্দ্রীয় হিসাব রাখে। Deployment-এর সময় সে আসল অবস্থার সাথে template মেলায়। তাই আলাদা ফাইল সামলাতে হয় না। কিন্তু এই সুবিধা এসেছে একটি একক platform-এর নিয়ন্ত্রণ থেকে। Code থেকে সরানো জিনিস মোছা ধরনের কাজ তখন আলাদা সুবিধা দিয়ে সামলাতে হয়, যেমন Azure-এর deployment mode বা deployment stack. CloudFormation-ও state নিজের ভেতরে রাখে, তবে সেই tool শুধু AWS-এর জন্য।

বৃহত্তর ছবি: Kubernetes আর Crossplane-এ etcd-ই সেই খাতা। সবাই কোথাও না কোথাও একটি রেকর্ড রাখে। প্রশ্ন হলো, সেটি কে সামলায়: আপনি, না platform?

মূল উপলব্ধি: বহু-provider স্বাধীনতার দাম হলো state-এর দায়িত্ব নিজের হাতে নেওয়া। একক platform সেই দায় সরিয়ে নেয়, কিন্তু আপনাকে তার সীমার ভেতরে রাখে।

প্র ০২Kubernetes অবিরাম reconcile করে। Terraform-কেও প্রতি পাঁচ মিনিটে apply -auto-approve করলে সমস্যা কোথায়?

শুনতে যৌক্তিক লাগে: drift নিজে থেকে মিটে যাবে। কিন্তু দুটি system-এর বদলের ধরন আলাদা।

বদলের ওজন: Kubernetes-এর controller একটি Pod মরলে আরেকটি বানায়। এই কাজ ছোট, সস্তা, আর ফেরানো যায়। Terraform-এর plan-এ থাকতে পারে একটি database replace বা network মোছা। মানুষের চোখ ছাড়া সেটি চালানো মানে একটি ভুল বদল পাঁচ মিনিটের মধ্যে production-এ যাওয়া।

আমাদের drift-এর উদাহরণ: রাত দুইটায় replica ৩ করা ছিল ঠিক সিদ্ধান্ত। স্বয়ংক্রিয় apply সেটিকে পাঁচ মিনিট পরে ১-এ নামিয়ে দিত, আর incident আরও খারাপ হতো। Tool দেখে পার্থক্য, কিন্তু মানে বোঝে না।

API-র খরচ: হাজার resource-এর refresh মানে হাজার API call, যা rate limit-এ আটকাতে পারে। লম্বা apply চলাকালে lock-ও ধরা থাকে, তাই অন্য কাজ আটকায়।

তাহলে অবিরাম মডেল কি ভুল? না। ছোট ঝুঁকির বদলে এটি দারুণ, আর GitOps tool সেটিই করে (পর্ব ৬০)। Crossplane আবার cloud resource-কেও Kubernetes-এর মতো অবিরাম reconcile করে। তবে সেখানেও দলগুলো সাধারণত কিছু resource-কে সরাসরি মোছা থেকে রক্ষা করে, আর হাতে বদল ঠেকাতে অনুমতি কমায়।

ব্যবহারিক মাঝপথ: Schedule-করা plan চালান, কিন্তু apply নয়। পার্থক্য পেলে alert বা একটি ticket তৈরি হোক, তারপর মানুষ সিদ্ধান্ত নিক। আর হাতে বদলের পথ যতটা সম্ভব বন্ধ রাখুন।

মূল উপলব্ধি: স্বয়ংক্রিয় reconcile তখনই নিরাপদ, যখন প্রতিটি কাজ ছোট আর ফেরানো যায়। কাজ যত ভারী আর অপরিবর্তনীয়, মানুষের অনুমোদন তত মূল্যবান।

প্র ০৩Terraform-এর licence বদল থেকে একটি দল কী শিখবে? OpenTofu-তে যাওয়া কি জরুরি?

ঘটনাটি আগে পরিষ্কার করি। Terraform বহু বছর MPL ২.০ licence-এ ছিল, আর এর চারপাশে বিশাল provider আর module-এর জগৎ গড়ে উঠেছিল। ২০২৩ সালের আগস্টে HashiCorp তাদের প্রধান পণ্যগুলোকে Business Source License ১.১-এ নিয়ে যায়। BUSL-কে সাধারণত OSI-এর সংজ্ঞায় open source ধরা হয় না, কারণ এতে ব্যবহারে সীমা আছে। মূল সীমাটি HashiCorp-এর প্রতিযোগী পণ্যের ওপর। এরপর কয়েকটি কোম্পানি ও সম্প্রদায় একটি fork শুরু করে, যা OpenTofu নামে Linux Foundation-এ যায়। পরে IBM HashiCorp-কে কিনে নেয় (ঘোষণা ২০২৪, সম্পন্ন ২০২৫)।

আপনার দলের ওপর প্রভাব: যে দল নিজের infrastructure চালাতে Terraform ব্যবহার করে, তাদের জন্য সাধারণত এই বদল সরাসরি নিষেধ আনে না। ঝুঁকি বেশি তাদের, যারা Terraform-কে ঘিরে একটি বাণিজ্যিক সেবা বানায়। তবে এটি আইনি প্রশ্ন, তাই বড় প্রতিষ্ঠানে licence-এর আসল লেখা আর আইনজীবীর মত দেখা উচিত।

যাওয়া কি জরুরি? না, তবে বিকল্প জানা দরকার। লেখার সময়ে দুটি tool-এ HCL আর workflow অনেকটা এক, তবে সময়ের সাথে কিছু সুবিধা আলাদা হয়েছে। যেমন OpenTofu state encryption যোগ করেছে। তাই সিদ্ধান্ত নিন আপনার বর্তমান version, CI আর provider-এর সামঞ্জস্য পরীক্ষা করে, শুধু খবর পড়ে নয়।

বড় শিক্ষা: Tool-এর licence, মালিকানা আর দাম বদলাতে পারে। MongoDB, Elastic আর Redis-এর licence বদলের ইতিহাসও একই কথা বলে। তাই সিদ্ধান্তের সময় “এই tool ছাড়তে হলে কত খরচ?” প্রশ্নটি করুন। Module-গুলো পরিষ্কার রাখা, version বাঁধা আর ধারণা শেখা, এগুলোই lock-in কমায়।

মূল উপলব্ধি: Tool বদলায়, ধারণা থাকে। Desired state, plan, drift আর review বুঝলে আপনি যেকোনো tool-এ দ্রুত কাজ করতে পারবেন।

প্র ০৪বাস্তবের বড় ঘটনাগুলো IaC আর ছোট blast radius সম্পর্কে কী শেখায়?

দুটি প্রকাশ্য ঘটনা দেখা যাক। কোনোটিই সরাসরি Terraform-এর ঘটনা নয়, কিন্তু দুটিই এই পর্বের শিক্ষার সাথে মেলে।

Knight Capital, ২০১২: মার্কিন SEC-এর ২০১৩ সালের নথি অনুযায়ী, প্রতিষ্ঠানটি হাতে করে নতুন code আটটি server-এ বসাচ্ছিল। একজন technician একটি server-এ কপি করতে ভুলে যান। দ্বিতীয় কোনো মানুষ বা স্বয়ংক্রিয় পরীক্ষা সেটি ধরেনি। বাজার খোলার পরে সেই একটি server পুরোনো, অব্যবহৃত logic চালাতে শুরু করে, আর ৪৫ মিনিটের মতো সময়ে কয়েকশো মিলিয়ন ডলারের ক্ষতি হয়। এটি drift-এর একটি চরম রূপ: আটটি server-এর একটি আলাদা ছিল, আর কেউ জানত না। এখানে দুইবার দেখার ব্যবস্থা আর স্বয়ংক্রিয় deploy থাকলে ঝুঁকি অনেক কমত।

Amazon S3, ফেব্রুয়ারি ২০১৭: AWS-এর প্রকাশিত সারাংশ অনুযায়ী, একজন অনুমোদিত কর্মী একটি নির্ধারিত প্রক্রিয়ায় কয়েকটি server সরানোর কমান্ড দিচ্ছিলেন, আর ইনপুটের ভুলে অনেক বেশি server সরে যায়। ফলে একটি region-এ বড় সমস্যা হয়। পরে AWS জানায়, tool-এ এমন বদল আনা হচ্ছে যাতে capacity ধীরে সরে, আর ন্যূনতম সীমার নিচে নামানো না যায়।

দুটি শিক্ষা: প্রথমত, মানুষের নির্ভুলতার ওপর নির্ভর করবেন না। ভুল হবেই, তাই পথটি এমন বানান যাতে দ্বিতীয় চোখ আর স্বয়ংক্রিয় যাচাই থাকে। দ্বিতীয়ত, tool ক্ষমতা বাড়ায়, তাই ভুলের সীমাও বাড়ায়। Automation মানেই নিরাপদ নয়। একটি ভুল কমান্ড একটি স্ক্রিপ্টে থাকলে আরও দ্রুত ছড়ায়। সে কারণেই plan, ছোট stack, ধীরে rollout আর prevent_destroy-এর মতো সীমা দরকার।

মূল উপলব্ধি: ভালো ব্যবস্থা মানুষকে নিখুঁত হতে বলে না। সে ধরে নেয় ভুল হবে, আর সেই ভুল যেন ছোট থাকে, দ্রুত ধরা পড়ে, আর ফেরানো যায়।

প্র ০৫Declarative কি সব সময় imperative-এর চেয়ে ভালো? Pulumi বা CDK-এর মতো সাধারণ ভাষার tool কেন এলো?

না। Declarative একটি খুব ভালো default, কিন্তু সর্বজনীন উত্তর নয়। প্রথমে একটি ভুল ধারণা সরাই: যেকোনো declarative tool-এর ভেতরে একটি imperative engine থাকে। Terraform শেষে API-কে ধাপে ধাপে ডাকে। Declarative মানে ধাপ নেই তা নয়, মানে ধাপ ঠিক করার দায়িত্ব tool নিয়েছে।

যেখানে declarative সেরা: শেষ অবস্থা সহজে বর্ণনা করা যায়, আর ক্রম থেকে ফল নির্ভর করে না। Network, firewall rule, IAM আর DNS এর উদাহরণ।

যেখানে এটি কষ্ট দেয়: যেখানে পথটাই গুরুত্বপূর্ণ। Database-এর schema migration ভাবুন। “শেষে এই column থাকবে” বলা সহজ, কিন্তু কোটি row না থামিয়ে সেখানে পৌঁছানোর ধাপগুলোই আসল কাজ। Blue-green traffic সরানো বা ক্রমে region বদলানোও এমন। Ansible-এর playbook এজন্যই ক্রমিক, যদিও তার module-গুলো সাধারণত idempotent.

সাধারণ ভাষা কেন এলো: HCL ইচ্ছে করেই সীমিত, যাতে পড়া সহজ আর plan নির্ভরযোগ্য হয়। কিন্তু বড় প্রতিষ্ঠানে লুপ, শর্ত, ভাগ করা library আর unit test দরকার হয়। Pulumi এবং AWS CDK তাই Python বা TypeScript দেয়। তবে লক্ষ করুন, এগুলোও শেষে declarative: কোড চালালে একটি কাঙ্ক্ষিত অবস্থার বর্ণনা তৈরি হয়, আর engine সেটি মেলায়। CDK তৈরি করে CloudFormation template. অর্থাৎ বদলেছে শুধু লেখার ভাষা, মডেল নয়।

দাম: সাধারণ ভাষায় লেখা জটিল abstraction পড়া কঠিন হতে পারে। Reviewer-কে কোড চালিয়ে দেখতে হয় আসলে কী তৈরি হবে। তাই plan বা synthesized template দেখাই তখনও আসল review.

মূল উপলব্ধি: প্রশ্নটি declarative বনাম imperative নয়। প্রশ্ন হলো, এই কাজের গুরুত্ব শেষ অবস্থায়, না পথে? শেষ অবস্থায় হলে declarative, পথে হলে সাবধানে লেখা idempotent ধাপ।

অনুশীলন

প্রথম তিনটিতে কিছু ইনস্টল করতে হবে না। চতুর্থটি ঐচ্ছিক, আর তাতে কোনো cloud account লাগে না।

  1. Plan-এর হিসাব: একটি stack-এ এখন ৮টি resource আছে। নতুন code-এ: ৩টি নতুন resource, ২টির মান জায়গায় বদল, ১টির নাম বদল যা replacement ঘটায়, ১টি code থেকে মোছা, বাকি ৪টি অপরিবর্তিত। (ক) Plan-এর summary লাইনটি লিখুন। (খ) Apply-এর পরে state-এ কয়টি resource থাকবে?

    (ক) Add = ৩ নতুন + ১ replace = ৪। Change = ২। Destroy = ১ মোছা + ১ replace = ২। তাই লাইনটি: Plan: 4 to add, 2 to change, 2 to destroy.

    (খ) ৮ − ১ (মোছা) + ৩ (নতুন) = ১০। Replace-এর সংখ্যা বদলায় না, কারণ একটি যায়, একটি আসে। হিসাব মিলিয়ে দেখুন: ৩ + ২ + ১ + ৪ = ১০।

  2. ফল আগে বলুন: Code-এ replicas = 2, আর apply শেষ। এবার পরপর চলল: kubectl scale deployment web --replicas=5, তারপর terraform plan -detailed-exitcode. বলুন, (ক) plan-এর গুরুত্বপূর্ণ লাইন, (খ) exit code, (গ) plan-এর পরে cluster-এ replica কত, আর state ফাইলে কত, (ঘ) এরপর শুধু code-এ replicas = 5 করে আবার plan চালালে কী আসবে?

    (ক) ~ replicas = "5" -> "2" আর Plan: 0 to add, 1 to change, 0 to destroy.

    (খ) 2, কারণ পার্থক্য আছে।

    (গ) Cluster-এ ৫, state ফাইলে ২। Plan কিছুই বদলায় না।

    (ঘ) No changes. কারণ code আর আসল অবস্থা এখন মিলে গেছে। পরের apply-এ state-ও হালনাগাদ হবে।

  3. নকশা সমালোচনা: একটি দলের সব কিছু, অর্থাৎ dev আর prod-এর network, database, app আর DNS, এক repository-র একটি state-এ। সবাই নিজের ল্যাপটপ থেকে admin key দিয়ে terraform apply -auto-approve চালান। State ফাইল Git-এ commit করা। ঝুঁকিগুলো ধরুন আর একটি ভালো নকশা প্রস্তাব করুন।

    ঝুঁকি: blast radius বিশাল, কারণ dev-এর বদল prod-কে ছুঁতে পারে। -auto-approve মানে কেউ plan পড়ে না। Admin key ল্যাপটপে থাকলে চুরির ঝুঁকি আর least privilege ভঙ্গ। Git-এ state মানে গোপন মান ইতিহাসে চিরকাল, আর দুজন একসাথে চালালে state নষ্ট হবে, কারণ lock নেই।

    ভালো নকশা: Environment অনুযায়ী আলাদা state আর অনুমতি। Network, data আর app-এর stack আলাদা। Remote backend-এ lock আর encryption. PR-এ CI plan, reviewer অনুমোদন, merge-এর পরে pipeline saved plan চালাবে। মানুষের কাছে production-এ লেখার সাধারণ অনুমতি থাকবে না, শুধু জরুরি অবস্থার একটি নথিভুক্ত পথ। আর schedule-করা drift পরীক্ষা।

  4. হাতে করে দেখুন (ঐচ্ছিক, খরচ নেই): Terraform বা OpenTofu ইনস্টল থাকলে একটি খালি ফোল্ডারে local_file-এর main.tf রাখুন, তারপর terraform init, terraform apply দুইবার চালান। এবার site/app.conf হাতে বদলে terraform plan চালান। কী এলো? শেষে পরিষ্কার করতে terraform destroy চালান।

    আমাদের run-এ ফাইলে port=9090 লেখার পরে plan বলেছে # local_file.app_conf will be created আর Plan: 1 to add, 0 to change, 0 to destroy. Provider দেখে ফাইলের content আর মেলে না, তাই resource-কে নেই ধরে নতুন করে লেখে। Apply-এর পরে ফাইলে আবার port=8080. এই অভ্যাসটি নিরাপদ, কারণ সব কিছু আপনার নিজের ফোল্ডারেই ঘটে। hashicorp/local provider-এর version ভেদে আউটপুট কিছুটা আলাদা হতে পারে।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমএকটি infrastructure বদলের নিরাপদ পথ সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. Branch-এ code বদল
  2. Pull request খোলা
  3. CI-তে validate আর plan -out
  4. Reviewer-এর plan পড়া ও অনুমোদন
  5. Merge-এর পরে সেভ করা plan apply
  6. আবার plan চালিয়ে No changes যাচাই

একটি shell script প্রতিবার চালালে একটি config ফাইলে একই লাইন আরও একবার যোগ হয়। Script-টির কোন গুণ নেই?

  • Portability
  • Idempotency
  • Concurrency
  • Encryption

Terraform-এর summary এলো: Plan: 2 to add, 1 to change, 2 to destroy. এর সবচেয়ে সতর্ক ব্যাখ্যা কোনটি?

  • অবশ্যই ৫টি আলাদা resource ছোঁয়া হবে
  • Plan চলার সময়েই ২টি resource মুছে গেছে
  • কিছু replace থাকতে পারে, যা add আর destroy দুটিতেই গোনা হয়; কোনটি মুছবে পড়ে দেখতে হবে
  • Add বেশি হলে destroy নিয়ে ভাবার দরকার নেই

Terraform-এ code-এ replicas = 1, কিন্তু কেউ kubectl scale দিয়ে ৩ করেছেন। Apply করার আগে কী করা সবচেয়ে ঠিক?

  • কেন বদলানো হয়েছিল জেনে সিদ্ধান্ত নেওয়া: ফিরিয়ে দেওয়া, না code-এ গ্রহণ করা
  • সঙ্গে সঙ্গে apply করে ১-এ ফিরিয়ে দেওয়া
  • State ফাইল মুছে দেওয়া
  • কিছু না করা, Terraform নিজেই ঠিক করে নেবে

Plan-এ একটি লাইন দেখা গেল: ~ name = "a" -> "b" # forces replacement. এর অর্থ কী?

  • নাম জায়গায় বদলে যাবে, কিছু মুছবে না
  • Code-এ একটি syntax error আছে
  • এটি একটি মন্তব্য, উপেক্ষা করলেই হয়
  • এই field-এর বদলে resource মুছে নতুন করে বানাতে হবে

Terraform একটি resource-কে code থেকে সরিয়ে দিলে সেটি মুছতে হবে, এটি কীভাবে জানে?

  • Code পড়ে জানতে পারে
  • State-এ নামটি আছে, code-এ নেই, এই অমিল থেকে
  • পুরো cloud account খুঁজে দেখে
  • Git-এর আগের commit পড়ে

একটি resource A-এর মান resource B ব্যবহার করে (B নির্ভর করে A-এর ওপর)। Terraform কোন ক্রমে কাজ করবে?

  • ফাইলে যেটি আগে লেখা, সেটি আগে
  • তৈরি আর মোছা দুটিই সব সময় একসাথে
  • তৈরিতে A আগে, মোছায় B আগে
  • তৈরিতে B আগে, মোছায় A আগে

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. ClickOps-এ রেকর্ড, review আর পুনরাবৃত্তি নেই; IaC তিনটিই দেয়।
  2. Declarative tool শেষ ফল থেকে পার্থক্য হিসাব করে; idempotent হলে দ্বিতীয় apply-এ No changes.
  3. তিন অবস্থা: desired, recorded, real. Plan হলো পার্থক্যের তালিকা, আর replace add ও destroy দুটিতেই গোনা হয়।
  4. Drift plan ধরে, কিন্তু ফিরিয়ে দেওয়া না গ্রহণ করা, সেই সিদ্ধান্ত মানুষের।
  5. Git, review, saved plan আর ছোট stack মিলে blast radius কমে। Plan না পড়ে apply নয়।

পরের পর্বে: ধারণা তৈরি। এবার Terraform হাতে নেব: provider, resource, variable, init, plan আর apply কীভাবে একসাথে কাজ করে।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান