এই পর্বে যা শিখবেন
- কোন ভুল কোন ধাপে ধরা পড়ে, আর কোন ধাপ কত সময় ও খরচ নেয়
validation,precondition,postconditionআরcheck-এর পার্থক্য, প্রতিটির আসল ব্যর্থতাসহterraform testদিয়ে mock আর আসল, দুই ধরনের পরীক্ষা লেখা- Plan JSON পড়ে নিজের policy script লেখা, আর OPA, Sentinel ও scanner কোথায় বসে তা বোঝা
- CI-তে plan comment, approval, saved plan, destroy সুরক্ষা আর খরচের হিসাব মিলিয়ে একটি গেট বানানো
১ · “Plan সবুজ” মানেই নিরাপদ নয়
একটি কাল্পনিক অনলাইন বাজার অ্যাপের কথা ভাবুন। এক শুক্রবার দুপুরে একজন প্রকৌশলী একটি ছোট pull request খুললেন। বদল মাত্র একটি লাইন: database-এর একটি setting. Plan চলল, কোনো error নেই। সবার চোখ গেল শেষ লাইনে, 1 to add, 0 to change, 1 to destroy. কেউ খেয়াল করলেন না যে এই “১টি destroy” আসলে orders database.
Terraform-এর ভাষায় code ঠিক ছিল। ব্যাকরণ ঠিক, provider খুশি, plan তৈরি। ভুলটি ছিল অর্থে। IaC-র পরীক্ষা ঠিক এই ফাঁক ভরে: code শুধু “চলে কি না” নয়, “যা করবে তা আমরা চাই কি না”।
সদরঘাটে একটি লঞ্চ ছাড়ার আগে একটিমাত্র যাচাই হয় না। খাতায় যাত্রীর হিসাব মেলানো হয়, ওজন দেখা হয়, ফিটনেসের কাগজ দেখা হয়, আবহাওয়ার সংকেত দেখা হয়, শেষে একজন দায়িত্বশীল মানুষ ছাড়পত্র দেন। প্রতিটি যাচাই আলাদা ভুল ধরে। কাগজ ঠিক থাকলেও ঝড়ের সংকেত থাকতে পারে। IaC-র পরীক্ষাও এমন স্তরে স্তরে সাজানো।
পরীক্ষার মই
প্রতিটি ধাপ আগের ধাপের চেয়ে ধীর ও দামি, কিন্তু আরও বাস্তব ভুল ধরে। তাই সস্তা ধাপ আগে চালানো হয়, যাতে ভুল যত দ্রুত সম্ভব ধরা পড়ে। এই চিন্তাকে বলে shift left.
পরীক্ষার মই: গতি বনাম ভরসা
The IaC testing ladder, fastest at the bottom
| ধাপ | কী ধরে | কী ধরে না | যা লাগে |
|---|---|---|---|
fmt | এলোমেলো লেখা | অর্থের কিছুই নয় | কিছু না |
validate | ভুল argument, ভুল type | আসল সিস্টেমের অবস্থা | Provider schema |
| Static analysis | ঝুঁকিপূর্ণ setting, অচল মান | চলার সময়ে ঠিক হওয়া মান | Scanner |
| Plan ও policy | কী বদলাবে, নিয়ম ভাঙে কি না | Provider-এর চলার সময়ের ব্যর্থতা | State, read access |
| Unit test | আপনার module-এর যুক্তি | আসল API-র আচরণ | Mock |
| Integration test | সত্যিই তৈরি হয় কি না | Production-এর চাপ | আসল account, খরচ |
| Drift check | পরে হাতে করা বদল | নতুন code-এর ভুল | নিয়মিত সূচি |
এই পর্বের সব Terraform আউটপুট একটি আসল run থেকে নেওয়া: Terraform ১.১৭, local আর kubernetes provider, একটি স্থানীয় single-node cluster. কোনো cloud account ছোঁয়া হয়নি। ID, hash আর সময় আপনার run-এ আলাদা হবে। AWS, Azure বা Google Cloud-এর code থাকলে সেটি চালানো হয়নি, আর সেখানে তা লেখা আছে।
২ · সবচেয়ে সস্তা তিন ধাপ: fmt, validate আর scanner
ধরুন AI একটি ছোট file resource লিখে দিল। লেখা এলোমেলো, আর দুটি argument দেখতে খুব বিশ্বাসযোগ্য। প্রথম ধাপ terraform fmt -check. এটি কিছু বদলায় না, শুধু জানায়। আসল run:
$ terraform fmt -check -diff
main.tf
--- old/main.tf
+++ new/main.tf
@@ -8,8 +8,8 @@
}
resource "local_file" "banner" {
- filename="banner.txt"
- content = "welcome"
+ filename = "banner.txt"
+ content = "welcome"
file_mode = "0640"
- owner = "deploy"
+ owner = "deploy"
}
$ echo $?
3Exit code শূন্য নয়, তাই CI-তে এই ধাপ লাল হবে। সমাধান একটিই: terraform fmt চালিয়ে commit করা। এতে review-তে স্থান আর লাইন মেলানো নিয়ে কথা বলতে হয় না। মনে রাখুন, fmt-এর চোখে file_mode একদম ঠিক। সে শুধু লেখার ধরন দেখে।
পরের ধাপ terraform validate. এটি provider-এর schema-র সাথে মেলায়, কিন্তু কোনো cloud API ডাকে না। তাই CI-তে terraform init -backend=false-এর পর credential ছাড়াই চলে।
$ terraform validate
Error: Unsupported argument
on main.tf line 13, in resource "local_file" "banner":
13: file_mode = "0640"
An argument named "file_mode" is not expected here.
Error: Unsupported argument
on main.tf line 14, in resource "local_file" "banner":
14: owner = "deploy"
An argument named "owner" is not expected here.
$ echo $?
1AI-এর একটি পরিচিত ভুল এখানেই ধরা পড়ল। local_file-এ file_mode বা owner নেই। আসল argument-টি file_permission. ভুল argument এই ধাপে খরচ ছাড়াই আটকে যায়। কিন্তু validate সবুজ হলেও এটুকুই জানা গেল: ব্যাকরণ ঠিক। Bucket সবার জন্য খোলা কি না, সে তা বলবে না।
Static analysis: code পড়ে ঝুঁকি খোঁজা
Static analysis মানে কিছু না চালিয়ে শুধু লেখা পড়ে সমস্যা খোঁজা। IaC-তে এর তিনটি পরিচিত নাম আছে।
tflint
tflint একটি linter. Provider plugin দিয়ে সে অচল instance type-এর মতো ভুলও ধরতে পারে, যা validate ধরে না।
Checkov
Checkov ঝুঁকিপূর্ণ setting খোঁজে। HCL পড়তে পারে, plan JSON-ও পড়তে পারে। Bridgecrew-এ শুরু, পরে Palo Alto Networks-এর হাতে আসে।
নিরাপত্তা নিয়মTrivy config
Trivy মূলত image scanner, কিন্তু trivy config IaC-ও দেখে। পুরোনো tfsec-এর কাজ Aqua Security এখানে নিয়ে এসেছে।
এই তিনটি tool এই পর্ব লেখার সময় চালানো হয়নি। নিচের দুটি ব্লক আসল run নয়, শুধু ফরম্যাট দেখানোর নমুনা। Rule ID, লেখা আর সাজানো version-ভেদে আলাদা হতে পারে।
$ checkov -f plan.json --framework terraform_plan --compact
Passed checks: 7, Failed checks: 1, Skipped checks: 0
Check: CKV_AWS_20: "S3 Bucket has an ACL defined which allows public READ access."
FAILED for resource: aws_s3_bucket.assets$ trivy config --severity HIGH,CRITICAL .
main.tf (terraform)
Tests: 12 (SUCCESSES: 11, FAILURES: 1)
Failures: 1 (HIGH: 1, CRITICAL: 0)
AVD-AWS-0086 (HIGH): No public access block so not blocking public aclsদুটি নমুনাই একই ধরনের কথা বলছে, যা পর্ব ৩৪-এর misconfiguration-এর গল্প। Scanner-এর দুর্বলতাও জানা দরকার। কিছু মান apply-এর আগে অজানা থাকে, তাই HCL-এ ঠিক দেখালেও ফল ভুল হতে পারে। এজন্য plan JSON-এর ওপরেও চালানো ভালো। আবার scanner অনেক সময় ভুল সতর্কতা দেয়। প্রতিটি skip-এর কারণ লিখে রাখা তাই জরুরি।
৩ · HCL-এর ভেতরের চার পাহারাদার
Terraform-এর HCL ভাষার ভেতরেই নিয়ম লেখা যায়। নিয়মগুলো code-এর পাশে থাকে, তাই module যিনিই ব্যবহার করুন, নিয়ম সাথে যায়। চারটি উপায় আছে, আর পার্থক্য হলো কখন চলে আর ব্যর্থ হলে কী হয়।
| যাচাই | কী দেখে | ব্যর্থ হলে | প্রথম এসেছে |
|---|---|---|---|
validation | ইনপুট variable | Error, run থামে | ০.১৩ (২০২০) |
precondition | Resource তৈরির আগের অনুমান | Error, ওই resource তৈরি হয় না | ১.২ (২০২২) |
postcondition | তৈরি বা পড়ার পরের ফল | Error, তবে বদল হয়ে যেতে পারে | ১.২ (২০২২) |
check | সার্বিক অবস্থা | শুধু warning, run চলে | ১.৫ (২০২৩) |
একটি ছোট configuration-এ চারটিই বসিয়েছি। নিচের অংশগুলো পুরো ফাইল থেকে নেওয়া, আর ফাইলটি fmt -check ও validate পাস করেছে।
variable "env" {
type = string
default = "dev"
}
variable "replicas" {
type = number
default = 2
validation {
condition = var.replicas >= 1 && var.replicas <= 6
error_message = "replicas must be between 1 and 6."
}
}
# Data source with a postcondition: the namespace must carry a team label.
data "kubernetes_namespace_v1" "target" {
metadata {
name = "abcl-tp"
}
lifecycle {
postcondition {
condition = can(self.metadata[0].labels["team"])
error_message = "Namespace needs a team label."
}
}
}
resource "local_file" "app_config" {
filename = "${path.module}/app.conf"
content = "env=${var.env}\nreplicas=${var.replicas}\n"
lifecycle {
precondition {
condition = var.env != "prod" || var.replicas >= 2
error_message = "prod needs at least 2 replicas."
}
}
}
# check block: a failed assert only warns, it never stops the run.
check "namespace_has_quota" {
data "kubernetes_resources" "quotas" {
api_version = "v1"
kind = "ResourceQuota"
namespace = "abcl-tp"
}
assert {
condition = length(data.kubernetes_resources.quotas.objects) > 0
error_message = "Namespace abcl-tp has no ResourceQuota."
}
}এবার প্রতিটিকে ইচ্ছা করে ভাঙলাম। লম্বা আউটপুট থেকে অপ্রাসঙ্গিক লাইন বাদ দিয়ে … রেখেছি।
$ terraform plan -var replicas=9
…
Error: Invalid value for variable
on main.tf line 24:
24: variable "replicas" {
├────────────────
│ var.replicas is 9
replicas must be between 1 and 6.
This was checked by the validation rule at main.tf:28,3-13.
$ terraform plan -var env=prod -var replicas=1
…
Error: Resource precondition failed
on main.tf line 54, in resource "local_file" "app_config":
54: condition = var.env != "prod" || var.replicas >= 2
├────────────────
│ var.env is "prod"
│ var.replicas is 1
prod needs at least 2 replicas.
$ terraform plan # namespace-এ team label নেই
…
Error: Resource postcondition failed
on main.tf line 42, in data "kubernetes_namespace_v1" "target":
42: condition = can(self.metadata[0].labels["team"])
├────────────────
│ self.metadata[0].labels is map of string with 1 element
Namespace needs a team label.
$ terraform apply -auto-approve -var env=prod -var replicas=2
…
Warning: Check block assertion failed
on main.tf line 69, in check "namespace_has_quota":
69: condition = length(data.kubernetes_resources.quotas.objects) > 0
├────────────────
│ data.kubernetes_resources.quotas.objects is empty tuple
Namespace abcl-tp has no ResourceQuota.
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
$ echo $?
0প্রথম তিনটিতে exit code ১. শেষটিতে warning এলেও exit code ০, আর ফাইল তৈরি হয়ে গেছে। এটাই check-এর চরিত্র: “জানিয়ে রাখলাম, আটকালাম না।” তাই এটি চলমান সিস্টেমের স্বাস্থ্য দেখার জন্য ভালো, কিন্তু কঠোর নিয়মের জন্য নয়।
Postcondition-এর একটি ফাঁদ
Resource-এর কোনো মান apply-এর পরেই জানা গেলে postcondition চলে সেই পরে। ততক্ষণে জিনিসটি তৈরি হয়ে গেছে। একটি Service-এ ইচ্ছা করে ভুল IP-পরিসর চাইলাম:
$ terraform apply -auto-approve
…
kubernetes_service_v1.api: Creating...
kubernetes_service_v1.api: Creation complete after 0s [id=abcl-tp/pc-api]
Error: Resource postcondition failed
on main.tf line 28, in resource "kubernetes_service_v1" "api":
28: condition = startswith(self.spec[0].cluster_ip, "192.168.")
├────────────────
│ self.spec[0].cluster_ip is "10.110.80.18"
Service IP is outside the expected range.
$ terraform state list
kubernetes_service_v1.apiExit code ১, কিন্তু Service আছে, আর সেটি state-এও আছে। এরপর সাধারণ plan-ও একই error-এ থেমেছে। অর্থাৎ postcondition কোনো rollback নয়। সে শুধু পরের ধাপকে থামায়। ব্যর্থ হলে জিনিসটি ঠিক করা বা destroy করার দায় আপনার।
৪ · terraform test: HCL দিয়েই HCL-এর পরীক্ষা
terraform test আসে ১.৬ সংস্করণে (২০২৩), আর mock_provider আসে ১.৭-এ (২০২৪)। Test লেখা হয় .tftest.hcl ফাইলে। প্রতিটি run ব্লক একবার plan বা apply চালিয়ে assert মেলায়। Go শেখার দরকার পড়ে না। OpenTofu-তেও tofu test আছে।
আমাদের module-এর নিয়ম: prod-এ কমপক্ষে ২টি replica. Code-এ একটি ইচ্ছাকৃত বাগ রাখলাম, max-এর জায়গায় min:
locals {
# Rule: prod always runs at least 2 copies.
replicas = var.env == "prod" ? min(var.replicas, 2) : var.replicas
}# Unit test: a fake kubernetes provider, so no cluster is needed.
mock_provider "kubernetes" {}
variables {
name = "bazar-api"
}
run "dev_keeps_one_replica" {
command = plan
assert {
condition = output.replicas == 1
error_message = "dev should keep 1 replica."
}
}
run "prod_runs_at_least_two" {
command = plan
variables {
env = "prod"
replicas = 1
}
assert {
condition = output.replicas >= 2
error_message = "prod must run at least 2 replicas."
}
}
run "too_many_replicas_rejected" {
command = plan
variables {
replicas = 9
}
expect_failures = [var.replicas]
}$ terraform test
tests\unit.tftest.hcl... in progress
run "dev_keeps_one_replica"... pass
run "prod_runs_at_least_two"... fail
Error: Test assertion failed
on tests\unit.tftest.hcl line 26, in run "prod_runs_at_least_two":
26: condition = output.replicas >= 2
├────────────────
│ Diff:
│ --- actual
│ +++ expected
│ - 1
│ + 2
prod must run at least 2 replicas.
run "too_many_replicas_rejected"... pass
tests\unit.tftest.hcl... tearing down
tests\unit.tftest.hcl... fail
Failure! 2 passed, 1 failed.
$ echo $?
1কোনো cluster লাগেনি, কারণ mock_provider নকল উত্তর দেয়। এই আসল run Windows-এ হয়েছে, তাই path-এ \ দেখছেন। তৃতীয় run-টি pass করেছে কারণ expect_failures বলছে “এখানে ব্যর্থ হওয়াই সঠিক।” নিয়ম কেউ মুছে দিলে এই test লাল হবে। min বদলে max করার পরের run:
tests\unit.tftest.hcl... in progress
run "dev_keeps_one_replica"... pass
run "prod_runs_at_least_two"... pass
run "too_many_replicas_rejected"... pass
tests\unit.tftest.hcl... tearing down
tests\unit.tftest.hcl... pass
Success! 3 passed, 0 failed.
Unit বনাম integration
command = plan আর mock মিলে unit test. এটি আপনার যুক্তি দেখে। কিন্তু আসল API কী বলবে, mock তা জানে না। Integration test-এ command = apply সত্যিই জিনিস বানায়, মেলায়, আর শেষে মুছে দেয়। নিচের test স্থানীয় cluster-এ একটি আসল Deployment চালিয়ে মুছে ফেলেছে:
# Integration test: real provider, real Deployment, destroyed afterwards.
provider "kubernetes" {
config_context = "docker-desktop"
}
run "real_rollout_in_scratch_namespace" {
command = apply
variables {
name = "tp-itest"
env = "prod"
replicas = 2
}
assert {
condition = kubernetes_deployment_v1.app.spec[0].replicas == "2"
error_message = "The Deployment should ask for 2 replicas."
}
}$ terraform test -filter='tests\integration.tftest.hcl'
tests\integration.tftest.hcl... in progress
run "real_rollout_in_scratch_namespace"... pass
tests\integration.tftest.hcl... tearing down
tests\integration.tftest.hcl... pass
Success! 1 passed, 0 failed.
$ kubectl get deploy -n abcl-tp
No resources found in abcl-tp namespace.একটি নীরব ফাঁদ: -filter-এ path ভুল লিখলে এই run-এ আউটপুট এলো Success! 0 passed, 0 failed., সাথে একটি Unknown test file warning, আর exit code ০. অর্থাৎ CI সবুজ, অথচ কোনো পরীক্ষাই হয়নি। তাই pipeline-এ পাস করা test-এর সংখ্যাও দেখুন।
৫ · Plan পড়া: মানুষের চোখ আর যন্ত্রের চোখ
Plan review-এর প্রথম নিয়ম: শেষ লাইনের সংখ্যা নয়, চিহ্ন পড়ুন। + মানে তৈরি, ~ মানে জায়গায় বদল, - মানে মুছে ফেলা, আর -/+ মানে মুছে নতুন করে বানানো। এই শেষটিই সবচেয়ে ভয়ের, কারণ যে argument-এর জন্য এটি ঘটছে তার পাশে # forces replacement লেখা থাকলেও লম্বা plan-এ চোখ এড়িয়ে যায়।
কিন্তু ৮০০ লাইনের plan মানুষ মন দিয়ে পড়তে পারে না। তাই plan-কে যন্ত্রে পড়ার মতো করা হয়। দুটি কমান্ড:
$ terraform plan -out=bad.tfplan -var replicas=8 \
-var 'labels={app="bazar"}' -var service_type=LoadBalancer
…
Saved the plan to: bad.tfplan
To perform exactly these actions, run the following command to apply:
terraform apply "bad.tfplan"
$ terraform show -json bad.tfplan > bad.jsonSaved plan ফাইলটি binary, পড়ার জন্য নয়। show -json তাকে plan JSON বানায়। এর আসল অংশ resource_changes: প্রতিটি resource, তার actions, আর আগের ও পরের মান। আসল run থেকে একটি এন্ট্রি, ছোট করে:
{
"format_version": "1.2",
"terraform_version": "1.17.1",
"resource_changes": [
{
"address": "kubernetes_deployment_v1.api",
"type": "kubernetes_deployment_v1",
"change": {
"actions": ["create"],
"before": null,
"after": {
"metadata": [{ "labels": { "app": "bazar" }, "name": "bazar-api" }],
"spec": [{ "replicas": "8" }]
}
}
}
]
}এবার একটি সত্যিকারের ছোট policy as code. শুধু Python-এর standard library, আর exit code দিয়ে pipeline-কে জানানো। ফাইলটি আসলে এই bad.json-এর ওপর চলেছে:
#!/usr/bin/env python3
"""Deny risky changes in a Terraform plan. Usage: policy.py plan.json"""
import json
import sys
MAX_REPLICAS = 6
REQUIRED_LABELS = {"app", "owner"}
plan = json.load(open(sys.argv[1], encoding="utf-8"))
denies = []
for rc in plan.get("resource_changes", []):
actions, after = rc["change"]["actions"], rc["change"]["after"] or {}
if actions in (["no-op"], ["read"], ["delete"]):
continue
if rc["type"] == "kubernetes_deployment_v1":
labels = after["metadata"][0].get("labels") or {}
missing = sorted(REQUIRED_LABELS - set(labels))
if missing:
denies.append(f"{rc['address']}: missing labels {missing}")
replicas = int(after["spec"][0]["replicas"])
if replicas > MAX_REPLICAS:
denies.append(f"{rc['address']}: replicas {replicas} > {MAX_REPLICAS}")
if rc["type"] == "kubernetes_service_v1":
if after["spec"][0].get("type") == "LoadBalancer":
denies.append(f"{rc['address']}: public LoadBalancer needs an exception")
for d in denies:
print("DENY ", d)
print(f"{len(denies)} deny, checked {len(plan.get('resource_changes', []))} changes")
sys.exit(1 if denies else 0)$ python policy.py bad.json
DENY kubernetes_deployment_v1.api: missing labels ['owner']
DENY kubernetes_deployment_v1.api: replicas 8 > 6
DENY kubernetes_service_v1.api: public LoadBalancer needs an exception
3 deny, checked 3 changes
$ echo $?
1
$ python policy.py good.json
0 deny, checked 3 changes
$ echo $?
0
$ terraform apply good.tfplan
…
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.তিনটি নিয়মই একসাথে ধরা পড়েছে। একটি plan-এ সব ভুল জানানো ভালো, যাতে লেখককে বারবার push করতে না হয়। Script-টি ["delete"] বাদ দেয়, কারণ মুছে ফেলা জিনিসের after থাকে null. খেয়াল করুন, Kubernetes provider এখানে replicas-কে string হিসেবে রাখে, তাই int() দরকার ছিল। Policy লিখতে হলে JSON-এর আসল গঠন নিজের চোখে দেখতে হয়।
Plan ফাইল আর plan JSON গোপন তথ্য বহন করতে পারে। Terraform-এর sensitive চিহ্ন শুধু পর্দায় লুকায়; plan ফাইলে মান থেকে যায়। তাই এগুলোকে পর্ব ৩৩-এর secret-এর মতো দেখুন: অল্প সময় রাখুন, access সীমিত রাখুন।
ধাপে ধাপে: public bucket ধরা
একই কৌশল cloud-এর একটি পরিচিত ভুলে কেমন কাজ করে দেখুন। ধরা যাক একটি রিপোর্টের ছবি দ্রুত দেখাতে কেউ পর্ব ২১-এর bucket-এর public block খুলে দিয়েছেন। নিচের plan একটি নমুনা, AWS provider এখানে চালানো হয়নি।
# aws_s3_bucket_public_access_block.assets will be updated in-place
~ resource "aws_s3_bucket_public_access_block" "assets" {
~ block_public_policy = true -> false
~ restrict_public_buckets = true -> false
}
Plan: 0 to add, 1 to change, 0 to destroy.
"type": "aws_s3_bucket_public_access_block",
"change": {
"actions": ["update"],
"before": { "block_public_policy": true },
"after": { "block_public_policy": false }
}
if type == "aws_s3_bucket_public_access_block"
and "delete" not in actions
and after.block_public_policy == false:
deny("public access is allowed")
DENY NO_PUBLIC_BUCKET: ...assets: public access is allowed
1 deny, checked 1 changes
exit code 1
# public block আগের মতোই থাকল + resource "aws_cloudfront_distribution" "assets" { ... }
Plan: 1 to add, 0 to change, 0 to destroy. 0 deny, checked 1 changes exit code 0
- বদলটি দেখতে নিরীহ। Plan বলছে মাত্র ১টি change. কোনো destroy নেই, তাই দ্রুত চোখ বোলালে কিছুই ভয়ের লাগে না। অথচ দুটি
truefalseহয়ে গেছে। - JSON-এ একই তথ্য।
terraform show -json-এর পরে এটি একটি পরিষ্কার মান:after.block_public_policyহলোfalse. যন্ত্র এখানে ক্লান্ত হয় না। - নিয়ম মেলানো। Policy শুধু এই type দেখে, মুছে ফেলার ক্ষেত্র বাদ দেয়, তারপর মান পরীক্ষা করে। তিনটি শর্তই মিলল।
- Deny. Script exit code ১ দিয়ে থামল। PR-এর check লাল, merge বন্ধ। কারণটি লেখাতেই আছে, তাই লেখক জানেন কী বদলাতে হবে।
- ঠিক সমাধান। Bucket খোলার বদলে লেখক সামনে একটি CDN বসালেন, যেখানে শুধু CDN bucket পড়তে পারে। Public block আগের মতোই থাকল। (নমুনাটি সংক্ষিপ্ত, পুরো resource নয়।)
- নতুন plan পাস।
1 to add, 0 to change, policy ০ deny, exit code ০. PR আবার review-এর জন্য তৈরি। নিয়ম কাজ আটকায়নি, ভুল পথ আটকেছে।
এবার নিজে নিয়ম বদলে দেখুন। নিচে একটি ছোট policy evaluator, যা নমুনা resource change-এর ওপর চলে। এতে একটি exception-ও আছে, যার মেয়াদ শেষ হয়। today-কে ১২১ করলে কী হয়, বা bucket-এর মান true করলে কী হয়, দেখুন:
// A tiny policy engine over sample Terraform resource changes.
const changes = [
{ address: "aws_s3_bucket_public_access_block.assets", type: "public_access_block",
actions: ["update"], after: { block_public_policy: false } },
{ address: "google_compute_firewall.ssh", type: "firewall",
actions: ["create"], after: { source_ranges: ["0.0.0.0/0"], ports: ["22"] } },
{ address: "kubernetes_deployment_v1.api", type: "deployment",
actions: ["update"], after: { replicas: 8, labels: { app: "bazar" } } },
{ address: "azurerm_storage_account.logs", type: "storage",
actions: ["create"], after: { min_tls_version: "TLS1_2", tags: { owner: "ops" } } },
];
const rules = [
{ id: "NO_PUBLIC_BUCKET", type: "public_access_block",
bad: (a) => a.block_public_policy === false ? "public access is allowed" : null },
{ id: "NO_OPEN_SSH", type: "firewall",
bad: (a) => a.source_ranges.includes("0.0.0.0/0") && a.ports.includes("22")
? "port 22 is open to the whole internet" : null },
{ id: "MAX_REPLICAS", type: "deployment",
bad: (a) => a.replicas > 6 ? "replicas " + a.replicas + " > 6" : null },
{ id: "OWNER_LABEL", type: "deployment",
bad: (a) => a.labels.owner ? null : "label owner is missing" },
];
const waivers = [{ rule: "MAX_REPLICAS", address: "kubernetes_deployment_v1.api", until: 120 }]; // expires
const today = 100; // simulated day number, so the output never changes
let denies = 0, waived = 0;
for (const c of changes) for (const r of rules) {
if (r.type !== c.type) continue;
const why = r.bad(c.after);
if (!why) continue;
const w = waivers.find((x) => x.rule === r.id && x.address === c.address && x.until >= today);
if (w) { waived++; console.log("WAIVED " + r.id + ": " + why + " (until day " + w.until + ")"); }
else { denies++; console.log("DENY " + r.id + ": " + c.address + ": " + why); }
}
console.log("result: " + denies + " deny, " + waived + " waived, " + changes.length + " changes");
console.log(denies ? "exit code 1: stop the pipeline" : "exit code 0: continue");আউটপুট:
DENY NO_PUBLIC_BUCKET: aws_s3_bucket_public_access_block.assets: public access is allowed
DENY NO_OPEN_SSH: google_compute_firewall.ssh: port 22 is open to the whole internet
WAIVED MAX_REPLICAS: replicas 8 > 6 (until day 120)
DENY OWNER_LABEL: kubernetes_deployment_v1.api: label owner is missing
result: 3 deny, 1 waived, 4 changes
exit code 1: stop the pipeline
Azure storage-এর এন্ট্রিটি নিয়ম মেলে না বলে নীরবে পাস করেছে। MAX_REPLICAS-এর exception-টি একটি নির্দিষ্ট resource আর একটি নির্দিষ্ট তারিখে বাঁধা। এটাই ভালো exception-এর লক্ষণ: নিয়ম বন্ধ হয় না, শুধু একটি জায়গায়, কিছু দিনের জন্য ছাড় পায়।
৬ · Policy engine: নিজের script কখন যথেষ্ট নয়
ত্রিশ লাইনের Python দিয়ে শুরু করা ভালো। কিন্তু নিয়ম ২০০টি হলে আপনার লাগবে নিয়মের test, version, exception, audit log আর আলাদা দলের মালিকানা। Policy engine এই কাঠামো দেয়।
| Tool | ভাষা | কোথায় চলে | মনে রাখুন |
|---|---|---|---|
| OPA | Rego | যেকোনো জায়গায়, JSON পেলেই | সাধারণ policy engine; CNCF graduated (২০২১) |
| Conftest | Rego | CI, ফাইলের ওপর | OPA-কে Terraform, YAML, Dockerfile-এ লাগানোর সহজ CLI |
| Sentinel | নিজস্ব ভাষা | HCP Terraform, Terraform Enterprise | HashiCorp-এর বাণিজ্যিক পণ্যের অংশ |
| Kyverno | YAML-ধাঁচের | Kubernetes admission | Plan নয়, cluster-এ আসা object দেখে |
| OPA Gatekeeper | Rego | Kubernetes admission | পর্ব ৪৯-এ দেখেছেন |
আমাদের owner নিয়মটি Rego-তে এমন। এটি নমুনা, এই পর্বের জন্য চালানো হয়নি। Conftest সাধারণত main package-এর deny নিয়ম খোঁজে:
package main
deny contains msg if {
rc := input.resource_changes[_]
rc.type == "kubernetes_deployment_v1"
rc.change.after != null
not rc.change.after.metadata[0].labels.owner
msg := sprintf("%s: owner label is missing", [rc.address])
}$ conftest test bad.json --policy policy/
FAIL - bad.json - main - kubernetes_deployment_v1.api: owner label is missing
1 test, 0 passed, 0 warnings, 1 failure, 0 exceptionsRego নিয়ম ঘোষণা করে, ধাপ লেখে না। সব শর্ত সত্য হলেই msg তৈরি হয়। after != null লাইনটি ছাড়া মুছে ফেলা Deployment-ও ভুলভাবে deny হতো, কারণ Rego-তে না-থাকা (undefined) মানের ওপর not সত্য হয়। আমাদের Python script যে কারণে delete বাদ দিয়েছে, এখানেও সেই একই কথা।
Sentinel-এর একটি ধারণা সব জায়গায় কাজে লাগে: নিয়মের তিনটি কঠোরতা। advisory শুধু জানায়, soft-mandatory আটকায় তবে অনুমোদিত মানুষ ছাড় দিতে পারেন, আর hard-mandatory কেউ এড়াতে পারে না। নতুন নিয়ম advisory দিয়ে শুরু করলে দেখা যায় কতগুলো ভুল সতর্কতা আসছে। এরপর ধাপে ধাপে কঠোর করা নিরাপদ।
একই নিয়ম একাধিক স্তরে থাকতে পারে। CI-র policy লেখককে দ্রুত জানায়, কিন্তু কেউ console-এ হাতে বদল করলে CI দেখে না। তাই নিচে থাকে guardrail: পর্ব ৩৫-এর organization policy আর Kubernetes-এর admission control. প্রথমটি দ্রুত উত্তর দেয়, দ্বিতীয়টি নিশ্চয়তা দেয়। একটি আরেকটির বিকল্প নয়।
৭ · CI-তে নিরাপদ plan: comment, approval, saved plan
এবার সব ধাপ এক জায়গায়। নিয়ম তিনটি: সব পরীক্ষা pull request-এ চলবে, plan মানুষের সামনে আসবে, আর apply হবে শুধু সেই plan যা দেখা হয়েছে। Pull request-এর সাধারণ প্রবাহ Software Engineering কোর্সে আছে, এখানে শুধু Terraform-এর অংশটুকু। সংখ্যাগুলো একটি কাল্পনিক PR-এর।
- PushPR #57
- Lintfmt, validate
- Testtest, scan
- Plan-out
- Policyplan JSON
- CommentPR-এ plan
- Approveমানুষ
- Applysaved plan
| কমান্ড | |
| ফল | |
| Merge বা apply |
- Push. Branch থেকে PR খোলা হলো।
infra/ফোল্ডারে বদল, তাই workflow শুরু হলো। এখনো কোনো অনুমতি নেই। - Lint. Credential ছাড়াই
fmtআরvalidateচলে গেল। ভুল থাকলে এখানেই সেকেন্ডের মধ্যে থামত। - Test আর scan. Mock-ভিত্তিক
terraform testআর scanner চলল। এখনো আসল সিস্টেম ছোঁয়া হয়নি। - Plan. এই প্রথম state আর আসল API পড়া হলো। তাই শুধু read-অনুমতির একটি স্বল্পমেয়াদি OIDC credential যথেষ্ট। ফল
tfplanফাইলে জমা। - Policy. Plan JSON-এর ওপর নিয়ম চলল। ৩টি change, ০ deny.
- Comment. Plan লেখাটি PR-এ এলো, যাতে রিভিউয়ারকে log খুঁজতে না হয়। মেশিন নিয়ম দেখেছে, মানুষ এবার উদ্দেশ্য দেখবেন।
- Approve. রিভিউয়ার চিহ্নগুলো পড়লেন: ১টি নতুন, ২টি বদল, কোনো মুছে ফেলা নেই। Production environment-এর গেটে approve করলেন।
- Apply. শুধু
tfplanapply হলো। Terraform নতুন করে কিছু ভাবল না, plan-এ যা ছিল ঠিক তাই করল:1 added, 2 changed, 0 destroyed.
GitHub Actions-এ এর একটি কাঠামো নিচে। অন্য CI-তেও ধারণা একই। এই YAML parse করে যাচাই করা হয়েছে, কিন্তু আসল repository-তে চালানো হয়নি। Action-এর version লেখার সময়ের, মিলিয়ে নিন। Cloud login-এর ধাপ এখানে বাদ দেওয়া। Pipeline-এর গতি আর cache নিয়ে কথা হবে পর্ব ৫৭-এ, আর OIDC-র বিস্তারিত পর্ব ৬১-তে।
name: terraform
on:
pull_request:
paths: ["infra/**"]
push:
branches: [main]
paths: ["infra/**"]
permissions:
contents: read
id-token: write # short-lived cloud login (OIDC), no stored keys
pull-requests: write # lets the job comment on the PR
jobs:
plan:
runs-on: ubuntu-latest
defaults:
run:
working-directory: infra
steps:
- uses: actions/checkout@v7 # in production pin a full commit SHA
- uses: hashicorp/setup-terraform@v4
with:
terraform_version: 1.17.1
terraform_wrapper: false
- run: terraform fmt -check -recursive
- run: terraform init -input=false
- run: terraform validate
- run: terraform test
- run: terraform plan -input=false -out=tfplan
- run: terraform show -json tfplan > plan.json
- run: python3 policy.py plan.json
- name: Comment the plan on the PR
if: github.event_name == 'pull_request'
env:
GH_TOKEN: ${{ github.token }}
run: |
terraform show -no-color tfplan > plan.txt
gh pr comment ${{ github.event.pull_request.number }} --body-file plan.txt
- uses: actions/upload-artifact@v7
with:
name: tfplan
path: infra/tfplan
retention-days: 1
apply:
if: github.event_name == 'push'
needs: plan
runs-on: ubuntu-latest
environment: production # required reviewers approve here
concurrency:
group: terraform-prod
cancel-in-progress: false # never kill a running apply
defaults:
run:
working-directory: infra
steps:
- uses: actions/checkout@v7
- uses: hashicorp/setup-terraform@v4
with:
terraform_version: 1.17.1
terraform_wrapper: false
- uses: actions/download-artifact@v8
with:
name: tfplan
path: infra
- run: terraform init -input=false
- run: terraform apply -input=false tfplanconcurrency দুটি apply-কে একসাথে চলতে দেয় না। environment: production-এ GitHub-এর required reviewer বসালে job সেখানে অপেক্ষা করে। Apply ধাপে কোনো নতুন plan নেই, আর -auto-approve-ও নেই, কারণ saved plan apply করতে প্রশ্ন লাগে না।
একটি সূক্ষ্ম ফাঁক আছে: PR-এ দেখা plan আর merge-এর পরের plan দুটি আলাদা run. তাই reviewer-কে production গেটেও plan দেখতে হয়। Atlantis-এর মতো tool অন্য পথ নেয়: PR-এর ভেতরেই comment দিয়ে apply করে, আর apply শেষে merge হয়।
Plan-ও code চালায়। terraform plan provider চালায়, আর external data source-এর মতো জিনিস যেকোনো প্রোগ্রাম চালাতে পারে। তাই অচেনা fork-এর PR-এ production credential দিয়ে কখনো plan চালাবেন না। PR-এর plan-এর জন্য read-only role দিন, আর apply-এর জন্য আলাদা role.
Saved plan কেন, আর সেটি কখন বাসি হয়
ভাবুন, আপনি ফ্রিজ দেখে বাজারের ফর্দ বানালেন। আপনার ভাই ইতোমধ্যে ডিম কিনে এনেছেন। পুরোনো ফর্দ ধরে গেলে ডিম দ্বিগুণ হবে। plan -out হলো সেই ফর্দ। Saved plan-এর ভেতরে লেখা থাকে কোন state দেখে এটি বানানো। State-এর serial নম্বর মিলছে না মানে কেউ এর মধ্যে বদল করেছে, আর plan-টি হয়ে গেছে stale. আসল error নিচে, serial-এর সংখ্যাগুলো বোঝার জন্য ধরা।
| কে কাজ করছেন | |
| State serial | |
| a.tfplan যে serial দেখেছে | |
| Deployment-এর label | |
| replicas |
- শুরু। State-এর serial ৭. Deployment চলছে ২টি replica-য়, আর সব মিলে আছে।
- রিনার plan. রিনা replica ৩ করতে
plan -out=a.tfplanচালালেন। ফাইলটি মনে রাখল, সে serial ৭ দেখে বানানো। Review-এর অপেক্ষায় রইল। - সোহানের apply. এর মধ্যে সোহান একটি label যোগ করে apply করলেন। প্রতিটি লেখায় serial বাড়ে, তাই এখন ৮.
- রিনার apply ব্যর্থ। রিনা
apply a.tfplanচালালেন। Terraform দেখল ফাইলের serial ৭, আর state-এর ৮. কিছু না ছুঁয়েই থেমে গেল। এটি বাগ নয়, এটিই সুরক্ষা। - নতুন plan. রিনাকে আবার plan করতে হবে। নতুন ফাইল serial ৮ দেখবে, label সহ পুরো ছবি পাবে, আর আবার review হবে।
$ terraform plan -out=a.tfplan -var replicas=3
…
Plan: 0 to add, 1 to change, 0 to destroy.
Saved the plan to: a.tfplan
$ terraform apply -auto-approve -var 'labels={app="bazar",owner="team-payments",tier="web"}'
…
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
$ terraform apply a.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.
$ echo $?
1Plan-এর ঝুঁকি মাপা
সব plan-এ সমান মনোযোগ দেওয়া যায় না। তাই অনেক দল delete আর replace-এর সংখ্যা থেকে একটি risk score বানায়। নিচের ওজনগুলো আমাদের ধরে নেওয়া, কোনো মান (standard) নয়। আসল plan JSON-এ ["delete","create"] আর ["create","delete"] (create_before_destroy থাকলে) গুনলেই replace-এর সংখ্যা মেলে।
// Plan risk score from counts. The weights are our assumption, not a standard.
const weight = { create: 1, update: 2, replace: 8, delete: 10 };
const envFactor = { dev: 1, staging: 2, prod: 4 };
function score(p) {
const base = p.create * weight.create + p.update * weight.update +
p.replace * weight.replace + p.delete * weight.delete;
const stateful = p.statefulTouched * 25; // database or storage replaced/deleted
return (base + stateful) * envFactor[p.env];
}
function band(s) {
if (s < 20) return "low";
if (s < 80) return "medium";
return "high";
}
const plans = [
{ name: "new tag on 12 resources", env: "prod", create: 0, update: 12, replace: 0, delete: 0, statefulTouched: 0 },
{ name: "add 3 subnets", env: "prod", create: 3, update: 1, replace: 0, delete: 0, statefulTouched: 0 },
{ name: "replace 1 service", env: "prod", create: 0, update: 1, replace: 1, delete: 0, statefulTouched: 0 },
{ name: "replace 1 database", env: "prod", create: 0, update: 0, replace: 1, delete: 0, statefulTouched: 1 },
{ name: "same database in dev", env: "dev", create: 0, update: 0, replace: 1, delete: 0, statefulTouched: 1 },
{ name: "delete 40 old objects", env: "staging", create: 0, update: 0, replace: 0, delete: 40, statefulTouched: 0 },
];
console.log("score band plan");
for (const p of plans) {
const s = score(p);
console.log(String(s).padStart(5) + " " + band(s).padEnd(6) + " " + p.name + " (" + p.env + ")");
}আউটপুট:
score band plan
96 high new tag on 12 resources (prod)
20 medium add 3 subnets (prod)
40 medium replace 1 service (prod)
132 high replace 1 database (prod)
33 medium same database in dev (dev)
800 high delete 40 old objects (staging)
হাতে মিলিয়ে দেখুন: database-এর base ৮, stateful ২৫, মোট ৩৩; prod-এ ×৪ = ১৩২. এবার প্রথম সারি দেখুন। ১২টি tag যোগ একটি নিরীহ কাজ, অথচ score ৯৬. শুধু সংখ্যা গুনলে এমন ভুল হবেই। তাই score সিদ্ধান্ত নেয় না, শুধু ঠিক করে কতটা মনোযোগ লাগবে। চাইলে update-এর ওজন ১ করে আবার চালান।
শেষ গেট: pass, review না block
আগের সব ফল এবার একটি জায়গায় আসে। গেটের যুক্তি ইচ্ছা করেই সরল: কঠিন ব্যর্থতা আটকে দেয়, সন্দেহজনক জিনিস মানুষের কাছে পাঠায়, বাকিটা এগিয়ে যায়।
// A pipeline gate: every input is a fact from an earlier stage.
function gate(r) {
const block = [], review = [];
if (!r.fmtOk || !r.validateOk) block.push("format or validate failed");
if (r.testsFailed > 0) block.push(r.testsFailed + " test failed");
if (r.policyDenies > 0) block.push(r.policyDenies + " policy deny");
if (r.criticalFindings > 0) block.push(r.criticalFindings + " critical finding");
if (r.stateSerial !== r.planSerial) block.push("saved plan is stale");
if (r.deletesProtected > 0) block.push("deletes a protected resource");
if (r.risk >= 80) review.push("risk " + r.risk);
if (r.costDelta >= 100) review.push("cost +$" + r.costDelta + "/month");
if (r.env === "prod") review.push("prod needs a human");
if (block.length) return "BLOCK " + block.join("; ");
if (review.length) return "REVIEW " + review.join("; ");
return "PASS safe to apply the saved plan";
}
const ok = { fmtOk: true, validateOk: true, testsFailed: 0, policyDenies: 0, criticalFindings: 0,
deletesProtected: 0, stateSerial: 42, planSerial: 42, risk: 8, costDelta: 0, env: "dev" };
const cases = [
["small dev change", {}],
["same change in prod", { env: "prod", risk: 32 }],
["costly scale-up", { env: "staging", risk: 24, costDelta: 360 }],
["public bucket", { env: "prod", policyDenies: 1 }],
["state moved after plan", { env: "prod", stateSerial: 43 }],
["database replace", { env: "prod", risk: 132, deletesProtected: 1 }],
["format and test fail", { fmtOk: false, testsFailed: 1 }],
];
for (const [name, patch] of cases) {
console.log(name.padEnd(24) + gate({ ...ok, ...patch }));
}আউটপুট:
small dev change PASS safe to apply the saved plan
same change in prod REVIEW prod needs a human
costly scale-up REVIEW cost +$360/month
public bucket BLOCK 1 policy deny
state moved after plan BLOCK saved plan is stale
database replace BLOCK deletes a protected resource
format and test fail BLOCK format or validate failed; 1 test failed
Block-এর তালিকা আগে দেখা হয়, তাই ঝুঁকি কম হলেও একটি policy deny সব থামায়। Prod সবসময় অন্তত review-তে যায়। শেষ সারিতে দুটি কারণই একসাথে জানানো হয়েছে, যেন লেখক এক push-এই সব ঠিক করতে পারেন।
৮ · Destroy সুরক্ষা, drift আর খরচ
Destroy সুরক্ষার স্তর
Terraform-এর নিজের সুরক্ষা হলো prevent_destroy. একটি ConfigMap-এ এটি বসিয়ে পুরো configuration-এ terraform destroy চালালাম:
resource "kubernetes_config_map_v1" "settings" {
metadata {
name = "bazar-settings"
namespace = "abcl-tp"
}
data = { currency = "BDT" }
lifecycle {
prevent_destroy = true
}
}$ terraform destroy -auto-approve
…
Plan: 0 to add, 0 to change, 3 to destroy.
Error: Instance cannot be destroyed
on main.tf line 29:
29: resource "kubernetes_config_map_v1" "settings" {
Resource kubernetes_config_map_v1.settings 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.
$ echo $?
1একটি resource সুরক্ষিত ছিল, তবু বাকি দুটিও মুছল না, কারণ পুরো plan-ই ব্যর্থ। এবার ফাঁকটি দেখুন। Resource-এর পুরো ব্লকটিই ফাইল থেকে মুছে plan চালালাম:
# kubernetes_config_map_v1.settings will be destroyed
# (because kubernetes_config_map_v1.settings is not in configuration)
…
Plan: 0 to add, 1 to change, 1 to destroy.
কোনো error নেই, exit code ০. নিয়মটি ব্লকের ভেতরে ছিল, ব্লকের সাথেই চলে গেছে। অর্থাৎ prevent_destroy শুধু দুর্ঘটনা আটকায়, উদ্দেশ্যমূলক মুছে ফেলা নয়। তাই এটি Terraform-এর বাইরের সুরক্ষার বিকল্প নয়।
গুরুত্বপূর্ণ database-এ তাই স্তর বসান। নিচের HCL একটি নমুনা, চালানো হয়নি। Provider-ভেদে নাম আলাদা: AWS RDS আর Google Cloud SQL-এ deletion_protection ধরনের setting আছে, Azure-এ resource lock আছে। ভরসার শেষ স্তর হলো পর্ব ২৪-এর backup, আর মাঝে মাঝে তা ফিরিয়ে এনে যাচাই করা।
resource "aws_db_instance" "orders" {
# ...engine, size and network omitted...
deletion_protection = true # the cloud API itself refuses delete
skip_final_snapshot = false
final_snapshot_identifier = "orders-final"
backup_retention_period = 7
lifecycle {
prevent_destroy = true # Terraform refuses at plan time
}
}Drift check: সূচি ধরে plan
Drift পর্ব ৫৩-এর বিষয়। এখানে দেখব এটিকে পরীক্ষার ধাপ কীভাবে বানাবেন। -detailed-exitcode দিলে plan-এর exit code তিন রকম: ০ মানে কোনো বদল নেই, ১ মানে error, ২ মানে বদল আছে। হাতে kubectl scale চালিয়ে replica ২ থেকে ৪ করার পরের আসল run:
$ kubectl scale deploy bazar-api --replicas=4
deployment.apps/bazar-api scaled
$ terraform plan -refresh-only -detailed-exitcode
…
Note: Objects have changed outside of Terraform
Terraform detected the following changes made outside of Terraform since the
last "terraform apply" which may have affected this plan:
# kubernetes_deployment_v1.api has changed
~ resource "kubernetes_deployment_v1" "api" {
…
~ spec {
~ replicas = "2" -> "4"
…
$ echo $?
2একটি নির্ধারিত job রাতে এটি চালাতে পারে। Exit code ২ এলে শুধু alert দেবে, নিজে থেকে apply করবে না, কারণ হাতের বদলটি হয়তো কোনো incident সামলাতে করা হয়েছিল। মাঝের exit code ২-কে CI অনেক সময় ব্যর্থতা ধরে, তাই script-এ এটি আলাদা করে দেখতে হয়। -refresh-only কিছু বদলায় না, শুধু আসল অবস্থা পড়ে জানায়।
খরচও একটি পরীক্ষা
Infracost plan পড়ে provider-এর দামের তালিকা মিলিয়ে PR-এ মাসিক খরচের পার্থক্য দেখায়। এটি এই পর্বে চালানো হয়নি, তাই শুধু ধারণাটি বলি। হিসাবটি হাতেও করা যায়। ধরে নিই একটি VM-এর ভাড়া ঘণ্টায় $০.১০, আর মাস ৩০ দিনের:
৫ × $০.১০ × ৭২০ ঘণ্টা = মাসে +$৩৬০
এই সংখ্যাই গেটে costDelta: 360 হিসেবে গিয়েছিল। দাম সবসময় provider-এর pricing পাতায় মিলিয়ে নিন। এ ধরনের tool ব্যবহার-নির্ভর খরচ (data transfer, request) ঠিকমতো জানতে পারে না, যদি না ব্যবহারের অনুমান আপনি দেন। পুরো হিসাব পর্ব ৭২ আর ৭৩-এ, আর budget alert থাকলে ভুল apply-এর খরচও দ্রুত চোখে পড়ে।
৯ · পরীক্ষার environment, preview stack আর দুর্ঘটনার শিক্ষা
Integration test-এর জন্য আলাদা জায়গা লাগে। আদর্শ হলো একটি আলাদা sandbox account বা project, যেখানে কড়া budget আর সীমিত অনুমতি আছে। Production-এর পাশে test চালালে test-এর ভুল সরাসরি production-এ লাগতে পারে।
Ephemeral preview stack মানে প্রতিটি PR-এর জন্য ক্ষণস্থায়ী একটি পুরো পরিবেশ। PR খুললে তৈরি, বন্ধ করলে মুছে যায়। বিয়েবাড়ির প্যান্ডেলের মতো: অনুষ্ঠানের জন্য বসে, শেষে খুলে নেওয়া হয়। কিন্তু খুলে নিতে ভুলে গেলে ভাড়া চলতেই থাকে।
- নাম: প্রতিটি resource-এর নামে PR নম্বর রাখুন, যেমন
bazar-pr-57, যাতে দুই PR ধাক্কা না খায়। Module-এর variable দিয়েই এটি হয়। - Tag:
ttl=2dআরownertag বসান। একটি নির্ধারিত job মেয়াদোত্তীর্ণ জিনিস খুঁজে মুছবে। Workflow মাঝপথে ব্যর্থ হলে এই job-ই শেষ ভরসা। - মাপ: ছোট instance আর ছোট database. শুধু test-এর ডেটা, production-এর আসল ডেটা কখনো নয়।
- Clean-up: PR বন্ধ হলে
terraform destroy. তারপর পরীক্ষা করুন যে সত্যিই কিছু বাকি নেই।prevent_destroyবাdeletion_protectionপরিষ্কারের পথ আটকাতে পারে, তাই test environment-এ এগুলো variable দিয়ে সামলান। - Provider-এর সীমা: কিছু জিনিস তৈরি হতে অনেক সময় নেয় বা quota-য় আটকায়। এগুলো preview-তে না বানিয়ে একটি ভাগ করা সংস্করণ ব্যবহার করুন।
বাস্তব দুর্ঘটনা থেকে যা নেওয়ার
নিচের ঘটনাগুলো কোম্পানিগুলোর প্রকাশিত বিবরণ বা সরকারি নথি থেকে, সংক্ষেপে। সেগুলো Terraform-এর ঘটনা নয়, কিন্তু শিক্ষা এখানেই খাটে।
| ঘটনা | প্রকাশিত বিবরণ অনুযায়ী | IaC-তে শিক্ষা |
|---|---|---|
| Amazon S3, ফেব্রুয়ারি ২০১৭ | একটি কমান্ডের ইনপুট ভুল হওয়ায় উদ্দেশ্যের চেয়ে অনেক বেশি server সরে যায়। AWS পরে tool-এ সীমা বসায়। | মুছে ফেলার সংখ্যায় সীমা রাখুন, blast radius ছোট করুন। |
| Knight Capital, আগস্ট ২০১২ | SEC-এর নথি অনুযায়ী নতুন code আটটির মধ্যে একটি server-এ পৌঁছায়নি; প্রায় ৪৫ মিনিটে ক্ষতি কয়েকশো মিলিয়ন ডলার। | হাতে deploy আর server-এর অমিল, অর্থাৎ drift, যাচাই ছাড়া ছাড়বেন না। |
| Facebook, অক্টোবর ২০২১ | Meta জানায়, একটি কমান্ড backbone সংযোগ নামিয়ে দেয়, আর যে audit tool থামানোর কথা, তার বাগে সেটি থামেনি। | গেটও সফটওয়্যার, গেটেরও পরীক্ষা লাগে। Policy-র test লিখুন। |
| খোলা object storage, বহু ঘটনা | বিভিন্ন সংবাদে ভুল সেটিংয়ের কারণে ডেটা ফাঁসের খবর এসেছে। AWS ২০১৮ সালে Block Public Access আনে, আর ২০২৩ সাল থেকে নতুন bucket-এ এটি ডিফল্ট চালু। | নিরাপদ ডিফল্ট আর plan-এর policy একসাথে রাখুন। |
আরও গভীরে: OPA-র মডেল হলো সিদ্ধান্তকে কার্যকর করা থেকে আলাদা করা: আপনার system প্রশ্ন পাঠায়, OPA উত্তর দেয়। একে অনেকে PDP (decision point) আর PEP (enforcement point) ভাগ বলেন, যা XACML-এর মতো পুরোনো access-control মান থেকে এসেছে। Plan JSON-এর after_unknown অংশ জানায় কোন মান apply-এর আগে অজানা; এই মানের ওপর নিয়ম বসালে policy হয় ভুল করে পাস করে, নয় ভুল করে আটকায়। Terraform-এর JSON আউটপুটের নিজস্ব format_version আছে, তাই policy tool আপগ্রেডের আগে আপনার নিয়মগুলোর test চালানো জরুরি।
পরীক্ষা ও ইন্টারভিউতে:
checkআরprecondition-এর পার্থক্য?checkব্যর্থ হলে শুধু warning;preconditionব্যর্থ হলে error, resource তৈরি হয় না।- Plan JSON কেন? HCL-এ variable আর module গোঁজা থাকে; plan-এ চূড়ান্ত মান থাকে, তাই policy বেশি নির্ভুল।
- Saved plan কেন apply করবেন? যা review হয়েছে ঠিক তাই চলবে; state বদলালে Terraform stale বলে থামবে।
prevent_destroyকি যথেষ্ট? না। ব্লক মুছলেই নিয়ম চলে যায়; provider-এর deletion protection আর backup লাগে।- Mock test-এর সীমা কী? এটি আপনার যুক্তি দেখে, আসল API-র আচরণ নয়; তাই অল্প কিছু integration test-ও লাগে।
১০ · AI-এর যুগে IaC-র পরীক্ষা
AI কয়েক সেকেন্ডে শত লাইন HCL লিখে দেয়, আর এই পর্বের সব ধাপ ঠিক সেই লেখা যাচাইয়ের জন্যই কাজে লাগে। আমরা শুরুতেই দেখেছি, দেখতে বিশ্বাসযোগ্য file_mode আসলে কোনো argument নয়। AI-এর লেখা policy-ও পরীক্ষা ছাড়া নেবেন না। AI প্রায়ই খোলা 0.0.0.0/0, * অনুমতি, public storage, code-এ বসানো secret আর latest tag দেয়। সে খরচের কথা ভাবে না, আর সতর্কতা ছাড়াই terraform destroy বা rm -rf বলে দিতে পারে।
AI-কে কোড লিখতে বলার সময় যাচাইয়ের দায়িত্বও ভাগ করে দিন। শেষ দুটি লাইনই আসল কাজ করে।
AI-এর লেখা HCL হাতে পেলে
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।
প্র ০১Plan review মানুষের হাতে রাখলেই কি নিরাপদ? যন্ত্র সব পরীক্ষা করে দিলে মানুষের কাজটি আসলে কী?
“একজন মানুষ দেখেছেন” কথাটি শুনতে ভরসার, কিন্তু মনোযোগ সীমিত। ১৯৮৩ সালে Lisanne Bainbridge তাঁর “Ironies of Automation” প্রবন্ধে একটি পরিচিত সমস্যা দেখান: যন্ত্র বেশিরভাগ কাজ করলে মানুষের ভাগে পড়ে নজরদারি, অথচ দীর্ঘ সময় স্বাভাবিক কিছু দেখতে দেখতে মানুষ অস্বাভাবিকটি চিনতে কম পারে। Plan review-তে এটি রোজের ঘটনা। প্রতিদিন দশটি নিরীহ plan দেখলে এগারোতম plan-এর একটি -/+ চোখে পড়ে না।
আরেকটি ফাঁদ হলো দ্রুততার চাপ। বারবার অপেক্ষা করতে হলে approval একটি অভ্যাসের ক্লিক হয়ে যায়। তখন গেটটি নিরাপত্তা না দিয়ে শুধু ধীর করে।
তাই কাজ ভাগ করতে হয়। যা নিয়মে বাঁধা যায়, তা যন্ত্রের: label আছে কি না, port খোলা কি না, কতগুলো জিনিস মুছছে। মানুষের জন্য থাকে যা নিয়মে বাঁধা যায় না: “এই বদলটি কি PR-এর উদ্দেশ্যের সাথে মেলে?”, “এই সময়ে এটি করা ঠিক?”, “ব্যর্থ হলে ফেরার পথ কী?” একটি সহজ কৌশল হলো plan-এর সারাংশ ওপরে দেখানো: কতগুলো delete, replace, কোন stateful resource. নিরীহ plan স্বয়ংক্রিয়ভাবে পার হলে মানুষের মনোযোগ বাঁচে ঝুঁকির plan-এর জন্য।
তবু স্বয়ংক্রিয় পাসে সতর্ক থাকুন, কারণ risk score নিজেও ভুল করে। আমরা দেখেছি ১২টি tag-এর score বড় হয়ে গেল। উল্টো দিকে একটি সামান্য security group বদলের score ছোট হলেও পুরো সার্ভিসকে অচল করতে পারে। তাই score-এর ওজন নিয়মিত পুরোনো incident-এর সাথে মিলিয়ে ঠিক করা ভালো।
মূল উপলব্ধি: মানুষকে যন্ত্রের কাজ দিলে দুজনেই খারাপ করে। যন্ত্র নিয়ম দেখুক, মানুষ উদ্দেশ্য দেখুক, আর মানুষের সামনে আসুক শুধু সেই তথ্য যেখানে তার বিচার দরকার।
প্র ০২IaC-তে mock test আর আসল integration test-এর ভাগ কেমন হওয়া উচিত? Software-এর test pyramid কি এখানে হুবহু খাটে?
Mike Cohn তাঁর Succeeding with Agile (২০০৯) বইয়ে test pyramid জনপ্রিয় করেন: অনেক unit test, কিছু integration test, অল্প end-to-end test. সাধারণ software-এ এটি ভালো খাটে, কারণ সেখানে আপনার নিজের যুক্তি অনেক। Terraform code-এ যুক্তি প্রায়ই পাতলা। বেশিরভাগ লাইন “এই setting-টি এই মান” বলে। এমন code-এ mock test লিখলে অনেক সময় আপনি একই কথা দুবার লেখেন, আর test শুধু code-এর আয়না হয়ে যায়।
Mock কোথায় সত্যিই দামি? যেখানে module-এ শর্ত, loop, হিসাব বা নিয়ম আছে। যেমন আমাদের prod-এর “কমপক্ষে ২” নিয়ম। সেখানে এক সেকেন্ডের test আসল বাগ ধরেছে, আর কোনো খরচ হয়নি। expect_failures দিয়ে validation বেঁচে আছে কি না, সেটিও mock-এ দেখা যায়।
আসল ঝুঁকি থাকে অন্য জায়গায়: IAM অনুমতি কম কি না, দুটি resource-এর ক্রম ঠিক কি না, provider-এর আসল ডিফল্ট কী, region-এ জিনিসটি পাওয়া যায় কি না। Mock এগুলো জানে না, কারণ সে শুধু নকল মান ফেরত দেয়। তাই IaC-র আকৃতি pyramid-এর চেয়ে একটু মোটা মাঝখানের মতো: scanner আর policy-র চওড়া ভিত, তার ওপরে কিছু লক্ষ্য করা integration test.
ইতিহাসও এটি দেখায়। Gruntwork-এর Terratest (Go-তে লেখা) অনেক বছর ধরে আসল infrastructure বানিয়ে পরীক্ষা করার জনপ্রিয় পথ ছিল। ২০২৩ সালে terraform test আর ২০২৪ সালে mock আসায় HCL-এই সহজ test লেখা গেল, কিন্তু আসল পরীক্ষার দরকার ফুরায়নি। Integration test-এর দাম আছে: মিনিট লাগে, টাকা লাগে, আর মাঝপথে থেমে গেলে জিনিস পড়ে থাকে। তাই এগুলো প্রতিটি PR-এ না চালিয়ে module বদলালে বা রাতে চালানো সাধারণ রীতি।
মূল উপলব্ধি: Test-এর আকৃতি ঠিক করে আপনার ঝুঁকি কোথায়। যুক্তির ঝুঁকিতে mock, cloud-এর আচরণের ঝুঁকিতে আসল পরীক্ষা, আর দুটির ওপরে সস্তা scanner.
প্র ০৩CI-তে policy থাকলে cloud account-এর guardrail কি বাড়তি? একই নিয়ম একাধিক জায়গায় রাখা অপচয় নয় কি?
Security-তে একটি পুরোনো কথা আছে: যে নিয়ম একটি মাত্র দরজায় পাহারা দেয়, সেটি অন্য দরজা দিয়ে এড়ানো যায়। CI-র policy শুধু সেই পরিবর্তন দেখে যা CI দিয়ে আসে। কেউ console-এ হাতে bucket খুলে দিলে, অন্য একটি script চালালে, বা incident-এর সময় সরাসরি CLI ব্যবহার করলে CI কিছুই জানবে না।
S3-এর ইতিহাস ভালো উদাহরণ। খোলা bucket থেকে ডেটা ফাঁসের অনেক খবর আসার পর AWS ২০১৮ সালে Block Public Access যোগ করে, যা account স্তরে বসিয়ে ভুল bucket setting-কেও অকেজো করে দেওয়া যায়। পরে ২০২৩ সাল থেকে নতুন bucket-এ এটি ডিফল্টভাবে চালু হয়। অর্থাৎ শেষ পর্যন্ত সমস্যাটি সবচেয়ে নিচের স্তরে নিরাপদ ডিফল্ট দিয়ে কমানো হলো, মানুষকে আরও সতর্ক হতে বলে নয়।
তাহলে নিচের guardrail থাকলে CI-র policy কেন? কারণ অভিজ্ঞতার পার্থক্য। Organization policy যখন apply-এর মাঝখানে ব্যর্থ হয়, তখন আধা-তৈরি অবস্থা থাকে, আর error প্রায়ই অস্পষ্ট AccessDenied. CI-র policy একই কথা PR-এ, plan-এর আগেই, পরিষ্কার ভাষায় বলে। একটি লেখককে শেখায়, অন্যটি সিস্টেমকে রক্ষা করে।
এটিই defense in depth, যা পর্ব ৩৪-এ দেখেছেন। তবে সত্যিকারের অপচয়ও আছে: একই নিয়ম তিনটি ভাষায় তিনবার লেখা, যেগুলো ধীরে ধীরে আলাদা হয়ে যায়। সমাধান হলো একটি জায়গায় নিয়মের অর্থ লিখে রাখা, প্রতিটি নিয়মের একটি মালিক আর একটি ID রাখা, আর detection-এর জন্য চলমান সিস্টেমেও একটি পাহারা রাখা। Kubernetes-এ এই ভূমিকা admission control নেয়।
মূল উপলব্ধি: বাঁয়ের পরীক্ষা দ্রুত জানায়, ডানের পরীক্ষা নিশ্চয়তা দেয়। একই নিয়ম দুই জায়গায় থাকা অপচয় নয়, যদি দুটির কাজ আলাদা হয়।
প্র ০৪Policy খুব কড়া হলে দলগুলো পথ এড়াতে শুরু করে। ব্যতিক্রম আর কঠোরতার ভারসাম্য কীভাবে রাখবেন?
ধরুন একটি নতুন scanner প্রথম দিনেই ৪০০টি সতর্কতা দিল আর pipeline বন্ধ করে দিল। দুই সপ্তাহ পরে কী হবে সবাই জানেন: কেউ || true বসাবেন, কেউ পুরো নিয়ম skip করবেন, আর কেউ console-এ চলে যাবেন। নিয়ম খাতায় থাকবে, নিরাপত্তা থাকবে না। তাই policy একটি সামাজিক ব্যবস্থাও, শুধু প্রযুক্তি নয়।
একটি প্রমাণিত পথ হলো ধাপে ধাপে চালু করা। Sentinel-এর advisory, soft-mandatory, hard-mandatory স্তরগুলো এই চিন্তা থেকেই। Kyverno-তে এই একই চিন্তা audit আর enforce মোডে দেখা যায়, আর Gatekeeper-এ dry-run ও deny-তে। প্রথমে শুধু দেখুন কতগুলো সতর্কতা আসে আর কতগুলো ভুল। ভুলগুলো ঠিক করুন, পুরোনো জিনিসের তালিকা বানান, নতুন জিনিসে আগে কঠোর হোন, তারপর পুরোনোতে।
Exception-কে লুকিয়ে রাখবেন না, code-এ আনুন। একটি ভালো exception-এ থাকে নির্দিষ্ট resource, নির্দিষ্ট নিয়ম, কারণ, অনুমোদনকারী আর মেয়াদ। আমাদের simulator-এর until ক্ষেত্রটি ঠিক এই কাজ করে। মেয়াদ না থাকলে “সাময়িক” ছাড় চিরস্থায়ী হয়ে যায়। Exception-এর সংখ্যা একটি ভালো মাপকাঠিও: কোনো নিয়মে খুব বেশি exception মানে নিয়মটিই হয়তো ভুল।
আর গেটের নিজের বাগও ভুললে চলবে না। ২০২১ সালের Facebook outage-এর বিবরণে Meta লেখে, ভুল কমান্ড থামানোর জন্য একটি audit tool ছিল, কিন্তু তার বাগের কারণে সেটি কাজ করেনি। তাই policy-র জন্যও test চাই: একটি ভালো plan যা পাস করবে, একটি খারাপ plan যা ব্যর্থ হবে। OPA-তে opa test আর Conftest-এ conftest verify দিয়ে নিয়মের unit test লেখা যায়। ভাঙা গেট আর গেট না থাকা প্রায় একই, বরং আগেরটি বেশি বিপজ্জনক, কারণ সবাই ভরসা করে থাকে।
মূল উপলব্ধি: ভালো policy মানুষকে সহজ ও নিরাপদ পথ দেখায়। সে পথ কঠিন হলে মানুষ অন্য পথ খুঁজবে। পরিমাপ করুন, ধাপে ধাপে কঠোর করুন, আর ব্যতিক্রমের মেয়াদ রাখুন।
প্র ০৫Merge-এর আগে apply, নাকি merge-এর পরে apply? দুই পক্ষের যুক্তি কী, আর এটি GitOps-এর সাথে কীভাবে মেলে?
এটি IaC দলের একটি ক্লাসিক তর্ক। সাধারণ software-এ merge আগে, deploy পরে। Infrastructure-এ সমস্যা হলো, apply ব্যর্থ হতে পারে এমন কারণে যা plan দেখেনি: quota শেষ, নাম আগে থেকেই আছে, permission কম। তখন main বলে একটি কথা, আর আসল সিস্টেম আরেক অবস্থায়।
Apply-before-merge: Atlantis এই ধারা জনপ্রিয় করে। এটি Hootsuite-এ তৈরি হয়ে ২০১৭ সালে open source হয়। PR-এ comment করলে plan হয়, approval-এর পরে comment করলে apply হয়, সফল হলে merge. সুবিধা: main সবসময় যা সত্যিই চলেছে তাই। অসুবিধা: দুটি PR একই জিনিসে হাত দিলে দ্বন্দ্ব হয়। তাই Atlantis নিজে project ধরে lock রাখে। আর merge না হওয়া code production-এ চলে গেছে, এই অস্বস্তিও থাকে।
Apply-after-merge: এতে Git-এর ইতিহাস পরিষ্কার, আর branch protection সহজে কাজ করে। কিন্তু apply ব্যর্থ হলে main-ই ভাঙা, তখন একটি নতুন PR লাগে। PR-এর plan আর merge-এর পরের plan আলাদা হতে পারে, কারণ এর মধ্যে অন্য PR ঢুকেছে। আমাদের workflow-এ তাই merge-এর পরে নতুন plan হয়, আর production গেটে সেটিই দেখতে হয়।
দুই পথেই saved plan কাজে লাগে, আর stale plan-এর সুরক্ষা ঝুঁকি কমায়। ছোট দলে apply-after-merge আর একটি মাত্র apply queue সহজ। অনেক দল ও অনেক state থাকলে PR-ভিত্তিক automation বেশি কাজে দেয়।
পর্ব ৬০-এ দেখবেন GitOps আরেক ধাপ এগিয়ে যায়: একটি agent নিয়মিত Git-এর সাথে চলমান অবস্থা মেলায়, আর main-ই একমাত্র সত্য। Kubernetes-এ এই reconcile loop স্বাভাবিক, কারণ cluster নিজেই এই মডেলে চলে। Terraform-এ state আর plan-এর ধাপ থাকায় এটি হুবহু খাটে না, যদিও একই লক্ষ্যে কিছু controller-ভিত্তিক tool তৈরি হয়েছে।
মূল উপলব্ধি: দুটি পথই ঠিক, শুধু ঝুঁকি আলাদা জায়গায় থাকে। আসল প্রশ্ন: ব্যর্থ apply-এর পরে Git আর সিস্টেমের অমিল আপনি কোথায় সইতে রাজি?
অনুশীলন
প্রথম তিনটি কাগজে-কলমেই হয়। চতুর্থটিতে কোনো cloud account লাগে না।
-
ঝুঁকির হিসাব (সংখ্যাগত): ওজন ধরুন create ১, update ২, replace ৮, delete ১০; stateful resource replace বা delete হলে প্রতিটিতে বাড়তি ২৫; পরিবেশের গুণিতক dev ১, staging ২, prod ৪. একটি prod plan-এ ২টি create, ৩টি update, ১টি replace আছে, আর কোনো stateful resource নেই। Score কত? Replace-টি database হলে কত হবে? ২০-এর নিচে low, ৮০-এর নিচে medium, ৮০ বা তার ওপরে high ধরলে দুই ক্ষেত্রে band কী?
Base = ২×১ + ৩×২ + ১×৮ = ২ + ৬ + ৮ = ১৬. Prod-এ ১৬ × ৪ = ৬৪, অর্থাৎ medium.
Database হলে (১৬ + ২৫) × ৪ = ৪১ × ৪ = ১৬৪, অর্থাৎ high. একটি মাত্র শব্দের পার্থক্যে score আড়াই গুণের বেশি হলো। এটি ইচ্ছাকৃত, কারণ stateful জিনিস ফেরানো কঠিন।
-
ফল আগে বলুন: একটি নতুন খালি state-এ পরপর এই কমান্ডগুলো চলল: (১)
terraform plan -out=x.tfplan, যা ১টি resource তৈরি করবে; (২)terraform apply x.tfplan; (৩)terraform apply x.tfplanআবার; (৪)terraform plan -detailed-exitcode. প্রতিটির শেষে resource-এর সংখ্যা, আর (৩) ও (৪)-এর ফল কী হবে?(১)-এর পরে resource ০, কারণ plan কিছু তৈরি করে না। (২)-এর পরে resource ১, আর state-এর serial বেড়েছে। (৩) ব্যর্থ হবে, কারণ
x.tfplanপুরোনো state দেখে বানানো:Saved plan is stale, exit code ১, resource তবু ১. একটি saved plan মাত্র একবার চলে। (৪)-এ code আর আসল অবস্থা মিলে গেছে, তাইNo changes, exit code ০. -
Pipeline-এর সমালোচনা: একটি দলের workflow:
main-এ push হলেcheckov -d . || true, তারপরterraform apply -auto-approve, প্রতিটি repository-তে একই admin key secret হিসেবে রাখা। State local ফাইলে, runner-এ। কোথায় কোথায় সমস্যা, আর আপনি কী বদলাবেন?|| truescanner-এর ব্যর্থতা গিলে ফেলে। পাহারাদার আছে, বাঁশি নেই। কারণসহ skip রাখুন।plan -outনেই, PR review নেই, তাই কেউ জানে না কী হবে। PR-এ plan, policy আর approval বসান।-auto-approveনতুন plan বানিয়েই চালায়, যা কেউ দেখেনি। শুধু saved plan apply করুন।- স্থায়ী admin key: একটি ফাঁসেই সব শেষ। OIDC-র স্বল্পমেয়াদি credential আর plan ও apply-এর আলাদা, সীমিত role নিন।
- Runner-এ local state: runner মুছলে state হারায়, আর lock নেই। Lock-সহ remote backend ব্যবহার করুন (পর্ব ৫৩)।
- Destroy সুরক্ষা, drift check আর test কিছুই নেই। মই-এর নিচ থেকে একে একে যোগ করুন।
-
নিজে চালান (ঐচ্ছিক, খরচ নেই): Terraform ইনস্টল থাকলে একটি খালি ফোল্ডারে
hashicorp/localprovider দিয়ে একটিlocal_fileবানান, যার content-এvar.envবসে।env-এvalidationদিন, আর একটি.tftest.hcl-এexpect_failuresদিয়ে ভুল মান পরীক্ষা করুন। শেষে কী মুছবেন?এই কাঠামোতে
tests/env.tftest.hclলিখুন:run "bad_env_rejected" { command = plan variables { env = "qa" } expect_failures = [var.env] }এরপর
terraform initআরterraform testচালান।validation-এcontains(["dev", "prod"], var.env)থাকলে test পাস করবে। এইplan-ভিত্তিক test কিছু তৈরি করে না। আগেapplyকরে থাকলে ফোল্ডারে একটি ছোট ফাইল আছে;terraform destroyচালিয়ে, পরে ফোল্ডারটি মুছলেই পরিষ্কার। কোনো cloud account লাগে না, তাই খরচও নেই।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি নিরাপদ Terraform pipeline-এর ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
terraform fmt -checkআরvalidateterraform testআর scannerterraform plan -out=tfplanterraform show -jsonকরে policy চালানো- Plan-এ মানুষের approval
terraform apply tfplan
Terraform-এর check ব্লকের assert ব্যর্থ হলে কী ঘটে?
- Run সাথে সাথে error দিয়ে থামে
- আগের apply স্বয়ংক্রিয়ভাবে rollback হয়
- Warning দেখায়, কিন্তু run শেষ হয়
- কিছুই দেখায় না
একটি resource-এ postcondition আছে, যা শুধু apply-এর পরে জানা যায় এমন একটি মান দেখে। Apply-এর পরে শর্তটি ব্যর্থ হলে resource-টির কী হয়?
- Resource তৈরিই হয়নি
- Resource থেকে যায়, কিন্তু run error দিয়ে থামে
- Terraform resource-টি মুছে ফেলে
- শুধু warning আসে, পরের সব কাজ চলে
Terraform plan-এর ওপর policy চালাতে terraform show -json tfplan কেন ব্যবহার করা হয়?
- এতে plan দ্রুত তৈরি হয়
- এটি সব secret মুছে ফেলে
- JSON ফাইলটি পরে apply করার জন্য লাগে
- Saved plan binary, আর JSON যন্ত্রে পড়া যায়
terraform plan -detailed-exitcode exit code ২ দিলে তার অর্থ কী?
- Plan সফল, আর কিছু বদল বাকি আছে
- Plan error দিয়ে ব্যর্থ হয়েছে
- কোনো বদল নেই
- Plan-এর পরিবর্তনগুলো apply হয়ে গেছে
একটি resource-এ prevent_destroy = true ছিল। কেউ পুরো resource ব্লকটি ফাইল থেকে মুছে PR খুললেন। Plan-এ কী ঘটবে?
- Instance cannot be destroyed error আসবে
- Plan-এ resource-টি destroy দেখাবে, কোনো error ছাড়াই
- Resource থাকবে, শুধু state থেকে সরে যাবে
- terraform validate ব্যর্থ হবে
দুজন প্রকৌশলীর একজন saved plan বানালেন, অন্যজন এর মধ্যে একই state-এ apply করলেন। প্রথমজন saved plan apply করলে কী ঘটবে?
- Plan চলবে, দ্বিতীয়জনের বদল মুছে যাবে
- Terraform নিজে থেকে নতুন plan বানিয়ে চালাবে
- “Saved plan is stale” error দিয়ে থামবে
- দুটি বদল স্বয়ংক্রিয়ভাবে মিশে যাবে
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- পরীক্ষা মই-এর মতো: fmt, validate, scanner, plan ও policy, test, drift. সস্তা ধাপ আগে।
validationইনপুট,preconditionআগের অনুমান,postconditionপরের ফল দেখে।checkশুধু সতর্ক করে।- Plan JSON policy-কে চূড়ান্ত মান দেখায়। এটি আর plan ফাইল গোপন তথ্যের মতো রাখুন।
- Apply করুন শুধু review করা saved plan. State বদলালে সেটি stale হয়।
prevent_destroyব্লক মুছলেই চলে যায়। Provider-এর সুরক্ষা আর যাচাই করা backup রাখুন।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৫৬ · Configuration management — Ansible, Packer আর immutable infrastructureপরবর্তী পর্বServer-এর ভেতরের কাজ কোড দিয়ে সামলানো।
- ক্লাউড কম্পিউটিং ও DevOps · কোর্স হোম৮১টি পর্বপুরো কোর্সের রোডম্যাপ।
- পর্ব ৩২ · IAM policy evaluationআগের পর্বPolicy linter, simulator আর ভুল অনুমতি ধরা।
- Software Engineering · Test pyramid ও integrationপাশের ট্র্যাকপরীক্ষার স্তর ভাগের সাধারণ তত্ত্ব।
- Software Engineering · Continuous Integrationপাশের ট্র্যাকPipeline, matrix আর required check.
- System Design · Deployment ও Releaseগভীরে যানRelease-এর ঝুঁকি কমানোর নকশা।