RHEL 8 Beta oferă dezvoltatorilor multe noi oportunități, enumerate pe pagini întregi, însă a explora noul este întotdeauna mai eficient prin practică, așa că vă propunem mai jos să parcurgem un workshop despre crearea reală a infrastructurii aplicațiilor bazate pe Red Hat Enterprise Linux 8 Beta.

Vom folosi Python, un limbaj de programare popular printre dezvoltatori, împreună cu Django și PostgreSQL, o combinație destul de comună pentru crearea aplicațiilor, și vom configura RHEL 8 Beta pentru a lucra cu acestea. Apoi, vom adăuga încă câteva ingrediente (neconfidențiale).
Mediul de testare va variate, deoarece este interesant să explorăm automatizarea, lucrul cu containerele și să încercăm medii cu mai multe servere. Pentru a începe cu un nou proiect, se poate crea un mic prototip manual - astfel, se poate observa ce ar trebui să se întâmple și cum se realizează interacțiunea, apoi se poate trece la automatizare și la crearea unor configurații mai complexe. Astăzi, vorbim despre crearea unui astfel de prototip.
Să începem cu desfășurarea imaginii unei mașini virtuale RHEL 8 Beta VM. Se poate instala o mașină virtuală de la zero sau se poate folosi imaginea gazdă KVM disponibilă împreună cu abonamentul la Beta. Când folosiți imaginea gazdă, va trebui să configurați un CD virtual care va conține metadatele și datele utilizatorului pentru inițializarea cloud (cloud-init). Nu este necesar să faceți nimic special cu structura discului sau pachetele disponibile; orice configurație va fi potrivită.
Hai să examinăm întregul proces mai detaliat.
Instalarea Django
Cu cea mai recentă versiune de Django, va fi nevoie de un mediu virtual (virtualenv) cu Python 3.5 sau o versiune ulterioară. În notele pentru Beta, se menționează că Python 3.6 este disponibil, haideți să verificăm dacă este într-adevăr așa:
[cloud-user@8beta1 ~]$ python
-bash: python: command not found
[cloud-user@8beta1 ~]$ python3
-bash: python3: command not found
Red Hat folosește în mod activ Python ca instrument de sistem în RHEL, de ce apare însă acest rezultat?
Este adevărat că mulți dezvoltatori care folosesc Python încă se gândesc la tranziția de la Python 2 la Python 2, în timp ce Python 3 este în activă dezvoltare și apar constant versiuni noi și noi. Prin urmare, pentru a satisface nevoia de instrumente de sistem stabile și, în același timp, a oferi utilizatorilor acces la diferitele versiuni noi de Python, Python-ul de sistem a fost mutat într-un nou pachet, având posibilitatea de a instala atât Python 2.7, cât și 3.6. Informații mai detaliate despre modificări și despre motivul pentru care a fost făcut acest lucru pot fi găsite în publicația din (Langdon White).
Așadar, pentru a obține Python funcțional, este necesar să instalați doar două pachete, iar python3-pip va fi instalat ca dependență.
sudo yum install python36 python3-virtualenv
De ce nu ar trebui să folosiți apeluri directe la modul, așa cum sugerează Langdon, și să instalați pip3? Având în vedere automatizarea viitoare, este cunoscut faptul că pentru funcționarea Ansible este necesar să fie instalat pip, deoarece modulul pip nu suportă medii virtuale (virtualenvs) cu un executabil pip personalizat.
Având un interpretor python3 funcțional, puteți continua procesul de instalare Django și obțineți un sistem funcțional împreună cu celelalte componente ale noastre. Există multe variante de implementare disponibile online. Aici este prezentată o versiune, dar utilizatorii pot folosi propriile procese.
Versiunile PostgreSQL și Nginx disponibile în RHEL 8 vor fi instalate folosind Yum.
sudo yum install nginx postgresql-server
Pentru PostgreSQL va fi necesar psycopg2, dar trebuie să fie disponibil doar în mediul virtual, așa că îl vom instala folosind pip3 împreună cu Django și Gunicorn. Dar mai întâi trebuie să configurăm virtualenv.
Există multe dezbateri despre alegerea corectă a locului pentru instalarea proiectelor Django, dar când apar îndoieli, întotdeauna se poate consulta standardul Linux Filesystem Hierarchy Standard. În special, în FHS se menționează că /srv este folosit pentru: „stocarea datelor specifice unui nod - date care sunt furnizate de sistem, precum date și scripturi ale serverelor web, date stocate pe serverele FTP, precum și repozitorii de sisteme de control al versiunilor (apărute în FHS-2.3 în 2004)”.
Aceasta este situația noastră, așa că punem tot ceea ce avem nevoie în /srv, a cărei proprietar este utilizatorul aplicației (cloud-user).
sudo mkdir /srv/djangoapp
sudo chown cloud-user:cloud-user /srv/djangoapp
cd /srv/djangoapp
virtualenv django
source django/bin/activate
pip3 install django gunicorn psycopg2
./django-admin startproject djangoapp /srv/djangoapp
Configurarea PostgreSQL și Django nu este complicată: creăm o bază de date, creăm un utilizator și configurăm permisiunile. Există un aspect pe care ar trebui să-l avem în vedere la instalarea inițială a PostgreSQL – este vorba de scriptul postgresql-setup, care se instalează împreună cu pachetul postgresql-server. Acest script ajută la realizarea sarcinilor de bază legate de administrarea cluster-ului de baze de date, cum ar fi inițializarea cluster-ului sau procesul de actualizare. Pentru a configura o nouă instanță PostgreSQL pe sistemul RHEL, trebuie să executăm comanda:
sudo /usr/bin/postgresql-setup -initdb
După aceea, putem porni PostgreSQL cu systemd, să creăm baza de date și să configurăm proiectul în Django. Nu uitați să reporniți PostgreSQL după ce ați efectuat modificări în fișierul de configurare a autentificării clientului (de obicei, pg_hba.conf) pentru a configura stocarea parolei pentru utilizatorul aplicației. Dacă întâmpinați alte dificultăți, asigurați-vă că setările IPv4 și IPv6 din fișierul pg_hba.conf au fost modificate.
systemctl enable --now postgresql
sudo -u postgres psql
postgres=# create database djangoapp;
postgres=# create user djangouser with password 'qwer4321';
postgres=# alter role djangouser set client_encoding to 'utf8';
postgres=# alter role djangouser set default_transaction_isolation to 'read committed';
postgres=# alter role djangouser set timezone to 'utc';
postgres=# grant all on DATABASE djangoapp to djangouser;
postgres=# q
În fișierul /var/lib/pgsql/data/pg_hba.conf:
# IPv4 local connections:
host all all 0.0.0.0/0 md5
# IPv6 local connections:
host all all ::1/128 md5
În fișierul /srv/djangoapp/settings.py:
# Database
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': '{{ db_name }}',
'USER': '{{ db_user }}',
'PASSWORD': '{{ db_password }}',
'HOST': '{{ db_host }}',
}
}
După ce a fost configurat fișierul settings.py în proiect și configurarea bazei de date, putem porni serverul de dezvoltare pentru a ne asigura că totul funcționează. După ce serverul de dezvoltare a fost pornit, este bine să creăm un utilizator admin pentru a testa conexiunea cu baza de date.
./manage.py runserver 0.0.0.0:8000
./manage.py createsuperuser
WSGI? Ce?
Serverul de dezvoltare este util pentru testare, dar pentru a rula aplicația este necesară configurarea serverului și a proxy-ului corespunzător pentru Web Server Gateway Interface (WSGI). Există câteva combinații comune, cum ar fi Apache HTTPD cu uWSGI sau Nginx cu Gunicorn.
Scopul Web Server Gateway Interface este de a redirecționa cererile de la server web la cadrul framework-ului web Python. WSGI este un fel de moștenire a unui trecut teribil, când erau folosite mecanisme CGI, și astăzi WSGI este practic un standard, indiferent de serverul web sau de framework-ul Python utilizat. Cu toate acestea, în ciuda răspândirii sale largi, există în continuare multiple nuanțe în utilizarea acestor framework-uri și numeroase opțiuni de alegere. În acest caz, vom încerca să configurăm interacțiunea dintre Gunicorn și Nginx printr-un socket.
Dat fiind că ambele componente sunt instalate pe același server, vom încerca să folosim în loc de un socket de rețea un socket UNIX. De vreme ce pentru comunicații, într-un fel sau altul, avem nevoie de un socket, vom face un pas suplimentar și vom configura activarea socket-ului pentru Gunicorn prin systemd.
Procesul de creare a serviciilor activate prin socket (socket activated services) este destul de simplu. Mai întâi, se creează un fișier unit care conține directiva ListenStream, indicând punctul unde va fi creat socket-ul UNIX, apoi un fișier unit pentru serviciu în care directiva Requires va indica unit-ul socket-ului. Apoi, în fișierul unit al serviciului, va trebui doar să apelăm Gunicorn din mediul virtual și să creăm un binding WSGI pentru socket-ul UNIX și aplicația Django.
Iată câteva exemple de fișiere unit care pot fi folosite ca bază. Mai întâi configurăm socket-ul.
[Unit]
Description=Socket WSGI Gunicorn
[Socket]
ListenStream=\/run\/gunicorn.sock
[Install]
WantedBy=sockets.target
Acum trebuie să configurăm demonul Gunicorn.
[Unit]
Description=Daemon Gunicorn
Requires=gunicorn.socket
After=network.target
[Service]
User=cloud-user
Group=cloud-user
WorkingDirectory=\/srv\/djangoapp
ExecStart=\/srv\/djangoapp\/django\/bin\/gunicorn
—access-logfile -
—workers 3
—bind unix:gunicorn.sock djangoapp.wsgi
[Install]
WantedBy=multi-user.target
Pentru Nginx, este suficient să creați fișierele de configurare pentru proxy și să configurați directorul pentru a stoca conținutul static, dacă îl utilizați. În RHEL, fișierele de configurare Nginx se află în \/etc\/nginx\/conf.d. Puteți copia următorul exemplu în fișierul \/etc\/nginx\/conf.d\/default.conf și să porniți serviciul. Asigurați-vă că ați specificat server_name conform numelui gazdelor dvs.
server {
listen 80;
server_name 8beta1.example.com;
location = \/favicon.ico { access_log off; log_not_found off; }
location \/static\/ {
root \/srv\/djangoapp;
}
location \/ {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http:\/\/unix:\/run\/gunicorn.sock;
}
}
Porniți socket-ul Gunicorn și Nginx folosind systemd, iar apoi puteți începe testarea.
Eroare Bad Gateway?
Dacă introduceți adresa în browser, cel mai probabil veți primi eroarea 502 Bad Gateway. Aceasta poate fi cauzată de permisiuni configurate incorect pentru socket-ul UNIX, sau de probleme mai complexe legate de gestionarea accesului în SELinux.
În jurnalul de erori nginx, puteți întâlni o linie similară cu următoarea:
2018/12/18 15:38:03 [crit] 12734#0: *3 connect() to unix:/run/gunicorn.sock failed (13: Permission denied) while connecting to upstream, client: 192.168.122.1, server: 8beta1.example.com, request: "GET / HTTP/1.1", upstream: "http://unix:/run/gunicorn.sock:/", host: "8beta1.example.com"
Dacă testăm Gunicorn direct, atunci vom obține un răspuns gol.
curl --unix-socket /run/gunicorn.sock 8beta1.example.com
Să analizăm de ce se întâmplă acest lucru. Dacă deschidem jurnalul, cel mai probabil vom observa că problema este legată de SELinux. Deoarece avem un daemon în execuție, pentru care nu a fost creată o politică proprie, acesta este marcat ca init_t. Să verificăm această teorie în practică.
sudo setenforce 0
Toate acestea pot provoca critici și lacrimi amare, dar este doar o depanare a prototipului. Vom dezactiva verificarea doar pentru a ne asigura că problema este într-adevăr aceasta, după care vom readuce totul înapoi la locul său.
După ce actualizăm pagina în browser sau repornim comanda noastră curl, putem vedea pagina de testare Django.
Așadar, asigurându-ne că totul funcționează și nu mai există probleme legate de permisiuni, reactivăm SELinux.
sudo setenforce 1
Aici nu va fi o poveste despre audit2allow și despre crearea politicilor bazate pe alerte folosind sepolgen, deoarece în prezent nu există o aplicație Django reală, astfel că nu există o hartă completă a ceea ce ar putea dori Gunicorn să acceseze și ce acces ar trebui să fie interzis. Prin urmare, este necesar să păstrăm funcționalitatea SELinux pentru a proteja sistemul, dar în același timp să permitem aplicației să ruleze și să lase mesaje în jurnalul de audit, astfel încât să putem crea ulterior o politică reală pe baza acestora.
Specificarea domeniilor permise (permissive domains)
Nu toată lumea a auzit despre domeniile permise în SELinux, dar ele nu sunt ceva nou. Mulți au lucrat cu ele, fără să își dai seama. Atunci când se creează o politică pe baza mesajelor de audit, politica creată reprezintă un domeniu permis. Haideți să încercăm să creăm o politică de permisie cât mai simplă.
Pentru a crea un domeniu specific permis pentru Gunicorn, este nevoie de o politică, precum și de marcarea fișierelor corespunzătoare. De asemenea, sunt necesare instrumente pentru a crea noi politici.
sudo yum install selinux-policy-devel
Mecanismul domeniilor permise este un instrument excelent pentru identificarea problemelor, mai ales atunci când vine vorba de aplicații personalizate sau aplicații livrate fără politici create anterior. În acest caz, politica domeniului permis pentru Gunicorn va fi cât mai simplă - vom declara tipul principal (gunicorn_t), vom declara un tip pe care îl vom folosi pentru a marca mai multe fișiere executabile (gunicorn_exec_t) și apoi vom configura tranziția (transition) pentru system, pentru a marca corespunzător procesele care rulează. Ultima linie stabilește politica ca permisivă implicit în momentul încărcării acesteia.
gunicorn.te:
policy_module(gunicorn, 1.0)
type gunicorn_t;
type gunicorn_exec_t;
init_daemon_domain(gunicorn_t, gunicorn_exec_t)
permissive gunicorn_t;
Acest fișier de politici poate fi compilat și adăugat în sistem.
make -f /usr/share/selinux/devel/Makefile
sudo semodule -i gunicorn.pp
sudo semanage permissive -a gunicorn_t
sudo semodule -l | grep permissive
Hai să verificăm dacă SELinux blochează ceva suplimentar, pe lângă ceea ce accesează demonul nostru necunoscut.
sudo ausearch -m AVC
type=AVC msg=audit(1545315977.237:1273): avc: denied { write } for pid=19400 comm="nginx" name="gunicorn.sock" dev="tmpfs" ino=52977 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:var_run_t:s0 tclass=sock_file permissive=0
SELinux nu permite Nginx să scrie date în socket-ul UNIX utilizat de Gunicorn. De obicei, în astfel de situații, se încep modificările politicilor, dar mai sunt și alte probleme de rezolvat. Se poate, de asemenea, schimba setările domeniului, transformându-l dintr-un domeniu de restricții într-un domeniu permisiv. Acum vom muta httpd_t în domeniul permisiv. Asta va oferi Nginx accesul necesar și vom putea continua cu depanarea.
sudo semanage permissive -a httpd_t
Deci, când am reușit să menținem protecția SELinux (de fapt, nu ar trebui să lăsați proiectul în modul restricții) și domeniile permisive sunt încărcate, este necesar să determinăm ce anume trebuie marcat ca gunicorn_exec_t pentru a funcționa din nou corect. Să încercăm să accedem la site-ul web pentru a vedea noile mesaje privind restricțiile de acces.
sudo ausearch -m AVC -c gunicorn
Se pot observa numeroase mesaje care conțin 'comm=«gunicorn»', care execută diverse acțiuni asupra fișierelor din /srv/djangoapp, așa că, evident, aceasta este una dintre comenzile pe care ar trebui să le marcăm.
Dar, în plus, apare un mesaj de acest tip:
type=AVC msg=audit(1545320700.070:1542): avc: denied { execute } for pid=20704 comm="(gunicorn)" name="python3.6" dev="vda3" ino=8515706 scontext=system_u:system_r:init_t:s0 tcontext=unconfined_u:object_r:var_t:s0 tclass=file permissive=0
Dacă verifici starea serviciului gunicorn sau executi comanda ps, nu vor apărea procese active. Se pare că gunicorn încearcă să acceseze interpretul Python din mediul nostru virtualenv, probabil pentru a rula scripturi de lucru (workers). Așadar, acum vom marca aceste două fișiere executabile și vom verifica dacă putem deschide pagina noastră de test Django.
chcon -t gunicorn_exec_t /srv/djangoapp/django/bin/gunicorn /srv/djangoapp/django/bin/python3.6
Va trebui să repornești serviciul gunicorn pentru a putea aplica noua etichetă. Poți să-l repornești imediat sau să oprești serviciul și să permiți socket-ului să-l pornească la deschiderea site-ului în browser. Asigură-te că procesele au primit etichetele corecte, folosind ps.
ps -efZ | grep gunicorn
Nu uita să creezi apoi o politică SELinux adecvată!
Dacă verifici mesajele AVC acum, ultimul mesaj conține permissive=1 pentru tot ceea ce ține de aplicație și permissive=0 pentru întreaga altă sistem. Dacă înțelegi ce acces este necesar pentru aplicația reală, poți găsi mai repede o soluție optimă pentru aceste probleme. Dar până atunci, este mai bine ca sistemul să fie protejat și să obții un audit clar și util pentru proiectul Django.
sudo ausearch -m AVC
A reușit!
A apărut un proiect Django funcțional cu frontend pe Nginx și Gunicorn WSGI. Am configurat Python 3 și PostgreSQL 10 din repozitoarele RHEL 8 Beta. Acum putem merge mai departe și crea (sau pur și simplu desfășura) aplicații Django sau să explorăm alte instrumente disponibile în RHEL 8 Beta pentru automatizarea procesului de configurare, îmbunătățirea performanței sau chiar containerizarea acestei configurații.
Sursa: habr.com
