Ansible templates और handlers का उपयोग कैसे करें
Jinja2 template से nginx config को render करना सीखें। एक handler का उपयोग करके केवल वास्तविक बदलाव पर service reload करना और idempotence की जांच करना इस लेख में शामिल है।
Ansible templates और handlers आपके पहले playbook में क्या जोड़ते हैं
Ansible templates और handlers वे दो घटक हैं जो एक static playbook को उपयोगी बनाते हैं। एक template आपके variables से config file को render करता है, जिससे एक ही file सभी hosts के लिए काम करती है। एक handler केवल तब चलता है जब किसी task ने वास्तव में कुछ बदला हो, इसलिए service केवल वास्तविक config बदलाव पर reload होती है और बाकी समय उसे छेड़ा नहीं जाता।
यह guide वहीं से शुरू होती है जहाँ आपका पहला Ansible playbook on a VPS समाप्त हुआ था। आपके पास पहले से ही एक play है जो package install करती है और service start करती है। नीचे दी गई हर चीज़ एक ही machine पर चलती है, क्योंकि play local connection के माध्यम से localhost को target करती है। इसे follow करने के लिए आपको दूसरे server की आवश्यकता नहीं है। यही play बिना किसी बदलाव के वास्तविक inventory hosts पर भी चलती है, और अंतिम section में बताया गया है कि क्या बदलता है।
कार्यशील निर्देशिका (working directory) सेट करें
sudo apt update
sudo apt install -y ansible nginx
ansible --version
mkdir -p ~/ansible-templates/templates
cd ~/ansible-templatesnginx यहाँ केवल इसलिए है क्योंकि यह एक वास्तविक service है जिसमें एक config file और एक reload command है, जो इस उदाहरण के लिए आवश्यक है। ansible --version, ansible-core का version और उपयोग होने वाला Python interpreter प्रिंट करता है। दोनों को नोट कर लें। नीचे दिया गया playbook, ansible.builtin.template जैसे fully qualified module names का उपयोग करता है, जिसके लिए Ansible 2.10 या उससे नया version चाहिए, और कोई भी वर्तमान distribution package इससे काफी आगे है।
inventory.ini बनाएँ:
[local]
localhost ansible_connection=local ansible_python_interpreter="{{ ansible_playbook_python }}"ansible_connection=local, Ansible को प्रत्येक task को local process के रूप में चलाने का निर्देश देता है, बजाय इसके कि वह खुद के लिए SSH session खोले। दूसरी setting केवल दिखावा नहीं है। जब आप inventory file में localhost लिखते हैं, तो यह एक सामान्य host बन जाता है, और यह उस interpreter को खो देता है जिसे Ansible implicit localhost को मुफ्त में देता है। इसलिए, यह interpreter discovery पर वापस चला जाता है और play चलाने वाले Python से अलग Python चुन सकता है। ansible_playbook_python वह interpreter है जो अभी ansible-playbook चला रहा है, जो दोनों को एक समान रखता है।
ansible.cfg बनाएँ:
[defaults]
inventory = inventory.iniउस file के बिना आपको हर command पर -i inventory.ini पास करना होगा। बिना किसी inventory के, Ansible [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all' प्रिंट करता है, और hosts: all वाला play फिर किसी से match नहीं करता। ansible.cfg के बारे में एक और बात: जब यह world-writable directory में होता है तो Ansible इसे ignore कर देता है, इसलिए project को अपनी home directory के अंदर रखें। एक inventory file में hosts की सूची से अधिक जानकारी होती है, और यह सबसे छोटी file है जो यह काम करती है।
template बनाम copy, और प्रत्येक का सही उपयोग कब करें
ansible.builtin.copy किसी फ़ाइल को वैसा ही स्थानांतरित करता है जैसी वह है। ansible.builtin.template फ़ाइल को पहले Jinja2 के माध्यम से चलाता है और परिणाम को स्थानांतरित करता है। मॉड्यूल स्रोत template को "एक वर्चुअल मॉड्यूल के रूप में वर्णित करता है जो पूरी तरह से एक एक्शन प्लगइन के रूप में कार्यान्वित है और कंट्रोलर पर चलता है", और इसका एक परिणाम है जिसे याद रखना चाहिए: रेंडरिंग उस मशीन पर होती है जहाँ आपने ansible-playbook टाइप किया था। टारगेट होस्ट कभी भी आपके वेरिएबल्स को नहीं देखता है और उसे Jinja2 इंस्टॉल करने की आवश्यकता नहीं होती है।
जब फ़ाइल हर होस्ट पर समान हो तो copy का उपयोग करें। जैसे ही प्रति होस्ट एक भी मान अलग हो, या आपको {% for %} लूप या {% if %} ब्लॉक की आवश्यकता हो, तो template का उपयोग करें। copy में content: पैरामीटर होता है, और इसके अंदर के वेरिएबल्स को किसी भी अन्य टास्क आर्ग्युमेंट की तरह प्रतिस्थापित किया जाता है, लेकिन वहां कोई लूप या कंडीशनल नहीं होते हैं, इसलिए संरचना वाली कोई भी चीज़ टेम्पलेट में होनी चाहिए। दोनों मॉड्यूल समान फ़ाइल विकल्प लेते हैं, क्योंकि दोनों समान डॉक्यूमेंटेशन फ्रैगमेंट का उपयोग करते हैं, इसलिए owner, group, mode, backup और validate प्रत्येक में समान रूप से व्यवहार करते हैं।
टेम्पलेट लिखें: एक वेरिएबल, एक लूप
इसे templates/app.conf.j2 के रूप में सेव करें:
# {{ ansible_managed }}
upstream {{ app_name }}_backend {
{% for backend in app_backends %}
server {{ backend.host }}:{{ backend.port }} weight={{ backend.weight }};
{% endfor %}
}
server {
listen {{ app_listen_port }};
server_name {{ app_server_name }};
location / {
proxy_pass http://{{ app_name }}_backend;
proxy_set_header Host $host;
}
}यहाँ दो प्रकार के Jinja2 टैग काम करते हैं। {{ ... }} एक एक्सप्रेशन है और अपने मान को प्रिंट करता है। {% ... %} एक स्टेटमेंट है और स्वयं कुछ भी प्रिंट नहीं करता है। app_backends डिक्शनरी की एक सूची है, इसलिए backend.host प्रत्येक प्रविष्टि से एक की (key) पढ़ता है, और लूप प्रत्येक प्रविष्टि के लिए एक server लाइन लिखता है, चाहे आप कितनी भी प्रविष्टियाँ परिभाषित करें।
व्हाइटस्पेस के बारे में एक विवरण, क्योंकि यह उन लोगों को आश्चर्यचकित करता है जो Jinja2 को कहीं और से जानते हैं। Ansible डिफ़ॉल्ट रूप से trim_blocks को yes पर सेट करता है, जो Jinja2 स्वयं नहीं करता है, इसलिए {% ... %} टैग के ठीक बाद की नई लाइन हटा दी जाती है और लूप अपने पीछे कोई खाली लाइन नहीं छोड़ता है। Ansible lstrip_blocks को no पर छोड़ देता है, इसलिए {% टैग के सामने आप जो भी स्पेस डालते हैं, वे बने रहते हैं और रेंडर की गई फ़ाइल में दिखाई देते हैं। यदि आपका आउटपुट अतिरिक्त इंडेंटेशन के साथ आता है, तो टेम्पलेट टास्क पर lstrip_blocks: true सेट करें।
{{ ansible_managed }} डिफ़ॉल्ट रूप से शाब्दिक टेक्स्ट Ansible managed के रूप में रेंडर होता है। इसे वैसे ही रहने दें। लोग अक्सर तारीख शामिल करने के लिए ansible.cfg में ansible_managed को फिर से परिभाषित करते हैं, और जैसे ही वे ऐसा करते हैं, रेंडर की गई फ़ाइल हर रन पर अलग हो जाती है, टास्क हर रन पर बदलाव की रिपोर्ट करता है, और सर्विस हर रन पर रीलोड होती है। वह एक सेटिंग उस गुण को नष्ट कर देती है जिसके बारे में यह पूरी गाइड है। .j2 एक्सटेंशन एक कन्वेंशन है, और Ansible इसकी जाँच नहीं करता है।
The playbook
इसे site.yml के रूप में save करें:
- name: Render an nginx site from a template
hosts: local
become: true
vars:
app_name: learn
app_listen_port: 8080
app_server_name: learn.example.com
app_backends:
- host: 127.0.0.1
port: 9001
weight: 3
- host: 127.0.0.1
port: 9002
weight: 1
tasks:
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
cache_valid_time: 3600
- name: Render the site configuration
ansible.builtin.template:
src: templates/app.conf.j2
dest: "/etc/nginx/conf.d/{{ app_name }}.conf"
owner: root
group: root
mode: '0644'
backup: true
notify: nginx config changed
- name: Make sure nginx is enabled and running
ansible.builtin.service:
name: nginx
state: started
enabled: true
handlers:
- name: Test the nginx configuration
ansible.builtin.command:
cmd: /usr/sbin/nginx -t
changed_when: false
listen: nginx config changed
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
listen: nginx config changedmode: '0644' को जानबूझकर quote किया गया है। file options documentation के अनुसार octal numbers को quote करना चाहिए "ताकि Ansible को एक string प्राप्त हो और वह string से number में अपना conversion कर सके"। बिना quote किए, YAML parser 0644 को एक साधारण number के रूप में पढ़ता है और आपको ऐसी permissions मिल सकती हैं जो आपने नहीं मांगी थीं।
notify: nginx config changed एक topic का नाम है, handler का नहीं। दोनों handlers में listen: nginx config changed शामिल है, इसलिए एक notify दोनों को trigger करता है। बाद में उसी listen line के साथ एक तीसरा handler जोड़ें और template task में किसी बदलाव की आवश्यकता नहीं होगी। cache_valid_time: 3600 एक घंटे के भीतर दूसरी बार चलने पर package mirrors पर दोबारा जाने से रोकता है।
इसे एक बार चलाएं, फिर देखें कि इसने क्या प्रिंट किया है
ansible-playbook site.ymlयदि आपका sudo पासवर्ड मांगता है, तो -K जोड़ें और Ansible आपसे इसके लिए प्रॉम्प्ट करेगा।
पहले प्रति-कार्य (per-task) लाइनों को पढ़ें, फिर नीचे दिए गए PLAY RECAP को देखें। प्रत्येक कार्य changed: प्रिंट करता है जब Ansible को कुछ करना पड़ता है, या ok: जब होस्ट पहले से ही वांछित स्थिति में होता है, और recap प्रत्येक होस्ट के लिए उन काउंटरों का योग दिखाता है। प्ले में प्रत्येक कार्य समाप्त होने के बाद, और उससे एक पल पहले नहीं, आपको RUNNING HANDLER [Test the nginx configuration] प्राप्त होता है जिसके बाद RUNNING HANDLER [Reload nginx] आता है।
अब आउटपुट पर भरोसा करने के बजाय स्वयं मशीन की जाँच करें:
sudo cat /etc/nginx/conf.d/learn.conf
sudo /usr/sbin/nginx -t
curl -sI http://127.0.0.1:8080/nginx -t प्रिंट करता है nginx: configuration file /etc/nginx/nginx.conf test is successful जब असेंबल किया गया कॉन्फ़िगरेशन पार्स हो जाता है। curl nginx से एक स्टेटस लाइन लौटाता है, और 502 Bad Gateway यहाँ सही उत्तर है, क्योंकि सर्वर ब्लॉक लाइव है और पोर्ट 9001 या 9002 पर कुछ भी लिसन नहीं कर रहा है। sudo tail /var/log/nginx/error.log कारण को स्पष्ट शब्दों में बताता है: connect() failed (111: Connection refused) while connecting to upstream।
Idempotence सिद्ध करने के लिए इसे दूसरी बार चलाएं
ansible-playbook site.ymlयह रन महत्वपूर्ण है, इसलिए इसके आउटपुट की तुलना पहले वाले से पंक्ति-दर-पंक्ति करें। template task को अब ok: प्रिंट करना चाहिए जहाँ इसने changed: प्रिंट किया था, और आउटपुट में कहीं भी कोई handler नहीं दिखना चाहिए।
यह तंत्र सरल है और इसे जानना उपयोगी है, क्योंकि इसी के आधार पर आप debugging करते हैं। template फाइल को controller पर render करता है और परिणाम के checksum की तुलना dest पर पहले से मौजूद फाइल के checksum से करता है। यदि content, ownership और mode मेल खाते हैं, तो करने के लिए कुछ नहीं होता है, इसलिए task ok रिपोर्ट करता है, इसलिए notify कभी trigger नहीं होता, और handler कभी नहीं चलता। Handlers केवल changed पर चलते हैं और किसी अन्य स्थिति में नहीं।
दूसरी दिशा को भी सिद्ध करें। vars में weight: 3 को weight: 1 में बदलें, play को फिर से चलाएं, और template task changed रिपोर्ट करेगा, दोनों handlers चलेंगे, और sudo cat /etc/nginx/conf.d/learn.conf नया मान दिखाएगा।
यदि दूसरा समान रन अभी भी बदलाव (change) रिपोर्ट करता है, तो render स्थिर नहीं है। सबसे पहले आउटपुट में समय-आधारित (time-based) किसी चीज़ को देखें, क्योंकि यह सामान्य कारण है और एक कस्टमाइज़्ड ansible_managed अक्सर इसका दोषी होता है। उसके बाद, जांचें कि task पर mode और owner डिस्क पर मौजूद मानों से मेल खाते हैं या नहीं, क्योंकि bytes समान होने पर भी mismatch एक बदलाव माना जाता है।
बदलाव करने से पहले उसे देखें
ansible-playbook site.yml --check --diff--check होस्ट में कोई बदलाव किए बिना प्ले को रन करता है। --diff यह प्रिंट करता है कि प्रत्येक टास्क क्या बदलाव करेगा, जो template के लिए रेंडर किए गए कंटेंट और डिस्क पर मौजूद फाइल के बीच लाइन-दर-लाइन अंतर (diff) दिखाता है। ये दोनों मिलकर "यह रन क्या करेगा" वाले सवाल का जवाब देते हैं, बिना वास्तव में कुछ किए। Check mode की अपनी सीमाएं हैं, जो मुख्य रूप से उन टास्क पर लागू होती हैं जिनका परिणाम किसी पिछले टास्क पर निर्भर करता है जिसे चेक मोड ने वास्तव में निष्पादित नहीं किया था।
Handlers अंत तक प्रतीक्षा क्यों करते हैं
Handlers का documentation इस बारे में स्पष्ट है: "डिफ़ॉल्ट रूप से, handlers एक विशेष play के सभी tasks पूरे होने के बाद चलते हैं। सूचित (notified) किए गए handlers निम्नलिखित sections के बाद, इसी क्रम में स्वचालित रूप से निष्पादित होते हैं: pre_tasks, roles/tasks और post_tasks।"
इसका कारण batching है। एक play जो किसी service के लिए चार config files तैयार करती है, उसे अंत में एक बार service को restart करना चाहिए, जब चारों files अपनी जगह पर हों। हर file के बाद restart करने से service चार बार restart होगी, और उनमें से तीन restarts अधूरी configuration को load करेंगे। वही पृष्ठ इस गारंटी को स्पष्ट रूप से बताता है: "एक ही handler को कई बार notify करने का परिणाम यह होता है कि handler केवल एक बार निष्पादित होता है, चाहे उसे कितने भी tasks ने notify किया हो।"
क्रम भी निश्चित है: "Handlers उसी क्रम में निष्पादित होते हैं जिस क्रम में वे handlers section में परिभाषित होते हैं, न कि उस क्रम में जो notify statement में सूचीबद्ध है।" यही कारण है कि playbook में Test the nginx configuration, Reload nginx के ऊपर स्थित है। test पहले चलता है क्योंकि इसे पहले लिखा गया है, और notify line में मौजूद कोई भी चीज़ इसे प्रभावित नहीं करती है।
Handlers को जल्दी कैसे चलाएं, और विफलता के बाद उन्हें कैसे चलाएं
कभी-कभी उसी play में बाद के किसी task को यह आवश्यकता होती है कि service पहले से ही नए configuration के साथ चल रही हो। उस बिंदु पर notified handlers को meta module के साथ flush करें, जिसे docs में "Ansible run any handler tasks which have thus far been notified" के रूप में वर्णित किया गया है।
- name: Run the notified handlers now instead of at the end of the play
ansible.builtin.meta: flush_handlers
- name: Wait for the new listener to accept connections
ansible.builtin.wait_for:
host: 127.0.0.1
port: 8080
timeout: 10यदि आप उस meta लाइन को हटा देते हैं, तो wait_for task तब चलता है जब nginx अभी भी पुराने configuration को ही serve कर रहा होता है। पहली बार चलाने पर port 8080 पर कोई listener मौजूद नहीं होता है, इसलिए task पूरे दस सेकंड तक प्रतीक्षा करता है और फिर fail हो जाता है।
दूसरा मामला विफलता का है। "यदि कोई task किसी handler को notify करता है लेकिन play में बाद में कोई अन्य task fail हो जाता है, तो डिफ़ॉल्ट रूप से handler उस host पर नहीं चलता है, जिससे host एक अनपेक्षित स्थिति में रह सकता है।" इसलिए, एक play जो config को render करती है और फिर किसी असंबंधित task पर अटक जाती है, वह disk पर नई फ़ाइल तो छोड़ देती है लेकिन चल रही service में पुराना configuration ही लोड रहता है। इसे command line पर --force-handlers के साथ, या play में force_handlers: true के साथ override करें। यही switch ansible.cfg में [defaults] के अंतर्गत force_handlers = True के रूप में, और environment variable ANSIBLE_FORCE_HANDLERS के रूप में भी मौजूद है। डिफ़ॉल्ट मान False है।
Handler के नाम आपस में टकराते हैं और हारने वाला चुप रहता है
दस्तावेज़ में यह नियम दिया गया है: "प्रत्येक handler का नाम विश्व स्तर पर अद्वितीय (globally unique) होना चाहिए। यदि एक ही नाम के कई handlers परिभाषित किए जाते हैं, तो केवल play में लोड किया गया अंतिम handler ही notify और execute किया जा सकता है।" किसी role के अंदर परिभाषित handlers उस role तक सीमित नहीं होते हैं। उन्हें पूरी play के लिए एक वैश्विक handler सूची में डाला जाता है, इसलिए दो roles जिनमें से प्रत्येक Restart nginx को परिभाषित करता है, आपको एक ऐसा नाम देते हैं जो उनमें से केवल एक पर ही लागू होता है, और यह लोड होने का क्रम तय करता है, न कि वह role जहाँ से आपने notify किया था।
इस पर भरोसा करने से पहले उस नियम का परीक्षण करें। इसे handlers-dup.yml के रूप में सहेजें:
- name: Two handlers, one name
hosts: local
gather_facts: false
tasks:
- name: Notify the duplicated name
ansible.builtin.command:
cmd: /bin/true
changed_when: true
notify: Duplicated handler
handlers:
- name: Duplicated handler
ansible.builtin.file:
path: /tmp/dup-first
state: touch
mode: '0644'
- name: Duplicated handler
ansible.builtin.file:
path: /tmp/dup-second
state: touch
mode: '0644'rm -f /tmp/dup-first /tmp/dup-second
ansible-playbook handlers-dup.yml
ls -l /tmp/dup-first /tmp/dup-secondPlay सफल होती है, RUNNING HANDLER [Duplicated handler] एक बार दिखाई देता है, और ls एक लाइन /tmp/dup-first के लिए और दूसरी ls: cannot access '/tmp/dup-second': No such file or directory के लिए प्रिंट करता है। जो handler चला वह वह है जिसे पहले लिखा गया था, न कि वह जो अंत में लोड हुआ, जो उस वाक्य की भविष्यवाणी के विपरीत है।
अंतर को समझना महत्वपूर्ण है, क्योंकि प्रलेखित नियम handler blocks के बारे में है, न कि फ़ाइल में मौजूद पंक्तियों के बारे में। अलग-अलग स्थानों से आने वाले handlers, एक role और फिर दूसरा, अलग-अलग blocks होते हैं, और बाद वाला block पहले वाले को shadow कर देता है। एक play में एक साधारण handlers: सूची एक एकल block होती है, और block के अंदर खोज ऊपर से नीचे तक चलती है और पहले मिलान वाले नाम पर रुक जाती है। इसलिए एक फ़ाइल के अंदर पहली परिभाषा उत्तर देती है और दूसरी तक पहुँचा नहीं जा सकता, जबकि roles के बीच shadowing उसी तरह काम करती है जैसा कि दस्तावेज़ में वर्णित है। किसी भी तरह से आप दोनों तक कभी नहीं पहुँच सकते, और कोई भी दिशा ऐसी नहीं है जिस पर निर्माण किया जा सके।
इससे बाहर निकलने के दो साफ तरीके हैं। प्रत्येक handler नाम को उसके role के लिए विशिष्ट prefix दें, या योग्य रूप role_name : handler_name को notify करें, जिसे दस्तावेज़ "यह सुनिश्चित करने के तरीके के रूप में देता है कि एक role से handler को notify किया गया है, न कि role के बाहर से उसी नाम वाले किसी handler को"। कोलन के आसपास के spaces उस syntax का हिस्सा हैं। जिस क्षण आप ऐसे roles को शामिल करना शुरू करते हैं जिन्हें आपने नहीं लिखा है, यह एक वास्तविक समस्या बन जाती है।
उसी पृष्ठ से एक और नियम: "Handler के नाम में variables रखने से बचें। चूंकि handler के नाम बहुत पहले ही templated हो जाते हैं, इसलिए Ansible के पास इस तरह के handler नाम के लिए मान उपलब्ध नहीं हो सकता है।" Restart {{ service_name }} नामक एक handler पूरी play को विफल कर देता है जब वह variable उस समय परिभाषित नहीं होता है जब नाम को template किया जा रहा होता है। Handler के नामों को fixed strings के रूप में रखना और उन्हें listen के साथ समूहित करना इस प्रश्न से बचाता है।
validate: खराब रेंडर को इंस्टॉल होने से रोकना
validate रेंडर की गई फाइल को Ansible द्वारा उसके अंतिम स्थान पर ले जाने से पहले उस पर एक कमांड चलाता है। दस्तावेज़ीकरण के अनुसार: "अपडेट की गई फाइल को अंतिम गंतव्य पर कॉपी करने से पहले चलाने के लिए वैलिडेशन कमांड। वैलिडेशन के लिए एक अस्थायी फाइल पाथ का उपयोग किया जाता है, जिसे %s के माध्यम से पास किया जाता है, जिसका नीचे दिए गए उदाहरणों के अनुसार मौजूद होना अनिवार्य है। साथ ही, कमांड को सुरक्षित रूप से पास किया जाता है, इसलिए expansion और pipes जैसी shell सुविधाएँ काम नहीं करेंगी।"
इस टेक्स्ट से दो नियम सीधे निकलकर आते हैं। %s अनिवार्य है, और इसके बिना कोई भी validate स्ट्रिंग कार्य को validate must contain %s के साथ विफल कर देती है। और यहाँ कोई shell नहीं है, इसलिए pipes, redirection, globbing और && काम नहीं करते हैं। केवल एक कमांड और एक फाइल आर्गुमेंट का उपयोग करें।
आधिकारिक मॉड्यूल उदाहरण वे दो मामले हैं जहाँ यह पूरी तरह से काम करता है:
- name: Copy a new sudoers file into place, after passing validation with visudo
ansible.builtin.template:
src: /mine/sudoers
dest: /etc/sudoers
validate: /usr/sbin/visudo -cf %s
- name: Update sshd configuration safely, avoid locking yourself out
ansible.builtin.template:
src: etc/ssh/sshd_config.j2
dest: /etc/ssh/sshd_config
owner: root
group: root
mode: '0600'
validate: /usr/sbin/sshd -t -f %s
backup: yesदोनों इसलिए काम करते हैं क्योंकि प्रत्येक चेकर एक फाइल लेता है और उसे अपनी शर्तों पर परखता है। visudo -cf एक sudoers फाइल को पढ़ता है। sshd -t -f एक पूर्ण sshd_config को पढ़ता है।
इस गाइड में validate, nginx फाइल की जाँच क्यों नहीं कर सकता
ऊपर दिए गए टेम्पलेट टास्क में validate: /usr/sbin/nginx -t -c %s जोड़ें, तो टास्क विफल हो जाता है। संदेश कारण स्पष्ट करता है:
nginx: [emerg] "upstream" directive is not allowed here in <ansible temporary path>:2nginx -t -c एक पूर्ण कॉन्फ़िगरेशन की अपेक्षा करता है जो शीर्ष स्तर पर events और http ब्लॉक से शुरू होता है। यह प्ले जिस फाइल को रेंडर करता है, वह एक अंश (fragment) है, जिसे /etc/nginx/nginx.conf के अंदर include /etc/nginx/conf.d/*.conf; द्वारा http ब्लॉक में खींचा जाता है। अपने आप में, उस संदर्भ से बाहर, upstream वास्तव में गलत जगह पर एक निर्देश है, इसलिए nginx उस फाइल को अस्वीकार कर देता है जो वास्तव में जहाँ रहती है, वहाँ पूरी तरह सही है। चेकर को एक अंश दिया गया था और उसे पूरे कॉन्फ़िगरेशन के रूप में मानने के लिए कहा गया था।
इसका व्यावहारिक समाधान वही है जो पहले से प्लेबुक में है। अंश को इंस्टॉल करें, फिर रीलोड हैंडलर के ऊपर परिभाषित हैंडलर में असेंबल किए गए कॉन्फ़िगरेशन की जाँच करें। चूंकि हैंडलर उसी क्रम में चलते हैं जिस क्रम में उन्हें परिभाषित किया गया है, nginx -t आपके अंश के साथ वास्तविक /etc/nginx/nginx.conf को देखता है, और वहाँ विफलता होने पर systemctl reload के कॉल होने से पहले ही प्ले विफल हो जाता है। इसकी कीमत के बारे में स्पष्ट रहें: जब वह जाँच विफल होती है, तो टूटी हुई फाइल डिस्क पर होती है, और nginx तब तक पिछली कॉन्फ़िगरेशन को सर्व करना जारी रखता है जब तक कि कोई उसे रीस्टार्ट न करे।
यही कारण है कि backup: true अपना स्थान बनाता है। यह ओवरराइट करने से पहले मूल फाइल की एक कॉपी को उसके बगल में लिखता है, जिसका नाम basename.PID.YYYY-MM-DD@HH:MM:SS~ होता है, इसलिए डायरेक्टरी में learn.conf.4127.2026-08-20@11:42:09~ जैसी प्रविष्टियाँ रह जाती हैं। बदलाव के बाद sudo ls -l /etc/nginx/conf.d/ चलाएँ और आपको एक मिल जाएगी।
वह नामकरण विवरण दिखने से कहीं अधिक मायने रखता है। बैकअप /etc/nginx/conf.d/ में हानिकारक नहीं है क्योंकि मुख्य कॉन्फ़िगरेशन में केवल conf.d/*.conf शामिल है और बैकअप नाम टिल्ड (tilde) पर समाप्त होता है। यह उस डायरेक्टरी में हानिकारक नहीं है जिसे एक साधारण * के साथ शामिल किया गया है, और Debian और Ubuntu पर /etc/nginx/nginx.conf में /etc/nginx/sites-enabled/* बिल्कुल उसी तरह शामिल है। backup: true के साथ sites-enabled में टेम्पलेट करें और nginx बैकअप को दूसरे लाइव सर्वर ब्लॉक के रूप में लोड कर लेता है, यही कारण है कि यह प्ले conf.d में लिखता है।
वास्तविक इन्वेंट्री होस्ट्स पर वही प्ले चलाना
hosts: local को उस ग्रुप नाम में बदलें जिसका आप उपयोग करते हैं, और प्ले में कुछ भी बदलने की आवश्यकता नहीं है। टेम्पलेट प्रति होस्ट एक बार रेंडर होता है, इसलिए app_listen_port और app_backends को group_vars और host_vars से लिया जा सकता है, जबकि टेम्पलेट फ़ाइल एक ही रहती है। फ़ाइल के बजाय वेरिएबल्स में मान रखने का यही लाभ है।
दो चीजें बदलती हैं। become: true को अब प्रत्येक टारगेट पर sudo पासवर्ड की आवश्यकता होगी, जब तक कि आपके पास वहां पासवर्ड-रहित sudo न हो, इसलिए -K जोड़ें। और उस टेम्पलेट में कोई भी सीक्रेट, जैसे डेटाबेस पासवर्ड या API टोकन, उस फ़ाइल में सादे vars: टेक्स्ट के रूप में नहीं होना चाहिए जिसे आप कमिट करते हैं। उन मानों को Ansible Vault के साथ एन्क्रिप्ट करें और उन्हें वैसे ही नाम से रेफर करें जैसे आप अभी करते हैं, क्योंकि टेम्पलेट को इससे कोई फर्क नहीं पड़ता कि वेरिएबल कहाँ से आया है।
जब प्ले एक सर्विस से अधिक बड़ा हो जाता है, तो vars:, templates/ और handlers: सभी के लिए एक मानक स्थान पहले से ही तैयार रहता है। उन्हें वहां ले जाना ही प्लेबुक और रोल के बीच विभाजन का मुख्य उद्देश्य है।
FAQ
मेरा Ansible handler क्यों नहीं चला?
ऐसा लगभग हमेशा इसलिए होता है क्योंकि उसे notify करने वाले task ने changed के बजाय ok रिपोर्ट किया होता है। Handlers केवल बदलाव (change) होने पर ही चलते हैं, अन्यथा नहीं। इसलिए, यदि किसी template task द्वारा रेंडर किया गया आउटपुट पहले से डिस्क पर मौजूद फाइल के समान है, तो वह किसी भी handler को notify नहीं करेगा। इसके बाद, चार चीजों की जाँच करें। notify में दी गई स्ट्रिंग का, handler के name या listen टॉपिक से बिल्कुल मेल खाना आवश्यक है, जिसमें केस और स्पेसिंग भी शामिल है। यदि उस host पर बाद का कोई task विफल हो जाता है, तो वह notified handlers को रोक देता है, जब तक कि आप --force-handlers का उपयोग न करें। किसी अन्य play में परिभाषित handler इस play से दिखाई नहीं देता है। और यदि कोई notifying task किसी when शर्त के कारण skip हो गया है, तो वह कभी भी notify नहीं करेगा।
मेरा playbook हर बार 'changed' क्यों रिपोर्ट करता है?
रेंडर किया गया टेक्स्ट हर रन के बीच स्थिर नहीं है। इसका सबसे सामान्य कारण आउटपुट में टाइमस्टैम्प का होना है, और एक कस्टमाइज्ड ansible_managed स्ट्रिंग जिसमें तारीख शामिल हो, ठीक यही करती है। अगली चीज जो जाँचनी है, वह है task पर mode और owner: यदि वे डिस्क पर पहले से मौजूद फाइल से मेल नहीं खाते हैं, तो Ansible उन्हें ठीक करता है और बदलाव रिपोर्ट करता है, भले ही सामग्री समान हो। यह देखने के लिए कि इन दोनों में से क्या समस्या है, ansible-playbook site.yml --check --diff चलाएं, क्योंकि --diff आपको वह अंतर दिखाता है जो task करना चाहता है।
Ansible में template और copy के बीच क्या अंतर है?
ansible.builtin.copy एक फाइल को बिना बदले भेजता है। ansible.builtin.template इसे पहले controller पर Jinja2 के माध्यम से रेंडर करता है और फिर परिणाम भेजता है, इसलिए target host तक फाइल पहुँचने से पहले ही variables और loops हल हो जाते हैं। ऐसी फाइल के लिए copy का उपयोग करें जो हर जगह बाइट-दर-बाइट समान हो। ऐसी किसी भी चीज के लिए template का उपयोग करें जो host के अनुसार बदलती है। वे समान फाइल विकल्पों को साझा करते हैं, इसलिए mode, owner, backup और validate दोनों में एक ही तरह से काम करते हैं।
मैं play के बीच में handler को कैसे चलाऊं?
जिस बिंदु पर आप उन्हें चलाना चाहते हैं, वहां एक task के रूप में ansible.builtin.meta: flush_handlers जोड़ें। यह अब तक notify किए गए प्रत्येक handler को ट्रिगर करता है, और फिर play सामान्य रूप से जारी रहता है। इसका उपयोग तब करें जब उसी play का कोई बाद का task इस बात पर निर्भर हो कि सर्विस पहले से ही नई कॉन्फ़िगरेशन चला रही हो, उदाहरण के लिए किसी ऐसे पोर्ट पर wait_for जो केवल रीलोड के बाद ही मौजूद होता है। play के अंत से पहले handler चलाने का यही समर्थित तरीका है।
क्या मैं nginx कॉन्फ़िगरेशन फ्रैगमेंट के साथ validate का उपयोग कर सकता हूँ?
nginx -t -c %s के साथ ऐसा नहीं किया जा सकता। वह कमांड एक पूर्ण कॉन्फ़िगरेशन की अपेक्षा करती है जो शीर्ष-स्तरीय events और http ब्लॉक्स से शुरू हो, इसलिए वह conf.d फ्रैगमेंट को "upstream" directive is not allowed here जैसे संदेश के साथ अस्वीकार कर देती है। फ्रैगमेंट http ब्लॉक के अंदर मान्य है, लेकिन अकेले मान्य नहीं है। फाइल को इंस्टॉल करें, फिर रीलोड handler के ऊपर परिभाषित एक handler में असेंबल की गई कॉन्फ़िगरेशन के विरुद्ध nginx -t चलाएं। Handlers उसी क्रम में चलते हैं जिसमें वे परिभाषित होते हैं, इसलिए एक खराब कॉन्फ़िगरेशन रीलोड का प्रयास करने से पहले ही play को विफल कर देती है। template task पर backup: true सेट करें ताकि पिछली फाइल वापस लाने के लिए उपलब्ध रहे।