Back-End

12 jan, 2017

Trabalhando com Docker, Silex, Python, Node e WebSockets

Publicidade

Estou aprendendo o Docker. Neste artigo, quero compartilhar um pequeno experimento que eu fiz. Eu sei que o código parece mais complicado do que o necessário, mas é apenas uma desculpa para construir algo com o docker e contêineres. Deixe-me explicar um pouco.

A ideia é construir um relógio no navegador. Algo assim:

skitch

Sim, eu sei. Podemos fazê-lo apenas com js, css e html, mas quero trabalhar um pouco mais. A ideia é criar:

  • Uma interface em Silex/PHP
  • Um servidor WebSocket com socket.io/node
  • Um script Python para obter a hora atual

O servidor WebSocket abrirá 2 ports: um para servir os webSockets (socket.io) e outro como um servidor http (express). O script Python receberá a hora atual e a enviará para o servidor webSocket. Finalmente, uma interface (silex) estará ouvindo o evento do WebSocket e renderizará a hora atual.

Esse é o servidor WebSocket (com socket.io e express)

var
    express = require('express'),
    expressApp = express(),
    server = require('http').Server(expressApp),
    io = require('socket.io')(server, {origins: 'localhost:*'})
    ;
 
expressApp.get('/tic', function (req, res) {
    io.sockets.emit('time', req.query.time);
    res.json('OK');
});
 
expressApp.listen(6400, '0.0.0.0');
 
server.listen(8080);

Este é o nosso script Python

from time import gmtime, strftime, sleep
import httplib2
 
h = httplib2.Http()
while True:
    (resp, content) = h.request("http://node:6400/tic?time=" + strftime("%H:%M:%S", gmtime()))
    sleep(1)

E a nossa interface Silex

use Silex\Application;
use Silex\Provider\TwigServiceProvider;
 
$app = new Application(['debug' => true]);
$app->register(new TwigServiceProvider(), [
    'twig.path' => __DIR__ . '/../views',
]);
 
$app->get("/", function (Application $app) {
    return $app['twig']->render('index.twig', []);
});
 
$app->run();

Usando este template twig

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Docker example</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <link href="css/app.css" rel="stylesheet">
    <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
    <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
</head>
<body>
<div class="site-wrapper">
    <div class="site-wrapper-inner">
        <div class="cover-container">
            <div class="inner cover">
                <h1 class="cover-heading">
                    <div id="display">
                        display
                    </div>
                </h1>
            </div>
        </div>
    </div>
</div>
<script src="//localhost:8080/socket.io/socket.io.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script>
var socket = io.connect('//localhost:8080');
 
$(function () {
    socket.on('time', function (data) {
        $('#display').html(data);
    });
});
</script>
</body>
</html>

A ideia é usar um contêiner Docker para cada processo. Eu gosto de ter todo o código em um só lugar para que todos os contêineres compartilhem o mesmo volume com o código-fonte.

Primeiro, o contêiner de node (servidor WebSocket)

FROM node:argon
 
RUN mkdir -p /mnt/src
WORKDIR /mnt/src/node
 
EXPOSE 8080 6400

Agora o contêiner python

FROM python:2
 
RUN pip install httplib2
 
RUN mkdir -p /mnt/src
WORKDIR /mnt/src/python

E, finalmente, o container Frontend (apache2 com Ubuntu 16.04)

FROM ubuntu:16.04
 
RUN locale-gen es_ES.UTF-8
RUN update-locale LANG=es_ES.UTF-8
ENV DEBIAN_FRONTEND=noninteractive
 
RUN apt-get update -y
RUN apt-get install --no-install-recommends -y apache2 php libapache2-mod-php
RUN apt-get clean -y
 
COPY ./apache2/sites-available/000-default.conf /etc/apache2/sites-available/000-default.conf
 
RUN mkdir -p /mnt/src
 
RUN a2enmod rewrite
RUN a2enmod proxy
RUN a2enmod mpm_prefork
 
RUN chown -R www-data:www-data /mnt/src
ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
ENV APACHE_LOG_DIR /var/log/apache2
ENV APACHE_LOCK_DIR /var/lock/apache2
ENV APACHE_PID_FILE /var/run/apache2/apache2.pid
ENV APACHE_SERVERADMIN admin@localhost
ENV APACHE_SERVERNAME localhost
 
EXPOSE 80

Agora temos os três contêineres, mas queremos usar todos juntos. Usaremos um arquivo docker-compose.yml. O contêiner da web expõe a porta 80 e o contêiner node a 8080. O contêiner node também abre a 6400, mas essa é uma porta interna. Nós não precisamos acessar essa porta fora. Somente o contêiner Python precisa acessar essa porta, porquea  6400 não está mapeada para nenhuma porta em docker-compose

version: '2'
 
services:
  web:
    image: gonzalo123/example_web
    container_name: example_web
    ports:
     - "80:80"
    restart: always
    depends_on:
      - node
    build:
      context: ./images/php
      dockerfile: Dockerfile
    entrypoint:
      - /usr/sbin/apache2
      - -D
      - FOREGROUND
    volumes:
     - ./src:/mnt/src
 
  node:
    image: gonzalo123/example_node
    container_name: example_node
    ports:
     - "8080:8080"
    restart: always
    build:
      context: ./images/node
      dockerfile: Dockerfile
    entrypoint:
      - npm
      - start
    volumes:
     - ./src:/mnt/src
 
  python:
      image: gonzalo123/example_python
      container_name: example_python
      restart: always
      depends_on:
        - node
      build:
        context: ./images/python
        dockerfile: Dockerfile
      entrypoint:
        - python
        - tic.py
      volumes:
       - ./src:/mnt/src

E isso é tudo. Só precisamos iniciar nossos contêineres

docker-compose up --build -d

e abrir o nosso navegador em: http: // localhost para ver o nosso relógio.

Código-fonte completo disponível na minha conta no github.

***

Gonzalo Ayuso faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: https://gonzalo123.com/2017/01/02/playing-with-docker-silex-python-node-and-websockets/