Практикум RHEL 8 Beta: Збираємо працюючі веб-додатки

RHEL 8 Beta пропонує розробникам багато нових можливостей, перелік яких може зайняти сторінки, проте вивчати нове завжди краще на практиці, тому нижче пропонуємо пройти практикум реального створення інфраструктури додатків на базі Red Hat Enterprise Linux 8 Beta.

Практикум RHEL 8 Beta: Збираємо працюючі веб-додатки

За основу візьмемо Python, популярну серед розробників мову програмування, комбінацію Django і PostgreSQL, досить поширену зв'язку для створення додатків, і налаштуємо RHEL 8 Beta для роботи з ними. Потім додамо ще кілька (несекретних) інгредієнтів.

Тестове оточення змінюватиметься, адже цікаво вивчити можливості автоматизації, роботу з контейнерами та спробувати середовища з кількома серверами. Для початку роботи з новим проектом можна почати зі створення невеликого простого прототипу вручну – таким чином можна побачити, що саме має відбутися і як здійснюється взаємодія, а потім перейти до автоматизації та створення більш складних конфігурацій. Сьогодні розповідь про створення такого прототипу.

Розпочнемо з розгортання образу віртуальної машини RHEL 8 Beta VM. Можна встановити віртуальну машину з нуля або використовувати гостьовий образ 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 } 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 не дає 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 } 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

Якщо подивитися статус сервісу 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

Додати коментар або відгук