protocol buffer e libra

Protocol buffers e gRPC

Protocol Buffesr o protobuff per gli amici è un protocollo versatile per serializzare dati strutturati e definire servizi RPC.

Perchè protocol buffers è versatile?

  • E’ indipendente dai linguaggi di programmazione.
  • E’ indipendente dalle piattaforme.
  • Può essere utilizzato nei protocolli di comunicazione, come nei data storage.
  • E’ molto ben documentato.
message Person {
  string name = 1;
  int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}

Come potete vedere la sintassi è molto semplice. Andiamo per gradi.
Con “message Person { ..}” definiamo un tipo di messaggio chiamato Person.
string name = 1 è uno dei quattro field appartenenti al messaggio.
Possiamo usare le keyword optional se vogliamo renderlo opzionale, oppure repeated se vogliamo ripeterlo da 0 a molte volte.
String indica che il tipo di field è una stringa, name è il nome del field, mentre “= 1” non sta ad indicare che il field ha valore 1, ma in protobuff ogni definizione dei field deve avere un numero univoco, identificativo.
la documentazione ufficiale dice:

As you can see, each field in the message definition has a unique number. These field numbers are used to identify your fields in the message binary format, and should not be changed once your message type is in use. 

Per quanto riguarda tutti i tipi di variabile, con la relativa conversione nel linguaggio scelto, la Documentazione ufficiale ci viene incontro con una tabella ben dettagliata.

I messaggi sono niente senza RPC (Remote Procedure Call)

Per utilizzare i messaggi definiti in precedenza attraverso un sistema RPC è necessario definire l’interfaccia di un servizio in un .proto file.

service SearchService {
  rpc Search (SearchRequest) returns (SearchResponse);
}

Protocol Buffer compilerà il file .proto e genererà il codice dell’interfaccia e degli stub (nel linguaggio da te scelto. Un esempio più completo, preso da qua:

syntax = "proto3";

service FooService {
  rpc GetFoo (GetFooRequest) returns (GetFooResponse);
}
message GetFooRequest {
  string id = 1;
}
message GetFooResponse {
  string fooName = 1;
  int64 fooValue = 2;
}

gRPC ..wut?

gRPC

gRPC può usare i Protocol Buffer sia per le definizioni delle interfacce ( i service), sia come formato di interscambio di messaggi (i message).

Installazione

Prima di tutto è necessario creare un virtualenv di python 3.6, per chi ha mkvirtuaenv:

$ mkvirtualenv grpc_test --python=/usr/bin/python3.6

altrimenti:

$ python -m pip install virtualenv
$ virtualenv grpc_test -p=python3.6
$ source venv/bin/activate
$ python -m pip install --upgrade pip

Successivamente attivate il virtualenv e poi:

$ pip install grpcio
$ pip install grpcio-tools

Salviamo il file di prima con il nome example.proto e lanciamo il seguente comando:

$ python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. example.proto

l’output saranno due file python. Il primo example_pb2.py

# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: example.proto

import sys
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()




DESCRIPTOR = _descriptor.FileDescriptor(
  name='example.proto',
  package='',
  syntax='proto3',
  serialized_options=None,
  serialized_pb=_b('\n\rexample.proto\"\x1b\n\rGetFooRequest\x12\n\n\x02id\x18\x01 \x01(\t\"3\n\x0eGetFooResponse\x12\x0f\n\x07\x66ooName\x18\x01 \x01(\t\x12\x10\n\x08\x66ooValue\x18\x02 \x01(\x03\x32\x37\n\nFooService\x12)\n\x06GetFoo\x12\x0e.GetFooRequest\x1a\x0f.GetFooResponseb\x06proto3')
)




_GETFOOREQUEST = _descriptor.Descriptor(
  name='GetFooRequest',
  full_name='GetFooRequest',
  filename=None,
  file=DESCRIPTOR,
  containing_type=None,
  fields=[
    _descriptor.FieldDescriptor(
      name='id', full_name='GetFooRequest.id', index=0,
      number=1, type=9, cpp_type=9, label=1,
      has_default_value=False, default_value=_b("").decode('utf-8'),
      message_type=None, enum_type=None, containing_type=None,
      is_extension=False, extension_scope=None,
      serialized_options=None, file=DESCRIPTOR),
  ],
  extensions=[
  ],
  nested_types=[],
  enum_types=[
  ],
  serialized_options=None,
  is_extendable=False,
  syntax='proto3',
  extension_ranges=[],
  oneofs=[
  ],
  serialized_start=17,
  serialized_end=44,
)


_GETFOORESPONSE = _descriptor.Descriptor(
  name='GetFooResponse',
  full_name='GetFooResponse',
  filename=None,
  file=DESCRIPTOR,
  containing_type=None,
  fields=[
    _descriptor.FieldDescriptor(
      name='fooName', full_name='GetFooResponse.fooName', index=0,
      number=1, type=9, cpp_type=9, label=1,
      has_default_value=False, default_value=_b("").decode('utf-8'),
      message_type=None, enum_type=None, containing_type=None,
      is_extension=False, extension_scope=None,
      serialized_options=None, file=DESCRIPTOR),
    _descriptor.FieldDescriptor(
      name='fooValue', full_name='GetFooResponse.fooValue', index=1,
      number=2, type=3, cpp_type=2, label=1,
      has_default_value=False, default_value=0,
      message_type=None, enum_type=None, containing_type=None,
      is_extension=False, extension_scope=None,
      serialized_options=None, file=DESCRIPTOR),
  ],
  extensions=[
  ],
  nested_types=[],
  enum_types=[
  ],
  serialized_options=None,
  is_extendable=False,
  syntax='proto3',
  extension_ranges=[],
  oneofs=[
  ],
  serialized_start=46,
  serialized_end=97,
)

DESCRIPTOR.message_types_by_name['GetFooRequest'] = _GETFOOREQUEST
DESCRIPTOR.message_types_by_name['GetFooResponse'] = _GETFOORESPONSE
_sym_db.RegisterFileDescriptor(DESCRIPTOR)

GetFooRequest = _reflection.GeneratedProtocolMessageType('GetFooRequest', (_message.Message,), dict(
  DESCRIPTOR = _GETFOOREQUEST,
  __module__ = 'example_pb2'
  # @@protoc_insertion_point(class_scope:GetFooRequest)
  ))
_sym_db.RegisterMessage(GetFooRequest)

GetFooResponse = _reflection.GeneratedProtocolMessageType('GetFooResponse', (_message.Message,), dict(
  DESCRIPTOR = _GETFOORESPONSE,
  __module__ = 'example_pb2'
  # @@protoc_insertion_point(class_scope:GetFooResponse)
  ))
_sym_db.RegisterMessage(GetFooResponse)



_FOOSERVICE = _descriptor.ServiceDescriptor(
  name='FooService',
  full_name='FooService',
  file=DESCRIPTOR,
  index=0,
  serialized_options=None,
  serialized_start=99,
  serialized_end=154,
  methods=[
  _descriptor.MethodDescriptor(
    name='GetFoo',
    full_name='FooService.GetFoo',
    index=0,
    containing_service=None,
    input_type=_GETFOOREQUEST,
    output_type=_GETFOORESPONSE,
    serialized_options=None,
  ),
])
_sym_db.RegisterServiceDescriptor(_FOOSERVICE)

DESCRIPTOR.services_by_name['FooService'] = _FOOSERVICE

# @@protoc_insertion_point(module_scope)

il secondo example_pb2_grpc.py

# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
import grpc

import example_pb2 as example__pb2


class FooServiceStub(object):
  # missing associated documentation comment in .proto file
  pass

  def __init__(self, channel):
    """Constructor.

    Args:
      channel: A grpc.Channel.
    """
    self.GetFoo = channel.unary_unary(
        '/FooService/GetFoo',
        request_serializer=example__pb2.GetFooRequest.SerializeToString,
        response_deserializer=example__pb2.GetFooResponse.FromString,
        )


class FooServiceServicer(object):
  # missing associated documentation comment in .proto file
  pass

  def GetFoo(self, request, context):
    # missing associated documentation comment in .proto file
    pass
    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
    context.set_details('Method not implemented!')
    raise NotImplementedError('Method not implemented!')


def add_FooServiceServicer_to_server(servicer, server):
  rpc_method_handlers = {
      'GetFoo': grpc.unary_unary_rpc_method_handler(
          servicer.GetFoo,
          request_deserializer=example__pb2.GetFooRequest.FromString,
          response_serializer=example__pb2.GetFooResponse.SerializeToString,
      ),
  }
  generic_handler = grpc.method_handlers_generic_handler(
      'FooService', rpc_method_handlers)
  server.add_generic_rpc_handlers((generic_handler,))

Adesso implementiamo il server my_server.py:

from concurrent import futures
import time
import logging

import grpc

import example_pb2
import example_pb2_grpc

_ONE_DAY_IN_SECONDS = 60 * 60 * 24


class FooService(example_pb2_grpc.FooServiceServicer):

    def GetFoo(self, request, context):
        return example_pb2.GetFooResponse(fooName='Hello, %s!' % request.id)


def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    example_pb2_grpc.add_FooServiceServicer_to_server(FooService(), server)
    server.add_insecure_port('[::]:50051')
    server.start()
    try:
        while True:
            time.sleep(_ONE_DAY_IN_SECONDS)
    except KeyboardInterrupt:
        server.stop(0)


if __name__ == '__main__':
    logging.basicConfig()
    serve()

Da notare il fatto che
la definizione nel nostro Proto file è passata da;

service FooService {
  rpc GetFoo (GetFooRequest) returns (GetFooResponse);
}

a così in Python:

class FooService(example_pb2_grpc.FooServiceServicer):

    def GetFoo(self, request, context):
        return example_pb2.GetFooResponse(fooName='Hello, %s!' % request.id)

il parametro request in ingresso alla GetFoo non è altro che un’istanza della classe GetFooRequest. Infatti accediamo al parametro id.

Il client sarà my_client.py:

 
from __future__ import print_function
import logging

import grpc

import example_pb2
import example_pb2_grpc


def run():
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = example_pb2_grpc.FooServiceStub(channel)
        response = stub.GetFoo(example_pb2.GetFooRequest(id='ciao ale'))
    print("Greeter client received: " + response.fooName)


if __name__ == '__main__':
    logging.basicConfig()
    run()

Per far funzionare il tutto, basterà lanciare prima my_server.py e successivamente my_client.py e l’output che ottenendo questo output:

$ Greeter client received: Hello, ciao ale!

Perchè questa guida? Perchè Libra ha tutti i servizi definiti in .proto files ed un codice in Rust.

FA instagram

Come creare un instagram bot per automatizzare like e follow.

Se io fossi Aranzulla inizierei scrivendo questo post dicendovi cosa è Instagram, ma io non sono Aranzulla (sempre sia lodato) e quindi assumo il fatto che voi sappiate cosa è Instagram.
Un Bot invece è un software che automatizza dei compiti che solitamente dovrebbero essere svolti a mano. In definitiva un instagram bot, è un software che automatizza delle azioni su Instagram. Like, commenti, follow e defollow.

E’ legale usare dei Bot su instagram?

No.

Ma ci sono dei servizi a pagamento che forniscono dei bot!

Nel caso in cui non siano ufficialmente autorizzati da Instagram ad usare le loro API, li puoi usare a tuo rischio e pericolo. Il bot che ti illustrerò successivamente è gratuito, ma viole le policy di Instagram.

Maggiori informazioni qua

Come funziona questo Instagram bot in python?

instabot.py è un progetto Open Source che potete trovare su Github e funziona fingendosi un browser web e usando le API web di Instagram. Infatti spulciando nel suo codice si trova questa sezione qua:

    "list_of_ua": [
        "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; FSL 7.0.6.01001)",
        "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; FSL 7.0.7.01001)",
        "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; FSL 7.0.5.01003)",
        "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0",
        "Mozilla/5.0 (X11; U; Linux x86_64; de; rv:1.9.2.8) Gecko/20100723 Ubuntu/10.04 (lucid) Firefox/3.6.8",
        "Mozilla/5.0 (Windows NT 5.1; rv:13.0) Gecko/20100101 Firefox/13.0.1",
        "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:11.0) Gecko/20100101 Firefox/11.0",
        "Mozilla/5.0 (X11; U; Linux x86_64; de; rv:1.9.2.8) Gecko/20100723 Ubuntu/10.04 (lucid) Firefox/3.6.8",
        "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705)",
        "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)",
        "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)",
        "Opera/9.80 (Windows NT 5.1; U; en) Presto/2.10.289 Version/12.01",
        "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)",
        "Mozilla/5.0 (Windows NT 5.1; rv:5.0.1) Gecko/20100101 Firefox/5.0.1",
        "Mozilla/5.0 (Windows NT 6.1; rv:5.0) Gecko/20100101 Firefox/5.02",
        "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.112 Safari/535.1",
        "Mozilla/4.0 (compatible; MSIE 6.0; MSIE 5.5; Windows NT 5.0) Opera 7.02 Bork-edition [en]",

che elenca i possibili Usera Agent con cui instabot si presenta a Instagram.

Primo step, installare il virtualenvwrapper, per gestire al meglio i virtualenv.

sudo pip install virtualenvwrapper

Chiudiamo il terminale e riapriamolo. Creiamo una cartella:

mkdir InstagramBot

e creaiamo il virtualenv:

mkvirtualenv instabot --python=/usr/bin/python3.6

mkvirtualenv è il comando per creare il virtualenv, instabot è il nome del’ambiente e con “–python=/usr/bin/python3.6” specifichiamo la versione di python da usare

Un rapido controllo per vedere se il virtuaenv è attivo, è quello di notare la scritta a sinistra del nome nel terminale:

(instabot) fabio@apptec-dev ~/instabot

Se così non fosse, basta lanciare il comando:

workon instabot

Attivato il virtulenv non rimane che installare i pacchetti:

pip install instabot-py
pip install pyyaml

pyyaml bisogna installarlo a mano, perché per qualche arcano motivo non è incluso fra le dipendenze di instabot.

Per avviare instabot si deve lanciare il comando

instabot-py

e vi verranno poste delle domande che vanno dal nome utente, a come commentare, come seguire e così via.
Nel caso in cui aveste risposto in maniera errata, avete due possibilità:
La prima è quella di premere CTRL+C e uscire dall’applicazione, cancellare il file .sessions, il file instabot.config.ini e il file sqlite.db
L’altra possibilità è quella di arrivare fino in fondo alla procedura di configurazione e poi aprire il file instabot.config.ini ed editare i campi che contenevano gli errori.

Anche se la procedura si è conclusa a dovere, vi consiglio di aprire il file instabot.config.ini e di controllare che tutto sia configurato a dovere.
(Fidarsi è bene, non fidarsi è meglio)

nano instabot.config.ini

Ricordatevi di non esagerare con i like, con i follow e defollow, ma sopratutto non condividete il file instabot.config.ini con nessuno, perché sopra c’è salvata la vostra password in chiaro.

Per ri-avviare l’applicazione basterà lanciare il comando:

instabot-py

(assicuratevi sempre di avere il virtuaenv attivo)

Coding

Configurare le Bitbucket pipelines per eseguire i test di Django

Le Bitbucket pipelines sono un potente strumento per eseguire i deploy (che vedremo in seguito) in sicurezza e in un ambiente controllato. Sopratutto senza grosse perdite di tempo.
Configurare le Bitbucket pipeline non è difficile. Vediamo come.
Ad oggi, le Bitbicket Pipelines offrono “gratuitamente” 50 minuti di tempo macchina. Per progetti piccoli può andare, ma per progetti medio grandi sono estremamente pochi.

Prezzo bitbucket pipelines
Prezzo delle bitbucket pipeline a giugno 2019

Inoltre è possibile acquistare minuti di compilazione per specifici account

Bitckuet prezzo minuti per account
Prezzo per l’acquisto di minuti per account

Per abilitare le pipeline basta andare su “Settings” e dal sotto menu “Pipeline” andare ancora su “settings” e poi spuntare “Enable Pipelines”

La prima cosa che ti chiederà Bitbucket per configurare le pipelines sarà quella di creare e riempire il file “bitbucket-pipelines.yml”
Un esempio di configurazione minima del file è la seguente:

image: python:3.5.1

pipelines:
  branches:
    master:
      - step:
          caches:
            - pip
          script:
            - pip install -r requirements.txt
            - python manage.py test --settings=bay.settings_test
          services:
            - postgres

definitions:
  services:
    postgres:
      image: postgres:10
      variables:
        POSTGRES_DB: 'pipelines'
        POSTGRES_USER: 'test_user'
        POSTGRES_PASSWORD: 'test_user_password'

Definizione delle pipelines

Visto che è quasi estate, il progetto di Django di prova abbiamo deciso di chiamarlo “bay”.
Tornando al problema principale di come configurare le Bitbucket Pipelines, vediamo che cosa ho scritto nel file di configurazione.

Image: python:3.5.1 specifica l’immagine di Docker da usare per eseguire i test. Solitamente noi per django usiamo le immagini ufficiali di Python o quelle di ubuntu. Le prime sono più leggere ( niente a che vedere con le alpine però), le seconde più pesanti ma più semplici da configurare sopratutto se ci sono tante dipendenze.
La sezione pipelines specifica i comandi che verranno eseguiti sugli specifici branch. Nel nostro caso abbiamo tutti i trigger attivati solamente su master.
Il primo step riguarda la gestione delle dipendenze di python con pip.
Il comando di caches ordina a Bitbucket di salvarsi i pacchetti di pip in locale per poi riutilizzarli per le successive build. Questo comando velocizza non di poco la creazione delle build successive.
Praticamente Bitbucket andrà a cercare i pacchetti prima in locale e successivamente sui server di pip.
I comandi sotto la sezione script sono quelli necessari per installare le dipendenze ed eseguire i test. “pip insyall -r requirements.txt” scarica le dipendenze e le installa, mentre “python manage.py test –settings=bay.settings_test” esegue i test di django, però con un settings file diverso da quello standard.
Perchè?
Una delle difficoltà che ho avuto con Bitbucket è stata quella relativa alla configurazione delle variabili di ambiente. Allora la soluzione che abbiamo trovato è stata quella di creare un secondo file di settings, che importa tutte le definizioni di quello standard, ma che va a sovrascrivere solo alcuni settaggi. In parole povere il file settings_test.py è queste 13 righe:

from .settings import *


DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'pipelines',
        'USER': 'test_user',
        'PASSWORD': 'test_user_password',
        'HOST': '127.0.0.1',
        'PORT': '5432',
    }
}

La sezione services indica a quale altri servizi si dovrà attaccare il nostro applicativo. Per adesso soltanto a postgres.

Definizioni dei servizi

Sotto la sezione definitions vengono definiti tutti i servizi necessari affinché gli script della pipelnes possano essere eseguiti. L’unico servizio che al momento ci serve è postgres ed è configurato nella seguente maniera:

  services:
    postgres:
      image: postgres:10
      variables:
        POSTGRES_DB: 'pipelines'
        POSTGRES_USER: 'test_user'
        POSTGRES_PASSWORD: 'test_user_password'

Le variabile POSTGRES_DB, POSTGRES_USER e POSTGRES_PASSWORD sono le medesime che abbiamo “hardcodato” nel settings_test.py

Per testare la pipeline basterà eseguire push su master ed aspettare che la compilazione (che coincide con l’instalalzione ed esecuzione dei test) finisca.
Nel caso in cui un test fallisca, anche la pipeline fallisce.
Prima di concludere vi avviso che i 50 minuti di compilazione “offerti” da Bitbucket sono veramente pochi. Anche perché sono suddivisi fra tutti i repo dell’utente.

Linux

Come editare il file hosts su linux

In questo breve articolo vi spiegherò cosa è il file di hosts e come editarlo su linux.
Editare il file di hosts su linux è molto semplice perchè ha una sintassi molto semplice, e in maniera un pò banale è possibile definirlo come “il primo DNS” che contatta il Sistema Operativo.

Perchè modificare il file hosts?

I casi in cui conviene modificare il file hosts sono molteplici, per esempio:

  • Bloccare un dominio
  • Testare le configurazioni di apache
  • Simulare un piccolo network

Passiamo alla pratica, editiamo il file di hosts su linux.

Il file di Hosts sui sistemi linux lo troviamo nella cartella /etc/ quindi per leggerlo possiamo lanciare il seguente comando:

less /etc/hosts

e l’output, nel mio caso è:

127.0.0.1       localhost
127.0.1.1       foobar

# The following lines are desirable for IPv6 capable hosts
::1     localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

In parole povere, digitando localhost sulla barra del browser risponderà l’indirizzo 127.0.0.1, uguale digitando foobar.
Possiamo editare il file di hosts usando vi oppure se siete utenti meno esperti c’è nano.

sudo nano /etc/hosts
sudo vi /etc/hosts

Mentre in lettura abbiamo potuto leggere il file di hosts senza essere superutenti, per editare il file di hosts è necessario essere superutenti.
Una volta aperto il file di hosts, ci posizionaniamo alla riga sotto 127.0.0.1, scriviamo l’indirizzo IP che desideriamo, premiamo TAB e poi scriviamo il dominio.
Per esempio:

127.0.0.1       localhost
127.0.1.1       OpenPingu
192.168.1.21    foo.apptecsrl.com

# The following lines are desirable for IPv6 capable hosts
::1     localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

Una volta salvato, se digito sul browser foo.apptecsrl.com mi risponderà l’indirizzo IP 192.168.1.21

Cosa possiamo fare in pratica?

Se vogliamo bloccare un dominio, basta ridirigire il dominio target su un indirizzo IP, anche localhost. Per esempio:

127.0.1.1       www.ilgiornale.it

In questo caso, quando sul PC digiti il suddetto indirizzo web sarai reindirizzato alla porta 80 del tuo pc.
Per testare le configurazioni di Apache bisogna modificare il file di hosts sia della macchina che effettua la richiesta, sia quella che la gestisce.
Supponiamo che la macchina che ha il webserver risponda all’indirizzo 150.217.23.45 e al dominio www.example.com. Il nostro obiettivo è testare il servizio che risponderà all’indirizzo foo.example.com
Sia sullla macchina server, che sulla nostra modifichiamo il file hosts inserendo la stringa:

150.217.23.45      foo.example.com

Soltanto chi avrà il file hosts modificato potrà accedere al servizio che risponde al dominio foo.example.com

Infine, possiamo simulare anche un piccolo network. Assumiamo che vogliamo testare una futura configurazione composta da 6 macchine ed ognuna al suo interno ha un demone di Docker. Tutte insieme fanno parte del solito stack dove è stata deployata la solita Webapp.
In questa configurazione ci possiamo veramente sbizzarrire, anche perchè possiamo partire da configurazioni più semplici, fino a finire con configurazioni che includono dei load balancer.

Coding

Deploy di Django con Apache/httpd e mod_wsgi

Premetto che ho intrapreso una personale crociata nei confronti dei deploy di Django con Apache. Spesso e volentieri mi trovo a dover utilizzare ancora questa modalità di deploy per motivi di forza maggiore.

Partiamo da dei concetti semplicissimi che devono essere dei capisaldi per ogni deploy, i file statici devono essere gestiti da servizi esterni ( ad esempio amazon S3), l’applicazione python deve essere dentro un virtualenv e devono poter convivere più applicazioni web assieme.
Sulle distribuzioni debian based, ubuntu compresa, è possibile installare il mod-wsgi lanciando il comando:

sudo apt-get install libapache2-mod-wsgi-py3

Mentre su Centos e derivate è possibile installare il modulo con:

sudo yum install python36u-mod_wsgi

Una precisazione, nei repository quando non viene specificata la versione di Python, viene assunto che il modulo è per python 2.7.x.
Successivamente, ricordatevi di riavviare Apache/httpd affinchè le modifiche abbiano effetto.

Permessi sui files

Passiamo adesso alla parte più noiosa, quella delle path e dei permessi.
Assumiamo che il vostro progetto di django si chiami test_app ( visto che fantasia!?) ed è installato nella path /home/foo/test_app/ mentre il virtualenv è installato nella path /home/foo/test_app_env/
Per quanto riguarda i permessi, di solito eseguo le seguenti operazioni:

sudo chmod +x /home/foo/test_app_env/

Il chmod + x sulle cartelle permette di entrare ed accedere ai file e cartelle. Successimavamente lancio:

find /home/foo/test_app_env -type d -exec chmod 755 {} \;
find /home/foo/test_app_env -type f -exec chmod 644 {} \;

Con il primo comando setto i permessi sulle cartelle e con il secondo sui file. In teoria adesso dovremmo essere in regola con i permessi.
E adesso il file di configurazione di VirtualHost che è il cuore del deploy di Django con Apache.

Configurazione di Apache per il deploy di Django


Nelle centos solitamente il file è posizionato in /etc/httpd/conf.d/ mentre sulle debian/ubuntu in /etc/apache2/conf.d/sites-available/ e bisogna usare gli script a2ensite e a2dissite

<VirtualHost *:80>  
ServerName foo.apptecsrl.com  

WSGIScriptAlias / /home/foo/test_app/test_app/wsgi.py 
WSGIDaemonProcess test_app processes=5 python-path=/home/foo/test_app:/home/foo/test_app_env/lib/python3.6/site-packages:/home/foo/test_app_env/lib/python3.6 threads=1 
WSGIProcessGroup test_app 
Alias /static/ /home/foo/test_app/test_app/public/static/ 
Alias /media/ /home/foo/test_app/test_app/public/media/ 
 
<Directory /home/foo/test_app/>  
  AllowOverride all  
  Require all granted  
  Options Indexes FollowSymlinks  
</Directory>  
 
</VirtualHost> 

Nota di merito di questa configurazione base, va alle direttive: WSGIDaemonProcess e ServerName.


ServerName specifica a quale nome risponde la configurazione. Quindi digitando foo.apptecsrl.com vi risponderà la configurazione appena creata.
Configurazioni diverse, con ServerName diversi corrisponsono a siti diversi. (Potete testare la configurazione editanto il file hosts)


WSGIDaemonProcess è la direttiva che specifica quanti demoni distinti devono essere creati. Ad ogni demone viene delegata l’esecuzione dell’applicazione wsgi.
test_app è il display-name dei singoli demoni, process=5 specifica il numero dei demoni che saranno avviati, nel nostro caso saranno 5.
python-path è la lista delle path necessarie per avviare la nostra applicazione. Nel nostro caso abbiamo specificato la path dell’applicazione, la site-packages del virtualenv e il binario dell’interprete.
threads=1 specifica il numero di thread che saranno creati da ogni demone per gestire la richiesta.


Ci tengo a precisare che questa configurazione l’ho usata per anni su un e-commerce con un traffico medio/basso, non ho mai avuto problemi di prestazioni, ma mi rendo anche conto che è una configurazione per niente ottimizzata.