Delavnica RHEL 8 Beta: Gradnja delujočih spletnih aplikacij

RHEL 8 Beta ponuja razvijalcem številne nove funkcije, katerih naštevanje bi lahko trajalo veliko, vendar je učenje novih stvari v praksi vedno boljše, zato spodaj ponujamo delavnico o dejanskem ustvarjanju aplikacijske infrastrukture, ki temelji na Red Hat Enterprise Linux 8 Beta.

Delavnica RHEL 8 Beta: Gradnja delujočih spletnih aplikacij

Za osnovo vzemimo Python, med razvijalci priljubljen programski jezik, kombinacijo Djanga in PostgreSQL, dokaj pogoste kombinacije za ustvarjanje aplikacij, in nastavimo RHEL 8 Beta za delo z njima. Nato bomo dodali še nekaj (neuvrščenih) sestavin.

Testno okolje se bo spremenilo, saj je zanimivo raziskovati možnosti avtomatizacije, dela s kontejnerji in preizkušanje okolij z več strežniki. Če želite začeti z novim projektom, lahko začnete tako, da ročno ustvarite majhen, preprost prototip, da boste lahko natančno videli, kaj se mora zgoditi in kako deluje, nato pa nadaljujete z avtomatizacijo in ustvarjanjem bolj zapletenih konfiguracij. Danes govorimo o ustvarjanju takšnega prototipa.

Začnimo z uvedbo slike RHEL 8 Beta VM. Navidezni stroj lahko namestite iz nič ali uporabite gostujočo sliko KVM, ki je na voljo z vašo naročnino Beta. Pri uporabi gostujoče slike boste morali konfigurirati virtualni CD, ki bo vseboval metapodatke in uporabniške podatke za inicializacijo v oblaku (cloud-init). S strukturo diska ali razpoložljivimi paketi vam ni treba storiti ničesar posebnega; zadostuje vsaka konfiguracija.

Oglejmo si podrobneje celoten postopek.

Namestitev Djanga

Z najnovejšo različico Djanga boste potrebovali virtualno okolje (virtualenv) s Pythonom 3.5 ali novejšim. V beta opombah lahko vidite, da je Python 3.6 na voljo, preverimo, ali je res tako:

[cloud-user@8beta1 ~]$ python
-bash: python: command not found
[cloud-user@8beta1 ~]$ python3
-bash: python3: command not found

Red Hat aktivno uporablja Python kot sistemski komplet orodij v RHEL, zakaj se torej to zgodi?

Dejstvo je, da številni razvijalci Pythona še vedno razmišljajo o prehodu s Python 2 na Python 2, sam Python 3 pa je v aktivnem razvoju in vedno več novih različic se pojavlja. Da bi zadovoljili potrebo po stabilnih sistemskih orodjih, hkrati pa uporabnikom ponudili dostop do različnih novih različic Pythona, je bil sistemski Python premaknjen v nov paket in je zagotovil možnost namestitve Pythona 2.7 in 3.6. Več informacij o spremembah in razlogih zanje najdete v objavi v Blog Langdona Whitea (Langdon White).

Če želite torej Python delovati, morate namestiti samo dva paketa, pri čemer je python3-pip vključen kot odvisnost.

sudo yum install python36 python3-virtualenv

Zakaj ne bi uporabili neposrednih klicev modulov, kot predlaga Langdon, in namestili pip3? Ob upoštevanju prihajajoče avtomatizacije je znano, da bo Ansible za delovanje zahteval nameščen pip, saj modul pip ne podpira virtualenvs z izvršljivo datoteko pip po meri.

Z delujočim tolmačem python3, ki vam je na voljo, lahko nadaljujete s postopkom namestitve Django in imate delujoč sistem skupaj z našimi drugimi komponentami. Na internetu je na voljo veliko možnosti izvedbe. Tukaj je predstavljena ena različica, vendar lahko uporabniki uporabljajo svoje lastne procese.

Z Yum bomo privzeto namestili različici PostgreSQL in Nginx, ki sta na voljo v RHEL 8.

sudo yum install nginx postgresql-server

PostgreSQL bo zahteval psycopg2, vendar mora biti na voljo le v okolju virtualenv, zato ga bomo namestili s pip3 skupaj z Django in Gunicorn. Toda najprej moramo nastaviti virtualenv.

Vedno je veliko razprav na temo izbire pravega mesta za namestitev projektov Django, a ko ste v dvomih, se lahko vedno obrnete na standard hierarhije datotečnega sistema Linux. Natančneje, FHS pravi, da se /srv uporablja za: »shranjevanje podatkov, specifičnih za gostitelja – podatkov, ki jih sistem proizvede, kot so podatki in skripti spletnega strežnika, podatki, shranjeni na strežnikih FTP, in repozitorije nadzornega sistema.« različice (ki se pojavljajo v FHS -2.3 leta 2004)."

To je točno naš primer, zato vse, kar potrebujemo, damo v /srv, ki je v lasti uporabnika naše aplikacije (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

Nastavitev PostgreSQL in Django je enostavna: ustvarite bazo podatkov, ustvarite uporabnika, konfigurirajte dovoljenja. Ena stvar, ki jo morate upoštevati pri začetni namestitvi PostgreSQL, je namestitveni skript postgresql, ki je nameščen s paketom postgresql-server. Ta skript vam pomaga izvajati osnovne naloge, povezane z upravljanjem gruče baze podatkov, kot je inicializacija gruče ali postopek nadgradnje. Če želite konfigurirati nov primerek PostgreSQL v sistemu RHEL, moramo zagnati ukaz:

sudo /usr/bin/postgresql-setup -initdb

Nato lahko zaženete PostgreSQL z uporabo systemd, ustvarite bazo podatkov in nastavite projekt v Djangu. Ne pozabite znova zagnati PostgreSQL, potem ko spremenite konfiguracijsko datoteko za preverjanje pristnosti odjemalca (običajno pg_hba.conf), da konfigurirate shranjevanje gesel za uporabnika aplikacije. Če naletite na druge težave, spremenite nastavitve IPv4 in IPv6 v datoteki pg_hba.conf.

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

V datoteki /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

V datoteki /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 }}',
   }
}

Ko konfigurirate datoteko settings.py v projektu in nastavite konfiguracijo baze podatkov, lahko zaženete razvojni strežnik, da se prepričate, da vse deluje. Po zagonu razvojnega strežnika je dobro ustvariti skrbniškega uporabnika, da preizkusite povezavo z bazo podatkov.

./manage.py runserver 0.0.0.0:8000
./manage.py createsuperuser

WSGI? Wai?

Razvojni strežnik je uporaben za testiranje, vendar morate za zagon aplikacije konfigurirati ustrezen strežnik in proxy za vmesnik spletnega strežnika Gateway (WSGI). Obstaja več pogostih kombinacij, na primer Apache HTTPD z uWSGI ali Nginx z Gunicorn.

Naloga vmesnika prehoda spletnega strežnika je posredovanje zahtev s spletnega strežnika spletnemu ogrodju Python. WSGI je ostanek strašne preteklosti, ko so bili motorji CGI naokoli, danes pa je WSGI de facto standard, ne glede na uporabljeni spletni strežnik ali ogrodje Python. Toda kljub široki uporabi je pri delu s temi okviri še vedno veliko odtenkov in veliko izbir. V tem primeru bomo poskušali vzpostaviti interakcijo med Gunicorn in Nginx prek vtičnice.

Ker sta obe komponenti nameščeni na istem strežniku, poskusimo namesto omrežne vtičnice uporabiti vtičnico UNIX. Ker komunikacija v vsakem primeru zahteva vtičnico, poskusimo narediti še en korak in konfigurirati aktivacijo vtičnice za Gunicorn prek systemd.

Postopek ustvarjanja storitev, aktiviranih z vtičnicami, je precej preprost. Najprej se ustvari datoteka enote, ki vsebuje direktivo ListenStream, ki kaže na točko, kjer bo ustvarjena vtičnica UNIX, nato pa datoteka enote za storitev, v kateri bo direktiva Zahteva kazala na datoteko enote vtičnice. Nato v datoteki storitvene enote ostane le še, da pokličemo Gunicorn iz virtualnega okolja in ustvarimo vezavo WSGI za vtičnico UNIX in aplikacijo Django.

Tukaj je nekaj primerov datotek enot, ki jih lahko uporabite kot osnovo. Najprej smo postavili vtičnico.

[Unit]
Description=Gunicorn WSGI socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target

Zdaj morate konfigurirati demona Gunicorn.

[Unit]
Description=Gunicorn daemon
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

Za Nginx je preprosto ustvarjanje konfiguracijskih datotek posrednika in nastavitev imenika za shranjevanje statične vsebine, če ga uporabljate. V RHEL se konfiguracijske datoteke Nginx nahajajo v /etc/nginx/conf.d. Naslednji primer lahko kopirate v datoteko /etc/nginx/conf.d/default.conf in zaženete storitev. Prepričajte se, da nastavite ime strežnika tako, da se ujema z imenom vašega gostitelja.

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

Zaženite vtičnico Gunicorn in Nginx z uporabo systemd in pripravljeni ste na začetek testiranja.

Napaka Bad Gateway?

Če naslov vnesete v brskalnik, boste najverjetneje prejeli napako 502 Bad Gateway. Lahko je posledica nepravilno konfiguriranih dovoljenj vtičnice UNIX ali pa je lahko posledica bolj zapletenih težav, povezanih z nadzorom dostopa v SELinuxu.

V dnevniku napak nginx lahko vidite vrstico, kot je ta:

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"

Če neposredno testiramo Gunicorn, bomo dobili prazen odgovor.

curl —unix-socket /run/gunicorn.sock 8beta1.example.com

Ugotovimo, zakaj se to zgodi. Če odprete dnevnik, boste najverjetneje videli, da je težava povezana s SELinuxom. Ker izvajamo demon, za katerega ni bil ustvarjen pravilnik, je označen kot init_t. Preizkusimo to teorijo v praksi.

sudo setenforce 0

Vse to lahko povzroči kritike in krvave solze, vendar je to le odpravljanje napak v prototipu. Onemogočimo preverjanje samo zato, da se prepričamo, da je to težava, potem pa bomo vse vrnili na svoje mesto.

Če osvežite stran v brskalniku ali znova zaženete naš ukaz curl, si lahko ogledate preskusno stran Django.

Torej, ko smo se prepričali, da vse deluje in ni več težav z dovoljenji, ponovno omogočimo SELinux.

sudo setenforce 1

Tukaj ne bom govoril o audit2allow ali ustvarjanju politik, ki temeljijo na opozorilih, s sepolgenom, ker trenutno ni dejanske aplikacije Django, zato ni popolnega zemljevida, do česa bi Gunicorn želel dostopati in do česa bi moral zavrniti dostop. Zato je treba ohraniti delovanje SELinuxa, da zaščitimo sistem, hkrati pa omogočimo aplikaciji, da teče in pusti sporočila v revizijskem dnevniku, tako da se lahko iz njih ustvari dejanski pravilnik.

Določanje permisivnih domen

Niso vsi slišali za dovoljene domene v SELinuxu, vendar niso nič novega. Mnogi so celo delali z njimi, ne da bi se tega sploh zavedali. Ko je pravilnik ustvarjen na podlagi revizijskih sporočil, ustvarjeni pravilnik predstavlja razrešeno domeno. Poskusimo ustvariti preprosto politiko izdajanja dovoljenj.

Če želite ustvariti določeno dovoljeno domeno za Gunicorn, potrebujete nekakšen pravilnik, prav tako pa morate označiti ustrezne datoteke. Poleg tega so potrebna orodja za sestavljanje novih pravilnikov.

sudo yum install selinux-policy-devel

Mehanizem dovoljenih domen je odlično orodje za prepoznavanje težav, zlasti ko gre za aplikacijo po meri ali aplikacije, ki so dobavljene brez že ustvarjenih pravilnikov. V tem primeru bo pravilnik o dovoljeni domeni za Gunicorn čim preprostejši – deklarirajte glavni tip (gunicorn_t), deklarirajte tip, ki ga bomo uporabili za označevanje več izvršljivih datotek (gunicorn_exec_t), nato pa nastavite prehod, da sistem pravilno označi tekoči procesi. Zadnja vrstica nastavi pravilnik kot privzeto omogočen v času nalaganja.

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;

To datoteko pravilnika lahko sestavite in jo dodate v svoj sistem.

make -f /usr/share/selinux/devel/Makefile
sudo semodule -i gunicorn.pp

sudo semanage permissive -a gunicorn_t
sudo semodule -l | grep permissive

Preverimo, ali SELinux blokira še kaj drugega kot tisto, do česar dostopa naš neznani demon.

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 preprečuje Nginxu pisanje podatkov v vtičnico UNIX, ki jo uporablja Gunicorn. Običajno se v takih primerih politike začnejo spreminjati, vendar so pred nami drugi izzivi. Prav tako lahko spremenite nastavitve domene iz domene omejitev v domeno dovoljenj. Zdaj premaknimo httpd_t v domeno dovoljenj. To bo Nginxu omogočilo potreben dostop in lahko nadaljujemo z nadaljnjim odpravljanjem napak.

sudo semanage permissive -a httpd_t

Torej, ko vam je uspelo zaščititi SELinux (resnično ne bi smeli pustiti projekta SELinux v omejenem načinu) in so domene dovoljenj naložene, morate ugotoviti, kaj točno je treba označiti kot gunicorn_exec_t, da bo vse delovalo pravilno ponovno. Poskusimo obiskati spletno mesto in si ogledati nova sporočila o omejitvah dostopa.

sudo ausearch -m AVC -c gunicorn

Videli boste veliko sporočil, ki vsebujejo 'comm="gunicorn"', ki izvajajo različne stvari v datotekah v /srv/djangoapp, zato je to očitno eden od ukazov, ki ga je vredno označiti.

Toda poleg tega se prikaže sporočilo, kot je to:

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

Če pogledate status storitve gunicorn ali zaženete ukaz ps, ne boste videli nobenih tekočih procesov. Videti je, da gunicorn poskuša dostopati do tolmača Python v našem okolju virtualenv, po možnosti za zagon delovnih skriptov. Označimo ti dve izvršljivi datoteki in preverimo, ali lahko odpremo našo testno stran Django.

chcon -t gunicorn_exec_t /srv/djangoapp/django/bin/gunicorn /srv/djangoapp/django/bin/python3.6

Storitev gunicorn bo treba znova zagnati, preden bo mogoče izbrati novo oznako. Takoj ga lahko znova zaženete ali zaustavite storitev in pustite, da jo vtičnica zažene, ko odprete spletno mesto v brskalniku. Z ps preverite, ali so procesi prejeli pravilne oznake.

ps -efZ | grep gunicorn

Ne pozabite pozneje ustvariti običajnega pravilnika SELinux!

Če zdaj pogledate sporočila AVC, zadnje sporočilo vsebuje permissive=1 za vse, kar je povezano z aplikacijo, in permissive=0 za preostali sistem. Če razumete, kakšen dostop potrebuje prava aplikacija, lahko hitro najdete najboljši način za reševanje takšnih težav. Toda do takrat je najbolje, da ohranite sistem varen in pridobite jasno, uporabno revizijo projekta Django.

sudo ausearch -m AVC

Zgodilo se!

Pojavil se je delujoč projekt Django s sprednjim delom, ki temelji na Nginx in Gunicorn WSGI. Konfigurirali smo Python 3 in PostgreSQL 10 iz repozitorijev RHEL 8 Beta. Zdaj se lahko premaknete naprej in ustvarite (ali preprosto uvedete) aplikacije Django ali raziščete druga razpoložljiva orodja v RHEL 8 Beta za avtomatizacijo postopka konfiguracije, izboljšanje zmogljivosti ali celo shranjevanje te konfiguracije.

Vir: www.habr.com

Dodaj komentar