Практикум RHEL 8 Beta: Създаване на работещи уеб приложения

RHEL 8 Beta предлага на множество нови възможности за разработчиците, изброяването на които би отнело страници. Въпреки това, изучаването на новото винаги е по-добре на практика, затова по-долу предлагаме да преминем през практическото изграждане на инфраструктура за приложения на базата на Red Hat Enterprise Linux 8 Beta.

Практикум RHEL 8 Beta: Създаване на работещи уеб приложения

Ще вземем за основа Python, популярен сред разработчиците програмен език, комбинацията от Django и PostgreSQL, доста разпространена за изграждане на приложения, и ще конфигурираме RHEL 8 Beta да работи с тях. След това ще добавим още няколко (несекретни) съставки.

Тестовата среда ще се променя, защото е интересно да изследваме възможностите за автоматизация, работа с контейнери и опити с среди с множество сървъри. За начало на работа с новия проект може да започнем със създаването на малък прост прототип на ръка – по този начин можем да видим какво точно трябва да се случи и как се извършва взаимодействието, а след това да преминем към автоматизация и изграждане на по-сложни конфигурации. Днес ще говорим за създаването на такъв прототип.

Нека започнем с разгръщането на образа на виртуална машина RHEL 8 Beta. Може да инсталирате виртуална машина от нулата или да използвате гостен образ KVM, достъпен с абонамента за Beta. При използване на гостен образ ще е необходимо да настроите виртуален CD, който ще съдържа метаданни и потребителски данни за облачна инициализация (cloud-init). Няма нужда от специални настройки на структурата на диска или наличните пакети, всяка конфигурация е подходяща.

Нека разгледаме целия процес по-подробно.

Инсталация на Django

С най-новата версия на Django ще ни е необходимо виртуално обкръжение (virtualenv) с Python 3.5 или по-нова версия. В бележките към Beta може да се види, че Python 3.6 е наличен, нека проверим дали е така:

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

Red Hat активно използва Python като системен инструмент в RHEL, така че защо получаваме такъв резултат?

Факт е, че много разработчици, които използват Python, все още обмислят преминаването от Python 2 на Python 2, при което самият Python 3 е в активна разработка и постоянно се появяват нови версии. Затова, за да отговорят на нуждата от стабилни системни инструменти, и в същото време да предоставят на потребителите достъп до различни нови версии на Python, системният Python беше прехвърлен в нов пакет и бе осигурена възможност за инсталиране както на Python 2.7, така и на 3.6. По-подробна информация за промените и защо е било направено това може да се намери в публикацията в блога на Лангдън Уайт (Langdon White).

И така, за да получите работещ Python, трябва да инсталирате само два пакета, като python3-pip ще се инсталира като зависимост.

sudo yum install python36 python3-virtualenv

Защо не трябва да се използват директни обаждания към модула, както предлага Лангдън, и не трябва ли да инсталирате pip3? Империите за автоматизация показват, че за работа с Ansible е необходим инсталиран pip, тъй като модулът pip не поддържа виртуални среди (virtualenvs) с кастомизирано изпълним файл pip.

Разполагайки с работещ интерпретатор python3, можете да продължите процеса на инсталиране на Django и да получите работеща система заедно с другите наши компоненти. В мрежата можете да намерите множество варианти за реализация. Тук е представена една версия, но потребителите могат да използват своите собствени процеси.

Версиите на PostgreSQL и Nginx, налични в RHEL 8 по подразбиране, ще инсталираме с помощта на Yum.

sudo yum install nginx postgresql-server

За PostgreSQL ще е необходим psycopg2, но трябва да бъде наличен само в средата на virtualenv, затова ще го инсталираме с pip3 заедно с Django и Gunicorn. Но първо трябва да настроим virtualenv.

По въпроса за правилния избор на място за инсталиране на Django проекти винаги се водят много спорове, но когато възникнат съмнения, винаги може да се обърнете към стандарта на Linux Filesystem Hierarchy Standard. В частност, в FHS се казва, че /srv се използва за: "съхраняване на данни, специфични за конкретен хост - данни, предоставяни от системата, например данни и скриптове от уеб сървъри, данни, съхранявани на FTP сървъри, както и репозитории на системи за контрол на версиите (появили се в FHS-2.3 през 2004 година)".

Това е точно нашият случай, затова слагаме всичко необходимо в /srv, собственик на което е нашият потребител на приложението (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

Настройването на PostgreSQL и Django не създава трудности: създаваме база данни, създаваме потребител, настройваме разрешения. Има един момент, който трябва да помним при първоначалната инсталация на PostgreSQL – скриптът postgresql-setup, който идва с пакета postgresql-server. Този скрипт помага за изпълнение на основни задачи, свързани с администрирането на клъстера от бази данни, като инициализацията на клъстера или процеса на обновление. За настройка на нов екземпляр PostgreSQL в системата RHEL трябва да изпълним командата:

sudo /usr/bin/postgresql-setup -initdb

След това можем да стартираме PostgreSQL с помощта на systemd, да създадем база данни и да настроим проекта в Django. Не забравяйте да рестартирате PostgreSQL след промени в конфигурационния файл за удостоверяване на клиента (обикновено pg_hba.conf), за да настроите съхранението на паролата за потребителя на приложението. Ако се сблъскате с други трудности, уверете се, че настройките за IPv4 и IPv6 са променени в файла 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

В файла /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

В файла /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 }}',
   }
}

След конфигурирането на файла settings.py в проекта и настройката на конфигурацията на базата данни, можете да стартирате сървъра за разработки, за да се уверите, че всичко работи. След стартиране на сървъра за разработки е добре да създадете потребител admin с цел да тествате връзката с базата данни.

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

WSGI? Какво?

Сървърът за разработки е полезен при тестване, но за да стартирате приложението, трябва да настроите съответния сървър и прокси за Web Server Gateway Interface (WSGI). Има няколко разпространени конфигурации, например Apache HTTPD с uWSGI или Nginx с Gunicorn.

Задачата на Web Server Gateway Interface е да пренасочва заявки от веб-сервера к уеб фреймворка на Python. WSGI е наследство от ужасното минало, когато механизмите CGI бяха на мода, и днес WSGI всъщност е стандарт, независимо от използвания уеб сървър или Python фреймворк. Но въпреки широкото си разпространение, все пак съществуват множество нюанси при работа с тези фреймворкове и множество възможности за избор. В този случай ще опитаме да настроим взаимодействието между Gunicorn и Nginx чрез сокет.

Тъй като и двете компоненти са инсталирани на един и същ сървър, ще опитаме да използваме вместо мрежов сокет сокет UNIX. Понеже за комуникации е нужен сокет, ще направим още една стъпка и ще настроим активация на сокета за Gunicorn чрез systemd.

Процесът на създаване на услуги, активирани от сокети (socket activated services), е доста прост. Първо, създава се unit файл, който съдържа директива ListenStream, посочваща точката, в която ще бъде създаден сокет UNIX, след това — unit файл за услугата, в която директивата Requires ще указва на unit файла на сокета. След това в unit файла на услугата остава само да се извика Gunicorn от виртуалната среда и да се създаде WSGI привръзка за сокета UNIX и приложението Django.

Ето няколко примера за unit файлове, които можете да вземете за основа. Първо, настройваме сокета.

[Unit]
Description=Gunicorn WSGI socket

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

[Install]
WantedBy=sockets.target

Сега е необходимо да настроим демона 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

За Nginx е достатъчно просто да създадете конфигурационни файлове за прокси и да настроите директория за статично съдържание, ако я използвате. В RHEL конфигурационните файлове на Nginx се намират в \/etc\/nginx\/conf.d. Можете да копирате следния пример в файла \/etc\/nginx\/conf.d\/default.conf и да стартирате услугата. Уверете се, че сте посочили server_name в съответствие с името на вашия хост.

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

Стартирайте сокета на Gunicorn и Nginx с помощта на systemd и можете да преминете към тестове.

Грешка Bad Gateway?

Ако въведете адрес в браузъра, по-вероятно, ще получите грешка 502 Bad Gateway. Тя може да бъде причинена от неправилно настроени разрешения за UNIX сокета или по-сложни проблеми, свързани с управлението на достъпа в SELinux.

В дневника за грешки на nginx може да срещнете ред подобен на:

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"

Ако тествате Gunicorn директно, ще получите празен отговор.

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

Нека да разберем защо това се случва. Ако отворите дневника, вероятно ще видите, че проблемът е свързан с SELinux. Понеже имаме стартиран демон, за който не е създадена собствена политика, той се обозначава като init_t. Нека тестваме тази теория в практика.

sudo setenforce 0

Всичко това може да предизвика критики и сълзи, но това е просто отстраняване на прото-тип. Ще изключим проверката, само за да се уверим, че проблемът наистина е в това, след което ще върнем всичко на мястото му.

Ако обновите страницата в браузъра или рестартирате нашата команда curl, можете да видите тестовата страница на Django.

И така, след като се уверихме, че всичко работи и повече няма проблеми с разрешенията, отново включваме SELinux.

sudo setenforce 1

Тук няма да се обсъжда audit2allow и създаването на политики на база известия с помощта на sepolgen, тъй като в момента няма реално приложение Django, следователно няма да имаме пълна карта на това за какво Gunicorn може да поиска достъп и какво трябва да бъде забранено. Затова е необходимо да се запази работата на SELinux за защита на системата, и в същото време да се разреши на приложението да стартира и да оставя съобщения в дневника за одит, така че след това да можем да създадем реална политика.

Определяне на разрешените домейни (permissive domains)

За разрешените домейни в SELinux не всички са чували, но в тях няма нищо ново. Много дори са работили с тях, без да знаят. Когато се създаде политика на базата на съобщения за одит, създадената политика представлява разрешен домейн. Нека опитаме да създадем най-простата разрешителна политика.

За да създадете конкретен разрешен домейн за Gunicorn, е необходима определена политика, както и трябва да маркирате съответните файлове. Освен това, са необходими инструменти за компилиране на нови политики.

sudo yum install selinux-policy-devel

Механизмът на разрешените домейни е отличен инструмент за идентифициране на проблеми, особено когато става въпрос за персонализирано приложение или приложения, които идват без вече създадени политики. В този случай политиката за разрешен домейн за Gunicorn ще бъде максимално проста – ще декларираме основния тип (gunicorn_t), ще обявим типа, който ще използваме за маркиране на няколко изпълними файла (gunicorn_exec_t), и след това ще настроим преход (transition) за system, за да маркираме правилно стартираните процеси. Последният ред задава политиката като разрешена по подразбиране по време на нейното зареждане.

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;

Можете да компилирате този файл с политики и да го добавите в системата.

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

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

Нека проверим дали SELinux блокира нещо друго, освен това, до което се обръща нашият неизвестен демон.

sudo ausearch -m AVC

type=AVC msg=audit(1545315977.237:1273): avc: denied { write } за 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 не позволява на Nginx да записва данни в UNIX сокета, използван от Gunicorn. Обикновено в такива случаи започват да променят политиките, но пред нас стои и друга задача. Можете също така да промените настройките на домейна, превръщайки го от домейн на ограничения в домейн на разрешения. Сега ще преместим httpd_t в домейн на разрешения. Това ще осигури необходимия достъп на Nginx, и ще можем да продължим с отстраняването на грешки.

sudo semanage permissive -a httpd_t

И така, когато успеете да запазите защитата на SELinux (всъщност не трябва да оставяте проекта с SELinux в режим на ограничения) и домейните на разрешения се зареждат, е необходимо да разберем какво точно трябва да маркираме като gunicorn_exec_t, за да може всичко отново да заработи коректно. Нека се опитаме да посетим уебсайта, за да видим нови съобщения за ограничения в достъпа.

sudo ausearch -m AVC -c gunicorn

Можете увидеть множество сообщений, содержащих ‘comm=«gunicorn»’, которые выполняют различные действия над файлами в /srv/djangoapp, поэтому это явно одна из команд, которую стоит отметить.

Но помимо этого, появляется сообщение следующего вида:

type=AVC msg=audit(1545320700.070:1542): avc: denied { execute } для 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

Если посмотреть статус сервиса gunicorn или выполнить команду ps, то не будет видно запущенных процессов. Похоже, что gunicorn пытается обратиться к интерпретатору Python в нашем окружении virtualenv, возможно, для запуска рабочих скриптов (workers). Поэтому сейчас пометим эти два исполняемых файла и проверим, получится ли открыть нашу тестовую страницу Django.

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

Потребуется перезапустить сервис gunicorn, чтобы применить новую метку. Его можно перезапустить сразу или остановить сервис и дать сокету запуститься при открытии сайта в браузере. Убедитесь, что процессы получили нужные метки, используя ps.

ps -efZ | grep gunicorn

Не забудьте потом создать правильную политику SELinux!

Если посмотреть сообщения AVC сейчас, то последнее сообщение содержит permissive=1 для всего, что относится к приложению, и permissive=0 для всей остальной системы. Если понять, какой именно доступ необходим реальному приложению, можно быстрее найти оптимальный способ решения подобных проблем. Но до тех пор лучше, чтобы система была защищена и получить понятный и пригодный к использованию аудит по проекту Django.

sudo ausearch -m AVC

Става!

У нас есть работающий проект Django с фронтендом на Nginx и Gunicorn WSGI. Мы настроили Python 3 и PostgreSQL 10 из репозиториев RHEL 8 Beta. Теперь можно двигаться дальше и создавать (или просто развертывать) приложения Django или изучать другие доступные инструменты в RHEL 8 Beta для автоматизации процесса настройки, повышения производительности или даже контейнеризации этой конфигурации.

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster