SSD Nodes Learn
गाइड Matt Connorलेखक: Matt Connor · अपडेट किया गया: 2026-07-24

Ansible tutorial: VPS पर पहला playbook

Ubuntu 24.04 पर pipx से Ansible install करें। Inventory और playbook बनाना सीखें और Permission denied व sudo errors को ठीक करने का तरीका जानें।

आप क्या बना रहे हैं

एक control machine जिसमें Ansible installed है, और एक या अधिक fresh Ubuntu 24.04 VPSes जिनमें केवल stock image है। अंत तक, आपके पास एक inventory file होगी जिसमें आपके servers के नाम होंगे, एक ad-hoc ping होगा जो यह prove करेगा कि authentication end to end काम कर रहा है, और एक playbook होगा जो पूरे new-VPS checklist को code के रूप में चलाएगा: आपकी SSH key के साथ एक deploy user, hardened sshd, fail2ban, unattended upgrades, और एक firewall जो बाकी सब कुछ deny करने से पहले OpenSSH को allow करता है। इसे एक server या बीस servers पर चलाएं। इसे दो बार चलाएं और दूसरी बार में कुछ भी नहीं बदलेगा — यही इसका मुख्य उद्देश्य है।

VPSes provision करने के पंद्रह वर्षों के अनुभव के बाद, मैं आपको एक सच्चाई बता सकता हूँ: हर कोई पहले पांच servers को manually setup करता है, फिर छठे server पर एक weekend बर्बाद कर देता है क्योंकि किसी को याद नहीं रहता कि उन्होंने पहले पांच servers के साथ क्या किया था। यह guide managing multiple Linux servers का विस्तार करती है — उस दिन इसे पढ़ें जब आप खुद को तीन terminals में एक ही apt install टाइप करते हुए पाएं।

Ansible वास्तव में क्या है, एक पैराग्राफ में

Ansible agentless है। जिन servers को यह manage करता है, उन पर कोई daemon install करने की आवश्यकता नहीं होती: control machine साधारण SSH के माध्यम से connect करती है, target पर एक छोटा Python module copy करती है, उसे execute करती है, उसके द्वारा print किए गए JSON को पढ़ती है, और उसे delete कर देती है। target के लिए केवल python3 की आवश्यकता होती है, जो हर stock Ubuntu image में पहले से मौजूद होता है। सबसे महत्वपूर्ण शब्द idempotent है, जिसका अर्थ सरल है: एक task एक state को describe करता है, न कि किसी action को। किसी package के लिए state: present का अर्थ है "सुनिश्चित करें कि यह installed है", न कि "installer चलाएं"। यदि वह state पहले से मौजूद है, तो Ansible कुछ भी नहीं बदलता और इसे changed के बजाय ok के रूप में report करता है। यही विशेषता इस product का मुख्य आधार है — यही वह गुण है जो playbook को दोबारा run करना सुरक्षित बनाता है, और सुरक्षित reruns ही shell script को infrastructure में बदलते हैं।

Prerequisites, aur shuruati savdhaniyan

  • Ek control machine: aapka laptop ya ek chhota VPS. Main Ubuntu 24.04 maan kar chal raha hoon; Homebrew se pipx install karne ke baad macOS bhi bilkul waise hi kaam karta hai.
  • Ek ya usse zyada target VPSes jin par KVM par Ubuntu 24.04 chal raha ho, aur jin tak root ke roop mein pahunch ho. Un par kuch bhi install nahi kiya jayega.
  • Har target ke liye SSH key authentication. Ansible bilkul waise hi authenticate hota hai jaise aapka ssh command — agar ssh root@host password maangta hai, to Ansible fail ho jayega.
  • Ubuntu 24.04 par, pip install ansible error: externally-managed-environment ke saath band ho jata hai. Yeh distro ki deliberate policy hai, koi breakage nahi. pipx ka upyog karein.
  • YAML whitespace syntax hai. Galat indent se mapping values are not allowed in this context hota hai, aur kahin bhi tab character hona fatal hai.
  • Jab playbook sshd ko harden kar raha ho, tab har target par ek working SSH session khula rakhein. Maine jin customers ki madad ki hai, un sabka lockout tabhi solve hua jab unhone "clean test" ke liye purana session band kiya.

Step 1: pip के बजाय pipx का उपयोग करके control machine पर Ansible install करें

सामान्य तरीका pip3 install ansible है। यदि 24.04 का बिल्कुल नया image एक कदम पहले ही विफल हो जाता है — Command 'pip3' not found, but can be installed with: sudo apt install python3-pip — और आप केवल pip install करते हैं, तो आप वास्तविक समस्या का सामना करेंगे:

pip3 install ansible
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
    python3-xyz, where xyz is the package you are trying to
    install.

Ubuntu 24.04 system Python को externally managed (PEP 668) मानता है, इसलिए pip और apt एक ही files के लिए आपस में संघर्ष नहीं कर सकते। --break-system-packages का उपयोग न करें; इसका flag स्पष्ट है। सही समाधान pipx है, जो Ansible के लिए अपना अलग isolated virtualenv बनाता है और binaries को आपके PATH में डाल देता है:

sudo apt update && sudo apt install -y pipx
pipx ensurepath
pipx install --include-deps ansible

PATH परिवर्तन लागू करने के लिए pipx ensurepath के बाद एक नया shell खोलें। --include-deps केवल सजावट नहीं है: ansible package में अपने स्वयं के console scripts नहीं होते हैं — ansible, ansible-playbook, और अन्य ansible-core dependency के entry points हैं — इसलिए flag के बिना pipx No apps associated with package ansible or its dependencies के साथ installation से मना कर देता है। और ansible-core के बजाय ansible package install करें — full package में community collections शामिल होते हैं, और यह playbook उनमें से दो (ansible.posix और community.general) के modules का उपयोग करता है।

ansible --version

सही परिणाम ansible [core 2.19.x] जैसी line के साथ शुरू होता है और उस Python का नाम बताता है जिसके अंतर्गत यह चलता है; यहाँ सब कुछ के लिए कोई भी वर्तमान core release सही है। ansible: command not found का अर्थ है कि ~/.local/bin अभी आपके PATH में नहीं है — नया shell खोलें, या source ~/.bashrc

यह पूरा installation है। target machines पर कुछ भी install नहीं होता है।

Step 2: प्रत्येक target तक SSH key access प्राप्त करें

ssh-keygen -t ed25519 -C "ansible control"
ssh-copy-id root@10.0.0.10
ssh-copy-id root@10.0.0.20

इसके बाद, प्रत्येक host के लिए एक बार इसे सत्यापित करें:

ssh root@10.0.0.10 true && echo ok

यह एक line दो कार्य करती है: यह पुष्टि करती है कि key authentication बिना password के काम कर रहा है, और यह host key को known_hosts में record करती है। इसे अभी करें, क्योंकि Ansible unrecorded host key को run के बीच में एक interactive prompt के रूप में दिखाता है, जो बिल्कुल system hang होने जैसा लगता है।

Step 3: inventory — शुरुआत में INI, विस्तार होने पर YAML

Inventory एक text file है जिसमें उन machines की सूची होती है जिन्हें Ansible एक्सेस कर सकता है। एक नए project directory में inventory.ini बनाएँ:

[vps]
web1 ansible_host=10.0.0.10
web2 ansible_host=10.0.0.20

[vps:vars]
ansible_user=root

web1 एक alias है जिसे आप चुनते हैं — यह output में दिखाई देता है और --limit web1 के माध्यम से इसे target किया जाता है। ansible_host वास्तविक address है। [vps] एक group है, और [vps:vars] उस group के प्रत्येक host के लिए variables सेट करता है; ansible_user वह user है जिससे Ansible login करता है। इसके बगल में, एक ansible.cfg है ताकि आपको दोबारा -i टाइप न करना पड़े:

[defaults]
inventory = inventory.ini

Ansible current directory से ansible.cfg पढ़ता है। YAML format में वही inventory — इसे inventory.yml के रूप में save करें और ansible.cfg को उस नाम पर point करें — तब उपयोगी होगी जब प्रत्येक host में कई variables होंगे:

vps:
  hosts:
    web1:
      ansible_host: 10.0.0.10
    web2:
      ansible_host: 10.0.0.20
  vars:
    ansible_user: root

ये दोनों समान हैं। दो servers के लिए INI देखना आसान है; बीस servers के लिए YAML बेहतर scale करता है। किसी एक को चुनें और उसके बारे में सोचना बंद करें।

Step 4: ad-hoc commands — the green pong that proves everything

ansible all -m ping

यह ICMP नहीं है। ping module एक पूर्ण rehearsal है: SSH login, module copy, target पर Python execution, और cleanup। सही परिणाम हरा (green) होना चाहिए, प्रत्येक host के लिए एक block:

web1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

Green SUCCESS का अर्थ है कि authentication, Python interpreter, और transport सभी सही काम कर रहे हैं — playbook भी सही काम करेगा। Red UNREACHABLE! का अर्थ है कि किसी भी module के चलने से पहले transport विफल हो गया; सटीक string और उसका समाधान नीचे failure modes section में दिया गया है। दो अन्य ad-hoc commands जो जानना आवश्यक हैं:

ansible all -a "uptime"
ansible all -m apt -a "update_cache=true upgrade=dist" --become

Ad-hoc का उपयोग एक बार के कार्यों (one-offs) और checks के लिए किया जाता है। कोई भी command जिसे आप दो बार चलाना चाहते हैं, उसे playbook में होना चाहिए।

Step 5: पहला playbook — new-VPS checklist as code

यह वे सभी कार्य हैं जो आप एक नए server पर पहले दस मिनटों में मैन्युअल रूप से करते। इसे site.yml के रूप में save करें:

---
- name: Baseline a fresh Ubuntu VPS
  hosts: vps
  become: true

  vars:
    deploy_user: deploy
    deploy_pubkey: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"
    baseline_packages:
      - fail2ban
      - unattended-upgrades
      - ufw
    baseline_services:
      - fail2ban
      - unattended-upgrades

  tasks:
    - name: Create the deploy user
      ansible.builtin.user:
        name: "{{ deploy_user }}"
        groups: sudo
        append: true
        shell: /bin/bash

    - name: Install the deploy user's SSH key
      ansible.posix.authorized_key:
        user: "{{ deploy_user }}"
        key: "{{ deploy_pubkey }}"

    - name: Passwordless sudo for the deploy user
      ansible.builtin.copy:
        dest: /etc/sudoers.d/deploy
        content: "{{ deploy_user }} ALL=(ALL) NOPASSWD:ALL\n"
        mode: "0440"
        validate: /usr/sbin/visudo -cf %s

    - name: Install baseline packages
      ansible.builtin.apt:
        name: "{{ baseline_packages }}"
        state: present
        update_cache: true

    - name: Enable and start baseline services
      ansible.builtin.service:
        name: "{{ item }}"
        state: started
        enabled: true
      loop: "{{ baseline_services }}"

    - name: Harden sshd with a drop-in
      ansible.builtin.copy:
        dest: /etc/ssh/sshd_config.d/00-hardening.conf
        content: |
          PasswordAuthentication no
          KbdInteractiveAuthentication no
          PermitRootLogin prohibit-password
          X11Forwarding no
        mode: "0644"
        validate: /usr/sbin/sshd -t -f %s
      notify: Restart ssh

    - name: Allow OpenSSH through ufw
      community.general.ufw:
        rule: allow
        name: OpenSSH

    - name: Enable ufw with default deny
      community.general.ufw:
        state: enabled
        policy: deny

  handlers:
    - name: Restart ssh
      ansible.builtin.service:
        name: ssh
        state: restarted

कॉपी करने के बजाय इन lines को समझना ज़रूरी है:

Variables vars: के अंतर्गत होते हैं और इन्हें "{{ deploy_user }}" के साथ reference किया जाता है — यदि value एक brace से शुरू होती है, तो पूरे expression को quote करें, अन्यथा YAML parser इसे गलत पढ़ सकता है। lookup('file', ...) runtime पर control machine से आपकी public key पढ़ता है, इसलिए playbook में कोई key material नहीं होता।

The loop. loop: "{{ baseline_services }}" प्रत्येक item के लिए service task को एक बार चलाता है, और output में प्रत्येक item अपनी अलग line पर दिखता है। ध्यान दें कि apt task पूरी package list को एक साथ लेता है — एक apt transaction तेज़ होता है और packages के लिए यही preferred pattern है; loops उन modules के लिए होते हैं जो वास्तव में एक समय में एक ही चीज़ पर कार्य करते हैं।

The handler वह concept है जिसे समझना ज़रूरी है। notify: Restart ssh का अर्थ "अभी sshd restart करें" नहीं है। यह handler को queue करता है, जो play के अंत में एक बार चलता है, और केवल तभी चलता है जब notifying task ने वास्तव में changed report किया हो। playbook को कल फिर से चलाएँ: drop-in file पहले से ही सही है, copy task ok report करता है, और sshd को restart नहीं किया जाता। validate: line एक safety mechanism है — sshd पुराने file को replace करने से पहले file को check करता है, इसलिए typo होने पर task fail हो जाता है, daemon break नहीं होता।

PermitRootLogin prohibit-password, न कि no — जानबूझकर। यह playbook एक key के साथ root के रूप में login करता है। prohibit-password password root logins को बंद कर देता है जबकि आपकी access चालू रखता है। एक बार deploy user सिद्ध हो जाने पर (ssh deploy@10.0.0.10 sudo true — plain address, क्योंकि web1 केवल Ansible जानता है), inventory में ansible_user=deploy को switch करें और बाद के run में इसे no तक tighten करें। hardening इस क्रम में करें कि आप कहीं फंस न जाएँ।

00- prefix महत्वपूर्ण है। अधिकांश keywords के लिए sshd अपने द्वारा parse किए गए पहले occurrence का सम्मान करता है, और Ubuntu का sshd_config अपने body से पहले lexical order में sshd_config.d/*.conf को शामिल करता है। Ubuntu 24.04 cloud images में उस directory में पहले से ही 60-cloudimg-settings.conf होता है, और जो providers cloud-init के माध्यम से password logins enable करते हैं, वे PasswordAuthentication yes के साथ 50-cloud-init.conf जोड़ते हैं; हमारे 00-hardening.conf को नाम देने से यह सबसे पहले sort होगा और दोनों पर जीत जाएगा।

Task order एक firewall safety है। Allow OpenSSH, deny policy के साथ Enable ufw से पहले चलता है — Ansible tasks को दिए गए क्रम में सख्ती से execute करता है, इसलिए दीवार बनने से पहले hole मौजूद होता है। यहाँ fail2ban को उपयोगी बनाने के लिए किसी configuration की आवश्यकता नहीं है; इसके Ubuntu defaults डिफ़ॉल्ट रूप से sshd की निगरानी करते हैं, और jails वास्तव में क्या करते हैं — और उन्हें कैसे tune करें — यह fail2ban on Ubuntu 24.04 guide में दिया गया है।

Step 6: dry run with --check, then run it for real

ansible-playbook site.yml --check

Check mode connects, computes what it would do, and changes nothing. Read the changed= count in the PLAY RECAP at the bottom — that is the number of tasks that would modify each host. One honest caveat: check mode has a structural limit wherever a later task depends on an earlier task's changes. Ubuntu's standard server image preships ufw, so this playbook dry-runs clean — but on a minimal image without it, the ufw tasks fail in check mode, because check mode never actually installed the package and the module then has nothing to call. That is a limit of dry runs, not a bug in your playbook. When the plan looks right:

ansible-playbook site.yml

Each task prints a line per host — yellow changed, green ok — and the recap should read:

PLAY RECAP *********************************************************************
web1 : ok=10  changed=9  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0
web2 : ok=10  changed=9  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0

Ten ok is fact-gathering plus eight tasks plus the handler. Your changed is allowed to differ from mine by one or two: Ubuntu's standard image preships ufw and unattended-upgrades, and fail2ban starts itself the moment apt installs it, so a task can legitimately report ok on its very first run — the state it declares already held. The numbers that must be zero are unreachable and failed. One note on become: true: it is a formality while you connect as root, but the moment you flip ansible_user to deploy, sudo is real — and the NOPASSWD sudoers file this playbook installs is exactly what keeps -K off your command line. Without it you get Missing sudo password, covered below.

Step 7: इसे दो बार चलाएं — idempotence ऐसा दिखता है

तुरंत वही command दोबारा चलाएं:

web1 : ok=9  changed=0  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0

changed=0, और ok में एक की कमी आई क्योंकि un-notified handler कभी नहीं चला। कुछ भी reinstall नहीं हुआ, sshd restart नहीं हुआ, और ufw में कोई बदलाव नहीं हुआ। यही विशेषता playbook को provisioner के साथ-साथ एक audit टूल भी बनाती है: अगले महीने inventory में web3 जोड़ें और फिर से चलाएं — नया box build हो जाएगा, और पुराने boxes verify हो जाएंगे। यदि किसी untouched box पर changed non-zero आता है, तो इसका मतलब drift है; यह बताता है कि किसी ने उस चीज़ को manually edit किया है जिसे playbook के ज़रिए edit किया जाना चाहिए था।

यहाँ से pattern और बढ़ता है। अगला playbook उसी VPS पर WireGuard VPN setup करने के लिए लिखा जा सकता है और ufw rule को tighten कर सकता है ताकि SSH केवल tunnel पर respond करे; उसके बाद, एक ऐसा playbook जो हर app server पर Docker and Compose install करे। जब site.yml तीन screens से ज़्यादा लंबा हो जाए, तब इसे roles में split करें — लेकिन उससे पहले नहीं।

Failure modes, with the strings you will see

UNREACHABLE with Permission denied.

web1 | UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: root@10.0.0.10: Permission denied (publickey).",
    "unreachable": true
}

SSH transport किसी भी module के चलने से पहले ही fail हो गया: ansible_user गलत है, key उस host पर copy नहीं की गई है, या गलत key का उपयोग किया जा रहा है। इसे reproduce करने के लिए plain ssh root@10.0.0.10 का उपयोग करें, फिर ssh -v चलाकर देखें कि कौन सी keys offered की गई थीं। यदि password SSH काम कर रहा है लेकिन Ansible नहीं, तो आपने ssh-copy-id को skip कर दिया है।

Missing sudo password.

web1 | FAILED! => {
    "msg": "Missing sudo password"
}

आपने become: true सेट किया है, non-root user के रूप में connect किया है, और उस user को sudo के लिए password की आवश्यकता है। या तो command line में -K (--ask-become-pass) जोड़ें, या user को NOPASSWD sudoers entry दें — playbook deploy के लिए यही entry install करता है ताकि आप switch कर सकें।

error: externally-managed-environment. आपने Ubuntu 24.04 पर system Python के विरुद्ध pip चलाया है। इसे step 1 में समझाया गया है: pipx का उपयोग करें, pip का नहीं, और न ही --break-system-packages का।

mapping values are not allowed in this context.

ERROR! Syntax Error while loading YAML.
  mapping values are not allowed in this context

यह लगभग हमेशा indentation की गलती होती है: key गलत depth पर है, या colon के बाद space नहीं है। रिपोर्ट किया गया line number गलती के पास का होता है, ठीक उस line पर नहीं — ऊपर वाली line भी check करें। इसका दूसरा रूप found character '\t' that cannot start any token है, जिसका अर्थ है कि tab का उपयोग हुआ है; YAML में tabs वर्जित हैं। हर run से पहले ansible-playbook site.yml --syntax-check की आदत डालें, और अपने editor को YAML के लिए two-space indentation पर सेट करें।

/usr/bin/python3: not found. standard Ubuntu 24.04 images पर यह दुर्लभ है, लेकिन minimal या netboot images पर यह common है: module execution fail हो जाता है क्योंकि target पर Python नहीं है। इसे raw module से bootstrap करें, यह एकमात्र module है जिसे remote side पर किसी चीज़ की आवश्यकता नहीं होती: ansible all -m raw -a "apt-get update && apt-get install -y python3" --become, फिर playbook को दोबारा चलाएं।

FAQ

क्या मुझे उन servers पर Ansible install करने की आवश्यकता है जिन्हें यह manage करता है?

नहीं। Ansible agentless है: control machine SSH के माध्यम से छोटे Python modules भेजती है, उन्हें run करती है, और फिर उन्हें हटा देती है। Target machine पर केवल python3 और SSH access की आवश्यकता होती है, जो stock Ubuntu images में पहले से मौजूद होते हैं। इस पूरे guide में केवल आपकी control machine पर ही installation होगा।

Ansible "Permission denied (publickey)" क्यों कहता है?

Permission denied (publickey) के साथ UNREACHABLE! block का अर्थ है कि Ansible के कुछ भी run करने से पहले SSH authentication विफल हो गया। Check करें कि inventory में ansible_user आपके द्वारा setup किए गए account से मेल खाता है, आपने उस host पर ssh-copy-id run किया है, और plain ssh user@host बिना password के login होता है। जो भी plain ssh command को ठीक करता है, वही Ansible को भी ठीक करता है, क्योंकि दोनों एक ही transport का उपयोग करते हैं।

Ansible में idempotent का क्या अर्थ है?

एक task किसी action के बजाय एक desired state declare करता है — जैसे "यह package मौजूद है" या "यह line इस file में है"। यदि state पहले से ही वैसी ही है, तो Ansible कुछ नहीं करता और changed के बजाय ok report करता है। यही कारण है कि playbook को दूसरी बार run करने पर दूसरी बार changed=0 दिखता है, और rerun करना एक risky re-install के बजाय एक safe audit है।

क्या मुझे Ubuntu 24.04 पर Ansible install करने के लिए pip या pipx का उपयोग करना चाहिए?

pipx का। Ubuntu 24.04 system Python को externally managed मानता है, इसलिए design के अनुसार pip install ansible, error: externally-managed-environment के साथ fail हो जाता है। pipx install --include-deps ansible Ansible को एक isolated virtualenv में रखता है और ansible, ansible-playbook और अन्य को आपके PATH पर साफ़ तरीके से उपलब्ध कराता है।

ansible और ansible-core packages के बीच क्या अंतर है?

ansible-core engine और केवल ansible.builtin modules है। ansible package core को curated community collections के साथ bundle करता है — जिसमें ansible.posix (authorized_key module) और community.general (ufw module) शामिल हैं, जिनका उपयोग इस guide में किया गया है। पूरे package के साथ शुरुआत करें; core और hand-picked collections तक तभी सीमित करें जब आपके पास कोई ठोस कारण हो।