Praktikum RHEL 8 Beta: Vytváříme funkční webové aplikace

RHEL 8 Beta nabízí vývojářům mnoho nových příležitostí, jejichž výčet by mohl zabrat stránky, nicméně se nové věci vždy lépe učí v praxi. Proto níže nabízíme praktikum reálného vytvoření infrastruktury aplikací na základě Red Hat Enterprise Linux 8 Beta.

Praktikum RHEL 8 Beta: Vytváříme funkční webové aplikace

Jako základ vezmeme Python, oblíbený programovací jazyk mezi vývojáři, kombinaci Django a PostgreSQL, poměrně rozšířenou dvojici pro tvorbu aplikací, a nakonfigurujeme RHEL 8 Beta pro práci s nimi. Poté přidáme ještě pár (mimoslovních) ingrediencí.

Testovací prostředí se bude měnit, neboť je zajímavé prozkoumat možnosti automatizace, práci s kontejnery a vyzkoušet prostředí s několika servery. Pro zahájení práce na novém projektu můžeme začít vytvářením malého jednoduchého prototypu ručně – tímto způsobem uvidíme, co by mělo probíhat a jak se uskutečňuje interakce, a poté přejdeme k automatizaci a vytváření složitějších konfigurací. Dnes je řeč o vytvoření takového prototypu.

Začneme nasazením obrazu virtuálního stroje RHEL 8 Beta VM. Můžeme nainstalovat virtuální stroj od nuly, nebo použít hostitelský obraz KVM, který je dostupný se subskripcí na Beta. Při použití hostitelského obrazu bude potřeba nastavit virtuální CD, které bude obsahovat metadata a uživatelská data pro cloudovou inicializaci (cloud-init). Není potřeba nic zvláštního se strukturou disku nebo dostupnými balíčky dělat, vyhovuje jakákoliv konfigurace.

Podívejme se na celý proces podrobněji.

Instalace Django

S nejnovější verzí Django bude potřeba virtuální prostředí (virtualenv) s Pythonem 3.5 nebo novější verzí. V poznámkách k Betě můžeme vidět, že je dostupný Python 3.6, zkontrolujme, zda tomu tak skutečně je:

[cloud-user@8beta1 ~]$ python
-bash: python: příkaz nebyl nalezen
[cloud-user@8beta1 ~]$ python3
-bash: python3: příkaz nebyl nalezen

Red Hat aktivně používá Python jako systémový nástroj v RHEL, tak proč dostáváme takový výsledek?

Skutečnost je taková, že mnoho vývojářů, kteří používají Python, stále zvažuje přechod z Pythonu 2 na Python 2, přičemž samotný Python 3 je aktivně vyvíjen a neustále se objevují nové verze. Abychom vyhověli poptávce po stabilních systémových nástrojích a zároveň nabídli uživatelům přístup k různým novým verzím Pythonu, byl systémový Python přesunut do nového balíčku a byla zajištěna možnost instalace jak Pythonu 2.7, tak 3.6. Podrobnější informace o změnách a důvodech pro tento krok lze nalézt v blogu Langdona Whitea (Langdon White).

Takže pro získání funkčního Pythonu je třeba nainstalovat pouze dva balíčky, přičemž python3-pip se stáhne jako závislost.

sudo yum install python36 python3-virtualenv

Proč se vyhnout přímým voláním modulu, jak navrhuje Langdon, a nenainstalovat pip3? Vzhledem k blížící se automatizaci je známo, že pro provoz Ansible je třeba mít nainstalovaný pip, protože modul pip nepodporuje virtuální prostředí (virtualenvs) s vlastním spustitelným souborem pip.

S funkčním interpretem python3 lze pokračovat v procesu instalace Django a získat funkční systém spolu s našimi dalšími komponenty. Internet nabízí mnoho variant realizace. Zde je jedna verze, ale uživatelé mohou použít své vlastní postupy.

Verze PostgreSQL a Nginx dostupné ve výchozím nastavení RHEL 8 budou instalovány pomocí Yum.

sudo yum install nginx postgresql-server

Pro PostgreSQL bude potřeba psycopg2, ale mělo by být dostupné pouze v prostředí virtualenv, proto ho nainstalujeme pomocí pip3 spolu s Django a Gunicorn. Nejdříve musíme však nastavit virtualenv.

O správném výběru místa pro instalaci projektů Django se vždy vedou debaty, ale kdykoli vzniknou pochybnosti, je možné se obrátit na standard Linux Filesystem Hierarchy Standard. V FHS se konkrétně uvádí, že /srv se používá pro: „ukládání dat specifických pro konkrétní uzel – dat, která systém poskytuje, například dat a skriptů webových serverů, dat uložených na FTP serverech, a také repozitářů systémů řízení verzí (které se objevily v FHS-2.3 v roce 2004)“.

Tohoto se přesně týkáme, proto vše potřebné vkládáme do /srv, jehož vlastníkem je náš aplikativní uživatel (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

Nastavení PostgreSQL a Django není složité: vytvoříme databázi, vytvoříme uživatele, nastavíme oprávnění. Existuje jeden aspekt, na který je třeba pamatovat při původní instalaci PostgreSQL – skript postgresql-setup, který se instaluje společně s balíčkem postgresql-server. Tento skript pomáhá provádět základní úkoly spojené s administrací klastru databází, jako například inicializaci klastru nebo proces aktualizace. Pro nastavení nového instance PostgreSQL na systému RHEL musíme vykonat příkaz:

sudo /usr/bin/postgresql-setup -initdb

Poté lze spustit PostgreSQL pomocí systemd, vytvořit databázi a nakonfigurovat projekt v Django. Nezapomeňte restartovat PostgreSQL po změnách v konfiguračním souboru pro autentizaci klienta (obvykle pg_hba.conf) pro nastavení ukládání hesla pro aplikativního uživatele. Pokud se setkáte s jinými obtížemi, ujistěte se, že jsou změněny nastavení IPv4 a IPv6 v souboru 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 souboru /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 souboru /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 }}',
   }
}

Po nakonfigurování souboru settings.py v projektu a nastavení konfigurace databáze lze spustit vývojový server, aby se ověřilo, že vše funguje. Po spuštění vývojového serveru je dobré vytvořit uživatele admin pro testování připojení k databázi.

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

WSGI? Co to je?

Vývojový server je užitečný při testování, ale pro spuštění aplikace je třeba nakonfigurovat odpovídající server a proxy pro Web Server Gateway Interface (WSGI). Existuje několik běžných kombinací, například Apache HTTPD s uWSGI nebo Nginx s Gunicorn.

Úkolem Web Server Gateway Interface je přesměrovávat požadavky od webového serveru k webovému rámcovému Pythonu. WSGI je jakýsi pozůstatek hrozné minulosti, kdy byly v oběhu mechanismy CGI, a dnes je WSGI de facto standardem, nezávisle na používaném webovém serveru nebo Pythonovém rámce. I přes jeho široké rozšíření však existuje mnoho nuancí při práci s těmito rámci a mnoho možností výběru. V tomto případě se pokusíme navázat spolupráci mezi Gunicorn a Nginx prostřednictvím soketu.

Jelikož jsou oba tyto komponenty nainstalovány na jednom a tomtéž serveru, pokusíme se místo síťového soketu použít soket UNIX. Protože pro komunikaci je soket v každém případě potřeba, uděláme ještě jeden krok a nastavíme aktivaci soketu pro Gunicorn prostřednictvím systemd.

Proces vytváření služeb aktivovaných sokety (socket activated services) je poměrně jednoduchý. Nejdříve se vytvoří unit-file, který obsahuje direktivu ListenStream, uvádějící bod, ve kterém bude vznikat soket UNIX, a poté — unit-file pro službu, ve které bude direktiva Requires ukazovat na unit-file soketu. Poté v unit-file služby zbývá pouze spustit Gunicorn z virtuálního prostředí a vytvořit WSGI vazbu pro soket UNIX a aplikaci Django.

Zde je několik příkladů unit souborů, které lze použít jako základ. Nejprve nastavíme soket.

[Unit]
Description=Gunicorn WSGI socket

[Socket]
ListenStream=\/run\/gunicorn.sock

[Install]
WantedBy=sockets.target

Nyní je třeba nastavit démon 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

Pro Nginx stačí jednoduše vytvořit konfigurační soubory proxy a nastavit adresář pro ukládání statického obsahu, pokud jej používáte. V RHEL se konfigurační soubory Nginx nacházejí v /etc/nginx/conf.d. Můžete tam zkopírovat následující příklad do souboru /etc/nginx/conf.d/default.conf a spustit službu. Ujistěte se, že jste nastavili server_name podle názvu vašeho hostitele.

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

Spusťte soket Gunicorn a Nginx pomocí systemd a můžete začít s testováním.

Jaká je chyba Bad Gateway?

Pokud zadáte adresu do prohlížeče, pravděpodobně dostanete chybu 502 Bad Gateway. Ta může být způsobena nesprávně nastavenými oprávněními pro UNIX socket, nebo složitějšími problémy souvisejícími s řízením přístupu v SELinux.

V chybovém deníku nginx můžete vidět řádek podobného typu:

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"

Pokud testujeme Gunicorn přímo, dostaneme prázdnou odpověď.

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

Pojďme se podívat, proč se to děje. Pokud otevřeme deník, pravděpodobně zjistíme, že problém souvisí se SELinux. Protože máme spuštěný démon, pro který nebyla vytvořena vlastní politika, je označen jako init_t. Ověřme tuto teorii v praxi.

sudo setenforce 0

To všechno může vyvolat kritiku a slzy, ale je to jen debugování prototypu. Vypneme kontrolu jen proto, abychom se ujistili, že tento problém je skutečně v tom, a poté všechno vrátíme na svá místa.

Aktualizováním stránky v prohlížeči nebo restartováním našeho příkazu curl můžeme vidět testovací stránku Django.

Takže, ujistíme se, že vše funguje a už nejsou žádné problémy s oprávněními, znovu zapneme SELinux.

sudo setenforce 1

Nebudeme zde hovořit o audit2allow a vytváření politik na základě upozornění pomocí sepolgen, protože v tuto chvíli neexistuje skutečná aplikace Django, takže není ani kompletní obraz toho, k čemu může Gunicorn chtít přistupovat a jakému přístupu by mělo být zakázáno. Proto je důležité udržovat SELinux v chodu pro ochranu systému a zároveň povolit aplikaci se spouštět a zanechávat zprávy v auditním deníku, aby bylo možné na jejich základě vytvořit skutečnou politiku.

Označení povolených domén (permissive domains)

O povolených doménách v SELinux ne každý slyšel, ale není na nich nic nového. Mnozí s nimi dokonce pracovali, aniž by to sami tušili. Když se vytvoří politika na základě zpráv z auditu, obdržená politika představuje povolenou doménu. Pokusíme se vytvořit nejjednodušší povolovací politiku.

Chcete-li vytvořit konkrétní povolenou doménu pro Gunicorn, je potřeba určitá politika a označit příslušné soubory. Kromě toho jsou potřebné nástroje pro sestavení nových politik.

sudo yum install selinux-policy-devel

Mechanismus povolených domén je skvělý nástroj pro odhalování problémů, zejména pokud se jedná o vlastní aplikaci nebo aplikace, které přicházejí bez již vytvořených politik. V tomto případě bude politika povolené domény pro Gunicorn co nejjednodušší – vyhlásíme základní typ (gunicorn_t), vyhlásíme typ, který budeme používat k označení několika spustitelných souborů (gunicorn_exec_t) a poté nastavíme přechod (transition) pro system, abychom správně označili spuštěné procesy. Poslední řádek nastavuje politiku jako povolenou ve výchozím nastavení v okamžiku její načtení.

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;

Tento soubor politik lze zkompilovat a přidat do systému.

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

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

Zkontrolujme, zda SELinux neblokuje něco jiného kromě toho, na co se odkazuje náš neznámý démon.

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 nedovoluje Nginxu zapisovat data do UNIX socketu používaného Gunicornem. Obvykle v takových případech začínáme měnit politiky, ale před námi je ještě řada dalších úkolů. Můžeme také změnit nastavení domény, přičemž ji převedeme z domény omezení na doménu povolení. Nyní přesuneme httpd_t do domény povolení. To zajistí Nginxu potřebný přístup a budeme moci pokračovat v dalším ladění.

sudo semanage permissive -a httpd_t

Takže, když se podařilo zachovat ochranu SELinux (ve skutečnosti byste neměli nechávat projekt s SELinux v režimu omezení) a domény povolení se načítají, musíme zjistit, co přesně potřebujeme označit jako gunicorn_exec_t, aby vše opět fungovalo jak má. Zkusíme se připojit k webové stránce, abychom viděli nové zprávy o omezeních v přístupu.

sudo ausearch -m AVC -c gunicorn

Můžete vidět mnoho zpráv obsahujících ‘comm=„gunicorn“’, které provádějí různé akce nad soubory v /srv/djangoapp, takže je jasné, že to je jedna z příkazů, kterou by bylo dobré vyznačit.

Kromě toho se objevuje zpráva v takovémto formátu:

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

Pokud zkontrolujete stav služby gunicorn nebo provedete příkaz ps, neobjeví se žádné spuštěné procesy. Zdá se, že gunicorn se pokouší přistoupit k interpretu Python ve našem prostředí virtualenv, možná pro spuštění pracovních skriptů. Proto nyní vyznačíme tyto dva spustitelné soubory a zkontrolujeme, zda můžeme otevřít naši testovací stránku Django.

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

Bude nutné restartovat službu gunicorn, aby se mohla vybrat nová značka. Můžete ji restartovat okamžitě nebo zastavit službu a nechat socket, aby ji spustil při otevření webu v prohlížeči. Ujistěte se, že procesy dostaly požadované značky, pomocí ps.

ps -efZ | grep gunicorn

Nezapomeňte pak vytvořit normální politiku SELinux!

Pokud se teď podíváte na zprávy AVC, poslední zpráva obsahuje permissive=1 pro všechno, co se týká aplikace, a permissive=0 pro zbytek systému. Pokud pochopíte, jaký konkrétní přístup skutečná aplikace potřebuje, můžete rychleji najít optimální způsob, jak podobné problémy řešit. Do té doby je lepší, aby byla systém chráněn a abyste dostali srozumitelný a použitelní audit projektu Django.

sudo ausearch -m AVC

Uspělo to!

Objevil se fungující projekt Django s frontendem na Nginx a Gunicorn WSGI. Nastavili jsme Python 3 a PostgreSQL 10 z repozitářů RHEL 8 Beta. Nyní můžeme pokračovat a vytvářet (nebo jen nasazovat) aplikace Django nebo zkoumat další dostupné nástroje v RHEL 8 Beta pro automatizaci procesu nastavení, zvýšení výkonu nebo dokonce kontejnerizaci této konfigurace.

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster