RHEL 8 Beta bietet Entwicklern zahlreiche neue Möglichkeiten, deren Aufzählung Seiten füllen könnte. Doch am besten lernt man durch praktische Anwendung. Deshalb laden wir Sie ein, einen praktischen Workshop zur tatsächlichen Erstellung einer Anwendungsinfrastruktur auf Basis von Red Hat Enterprise Linux 8 Beta zu absolvieren.

Wir verwenden Python, eine bei Entwicklern beliebte Programmiersprache, sowie die Kombination aus Django und PostgreSQL, die eine weit verbreitete Lösung zur Erstellung von Anwendungen darstellt. Anschließend konfigurieren wir RHEL 8 Beta für die Arbeit mit diesen Technologien und fügen noch ein paar (nicht geheime) Zutaten hinzu.
Die Testumgebung wird variieren, da wir die Möglichkeiten der Automatisierung erkunden, mit Containern arbeiten und Umgebungen mit mehreren Servern ausprobieren möchten. Zu Beginn eines neuen Projekts kann man mit der manuellen Erstellung eines einfachen Prototyps starten. So erhält man einen ersten Überblick über die Abläufe und Interaktionen, bevor man zur Automatisierung und zu komplexeren Konfigurationen übergeht. Heute berichten wir über die Erstellung eines solchen Prototyps.
Lassen Sie uns mit dem Deployment des RHEL 8 Beta VM-Images beginnen. Sie können die virtuelle Maschine von Grund auf neu installieren oder das KVM-Gast-Image verwenden, das mit dem Beta-Abonnement bereitgestellt wird. Bei der Verwendung des Gast-Images müssen Sie ein virtuelles CD-Laufwerk einrichten, das die Metadaten und Benutzerdaten für die Cloud-Initiierung (cloud-init) enthält. Es sind keine besonderen Änderungen an der Disk-Setup oder den verfügbaren Paketen erforderlich; jede Konfiguration ist geeignet.
Lassen Sie uns den gesamten Prozess im Detail betrachten.
Installation von Django
Für die neueste Version von Django benötigen Sie eine virtuelle Umgebung (virtualenv) mit Python 3.5 oder einer neueren Version. In den Beta-Hinweisen können Sie 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 nutzt Python aktiv als Systemwerkzeug in RHEL, also warum ergibt sich dieses Ergebnis?
Es ist so, dass viele Entwickler, die Python verwenden, immer noch über den Umstieg von Python 2 auf Python 3 nachdenken, während Python 3 aktiv weiterentwickelt wird und ständig neue Versionen erscheinen. Um den Bedarf an stabilen Systemwerkzeugen zu decken und den Benutzern gleichzeitig den Zugriff auf verschiedene neue Python-Versionen zu ermöglichen, wurde das System-Python in ein neues Paket verschoben, das die Installation von sowohl Python 2.7 als auch 3.6 ermöglicht. Weitere Informationen zu den Änderungen und den Gründen dafür finden Sie in der Veröffentlichung im (Langdon White).
Um ein funktionierendes Python zu erhalten, müssen nur zwei Pakete installiert werden, wobei python3-pip als Abhängigkeit hinzugefügt wird.
sudo yum install python36 python3-virtualenv
Warum man, wie Langdon vorschlägt, keine direkten Aufrufe an das Modul verwenden und pip3 nicht installieren sollte? In Anbetracht der bevorstehenden Automatisierung ist bekannt, dass Ansible einen installierten pip benötigt, da das pip-Modul keine virtuellen Umgebungen (virtualenvs) mit einer benutzerdefinierten pip-Ausführungsdatei unterstützt.
Mit einem funktionierenden Python 3-Interpreter können Sie den Installationsprozess von Django fortsetzen und ein funktionierendes System zusammen mit unseren anderen Komponenten erhalten. Im Internet gibt es zahlreiche Implementierungsmöglichkeiten. Hier ist eine Version dargestellt, aber Benutzer können ihre eigenen Prozesse verwenden.
Die standardmäßig in RHEL 8 verfügbaren Versionen von PostgreSQL und Nginx installieren wir über Yum.
sudo yum install nginx postgresql-server
Für PostgreSQL benötigen wir psycopg2, das jedoch nur in der virtualenv verfügbar sein soll; daher installieren wir es mit pip3 zusammen mit Django und Gunicorn. Zuerst müssen wir jedoch virtualenv einrichten.
Es gibt viele Diskussionen über die richtige Wahl des Installationsorts für Django-Projekte. Wenn Zweifel bestehen, kann man sich immer an den Linux Filesystem Hierarchy Standard (FHS) wenden. Speziell im FHS steht, dass /srv verwendet wird für: „Daten, die spezifisch für einen bestimmten Knoten sind – Daten, die das System bereitstellt, wie z. B. Daten und Skripte von Webservern, Daten, die auf FTP-Servern gespeichert sind, sowie Repositories für Versionskontrollsysteme (eingeführt in FHS-2.3 im Jahr 2004)“.
Das ist genau unser Fall, also legen wir alles Wichtige 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, einen Benutzer und konfigurieren die Berechtigungen. Es gibt einen Punkt, den man bei der Erstinstallation von PostgreSQL beachten sollte – das Skript postgresql-setup, das zusammen mit dem Paket postgresql-server installiert wird. Dieses Skript hilft, grundlegende Aufgaben im Zusammenhang mit der Verwaltung des Datenbankclusters auszuführen, wie etwa die Initialisierung des Clusters oder den Aktualisierungsprozess. Für die Konfiguration einer neuen PostgreSQL-Instanz auf einem RHEL-System müssen wir folgenden Befehl ausführen:
sudo /usr/bin/postgresql-setup -initdb
Nach dem können Sie PostgreSQL mit systemd starten, eine Datenbank erstellen und das Projekt in Django einrichten. Vergessen Sie nicht, PostgreSQL nach Änderungen an der Client-Authentifizierungsdatei (in der Regel pg_hba.conf) neu zu starten, um die Speicherung des Passworts für den Anwendungsbenutzer zu konfigurieren. Wenn Sie auf weitere Schwierigkeiten stoßen, stellen Sie sicher, dass die IPv4- und IPv6-Einstellungen 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 Datei settings.py im Projekt und 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?
Ein Entwicklungsserver ist nützlich für Tests, aber um eine Anwendung zu starten, müssen der passende Server und ein Proxy für die Web Server Gateway Interface (WSGI) konfiguriert werden. Es gibt mehrere gängige Kombinationen, wie Apache HTTPD mit uWSGI oder Nginx mit Gunicorn.
Die Aufgabe des Web Server Gateway Interface besteht darin, Anfragen von Webserver an das Python-Webframework weiterzuleiten. WSGI ist ein Erbe aus einer schwierigen Vergangenheit, als CGI-Mechanismen weit verbreitet waren, und ist heute de facto der Standard, unabhängig vom verwendeten Webserver oder Python-Framework. Trotz seiner breiten Verbreitung gibt es viele Nuancen bei der Arbeit mit diesen Frameworks und zahlreiche Auswahlmöglichkeiten. In diesem Fall versuchen wir, 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 zur Kommunikation ohnehin ein Socket benötigt wird, machen wir einen weiteren Schritt und konfigurieren die Aktivierung des Sockets für Gunicorn über systemd.
Der Prozess zur Erstellung von socket-aktivierten Diensten ist recht einfach. Zuerst wird eine Unit-Datei erstellt, die die Direktive ListenStream enthält, welche auf den Punkt verweist, an dem der UNIX-Socket erstellt wird. Anschließend folgt eine 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 der Aufruf von Gunicorn aus der virtuellen Umgebung und das Erstellen einer WSGI-Bindung für den UNIX-Socket und die Django-Anwendung.
Hier sind einige Beispiele für Unit-Dateien, die als Grundlage dienen 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 reicht es, einfach die Proxy-Konfigurationsdateien zu erstellen und das Verzeichnis für statische Inhalte einzurichten, falls Sie dieses nutzen. In RHEL befinden sich die Konfigurationsdateien von Nginx 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 Sie den server_name entsprechend dem Namen Ihres Hosts angeben.
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.
Bad Gateway-Fehler?
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 auf komplexere Probleme mit der Zugangskontrolle in SELinux zurückzuführen sein.
Im Nginx-Fehlerprotokoll kann eine ähnliche Zeile auftreten:
2018/12/18 15:38:03 [crit] 12734#0: *3 connect() zur unix:/run/gunicorn.sock fehlgeschlagen (13: Berechtigung verweigert) während der Verbindung zum 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 herausfinden, warum das passiert. Wenn wir das Protokoll öffnen, werden wir wahrscheinlich sehen, dass das Problem mit SELinux zusammenhängt. Da unser Dienst läuft, 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
Das kann Kritik und bittere Tränen hervorrufen, aber das ist nur das Debuggen eines Prototyps. Lassen Sie uns die Überprüfung deaktivieren, um sicherzustellen, dass das Problem tatsächlich daran liegt, und dann bringen wir alles wieder an seinen Platz.
Nachdem Sie die Seite im Browser aktualisiert oder unser curl-Kommando neu gestartet haben, können Sie die Testseite von Django sehen.
Nachdem wir sichergestellt haben, dass alles funktioniert und keine weiteren Berechtigungsprobleme bestehen, aktivieren wir SELinux erneut.
sudo setenforce 1
Hier wird nicht über audit2allow und die Erstellung von Richtlinien basierend auf Benachrichtigungen mit sepolgen gesprochen, da es im Moment keine echte Django-Anwendung gibt, und somit keine vollständige Übersicht darüber, auf was Gunicorn zugreifen möchte und was für den Zugriff verboten werden sollte. Deshalb ist es notwendig, dass SELinux aktiv bleibt, um das System zu schützen, während gleichzeitig die Anwendung laufen und Auditing-Meldungen erzeugen kann, damit darauf basierend eine echte Richtlinie erstellt werden kann.
Angabe erlaubter Domänen (permissive domains)
Nicht jeder hat von erlaubten Domänen in SELinux gehört, aber sie sind nichts Neues. Viele haben sogar bereits mit ihnen gearbeitet, ohne es zu wissen. Wenn eine Richtlinie basierend auf Auditing-Nachrichten erstellt wird, stellt die erstellte Richtlinie einen erlaubten Bereich dar. Lassen Sie uns versuchen, eine einfache erlaubende Richtlinie zu erstellen.
Um einen spezifischen erlaubten Bereich für Gunicorn zu erstellen, ist eine bestimmte Richtlinie erforderlich, und außerdem müssen die entsprechenden Dateien gekennzeichnet werden. Zudem sind Werkzeuge notwendig, um neue Richtlinien zu erstellen.
sudo yum install selinux-policy-devel
Der Mechanismus der erlaubten Domains ist ein hervorragendes Werkzeug zur Identifizierung von Problemen, insbesondere bei benutzerdefinierten Anwendungen oder Anwendungen, die ohne bereits erstellte Richtlinien bereitgestellt werden. In diesem Fall wird die Richtlinie für erlaubte Domains für Gunicorn sehr einfach sein – wir deklarieren den Haupttyp (gunicorn_t), wir deklarieren den Typ, den wir zur Kennzeichnung mehrerer ausführbarer Dateien verwenden werden (gunicorn_exec_t), und dann richten wir den Übergang (transition) für system ein, um die laufenden Prozesse korrekt zu kennzeichnen. Die letzte Zeile legt die Richtlinie als standardmäßig erlaubt zum Zeitpunkt ihrer Ladezeit 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 Politikdatei kann kompiliert und dem System hinzugefü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 anderes blockiert, abgesehen von dem, was unser unbekannter Daemon anfragt.
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. Normalerweise beginnt man in solchen Fällen, die Richtlinien zu ändern, aber es stehen noch andere Aufgaben an. Es ist auch möglich, die Einstellungen des Domains von einem Beschränkungs- in ein Erlaubnis-Domain zu ändern. Lassen Sie uns nun httpd_t in das Erlaubnis-Domain verschieben. Dadurch erhält Nginx den erforderlichen Zugriff, und wir können mit der Fehlersuche fortfahren.
sudo semanage permissive -a httpd_t
Wenn es also gelungen ist, den SELinux-Schutz aufrechtzuerhalten (tatsächlich sollte ein Projekt nicht mit SELinux im Beschränkungsmodus betrieben werden) und die Erlaubnis-Domains geladen sind, müssen wir herausfinden, was genau als gunicorn_exec_t markiert werden muss, damit alles wieder wie gewohnt funktioniert. Lassen Sie uns die Website besuchen, um neue Meldungen über Zugriffsbeschränkungen zu sehen.
sudo ausearch -m AVC -c gunicorn
Es sind zahlreiche Meldungen sichtbar, die 'comm=«gunicorn»' enthalten, die verschiedene Aktionen auf Dateien in /srv/djangoapp ausführen, sodass offensichtlich dies eine der Befehle ist, die markiert werden sollten.
Darüber hinaus erscheint eine Meldung in folgender Art:
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 den Status des Gunicorn-Dienstes überprüfen oder den Befehl ps ausführen, werden keine laufenden Prozesse angezeigt. Es scheint, dass Gunicorn versucht, auf den Python-Interpreter in unserer virtualenv zuzugreifen, möglicherweise um die Worker-Skripte zu starten. Lassen Sie uns diese beiden ausführbaren Dateien markieren und sehen, ob wir unsere Test-Django-Seite öffnen können.
chcon -t gunicorn_exec_t /srv/djangoapp/django/bin/gunicorn /srv/djangoapp/django/bin/python3.6
Es ist erforderlich, den Gunicorn-Dienst neu zu starten, damit die neue Etikettierung wirksam wird. Sie können ihn sofort neu starten oder den Dienst stoppen und den Socket ihn beim Öffnen der Website im Browser starten lassen. Stellen Sie sicher, dass die Prozesse die erforderlichen Etiketten erhalten haben, indem Sie ps verwenden.
ps -efZ | grep gunicorn
Vergessen Sie nicht, später eine ordnungsgemäße SELinux-Richtlinie zu erstellen!
Wenn man sich die AVC-Meldungen jetzt anschaut, enthält die letzte Meldung permissive=1 für alles, was mit der Anwendung zu tun hat, und permissive=0 für das gesamte andere System. Wenn man versteht, welcher Zugriff für die tatsächliche Anwendung erforderlich ist, kann man schneller einen optimalen Lösungsweg für solche Probleme finden. Bis dahin ist es jedoch besser, das System geschützt zu halten und eine verständliche sowie nutzbare Auditierung des Django-Projekts zu erhalten.
sudo ausearch -m AVC
Erledigt!
Es gibt ein funktionierendes Django-Projekt mit einem Frontend auf Nginx und Gunicorn WSGI. Wir haben Python 3 und PostgreSQL 10 aus den RHEL 8 Beta-Repositories eingerichtet. Jetzt können wir weiterarbeiten und Django-Anwendungen erstellen (oder einfach bereitstellen) oder andere verfügbare Werkzeuge in RHEL 8 Beta für die Automatisierung des Setups, die Leistungssteigerung oder sogar die Containerisierung dieser Konfiguration erkunden.
Quelle: habr.com
