Praktikum RHEL 8 Beta: Wir erstellen funktionierende Webanwendungen

RHEL 8 Beta bietet Entwicklern viele neue Möglichkeiten, deren Auflistung Seiten füllen könnte. Allerdings ist es immer besser, Neues praktisch zu erlernen. Daher schlagen wir vor, eine praktische Übung zur echten Erstellung einer Anwendungsinfrastruktur auf Basis von Red Hat Enterprise Linux 8 Beta durchzuführen.

Praktikum RHEL 8 Beta: Wir erstellen funktionierende Webanwendungen

Wir werden Python, eine bei Entwicklern beliebte Programmiersprache, und die Kombination aus Django und PostgreSQL, was ein recht verbreitetes Setup zur Erstellung von Anwendungen ist, als Grundlage nehmen und RHEL 8 Beta so konfigurieren, dass es mit diesen funktioniert. Danach fügen wir noch ein paar (nicht geheime) Zutaten hinzu.

Die Testumgebung wird sich ändern, denn es ist interessant, die Möglichkeiten der Automatisierung, den Umgang mit Containern zu erkunden und Umgebungen mit mehreren Servern auszuprobieren. Für den Start eines neuen Projekts kann man mit der manuellen Erstellung eines kleinen, einfachen Prototyps beginnen – so sieht man, was genau geschehen sollte und wie die Interaktion funktioniert, bevor man zur Automatisierung und zur Erstellung komplexerer Konfigurationen übergeht. Heute geht es um die Erstellung eines solchen Prototyps.

Wir beginnen mit dem Bereitstellen des RHEL 8 Beta VM-Images. Man kann die virtuelle Maschine von Grund auf neu installieren oder ein bereits vorhandenes KVM-Gast-Image verwenden, das mit dem Beta-Abonnement verfügbar ist. Bei der Verwendung des Gast-Images muss ein virtuelles CD-Laufwerk eingerichtet werden, das Metadaten und Benutzerdaten für die Cloud-Initialisierung (cloud-init) enthält. Es sind keine besonderen Veränderungen an der Struktur der Festplatte oder den verfügbaren Paketen erforderlich; jede Konfiguration ist ausreichend.

Lasst uns den gesamten Prozess im Detail betrachten.

Installation von Django

Für die neueste Version von Django wird eine virtuelle Umgebung (virtualenv) mit Python 3.5 oder einer neueren Version benötigt. In den Anmerkungen zu Beta ist zu sehen, dass Python 3.6 verfügbar ist. Lassen Sie uns überprüfen, ob das tatsächlich der Fall ist:

[cloud-user@8beta1 ~]$ python
-bash: python: Befehl nicht gefunden
[cloud-user@8beta1 ~]$ python3
-bash: python3: Befehl nicht gefunden

Red Hat verwendet Python aktiv als Systemwerkzeug in RHEL. Warum ergibt sich also dieses Ergebnis?

Viele Entwickler, die Python verwenden, ziehen immer noch in Betracht, von Python 2 auf Python 2 umzusteigen, während Python 3 aktiv weiterentwickelt wird und ständig neue Versionen erscheinen. Um den Bedarf an stabilen Systemwerkzeugen zu decken und gleichzeitig den Benutzern Zugang zu verschiedenen neuen Versionen von Python zu ermöglichen, wurde Python in ein neues Paket verschoben und die Installation sowohl von Python 2.7 als auch von 3.6 ermöglicht. Weitere Informationen zu den Änderungen und den Gründen dafür finden Sie in einem Beitrag im Blog von Langdon White (Langdon White).

Um also funktionierendes Python zu erhalten, sind nur zwei Pakete erforderlich, wobei python3-pip als Abhängigkeit mitinstalliert wird.

sudo yum install python36 python3-virtualenv

Warum man, wie Langdon vorschlägt, nicht direkt auf das Modul zugreifen und pip3 installieren sollte? Angesichts der bevorstehenden Automatisierung ist bekannt, dass für die Verwendung von Ansible pip installiert sein muss, da das pip-Modul keine virtuellen Umgebungen (virtualenvs) mit einer benutzerdefinierten pip-Ausführungsdatei unterstützt.

Mit einem funktionierenden python3-Interpreter kann der Installationsprozess für Django fortgesetzt werden, um ein funktionierendes System zusammen mit unseren anderen Komponenten zu erhalten. Im Internet gibt es viele Umsetzungsvarianten. Hier ist eine Version dargestellt, aber die Benutzer können ihre eigenen Prozesse verwenden.

Die in RHEL 8 standardmäßig verfügbaren Versionen von PostgreSQL und Nginx werden wir mit Yum installieren.

sudo yum install nginx postgresql-server

Für PostgreSQL ist psycopg2 erforderlich, es sollte jedoch nur in der Umgebung von virtualenv verfügbar sein, daher werden wir es zusammen mit Django und Gunicorn über pip3 installieren. Aber zuerst müssen wir virtualenv einrichten.

Es gibt viele Diskussionen darüber, wo Projekte von Django am besten installiert werden sollten, aber wenn Zweifel aufkommen, kann man immer auf den Linux Filesystem Hierarchy Standard zurückgreifen. Laut FHS wird /srv verwendet für: „das Speichern von daten, die spezifisch für einen bestimmten Knoten sind – Daten, die vom System bereitgestellt werden, wie z. B. Daten und Skripte von Webservern, Daten, die auf FTP-Servern gespeichert sind, sowie Repositories von Versionskontrollsystemen (die im FHS-2.3 im Jahr 2004 eingeführt wurden)“.

Das ist genau unser Fall, also legen wir alles Notwendige in /srv ab, dessen Eigentümer unser Anwendungsbenutzer (cloud-user) ist.

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

Die Einrichtung von PostgreSQL und Django ist unkompliziert: Wir erstellen eine Datenbank, legen einen Benutzer an und konfigurieren die Berechtigungen. Es gibt einen Punkt, den es bei der ursprünglichen Installation von PostgreSQL zu beachten gilt – das Skript postgresql-setup, das zusammen mit dem Paket postgresql-server installiert wird. Dieses Skript hilft bei der Durchführung grundlegender Aufgaben, die mit der Verwaltung eines Datenbankclusters verbunden sind, wie z.B. der Initialisierung des Clusters oder dem Upgrade-Prozess. Um eine neue PostgreSQL-Instanz auf einem RHEL-System einzurichten, müssen wir den folgenden Befehl ausführen:

sudo /usr/bin/postgresql-setup -initdb

Danach können wir PostgreSQL mit systemd starten, eine Datenbank erstellen und das Projekt in Django konfigurieren. Vergessen Sie nicht, PostgreSQL nach Änderungen an der Client-Authentifizierungsdatei (normalerweise pg_hba.conf) neu zu starten, um die Passwortspeicherung für den Anwendungsbenutzer zu konfigurieren. Wenn Sie auf andere Probleme stoßen, stellen Sie sicher, dass die Einstellungen für IPv4 und IPv6 in der Datei pg_hba.conf geändert wurden.

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 }}',
   }
}

Nach der Konfiguration der settings.py-Datei im Projekt und der Einrichtung der Datenbankkonfiguration können Sie den Entwicklungsserver starten, um sicherzustellen, dass alles funktioniert. Nach dem Start des Entwicklungsservers ist es ratsam, einen Admin-Benutzer zu erstellen, um die Verbindung zur Datenbank zu testen.

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

WSGI? Was ist das?

Der Entwicklungsserver ist hilfreich beim Testen, aber um die Anwendung bereitzustellen, muss ein geeigneter Server und Proxy für das Web Server Gateway Interface (WSGI) eingerichtet werden. Es gibt mehrere verbreitete Kombinationen, z.B. Apache HTTPD mit uWSGI oder Nginx mit Gunicorn.

Die Aufgabe des Web Server Gateway Interface besteht darin, Anfragen umzuleiten von Webserver. zum Python-Web-Framework. WSGI ist eine Art Erbe einer schrecklichen Vergangenheit, als CGI-Mechanismen im Einsatz waren, und heute ist WSGI praktisch der Standard, unabhängig vom verwendeten Webserver oder Python-Framework. Trotz seiner weit verbreiteten Nutzung gibt es jedoch viele Nuancen bei der Arbeit mit diesen Frameworks und zahlreiche Auswahlmöglichkeiten. In diesem Fall werden wir versuchen, die Interaktion zwischen Gunicorn und Nginx über einen Socket einzurichten.

Da beide Komponenten auf demselben Server installiert sind, versuchen wir, anstelle eines Netzwerksockets einen UNIX-Socket zu verwenden. Da für alle Kommunikationswege ein Socket erforderlich ist, wagen wir einen weiteren Schritt und konfigurieren die Aktivierung des Sockets für Gunicorn über systemd.

Der Prozess zur Erstellung von Socket-aktivierten Diensten ist ziemlich einfach. Zunächst wird eine Unit-Datei erstellt, die die Direktive ListenStream enthält, die auf den Punkt verweist, an dem der UNIX-Socket erstellt wird, gefolgt von einer Unit-Datei für den Dienst, in der die Direktive Requires auf die Unit-Datei des Sockets verweist. In der Unit-Datei des Dienstes bleibt dann nur noch, Gunicorn aus der virtuellen Umgebung aufzurufen und eine WSGI-Bindung für den UNIX-Socket und die Django-Anwendung zu erstellen.

Hier sind einige Beispiele für Unit-Dateien, die Sie als Grundlage verwenden können. Zuerst konfigurieren wir den Socket.

[Unit]
Description=Gunicorn WSGI socket

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

[Install]
WantedBy=sockets.target

Jetzt muss der Gunicorn-Daemon konfiguriert werden.

[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 genügt es, Konfigurationsdateien für den Proxy zu erstellen und das Verzeichnis für statische Inhalte einzurichten, falls Sie eines verwenden. In RHEL befinden sich die Nginx-Konfigurationsdateien unter \/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 Sie den server_name entsprechend dem Namen Ihres Hosts angegeben haben.

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 Sie können mit den Tests beginnen.

Fehler Bad Gateway?

Wenn Sie die Adresse im Browser eingeben, erhalten Sie wahrscheinlich den Fehler 502 Bad Gateway. Dieser kann durch falsch konfigurierte Berechtigungen für den UNIX-Socket verursacht werden oder durch komplexere Probleme im Zusammenhang mit dem Zugriffsmanagement in SELinux.

Im Nginx-Fehlerprotokoll könnte eine Zeile wie folgt aussehen:

2018/12/18 15:38:03 [crit] 12734#0: *3 connect() zu unix:/run/gunicorn.sock fehlgeschlagen (13: Berechtigung verweigert), während der Verbindung zu Upstream, Client: 192.168.122.1, Server: 8beta1.example.com, Anfrage: "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 untersuchen, warum dies passiert. Wenn wir das Protokoll öffnen, sehen wir wahrscheinlich, dass das Problem mit SELinux zusammenhängt. Da wir einen Daemon haben, für den keine eigene Richtlinie erstellt wurde, wird er als init_t gekennzeichnet. Lassen Sie uns diese Theorie in der Praxis überprüfen.

sudo setenforce 0

All dies kann Kritik und leidenschaftliche Tränen hervorrufen, aber es ist nur die Fehlersuche eines Prototyps. Lassen Sie uns die Überprüfung deaktivieren, um sicherzustellen, dass das Problem genau darin liegt, und danach alles wieder an seinen Platz zurückbringen.

Nachdem wir die Seite im Browser aktualisiert oder unser curl-Kommando neu gestartet haben, können wir die Testseite von Django sehen.

Nachdem wir sichergestellt haben, dass alles funktioniert und es keine weiteren Berechtigungsprobleme gibt, aktivieren wir SELinux wieder.

sudo setenforce 1

Hier wird nicht auf audit2allow und die Erstellung von Richtlinien basierend auf Benachrichtigungen über sepolgen eingegangen, da es derzeit keine tatsächliche Django-Anwendung gibt, und somit auch keine vollständige Übersicht darüber, auf was Gunicorn zugreifen möchte und was dieser Zugriff verbieten sollte. Daher ist es notwendig, die Funktionsweise von SELinux zum Schutze des Systems aufrechtzuerhalten und gleichzeitig der Anwendung zu ermöglichen, zu starten und Protokolleinträge zu hinterlassen, damit wir auf deren Basis eine echte Richtlinie erstellen können.

Angabe zulässiger Domains (permissive domains)

Nicht jeder hat von zulässigen Domains in SELinux gehört, aber sie sind nichts Neues. Viele haben damit gearbeitet, ohne es zu wissen. Wenn eine Richtlinie basierend auf Audit-Benachrichtigungen erstellt wird, stellt die erstellte Richtlinie eine zulässige Domain dar. Lassen Sie uns versuchen, eine einfachste Erlaubensrichtlinie zu erstellen.

Um eine spezifische erlaubte Domäne für Gunicorn zu erstellen, benötigt man eine entsprechende Politik und muss die entsprechenden Dateien kennzeichnen. Darüber hinaus sind Werkzeuge erforderlich, um neue Politiken zu erstellen.

sudo yum install selinux-policy-devel

Der Mechanismus für erlaubte Domänen ist ein hervorragendes Werkzeug zur Identifizierung von Problemen, insbesondere wenn es um benutzerdefinierte Anwendungen oder Anwendungen geht, die ohne bereits erstellte Politiken geliefert werden. In diesem Fall wird die Politik für die erlaubte Domäne von Gunicorn so einfach wie möglich gehalten – wir erklären den Haupttyp (gunicorn_t), erklären den Typ, den wir zum Markieren mehrerer ausführbarer Dateien verwenden werden (gunicorn_exec_t) und konfigurieren dann die Übergänge (transition) für system, um die gestarteten Prozesse korrekt zu markieren. Die letzte Zeile legt die Politik zum Zeitpunkt ihrer Ladezeit als Standard erlaubt fest.

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;

Diese Politiken-Datei kann kompiliert und in das System eingefügt werden.

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 überprüfen, ob SELinux noch etwas blockiert, neben dem, was unser unbekannter Daemon anfordert.

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 erlaubt Nginx nicht, Daten in den UNIX-Socket zu schreiben, der von Gunicorn verwendet wird. In solchen Fällen beginnen normalerweise Änderungen an den Politiken, aber zuvor müssen auch andere Aufgaben gelöst werden. Es ist auch möglich, die Eingabedomain zu ändern und sie von einer Eingabedomain in eine Erlaubdomain zu verwandeln. Lassen Sie uns jetzt httpd_t in die Erlaubdomain verschieben. Dies wird Nginx den erforderlichen Zugriff gewähren, so dass wir mit der weiteren Fehlersuche fortfahren können.

sudo semanage permissive -a httpd_t

Sobald es gelungen ist, die Sicherheit von SELinux zu wahren (man sollte das Projekt tatsächlich nicht mit SELinux im eingeschränkten Modus verlassen) und die Erlaubdomains geladen sind, muss herausgefunden werden, was genau als gunicorn_exec_t markiert werden muss, damit alles wieder ordnungsgemäß funktioniert. Versuchen wir, auf die Website zuzugreifen, um neue Zugriffsverweigerungsnachrichten zu sehen.

sudo ausearch -m AVC -c gunicorn

Es sind viele Nachrichten sichtbar, die ‘comm=«gunicorn»’ enthalten und verschiedene Aktionen an Dateien in /srv/djangoapp ausführen. Offensichtlich ist dies eine der Befehle, die markiert werden sollten.

Aber außerdem erscheint eine Nachricht in folgendem Format:

type=AVC msg=audit(1545320700.070:1542): avc: denied { execute } für 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 man den Status des Gunicorn-Dienstes ansieht oder den Befehl ps ausführt, erscheinen keine laufenden Prozesse. Es scheint, dass Gunicorn versucht, auf den Python-Interpreter in unserer virtualenv-Umgebung zuzugreifen, möglicherweise um Worker-Skripte auszuführen. Daher werden wir jetzt diese beiden ausführbaren Dateien markieren und überprüfen, ob wir unsere Testseite von Django öffnen können.

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

Es muss der Gunicorn-Dienst neu gestartet werden, damit das neue Label verwendet werden kann. Er kann sofort neu gestartet oder angehalten werden, sodass das Socket ihn beim Öffnen der Website im Browser startet. Stellen Sie sicher, dass die Prozesse die erforderlichen Labels erhalten haben, indem Sie ps verwenden.

ps -efZ | grep gunicorn

Vergessen Sie nicht, später eine angemessene SELinux-Politik zu erstellen!

Wenn man jetzt die AVC-Meldungen betrachtet, enthält die letzte Meldung permissive=1 für alles, was die Anwendung betrifft, und permissive=0 für den Rest des Systems. Wenn man versteht, welcher Zugriff für die tatsächliche Anwendung erforderlich ist, kann man schneller eine optimale Lösung für ähnliche Probleme finden. Bis dahin ist es jedoch besser, das System geschützt zu halten und einen klaren und brauchbaren Audit für das Django-Projekt zu erhalten.

sudo ausearch -m AVC

Es hat geklappt!

Ein funktionierendes Django-Projekt mit Frontend auf Nginx und Gunicorn WSGI ist entstanden. Wir haben Python 3 und PostgreSQL 10 aus den Repositories von RHEL 8 Beta eingerichtet. Jetzt können wir weiterarbeiten und Django-Anwendungen erstellen (oder einfach bereitstellen) oder andere verfügbare Tools in RHEL 8 Beta zur Automatisierung des Setuparbeitsprozesses, zur Leistungssteigerung oder sogar zur Containerisierung dieser Konfiguration erkunden.

Quelle: habr.com

60GB SSD 8Gb DDR4