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

Configuration management — Ansible, Packer আর immutable infrastructure

Configuration Management — Ansible, Packer and Immutable Infrastructure

Taught in Bangla, this lesson separates provisioning from configuration, then covers agentless SSH, inventory, playbooks, tasks and modules, reading changed and ok to judge idempotence, handlers, facts, Jinja2 and roles, safe changes with check, diff, limit and serial, Ansible Vault, mutable versus immutable servers, Packer image pipelines, and where configuration management still fits, with simulators.

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

  • Inventory, ad-hoc command, playbook, play, task আর module-এর পার্থক্য
  • changed আর ok পড়ে একটি task idempotent কি না বলা
  • Handler, variable, fact, Jinja2 template আর role দিয়ে পরিষ্কার playbook লেখা
  • --check, --diff, --limit, serial আর Vault দিয়ে নিরাপদে বদল আনা
  • Mutable আর immutable পথের হিসাব, Packer-এর image pipeline, আর configuration management এখনো কোথায় লাগে

১ · Terraform জমি বানায়, Ansible ঘর সাজায়

একটি বাড়ির কথা ভাবুন। প্রথমে জমি, ভিত, দেয়াল আর ছাদ। তারপর ভেতরের কাজ: ফ্যান লাগানো, আলমারি বসানো, রান্নাঘর গোছানো। দুটি কাজ আলাদা, মিস্ত্রিও আলাদা।

Cloud-এও তাই। Terraform (পর্ব ৫২) VM, network আর database তৈরি করে। এটি provisioning. কিন্তু নতুন VM একটি খালি বাড়ি। এতে nginx নেই, config নেই, app user নেই। এই ভেতরের কাজের নাম configuration management, সংক্ষেপে CM. এই পর্বের প্রধান tool Ansible.

প্রশ্নTerraformAnsible
মূল কাজResource তৈরি, বদল, মোছাServer-এর ভেতরের software আর config
কথা বলে কার সঙ্গেCloud-এর APIপ্রধানত server-এর SSH
অবস্থা মনে রাখেState ফাইলেকোনো state ফাইল নেই; প্রতিবার গিয়ে দেখে
লেখার ধরনপুরোপুরি declarativeTask-এর ক্রম আছে, প্রতিটি task declarative হতে পারে
মুছে ফেলাCode থেকে সরালে plan মুছতে চায়Task সরালে কিছুই মোছে না; মুছতে আলাদা task লাগে

শেষ সারিটি খেয়াল করুন। এটি প্রায়ই ভুল বোঝা হয়। Ansible-এ একটি task মুছে দিলে server থেকে তার কাজ মুছে যায় না। মুছতে চাইলে state: absent লেখা একটি task দরকার। সীমারেখা অবশ্য পাকা নয়: Ansible-ও cloud API ডাকতে পারে, Terraform-ও provisioner দিয়ে কমান্ড চালাতে পারে। তবে পরিষ্কার দল এই দুটি কাজ আলাদা রাখে।

দুটি একসঙ্গে কীভাবে চলে? একটি সাধারণ ধারা হলো: Terraform VM বানিয়ে তার address output দেয়। সেখান থেকে inventory তৈরি হয়, অথবা dynamic inventory plugin সরাসরি cloud-কে জিজ্ঞেস করে tag ধরে host খুঁজে নেয়। তারপর Ansible সেই host-গুলোতে চলে। অর্থাৎ Terraform-এর শেষ যেখানে, Ansible-এর শুরু সেখানে।

এই জগতের শুরু ১৯৯৩ সালে, Mark Burgess-এর CFEngine দিয়ে। এরপর Puppet (২০০৫), Chef (২০০৯), Salt (২০১১)। Ansible লেখেন Michael DeHaan, প্রথম প্রকাশ ২০১২ সালে; ২০১৫ সালে Red Hat প্রকল্পটির কোম্পানি কিনে নেয়। Immutable infrastructure শব্দটি ছড়ায় ২০১৩ সালে Chad Fowler-এর একটি লেখা থেকে। একই বছর Mitchell Hashimoto বের করেন Packer.

২ · Agentless SSH আর inventory

ধরুন একটি কাল্পনিক অনলাইন বাজার অ্যাপের দুটি web server আছে। Ansible চলে একটি control node থেকে: আপনার ল্যাপটপ, একটি bastion বা CI runner. যেসব server সে সামলায় সেগুলো managed node.

Ansible agentless. Server-এ আলাদা কোনো সবসময়-চলা program লাগে না। Linux server-এ লাগে দুটি জিনিস: SSH ঢোকার পথ, আর Python. Control node SSH দিয়ে ছোট একটি Python module পাঠায়, সেটি চালায়, JSON ফল নেয়, আর অস্থায়ী ফাইল মুছে দেয়। কাজ শুরু করে control node, তাই এটি push পদ্ধতি।

Control node থেকে host-এ push

SSH দিয়ে module যায়, JSON ফল ফেরে; host-এ agent থাকে না

Ansible control node থেকে managed node-এ SSH দিয়ে push Control node ল্যাপটপ, bastion বা CI runner inventory playbook SSH SSH SSH web1Python moduleচলে, মুছে যায় web2Python moduleচলে, মুছে যায় web3Python moduleচলে, মুছে যায় কাজ শুরু করে control node, এটিই push ফল JSON হয়ে একই SSH পথে ফেরে
ছবি ৫৬.১ — Control node একসঙ্গে কয়েকটি host-এ কাজ করে। Host-এ স্থায়ী কিছু থাকে না।

বিয়েবাড়ির ক্যাটারিং ম্যানেজারের কথা ভাবুন। প্রতিটি টেবিলে আলাদা স্থায়ী লোক নেই। ম্যানেজার হাতে তালিকা নিয়ে টেবিলে টেবিলে গিয়ে একই নির্দেশ দেন, কাজ দেখে পরের টেবিলে যান। তালিকাটি inventory, নির্দেশের খাতাটি playbook.

Inventory হলো host-এর তালিকা, দলে ভাগ করা। এই পর্বের সব আউটপুট একটি আসল run থেকে নেওয়া: ansible-core ২.১৯.৩, Docker Engine ২৯-এ চলা দুটি Alpine ৩.২০ container, আর একটি আলাদা control container. SSH সত্যিই চলেছে, একটি throwaway key দিয়ে। সময়, id আর host-এর ক্রম প্রতি run-এ আলাদা হতে পারে। কোনো cloud account লাগেনি।

[web]
web1 ansible_host=abcl-an-web1
web2 ansible_host=abcl-an-web2

[web:vars]
ansible_user=deploy
ansible_ssh_private_key_file=~/.ssh/abcl_demo
$ ansible-inventory --graph
@all:
  |--@ungrouped:
  |--@web:
  |  |--web1
  |  |--web2

$ ansible web -m ansible.builtin.ping
web1 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
web2 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

$ ansible web -m ansible.builtin.command -a "cat /etc/alpine-release"
web2 | CHANGED | rc=0 >>
3.20.10
web1 | CHANGED | rc=0 >>
3.20.10

ওপরের দুটি কমান্ড ad-hoc command: playbook ছাড়া এক লাইনের কাজ। ping এখানে নেটওয়ার্কের ICMP ping নয়; এটি যাচাই করে SSH আর Python ঠিক আছে কি না। আর দেখুন, cat কিছুই বদলায়নি, তবু লেখা CHANGED. কারণ command module জানে না কমান্ডটি কী করেছে, তাই নিরাপদে ধরে নেয় “বদলেছে”। পরের অংশে এটি বড় বিষয় হবে।

Ad-hoc কমান্ড কখন কাজে লাগে? ঘটনার সময় দ্রুত তথ্য নিতে: ৫০টি server-এ একটি package-এর version কত, কোন server-এর disk প্রায় ভরা। কিন্তু কিছু বদলানোর কাজ ad-hoc দিয়ে নয়, কারণ এর কোনো রেকর্ড Git-এ থাকে না। আর SSH-এর পথ আগে থেকে পরিষ্কার থাকা চাই: private subnet-এর server-এ সাধারণত bastion হয়ে যেতে হয়, যা ansible_ssh_common_args বা SSH config দিয়ে ঠিক করা যায়।

আমাদের lab-এ host_key_checking = False দেওয়া ছিল, কারণ container প্রতিবার নতুন key পায়। আসল server-এ এটি বন্ধ করবেন না। পরিবর্তে আগে থেকে known_hosts ভরে নিন (পর্ব ১২)। আর ansible_user হোক সাধারণ user, sudo-র অনুমতিসহ; কাজের সময় root হওয়ার উপায় become.

৩ · Playbook, play, task আর module

Ad-hoc কমান্ড মনে থাকে না, review-ও হয় না। আসল কাজ লেখা হয় playbook-এ, একটি YAML ফাইলে, যা Git-এ থাকে। চারটি শব্দের ক্রম মনে রাখুন:

  • Playbook: পুরো ফাইল। এতে এক বা একাধিক play থাকে।
  • Play: কোন host-এ কোন কাজগুলো হবে, তার একটি জোড়া।
  • Task: একটি কাজের নাম আর একটি module-এর ডাক।
  • Module: আসল কাজের ছোট program, যেমন file, template, user.
---
- name: Configure the web tier
  hosts: web
  become: true
  tasks:
    - name: Install nginx
      ansible.builtin.command:
        cmd: apk add --no-cache nginx
        creates: /usr/sbin/nginx

    - name: Web root exists
      ansible.builtin.file:
        path: /var/www/bazar
        state: directory
        mode: "0755"

    - name: Home page from template
      ansible.builtin.template:
        src: index.html.j2
        dest: /var/www/bazar/index.html
        mode: "0644"

    - name: Nginx config from template
      ansible.builtin.template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        mode: "0644"
        validate: nginx -t -c %s
      notify: Reload nginx

    - name: Is nginx running?
      ansible.builtin.command: pgrep nginx
      register: nginx_proc
      changed_when: false
      failed_when: false
      check_mode: false

    - name: Start nginx if it is not running
      ansible.builtin.command: nginx
      when: nginx_proc.rc != 0

  handlers:
    - name: Reload nginx
      ansible.builtin.command: nginx -s reload

এখানে দুটি কথা সৎভাবে বলা দরকার। আসল Debian বা RHEL server-এ ansible.builtin.apt বা dnf আর service module ব্যবহার করতেন। আমাদের image-এ শুধু ansible-core আছে; Alpine-এর apk module থাকে আলাদা collection-এ, আর container-এ service manager-ও নেই। তাই এখানে command, সঙ্গে creates: “এই ফাইল থাকলে কমান্ড চলবে না”।

এবার এই playbook দুটি নতুন host-এ চলছে। “পরে” চেপে দেখুন, Ansible একটি task সব host-এ শেষ করে তবেই পরের task-এ যায়। এটি default linear কৌশল। একসঙ্গে কত host-এ কাজ হবে তা ঠিক করে forks, যার default মান ৫.

0Gathering Facts (setup)1Install nginx2Web root exists3Home page from template4Nginx config from template5Is nginx running?6Start nginx if not running7HANDLER Reload nginx8PLAY RECAP
এই task-এর ফল
web1
web2
মোট ok
মোট changed
Handler-এর তালিকা
  1. Fact সংগ্রহ। প্রথম task playbook-এ লেখা নেই। Ansible নিজে setup module চালিয়ে OS, hostname ইত্যাদি জেনে নেয়। কিছু বদলায়নি, তাই ok.
  2. nginx বসল। /usr/sbin/nginx ছিল না, তাই creates শর্ত মেলেনি, কমান্ড চলল। ফল changed. মনে রাখুন, recap-এর ok-এ changed task-ও গোনা হয়, তাই ok ২, changed ১।
  3. ফোল্ডার তৈরি। file module দেখল ফোল্ডার নেই, বানাল। দুটি host-এই changed.
  4. Home page বসল। template প্রতিটি host-এর জন্য আলাদা করে ফাইল বানায়, তাই web1 আর web2-র লেখা আলাদা হতে পারে।
  5. Config বসল, handler তালিকায় উঠল। validate আগে অস্থায়ী কপিতে nginx -t চালিয়েছে, পাস করায় ফাইল বদলেছে। notify তখন “Reload nginx”-কে তালিকায় তুলল। কিন্তু handler এখনই চলে না।
  6. শুধু দেখা। pgrep ফল দিল nginx চলছে না। changed_when: false থাকায় এটি ok; তথ্য নেওয়ায় কিছু বদলায় না। ফলাফল জমা হলো nginx_proc-এ।
  7. শর্ত মিলল। nginx_proc.rc শূন্য নয়, তাই when সত্য। nginx চালু হলো। changed, মোট changed ৫।
  8. Handler চলল। সব task শেষে, play-এর শেষে handler চলে। তালিকা খালি হলো। মোট ok ৮, changed ৬।
  9. Recap. দুটি host-এর হিসাব একই: ok=8 changed=6, আর failed=0. এই এক লাইনই একটি run-এর সারাংশ।

৪ · Idempotent module বনাম shell

পর্ব ৫১-এ idempotency দেখেছেন: একই কাজ বারবার করলেও ফল একবার করার সমান। রেসিপি খাতায় যদি লেখা থাকে “এক চামচ লবণ দিন”, দুবার পড়লে দুই চামচ পড়বে। যদি লেখা থাকে “লবণ এক চামচ হওয়া চাই”, তাহলে আগে চেখে নিতে হবে। ভালো Ansible module দ্বিতীয় ধরনের। সে আগে দেখে, তারপর দরকার হলে বদলায়।

নিচের playbook-এর প্রথম তিনটি task module দিয়ে লেখা। চতুর্থটি ইচ্ছা করে সাধারণ shell.

---
- name: Prepare app servers
  hosts: web
  become: true
  gather_facts: false
  tasks:
    - name: App user exists
      ansible.builtin.user:
        name: bazar
        shell: /sbin/nologin

    - name: App folder exists
      ansible.builtin.file:
        path: /opt/bazar
        state: directory
        owner: bazar
        mode: "0755"

    - name: Limit is in app.conf (idempotent)
      ansible.builtin.lineinfile:
        path: /opt/bazar/app.conf
        line: max_conn=100
        create: true
        mode: "0644"

    - name: Limit is appended to naive.conf (naive)
      ansible.builtin.shell: echo "max_conn=100" >> /opt/bazar/naive.conf
# First run
TASK [App user exists] *********************************************************
changed: [web2]
changed: [web1]
…
PLAY RECAP *********************************************************************
web1 : ok=4    changed=4    unreachable=0    failed=0    skipped=0    rescued=0 …
web2 : ok=4    changed=4    unreachable=0    failed=0    skipped=0    rescued=0 …

# Second run
TASK [Limit is in app.conf (idempotent)] ***************************************
ok: [web2]
ok: [web1]

TASK [Limit is appended to naive.conf (naive)] *********************************
changed: [web1]
changed: [web2]

PLAY RECAP *********************************************************************
web1 : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0 …
web2 : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0 …

# After the third run
$ ansible web1 -m ansible.builtin.command -a "wc -l /opt/bazar/app.conf /opt/bazar/naive.conf"
web1 | CHANGED | rc=0 >>
        1 /opt/bazar/app.conf
        3 /opt/bazar/naive.conf
        4 total

দ্বিতীয় run-এ তিনটি module কিছু করেনি, কারণ তারা আগে পরীক্ষা করেছে। shell পরীক্ষা করে না, তাই প্রতিবার changed, আর তিন run-এ naive.conf-এ ৩টি লাইন। Recap-এর নিয়মটি মনে রাখুন: কিছু না বদলালে দ্বিতীয় run-এ changed=0 হওয়া উচিত। না হলে playbook-এ সমস্যা আছে।

নিজে বদলে দেখুন: changed বনাম ok

নিচের ছোট simulator-এ একটি host আর ওপরের চারটি task. RUNS বদলান, অথবা EDIT_BEFORE_RUN দিয়ে ঠিক করুন কোন run-এর আগে কেউ হাতে লাইনটি মুছে দেবে।

// One host, four tasks, repeated runs. Try RUNS = 6 or EDIT_BEFORE_RUN = 2.
const RUNS = 4;
const EDIT_BEFORE_RUN = 4;   // someone deletes the line by hand; 0 = nobody
const host = { users: [], dirs: [], appConf: [], naiveConf: [] };

const tasks = [
  ["user", h => h.users.includes("bazar") ? "ok"
                : (h.users.push("bazar"), "changed")],
  ["file", h => h.dirs.includes("/opt/bazar") ? "ok"
                : (h.dirs.push("/opt/bazar"), "changed")],
  ["lineinfile", h => h.appConf.includes("max_conn=100") ? "ok"
                : (h.appConf.push("max_conn=100"), "changed")],
  ["shell", h => (h.naiveConf.push("max_conn=100"), "changed")],
];

for (let run = 1; run <= RUNS; run++) {
  if (run === EDIT_BEFORE_RUN) host.appConf = [];
  const results = tasks.map(([name, act]) => name + "=" + act(host));
  const changed = results.filter(r => r.endsWith("=changed")).length;
  console.log("run " + run + ": ok=" + tasks.length + " changed=" + changed
    + " | app.conf lines=" + host.appConf.length
    + " naive.conf lines=" + host.naiveConf.length);
  console.log("   " + results.join("  "));
}

আউটপুট: run 1: ok=4 changed=4 | app.conf lines=1 naive.conf lines=1, run 2: ok=4 changed=1 | app.conf lines=1 naive.conf lines=2, run 3: ok=4 changed=1 | app.conf lines=1 naive.conf lines=3, আর run 4: ok=4 changed=2 | app.conf lines=1 naive.conf lines=4. প্রথম তিন run আসল recap আর wc-এর সঙ্গে হুবহু মেলে। চতুর্থ run দেখায় idempotent module-এর আরেক গুণ: কেউ হাতে লাইন মুছলে সে আবার বসিয়ে দেয়। সংখ্যা বদলে দেখুন, lineinfile-এর লাইন কখনো ১-এর বেশি হবে না।

কীভাবে command-কে সৎ বানাবেন

  • creates বা removes: ফাইল দেখে চলবে কি না ঠিক করে।
  • changed_when: কখন “changed” বলবে, তা আপনি বলে দেন। শুধু তথ্য পড়লে false.
  • when আর register: আগের task-এর ফল দেখে সিদ্ধান্ত।
  • সবচেয়ে ভালো পথ: এমন module খোঁজা, যা কাজটি নিজেই পারে। command শেষ ভরসা।

আমাদের নিজের ভুল থেকে শিখুন। এই পর্ব লেখার সময় প্রথমে “nginx চলছে” বোঝাতে লিখেছিলাম creates: /run/nginx/nginx.pid. প্রথম run-এই handler ব্যর্থ হলো: invalid PID number "" in "/run/nginx/nginx.pid". কারণ validate-এর nginx -t একটি খালি pid ফাইল বানিয়েছিল, তাই start task চলেনি। আরও খারাপ, পরের run-এর recap ছিল ok=6 changed=0 failed=0, অথচ nginx চলছিলই না। Handler একবার ব্যর্থ হলে পরের run-এ আর ফিরে আসে না, কারণ config তো আর বদলাচ্ছে না। পরে pgrep -x nginx লিখেও ঠকেছি: Alpine-এর BusyBox-এ এটি চলমান nginx-কেও খুঁজে পায়নি।

শিক্ষা তিনটি। changed=0 মানে “সব ঠিক” নয়; মানে “Ansible যা দেখে, তাতে বদল দরকার নেই”। তাই শেষে একটি health check রাখুন। আর ফাইল থাকা দিয়ে process চলা বোঝা যায় না। এই ধরনের ভুল ধরা পড়ে শুধু দ্বিতীয়বার চালিয়ে, পরিষ্কার host-এ।

৫ · Handler, variable, fact আর Jinja2

Nginx-এর config বদলালে reload করতে হয়, না বদলালে করা উচিত নয়; অকারণ reload-ও ঝুঁকি। Handler ঠিক এই সমস্যার সমাধান: সাধারণ task-এর মতোই, তবে শুধু notify পেলে চলে, আর পুরো play-এর শেষে একবার চলে।

নিচের ধাপগুলো আসল run. প্রথমে কিছু বদলায়নি। তারপর group_vars/web.yml-এ port ৮০৮০ থেকে ৮০৮১ করা হলো, আগে শুধু web1-এ, পরে সবখানে।

দুটি host-এর অবস্থা
কমান্ড
web1 config
web2 config
Handler-এর তালিকা
  1. কিছু বদলায়নি। template module নতুন লেখার checksum মিলিয়ে দেখল, ফাইল একই। ফল ok, তাই notify কাজ করেনি।
  2. Handler চলল না। Output-এ RUNNING HANDLER লাইনটিই নেই। Recap-এ changed=0. skipped=1 হলো “Start nginx” task, কারণ nginx চলছে।
  3. শুধু web1. --limit web1 দিয়ে চালানো হলো। Config বদলাল, changed, handler তালিকায় উঠল শুধু web1-এর জন্য। web2-তে Ansible যায়ইনি।
  4. web1-এ reload. Play-এর শেষে handler চলল। Recap: ok=7, changed=2; একটি config, একটি handler.
  5. এবার সব host. web1-এর ফাইল আগেই ঠিক ছিল, তাই ok. শুধু web2 changed. একই playbook, একই কমান্ড, তবু দুই host-এর ফল আলাদা: Ansible প্রতিটি host-এ আলাদা করে দেখে।
  6. শুধু web2-তে reload. Handler চলেছে যে host-এ বদল হয়েছে, সেখানেই। web1-এ অকারণ reload হয়নি। দুই host এখন একই অবস্থায়।

দুটি খুঁটিনাটি। একাধিক task একই handler-কে notify করলেও সে একবারই চলে। আর একটি task ব্যর্থ হলে সেই host-এ জমে থাকা handler সাধারণত আর চলে না; দরকার হলে --force-handlers আছে।

Variable, fact আর template

একই কাজ, তবে host-ভেদে মান আলাদা। এর জন্য variable. আমাদের lab-এর তিনটি উৎস:

# group_vars/web.yml  (every host in group web)
listen_port: 8080
app_title: Bazar

# host_vars/web2.yml  (only web2, wins over the group)
app_title: Bazar (canary)

# templates/index.html.j2
<h1>{{ app_title }}</h1>
<p>{{ ansible_facts['hostname'] }} on {{ ansible_facts['distribution'] }} {{ ansible_facts['distribution_version'] }}</p>

# templates/nginx.conf.j2
# Managed by Ansible. Manual edits will be overwritten.
worker_processes 1;
events { worker_connections 256; }
http {
    server {
        listen {{ listen_port }};
        root /var/www/bazar;
    }
}
$ ansible web -m ansible.builtin.setup -a "filter=ansible_distribution*"
web1 | SUCCESS => {
    "ansible_facts": {
        "ansible_distribution": "Alpine",
        …
        "ansible_distribution_version": "3.20.10"
    },
    "changed": false
}
…
$ ansible web -m ansible.builtin.command -a "wget -qO- http://127.0.0.1:8082/"
web1 | CHANGED | rc=0 >>
<h1>Bazar</h1>
<p>web1 on Alpine 3.20.10</p>
web2 | CHANGED | rc=0 >>
<h1>Bazar (canary)</h1>
<p>web2 on Alpine 3.20.10</p>

Fact হলো host থেকে সংগ্রহ করা তথ্য, যা আপনি লেখেননি। Jinja2 template-এর {{ }} ঘরে variable আর fact বসে। একই template থেকে দুটি আলাদা page হলো: host_vars web2-র title বদলেছে, আর hostname এসেছে fact থেকে। মান কোনটি জিতবে, তার লম্বা তালিকা Ansible-এর docs-এ আছে। মোটা দাগে: role default সবচেয়ে দুর্বল, তারপর group, তারপর host, আর কমান্ডের -e সবচেয়ে শক্তিশালী।

একটি সতর্কতা: -e দিয়ে দেওয়া মান কোথাও জমা থাকে না। পরের অংশে আমরা -e listen_port=8082 চালাব। ফাইলে মান তখনো 8081, তাই পরের সাধারণ run server-কে আবার 8081-এ ফিরিয়ে আনবে। স্থায়ী বদল সবসময় Git-এর ফাইলে করুন। আর template-এর প্রথম লাইনে “Managed by Ansible” লিখে রাখা ভালো অভ্যাস, যাতে server-এ ঢুকে কেউ ফাইলটি হাতে না বদলান।

৬ · Role, --check, --diff, --limit আর serial

একটি playbook বড় হলে ভাগ করতে হয়। Role হলো task, handler, template আর default একসঙ্গে গোছানো একটি নির্দিষ্ট ফোল্ডার। ফোল্ডারের নামই বলে দেয় কোন ফাইল কোথায় লোড হবে।

$ find roles -type f | sort
roles/web_site/defaults/main.yml
roles/web_site/handlers/main.yml
roles/web_site/tasks/main.yml
roles/web_site/templates/index.html.j2
roles/web_site/templates/nginx.conf.j2

$ cat site-role.yml
---
- name: Configure the web tier
  hosts: web
  become: true
  roles:
    - web_site

$ ansible-playbook site-role.yml
…
TASK [web_site : Nginx config from template] ***********************************
ok: [web1]
ok: [web2]
…
PLAY RECAP *********************************************************************
web1 : ok=6    changed=0    unreachable=0    failed=0    skipped=1    rescued=0 …
web2 : ok=6    changed=0    unreachable=0    failed=0    skipped=1    rescued=0 …

৪৪ লাইনের playbook নেমে এলো ৬ লাইনে। কাজ একই, তাই changed=0; এই recap-ই প্রমাণ যে ভাগ করতে গিয়ে কিছু ভাঙেনি। Task-এর আগে web_site : লেখা থাকে, যাতে বোঝা যায় কোন role থেকে এসেছে। Terraform module (পর্ব ৫৪)-এর মতোই এটি পুনর্ব্যবহারের একক।

চালানোর আগে দেখুন: --check আর --diff

Check mode কিছু না বদলে বলে কী বদলাত। --diff ফাইলের লাইন ধরে পার্থক্য দেখায়। দুটি একসঙ্গে অনেকটা terraform plan-এর মতো, যদিও অত নিশ্চিত নয়।

$ ansible-playbook site.yml --check --diff --limit web1
…
TASK [Nginx config from template] **********************************************
--- before: /etc/nginx/nginx.conf
+++ after: /root/.ansible/tmp/…/nginx.conf.j2
@@ -3,7 +3,7 @@
 events { worker_connections 256; }
 http {
     server {
-        listen 8080;
+        listen 8081;
         root /var/www/bazar;
     }
 }

changed: [web1]
…
RUNNING HANDLER [Reload nginx] *************************************************
skipping: [web1]

PLAY RECAP *********************************************************************
web1 : ok=6    changed=1    unreachable=0    failed=0    skipped=2    rescued=0 …

$ ansible web1 -m ansible.builtin.command -a "grep listen /etc/nginx/nginx.conf"
web1 | CHANGED | rc=0 >>
        listen 8080;

# The naive playbook in check mode: shell is not predicted, only skipped
TASK [Limit is appended to naive.conf (naive)] *********************************
skipping: [web2]
skipping: [web1]

Check বলছে changed, অথচ ফাইলে এখনো 8080. কিছুই বদলায়নি। তবে শেষ অংশটি সতর্কবার্তা: shell আর command check mode-এ সাধারণত চলে না, তাই তাদের প্রভাব এখানে দেখা যায় না। পরের task যদি আগের task-এর ফলের ওপর নির্ভর করে, check ভুল বলতে পারে। তাই check সহায়ক, প্রমাণ নয়।

--limit আর serial: অল্প অল্প করে

একটি ভুল config একসঙ্গে সব server-এ গেলে পুরো সাইট বন্ধ। লঞ্চঘাটে সবাই একসঙ্গে ওঠে না, কয়েকজন করে ওঠে। --limit একটি কমান্ডের জন্য host কমায়। আর playbook-এর serial host-গুলোকে batch-এ ভাগ করে, এক batch শেষ হলে তবে পরেরটি। এটিই rolling পদ্ধতি।

---
- name: Roll the web tier one host at a time
  hosts: web
  become: true
  serial: 1
  max_fail_percentage: 0
  roles:
    - web_site
  post_tasks:
    - name: Health check on the new port
      ansible.builtin.uri:
        url: "http://127.0.0.1:{{ listen_port }}/"
      register: health
      until: health is succeeded
      retries: 5
      delay: 1
$ ansible-playbook rolling.yml -e listen_port=8082
PLAY [Roll the web tier one host at a time] ************************************
…
RUNNING HANDLER [web_site : Reload nginx] **************************************
changed: [web1]

TASK [Health check on the new port] ********************************************
ok: [web1]

PLAY [Roll the web tier one host at a time] ************************************
…
TASK [Health check on the new port] ********************************************
ok: [web2]

PLAY RECAP *********************************************************************
web1 : ok=8    changed=2    unreachable=0    failed=0    skipped=1    rescued=0 …
web2 : ok=8    changed=2    unreachable=0    failed=0    skipped=1    rescued=0 …

# Now a bad value
$ ansible-playbook rolling.yml -e listen_port=notaport
…
TASK [web_site : Nginx config from template] ***********************************
[ERROR]: Task failed: Module failed: failed to validate
…
fatal: [web1]: FAILED! => {… "msg": "failed to validate", "stderr": "nginx: [emerg]
host not found in \"notaport\" of the \"listen\" directive in …

NO MORE HOSTS LEFT *************************************************************

NO MORE HOSTS LEFT *************************************************************

PLAY RECAP *********************************************************************
web1 : ok=4    changed=0    unreachable=0    failed=1    skipped=0    rescued=0 …

$ ansible web -m ansible.builtin.command -a "grep listen /etc/nginx/nginx.conf"
web2 | CHANGED | rc=0 >>
        listen 8082;
web1 | CHANGED | rc=0 >>
        listen 8082;

ভালো run-এ PLAY লাইন দুবার: প্রতিটি batch আলাদা। খারাপ run-এ দুই স্তরের সুরক্ষা কাজ করল। validate ভুল ফাইল বসতে দেয়নি, তাই web1-এর config ঠিক থাকল। আর batch ব্যর্থ হওয়ায় web2-তে Ansible গেলই না; recap-এ web2 নেই। সত্যিকারের rolling-এ pre_tasks-এ host-টিকে load balancer থেকে সরানো হয়, আর health check পাসের পর ফিরিয়ে আনা হয়।

থামার নিয়ম দুটি, আর এগুলো আমরা ১০টি local host-এ (ansible_connection=local) আসল run দিয়ে যাচাই করেছি। প্রথম: পুরো batch ব্যর্থ হলে থামে। দ্বিতীয়: max_fail_percentage দিলে, batch-এর ব্যর্থতার হার সেই মান ছাড়িয়ে গেলে থামে। সমান হলে থামে না। হিসাব প্রতি batch-এ আলাদা, পুরো fleet-এ জমা হয় না। serial: 25%-এর ভাগফল নিচের দিকে নামে: ১০ host-এ batch হয় ২।

// Rolling with serial and max_fail_percentage, using Ansible's two stop rules.
function mulberry32(a) {
  return () => { a |= 0; a = a + 0x6D2B79F5 | 0;
    let t = Math.imul(a ^ a >>> 15, 1 | a);
    t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
    return ((t ^ t >>> 14) >>> 0) / 4294967296; };
}
function roll(label, n, serial, maxFail, bad) {
  const size = typeof serial === "string"
    ? Math.max(1, Math.floor(parseInt(serial) / 100 * n)) : serial;
  let updated = 0, failed = 0, batch = 0, stopped = false;
  for (let first = 1; first <= n && !stopped; first += size) {
    const hosts = [];
    for (let h = first; h < first + size && h <= n; h++) hosts.push(h);
    const f = hosts.filter(h => bad.includes(h)).length;
    const rate = 100 * f / hosts.length;
    batch++; failed += f;
    stopped = f === hosts.length || (maxFail !== null && rate > maxFail);
    if (!stopped) updated += hosts.length - f;
  }
  const left = n - updated - failed;
  console.log(label + ": batches=" + batch + " updated=" + updated + " failed="
    + failed + " not finished=" + left + (stopped ? "  STOPPED" : "  done"));
}
roll("A serial 3, max 30, h2 bad ", 10, 3, 30, [2]);
roll("B serial 3, max 40, h2 h5  ", 10, 3, 40, [2, 5]);
roll("C serial 4, max 50, h1 h2  ", 10, 4, 50, [1, 2]);
roll("D serial 4, max 49, h1 h2  ", 10, 4, 49, [1, 2]);
roll("E serial 3, none, h1 h2 h3 ", 10, 3, null, [1, 2, 3]);
roll("F serial 25%, max 0, none  ", 10, "25%", 0, []);
const rnd = mulberry32(56), FAIL_RATE = 0.04, bad = [];
for (let h = 1; h <= 100; h++) if (rnd() < FAIL_RATE) bad.push(h);
console.log("seeded bad hosts: " + bad.join(" "));
roll("G 100 hosts, serial 10, max 10", 100, 10, 10, bad);

আউটপুটের শুরু: A serial 3, max 30, h2 bad : batches=1 updated=0 failed=1 not finished=9 STOPPED, শেষ লাইন: G 100 hosts, serial 10, max 10: batches=10 updated=97 failed=3 not finished=0 done. A থেকে F-এর ফল আসল run-এর সঙ্গে মিলিয়ে নেওয়া। A: ১/৩ = ৩৩.৩%, যা ৩০-এর বেশি, তাই প্রথম batch-এই থামে। B: প্রতি batch-এ ৩৩.৩%, যা ৪০-এর কম, তাই ২ ব্যর্থতা সত্ত্বেও শেষ পর্যন্ত যায়। C আর D-র পার্থক্য এক সংখ্যার: ৫০% ছাড়ায় না, কিন্তু ৪৯% ছাড়ায়। থেমে গেলে সেই batch-এর ভালো host-গুলোও “not finished”, কারণ আসল run-এ ওদের পরের task-ও আর চলেনি। E: max_fail_percentage নেই, তবু পুরো batch ব্যর্থ বলে থামে। F-এ ২৫% মানে ২ host, তাই ৫ batch. G-তে seed-এ বের হওয়া ৩টি খারাপ host (11, 38, 85) তিনটি আলাদা batch-এ পড়েছে; প্রতিটিতে ১০%, সীমা ছাড়ায়নি। FAIL_RATE আর seed বদলে দেখুন: ব্যর্থতা ছড়িয়ে থাকলে rollout শেষ পর্যন্ত যায়, এক batch-এ জড়ো হলে থামে।

৭ · Ansible Vault আর অন্য CM tool

Playbook Git-এ থাকে, তাই database password সেখানে সাধারণ লেখায় রাখা যায় না। Ansible Vault একটি ফাইল বা একটি মানকে password দিয়ে encrypt করে। Git-এ শুধু গোপন লেখা যায়। নিচের secret-টি বানানো, আর vault-এর password একটি আলাদা ফাইলে আছে, যা Git-এ যায় না এবং এখানে দেখানো হচ্ছে না।

$ ansible-vault encrypt secrets.yml --vault-password-file ~/.vault_pass
$ head -n 3 secrets.yml
$ANSIBLE_VAULT;1.1;AES256
33346133643538323036373939643165346566393531653537336534393739333633636662623938
3336376164366230626237636161653362323030653461610a373561303365646162316138396531

$ ansible-playbook vault_demo.yml
[ERROR]: Error reading `vars_files` file 'secrets.yml': Attempting to decrypt but no vault secrets found.
…
$ ansible-playbook vault_demo.yml --vault-password-file ~/.vault_pass
TASK [Write the env file] ******************************************************
changed: [web2]
changed: [web1]
…
$ ansible web1 -b -m ansible.builtin.command -a 'stat -c "%a %U %n" /opt/bazar/db.env'
web1 | CHANGED | rc=0 >>
600 bazar /opt/bazar/db.env

Password ছাড়া playbook শুরুই হয়নি। Password দিলে task চলল। Task-এ no_log: true ছিল, তাই secret log-এ আসেনি; দ্বিতীয় run-এ ফল ok. ফাইলের অনুমতি 600, মালিক শুধু app user. মনে রাখুন, Vault শুধু Git-এ রাখা অবস্থায় রক্ষা করে। Server-এ পৌঁছে secret আবার সাধারণ লেখা। পর্ব ৩৩-এর secret manager অনেক ক্ষেত্রে ভালো পথ, কারণ rotation আর audit সেখানে সহজ।

কাজের কয়েকটি কমান্ড: ansible-vault edit ফাইল খুলে আবার encrypt করে, encrypt_string পুরো ফাইল নয়, শুধু একটি মান encrypt করে, আর rekey password বদলায়। CI-তে vault password আসে pipeline-এর secret থেকে, কখনো repository থেকে নয়। Password ফাইলটি .gitignore-এ রাখুন।

Puppet, Chef আর Salt: সংক্ষেপে

Ansible একমাত্র CM tool নয়। Puppet আর Chef-এর মূল ধারা হলো pull agent: প্রতিটি server-এ একটি agent চলে, সে নিয়মিত কেন্দ্রীয় server থেকে নির্দেশ টেনে নেয়, আর নিজেকে ঠিক করে। এতে একটি বড় সুবিধা: কেউ হাতে বদল করলে কিছুক্ষণ পরে নিজে থেকেই ঠিক হয়। Ansible-এ আপনি না চালালে কিছু হয় না।

ToolAgentকাজের ধারালেখার ভাষা
PuppetআছেPull; default প্রায় ৩০ মিনিট পরপরনিজস্ব declarative ভাষা
ChefআছেPull; সময় দল ঠিক করেRuby-ভিত্তিক recipe
Saltসাধারণত আছে (minion)Minion master-এর সঙ্গে সংযুক্ত থাকে; master দ্রুত push করতে পারে, নির্দিষ্ট সময়ে state-ও চলতে পারেYAML আর Jinja
AnsibleনেইPush; ansible-pull দিয়ে pull-ও সম্ভবYAML আর Jinja2

Salt-কে অনেকে pull দলে রাখেন, তবে সেটি পুরোপুরি ঠিক নয়; এর mode একাধিক, এমনকি salt-ssh দিয়ে agent ছাড়াও চলে। আর push বনাম pull-এর এই তর্কই পর্ব ৬০-এর GitOps-এ ফিরে আসবে।

৮ · Mutable বনাম immutable: drift কোথা থেকে আসে

এখন পর্যন্ত আমরা চলমান server বদলেছি। এই পদ্ধতির নাম mutable infrastructure. এর শত্রু drift: রাত ২টায় কেউ হাতে একটি লাইন বদলালেন, এক server-এ একটি package বেশি বসল, এক server তিন মাস বন্ধ থাকায় update পায়নি। ধীরে ধীরে প্রতিটি server হয়ে যায় snowflake: অন্যরকম, আর নকল করা কঠিন।

CM কি drift ঠিক করে? আংশিক। আসুন পরীক্ষা করি। web2-তে হাতে দুটি কাজ করা হলো: Ansible-এর সামলানো config-এ port বদলানো হলো, আর একটি নতুন ফাইল বানানো হলো, যা Ansible চেনে না।

# A hand edit on web2: one managed file, one unmanaged file
$ ansible web2 -b -m ansible.builtin.shell -a "sed -i 's/listen 8081/listen 8099/' /etc/nginx/nginx.conf; nginx -s reload; echo hotfix > /etc/hotfix-note"
web2 | CHANGED | rc=0 >>

$ ansible-playbook site.yml --diff
…
TASK [Nginx config from template] **********************************************
ok: [web1]
--- before: /etc/nginx/nginx.conf
+++ after: /root/.ansible/tmp/…/nginx.conf.j2
@@ -3,7 +3,7 @@
 events { worker_connections 256; }
 http {
     server {
-        listen 8099;
+        listen 8081;
         root /var/www/bazar;
     }
 }

changed: [web2]
…
RUNNING HANDLER [Reload nginx] *************************************************
changed: [web2]

PLAY RECAP *********************************************************************
web1 : ok=6    changed=0    unreachable=0    failed=0    skipped=1    rescued=0 …
web2 : ok=7    changed=2    unreachable=0    failed=0    skipped=1    rescued=0 …

$ ansible web -b -m ansible.builtin.shell -a "grep listen /etc/nginx/nginx.conf; ls /etc/hotfix-note"
…
web1 | FAILED | rc=1 >>
        listen 8081;ls: /etc/hotfix-note: No such file or directory…
web2 | CHANGED | rc=0 >>
        listen 8081;
/etc/hotfix-note

Playbook হাতের বদল ধরে ফেলল: 8099 আবার 8081 হলো, শুধু web2-তে reload হলো। কিন্তু /etc/hotfix-note রয়ে গেল, আর recap-এ তার কোনো উল্লেখই নেই। CM শুধু তা-ই সামলায়, যা playbook-এ লেখা আছে। বাকি সবকিছু তার চোখের বাইরে জমতে থাকে।

Immutable infrastructure সমস্যাটিকে গোড়া থেকে কাটে: চালু হওয়ার পর server আর বদলানোই হয় না। বদল দরকার হলে নতুন golden image বানিয়ে পুরোনো server ফেলে নতুন server আনা হয় (পর্ব ১৬-এ দেখেছেন)। হাতে কিছু বদলালেও তার আয়ু সেই server-এর আয়ু পর্যন্ত। Pets vs cattle-এর ভাষায়, server আর পোষা প্রাণী নয়। এই ধারণা Martin Fowler-এর phoenix server-এর কাছাকাছি।

মেসের রান্নাঘর আর হোটেলের প্যাকেট খাবারের তফাত ভাবুন। মেসে প্রতিদিন একই হাঁড়িতে কিছু যোগ হয়, কিছু কমে; আগের দিনের স্বাদ রয়ে যায়। আর কারখানার প্যাকেট প্রতিবার একই রেসিপিতে বানানো। স্বাদ বদলাতে চাইলে প্যাকেটের ভেতরে হাত দেন না, নতুন ব্যাচ বানান।

নিজে বদলে দেখুন: drift বাড়ে কত দ্রুত

নিচের simulator-এর সব হার কল্পিত, বাস্তবের পরিসংখ্যান নয়। ২০টি server-এ প্রতিদিন হাতে বদলের ৩% সম্ভাবনা। তিনটি fleet: কোনো CM নেই; সাপ্তাহিক Ansible, যা শুধু ৪০% বদল দেখতে পায়; আর immutable, যেখানে প্রতিটি server ১৪ দিন পরপর বদলে যায়।

// Drift in a 20-server fleet over 90 simulated days. All rates are assumptions.
function mulberry32(a) {
  return () => { a |= 0; a = a + 0x6D2B79F5 | 0;
    let t = Math.imul(a ^ a >>> 15, 1 | a);
    t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
    return ((t ^ t >>> 14) >>> 0) / 4294967296; };
}
const SERVERS = 20, DAYS = 90, HAND_EDIT = 0.03;
const MANAGED = 0.4, CM_EVERY = 7, LIFETIME = 14;

function simulate(mode, seed) {
  const rnd = mulberry32(seed);
  let edits = Array.from({ length: SERVERS }, () => []);
  let nextId = 1;
  for (let day = 1; day <= DAYS; day++) {
    edits.forEach((list, s) => {
      if (rnd() < HAND_EDIT) list.push({ id: nextId++, managed: rnd() < MANAGED });
      if (mode === "immutable" && (day + s) % LIFETIME === 0) edits[s] = [];
    });
    if (mode === "ansible" && day % CM_EVERY === 0)
      edits = edits.map(list => list.filter(e => !e.managed));
    if (day % 30 === 0) {
      const drifted = edits.filter(l => l.length > 0).length;
      const total = edits.reduce((sum, l) => sum + l.length, 0);
      const shapes = new Set(edits.map(l => l.map(e => e.id).join(","))).size;
      console.log(mode.padEnd(10) + "day " + String(day).padStart(2)
        + ": drifted " + String(drifted).padStart(2) + "/" + SERVERS
        + ", edits alive " + String(total).padStart(2)
        + ", different servers " + shapes);
    }
  }
}
for (const mode of ["by hand", "ansible", "immutable"]) simulate(mode, 56);

৯০ দিনের লাইন তিনটি: by hand day 90: drifted 20/20, edits alive 52, different servers 20, ansible day 90: drifted 15/20, edits alive 25, different servers 16, আর immutable day 90: drifted 4/20, edits alive 4, different servers 5. হাতে চালানো fleet-এ drift শুধু বাড়ে। Ansible-এ ধীরে বাড়ে, কারণ অদেখা বদল জমতে থাকে। Immutable fleet-এ একটি নিচু সীমার কাছে ওঠানামা করে (৩০, ৬০ আর ৯০ দিনে ২, ৫, ৪), কারণ বদলের আয়ু ১৪ দিনের বেশি নয়। LIFETIME, MANAGED বা seed বদলান। ছবিটির আকার বদলাবে কম: শূন্যে নামানো নয়, আয়ু বেঁধে দেওয়াই আসল লাভ।

৯ · Packer, image pipeline আর CM কোথায় টিকে থাকে

Golden image হাতে বানানো যায় না; তাহলে ওই snowflake-ই ফিরে আসে। Packer একটি template পড়ে একটি অস্থায়ী VM চালায়, সেখানে provisioner দিয়ে সব বসায়, VM থেকে machine image বানায়, আর অস্থায়ী VM মুছে দেয়। মজার ব্যাপার, provisioner হিসেবে Ansible role-ই ব্যবহার করা যায়। তাই আপনার web_site role ফেলা যাবে না।

নিচের template চালানো হয়নি। এটি নমুনা: Packer এখানে বসানো নেই, আর কোনো cloud account ব্যবহার করা হয়নি। Plugin-এর version আর argument মিলিয়ে নিন Packer-এর docs থেকে। আমাদের lab-এর role Alpine-এর জন্য লেখা, তাই Ubuntu image-এ task বদলাতে হবে, আর যে কম্পিউটারে Packer চলে সেখানে Ansible বসানো থাকতে হবে। Azure আর Google-এর জন্যও একই ধরনের builder আছে (azure-arm, googlecompute).

packer {
  required_plugins {
    amazon = {
      source  = "github.com/hashicorp/amazon"
      version = "~> 1.3"
    }
    ansible = {
      source  = "github.com/hashicorp/ansible"
      version = "~> 1.1"
    }
  }
}

variable "image_version" {
  type = string
}

source "amazon-ebs" "web" {
  region        = "ap-southeast-1"
  instance_type = "t3.small"
  ssh_username  = "ubuntu"
  ami_name      = "bazar-web-${var.image_version}"

  source_ami_filter {
    filters = {
      name                = "ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"
      virtualization-type = "hvm"
    }
    owners      = ["123456789012"]   # sample id: use the publisher you trust
    most_recent = true
  }
}

build {
  sources = ["source.amazon-ebs.web"]

  provisioner "ansible" {
    playbook_file = "./site-role.yml"
    groups        = ["web"]   # the play says hosts: web
  }
}

Image pipeline

বদল যায় Git থেকে image-এ, image থেকে নতুন server-এ; চলমান server-এ নয়

Packer দিয়ে golden image pipeline Base imageবিশ্বস্ত, patch করা OS Packer + Ansible roleঅস্থায়ী VM-এ সব বসানো পরীক্ষাboot, health check, scan Golden image v42version করা, আর বদলায় না Launch templateএখন v42 দেখায় পুরোনো serverv41, সরে যায় নতুন serverv42, health পাস
ছবি ৫৬.২ — Pipeline-এ ব্যর্থ হলে কোনো চলমান server-এর ক্ষতি হয় না। ঝুঁকি সরে এসেছে build-এর সময়ে।

১০০টি server patch: দুই পথের হিসাব

একটি জরুরি patch এলো। দুই পথের অনুমান: ১০০ server, প্রতি batch-এ ১০টি। Ansible পথে এক batch-এ ৬ মিনিট (১ drain, ৩ patch, ১ restart, ১ health). Image পথে build ১৫ মিনিট, পরীক্ষা ১০ মিনিট, আর এক batch-এ ৬ মিনিট (৩ launch, ২ health, ১ drain). সংখ্যাগুলো কল্পিত; হিসাবটি আসল।

অনুমান: batch ১০, batch-এ ৬ মিনিট
Ansible সময়
Ansible: ঠিক / অর্ধেক
Image সময়
Image: নতুন server
  1. শুরু। ১০০টি চলমান server. দুই পথেই লক্ষ্য একই: সবাই নতুন patch-এ। দুই পথই ধীরে এগোবে।
  2. Ansible: তিন batch. serial: 10. ৩ × ৬ = ১৮ মিনিটে ৩০টি server শেষ। প্রতিটি আলাদা জায়গায় package নামাচ্ছে।
  3. Batch ৪-এ একটি ব্যর্থ। একটি server-এর package mirror timeout করল। ১/১০ = ১০%, যা max_fail_percentage: 10-এর সমান, ছাড়ায়নি। তাই ২৪ মিনিটে ৩৯টি ঠিক, ১টি অর্ধেক অবস্থায়, rollout চলছে।
  4. Ansible শেষ। Batch ৭-এ আরও একটির disk ভরা। ১০ × ৬ = ৬০ মিনিটে ৯৮টি ঠিক, ২টি অর্ধেক। ওই দুটি এখন হাতে ঠিক করতে হবে, আর এখান থেকেই নতুন drift-এর জন্ম।
  5. Image পথ: একবারই build. ১৫ + ১০ = ২৫ মিনিটে image v42 তৈরি ও পরীক্ষিত। এখনো কোনো চলমান server ছোঁয়া হয়নি। এখানে ব্যর্থ হলে ক্ষতি শূন্য।
  6. Image rollout. ১০ × ৬ = ৬০ মিনিট, মোট ৮৫ মিনিট। কোনো নতুন instance খারাপ হলে তাকে ফেলে আরেকটি আনা হয়। শেষে ১০০টি server হুবহু একই।
  7. তুলনা। সময়ে Ansible ২৫ মিনিট এগিয়ে। বদলে image পথ দিল একই রকম ১০০টি server, আর সহজ rollback: আগের image v41 আছেই। Patch ফেরানো সবসময় সহজ নয়। আসল লেনদেন গতি বনাম নিশ্চয়তা।

কোন পথ কখন

বিষয়জায়গায় patch (mutable)Server বদল (immutable)
ছোট বদলের গতিদ্রুতধীর, পুরো pipeline চলে
শেষ অবস্থাHost-ভেদে আলাদা হতে পারেসবখানে একই
ব্যর্থতা ধরা পড়েচলমান server-এBuild বা পরীক্ষার সময়ে
RollbackDowngrade, অনেক সময় অনিশ্চিতআগের image-এ ফেরা
পূর্বশর্তSSH পথ, চাবি সামলানোStateless server, load balancer, image pipeline
ভালো মানায়Stateful host, নিজের hardwareStateless web আর API fleet

বাস্তবে বেশিরভাগ দল দুটি মিলিয়ে চলে। Image-এ থাকে ধীরে বদলানো জিনিস: OS, runtime, agent. Boot-এর সময় আসে পরিবেশের মান আর secret. আর জরুরি ঝুঁকিতে, যখন image pipeline চলতে সময় লাগবে, Ansible দিয়ে দ্রুত patch করে পরে নতুন image-এ সেই বদল ঢোকানো হয়। শেষ অংশটি বাদ পড়লেই পরের rollout সেই patch মুছে দেবে।

Cloud-ভেদে নাম আলাদা, ধারণা এক (পর্ব ১৬ আর পর্ব ১৭):

ধারণাAWSAzureGoogle Cloud
ImageAMIManaged image, Compute GalleryCustom image
নকশাLaunch templateVM Scale Set modelInstance template
Server বদলInstance refreshScale Set rolling upgradeManaged instance group rolling update

CM এখনো কোথায় লাগে

  • Image বানানোর ভেতরে: Packer-এর provisioner হিসেবে। CM মরেনি, শুধু সময় বদলেছে: boot-এর পরে নয়, আগে।
  • যা বদলে ফেলা যায় না: নিজের hardware, পুরোনো server, database host, network device. এগুলোতে SSH-ভিত্তিক automation খুব কাজের।
  • Stateful machine: Disk-এ দামি data থাকলে replace সহজ নয়; সেখানে patch-ই বাস্তব পথ।
  • Boot-এর সময়ের ছোট কাজ: cloud-init আর secret আনা; পরিবেশ-ভেদে যা আলাদা, তা image-এ রাখবেন না (পর্ব ১৬)।
  • Day-2 কাজ: log সংগ্রহ, ঘটনার সময় ১০০ host-এ একই তথ্য দেখা, একটি জরুরি বদল। এমনকি কিছু Kubernetes cluster-ও Ansible দিয়ে বসানো হয় (Kubespray).

Container-ও একই immutable ধারণা, ছোট আকারে: image বানাই, চলমান container-এ হাত দিই না (পর্ব ৩৮, ৪৪)। তাই container-ভিত্তিক দলে Ansible-এর কাজ অনেক কমে যায়, তবে node-এর OS কাউকে না কাউকে সামলাতেই হয়।

আরও গভীরে: ২০০২ সালের LISA সম্মেলনে Steve Traugott আর Lance Brown-এর “Why Order Matters” পেপার automation-কে তিন ভাগে ভাগ করে: divergent (সময়ের সঙ্গে আলাদা হয়), convergent (লক্ষ্যের দিকে এগোয়) আর congruent (শুরু থেকে হুবহু এক)। CM মূলত convergent; immutable পথ congruent-এর কাছাকাছি। Module-এর ভেতরে Ansible কীভাবে কাজ করে, তা বোঝা যায় -vvv দিয়ে চালালে: কোন অস্থায়ী ফাইল পাঠাল আর কী JSON ফিরল, সব দেখা যায়।

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

  • Ansible agentless মানে কী? Host-এ স্থায়ী agent নেই; SSH আর Python দিয়ে কাজ হয়।
  • Terraform আর Ansible-এর পার্থক্য? Terraform resource বানায় ও state রাখে; Ansible ভেতরের config সামলায়, state ফাইল রাখে না।
  • Handler কখন চলে? Notify পেলে, play-এর শেষে, host প্রতি একবার।
  • serial কেন? বদল অল্প অল্প করে দিতে, যাতে ভুল ছড়ানোর আগে থামা যায়।
  • Mutable বনাম immutable? একটিতে চলমান server বদলায়, অন্যটিতে নতুন image থেকে server বদলে যায়।

১০ · AI-এর যুগে CM আর image

AI খুব দ্রুত playbook লিখে দেয়, আর সেগুলো প্রথম run-এ চলেও যায়। বিপদ সেখানেই। Ansible-এ ভুল লাইন একসঙ্গে সব server-এ পৌঁছায়। AI যেসব ভুল প্রায়ই করে:

AI-এর ভুলঝুঁকিকী করবেন
Module-এর বদলে shellIdempotent নয়, check-এ দেখা যায় নাদ্বিতীয়বার চালিয়ে changed=0 দেখুন
নেই এমন module বা argumentRun-এ ব্যর্থ, বা অন্য কিছু করেansible-doc আর --syntax-check
hosts: all, serial ছাড়াএক ভুলে সব server--limit, serial, health check
Password সরাসরি YAML-এGit-এ secret ফাঁসVault বা secret manager, no_log
ignore_errors: trueব্যর্থতা ঢাকা পড়েমুছে আসল কারণ খুঁজুন
chmod 777, NOPASSWD: ALL, 0.0.0.0/0অকারণ বেশি অনুমতিLeast privilege; প্রতিটি অনুমতির কারণ জানুন
latest image বা packageদুই build-এর ফল আলাদাVersion বেঁধে দিন
পুরোনো Packer বা Ansible syntax, খরচের হিসাব নেইBuild ভাঙে, অস্থায়ী VM বিল বাড়ায়Docs, throwaway environment, budget alert
সতর্কতা ছাড়া rm -rf বা state: absentData হারায়আগে --check --diff, এক host-এ পরীক্ষা

AI-কে কাজ দেওয়ার সময় সীমা বলে দিন, আর উত্তর যাচাইয়ের পথও চান। কোনো আসল password, IP বা host-এর নাম prompt-এ দেবেন না।

তুমি একজন সতর্ক DevOps reviewer. নিচের Ansible playbook দেখো (OS: Ubuntu 24.04, ansible-core 2.19, ৫০টি web server, load balancer-এর পেছনে): [playbook পেস্ট করুন; secret, IP আর আসল host নাম সরিয়ে] ১. কোন task idempotent নয়? দ্বিতীয় run-এ কী দেখাবে, বলো। ২. shell বা command-এর জায়গায় কোন ansible.builtin module চলে? ৩. serial, max_fail_percentage আর health check কেমন হওয়া উচিত, হিসাবসহ বলো। ৪. কোন module বা argument নিয়ে তুমি নিশ্চিত নও, তা আলাদা করে লেখো। ৫. আমি যাচাই করতে কোন কমান্ড চালাব, আগে কোনটি (--syntax-check, --check --diff, --limit)? কিছু মুছে ফেলার মতো task থাকলে আলাদা করে সতর্ক করো।

AI-এর লেখা playbook চালানোর আগে

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

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

প্র ০১Puppet আর Chef থাকতে Ansible এত জনপ্রিয় হলো কেন? Agentless হওয়ার দাম কী?

২০১২ সালে Puppet আর Chef ভালোভাবেই প্রতিষ্ঠিত ছিল, তবে শুরু করতে অনেক কাজ লাগত। আগে একটি master server বসাতে হতো, প্রতিটি machine-এ agent বসাতে হতো, certificate সই করতে হতো, আর শিখতে হতো নতুন একটি ভাষা বা Ruby. Michael DeHaan নিজে এর আগে Cobbler আর Func-এর মতো অবকাঠামো tool-এ কাজ করেছেন। তাঁর হিসাব ছিল সহজ: SSH তো সব server-এ আগেই আছে, Python-ও প্রায় সবখানে। তাহলে নতুন কিছু বসানোর দরকার কী?

ফল হলো খুব কম প্রবেশমূল্য। YAML পড়তে পারলে playbook বোঝা যায়, আর পাঁচ মিনিটে প্রথম কমান্ড চালানো যায়। নিরাপত্তার দিকেও একটি লাভ: খোলা নতুন port নেই, আলাদা agent-এর দুর্বলতা নেই, ব্যবহার হয় আগে থেকে পরীক্ষিত SSH.

দাম আছে তিনটি। এক, গতি। Control node-কে প্রতিটি host-এ SSH খুলতে হয়, তাই হাজার host-এ চালানো ধীর হয়; forks, SSH pipelining আর persistent connection এটি কিছুটা কমায়। দুই, কেউ পাহারায় নেই। Puppet-এর agent নিয়মিত জেগে নিজেকে ঠিক করে। Ansible না চালালে drift চুপচাপ জমতে থাকে, যদিও cron বা একটি automation platform দিয়ে নিয়মিত চালানো যায়। তিন, বন্ধ machine. Run-এর সময় যে host বন্ধ, সে বাদ পড়ে। Pull agent পরে জেগে নিজেই ধরে ফেলে। আর control node নিজেই একটি স্পর্শকাতর জায়গা, কারণ সেখানে সব host-এ ঢোকার চাবি থাকে।

মূল উপলব্ধি: Ansible জিতেছে সহজ শুরুর জন্য, সব দিক থেকে ভালো বলে নয়। Tool বাছার সময় শুধু সেরা feature নয়, শুরু করার খরচ আর চালিয়ে যাওয়ার খরচও গুনুন।

প্র ০২“Idempotent” playbook মানে কি দুটি server সত্যিই একই রকম হবে?

না, আর এই পার্থক্যটি খুব গুরুত্বপূর্ণ। Idempotent মানে একই playbook বারবার চালালে শেষ ফল বদলায় না। এটি শুধু playbook যা ছুঁয়েছে, তার কথা বলে। দুটি server-এর বাকি অংশ নিয়ে এটি কিছুই বলে না।

Steve Traugott আর Lance Brown ২০০২ সালের LISA সম্মেলনে “Why Order Matters: Turing Equivalence in Automated Systems Administration” পেপারে এই সমস্যাটি পরিষ্কার করেন। তাঁরা তিনটি ধারা আলাদা করেন। Divergent: হাতের কাজে server সময়ের সঙ্গে আলাদা হতে থাকে। Convergent: CFEngine-এর মতো tool একটি লক্ষ্যের দিকে টানে, তবে লক্ষ্যে না লেখা জিনিস আলাদাই থাকে। Congruent: শুরু থেকে একই ক্রমে একই কাজ, তাই server হুবহু এক। Mark Burgess-এর convergence ধারণা প্রথম ধারাকে দ্বিতীয় ধারায় নিয়ে আসে।

এই পর্বের পরীক্ষাই দেখুন: /etc/hotfix-note web2-তে রয়ে গেল, playbook টের পায়নি। আরও সূক্ষ্ম উদাহরণ: দুটি server ছয় মাস আগে আর পরে বানানো। একই playbook, তবে “latest” package আলাদা, অথবা apt repository-র অবস্থা আলাদা। Task-এর ক্রমও গুরুত্বপূর্ণ: কোনো task একবার ভুলে অন্য ফাইল বদলে থাকলে তার ফল থেকে যায়, আর সেই task সরালেও ফল মোছে না।

তাই দীর্ঘজীবী server-এ playbook একই হলেও ইতিহাস আলাদা, আর ইতিহাস আলাদা মানে অবস্থাও আলাদা হতে পারে। Immutable image এই ইতিহাসকেই ছেঁটে ফেলে: সব server একই image থেকে, একই শুরু।

মূল উপলব্ধি: Idempotent মানে “নিরাপদে আবার চালানো যায়”; identical নয়। হুবহু একই server চাইলে একই শুরু থেকে বানাতে হবে।

প্র ০৩২০১২ সালের Knight Capital ঘটনা থেকে configuration নিয়ে কী শেখার আছে?

মার্কিন SEC-এর ২০১৩ সালের প্রকাশিত আদেশ অনুযায়ী, ১ আগস্ট ২০১২ সকালে Knight Capital-এর একটি trading system প্রায় ৪৫ মিনিটে অনিয়ন্ত্রিত order পাঠায়। ক্ষতি হয় ৪৬০ মিলিয়ন ডলারের বেশি। আগের কয়েক দিনে নতুন code বসানো হয়েছিল আটটি server-এ, হাতে। একজন কর্মী একটি server-এ code বসাতে বাদ দেন, আর দ্বিতীয় কেউ এটি যাচাই করেননি। সেই একটি server-এ পুরোনো অব্যবহৃত code তখনো ছিল, আর নতুন একটি flag সেই পুরোনো code-কে জাগিয়ে তোলে।

কারিগরি দিক থেকে এটি নিখুঁত drift: আটটির মধ্যে সাতটি নতুন, একটি পুরোনো। সবাই ধরে নিয়েছিল fleet একই রকম। আর কোনো স্বয়ংক্রিয় যাচাই ছিল না, যা বলবে “এই server আলাদা”।

এখান থেকে আজকের ভাষায় কয়েকটি শিক্ষা। এক: deploy হাতে নয়, এমন tool দিয়ে, যার recap বলে কোন host-এ কী হলো, আর কোন host বাদ পড়েছে। দুই: শেষে একটি আলাদা যাচাই: সব server-এ version একই কি না। তিন: পুরোনো অব্যবহৃত code মুছে ফেলুন, আর পুরোনো flag-এর নাম নতুন কাজে ব্যবহার করবেন না। চার: ধীরে ছড়ান, আর বন্ধ করার দ্রুত পথ রাখুন। শুধু একটি tool এখানে যথেষ্ট হতো না; ব্যবস্থাপনা আর নিয়ন্ত্রণের দুর্বলতাও SEC-এর আদেশের বড় অংশ।

লক্ষ করুন, এই ঘটনায় কোনো হ্যাকার নেই, কোনো hardware নষ্ট হয়নি। শুধু একটি server অন্যদের মতো ছিল না। ব্যয়বহুল ভুল অনেক সময় এমন সাধারণ দেখায়।

মূল উপলব্ধি: “সব server একই” একটি অনুমান, প্রমাণ নয়। Automation আর যাচাই সেই অনুমানকে প্রমাণে বদলায়।

প্র ০৪Immutable server কি সত্যিই কখনো বদলায় না? Bug খুঁজতে SSH বন্ধ রাখলে চলে?

“Immutable” শব্দটি একটু বাড়িয়ে বলা। চলমান server-এর memory, log, temp ফাইল আর cache তো বদলায়ই। আসল অর্থ: চালু হওয়ার পর কেউ তার configuration বা software ইচ্ছা করে বদলায় না। Chad Fowler ২০১৩ সালে যখন “Trash Your Servers and Burn Your Code” লেখেন, তখনই মূল কথা ছিল server-কে ফেলে দেওয়ার মতো বানানো। Martin Fowler-এর phoenix server (২০১২) একই দিকে ইঙ্গিত করে। Netflix প্রকাশ্যে জানায় যে তারা AMI “bake” করে deploy করত, আর এই ধারা Aminator নামের tool-এ রূপ পায়।

কিন্তু একটি বড় শর্ত আছে: state-কে বাইরে রাখতে হবে। Upload করা ফাইল object storage-এ, session cache-এ, data managed database-এ, log কেন্দ্রীয় জায়গায় (পর্ব ৬৪)। না হলে server ফেলার সময় data-ও যাবে। পর্ব ১৭-র autoscaling-ও একই শর্ত চায়।

SSH নিয়ে: অনেক দল login পুরো বন্ধ রাখে, কারণ SSH খোলা মানেই হাতের বদলের লোভ। তবে সমস্যা খুঁজতে কখনো কখনো ভেতরে দেখতে হয়। ভারসাম্যের পথ হলো আগে metric, log আর trace (মডিউল ১১) দিয়ে উত্তর খোঁজা। দরকার হলে break-glass: audit করা, সময়সীমা-বাঁধা প্রবেশ। AWS Session Manager, Azure Bastion বা Google-এর IAP-এর মতো সেবাও খোলা SSH port ছাড়া ঢোকার পথ দেয়। আরেকটি ভালো অভ্যাস: যে server-এ ঢুকেছেন, তাকে কাজ শেষে replace করুন, কারণ সে আর “পরিচিত অবস্থায়” নেই।

আরেকটি দাম: ছোট একটি বদলের জন্যও পুরো pipeline চলে, যা হয়তো ত্রিশ মিনিট লাগে। তাই ঘনঘন বদলানো config (feature flag, limit) image-এ না রেখে বাইরে রাখা হয়।

মূল উপলব্ধি: Immutable কোনো ধর্ম নয়, একটি শৃঙ্খলা: state বাইরে, config বাইরে, আর server ফেলে দেওয়া যায় এমন।

প্র ০৫Ansible Vault-এ encrypt করা secret Git-এ রাখা কতটা নিরাপদ?

সাধারণ লেখার চেয়ে অনেক ভালো, তবে আপনাকে এর সীমা জানতে হবে। লেখার সময়ের Vault format-এ ফাইল AES-256 দিয়ে encrypt হয়, key আসে আপনার password থেকে PBKDF2 দিয়ে, আর HMAC দিয়ে লেখার অখণ্ডতা যাচাই হয়। Algorithm-এ জানা কোনো ব্যবহারিক দুর্বলতা নেই। দুর্বলতা থাকে চারপাশে।

Password-এর শক্তি: ফাইলটি Git-এ থাকলে, যে কেউ repository পেলে নিজের কম্পিউটারে অসীম চেষ্টা চালাতে পারে; কোনো rate limit নেই। তাই সব কিছু নির্ভর করে password কত লম্বা ও এলোমেলো, তার ওপর। দুর্বল password হলে encryption-এর দাম কমে যায়। ছোট একটি শব্দ নয়, password manager-এ রাখা লম্বা এলোমেলো মান ব্যবহার করুন।

Git ইতিহাস ভোলে না: Vault password ফাঁস হলে ansible-vault rekey শুধু নতুন commit-কে রক্ষা করে। পুরোনো commit-এর ফাইল আগের password দিয়েই খোলে। তাই password ফাঁস মানে শুধু rekey নয়, ভেতরের প্রতিটি secret-ও বদলাতে হবে।

একটি password, অনেক মানুষ: সাধারণত পুরো দল একই vault password জানে। কে কখন কোন secret পড়ল, তার কোনো audit নেই। কেউ চলে গেলে সব বদলানো উচিত। Vault ID দিয়ে dev আর prod আলাদা করা যায়, তবে সমস্যাটি পুরো যায় না।

Server-এ পৌঁছানোর পর: Secret সাধারণ লেখা হয়ে ফাইলে বসে, আর ভুলে -vvv বা no_log ছাড়া task-এ log-এ চলে যেতে পারে। পর্ব ৩৩-এর secret manager এখানে এগিয়ে: কেন্দ্রীয় audit, ছোট আয়ুর credential, আর instance role দিয়ে পরিচয়-ভিত্তিক প্রবেশ।

মূল উপলব্ধি: Vault ছোট দলের জন্য সৎ, সহজ সমাধান। দল আর secret বাড়লে প্রয়োজন audit আর rotation, যা একটি encrypted ফাইল দিতে পারে না।

অনুশীলন

প্রথম দুটি খাতা-কলমে করুন। নিজে চালাতে চাইলে শুধু throwaway container বা VM ব্যবহার করুন; cloud-এ চালালে খরচ হতে পারে, আর শেষে সব মুছে ফেলুন।

  1. Rollout-এর হিসাব: ২০০টি host, serial: 20%, max_fail_percentage: 10. প্রতি batch-এ ৫ মিনিট লাগে। (ক) Batch-এর আকার ও সংখ্যা কত? (খ) ব্যর্থতা না হলে মোট সময় কত? (গ) একটি batch-এ ৪টি ব্যর্থ হলে কী হবে? ৫টি হলে?

    (ক) ২০০-এর ২০% = ৪০ host প্রতি batch; ২০০ ÷ ৪০ = ৫ batch. (খ) ৫ × ৫ = ২৫ মিনিট। (গ) ৪/৪০ = ১০%, যা ১০-এর সমান, ছাড়ায়নি, তাই rollout চলবে। ৫/৪০ = ১২.৫%, যা ১০ ছাড়িয়েছে, তাই থেমে যাবে। পরের batch-এর host-গুলো ছোঁয়া হবে না।

  2. আউটপুট অনুমান করুন: একটি নতুন host-এ দুটি task. প্রথমটি lineinfile দিয়ে a.conf-এ debug=off লাইন নিশ্চিত করে। দ্বিতীয়টি shell: echo debug=off >> b.conf. Playbook তিনবার চালানো হলো, gather_facts: false. তৃতীয় run-এর recap-এ ok আর changed কত? a.conf আর b.conf-এ কত লাইন?

    তৃতীয় run: ok=2 changed=1. lineinfile ok, shell changed, আর changed task-ও ok-তে গোনা হয়। a.conf-এ ১ লাইন, b.conf-এ ৩ লাইন।

  3. Playbook সমালোচনা করুন: ৮০টি prod server-এর জন্য একজন লিখলেন: hosts: all, একটি shell: apt-get upgrade -y && reboot task, ignore_errors: true, আর vars-এ সরাসরি db_password: abc123. কী কী সমস্যা, আর কীভাবে ঠিক করবেন?
    • সব host একসঙ্গে reboot: পুরো সাইট বন্ধ। serial দিন, load balancer থেকে সরান, health check রাখুন।
    • hosts: all: নির্দিষ্ট group লিখুন, আগে --limit.
    • shell: ansible.builtin.apt আর ansible.builtin.reboot ব্যবহার করুন।
    • ignore_errors: ব্যর্থতা ঢাকে; মুছে max_fail_percentage দিন।
    • Password: Git-এ সাধারণ লেখা। এখনই বদলান, Vault বা secret manager-এ নিন। ইতিহাসে আগের মান থেকেই যাবে।
    • বৃহত্তর প্রশ্ন: ফেলে দেওয়ার মতো web server হলে upgrade না করে নতুন image rollout করাই ভালো।
  4. নকশা করুন: একটি কাল্পনিক অ্যাপের জন্য নিচের জিনিসগুলো কোথায় রাখবেন: golden image, boot-এর সময়, নাকি বাইরের কোনো সেবায়? (ক) nginx আর OS patch (খ) prod database-এর password (গ) App-এর version (ঘ) কোন environment, dev না prod (ঙ) ব্যবহারকারীর upload করা ছবি।

    (ক) Image-এ, Packer আর Ansible role দিয়ে। (খ) Secret manager-এ; boot-এর সময় instance role দিয়ে আনা, কখনো image-এ নয়। (গ) নিয়মিত release হলে image-এ, অথবা boot-এ artifact থেকে; দলের পছন্দ, তবে version বাঁধা থাকবে। (ঘ) Boot-এর সময়, user data বা tag থেকে, যাতে একই image সব environment-এ চলে। (ঙ) Object storage-এ, কারণ server ফেলে দিলে এগুলো হারালে চলবে না।

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

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

ক্রমImmutable পথে একটি বদলের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. Git-এ role বা template বদলে review করা
  2. Packer-এর অস্থায়ী VM-এ role চালিয়ে image বানানো
  3. Image boot করে health check আর scan চালানো
  4. Launch template-কে নতুন image-এ দেখানো
  5. Batch ধরে পুরোনো instance বদলে নতুন instance আনা

Ansible-কে “agentless” বলা হয় কেন?

  • Managed node-এ কিছুই লাগে না
  • Host-এ স্থায়ী agent নেই; SSH দিয়ে module পাঠিয়ে চালানো হয়
  • Host নিজে নিয়মিত সার্ভার থেকে নির্দেশ টেনে আনে
  • এটি শুধু cloud API দিয়ে কাজ করে

একটি playbook দ্বিতীয়বার চালানোর পর recap: ok=4 changed=1. কিছুই নতুন বদলানো হয়নি। এর সবচেয়ে সম্ভাব্য অর্থ কী?

  • একটি task ব্যর্থ হয়েছে
  • সব task idempotent, ফল ঠিক আছে
  • একটি task idempotent নয়, সম্ভবত সাধারণ shell বা command
  • একটি host-এ পৌঁছানো যায়নি

একটি task notify: Reload nginx দিয়েছে। এই run-এ task-টির ফল ok. Handler কী করবে?

  • চলবে না
  • সঙ্গে সঙ্গে চলবে
  • Play-এর শেষে সবসময় চলবে
  • Playbook ব্যর্থ হবে

৮ host-এ serial: 4 আর max_fail_percentage: 50. প্রথম batch-এ ঠিক ২টি host ব্যর্থ হলো। কী হবে?

  • Play সঙ্গে সঙ্গে থেমে যাবে
  • ব্যর্থ host-এ নিজে থেকে আবার চেষ্টা হবে
  • পুরো fleet শুরু থেকে চলবে
  • Rollout পরের batch-এ যাবে, কারণ ৫০% সীমা ছাড়ায়নি

কেউ একটি server-এ হাতে /etc/extra.conf ফাইল বানালেন। এই ফাইলের কথা কোনো playbook-এ নেই। পরের Ansible run-এ কী হবে?

  • ফাইলটি মুছে যাবে
  • ফাইলটি থেকে যাবে, recap-এ কিছুই দেখাবে না
  • Recap-এ drift হিসেবে দেখাবে
  • Playbook ব্যর্থ হবে

একটি stateful database host-কে immutable পথে রোজ ফেলে নতুন করে আনা যায় না। এর জন্য সঠিক ব্যবস্থা কোনটি হতে পারে?

  • প্রতিদিন server ফেলে দেওয়া, data-র কথা না ভেবে
  • কখনো patch না করা
  • Ansible দিয়ে serial-এ নিয়ন্ত্রিত patch, অথবা managed database ব্যবহার
  • প্রতিটি host-এ হাতে SSH করে patch করা

শব্দকোষ

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

মনে রাখুন

  1. Terraform বানায়, Ansible সাজায়। Ansible agentless, SSH আর Python দিয়ে push করে।
  2. দ্বিতীয় run-এ changed=0 হওয়া উচিত; shell ব্যবহার করলে সেটি আপনার দায়।
  3. Handler চলে শুধু বদল হলে। Fact, variable আর Jinja2 এক template-কে host-ভেদে আলাদা করে; role কাজ গোছায়।
  4. নিরাপদ পথ: --check --diff, --limit, serial, health check. Failure rate সীমা ছাড়ালে তবেই থামে।
  5. CM শুধু লেখা জিনিস সামলায়। Immutable পথে Packer image বানায়, আর server বদলে যায়; state থাকে বাইরে।

পরের পর্বে: মডিউল ৯ এখানেই শেষ। Infrastructure এখন code. এই code আর app-এর code পরীক্ষা, build আর deploy করবে কে? মডিউল ১০ শুরু হবে CI/CD pipeline-এর নকশা দিয়ে: stage, cache, parallel আর গতি।

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

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