El is érkeztünk a második részhez, amiben egy kis alkalmazást fogunk összerakni külön kliens és szerver oldali konténerrel. Aki esetleg lemaradt volna az előző részről, itt tudja pótolni.

A szerver oldalt a változatosság kedvéért most a Python fogja képviselni a Flask keretrendszerrel és az SQLAlchemy ORM-mel. A kliens oldalon a Mithril.js keretrendszer lesz segítségünkre a JavaScript-ben, a CSS-t pedig a Milligram szolgáltatja. A függőségek beszerzéséről az npm gondoskodik és végül az egészet a Webpack segítségével fogjuk egybegyúrni. A kliens oldal szereti a sok szereplőt. No de vágjunk is bele.

Az adatbázis

Egy hasonló példával fejeztük be az előző részt, úgyhogy ebben nem lesz sok újdonság:

docker-compose.yml
version: "3"
volumes:
  database-data:
services:
  database:
    image: mysql:5.7
    volumes:
      - database-data:/data
    environment:
      MYSQL_ROOT_PASSWORD: test
      MYSQL_DATABASE: test
      MYSQL_USER: test
      MYSQL_PASSWORD: test

Ha valamilyen oknál fogva közelebbről is meg szeretnénk vizsgálni ezt az adatbázist és a tartalmát, azt a

docker-compose run --rm database mysql -hdatabase -utest -ptest test

paranccsal tehetjük meg. Amennyiben ez galád módon olyasmire panaszkodik, hogy nem ismeri a "database" host-ot, akkor futtassunk egy ilyen parancsot is és próbáljuk újra:

docker-compose start database

A szerver oldal

A Python csomagkezelés egy kicsit eltér a PHP-ban vagy Node.js-ben megszokottól. Docker nélkül valószínűleg Virtualenv-et használnánk és azon belül telepítenénk a függőségeket, de így a legegyszerűbb az lesz, ha a Dockerfile-ban adjuk meg és a konténer build-elése során települnek majd.

server/Dockerfile
FROM python:3.6-alpine
RUN pip install Flask SQLAlchemy Flask-SQLAlchemy MySQL-Connector
WORKDIR /app/src
EXPOSE 5000
CMD ["flask", "run", "--host=0.0.0.0"]

A Flask alkalmazás az 5000-es porton fog alapból figyelni, úgyhogy ezt jelezzük a Docker felé is. A compose fájl services része pedig a következőkkel egészül ki:

docker-compose.yml
server:
  build: ./server
  depends_on:
    - database
  volumes:
    - ./server:/app
  environment:
    PYTHONDONTWRITEBYTECODE: 1
    FLASK_APP: /app/src/app.py
    DATABASE_URI: mysql+mysqlconnector://test:test@database/test

Környezeti változók segítségével megmondjuk a Python-nak, hogy ne generáljon .pyc és .pyo fájlokat, a flask run-nak, hogy hol találja az alkalmazásunkat, az alkalmazásunknak pedig azt, hogy hogyan tud csatlakozni az adatbázishoz.

A teljes alkalmazás kódjától megkímélem az olvasókat, az a kapcsolódó GitHub repo-ban megtekinthető az összes korábbi példával együtt. Egy apró részletet emelnék csak ki:

server/src/app.py
# az alkalmazás többi részének a helye

if __name__ == '__main__':
    db.create_all()

Ez egy kis egyszerűsítés, hogy könnyebben létre tudjuk hozni az adatbázis szerkezetet a frissen felhúzott MySQL-ben, amit így az alábbi módon tudunk megtenni:

docker-compose run --rm server python -m app

A kliens oldal

Hirtelen nem is tudom hol kezdjem, lesz dolgunk bőven. Talán essünk túl gyorsan a függőségeken:

client/package.json
{
  "devDependencies": {
    "babel-core": "^6.23.1",
    "babel-loader": "^6.3.2",
    "babel-preset-es2015": "^6.22.0",
    "css-loader": "^0.26.1",
    "milligram": "^1.3.0",
    "mithril": "^1.0.1",
    "style-loader": "^0.13.1",
    "webpack": "^2.2.1",
    "webpack-dev-server": "^2.3.0"
  }
}

Az első változatban még React-ot használtam, a hozzá tartozó babel csomaggal, egy külön HTTP klienssel és még Less-t is fordítottam.

Aztán megpróbáltam egyszerűsíteni egy kicsit a dolgokon, aminek ez lett az eredménye. Így a client könyvtárban kiadott npm install parancs már csak a fél világot fogja letölteni nekünk, nem az egészet. Amíg ez megtörténik, térjünk át a webpack konfigurálására.

client/webpack.config.js
const path = require('path');
const webpack = require('webpack');

module.exports = {
  entry: './src/app.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
    publicPath: '/dist/'
  }
};

Nem kell megijedni, ez nem a teljes fájl. Ennyivel nem ússzuk meg. Első körben csak megmondjuk a webpack-nak, hogy hol kezdődik az alkalmazásunk és hova szeretnénk tenni a végterméket. A következő lépésben beállítjuk, hogy mit kezdjen a CSS és JS fájlokkal:

client/webpack.config.js
module.exports = {
  // a korábbi beállítások helye

  module: {
    rules: [
      {
        test: /\.css$/,
        use: [ 'style-loader', 'css-loader' ]
      },
      {
        test: /\.js$/,
        use: [
          { loader: 'babel-loader', options: { presets: [ 'es2015' ] } }
        ]
      }
    ]
  }
};

Ebben a formában a CSS is a JS kódba fog belekerülni, ami azt eredményezi, hogy az oldal CSS nélkül jelenik meg egy pillanatra a betöltődés során. Egy példaalkalmazásnál azt hiszem ezzel együtt lehet élni, de még több függőség behúzásával természetesen ez a probléma is megoldható.

Végül, de nem utolsó sorában hátra van még a dev szerver konfigurálása. Itt van egy olyan huncutság, hogy a /api/ kezdetű kéréseket átdobjuk a Python szervernek, hogy ő kezdjen velük valamit.

client/webpack.config.js
module.exports = {
  // a korábbi beállítások helye

  devServer: {
    host: '0.0.0.0',
    proxy: {
      '/api/*': {
        target: 'http://server:5000',
        pathRewrite: { '^/api' : '' }
      }
    }
  }
};

Ha szerencsénk van, mostanra végzett az npm install, úgyhogy át is térhetünk a dolgok konténeres részére. Végül is ez lenne a fő téma.

client/Dockerfile
FROM node:6.9-alpine
WORKDIR /app
EXPOSE 8080
CMD ["/app/node_modules/.bin/webpack-dev-server"]

Semmi extra, a webpack dev szerverét fogjuk futtatni a konténerben, amit a 8080-as portra konfiguráltunk be az előző lépésekben. Természetesen a compose fájl services részét is ki kell még egészíteni.

docker-compose.yml
client:
  build: ./client
  depends_on:
    - server
  ports:
    - 8080:8080
  volumes:
    - ./client:/app

A kliens konténer függ a szerver konténertől, hogy a webpack dev szerverben beállított proxy-zás tudjon működni. A host 8080-as portját pedig bekötjük a konténer 8080-as portjára. Ezt a szerver konténer esetén nem volt szükséges, mivel csak a kliens konténeren keresztül fogunk vele kommunikálni.

Az alkalmazás kódtól itt is eltekintenék, a repóban megtalálható. Ha minden jól ment, egy docker-compose up kiadása után a http://127.0.0.1:8080/-t meglátogatva valami ilyesmit kellene látnunk:

Ezzel a végére is értünk a második résznek. A következő részben a Docker Compose néhány kevésbé szokványos felhasználását fogjuk megvizsgálni.