Workshop RHEL 8 Beta: Bygging av fungerende webapplikasjoner

RHEL 8 Beta tilbyr utviklere mange nye funksjoner, listen over disse kan ta sider, men å lære nye ting er alltid bedre i praksis, så nedenfor tilbyr vi en workshop om å faktisk lage en applikasjonsinfrastruktur basert på Red Hat Enterprise Linux 8 Beta.

Workshop RHEL 8 Beta: Bygging av fungerende webapplikasjoner

La oss ta Python, et populært programmeringsspråk blant utviklere, som grunnlag, en kombinasjon av Django og PostgreSQL, en ganske vanlig kombinasjon for å lage applikasjoner, og konfigurere RHEL 8 Beta til å jobbe med dem. Så legger vi til et par (uklassifiserte) ingredienser til.

Testmiljøet vil endre seg, fordi det er interessant å utforske mulighetene for automatisering, arbeid med containere og prøvemiljøer med flere servere. For å komme i gang med et nytt prosjekt, kan du starte med å lage en liten, enkel prototype for hånd, slik at du kan se nøyaktig hva som må skje og hvordan det samhandler, og deretter gå videre til å automatisere og lage mer komplekse konfigurasjoner. I dag snakker vi om etableringen av en slik prototype.

La oss starte med å distribuere RHEL 8 Beta VM-bilde. Du kan installere en virtuell maskin fra bunnen av, eller bruke KVM-gjestebildet som er tilgjengelig med Beta-abonnementet ditt. Når du bruker et gjestebilde, må du konfigurere en virtuell CD som vil inneholde metadata og brukerdata for skyinitiering (cloud-init). Du trenger ikke å gjøre noe spesielt med diskstrukturen eller tilgjengelige pakker; enhver konfigurasjon vil gjøre det.

La oss se nærmere på hele prosessen.

Installerer Django

Med den nyeste versjonen av Django trenger du et virtuelt miljø (virtualenv) med Python 3.5 eller nyere. I betanotatene kan du se at Python 3.6 er tilgjengelig, la oss sjekke om dette virkelig er tilfelle:

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

Red Hat bruker aktivt Python som et systemverktøysett i RHEL, så hvorfor resulterer dette?

Faktum er at mange Python-utviklere fortsatt vurderer overgangen fra Python 2 til Python 2, mens selve Python 3 er under aktiv utvikling, og stadig flere nye versjoner dukker opp. Derfor, for å møte behovet for stabile systemverktøy og samtidig tilby brukere tilgang til ulike nye versjoner av Python, ble systemet Python flyttet inn i en ny pakke og ga muligheten til å installere både Python 2.7 og 3.6. Mer informasjon om endringene og hvorfor de ble gjort finner du i publikasjonen i Langdon Whites blogg (Langdon White).

Så for å få fungerende Python trenger du bare å installere to pakker, med python3-pip inkludert som en avhengighet.

sudo yum install python36 python3-virtualenv

Hvorfor ikke bruke direkte modulkall som Langdon foreslår og installere pip3? Med tanke på den kommende automatiseringen, er det kjent at Ansible vil kreve pip installert for å kjøre, siden pip-modulen ikke støtter virtualenvs med en tilpasset pip-kjørbar.

Med en fungerende python3-tolk til din disposisjon, kan du fortsette med Django-installasjonsprosessen og ha et fungerende system sammen med våre andre komponenter. Det er mange implementeringsalternativer tilgjengelig på Internett. Det er én versjon presentert her, men brukere kan bruke sine egne prosesser.

Vi vil installere PostgreSQL- og Nginx-versjonene som er tilgjengelige i RHEL 8 som standard ved å bruke Yum.

sudo yum install nginx postgresql-server

PostgreSQL vil kreve psycopg2, men det må bare være tilgjengelig i et virtualenv-miljø, så vi vil installere det ved å bruke pip3 sammen med Django og Gunicorn. Men først må vi sette opp virtualenv.

Det er alltid mye debatt om emnet å velge riktig sted å installere Django-prosjekter, men når du er i tvil, kan du alltid gå til Linux Filesystem Hierarchy Standard. Spesifikt sier FHS at /srv brukes til å: "lagre vertsspesifikke data - data som systemet produserer, for eksempel webserverdata og skript, data lagret på FTP-servere og kontrollsystemlagre." versjoner (viser i FHS -2.3 i 2004)."

Dette er akkurat vårt tilfelle, så vi legger alt vi trenger inn i /srv, som eies av vår applikasjonsbruker (skybruker).

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

Det er enkelt å sette opp PostgreSQL og Django: opprett en database, opprett en bruker, konfigurer tillatelser. En ting å huske på når du først installerer PostgreSQL er postgresql-setup-skriptet som er installert med postgresql-server-pakken. Dette skriptet hjelper deg med å utføre grunnleggende oppgaver knyttet til databaseklyngeadministrasjon, for eksempel klyngeinitialisering eller oppgraderingsprosessen. For å konfigurere en ny PostgreSQL-forekomst på et RHEL-system, må vi kjøre kommandoen:

sudo /usr/bin/postgresql-setup -initdb

Du kan deretter starte PostgreSQL ved å bruke systemd, lage en database og sette opp et prosjekt i Django. Husk å starte PostgreSQL på nytt etter å ha gjort endringer i konfigurasjonsfilen for klientautentisering (vanligvis pg_hba.conf) for å konfigurere passordlagring for applikasjonsbrukeren. Hvis du støter på andre problemer, sørg for å endre IPv4- og IPv6-innstillingene i filen 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

I filen /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

I filen /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 }}',
   }
}

Etter å ha konfigurert filen settings.py i prosjektet og satt opp databasekonfigurasjonen, kan du starte utviklingsserveren for å sikre at alt fungerer. Etter å ha startet utviklingsserveren, er det en god idé å opprette en admin-bruker for å teste tilkoblingen til databasen.

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

WSGI? Wai?

Utviklingsserveren er nyttig for testing, men for å kjøre applikasjonen må du konfigurere riktig server og proxy for Web Server Gateway Interface (WSGI). Det er flere vanlige kombinasjoner, for eksempel Apache HTTPD med uWSGI eller Nginx med Gunicorn.

Jobben til Web Server Gateway Interface er å videresende forespørsler fra webserveren til Python-nettverket. WSGI er en levning fra den forferdelige fortiden da CGI-motorer fantes, og i dag er WSGI de facto-standarden, uavhengig av nettserveren eller Python-rammeverket som brukes. Men til tross for utbredt bruk, er det fortsatt mange nyanser når man jobber med disse rammeverkene, og mange valg. I dette tilfellet vil vi prøve å etablere interaksjon mellom Gunicorn og Nginx via en socket.

Siden begge disse komponentene er installert på samme server, la oss prøve å bruke en UNIX-kontakt i stedet for en nettverkskontakt. Siden kommunikasjon krever en socket i alle fall, la oss prøve å ta ett skritt til og konfigurere socketaktivering for Gunicorn via systemd.

Prosessen med å lage socketaktiverte tjenester er ganske enkel. Først opprettes en enhetsfil som inneholder et ListenStream-direktiv som peker til punktet der UNIX-socket vil bli opprettet, deretter en enhetsfil for tjenesten der Requires-direktivet vil peke til socket-enhetsfilen. Så, i tjenesteenhetsfilen, gjenstår det bare å ringe Gunicorn fra det virtuelle miljøet og lage en WSGI-binding for UNIX-kontakten og Django-applikasjonen.

Her er noen eksempler på enhetsfiler som du kan bruke som grunnlag. Først setter vi opp stikkontakten.

[Unit]
Description=Gunicorn WSGI socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target

Nå må du konfigurere Gunicorn-demonen.

[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

For Nginx er det en enkel sak å lage proxy-konfigurasjonsfiler og sette opp en katalog for å lagre statisk innhold hvis du bruker en. I RHEL er Nginx-konfigurasjonsfiler plassert i /etc/nginx/conf.d. Du kan kopiere følgende eksempel inn i filen /etc/nginx/conf.d/default.conf og starte tjenesten. Sørg for å angi servernavnet slik at det samsvarer med vertsnavnet ditt.

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

Start Gunicorn-kontakten og Nginx med systemd, og du er klar til å begynne å teste.

Dårlig gateway-feil?

Hvis du skriver inn adressen i nettleseren din, vil du mest sannsynlig få en 502 Bad Gateway-feil. Det kan være forårsaket av feilkonfigurerte UNIX-socket-tillatelser, eller det kan skyldes mer komplekse problemer knyttet til tilgangskontroll i SELinux.

I nginx-feilloggen kan du se en linje som denne:

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"

Tester vi Gunicorn direkte, får vi et tomt svar.

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

La oss finne ut hvorfor dette skjer. Hvis du åpner loggen, vil du mest sannsynlig se at problemet er relatert til SELinux. Siden vi kjører en demon som ingen policy er opprettet for, er den merket som init_t. La oss teste denne teorien i praksis.

sudo setenforce 0

Alt dette kan føre til kritikk og blodtårer, men dette er bare å feilsøke prototypen. La oss deaktivere sjekken bare for å forsikre oss om at dette er problemet, hvoretter vi vil returnere alt til sin plass.

Ved å oppdatere siden i nettleseren eller kjøre curl-kommandoen vår på nytt, kan du se Django-testsiden.

Så etter å ha forsikret oss om at alt fungerer og det ikke er flere tillatelsesproblemer, aktiverer vi SELinux igjen.

sudo setenforce 1

Jeg vil ikke snakke om audit2allow eller lage varslingsbaserte retningslinjer med sepolgen her, siden det ikke er noen faktisk Django-applikasjon for øyeblikket, så det er ikke noe fullstendig kart over hva Gunicorn kanskje vil ha tilgang til og hva den skal nekte tilgang til. Derfor er det nødvendig å holde SELinux i gang for å beskytte systemet, samtidig som det lar applikasjonen kjøre og legge igjen meldinger i revisjonsloggen slik at selve policyen så kan opprettes fra dem.

Spesifisere tillatelige domener

Ikke alle har hørt om tillatte domener i SELinux, men de er ikke noe nytt. Mange jobbet til og med med dem uten å være klar over det. Når en policy opprettes basert på revisjonsmeldinger, representerer den opprettede policyen det løste domenet. La oss prøve å lage en enkel tillatelsespolicy.

For å opprette et spesifikt tillatt domene for Gunicorn trenger du en slags policy, og du må også merke de riktige filene. I tillegg trengs verktøy for å sette sammen nye retningslinjer.

sudo yum install selinux-policy-devel

Mekanismen for tillatte domener er et flott verktøy for å identifisere problemer, spesielt når det kommer til en tilpasset applikasjon eller applikasjoner som sendes uten at retningslinjer allerede er opprettet. I dette tilfellet vil den tillatte domenepolicyen for Gunicorn være så enkel som mulig - erklær en hovedtype (gunicorn_t), erklær en type vi vil bruke til å merke flere kjørbare filer (gunicorn_exec_t), og sett opp en overgang for systemet til å merke riktig kjørende prosesser. Den siste linjen angir at policyen er aktivert som standard når den lastes inn.

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;

Du kan kompilere denne policyfilen og legge den til systemet ditt.

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

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

La oss sjekke om SELinux blokkerer noe annet enn det vår ukjente demon har tilgang til.

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 hindrer Nginx i å skrive data til UNIX-kontakten som brukes av Gunicorn. Vanligvis begynner politikken i slike tilfeller å endres, men det er andre utfordringer fremover. Du kan også endre domeneinnstillingene fra et restriksjonsdomene til et tillatelsesdomene. La oss nå flytte httpd_t til tillatelsesdomenet. Dette vil gi Nginx nødvendig tilgang og vi kan fortsette med videre feilsøkingsarbeid.

sudo semanage permissive -a httpd_t

Så, når du har klart å holde SELinux beskyttet (du bør egentlig ikke forlate et SELinux-prosjekt i begrenset modus) og tillatelsesdomenene er lastet inn, må du finne ut hva som må merkes som gunicorn_exec_t for å få alt til å fungere ordentlig en gang til. La oss prøve å besøke nettstedet for å se nye meldinger om tilgangsbegrensninger.

sudo ausearch -m AVC -c gunicorn

Du vil se mange meldinger som inneholder 'comm="gunicorn"' som gjør forskjellige ting på filer i /srv/djangoapp, så dette er åpenbart en av kommandoene som er verdt å flagge.

Men i tillegg kommer en melding som denne:

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

Hvis du ser på statusen til gunicorn-tjenesten eller kjører ps-kommandoen, vil du ikke se noen kjørende prosesser. Det ser ut som gunicorn prøver å få tilgang til Python-tolken i vårt virtualenv-miljø, muligens for å kjøre arbeiderskript. Så la oss nå merke disse to kjørbare filene og sjekke om vi kan åpne vår Django-testside.

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

Gunicorn-tjenesten må startes på nytt før den nye taggen kan velges. Du kan starte den på nytt umiddelbart eller stoppe tjenesten og la stikkontakten starte den når du åpner siden i nettleseren. Bekreft at prosesser har mottatt de riktige etikettene ved hjelp av ps.

ps -efZ | grep gunicorn

Ikke glem å lage en normal SELinux-policy senere!

Hvis du ser på AVC-meldingene nå, inneholder den siste meldingen permissive=1 for alt relatert til applikasjonen, og permissive=0 for resten av systemet. Hvis du forstår hva slags tilgang en reell applikasjon trenger, kan du raskt finne den beste måten å løse slike problemer på. Men inntil da er det best å holde systemet sikkert og få en tydelig, brukbar revisjon av Django-prosjektet.

sudo ausearch -m AVC

Skjedde!

Et fungerende Django-prosjekt har dukket opp med en frontend basert på Nginx og Gunicorn WSGI. Vi konfigurerte Python 3 og PostgreSQL 10 fra RHEL 8 Beta-repositoriene. Nå kan du gå videre og lage (eller ganske enkelt distribuere) Django-applikasjoner eller utforske andre tilgjengelige verktøy i RHEL 8 Beta for å automatisere konfigurasjonsprosessen, forbedre ytelsen eller til og med beholde denne konfigurasjonen.

Kilde: www.habr.com

Legg til en kommentar