K2 · Praktikum: kolmekihiline rakendus¶
Tänase lõpuks töötab kolmes masinas päris rakendus. vm1-s on nginx koormusjaoturina ja PostgreSQL andmebaasina, vm2 ja vm3 jooksutavad rakendust „Labori seis“. curl http://vm1/ vastab vaheldumisi vm2-st ja vm3-st ning leht töötab edasi ka siis, kui üks rakendus on maas. Kõik see on kirjas neljas rollis ja ühes site.yml-is ning teine jooks ei muuda midagi.
Osas A ehitad pinu juhendi järgi kiht korraga: inventar, muutujad, rollid, andmebaas, rakendus, koormusjaotur. Osa A lõpus annab leht 502 ja sa leiad põhjuse. Osas B on juhiseid vähem: leiad, miks koormus ei vaheldu, lased malli vea kinni püüda ja võtad ühe rakenduse maha. Oodatav tulemus ja vihjed on kinnistes plokkides. Tee enne ise ja siis võrdle.
Õpiväljundid
Praktikumi lõpuks oskad:
- kirjeldada mitmekihilist pinu inventari gruppide ja alamgruppidega;
- panna muutujad
group_vars-i jahost_vars-i ning ennustada, milline väärtus masinale jõuab; - jagada playbooki rollideks ja kokku panna
site.yml-i; - kasutada
import_tasks-i, handlereid,loop-i,when-i jaregister-it; - kirjutada Jinja2 malli, mis loeb teiste masinate andmeid
hostvars-ist, ja kaitsta sedavalidate-iga; - leida 502 põhjus logidest (SELinux, tulemüür) ja parandada see koodis;
- tõendada, et koormus jaguneb ja üks rakendus võib maas olla.
Enne alustamist¶
Selleks peaksid olema K1 praktikumi osa B teinud: ssh vm1, ssh vm2 ja ssh vm3 töötavad vm1-s võtmega ja ansible.posix kollektsioon on paigaldatud. Kui midagi on puudu, vaata Töökeskkond ja K1 praktikumi osa B.
Ava juhendaja jagatud Classroom 50 link ja klooni uus repo vm1-sse SSH-ga (Code → SSH):
Oodatav tulemus
.:
README.md ULESANNE.md ansible.cfg files host_vars logid vead
./files:
app.py
./host_vars:
vm3.yml
...
ansible.cfg on sama, mis K1-s. files/app.py on rakendus, mille paigaldad. host_vars/vm3.yml on kolleegi jäetud fail, sellest tuleb juttu A2-s. Kaust vead/ on kodutöö jaoks.
Kontrollnimekiri on su repos Issues all: issue Lab 02 · Kolmekihiline rakendus. Märgi ruut, kui osa on tehtud. Kinni? Küsi Discordis või ava issue mallist Vajan abi.
Mis repos lõpuks on
<sinu-repo>/
├── ansible.cfg
├── inventory.ini
├── site.yml
├── group_vars/
│ └── all.yml
├── host_vars/
│ └── vm3.yml
├── roles/
│ ├── common/ tasks/ defaults/
│ ├── db/ tasks/ handlers/ templates/ defaults/
│ ├── app/ tasks/ handlers/ templates/ files/ defaults/
│ └── lb/ tasks/ handlers/ templates/ defaults/
├── README.md
└── logid/
├── site_teine_jooks.txt
├── vaheldumine.txt
└── uks_maas.txt
Soovitatav töökäik iga sammu juures
- Enne:
ansible-playbook site.yml --syntax-checkja--check --diff(või--list-tasks). - Jooks:
ansible-playbook site.yml, esimesel korral vajadusel--limit. - Pärast: üks ad-hoc käsk, mis kontrollib tulemust masinas endas. Mitte „playbook oli roheline“, vaid „teenus vastab“.
A · Juhendatud osa¶
A1 · Kirjelda pinu inventaris¶
Selle sammu lõpuks on inventaris kolm rollipõhist gruppi ja üks grupp, mis sisaldab kõiki.
K1-s oli üks grupp veeb. Nüüd on masinatel erinevad ülesanded, seega grupeerid need ülesande järgi. vm1 on korraga kahes grupis.
Loo inventory.ini:
:childrentähendab, et grupi liikmed on teised grupid.stacksisaldab kõiki masinaid, mis onlb-,db- võiapp-grupis.
Kontrolli enne kasutamist:
Oodatav tulemus
@all:
|--@ungrouped:
|--@stack:
| |--@lb:
| | |--vm1
| |--@db:
| | |--vm1
| |--@app:
| | |--vm2
| | |--vm3
ping annab kolm pong-i, mitte neli. vm1 on kahes grupis, aga masinana üks. --list-hosts näitab vm2 ja vm3.
Grupid on aadressid. Play ütleb hosts: app ja Ansible teab, et see tähendab vm2 ja vm3. Kui homme tuleb neljas rakendusserver, lisad ühe rea [app] alla ja kõik, mis sihib app-i, hõlmab ka seda.
Mõtle (vabatahtlik)
Kumb on parem nimi grupile: vm2_vm3 või app? Mis juhtub esimese nimega, kui rakendus kolib teistesse masinatesse?
Loe juurde
A2 · Pane muutujad group_vars-i¶
Selle sammu lõpuks on rakenduse ja andmebaasi seaded ühes failis ning sa oskad igalt masinalt küsida, milline väärtus talle jõuab.
Loo group_vars/all.yml:
app_nimi: "Labori seis"
app_port: 8080
db_nimi: labor
db_kasutaja: labor
db_parool: "Muuda-mind-2026" # (1)!
db_port: 5432
- Parool avatud tekstina Gitis on halb. Täna jätame selle nii, et näeksid, kus muutuja liigub. Lisaülesandes peidad selle Vaulti.
Ansible loeb kausta group_vars/ ise: fail all.yml kehtib kõigile masinatele, app.yml oleks ainult app-grupile. Sama kehtib host_vars/ kohta masina nime järgi.
Enne kui edasi lähed, ennusta. Täida tabel vihikus:
| Masin | app_port ennustus |
Tegelik |
|---|---|---|
| vm1 | ||
| vm2 | ||
| vm3 |
Kontrolli:
Oodatav tulemus
host_vars/vm3.yml-is on kolleegi kommentaar: vm3 pordil 8080 on testteenus, seepärast kuulab rakendus seal 8081-l.
host_vars võidab group_vars-i, sest kitsam kehtib laiema üle. See on täiesti õige seadistus, jäta fail alles. B1-s näed, kus see mõne malli juures kätte maksab.
Proovi ka kõige tugevamat taset:
-e võidab kõik. Seda kasutad ühekordseks katseks, mitte püsiseadistuseks.
Eelistusjärjekord lühidalt (nõrgemast tugevamani)
| Kus | Näide tänases repos |
|---|---|
rolli defaults/main.yml |
lb_port: 80 |
group_vars/all.yml |
app_port: 8080 |
group_vars/<grupp>.yml |
|
host_vars/<masin>.yml |
app_port: 8081 vm3-le |
play vars: |
|
rolli vars/main.yml |
|
-e käsurealt |
-e app_port=9999 |
Täielik nimekiri on 22 taset. Igapäevaselt piisab neist seitsmest.
Loe juurde
A3 · Ehita rollide skelett ja site.yml¶
Selle sammu lõpuks on repos neli rolli ja site.yml, mis rakendab esimese rolli kõigile masinatele.
Loo kaustad:
mkdir -p roles/common/{tasks,defaults}
mkdir -p roles/db/{tasks,handlers,templates,defaults}
mkdir -p roles/app/{tasks,handlers,templates,files,defaults}
mkdir -p roles/lb/{tasks,handlers,templates,defaults}
mv files/app.py roles/app/files/
tree roles
Iga kaust on kokkulepe: Ansible otsib task'e failist tasks/main.yml, handlereid failist handlers/main.yml, malle kaustast templates/, kopeeritavaid faile kaustast files/ ja vaikeväärtusi failist defaults/main.yml. Rollis ei pea kirjutama teekonda, src: app.py leitakse ise.
Rolli common vaikeväärtused, roles/common/defaults/main.yml:
Task'id, roles/common/tasks/main.yml:
- name: Põhipaketid on paigaldatud
ansible.builtin.package:
name: "{{ common_paketid }}"
state: present
- name: firewalld käib
ansible.builtin.service:
name: firewalld
state: started
enabled: true
Rollifailis pole hosts:-i ega become:-i. Roll ütleb, mida teha. Kuhu, ütleb site.yml. Loo site.yml:
Kontrolli ja jooksuta:
ansible-playbook site.yml --syntax-check
ansible-playbook site.yml --list-tasks
ansible-playbook site.yml
Oodatav tulemus
playbook: site.yml
play #1 (stack): Ühine baas kõigile masinatele TAGS: []
tasks:
common : Põhipaketid on paigaldatud TAGS: []
common : firewalld käib TAGS: []
Jooksul on task'i nime ees rolli nimi: TASK [common : Põhipaketid on paigaldatud]. Kolmel masinal failed=0. changed sõltub sellest, mis K1-st juba olemas oli.
Pärast: ansible stack -m command -a "systemctl is-active firewalld" vastab kolm korda active.
Mõtle (vabatahtlik)
Miks on common_paketid failis defaults/, mitte group_vars/all.yml-is? Kes võiks tahta seda nimekirja muuta ja kuidas ta seda teeks rolli puutumata?
Loe juurde
A4 · Paigalda andmebaas rolliga¶
Selle sammu lõpuks käib vm1-s PostgreSQL, selles on andmebaas labor ja ainult vm2 ning vm3 pääsevad sellele võrgust ligi.
Andmebaasi roll on pikem kui common, seega ehitad selle kolme task-faili kaupa: paigaldus, seadistus, kasutaja. Pärast iga faili jooksutad ja kontrollid. Nii tead alati, mis osa vea tegi.
Samm 1. Rolli sisukord. roles/db/tasks/main.yml ei tee ise midagi, ta ainult loetleb task-failid järjekorras. Alusta ühega:
import_tasks loeb faili sisse juba enne jooksu, seega --list-tasks näitab ka selle task'e. Lisa ka rolli vaikeväärtus roles/db/defaults/main.yml:
Samm 2. Paigaldus. Loo roles/db/tasks/paigaldus.yml. Esimene task on tuttav K1-st:
- name: PostgreSQL server on paigaldatud
ansible.builtin.package:
name:
- postgresql-server
- postgresql
state: present
PostgreSQL-i andmekaust tuleb üks kord luua käsuga postgresql-setup --initdb. Moodulit selleks pole, seega kasutad command-i ja teed selle idempotentseks sama võttega mis K1 A5-s. Kui fail PG_VERSION on olemas, on kaust juba loodud:
- name: Andmekaust on initsialiseeritud
ansible.builtin.command: postgresql-setup --initdb
args:
creates: "{{ db_andmekaust }}/PG_VERSION"
Kolmas task paneb teenuse käima. Kirjuta see ise: moodul ansible.builtin.service, teenus postgresql, käib ja käivitub buutimisel.
Näide, kui ikka kinni
Lisa site.yml-i teine play ja jooksuta:
ansible-playbook site.yml --syntax-check
ansible-playbook site.yml
ansible db -m wait_for -a "port=5432 timeout=5"
Oodatav tulemus
Kolm uut task'i db : ... nimega, kõik changed. wait_for vastab SUCCESS: PostgreSQL kuulab, aga praegu ainult localhost-is.
Samm 3. Seadistus. Lisa tasks/main.yml-i teine rida:
PostgreSQL peab kuulama võrgus, mitte ainult localhost-is, ja paroolid peavad olema tänapäevase räsiga. Need on kaks rida failis postgresql.conf. Ühe task'iga saab muuta mõlemat, kui kasutad loop-i. Loo roles/db/tasks/seadistus.yml:
- name: postgresql.conf seaded on paigas
ansible.builtin.lineinfile:
path: "{{ db_andmekaust }}/postgresql.conf"
regexp: "^#?{{ item.nimi }}\\s*="
line: "{{ item.nimi }} = '{{ item.vaartus }}'"
loop:
- { nimi: listen_addresses, vaartus: "*" }
- { nimi: password_encryption, vaartus: scram-sha-256 }
notify: PostgreSQL taaskäivitub
Task jookseb iga loop-i elemendi kohta ja element on muutujas item. regexp leiab rea ka siis, kui see on välja kommenteeritud (#listen_addresses = 'localhost'). Ilma selleta lisaks lineinfile igal jooksul uue rea.
notify ütleb: kui see task muutis midagi, taaskäivita PostgreSQL play lõpus. Teenus loeb listen_addresses-i ainult käivitudes. Handler läheb rolli faili roles/db/handlers/main.yml:
notify tekst ja handleri name peavad olema täpselt samad, suurtähed loevad.
Nüüd kõige huvitavam rida tänases praktikumis. PostgreSQL lubab võrgust ühenduda ainult aadressidelt, mis on kirjas failis pg_hba.conf. Sinna on vaja vm2 ja vm3 IP-d. Neid ei kirjuta sa käsitsi, mall arvutab need. Loo roles/db/templates/pg_hba.conf.j2. Kõigepealt read, mis on igas pg_hba.conf-is:
# {{ ansible_managed }}
# TYPE DATABASE USER ADDRESS METHOD
local all all peer
host all all 127.0.0.1/32 scram-sha-256
host all all ::1/128 scram-sha-256
Siis tsükkel üle app-grupi. groups['app'] on nimekiri ['vm2', 'vm3'], hostvars[h] on kõik, mida Ansible masina h kohta teab, ka tema IP:
{% for h in groups['app'] %}
host {{ db_nimi }} {{ db_kasutaja }} {{ hostvars[h].ansible_default_ipv4.address }}/32 scram-sha-256
{% endfor %}
Mall jookseb vm1 jaoks, aga kirjutab faili vm2 ja vm3 IP-d. Faktid on olemas, sest esimene play (hosts: stack) kogus need kõigist kolmest.
Malli paigaldab task. Kirjuta see ise seadistus.yml-i lõppu: moodul ansible.builtin.template, src: pg_hba.conf.j2, dest andmekaustas, omanik ja grupp postgres, õigused 0600, teavitab handlerit PostgreSQL laeb seaded uuesti. Lisa sama nimega handler, mis teeb state: reloaded: pg_hba.conf-i muutusele piisab uuesti laadimisest.
Näide, kui ikka kinni
- name: Rakendusserverid pääsevad andmebaasi
ansible.builtin.template:
src: pg_hba.conf.j2
dest: "{{ db_andmekaust }}/pg_hba.conf"
owner: postgres
group: postgres
mode: "0600"
notify: PostgreSQL laeb seaded uuesti
Handler handlers/main.yml-i:
Viimane task seadistus.yml-is avab pordi tulemüüris. Sama moodul mis K1 B5-s, aga service: http asemel port:
- name: Andmebaasi port on tulemüüris avatud
ansible.posix.firewalld:
port: "{{ db_port }}/tcp"
permanent: true
immediate: true
state: enabled
Jooksuta ja kontrolli:
Oodatav tulemus
TASK [db : postgresql.conf seaded on paigas] ******************
changed: [vm1] => (item={'nimi': 'listen_addresses', 'vaartus': '*'})
changed: [vm1] => (item={'nimi': 'password_encryption', 'vaartus': 'scram-sha-256'})
...
RUNNING HANDLER [db : PostgreSQL taaskäivitub] ****************
changed: [vm1]
pg_hba.conf-i lõpus on kaks rida, üks vm2 ja üks vm3 IP-ga.
Kui näed hoiatust Module remote_tmp ... did not exist and was created with a mode of 0700
See on esimese jooksu hoiatus, mitte viga. Ansible lõi kasutajatele root ja postgres ajutise kausta. Järgmisel jooksul hoiatust enam pole.
Samm 4. Kasutaja ja andmebaas. Rakendus vajab andmebaasi labor ja kasutajat labor. PostgreSQL-i kasutajate jaoks on olemas kollektsioon community.postgresql, aga meie masinates seda pole. Seepärast teed sama command-iga ja teed selle ise idempotentseks: esmalt küsid, kas kasutaja on olemas, ja lood ta ainult siis, kui pole.
Lisa tasks/main.yml-i lõppu kaks rida. flush_handlers käivitab ootel handlerid kohe, mitte play lõpus. PostgreSQL peab uue password_encryption seadega käima enne, kui kasutaja luuakse:
- name: Seaded rakenduvad enne kasutaja loomist
ansible.builtin.meta: flush_handlers
- name: Andmebaas ja kasutaja
ansible.builtin.import_tasks: kasutaja.yml
Loo roles/db/tasks/kasutaja.yml. Esimene task küsib ja ei muuda midagi:
- name: Kontrolli, kas andmebaasi kasutaja on olemas
ansible.builtin.command: psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='{{ db_kasutaja }}'"
become_user: postgres # (1)!
register: db_roll # (2)!
changed_when: false # (3)!
check_mode: false # (4)!
- Käsk jookseb kasutajana
postgres, kellel on andmebaasis kõik õigused. - Käsu tulemus (
stdout,rcjm) salvestatakse muutujassedb_roll. - Päring ei muuda midagi, seega ei tohi see olla
changed. Muidu poleks teine jooks kunagichanged=0. - Päring jookseb ka
--checkall. Ilma selleta jäetaks see vahele ja järgmine task ei teaks, mida otsustada.
Teine task loob kasutaja, aga ainult siis, kui päring ei vastanud 1:
- name: Andmebaasi kasutaja on olemas
ansible.builtin.command: psql -c "CREATE ROLE {{ db_kasutaja }} LOGIN PASSWORD '{{ db_parool }}'"
become_user: postgres
when: db_roll.stdout != "1"
no_log: true # (1)!
- Parool ei satu väljundisse ega logisse.
Andmebaasi jaoks on sama muster. Kirjuta need kaks task'i ise:
- kontroll:
psql -tAc "SELECT 1 FROM pg_database WHERE datname='{{ db_nimi }}'", tulemus muutujassedb_baas; - loomine:
createdb -O {{ db_kasutaja }} {{ db_nimi }}, ainult siis, kui andmebaasi pole.
Näide, kui ikka kinni
- name: Kontrolli, kas andmebaas on olemas
ansible.builtin.command: psql -tAc "SELECT 1 FROM pg_database WHERE datname='{{ db_nimi }}'"
become_user: postgres
register: db_baas
changed_when: false
check_mode: false
- name: Andmebaas on olemas
ansible.builtin.command: createdb -O {{ db_kasutaja }} {{ db_nimi }}
become_user: postgres
when: db_baas.stdout != "1"
Jooksuta kaks korda ja kontrolli:
ansible-playbook site.yml
ansible-playbook site.yml
ssh -t vm1 "sudo -u postgres psql -c '\l labor'"
Oodatav tulemus
Esimesel jooksul on Andmebaasi kasutaja on olemas ja Andmebaas on olemas changed. Teisel jooksul on need skipped ja kontrollpäringud ok. \l labor näitab andmebaasi, mille omanik on labor.
Kui psql annab could not change directory to \"/home/...\": Permission denied
See on hoiatus, mitte viga. Kasutaja postgres ei pääse sinu kodukausta, kust käsk käivitati. Käsk ise töötab.
Mõtle (vabatahtlik)
Mis juhtuks, kui muudaksid group_vars/all.yml-is parooli: kas andmebaasis parool muutuks? Miks mitte?
A5 · Paigalda rakendus vm2-le ja vm3-le¶
Selle sammu lõpuks käib vm2-s ja vm3-s systemd teenus labori-app, mis kirjutab iga külastuse vm1 andmebaasi.
Vaata enne rakendus läbi: less roles/app/files/app.py. Seaded tulevad keskkonnamuutujatest (DB_HOST, APP_PORT jt), aadressil /health vastab rakendus ok, kui andmebaas vastab. Sinu ülesanne on see masinasse panna nii, nagu paneksid iga teise teenuse: kasutaja, kood, seaded, systemd.
Samm 1. Kasutaja ja draiver. Rakendus ei jookse root'ina, vaid oma süsteemikasutajana. Lisa rolli vaikeväärtused roles/app/defaults/main.yml:
Esimene task roles/app/tasks/main.yml-is:
- name: Rakenduse kasutaja on olemas
ansible.builtin.user:
name: "{{ app_kasutaja }}"
system: true
shell: /sbin/nologin
create_home: false
system: true ja /sbin/nologin tähendavad: teenuse kasutaja, sisse logida ei saa. Teine task paigaldab paketi python3-psycopg2 (Pythoni PostgreSQL-i draiver). Kirjuta see ise.
Kolmas task loob kausta {{ app_kaust }} õigustega 0755. Moodul ansible.builtin.file, state: directory.
Näide, kui ikka kinni
Samm 2. Kood ja seaded. Kood kopeeritakse muutmata, seega copy. Rollis ei pea kirjutama teed, src: app.py leitakse kaustast roles/app/files/:
- name: Rakenduse kood on paigas
ansible.builtin.copy:
src: app.py
dest: "{{ app_kaust }}/app.py"
mode: "0755"
notify: Rakendus taaskäivitub
Seaded on igal masinal erinevad (port, andmebaasi IP), seega mall. Loo roles/app/templates/labori-app.env.j2:
# {{ ansible_managed }}
APP_NIMI="{{ app_nimi }}"
APP_VERSIOON="{{ app_versioon }}"
APP_PORT={{ app_port }}
DB_HOST={{ hostvars[groups['db'][0]].ansible_default_ipv4.address }}
DB_PORT={{ db_port }}
DB_NIMI={{ db_nimi }}
DB_KASUTAJA={{ db_kasutaja }}
DB_PAROOL={{ db_parool }}
Vaata rida DB_HOST. groups['db'][0] on db-grupi esimene masin, vm1. Tema IP tuleb jälle hostvars-ist. Kui andmebaas homme kolib, muudad inventari ja see rida uueneb ise.
Malli paigaldab task, mis on peaaegu sama nagu koodi oma. Kirjuta see ise: template, dest: /etc/labori-app.env, õigused 0600 (failis on parool), teavitab sama handlerit.
Näide, kui ikka kinni
Samm 3. systemd teenus. systemd vajab teenusefaili. Loo mall roles/app/templates/labori-app.service.j2:
# {{ ansible_managed }}
[Unit]
Description={{ app_nimi }}
After=network-online.target
Wants=network-online.target
[Service]
User={{ app_kasutaja }}
EnvironmentFile=/etc/labori-app.env
ExecStart=/usr/bin/python3 {{ app_kaust }}/app.py
Restart=on-failure
RestartSec=3
[Install]
WantedBy=multi-user.target
EnvironmentFile loeb sammu 2 seaded keskkonnamuutujateks. systemd loeb faili root'ina enne, kui rakendus kasutajana labori käivitub, seepärast sobib õigus 0600.
Lisa task'id: teenusefail malli järgi (dest: /etc/systemd/system/labori-app.service, 0644, teavitab handlerit) ja teenus käima:
- name: Rakendus käib
ansible.builtin.systemd:
name: labori-app
state: started
enabled: true
daemon_reload: true # (1)!
- systemd loeb uue või muudetud
.servicefaili sisse alles pärastdaemon-reload-i.
Näide: teenusefaili task, kui ikka kinni
Viimaseks handler roles/app/handlers/main.yml:
- name: Rakendus taaskäivitub
ansible.builtin.systemd:
name: labori-app
state: restarted
daemon_reload: true
Kolm task'i teavitavad sama handlerit. Kui muutuvad kõik kolm, taaskäivitub rakendus ikkagi ainult üks kord, play lõpus.
Lisa site.yml-i kolmas play (hosts: app, roll app) samal kujul nagu eelmised. Jooksuta:
Pärast küsi rakenduselt endalt, kas see näeb andmebaasi. Ad-hoc käsus saad kasutada muutujat, see arvutatakse iga masina jaoks eraldi:
Oodatav tulemus
Kui /health vastab 503 või uri kukub
Vaata rakenduse logi: ansible app -b -m command -a "journalctl -u labori-app -n 15 --no-pager".
no pg_hba.conf entry for host: vm1pg_hba.conf-is pole selle masina IP-d. Kas faktid olid kogutud? Jooksutasite.ymltervikuna, ilma--limit-ita.password authentication failed: paroolgroup_vars/all.yml-is ja andmebaasis erinevad. Kasutaja loodi varem teise parooliga.Connection refusedvõitimeout: vm1 tulemüür (5432/tcp) võilisten_addresses.Address already in use: port on teise teenuse all (ss -tlnp).
Mõtle (vabatahtlik)
Muuda group_vars/all.yml-is app_nimi ja jooksuta. Mitu task'i on changed ja mitu korda rakendus taaskäivitub? Taasta nimi ja jooksuta uuesti.
A6 · Pane koormusjaotur ette¶
Selle sammu lõpuks saadab vm1 nginx päringud edasi rakendusserveritele ja kogu nginx-i seadistus tuleb mallist.
Mall asendab terve /etc/nginx/nginx.conf-i. K1 avaleht vm1-s kaob, nii peabki olema. Ehitad malli kolmes osas. Lisa enne rolli vaikeväärtus roles/lb/defaults/main.yml:
Samm 1. Malli algus. Loo roles/lb/templates/nginx.conf.j2. Esimene osa on iga nginx-i põhi: kasutaja, protsessid, logid:
# {{ ansible_managed }}
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format lb '$remote_addr "$request" $status -> $upstream_addr $upstream_status';
access_log /var/log/nginx/access.log lb;
log_format lb kirjutab igale logireale, millisele rakendusserverile päring läks ja mis sealt vastati. B1-s on sellest abi.
Samm 2. Upstream. upstream on nimekiri serveritest, kuhu nginx päringud edasi saadab. Sama tsükkel mis pg_hba.conf-is, ainult et nüüd on vaja ka porti. Lisa malli:
upstream rakendus {
{% for h in groups['app'] %}
server {{ hostvars[h].ansible_default_ipv4.address }}:{{ app_port }} max_fails=1 fail_timeout=10s; # {{ h }}
{% endfor %}
}
max_fails=1 fail_timeout=10s: kui server ei vasta, jätab nginx selle 10 sekundiks vahele.
Samm 3. Server. Viimane osa võtab pordil 80 päringud vastu ja saadab need upstream-i:
server {
listen {{ lb_port }} default_server;
server_name _;
location / {
proxy_pass http://rakendus;
proxy_connect_timeout 2s;
proxy_next_upstream error timeout http_502 http_503;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
proxy_next_upstream: kui üks rakendusserver ei vasta, proovib nginx sama päringut järgmisega. Kasutaja viga ei näe.
Samm 4. Task'id. roles/lb/tasks/main.yml-is on neli task'i. Kolm neist on sulle tuttavad, kirjuta need ise:
- pakett
nginxon paigaldatud; - teenus
nginxkäib ja käivitub buutimisel; - tulemüüris on avatud teenus
http(sama mis K1 B5).
Uus on malli task. Pane see paketi ja teenuse vahele:
- name: Koormusjaoturi seadistus on paigas
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
mode: "0644"
validate: nginx -t -c %s # (1)!
notify: nginx laeb seaded uuesti
- Enne kui fail asendatakse, kontrollib nginx uut versiooni (
%son ajutise faili tee). Kui kontroll kukub, jääb vana fail paigale ja nginx töötab edasi. B2-s proovid seda.
Handler roles/lb/handlers/main.yml-is teeb reload-i, mitte restart-i. reload ei katkesta pooleliolevaid ühendusi. Kirjuta see ise: nimi nginx laeb seaded uuesti, state: reloaded.
Näide: kogu tasks/main.yml ja handler, kui ikka kinni
- name: nginx on paigaldatud
ansible.builtin.package:
name: nginx
state: present
- name: Koormusjaoturi seadistus on paigas
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
mode: "0644"
validate: nginx -t -c %s
notify: nginx laeb seaded uuesti
- name: nginx käib
ansible.builtin.service:
name: nginx
state: started
enabled: true
- name: HTTP on tulemüüris avatud
ansible.posix.firewalld:
service: http
permanent: true
immediate: true
state: enabled
Lisa site.yml-i neljas play (hosts: lb, roll lb). Enne jooksu vaata, mida mall kirjutaks:
Kui --limit lb annab 'dict object' has no attribute 'ansible_default_ipv4'
--limit lb piirab ka faktide kogumist. vm2 ja vm3 fakte ei kogutud, seega mall ei tea nende IP-d. Jooksuta ilma --limit-ita.
Jooksuta päriselt ja proovi:
Oodatav tulemus
Playbook oli roheline, aga leht ei tööta. Seda parandad järgmises sammus.
Loe juurde
A7 · Leia, miks leht annab 502¶
Selle sammu lõpuks vastab curl http://vm1/ rakenduse lehega ja mõlemad parandused on rollides, mitte käsitsi tehtud.
502 tähendab, et nginx ise töötab, aga ei saanud rakendusserverilt vastust. Põhjus on nginx-i vealogis. Vaata seda vm1-s:
Oodatav tulemus
Permission denied ühenduse loomisel, kuigi nginx jookseb ja port on õige.
Kui root-õigustega teenus saab Permission denied, on põhjus AlmaLinuxis tavaliselt SELinux. Kontrolli:
Oodatav tulemus
SELinux lubab veebiserveril (httpd_t) vaikimisi ainult vastu võtta ühendusi, mitte ise teistele masinatele ühenduda. Selleks on lüliti (boolean). Ära lülita SELinuxit välja. Lülita sisse just see üks luba, ja tee seda rollis.
Lisa roles/lb/tasks/main.yml-i enne malli task'i:
- name: nginx tohib ühenduda rakendusserveritega (SELinux)
ansible.posix.seboolean:
name: httpd_can_network_connect
state: true
persistent: true # (1)!
- Kehtib ka pärast taaskäivitust. Ilma selleta kaoks luba esimesel reboot'il.
Jooksuta ja proovi uuesti:
ansible-playbook site.yml
curl -s -i http://localhost/ | head -n 1
sudo tail -n 3 /var/log/nginx/error.log
Oodatav tulemus
Ikka 502, aga vealogis on teine viga:
Nüüd jõuab ühendus võrku, aga teine pool keeldub. No route to host on see, mida firewalld vastab suletud pordile. A5-s kontrollisid rakendust localhost-ist, mis tulemüürist läbi ei käi. Kontrolli:
Lisa roles/app/tasks/main.yml-i lõppu:
- name: Rakenduse port on tulemüüris avatud
ansible.posix.firewalld:
port: "{{ app_port }}/tcp"
permanent: true
immediate: true
state: enabled
Oodatav tulemus
Leht töötab ja loendur kasvab, nii et andmebaas on päriselt kasutusel. Vastab ainult vm2. Selle põhjuse leiad B1-s.
Kaks kihti, kaks eraldi kontrolli. SELinux otsustab, kas protsess tohib ühendust luua. Tulemüür otsustab, kas pakett pääseb masinasse. Mõlemad annavad kasutajale sama 502. Erinevuse näeb ainult logist: 13: Permission denied on oma masina SELinux, 113: No route to host on teise masina tulemüür.
Mõtle (vabatahtlik)
Kolleeg ütleb, et kiirem on setenforce 0 ja systemctl stop firewalld. Mis juhtub järgmisel reboot'il ja mis juhtub järgmisel site.yml jooksul?
A8 · Tõenda, et teine jooks ei muuda midagi¶
Selle sammu lõpuks on failis logid/site_teine_jooks.txt tõend, et kogu pinu on soovitud olekus.
Oodatav tulemus
PLAY RECAP ****************************************************
vm1 : ok=19 changed=0 unreachable=0 failed=0 skipped=2
vm2 : ok=12 changed=0 unreachable=0 failed=0 skipped=0
vm3 : ok=12 changed=0 unreachable=0 failed=0 skipped=0
skipped=2 vm1-l on kaks when-iga task'i: kasutaja ja andmebaas on juba olemas. Täpsed ok arvud võivad erineda.
Kui mõni task on igal jooksul changed
Vaata, milline. Tavalised põhjused täna: kontrollpäringul puudub changed_when: false, või lineinfile regexp ei leia oma rida ja lisab igal korral uue (grep listen_addresses /var/lib/pgsql/data/postgresql.conf).
Commit'i see seis enne osa B-d:
B · Iseseisev osa¶
Pinu töötab, aga mitte nii, nagu lubati: koormus ei jagune ja keegi pole kontrollinud, mis juhtub vigase malli või maas oleva rakendusega. Osas B leiad ja parandad. Sammud on antud, lahenduse leiad ise.
Piirangud kogu B-osas:
- Iga parandus on rollis, mallis või muutujates. Käsitsi tehtud muudatus masinas läheb järgmise jooksuga kaotsi.
host_vars/vm3.ymljääb alles: vm3 rakendus kuulab ka lõpus pordil 8081.- Pärast igat parandust:
--check --diff, siis jooks, siis ad-hoc kontroll.
B1 · Pane koormus vahelduma¶
Selle sammu lõpuks vastavad päringud vaheldumisi vm2-st ja vm3-st ning tõend on failis logid/vaheldumine.txt.
Praegu vastab ainult vm2. Leia põhjus ja paranda see koodis. Kontroll:
Vihje 1: kust otsida
nginx-i juurdepääsulogis on iga päringu kohta kirjas, kuhu see läks: sudo tail -n 6 /var/log/nginx/access.log. Vaata -> järel olevaid aadresse ja porte. Siis vaata, mis on mallist kirjutatud: grep server /etc/nginx/nginx.conf.
Vihje 2: kelle muutuja
Mall jookseb vm1 jaoks. {{ app_port }} mallis on vm1 väärtus. Mis on vm1 app_port? Mis on vm3 oma? Kuidas küsida mallis vm3 muutujat?
Näide, kui ikka kinni
{{ hostvars[h].app_port }}. Sama nagu IP-ga real eespool.
Mõtle (vabatahtlik)
Miks ei näinud kasutaja ühtki viga, kuigi pool päringuid läks vale pordi peale? Kas see on hea või halb?
B2 · Lase vigasel mallil kinni jääda¶
Selle sammu lõpuks oled näinud, et vigane nginx-i seadistus ei jõua masinasse ja leht töötab vea ajal edasi.
- Lisa malli
location /plokki ridaproxy_read_timeout 5silma semikoolonita. - Jooksuta
ansible-playbook site.yml. - Samal ajal või kohe pärast:
curl -s http://localhost/ | grep Vastas. - Paranda mall ja jooksuta uuesti.
Oodatav tulemus
TASK [lb : Koormusjaoturi seadistus on paigas] ****************
fatal: [vm1]: FAILED! => changed=false
...
msg: failed to validate
stderr: |-
nginx: [emerg] directive "proxy_read_timeout" is not terminated by ";" in /home/.../.ansible/tmp/.../source:...
curl vastab edasi, /etc/nginx/nginx.conf on vana. Handler ei käivitunud, sest faili ei muudetud.
Ilma validate-ita oleks vigane fail kirjutatud ja handler oleks teinud reload-i. reload vigase failiga ebaõnnestub ja vana protsess töötab edasi, aga järgmine restart või reboot jätaks nginx-i maha.
Mõtle (vabatahtlik)
Milliste teiste failide juures tänases repos oleks validate kasulik? Mis käsuga kontrolliksid sshd_config-i (K1 kodutöö H2)?
B3 · Võta üks rakendus maha¶
Selle sammu lõpuks on tõend, et ühe rakenduse kadumisel töötab leht edasi, ja site.yml toob rakenduse tagasi.
ssh -t vm2 "sudo systemctl stop labori-app"
for i in $(seq 1 6); do curl -s -o /dev/null -w "%{http_code} " http://localhost/; curl -s http://localhost/ | grep Vastas; done | tee logid/uks_maas.txt
Oodatav tulemus
Kõik read algavad 200-ga ja vastab ainult vm3. Ühtegi 502-te pole.
Nüüd ennusta, enne kui jooksutad: mitu changed-i ja millises masinas?
ansible-playbook site.yml --check
ansible-playbook site.yml
for i in $(seq 1 4); do curl -s http://localhost/ | grep Vastas; done
Oodatav tulemus
vm2 näitab changed=1 (task Rakendus käib), teised changed=0. Pärast vastavad jälle vm2 ja vm3 vaheldumisi.
Peatatud teenus on drift nagu K1-s. Playbook ei pea teadma, mis katki läks.
Vihje: kui 502 siiski tuleb
Kontrolli mallis proxy_next_upstream rida ja max_fails. Ilma nendeta saab kasutaja vea, kuni nginx märkab, et server on maas.
B4 · Lisa site.yml-i lõppu kontroll¶
Selle sammu lõpuks kukub site.yml ise punaseks, kui leht läbi koormusjaoturi ei vasta, isegi kui kõik task'id olid rohelised.
A6 näitas, et roheline playbook ja töötav leht pole sama asi. Lisa site.yml-i viimane play hosts: lb, mis:
- teeb päringu
http://localhost/ja salvestab vastuse; - kordab päringut kuni 5 korda 2-sekundilise vahega, kui vastus pole 200;
- kontrollib, et vastuses on
Vastas:jaKülastused:, ja annab muidu arusaadava veateate; - ei ole kunagi
changedja jookseb ka--checkall.
Vihje: moodulid ja parameetrid
ansible.builtin.uri (return_content), register, until/retries/delay, changed_when, check_mode; kontroll ansible.builtin.assert (that, fail_msg). Faktid pole siin vaja: gather_facts: false.
Näide, kui ikka kinni
- name: "Kontroll: leht vastab läbi koormusjaoturi"
hosts: lb
gather_facts: false
tasks:
- name: Avaleht vastab
ansible.builtin.uri:
url: "http://localhost/"
return_content: true
register: vastus
check_mode: false
changed_when: false
retries: 5
delay: 2
until: vastus.status == 200
- name: Vastuses on rakenduse masin ja andmebaasi loendur
ansible.builtin.assert:
that:
- "'Vastas:' in vastus.content"
- "'Külastused:' in vastus.content"
fail_msg: "Leht vastas, aga mitte rakendus: {{ vastus.content | truncate(200) }}"
Proovi, et kontroll päriselt kukub: peata mõlemas masinas labori-app ja jooksuta ainult viimane play (--start-at-task "Avaleht vastab"). Siis jooksuta tervet site.yml-i ja vaata, et kõik paraneb ja kontroll läbib.
Lõpuks salvesta uuesti teise jooksu tõend:
Lisaülesanne · Peida parool Vaulti¶
Selle ülesande lõpuks ei ole andmebaasi parool Gitis loetaval kujul, aga site.yml töötab nagu enne.
- Tõsta parool eraldi faili
group_vars/all/vault.ymlmuutujanavault_db_paroolja krüpti fail:ansible-vault encrypt group_vars/all/vault.yml. - Tõsta
group_vars/all.ymlsama kaustagroup_vars/all/main.yml-iks ja kirjuta sinnadb_parool: "{{ vault_db_parool }}". - Jooksuta
ansible-playbook site.yml --ask-vault-pass. Teine jookschanged=0. - Vaata
git diff --statjacat group_vars/all/vault.yml: failis on ainult$ANSIBLE_VAULT;1.1;AES256ja numbrid.
Vihje: miks kaks faili
Kui db_parool on otse krüptitud failis, ei leia keegi grep db_parool-iga üles, kust väärtus tuleb. Kaudne nimi (vault_ ees) hoiab muutujate nimekirja loetavana ja ainult väärtuse saladuses.
Vihje: Vaulti parool
Vaulti parooli ei pane reposse. Kui ei taha iga kord trükkida, pane see faili väljaspool repot (~/.vault_pass, õigused 600) ja ansible.cfg-s vault_password_file = ~/.vault_pass.
Loe juurde
Esitamine¶
README¶
Täida repos olev README.md mall: asenda nurksulgudes kohad oma tööga ja kustuta ülemine kast. ULESANNE.md jääb muutmata. Peegeldusküsimustele vasta lühidalt, üks-kaks lauset piisab. README täitmine on boonus: kontroll BOONUS README läbib, kui täitmata kohti enam pole.
Commit ja push¶
git status enne add-i näitab, mis faile lisad. Kui tegid lisaülesande, kontrolli, et group_vars/all/vault.yml on krüptitud enne, kui see Giti läheb.
Automaatne kontroll¶
Ava GitHubis oma repo → Actions → viimane Autograde:
| Kontroll | Mida vaatab |
|---|---|
| K1 | inventory.ini, site.yml, group_vars/all.yml või group_vars/all/, nelja rolli tasks/main.yml, logid/site_teine_jooks.txt olemas |
| K2 | site.yml süntaks |
| K3 | inventaris grupid lb, db, app; site.yml kasutab rolle; db-rollis import_tasks; handlerid db-, app- ja lb-rollis |
| K4 | lb-rolli mallis hostvars ja app_port; validate; seboolean; app-rollis firewalld |
| K5 | site_teine_jooks.txt: kolm masinat, kõigil changed=0; vaheldumine.txt: nii vm2 kui vm3; uks_maas.txt: ei ühtegi 502-te |
Hinne tuleb kontrollidest K1–K5 (45 punkti). Kontrollid nimega BOONUS (README ja kodutöö) punkte ei anna ja kukuvad seni, kuni need on tegemata. Punane rist tähendab siis ainult, et boonus on tegemata.
Kodutöö¶
Kodune õpe ja kodutöö on eraldi lehel: K2 · Kodune õpe ja kodutöö. Samasse reposse, tähtaeg Classroom 50-s.
Veaotsing¶
Veaotsingu tabel: tüüpilised vead ja lahendused
Kontrolli järjekorras: loe veateade algusest lõpuni, korda käsku -v-ga, vaata teenuse logi sihtmasinas.
| Probleem | Põhjus | Lahendus |
|---|---|---|
the role 'db' was not found |
rolli kaust on vales kohas või vale nimega | roles/db/tasks/main.yml repo juurest; ls roles |
Could not find or access 'app.py' |
fail pole rolli files/ kaustas |
mv files/app.py roles/app/files/ |
Could not find or access 'nginx.conf.j2' |
mall pole rolli templates/ kaustas |
vaata ls roles/lb/templates |
The requested handler '...' was not found |
notify ja handleri name erinevad |
kopeeri nimi täpselt, suurtähed loevad |
'dict object' has no attribute 'ansible_default_ipv4' |
teise masina fakte pole kogutud (--limit) |
jooksuta ilma --limit-ita |
'app_port' is undefined |
muutuja puudub group_vars-ist või failinimi on vale |
ansible-inventory --host vm2 |
group_vars ei rakendu |
kaust pole repo juures või fail on .yaml/.yml asemel muu |
ls group_vars, ansible stack -m debug -a var=db_nimi |
postgresql-setup: ... Data directory is not empty |
andmekaust on juba olemas | creates puudub või vale tee |
task postgresql.conf seaded igal jooksul changed |
regexp ei leia rida |
regexp: "^#?listen_addresses\\s*=" |
Andmebaasi kasutaja on olemas igal jooksul changed või failed (already exists) |
when vaatab vale muutujat |
debug: var=db_roll |
--check kukub task'is Andmebaasi kasutaja on olemas |
kontrollpäringul puudub check_mode: false |
lisa see |
/health 503, logis no pg_hba.conf entry |
masina IP pole pg_hba.conf-is |
jooksuta site.yml tervikuna |
/health 503, logis password authentication failed |
parool on andmebaasis teine | sudo -u postgres psql -c "ALTER ROLE labor PASSWORD '...'" või tühjenda andmebaas |
rakendus ei käivitu, ModuleNotFoundError: psycopg2 |
pakett puudu | python3-psycopg2 app-rollis |
502, logis 13: Permission denied |
SELinux | A7, httpd_can_network_connect |
502, logis 113: No route to host |
rakendusserveri tulemüür | A7, app_port firewalld-s |
502, logis 111: Connection refused |
rakendus ei käi või kuulab teisel pordil | systemctl status labori-app, ss -tlnp |
| vastab ainult üks rakendusserver | mallis on kõigil sama port | B1, hostvars |
failed to validate |
mallis on süntaksiviga | B2, loe stderr-i rida |
curl näitab nginx-i vaikelehte |
nginx.conf pole asendatud (dest vale) |
dest: /etc/nginx/nginx.conf |
Decryption failed |
vale Vaulti parool | --ask-vault-pass |