Практыкум 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

Дадаць каментар