SSD Nodes Learn
Mga Gabay Matt ConnorNi Matt Connor · Na-update 2026-07-24

Paano gamitin ang Ansible sa VPS (Tutorial)

Matutong mag-install ng Ansible gamit ang pipx sa Ubuntu 24.04. Gumawa ng inventory at playbook para i-harden ang iyong VPS at ayusin ang sudo errors.

Ang iyong bubuuin

Isang control machine na may nakainstall na Ansible, at isa o higit pang mga bagong Ubuntu 24.04 VPS na may stock image lamang. Pagkatapos, magkakaroon ka ng inventory file na naglalaman ng pangalan ng iyong mga server, isang ad-hoc ping na nagpapatunay na gumagana ang authentication, at isang playbook na nagpapatakbo ng buong checklist para sa bagong VPS bilang code: isang deploy user na may iyong SSH key, hardened sshd, fail2ban, unattended upgrades, at isang firewall na pinapayagan ang OpenSSH bago i-deny ang lahat ng iba pa. Gamitin ito sa isang server o dalawampung server. Patakbuhin ito nang dalawang beses at walang babaguhin ang pangalawang run — iyan ang pangunahing layunin.

Matapos ang labinlimang taon ng pag-provision ng mga VPS, ito ang totoong pattern: manu-manong inaayos ng lahat ang unang limang server, pagkatapos ay nasasayang ang isang weekend sa ikaanim dahil walang nakakaalala sa ginawa sa unang limang server. Pinapalalim ng gabay na ito ang pagsusuri sa pag-manage ng maraming Linux servers — basahin ito sa araw na mapansin mong nagta-type ka na ng parehong apt install sa tatlong terminal.

Ano ang tunay na kahulugan ng Ansible sa isang talata

Ang Ansible ay agentless. Walang kailangang i-install na daemon sa mga server na pinamamahalaan nito: kumokonekta ang control machine gamit ang ordinaryong SSH, kinokopya ang isang maliit na Python module sa target, ini-execute ito, binabasa ang JSON na output nito, at pagkatapos ay binubura ito. Ang tanging kailangan ng target ay python3, na kasama na sa bawat stock Ubuntu image. Ang mahalagang konsepto ay idempotent, na nangangahulugang simple: ang isang task ay naglalarawan ng isang state, hindi isang action. Ang state: present para sa isang package ay nangangahulugang "siguraduhing installed ito", hindi "i-run ang installer". Kung ang state ay naka-set na, walang babaguhin ang Ansible at ire-report ito bilang ok sa halip na changed. Ang katangiang ito ang mismong core ng produkto — ito ang dahilan kung bakit ligtas ang muling pag-run ng playbook, at ang ligtas na muling pag-run ang nagpapabago sa isang shell script para maging infrastructure.

Mga Prerequisites, at ang mga dapat bantayan

  • Isang control machine: ang iyong laptop o isang maliit na VPS. Ipinapalagay ko na Ubuntu 24.04 ang gamit mo; pareho lang ang proseso sa macOS kapag naka-install na ang pipx mula sa Homebrew.
  • Isa o higit pang target VPSes na tumatakbo sa Ubuntu 24.04 sa KVM, at accessible bilang root. Walang anumang i-install sa mga ito.
  • SSH key authentication sa bawat target. Ang Ansible ay susunod sa authentication ng iyong ssh command — kung hihingi ng password ang ssh root@host, mag-eerror ang Ansible.
  • Sa Ubuntu 24.04, hindi gagana ang pip install ansible dahil sa error: externally-managed-environment. Sadyang polisiya ito ng distro at hindi ito bug. Gamitin ang pipx.
  • Ang whitespace sa YAML ay bahagi ng syntax. Ang maling indent ay magdudulot ng mapping values are not allowed in this context, at ang paggamit ng tab character ay magiging sanhi ng error.
  • Panatilihing bukas ang isang working SSH session sa bawat target habang pini-pirmis ng playbook ang sshd. Ang bawat lockout na natulungan ko ang customer ay nangyari dahil isinara nila ang huling session "para i-test mula sa clean state".

Step 1: i-install ang Ansible sa control machine gamit ang pipx, hindi ang pip

Ang karaniwang instinct ay ang paggamit ng pip3 install ansible. Sa isang bagong 24.04 image na nagkakaroon ng error sa unang hakbang — Command 'pip3' not found, but can be installed with: sudo apt install python3-pip — ang pag-install ng pip ay magdudulot lamang ng mas malaking problema:

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.

Itinuturing ng Ubuntu 24.04 ang system Python bilang externally managed (PEP 668), kaya hindi maaaring maglaban ang pip at apt sa parehong mga file. Huwag gamitin ang --break-system-packages; tapat ang pangalan ng flag na ito. Ang tamang solusyon ay pipx, dahil nagbibigay ito sa Ansible ng sariling isolated virtualenv at inilalagay ang mga binaries sa iyong PATH:

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

Magbukas ng bagong shell pagkatapos ng pipx ensurepath para mag-apply ang pagbabago sa PATH. Hindi lamang dekorasyon ang --include-deps: ang ansible package ay walang sariling console scripts — ang ansible, ansible-playbook, at iba pa ay mga entry point ng ansible-core dependency nito — kaya kung walang flag, tatanggihan ng pipx ang install gamit ang No apps associated with package ansible or its dependencies. I-install ang ansible package, hindi ang bare ansible-core — ang full package ay kasama na ang community collections, at ang playbook na ito ay gumagamit ng modules mula sa dalawa sa mga ito (ansible.posix at community.general).

ansible --version

Ang tamang resulta ay dapat magsimula sa linyang gaya ng ansible [core 2.19.x] at ipakita ang Python na ginagamit nito; kahit anong current core release ay ayos na para sa lahat ng ito. Ang ansible: command not found ay nangangahulugang hindi pa nasa PATH ang ~/.local/bin — magbukas ng bagong shell, o gamitin ang source ~/.bashrc.

Iyon ang kabuuan ng installation. Walang mababago sa mga target.

Step 2: SSH key access sa bawat target

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

Pagkatapos, i-verify ito para sa bawat host:

ssh root@10.0.0.10 true && echo ok

Ang isang linyang iyon ay may dalawang tungkulin: kinukumpirma nito na gumagana ang key auth nang walang password, at nire-record nito ang host key sa known_hosts. Gawin ito agad, dahil ang Ansible ay nagpapakita ng unrecorded host key bilang isang interactive prompt sa gitna ng execution, na mukhang nag-hang ang proseso.

Step 3: ang inventory — INI muna, YAML kapag lumaki na

Ang inventory ay isang text file na naglilista ng mga machine na maaaring i-manage ng Ansible. Gumawa ng inventory.ini sa isang bagong project directory:

[vps]
web1 ansible_host=10.0.0.10
web2 ansible_host=10.0.0.20

[vps:vars]
ansible_user=root

Ang web1 ay isang alias na ikaw ang pipili — ito ang lalabas sa output at ang gagamitin mo sa --limit web1. Ang ansible_host ay ang totoong address. Ang [vps] ay isang group, at ang [vps:vars] ay nagtatakda ng mga variable para sa lahat ng host sa loob nito; ang ansible_user ay ang user na gagamitin ng Ansible para mag-log in. Sa tabi nito, maglagay ng ansible.cfg para hindi mo na kailangang i-type ang -i:

[defaults]
inventory = inventory.ini

Binabasa ng Ansible ang ansible.cfg mula sa current directory. Ang parehong inventory sa format na YAML — i-save ito bilang inventory.yml at ituro ang ansible.cfg sa pangalang iyon — ang mas mainam gamitin kapag ang bawat host ay may marami nang variables:

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

Magkapareho lang ang mga ito. Mas madaling basahin ang INI kung dalawang server lang ang gamit; mas mainam ang YAML kapag umabot na sa dalawampu ang mga host. Pumili ng isa at huwag na itong pag-isipan pa.

Step 4: ad-hoc commands — ang green pong na nagpapatunay sa lahat

ansible all -m ping

Hindi ito ICMP. Ang ping module ay isang full dress rehearsal: SSH login, module copy, Python execution sa target, at cleanup. Ang tamang resulta ay green, isang block bawat host:

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

Ang green SUCCESS ay nangangahulugang gumagana ang authentication, ang Python interpreter, at ang transport — gagana rin ang playbook. Ang red UNREACHABLE! ay nangangahulugang nabigo ang transport bago pa man tumakbo ang anumang module; ang eksaktong string at ang solusyon ay nasa failure modes section sa ibaba. Dalawa pang ad-hoc commands na dapat malaman:

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

Ang ad-hoc ay para sa mga one-off at checks. Anumang command na tatakbo nang higit sa isang beses ay dapat ilagay sa isang playbook.

Step 5: ang unang playbook — ang new-VPS checklist bilang code

Ito ang lahat ng dapat mong gawin nang manual sa unang sampung minuto sa isang bagong server. I-save ito bilang site.yml:

---
- 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

Ang mga linyang dapat intindihin sa halip na i-copy lang:

Ang mga Variables ay nasa ilalim ng vars: at ginagamit ang "{{ deploy_user }}" — i-quote ang buong expression kung ang value ay nagsisimula sa brace, dahil maaaring magkamali ang YAML parser. Binabasa ng lookup('file', ...) ang iyong public key mula sa control machine sa runtime, kaya walang key material na kasama sa playbook.

Ang loop. Pinapatakbo ng loop: "{{ baseline_services }}" ang service task nang isang beses para sa bawat item, at ang output ay ipinapakita ang bawat item sa sarili nitong linya. Tandaan na ang apt task ay kinukuha ang buong package list nang sabay-sabay — mas mabilis ang isang apt transaction at ito ang preferred pattern para sa mga package; ang mga loop ay para sa mga module na talagang kumikilos sa isang bagay sa bawat pagkakataon.

Ang handler ang konsepto na dapat isapuso. Ang notify: Restart ssh ay hindi nangangahulugang "i-restart ang ssh ngayon". Ipinapila nito ang handler, na tatakbo nang isang beses sa dulo ng play, at lamang kung ang notifying task ay nag-report ng changed. I-run muli ang playbook bukas: tama na ang drop-in file, ang copy task ay mag-uulat ng ok, at hindi muling magba-bounce ang sshd. Ang validate: na linya ang nagsisilbing safety sa trigger — sinusuri ng sshd ang file bago nito palitan ang luma, kaya ang typo ay magreresulta sa pag-fail ng task sa halip na masira ang daemon.

PermitRootLogin prohibit-password, hindi no — sadyang ganito. Ang playbook na ito ay naglo-log in bilang root gamit ang isang key. Pinapatay ng prohibit-password ang password root logins habang pinapanatili ang iyong access. Kapag napatunayan na ang deploy user (ssh deploy@10.0.0.10 sudo true — ang plain address, dahil ang web1 ay isang alias na Ansible lang ang nakakaalam), palitan ang ansible_user=deploy sa inventory at higpitan ito sa no sa susunod na run. Mag-harden sa pagkakasunod-sunod na hindi ka mawawalan ng access.

Mahalaga ang 00- prefix. Para sa karamihan ng keywords, sinusunod ng sshd ang unang occurrence na mababasa nito, at ang sshd_config ng Ubuntu ay may kasamang sshd_config.d/*.conf sa lexical order bago ang sarili nitong body. Ang Ubuntu 24.04 cloud images ay mayroon nang 60-cloudimg-settings.conf sa directory na iyon, at ang mga provider na nag-e-enable ng password logins sa pamamagitan ng cloud-init ay nagdadagdag ng 50-cloud-init.conf na may PasswordAuthentication yes; ang pagpapangalan sa atin bilang 00-hardening.conf ay nagpapasa sa atin sa sorting para mauna sa dalawa.

Ang task order ang nagsisilbing firewall safety. Pinapatakbo ang Allow OpenSSH bago ang Enable ufw na may deny policy — sinusunod ng Ansible ang pagkakasunod-sunod ng mga nakalistang task, kaya ang butas ay naroon na bago pa itayo ang pader. Hindi na kailangan ng fail2ban ng configuration para maging kapaki-pakinabang dito; ang Ubuntu defaults nito ay binabantayan ang sshd agad, at ang ginagawa ng mga jails — at kung ano ang dapat i-tune — ay covered sa fail2ban on Ubuntu 24.04 guide.

Step 6: dry run gamit ang --check, pagkatapos ay i-run na ito nang tuluyan

ansible-playbook site.yml --check

Ang check mode ay kumokonekta, kinakalkula ang mga gagawin nito, at walang binabago. Basahin ang changed= count sa PLAY RECAP sa ibaba — ito ang bilang ng mga task na magbabago sa bawat host. Isang paalala: may structural limit ang check mode kapag ang isang susunod na task ay nakadepende sa mga pagbabagong ginawa ng naunang task. Ang standard server image ng Ubuntu ay may kasamang ufw, kaya malinis ang dry-run ng playbook na ito — ngunit sa isang minimal image na walang ufw, mag-eerror ang mga ufw task sa check mode. Nangyayari ito dahil hindi talaga ininstall ng check mode ang package kaya walang ma-call ang module. Limitasyon ito ng dry runs at hindi bug sa iyong playbook. Kapag tama na ang plano:

ansible-playbook site.yml

Ang bawat task ay magpi-print ng isang linya bawat host — dilaw na changed, berde na ok — at ang recap ay dapat ganito ang itsura:

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

Ang sampung ok ay ang fact-gathering plus walong tasks plus ang handler. Maaaring magkaiba ang changed mo sa akin ng isa o dalawa: ang standard image ng Ubuntu ay may kasama nang ufw at unattended-upgrades, at ang fail2ban ay awtomatikong nag-i-start pagka-install ng apt, kaya maaaring mag-report ang isang task ng ok sa unang run nito — dahil ang state na idineklara nito ay dati nang naroon. Ang mga numerong dapat ay zero ay unreachable at failed. Isang nota tungkol sa become: true: pormalidad lang ito habang naka-connect ka bilang root, pero sa sandaling i-set ang ansible_user sa deploy, aktibo na ang sudo — at ang NOPASSWD sudoers file na ini-install ng playbook na ito ang dahilan kung bakit hindi lalabas ang -K sa iyong command line. Kung wala ito, makakakuha ka ng Missing sudo password, na tatalakayin sa ibaba.

Step 7: patakbuhin ito nang dalawang beses — ganito ang itsura ng idempotence

Patakbuhin muli ang parehong command agad-agad:

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

Bumaba ng isa ang changed=0 at ok dahil hindi tumakbo ang un-notified handler. Walang na-reinstall, hindi ni-restart ang sshd, at hindi binago ang ufw. Ito ang dahilan kung bakit ang playbook ay nagsisilbing audit pati na rin provisioner: magdagdag ng web3 sa inventory sa susunod na buwan at i-rerun — mabubuo ang bagong box, at mabe-verify ang mga lumang box. Ang nonzero na changed sa isang box na hindi mo naman binago ay drift, at senyales ito na may nag-edit nang manual sa dapat ay sa playbook ginawa.

Mula rito, lalong lalawak ang pattern. Ang susunod na playbook na dapat isulat ay maglalagay ng WireGuard VPN sa parehong VPS at maghihigpit sa ufw rule para sa SSH sa tunnel lamang; pagkatapos niyon, isang playbook para sa pag-install ng Docker at Compose sa bawat app server. Kapag lumampas na sa tatlong screen ang site.yml, i-split na ito sa mga roles — pero huwag muna bago iyon.

Failure modes, kasama ang mga string na makikita mo

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
}

Nabigo ang SSH transport bago pa tumakbo ang anumang module: mali ang ansible_user, hindi na-copy ang key sa host na iyon, o maling key ang ginagamit. I-reproduce gamit ang plain ssh root@10.0.0.10, pagkatapos ay ssh -v para makita ang mga inoffered na keys. Kung gumagana ang password SSH pero hindi ang Ansible, nalaktawan mo ang ssh-copy-id.

Missing sudo password.

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

Nag-set ka ng become: true, nag-connect bilang non-root user, at kailangan ng user na iyon ng password para sa sudo. Magdagdag ng -K (--ask-become-pass) sa command line, o bigyan ang user ng NOPASSWD sudoers entry — ito ang dahilan kung bakit nag-iinstall ang playbook ng entry para sa deploy bago ka pa mag-switch dito.

error: externally-managed-environment. Nag-run ka ng pip laban sa system Python sa Ubuntu 24.04. Tinalakay na ito sa step 1: gamitin ang pipx, hindi pip, at hindi --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

Halos laging indentation ang sanhi: maling depth ng key, o kulang na space pagkatapos ng colon. Ang reported line number ay malapit lang sa pagkakamali, hindi mismong sa pagkakamali — i-check din ang linya sa itaas. Ang katumbas nito na found character '\t' that cannot start any token ay nangangahulugang may nakalusot na tab; bawal ang tab sa YAML. Gawing reflex ang ansible-playbook site.yml --syntax-check bago ang bawat run, at i-set ang editor sa two-space indentation para sa YAML.

/usr/bin/python3: not found. Bihira sa standard Ubuntu 24.04 images, pero common sa minimal o netboot images: nabigo ang module execution dahil walang Python ang target. I-bootstrap ito gamit ang raw module, ang tanging module na walang kailangang dependency sa far side: ansible all -m raw -a "apt-get update && apt-get install -y python3" --become, pagkatapos ay i-rerun ang playbook.

FAQ

Kailangan ko bang mag-install ng Ansible sa mga server na mamanage nito?

Hindi. Agentless ang Ansible: nagpapadala ang control machine ng maliliit na Python modules via SSH, pinapatakbo ang mga ito, at pagkatapos ay tinatanggal ang mga ito. Ang kailangan lang sa target machine ay python3 at SSH access, na parehong available na sa mga stock Ubuntu images. Ang tanging installation sa buong guide na ito ay gagawin sa iyong control machine.

Bakit sinasabi ng Ansible na "Permission denied (publickey)"?

Ang UNREACHABLE! block na may Permission denied (publickey) ay nangangahulugang nabigo ang SSH authentication bago pa man magpatakbo ng kahit ano ang Ansible. Siguraduhin na ang ansible_user sa inventory ay tugma sa account na iyong ginawa, na nag-run ka ng ssh-copy-id sa host na iyon, at ang plain ssh user@host ay nakakapag-login nang walang password. Anumang solusyon sa plain ssh command ay solusyon din sa Ansible, dahil pareho sila ng transport mechanism.

Ano ang ibig sabihin ng idempotent sa Ansible?

Nagdedeklara ang isang task ng desired state — halimbawa, "naka-install ang package na ito" o "nandoon ang linyang ito sa file na ito" — sa halip na isang action na dapat gawin. Kung ang state ay nandoon na, walang gagawin ang Ansible at mag-uulat ng ok sa halip na changed. Ito ang dahilan kung bakit changed=0 ang makikita sa pangalawang pag-run ng playbook, at kung bakit ang rerun ay isang ligtas na audit sa halip na isang risky na re-install.

Pip o pipx ba ang dapat gamitin para i-install ang Ansible sa Ubuntu 24.04?

pipx. Itinuturing ng Ubuntu 24.04 ang system Python bilang externally managed, kaya ang pip install ansible ay mabibigo sa pamamagitan ng error: externally-managed-environment by design. Inilalagay ng pipx install --include-deps ansible ang Ansible sa isang isolated virtualenv at malinis na inilalabas ang ansible, ansible-playbook, at iba pa sa iyong PATH.

Ano ang pagkakaiba ng ansible at ansible-core packages?

Ang ansible-core ay ang engine kasama ang mga ansible.builtin modules lamang. Ang ansible package ay pinagsasama ang core kasama ang mga curated community collections — kabilang ang ansible.posix (ang authorized_key module) at community.general (ang ufw module), na parehong ginamit sa guide na ito. Simulan ang paggamit sa full package; mag-slim down sa core plus hand-picked collections lamang kung mayroon kang sapat na dahilan.