RHEL 8 Beta-Workshop: Erstellen funktionierender Webanwendungen

RHEL 8 Beta bietet Entwicklern viele neue Funktionen, deren Auflistung mehrere Seiten in Anspruch nehmen könnte. In der Praxis ist es jedoch immer besser, Neues zu lernen. Deshalb bieten wir im Folgenden einen Workshop zum tatsächlichen Erstellen einer Anwendungsinfrastruktur auf Basis von Red Hat Enterprise Linux 8 Beta an.

RHEL 8 Beta-Workshop: Erstellen funktionierender Webanwendungen

Nehmen wir als Grundlage Python, eine bei Entwicklern beliebte Programmiersprache, eine Kombination aus Django und PostgreSQL, einer recht verbreiteten Kombination zum Erstellen von Anwendungen, und konfigurieren wir RHEL 8 Beta für die Arbeit mit ihnen. Dann fügen wir noch ein paar weitere (nicht klassifizierte) Zutaten hinzu.

Die Testumgebung wird sich ändern, da es interessant ist, die Möglichkeiten der Automatisierung zu erkunden, mit Containern zu arbeiten und Umgebungen mit mehreren Servern auszuprobieren. Um mit einem neuen Projekt zu beginnen, können Sie zunächst einen kleinen, einfachen Prototyp von Hand erstellen, damit Sie genau sehen können, was passieren muss und wie es interagiert, und dann mit der Automatisierung und der Erstellung komplexerer Konfigurationen fortfahren. Heute sprechen wir über die Erstellung eines solchen Prototyps.

Beginnen wir mit der Bereitstellung des RHEL 8 Beta VM-Images. Sie können eine virtuelle Maschine von Grund auf installieren oder das mit Ihrem Beta-Abonnement verfügbare KVM-Gast-Image verwenden. Wenn Sie ein Gast-Image verwenden, müssen Sie eine virtuelle CD konfigurieren, die Metadaten und Benutzerdaten für die Cloud-Initialisierung (cloud-init) enthält. Sie müssen nichts Besonderes an der Festplattenstruktur oder den verfügbaren Paketen vornehmen; jede Konfiguration reicht aus.

Schauen wir uns den gesamten Prozess genauer an.

Django installieren

Mit der neuesten Version von Django benötigen Sie eine virtuelle Umgebung (virtualenv) mit Python 3.5 oder höher. In den Beta-Notizen können Sie sehen, dass Python 3.6 verfügbar ist. Schauen wir uns an, ob dies tatsächlich der Fall ist:

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

Red Hat nutzt Python aktiv als System-Toolkit in RHEL. Warum kommt es zu diesem Ergebnis?

Tatsache ist, dass viele Python-Entwickler immer noch über den Übergang von Python 2 zu Python 2 nachdenken, während sich Python 3 selbst in der aktiven Entwicklung befindet und immer mehr neue Versionen erscheinen. Um den Bedarf an stabilen Systemtools zu decken und Benutzern gleichzeitig Zugriff auf verschiedene neue Versionen von Python zu bieten, wurde System-Python daher in ein neues Paket verschoben und bietet die Möglichkeit, sowohl Python 2.7 als auch 3.6 zu installieren. Weitere Informationen zu den Änderungen und warum sie vorgenommen wurden, finden Sie in der Veröffentlichung in Langdon Whites Blog (Langdon White).

Um Python zum Laufen zu bringen, müssen Sie also nur zwei Pakete installieren, wobei python3-pip als Abhängigkeit enthalten ist.

sudo yum install python36 python3-virtualenv

Warum nicht direkte Modulaufrufe verwenden, wie Langdon vorschlägt, und pip3 installieren? Angesichts der bevorstehenden Automatisierung ist bekannt, dass für die Ausführung von Ansible pip installiert sein muss, da das pip-Modul keine virtuellen Umgebungen mit einer benutzerdefinierten ausführbaren pip-Datei unterstützt.

Wenn Ihnen ein funktionierender Python3-Interpreter zur Verfügung steht, können Sie mit dem Django-Installationsprozess fortfahren und zusammen mit unseren anderen Komponenten über ein funktionierendes System verfügen. Im Internet stehen zahlreiche Umsetzungsmöglichkeiten zur Verfügung. Hier wird eine Version vorgestellt, aber Benutzer können ihre eigenen Prozesse verwenden.

Wir werden die in RHEL 8 verfügbaren PostgreSQL- und Nginx-Versionen standardmäßig mit Yum installieren.

sudo yum install nginx postgresql-server

PostgreSQL erfordert psycopg2, aber es muss nur in einer Virtualenv-Umgebung verfügbar sein, daher werden wir es mit pip3 zusammen mit Django und Gunicorn installieren. Aber zuerst müssen wir Virtualenv einrichten.

Über die Wahl des richtigen Ortes für die Installation von Django-Projekten wird immer viel diskutiert, aber im Zweifelsfall können Sie sich jederzeit an den Linux Filesystem Hierarchy Standard wenden. Konkret sagt die FHS, dass /srv verwendet wird, um „hostspezifische Daten zu speichern – Daten, die das System erzeugt, wie Webserverdaten und -skripte, auf FTP-Servern gespeicherte Daten und Steuersystem-Repositorys.“ Versionen (erscheint in FHS -2.3 im Jahr 2004).

Das ist genau unser Fall, also legen wir alles, was wir brauchen, in /srv ab, das unserem Anwendungsbenutzer (Cloud-Benutzer) gehört.

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

Das Einrichten von PostgreSQL und Django ist einfach: Erstellen Sie eine Datenbank, erstellen Sie einen Benutzer, konfigurieren Sie Berechtigungen. Bei der Erstinstallation von PostgreSQL sollten Sie Folgendes beachten: Das Postgresql-Setup-Skript, das mit dem Postgresql-Server-Paket installiert wird. Dieses Skript unterstützt Sie bei der Ausführung grundlegender Aufgaben im Zusammenhang mit der Datenbank-Cluster-Verwaltung, beispielsweise der Cluster-Initialisierung oder dem Upgrade-Prozess. Um eine neue PostgreSQL-Instanz auf einem RHEL-System zu konfigurieren, müssen wir den Befehl ausführen:

sudo /usr/bin/postgresql-setup -initdb

Anschließend können Sie PostgreSQL mit systemd starten, eine Datenbank erstellen und ein Projekt in Django einrichten. Denken Sie daran, PostgreSQL neu zu starten, nachdem Sie Änderungen an der Client-Authentifizierungskonfigurationsdatei (normalerweise pg_hba.conf) vorgenommen haben, um die Passwortspeicherung für den Anwendungsbenutzer zu konfigurieren. Wenn Sie auf andere Schwierigkeiten stoßen, stellen Sie sicher, dass Sie die IPv4- und IPv6-Einstellungen in der Datei pg_hba.conf ändern.

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

In der Datei /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

In der Datei /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 }}',
   }
}

Nachdem Sie die Datei „settings.py“ im Projekt konfiguriert und die Datenbankkonfiguration eingerichtet haben, können Sie den Entwicklungsserver starten, um sicherzustellen, dass alles funktioniert. Nach dem Start des Entwicklungsservers empfiehlt es sich, einen Admin-Benutzer anzulegen, um die Verbindung zur Datenbank zu testen.

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

WSGI? Wai?

Der Entwicklungsserver ist zum Testen nützlich, aber um die Anwendung auszuführen, müssen Sie den entsprechenden Server und Proxy für das Web Server Gateway Interface (WSGI) konfigurieren. Es gibt mehrere gängige Kombinationen, zum Beispiel Apache HTTPD mit uWSGI oder Nginx mit Gunicorn.

Die Aufgabe des Web Server Gateway Interface besteht darin, Anfragen vom Webserver an das Python-Webframework weiterzuleiten. WSGI ist ein Relikt aus der schrecklichen Vergangenheit, als es CGI-Engines gab, und heute ist WSGI der De-facto-Standard, unabhängig vom verwendeten Webserver oder Python-Framework. Aber trotz ihrer weiten Verbreitung gibt es bei der Arbeit mit diesen Frameworks immer noch viele Nuancen und viele Möglichkeiten. In diesem Fall werden wir versuchen, eine Interaktion zwischen Gunicorn und Nginx über einen Socket herzustellen.

Da beide Komponenten auf demselben Server installiert sind, versuchen wir, einen UNIX-Socket anstelle eines Netzwerk-Sockets zu verwenden. Da für die Kommunikation in jedem Fall ein Socket erforderlich ist, versuchen wir noch einen Schritt weiterzugehen und die Socket-Aktivierung für Gunicorn über systemd zu konfigurieren.

Der Prozess zum Erstellen von Socket-aktivierten Diensten ist recht einfach. Zuerst wird eine Unit-Datei erstellt, die eine ListenStream-Direktive enthält, die auf den Punkt zeigt, an dem der UNIX-Socket erstellt wird, und dann eine Unit-Datei für den Dienst, in der die Requires-Direktive auf die Socket-Unit-Datei verweist. Dann müssen Sie in der Service-Unit-Datei nur noch Gunicorn aus der virtuellen Umgebung aufrufen und eine WSGI-Bindung für den UNIX-Socket und die Django-Anwendung erstellen.

Hier finden Sie einige Beispiele für Unit-Dateien, die Sie als Grundlage verwenden können. Zuerst richten wir den Sockel ein.

[Unit]
Description=Gunicorn WSGI socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target

Jetzt müssen Sie den Gunicorn-Daemon konfigurieren.

[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

Für Nginx ist es einfach, Proxy-Konfigurationsdateien zu erstellen und ein Verzeichnis zum Speichern statischer Inhalte einzurichten, falls Sie eines verwenden. In RHEL befinden sich Nginx-Konfigurationsdateien in /etc/nginx/conf.d. Sie können das folgende Beispiel in die Datei /etc/nginx/conf.d/default.conf kopieren und den Dienst starten. Stellen Sie sicher, dass der Servername mit Ihrem Hostnamen übereinstimmt.

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

Starten Sie den Gunicorn-Socket und Nginx mit systemd und schon können Sie mit dem Testen beginnen.

Fehler „Bad Gateway“?

Wenn Sie die Adresse in Ihren Browser eingeben, erhalten Sie höchstwahrscheinlich den Fehler 502 Bad Gateway. Dies kann durch falsch konfigurierte UNIX-Socket-Berechtigungen oder durch komplexere Probleme im Zusammenhang mit der Zugriffskontrolle in SELinux verursacht werden.

Im Nginx-Fehlerprotokoll können Sie eine Zeile wie diese sehen:

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"

Wenn wir Gunicorn direkt testen, erhalten wir eine leere Antwort.

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

Lassen Sie uns herausfinden, warum das passiert. Wenn Sie das Protokoll öffnen, werden Sie höchstwahrscheinlich feststellen, dass das Problem mit SELinux zusammenhängt. Da wir einen Daemon ausführen, für den keine Richtlinie erstellt wurde, ist er als init_t gekennzeichnet. Lassen Sie uns diese Theorie in der Praxis testen.

sudo setenforce 0

All dies mag Kritik und Tränen des Blutes hervorrufen, aber hier geht es nur um das Debuggen des Prototyps. Deaktivieren wir die Prüfung, um sicherzustellen, dass dies das Problem ist. Anschließend bringen wir alles wieder an seinen Platz zurück.

Durch Aktualisieren der Seite im Browser oder erneutes Ausführen unseres Curl-Befehls können Sie die Django-Testseite sehen.

Nachdem wir sichergestellt haben, dass alles funktioniert und keine Berechtigungsprobleme mehr auftreten, aktivieren wir SELinux erneut.

sudo setenforce 1

Ich werde hier nicht über audit2allow oder die Erstellung alarmbasierter Richtlinien mit Sepolgen sprechen, da es derzeit keine tatsächliche Django-Anwendung gibt und es daher keine vollständige Übersicht darüber gibt, worauf Gunicorn möglicherweise zugreifen möchte und worauf es den Zugriff verweigern sollte. Daher ist es notwendig, SELinux zum Schutz des Systems am Laufen zu halten und gleichzeitig die Ausführung der Anwendung zuzulassen und Meldungen im Audit-Log zu hinterlassen, damit daraus dann die eigentliche Richtlinie erstellt werden kann.

Angabe zulässiger Domänen

Nicht jeder hat von erlaubten Domänen in SELinux gehört, aber sie sind nichts Neues. Viele arbeiteten sogar mit ihnen, ohne es zu merken. Wenn eine Richtlinie basierend auf Prüfmeldungen erstellt wird, stellt die erstellte Richtlinie die aufgelöste Domäne dar. Versuchen wir, eine einfache Genehmigungsrichtlinie zu erstellen.

Um eine bestimmte zulässige Domäne für Gunicorn zu erstellen, benötigen Sie eine Richtlinie und müssen außerdem die entsprechenden Dateien markieren. Darüber hinaus werden Werkzeuge benötigt, um neue Richtlinien zusammenzustellen.

sudo yum install selinux-policy-devel

Der Mechanismus „Zugelassene Domänen“ ist ein hervorragendes Tool zur Identifizierung von Problemen, insbesondere wenn es sich um eine benutzerdefinierte Anwendung oder Anwendungen handelt, die ohne bereits erstellte Richtlinien ausgeliefert werden. In diesem Fall ist die zulässige Domänenrichtlinie für Gunicorn so einfach wie möglich: Deklarieren Sie einen Haupttyp (gunicorn_t), deklarieren Sie einen Typ, den wir zum Markieren mehrerer ausführbarer Dateien verwenden (gunicorn_exec_t), und richten Sie dann einen Übergang ein, damit das System die korrekte Markierung vornimmt laufende Prozesse. Die letzte Zeile legt fest, dass die Richtlinie zum Zeitpunkt des Ladens standardmäßig aktiviert ist.

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;

Sie können diese Richtliniendatei kompilieren und Ihrem System hinzufügen.

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

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

Lassen Sie uns prüfen, ob SELinux etwas anderes als das blockiert, auf das unser unbekannter Daemon zugreift.

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 verhindert, dass Nginx Daten in den von Gunicorn verwendeten UNIX-Socket schreibt. Typischerweise beginnen in solchen Fällen Richtlinien zu ändern, es stehen jedoch noch andere Herausforderungen bevor. Sie können die Domäneneinstellungen auch von einer Einschränkungsdomäne in eine Berechtigungsdomäne ändern. Verschieben wir nun httpd_t in die Berechtigungsdomäne. Dadurch erhält Nginx den nötigen Zugriff und wir können mit der weiteren Debugging-Arbeit fortfahren.

sudo semanage permissive -a httpd_t

Wenn Sie es also geschafft haben, SELinux geschützt zu halten (Sie sollten ein SELinux-Projekt wirklich nicht im eingeschränkten Modus belassen) und die Berechtigungsdomänen geladen sind, müssen Sie herausfinden, was genau als gunicorn_exec_t markiert werden muss, damit alles ordnungsgemäß funktioniert wieder. Versuchen wir, die Website zu besuchen, um neue Meldungen zu Zugangsbeschränkungen zu sehen.

sudo ausearch -m AVC -c gunicorn

Sie werden viele Meldungen sehen, die „comm="gunicorn"“ enthalten und verschiedene Dinge mit Dateien in /srv/djangoapp tun, daher ist dies offensichtlich einer der Befehle, die es wert sind, markiert zu werden.

Aber zusätzlich erscheint eine Meldung wie diese:

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

Wenn Sie sich den Status des Gunicorn-Dienstes ansehen oder den Befehl ps ausführen, werden Sie keine laufenden Prozesse sehen. Es sieht so aus, als würde gunicorn versuchen, auf den Python-Interpreter in unserer Virtualenv-Umgebung zuzugreifen, möglicherweise um Worker-Skripte auszuführen. Markieren wir nun diese beiden ausführbaren Dateien und prüfen wir, ob wir unsere Django-Testseite öffnen können.

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

Der Gunicorn-Dienst muss neu gestartet werden, bevor das neue Tag ausgewählt werden kann. Sie können ihn sofort neu starten oder den Dienst stoppen und ihn vom Socket starten lassen, wenn Sie die Site im Browser öffnen. Überprüfen Sie mithilfe von ps, ob Prozesse die richtigen Bezeichnungen erhalten haben.

ps -efZ | grep gunicorn

Vergessen Sie nicht, später eine normale SELinux-Richtlinie zu erstellen!

Wenn Sie sich jetzt die AVC-Nachrichten ansehen, enthält die letzte Nachricht permissive=1 für alles, was mit der Anwendung zu tun hat, und permissive=0 für den Rest des Systems. Wenn Sie verstehen, welche Art von Zugriff eine echte Anwendung benötigt, können Sie schnell den besten Weg finden, solche Probleme zu lösen. Aber bis dahin ist es am besten, das System sicher zu halten und eine klare, brauchbare Prüfung des Django-Projekts zu erhalten.

sudo ausearch -m AVC

Es stellte sich heraus!

Es ist ein funktionierendes Django-Projekt mit einem Frontend auf Basis von Nginx und Gunicorn WSGI erschienen. Wir haben Python 3 und PostgreSQL 10 aus den RHEL 8 Beta-Repositorys konfiguriert. Jetzt können Sie Django-Anwendungen erstellen (oder einfach bereitstellen) oder andere verfügbare Tools in RHEL 8 Beta erkunden, um den Konfigurationsprozess zu automatisieren, die Leistung zu verbessern oder diese Konfiguration sogar zu containerisieren.

Source: habr.com

Kommentar hinzufügen