Virtuális hosztok Dockerbarát módon NginX reverse proxy-val

proxy network felirat a pixabay.com-ról kevinandthepup felhasználótól

Minden eddigi megközelítésünk lényege az egyszerűség volt, hogy kis lépésekben haladjunk előre. Eljött az a pont, amikor már elhagyjuk a kényelmes hoszt network-öt és a konténerek saját IP-t kapnak. Ezzel viszont bonyolódik a több weboldal elérése, amennyiben minden weboldalt külön konténerben indítunk. Bár a HTTPD-vel is konfiguráltunk proxy-t, jellemzően nem azt használjuk erre a célra, hanem például NginX-et vagy HAProxy-t. A következőkben az NginX proxy konfigurálását mutatom be dinamikusan generált konfigurációs fájllal, hiszen nem tudjuk előre a konténerek IP címeit, ráadásul meg is változhatnak időnként. Innentől kezdve minden konténert, beleértve az előző alkalmakkor konfigurált fájlfeltöltőt és letöltőt, illetve alapértelmezett weboldalt is ugyanazon a proxy-n keresztül fogjuk elérni. Bár élesben a fájlfeltöltő mögötti PHP sem a parancssori PHP-ból indított webszerverrel fog működni, ezt most hagyjuk, ahogy van és csak a proxy-ra koncentrálunk.

Tartalomjegyzék

Mappastruktúra átalakítás

[Tartalom]

Mivel virtuális hosztokkal kezdtük, a projekt neve is az volt, ami egyelőre marad, de a Python virtuális környezet nevét én leegyszerűsítem, hogy "venv" legyen a név a promptban és a shellben is egyszerűbben lehet hivatkozni rá aktiváláskor. Mielőtt bármit csinálok, előbb törlöm a jelenlegi konténereket a compose projektben, amíg még megvan a jelenlegi konfigurációs fájl, ezután ugyanis más lesz a projekt neve is.

docker-compose down -v

Ha elfelejtettem volna, akkor is tundám törölni a konténereket, network-öket és volume-okat a "docker" paranccsal, hiszen a Docker Compose csak egy segédprogram, ami YAML konfigurációs fájlt biztosít.

Törlöm a "httpd-vhosts-env" mappát és újratelepítem a Docker Compose-t is. Mindezt a sorozat első részében, a Docker Compose használatánál már bemutattam, de az új névvel az aktuális parancsok a következők:

sudo apt install python3-venv
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install docker-compose==1.28.5

Az új mappanév miatt a .gitignore-ban és a .dockerignore-ban is át kell írni a "httpd-vhosts-env"-et "venv"-re.

Mivel most nem csak több Docker image-ünk lesz, hanem több Docker Compose projektünk is, az eddigi projekt fájljai eggyel mélyebb mappaszintre kerülnek, a többi projektet pedig mellettük hozzuk majd létre:

mkdir fileshare
mv httpd fileshare/
mv uploader fileshare/
mv docker-compose.yml fileshare/

Több YAML fájlt fogunk szerkeszteni, ezért egy .editorconfig fájllal érdemes lehet beállítani a preferált indentálás és a sortörés stílusát. Ezt persze támogatnia kell a használt kódszerkesztő programnak is, de ma már általában ez nem probléma. Ez a lépés opcionális, hiszen, a YAML számára szükséges space-ek enélkül is beírhatók, a behúzás értéke (indent_size) pedig más is lehet, de enélkül nekem nem volt teljesen következetes, hogy éppen milyen behúzásokat használ a Visual Studio Code.

root = true

[*]
end_of_line = lf
indent_size = 2
indent_style = space

Proxy projekt

[Tartalom]

A proxy egy külön Docker Compose projekt lesz, ezért külön mappát kap a fileshare mappa mellett.

mkdir proxy
cd proxy

A proxy-hoz nem készítünk saját image-et, hanem egy létezőt fogunk használni Jason Wildertől. Kétféle image-e van:

  • jwilder/nginx-proxy: Tartalmazza az NginX szervert és a "docker-gen" komponenst, ami Go template-ből generálja az NginX számára a konfigurációs fájlt.
  • jwilder/docker-gen: Kizárólag a "docker-gen" komponenst tartalmazza, ezért bár hosszú távon hasznos lehet szétválasztani az NginX-et a konfigurációs fájlt generáló konténertől, ennek konfigurációja komplikáltabb.

Az egyszerűbb megoldásnál maradva egyelőre az "nginx-proxy" image-et használjuk aminek a működési elve a következő:

  • Az NginX a /etc/nginx/conf.d/default.conf konfigurációs fájl alapján fogja irányítani a kéréseket.
  • A default.conf-ot a docker-gen fogja generálni.
  • A generáláshoz a /etc/docker-gen/templates/nginx.tmpl sablonfájlt fogja használni.
  • A sablonfájl olyan adatokra támaszkodik, amit a docker-gen a Docker API-tól fog elkérni. Ezek az adatok például a konténer IP címe, a kért domain név és hogy milyen hálózatoknak a része, mert csak azonos Docker hálózatból érik el egymást a konténerek közvetlenül.
  • A konfigurációs fájl legenerálása után a docker-gen értesíti az NginX-et, hogy újra kell tölteni a konfigurációs fájlt.
  • Az NginX a default.conf alapján a kívülről beérkező HTTP kéréseket a tetszőleges porton futó szolgáltatások felé irányítja a konténerekben, miközben az NginX végig a 80-as porton érhető el, vagy a 443-as porton is HTTPS protokoll esetén.

Docker Gen és NginX működése animált GIF-en

Ahhoz, hogy a proxy és a többi konténer lássák egymást, azonos network-ön kell lenniük. Mivel alapértelmezetten minden Docker Compose projekt kap saját hálózatot, a proxy-nál ezt felülírjuk és egy külső Docker network-re csatlakozunk, amit előbb létre kell hozni a docker paranccsal:

docker network create proxy-web

A hálózat külső neve "proxy-web" lesz, hogy meg tudjuk különböztetni a compose fájlban a hivatkozható belső nevétől, ami "proxy" lesz és az alábbi módon konfiguráljuk közvetlenül a proxy mappában levő "docker-compose.yml" fájlban a compose fájl verzió megadása után.

version: "3.9"

networks
:
  proxy
:
    external
: true
    name
: proxy-web

A proxy-t ki is kell engedni a 80-as porton a külvilág felé, de mivel már nem a hoszt hálózatán indítjuk a konténert, ezért kell egy port átirányítás.

services:
  nginx-proxy
:
    image
: jwilder/nginx-proxy:0.9.0
    ports
:
     - "80:80"

Az első port szám a külső port, a második a ketttőspont után a konténernek a portja. Esetünben mindkettő 80-as, de a külső portot tetszőlegesen meg lehet változtatni. A "docker-gen"-nek el kell tudnia érni a Docker API-t, ezért felcsatoljuk a Docker socket fájlt bind mounttal a "ports" kulcsszóval egy oszlopban az "nginx-proxy" service alatt:

   volumes:
      - type
: bind
        source
: /var/run/docker.sock
        target
: /tmp/docker.sock

A httpd-vhosts.conf -ban elsőként definiált virtuális hoszt szerepét alapértelmezett hosztként egy külön konténer fogja felváltani, amihez tartozó domain nevet a DEFAULT_HOST környezeti változóban kell megadnunk. Ez a környezeti változó a konténerbe belépve akár a bash-ből is elérhető, de a "docker-gen" is ezt fogja felhasználni. Ennek a domainnek nem kell kívülről elérhetőnek lennie, így a neve lehet egyszerűen csak "default", ha a konténernek más célja nincs.

   environment:
      DEFAULT_HOST
: default

Mivel az NginX Proxy konténerben két névtelen volume is létrejön, azért, hogy a projekt törlése és újra létrehozása közben ne szemeteljük tele az amúgy nem is használt volume-okkal a volume listát, nevesített volume-ként definiáljuk a network definíció és a services kulcsó között:

volumes:
  nginx-dhparam
:
  nginx-certs
:

Ezt viszont fel is kell catolni a "volumes" blokk alatt, ahol a docker socket-et is felcsatoltuk:

 
      - type
: volume
        source
: nginx-dhparam
        target
: /etc/nginx/dhparam
      - type
: volume
        source
: nginx-certs
        target
: /etc/nginx/certs

A teljes docker-compose.yml a következő lesz ezután:

version: "3.9"

networks
:
  default
:
    external
: true
    name
: proxy-web

volumes
:
  nginx-dhparam
:
  nginx-certs
:

services
:
  nginx-proxy
:
    image
: jwilder/nginx-proxy:0.9.0
    ports
:
     - "80:80"
    volumes
:
      - type
: bind
        source
: /var/run/docker.sock
        target
: /tmp/docker.sock
      - type
: volume
        source
: nginx-dhparam
        target
: /etc/nginx/dhparam
      - type
: volume
        source
: nginx-certs
        target
: /etc/nginx/certs
    environment
:
      DEFAULT_HOST
: default

Ezután lehet indítani a projektet:

docker-compose up -d

A proxy ugyan elindul, de még egy hibaoldal fog megjelenni, mivel egyetlen konténer sincs, amire irányíthatná a kérést, még a default konténer sem készült el.

503-as HTTP kód NginX 1.19-ben

Alapértelmezett weblap

[Tartalom]

Az alapértelmezett weblap egy külön projekt lesz a "fileshare" és a "proxy" mellett. Először is "default" mappanévvel hozzuk létre:

cd ../
mkdir default
cd default

Ehhez az index.html tartalma legyen a következő:

<p style="font-size: 16pt; text-align: center;">Unknown site</p>

Az egyszerű Dockerfile-ban mindőssze ennek a HTML-nek a felmásolása lesz:

FROM httpd:2.4

COPY index.html /usr/local/apache2/htdocs/index.html

A gyökérkönyvtár most már viszont a /usr/local/apache2/htdocs lesz, ami az alapértelmezett gyükérkönyvtár a HTTPD-nél. Mivel a HTTPD virtuális hosztban sem konfiguráltunk mást, most itt is megelégszünk ennyivel. A dockr-compose.yml nem sok újdonságot tartalmaz majd. Minden compose projektben meg kell adni a proxy-hoz tartozó Docker hálózatot. Bemásolhatnánk ugyanazt is, mint amit a proxy-nál megadtunk, viszont itt egy másik megoldást alkalmazunk. Ahelyett, hogy az alapértelmezett hálózatát módosítanánk a compose projektnek, egy új néven hivatkozunk a "proxy-web" külső hálózatra. Itt elég lesz a "default" helyett csak a "proxy"-t írni a proxy projektben konfiguráltakhoz képest.

networks:
  proxy
:
    external
: true
    name
: proxy-web

Ilyenkor viszont a service-nél közvetlenül a "services" blokkban definiált "httpd" nevű service blokkjába a már ismerős "build"-del egy oszlopban felsoroljuk a service által elérhető hálózatokat. Jelenleg azt az egyet.

   networks:
     - proxy

Ezután pedig ugyanebben az oszlopban a VIRTUAL_HOST nevű változóban adjuk meg, hogy a konténerhez milyen domain nevet rendelünk.

   environment:
      VIRTUAL_HOST
: default

A teljes compose fájl a következő:

version: "3.9"

networks
:
  proxy
:
    external
: true
    name
: proxy-web

services
:
  httpd
:
    build
:
      context
: .
      dockerfile
: Dockerfile
    environment
:
      VIRTUAL_HOST
: default
    networks
:
     - proxy

Most a szokásos paranccsal elindítjuk az alapértelmezett szolgáltatást:

docker-compose up -d

Most már a 127.0.0.1 IP címen az "Unknown site" felirat lesz, de mivel még semmilyen másik servicünk nem fut, ezért mindenképpen ezt látnánk.

Letöltő szolgáltatás

[Tartalom]

A letöltő szintén külön projekt lesz, de csak egy kicsivel lesz bonyolultabb a default szolgáltatásnál. Ehhez a fileshare mappában most egy "downloads" mappát kell létrehozni, a "httpd" mappa pedig már csak referenciaként lesz használva, a Docker Compose-nak már nem lesz rá szüksége.

cd ../
mkdir fileshare/downloads
cd fileshare

Az index fájlt másolhatjuk a httpd mappából:

cp ../httpd/vhosts/downloads/index.html index.html

De emlékeztetőül a tartalma a következő

<p style="font-weight: bold; font-size: 26pt;">
  <a href="files">Downloads</a>
</p>

A Dockerfile-ban most egy új utasítást is használunk. Ez pedig a RUN, amivel shell parancsokat írhatunk. Ezeket majd && karakterekkel fűzzük össze, így, ha bármelyik utasítás hibával tér vissza, leáll az image készítés. Ha ez egy bash szkript lenne, akkor az alábbi sorokat írnánk ahhoz, hogy Az awk-val a ServerAdmin direktíva értékét megváltoztassuk a httpd.conf fájlban ahelyett, hogy az egész fájlt kimásolnánk az image-ből és csak az egy sor módosítása miatt visszatöltenénk.

mv conf/httpd.conf conf/httpd.conf.orig \
  && awk '\
        {\
          gsub("ServerAdmin you@example.com", "ServerAdmin webmaster@127.0.0.1.nip.io");\
          print $0\
        }\
     '
conf/httpd.conf.orig > conf/httpd.conf \
  && unlink conf/httpd.conf.orig

A Dockerfile-ban pedig hasonló lesz, csak a RUN kulcsszó után.

FROM httpd:2.4

RUN mv conf/httpd.conf conf/httpd.conf.orig \
 && awk '\
      {\
        gsub("ServerAdmin you@example.com", "ServerAdmin webmaster@127.0.0.1.nip.io");\
        print $0\
      }\
   ' conf/httpd.conf.orig > conf/httpd.conf \
 && unlink conf/httpd.conf.orig

COPY index.html /usr/local/apache2/htdocs/index.html

Akár már indíthatnánk is a HTTPD-t, de a feltöltőben is kell még egy apróságot változtatni előbb.

Fájlfeltöltő

[Tartalom]

Egy konténeren belül futó webes szolgáltatást a konténer IP címét megadva is el lehet érni akár a hosztról, akár az azonos Docker hálózatban levő konténerekből. Így dolgozik a proxy szerver is, de meg kell neki adni azt az infót is, hogy milyen porton fut a szolgáltatás a konténerben. Ezt lehetne akár a Docker Compose fájlban is, de a Dockerfile-ba is beírható, hiszen az image készítője a saját image-ében definiált portokat ismeri. A fileshare/uploader/Dockerfile végére tehát bekerül a következő:

EXPOSE 8080

Bár az NginX szervernek a 80-as portját érjük el, a konténer 8080-as portján fut a PHP CLI szerver. A teljes Dockerfile tehát:

FROM php:8.0.5-cli

COPY index.php /var/www/index.php

CMD [ "php", "-S", "0.0.0.0:8080", "-t", "/var/www" ]

EXPOSE 8080

Fileshare docker-compose.yml

[Tartalom]

Az image-ek leíró fájljai már készen vannak, viszont a "fileshare" projektben a docker-compose.yml is aktualizálásra szorul.

  • Először is a "httpd" nevű service most konkrétabban "downloads" lesz, aminek megfelelően a "context" értéke is megváltozik.
  • A "network_mode: host" helyett most a "default" projekthez hasonlóan itt is az external proxy hálózatra kell hivatkozni.
  • Megadjuk szintén a környezeti változóban a domain nevet, ami eddig a virtuális hoszt definiícióban volt.
  • A változó gyökérkönyvtár miatt a downloads szolgáltatásban a "files" volume felcsatolásánál a "target" könyvtár is változik /usr/local/apache2/htdocs/files-ra

Az egyenkénti kódsorok helyett most már gyakorlottabban nézhetjük egyben a teljes docker-compose.yml fájlt a fileshare mappában:

version: "3.9"

volumes
:
  files
:

networks
:
  proxy
:
    external
: true
    name
: proxy-web

services
:
  downloads
:
    build
:
      context
: downloads
      dockerfile
: Dockerfile
    volumes
:
      - type
: volume
        source
: files
        target
: /usr/local/apache2/htdocs/files
        read_only
: true
    environment
:
      VIRTUAL_HOST
: downloads.127.0.0.1.nip.io
    networks
:
     - proxy
 
  uploader
:
    build
:
      context
: uploader
      dockerfile
: Dockerfile
    volumes
:
      - type
: volume
        source
: files
        target
: /var/www/files
    environment
:
      VIRTUAL_HOST
: 127.0.0.1.nip.io
    networks
:
     - proxy

És igen, nincs más hátra, mint el is indítani a projektet:

docker-compose up -d

Végszó

[Tartalom]

Most már minden oldalunk a proxy-n keresztül érhető el. A konténerben tetszőleges porton futhatnak a szolgáltatások, aminek az értékét az "EXPOSE" metaadat definiálásával adjuk meg az NginX számára. A felhasználók szemszögéből ez ugyanúgy fog működni, mint a virtuális hosztok működtek, de már egymástól teljesen függetlenül tudjuk az egyes szolgáltatásokat frissíteni, illetve a HTTPD helyett tetszőleges szerver programokat használhatunk.

Igen, ezzel viszont egy újabb szoftvert kell karban tartanunk, mivel az NginX-en megy keresztül minden forgalom, annak konfigurációja is befolyásolja az alkalmazásaink működését, ezért az NginX konfigurációjával is tisztában kell lennünk. Adott esetben módosíthatjuk az alapértelmezett template-et, vagy fel is csatolhatunk további konfigurációs fájlokat egy adott hosztra vonatkozóan, vagy a teljes nginx-re érvényeset. Ennek mikéntjéről a Docker Hub-on is lehet olvasni.

Ha tetszett a cikk, lepj meg egy like-kal. Kérés vagy kérdés esetén pedig fordulj hozzám bizalommal. A hozzászólásaiddal te is alakíthatod az IT sziget tartalmait.

Kategóriák: 
Megosztás/Mentés