Skip to content

Ansible Fundamentals

Inventory, playbook, role, আর variable — কোনো agent install না করেই নির্ভরযোগ্যভাবে server configure করা।

বাস্তব জীবনের উদাহরণ

script হাতে একজন stage director: তিনি প্রতিটা কাজ নিজে করেন না — তিনি সঠিক actor-দের (host) সঠিক ক্রমে (task) নির্দেশ দেন, আর ভিন্ন character-কে ভিন্নভাবে সামলানোর নমনীয়তা রাখেন (variable আর conditional)। Ansible হলো director; আপনার server-গুলো হলো cast।

গল্পে বুঝি

সিনার একটা নামকরা রেস্তোরাঁর চেইন — শহরের নানা মহল্লায় ছড়ানো অনেকগুলো শাখার রান্নাঘর, প্রতিটাই আগে থেকে চালু। সমস্যা হলো, প্রতিটা শাখার রান্না একেক রকম হয়ে যাচ্ছিল। তাই সিনা একটা লিখিত নির্দেশনা-শিট বানালেন, ধাপে ধাপে সাজানো: “১. কাউন্টার ঘষে পরিষ্কার করো, ২. মসলার তাক ভরাও, ৩. চুলা জ্বালাও”। তারপর একজন দৌড়বিদ ছেলেকে দিয়ে প্রতিটা শাখায় হুবহু একই শিট পাঠিয়ে দিলেন। শাখায় কোনো স্থায়ী লোক বসিয়ে রাখতে হয়নি — দৌড়বিদ শুধু শিটটা পৌঁছে দিয়ে রাঁধুনিকে ধাপগুলো ধরিয়ে দিয়ে আসে।

মজার ব্যাপার হলো, কোনো ধাপ যদি আগে থেকেই করা থাকে — ধরুন মসলার তাক আগেই ভরা — রাঁধুনি সেটা টুক করে টপকে যায়, নতুন করে কিছু বদলায় না। তাই একই শিট আজ পাঠান, কাল পাঠান, পরশু পাঠান — কোনো ক্ষতি নেই; যেটুকু বাকি সেটুকুই ঠিক হয়, বাকিটা যেমন আছে তেমনই থাকে। খোয়ারিজমি চাইলে শুধু নতুন খোলা শাখাটায়ও একই শিট পাঠিয়ে সেটাকে সবার সাথে মিলিয়ে দিতে পারেন।

এই গল্পটাই আসলে Ansible। ধাপে ধাপে সাজানো নির্দেশনা-শিটটাই হলো একটা playbook, যার প্রতিটা লাইন একেকটা task। শিটটা চালু শাখাগুলোয় পাঠানোটাই push-based config — Ansible আপনার control node থেকে চালু server-গুলোতে SSH দিয়ে গিয়ে task চালায়। দৌড়বিদের জন্য শাখায় স্থায়ী কেউ লাগে না — এটাই agentless, target-এ কোনো daemon বসাতে হয় না। “আগে থেকে করা ধাপ টপকে যাও, কিছু বদলায় না” — এটাই idempotency, একই playbook বারবার চালালেও শুধু যা desired state-এ নেই সেটুকুই বদলায়। আর কোন কোন শাখায় শিট যাবে তার তালিকাটাই inventory। বাস্তবে ঠিক এভাবেই টিমগুলো Ansible দিয়ে ডজন ডজন চালু server-কে একই কনফিগে standardize করে — নতুন কোনো agent install না করেই।

Ansible কীভাবে কাজ করে

Ansible agentless — এটা target মেশিনে SSH করে আর task চালায়। Managed node-এ কোনো daemon install করতে হয় না। প্রয়োজন: control node থেকে SSH access, target-এ Python।

Control node (your laptop or CI server)
  → SSH to managed nodes
  → Copies and runs Python modules
  → Reports results back
  → Applies changes to reach desired state

Inventory

Inventory define করে Ansible কোন host manage করে আর কীভাবে সেগুলোতে পৌঁছায়:

# inventory/hosts.ini

[web]
web-1.example.com
web-2.example.com ansible_user=ubuntu

[db]
db-primary.example.com ansible_user=postgres ansible_port=2222

[all:vars]
ansible_user=admin
ansible_ssh_private_key_file=~/.ssh/deploy_key

[production:children]
web
db

Dynamic inventory (cloud environment-এর জন্য — scale করার সাথে host বদলায়):

# AWS dynamic inventory
ansible-inventory -i aws_ec2.yml --list

# aws_ec2.yml
plugin: aws_ec2
regions:
  - us-east-1
filters:
  instance-state-name: running
  tag:Environment: production
keyed_groups:
  - key: tags.Role     # group by Role tag: [tag_Role_web], [tag_Role_db]

Playbook

একটা playbook হলো play-এর একটা list। প্রতিটা play একদল host-এ task apply করে।

# site.yml
---
- name: Configure web servers
  hosts: web
  become: true # sudo
  vars:
    app_port: 3000
    nginx_worker_processes: auto

  tasks:
    - name: Update apt cache
      apt:
        update_cache: true
        cache_valid_time: 3600 # only update if cache is > 1hr old

    - name: Install packages
      apt:
        name:
          - nginx
          - certbot
          - python3-certbot-nginx
        state: present

    - name: Create app user
      user:
        name: appuser
        system: true
        shell: /usr/sbin/nologin

    - name: Deploy nginx config
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/sites-available/myapp
        owner: root
        group: root
        mode: '0644'
      notify: Reload nginx # triggers handler only if this task changed something

    - name: Enable nginx site
      file:
        src: /etc/nginx/sites-available/myapp
        dest: /etc/nginx/sites-enabled/myapp
        state: link

  handlers:
    - name: Reload nginx
      service:
        name: nginx
        state: reloaded # reload (not restart) — avoids dropping connections
# Run the playbook
ansible-playbook -i inventory/hosts.ini site.yml

# Dry run — show what would change
ansible-playbook -i inventory/hosts.ini site.yml --check --diff

# Limit to specific hosts
ansible-playbook -i inventory/hosts.ini site.yml --limit web-1.example.com

# Run specific tags
ansible-playbook -i inventory/hosts.ini site.yml --tags nginx

Template (Jinja2)

Template দিয়ে আপনি variable সহ config file generate করতে পারেন:

{# templates/nginx.conf.j2 #}
upstream app {
    {% for i in range(app_instances) %}
    server 127.0.0.1:{{ app_port + i }};
    {% endfor %}
}

server {
    listen 80;
    server_name {{ domain_name }};

    location / {
        proxy_pass http://app;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    access_log /var/log/nginx/{{ app_name }}.access.log;
    error_log  /var/log/nginx/{{ app_name }}.error.log;
}

Variable আসে: playbook vars, inventory vars, group_vars, host_vars থেকে, কিংবা runtime-এ পাস করা হয়।

Variable আর Precedence

# group_vars/web.yml — applies to all hosts in [web] group
app_port: 3000
nginx_worker_processes: auto
log_level: warn

# group_vars/production.yml — overrides for production
log_level: error

# host_vars/web-1.example.com.yml — overrides for specific host
nginx_worker_connections: 2048

Ansible variable precedence (ছোট নম্বর = কম priority, বড় নম্বর দিয়ে override হয়):

1. Role defaults
2. Inventory vars
3. group_vars
4. host_vars
5. Play vars
6. Task vars (highest — use sparingly)

Role

Role হলো পুনর্ব্যবহারযোগ্য, স্বয়ংসম্পূর্ণ Ansible content-এর unit। একটা বিশাল playbook-এর বদলে role task-গুলোকে function অনুযায়ী সাজায়।

roles/
  nginx/
    tasks/
      main.yml        # entry point
      install.yml
      configure.yml
    handlers/
      main.yml
    templates/
      nginx.conf.j2
      vhost.conf.j2
    defaults/
      main.yml        # default variable values
    vars/
      main.yml        # role-specific variables (higher precedence than defaults)
    files/
      dhparam.pem     # static files to copy
# roles/nginx/tasks/main.yml
---
- import_tasks: install.yml
- import_tasks: configure.yml

# roles/nginx/tasks/install.yml
---
- name: Install nginx
  apt:
    name: nginx
    state: present

# roles/nginx/defaults/main.yml
---
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_keepalive_timeout: 65
# Use roles in a playbook
- name: Configure web servers
  hosts: web
  become: true
  roles:
    - nginx
    - { role: certbot, domain: myapp.com }
    - node_app

Handler

Handler কেবল notify হলেই চলে — আর একবারই, play-এর শেষে, একাধিকবার notify হলেও:

tasks:
  - name: Deploy nginx.conf
    template:
      src: nginx.conf.j2
      dest: /etc/nginx/nginx.conf
    notify: Reload nginx

  - name: Deploy vhost config
    template:
      src: vhost.conf.j2
      dest: /etc/nginx/sites-available/myapp
    notify: Reload nginx # notified twice, but handler runs once

handlers:
  - name: Reload nginx
    service:
      name: nginx
      state: reloaded

যদি কোনো task-ই কিছু পরিবর্তন না করে (কারণ config আগে থেকেই সঠিক), তাহলে handler কখনো চলে না। এটাই idempotency বাস্তবে।

Ansible Vault দিয়ে Secrets

কখনো plaintext YAML-এ password বা API key রাখবেন না:

# Encrypt a file
ansible-vault encrypt group_vars/production/secrets.yml

# Edit encrypted file
ansible-vault edit group_vars/production/secrets.yml

# Run playbook with vault password
ansible-playbook site.yml --vault-password-file ~/.vault_pass
# or
ansible-playbook site.yml --ask-vault-pass
# group_vars/production/secrets.yml (encrypted at rest)
db_password: '{{ vault_db_password }}'
api_key: '{{ vault_api_key }}'

# group_vars/production/vars.yml (plaintext, references vault vars)
database_url: 'postgresql://app:{{ db_password }}@db.internal/mydb'

vault_pass CI secrets-এ রাখুন, repo-তে নয়। Encrypted file-গুলো নিরাপদে commit করা যায়।

একটা সম্পূর্ণ Server Provisioning উদাহরণ

# provision-web-server.yml
---
- name: Provision web server
  hosts: web
  become: true

  vars_files:
    - group_vars/all/secrets.yml

  roles:
    - common # base packages, users, sshd config, ufw
    - nginx # install, configure
    - node # install node via nvm
    - app # deploy application, systemd unit

  post_tasks:
    - name: Verify app is running
      uri:
        url: 'http://localhost:3000/health'
        status_code: 200
      retries: 5
      delay: 5

একটা নতুন server provision হওয়ার পর সেটার ওপর চালান:

ansible-playbook \
  -i "newserver.example.com," \  # comma = treat as list, not file
  provision-web-server.yml \
  --private-key ~/.ssh/deploy_key

যে playbook একটা নতুন server configure করে সেটাই পরে আবার চালিয়ে config পরিবর্তন apply করা বা application আপডেট করা যায়। একেবারে নিচ পর্যন্ত idempotent।