Nello sviluppo software moderno, i microservizi sono emersi come un’architettura fondamentale, che consente scalabilità, flessibilità e gestione efficiente di sistemi complessi.
I microservizi sono applicazioni piccole e indipendenti che svolgono compiti specifici, consentendo una distribuzione e una scalabilità flessibili. Questo approccio modulare alla progettazione del software allenta l’accoppiamento (coupling) tra i componenti, migliorando la flessibilità e la gestibilità durante lo sviluppo.
Questo articolo fornisce una panoramica sui microservizi, sulle loro funzionalità e sulla loro creazione con Python. Dimostra inoltre come distribuire i microservizi su Kinsta utilizzando un Dockerfile.
Cosa sono i microservizi?
I microservizi sono servizi indipendenti e autonomi all’interno di un’applicazione, ognuno dei quali risponde a specifiche esigenze aziendali. Comunicano attraverso API leggere o message broker, formando un sistema completo.
A differenza dei sistemi monolitici che scalano interamente in base alla domanda, i microservizi permettono di scalare singoli componenti ad alto traffico. Questa architettura facilita la gestione dei guasti e gli aggiornamenti delle funzionalità, contrastando le limitazioni dei sistemi monolitici.
I vantaggi dell’uso dei microservizi sono molteplici, come ad esempio:
- Flessibilità e scalabilità: il disaccoppiamento dei singoli servizi permette di aumentare il numero di nodi che eseguono un’istanza di un particolare servizio ad alto traffico.
- Modularità del codice: ciascun servizio può utilizzare uno stack tecnologico distinto, il che significa che si possono scegliere gli strumenti di sviluppo migliori per ciascuno di essi.
Tuttavia, le architetture a microservizi presentano alcune sfide:
- Monitoraggio di più servizi: il monitoraggio dei singoli servizi di un sistema diventa difficile quando le istanze di un particolare servizio sono distribuite su più nodi. Questa difficoltà è particolarmente evidente in caso di guasti alla rete o altri problemi di sistema.
- Costi: lo sviluppo di applicazioni a microservizi può essere molto più costoso rispetto alla costruzione di sistemi monolitici a causa dei costi associati alla gestione di più servizi. Ogni servizio richiede la propria infrastruttura e le proprie risorse, il che può diventare costoso, soprattutto in caso di espansione del sistema.
Come progettare un microservizio con Python
Ora che conosciamo i vantaggi dell’utilizzo di un’architettura a microservizi, è il momento di costruirne una con Python.
Per questo esempio, supponiamo di voler costruire un’applicazione web di e-commerce. Il sito web ha diversi componenti, tra cui il catalogo dei prodotti, l’elenco degli ordini, il sistema di elaborazione dei pagamenti e i log, ognuno dei quali deve essere implementato come servizio indipendente. Inoltre, è necessario stabilire un metodo di comunicazione da servizio a servizio per trasferire i dati tra questi servizi, come ad esempio HTTP, in modo efficiente.
Costruiamo un microservizio in Python per gestire un catalogo di prodotti. Il microservizio recupererà i dati dei prodotti da una fonte specificata e li restituirà in formato JSON.
Prerequisiti
Per seguire questo tutorial, è necessario avere:
- Familiarità con Flask
- Python, Postman Client e Docker Desktop installati sulla macchina
1. Creare il progetto
- Per iniziare, creiamo una cartella per il progetto chiamata flask-microservice e la directory corrente nella directory del progetto.
- Eseguiamo quindi
python3 --version
per verificare che Python sia installato correttamente sul computer. - Installiamo
virtualenv
per creare un ambiente di sviluppo isolato per il microservizio Flask eseguendo questo comando:pip3 install virtualenv
- Creiamo un ambiente virtuale eseguendo questo comando:
virtualenv venv
- Infine, attiviamo l’ambiente virtuale utilizzando uno dei seguenti comandi in base al sistema operativo del computer:
# Windows: .\venv\Scripts\activate # Unix or macOS: source venv/bin/activate
2. Configurare un server Flask
Nella directory principale, creiamo un file requirements.txt e aggiungiamo queste dipendenze.
flask
requests
Eseguiamo il file pip3
command on your terminal to install the dependencies.
pip install -r requirements.txt
Successivamente, creiamo una nuova cartella nella directory principale e chiamiamola services. All’interno di questa cartella, creiamo un nuovo file, products.py, e aggiungiamo il codice sottostante per configurare un server Flask.
import requests
import os
from flask import Flask, jsonify
app = Flask(__name__)
port = int(os.environ.get('PORT', 5000))
@app.route("/")
def home():
return "Hello, this is a Flask Microservice"
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=port)
Nel codice qui sopra, viene configurato un server Flask di base. Inizializza un’applicazione Flask, definisce una singola route per l’URL principale ("/"
) e, quando vi si accede, visualizza il messaggio "Hello, this is a Flask Microservice"
. Il server viene eseguito su una porta specificata, ottenuta da una variabile d’ambiente o predefinita alla porta 5000
, e si avvia in modalità di debug per essere pronto a gestire le richieste in arrivo.
3. Definire gli endpoint dell’API
Una volta configurato il server, creiamo un endpoint API per un microservizio che recupera i dati dei prodotti da un’API disponibile pubblicamente. Aggiungiamo questo codice al file products.py:
BASE_URL = "https://dummyjson.com"
@app.route('/products', methods=['GET'])
def get_products():
response = requests.get(f"{BASE_URL}/products")
if response.status_code != 200:
return jsonify({'error': response.json()['message']}), response.status_code
products = []
for product in response.json()['products']:
product_data = {
'id': product['id'],
'title': product['title'],
'brand': product['brand'],
'price': product['price'],
'description': product['description']
}
products.append(product_data)
return jsonify({'data': products}), 200 if products else 204
Il codice precedente crea un endpoint /products
nel server Flask. Quando vi si accede tramite una richiesta GET, recupera i dati dei prodotti da un’API fittizia. In caso di successo, elabora i dati recuperati, estrae i dettagli del prodotto e restituisce le informazioni in formato JSON. In caso di errori o di assenza di dati disponibili, risponde con un messaggio di errore e un codice di stato appropriati.
4. Testare il microservizio
A questo punto, abbiamo configurato con successo un semplice microservizio. Per avviare il servizio, avviamo il server di sviluppo, che inizierà a funzionare all’indirizzo http://localhost:5000
.
flask --app services/products run
Poi possiamo fare una richiesta GET
all’endpoint /products
usando il client Postman; dovremmo vedere una risposta simile a quella dello screenshot qui sotto.
Come implementare l’autenticazione e l’autorizzazione in un microservizio in Python
Quando si costruiscono microservizi, è importante implementare misure di sicurezza solide come l’autenticazione e l’autorizzazione. La sicurezza del microservizio assicura che solo gli utenti autorizzati possano accedere e utilizzare il servizio, proteggendo i dati sensibili e prevenendo gli attacchi malevoli.
Un metodo efficace per implementare l’autenticazione e l’autorizzazione sicura nei microservizi è rappresentato dai JSON Web Tokens (JWT).
I JWT sono uno standard aperto molto diffuso che offre un modo sicuro ed efficiente di trasmettere informazioni di autenticazione tra client e server. Si tratta di token compatti, crittografati e firmati digitalmente che vengono trasmessi insieme alle richieste HTTP. Includendo un JWT in ogni richiesta, il server può verificare rapidamente l’identità e i permessi di un utente.
Per implementare l’autenticazione JWT nel microservizio, procediamo come segue:
- Aggiungiamo il pacchetto
pyjwt
di Python al file requirements.txt e reinstalliamo le dipendenze usandopip install -r requirements.txt
. - Poiché il servizio non ha un database dedicato, creiamo un file users.json nella directory principale del progetto per memorizzare un elenco di utenti autorizzati. Incolliamo il codice sottostante nel file:
[ { "id": 1, "username": "admin", "password": "admin" } ]
- Quindi, nel file services/products.py, sostituiamo le dichiarazioni di importazione con le seguenti:
import requests from flask import Flask, jsonify, request, make_response import jwt from functools import wraps import json import os from jwt.exceptions import DecodeError
Stiamo importando questi moduli per gestire le richieste HTTP, creare un’app Flask, gestire i dati JSON, implementare l’autenticazione basata su JWT e gestire le eccezioni, abilitando un’ampia gamma di funzionalità all’interno del server Flask.
- Aggiungiamo il seguente codice sotto la creazione dell’istanza dell’app Flask per generare una chiave segreta che verrà utilizzata per firmare i token JWT.
app.config['SECRET_KEY'] = os.urandom(24)
- Per verificare i JWT, creiamo una funzione decoratore e aggiungiamo il seguente codice sopra le route API nel codice del server Flask. Questa funzione decoratore autenticherà e convaliderà gli utenti prima che accedano alle route protetti.
def token_required(f): @wraps(f) def decorated(*args, **kwargs): token = request.cookies.get('token') if not token: return jsonify({'error': 'Authorization token is missing'}), 401 try: data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"]) current_user_id = data['user_id'] except DecodeError: return jsonify({'error': 'Authorization token is invalid'}), 401 return f(current_user_id, *args, **kwargs) return decorated
Questa funzione decoratore controlla le richieste HTTP in arrivo alla ricerca di un token di autorizzazione JWT, che dovrebbe essere presente nelle intestazioni della richiesta o nei cookie. Se il token manca o non è valido, il decoratore invia un messaggio
unauthorized status code
come risposta.Al contrario, se è presente un token valido, il decoratore estrae l’ID utente dopo averlo decodificato. Questo processo salvaguarda gli endpoint API protetti consentendo l’accesso solo agli utenti autorizzati.
- Definiamo un endpoint API per l’autenticazione dell’utente utilizzando il codice seguente.
with open('users.json', 'r') as f: users = json.load(f) @app.route('/auth', methods=['POST']) def authenticate_user(): if request.headers['Content-Type'] != 'application/json': return jsonify({'error': 'Unsupported Media Type'}), 415 username = request.json.get('username') password = request.json.get('password') for user in users: if user['username'] == username and user['password'] == password: token = jwt.encode({'user_id': user['id']}, app.config['SECRET_KEY'],algorithm="HS256") response = make_response(jsonify({'message': 'Authentication successful'})) response.set_cookie('token', token) return response, 200 return jsonify({'error': 'Invalid username or password'}), 401
Per autenticare e autorizzare gli utenti, l’endpoint API
/auth
controlla le credenziali contenute nel payload JSON della richiestaPOST
rispetto all’elenco degli utenti autorizzati. Se le credenziali sono valide, genera un token JWT utilizzando l’ID dell’utente e la chiave segreta dell’applicazione e imposta il token come cookie nella risposta. Gli utenti possono ora utilizzare questo token per effettuare le successive richieste API.Dopo aver creato l’endpoint
/auth
, usiamo Postman per inviare una richiesta HTTPPOST
ahttp://localhost:5000/auth
. Nel corpo della richiesta, includiamo le credenziali del finto utente amministratore che abbiamo creato.Se la richiesta va a buon fine, l’API genererà un token JWT, lo imposterà nei cookie di Postman e invierà una risposta di successo autenticata.
- Infine, aggiorniamo l’endpoint dell’API
GET
per controllare e verificare il token JWT utilizzando il codice seguente:@app.route('/products', methods=['GET']) @token_required def get_products(current_user_id): headers = {'Authorization': f'Bearer {request.cookies.get("token")}'} response = requests.get(f"{BASE_URL}/products", headers=headers) if response.status_code != 200: return jsonify({'error': response.json()['message']}), response.status_code products = [] for product in response.json()['products']: product_data = { 'id': product['id'], 'title': product['title'], 'brand': product['brand'], 'price': product['price'], 'description': product['description'] } products.append(product_data) return jsonify({'data': products}), 200 if products else 204
Come containerizzare i microservizi Python con Docker
Docker è una piattaforma che impacchetta le applicazioni e le loro dipendenze in un ambiente di sviluppo isolato. L’impacchettamento dei microservizi in container ottimizza i processi di distribuzione e gestione nei server, poiché ogni servizio viene eseguito in modo indipendente nel proprio container.
Per containerizzare il microservizio, dobbiamo creare un’immagine Docker da un Dockerfile che specifichi le dipendenze necessarie per eseguire l’applicazione in un container. Creiamo un Dockerfile nella directory principale del progetto e aggiungiamo queste istruzioni:
FROM python:3.9-alpine
WORKDIR /app
COPY requirements.txt ./
RUN pip install -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "./services/products.py"]
Prima di creare l’immagine, diamo un’occhiata ai seguenti comandi:
FROM
: indica a Docker quale immagine di base utilizzare. Un’immagine di base è un’istanza precostituita contenente il software e le dipendenze per eseguire l’applicazione Flask in un container.WORKDIR
: imposta la directory specificata all’interno del container come directory di lavoro.COPY requirements.txt ./
: copia le dipendenze nel file requirements.txt nel file requirements.txt del container.RUN
– Esegue il comando specificato per installare le dipendenze richieste dall’immagine.COPY . .
: copia tutti i file dalla directory principale del progetto alla directory di lavoro all’interno del container.EXPOSE
: specifica la porta su cui il container ascolterà le richieste. Tuttavia, Docker non pubblica la porta sul computer host.CMD
: specifica il comando predefinito da eseguire all’avvio del container.
Successivamente, aggiungiamo un file .dockerignore nella directory principale del progetto per specificare i file che l’immagine Docker deve escludere. Limitare il contenuto dell’immagine ridurrà le dimensioni finali e il tempo di build associato.
/venv
/services/__pycache__/
.gitignore
Ora, eseguiamo il comando seguente per creare l’immagine Docker:
docker build -t flask-microservice .
Infine, una volta creata l’immagine, possiamo eseguire il microservizio in un container Docker utilizzando il seguente comando:
docker run -p 5000:5000 flask-microservice
Questo comando avvierà un container Docker che esegue il microservizio ed esporrà la porta 5000 del container alla porta 5000 del computer host, consentendo di effettuare richieste HTTP dal browser web o da Postman utilizzando l’URL http://localhost:5000
.
Distribuire microservizi Python con Kinsta
Kinsta offre soluzioni di hosting gestito per applicazioni web e database: si possono distribuire e gestire senza problemi i propri microservizi Python e le API di backend in un ambiente di produzione.
Seguiamo questi passaggi per preparare il microservizio Flask alla distribuzione con MyKinsta:
- Per prima cosa, creiamo un nuovo Procfile nella directory principale e aggiungiamo il codice qui sotto. Questo specifica il comando per eseguire il microservizio Flask sul server HTTP Gunicorn WSGI di Kinsta per le applicazioni Python.
web: gunicorn services.wsgi
- Nel file requirements.txt, aggiungiamo la dipendenza Gunicorn:
gunicorn==20.1.*
- Quindi, creiamo un nuovo file services/wsgi.py e aggiungiamo il codice seguente.
from services.products import app as application if __name__ == "__main__": application.run()
- Creiamo un file .gitignore nella cartella principale del progetto e aggiungiamo quanto segue:
services/__pycache__ venv
- Infine, creiamo un nuovo repository su GitHub e inviamo i file del progetto.
Quando il repository sarà pronto, basta seguire questi passaggi per distribuire il microservizio Flask su Kinsta:
-
- Accedere o creare un account per visualizzare la dashboard MyKinsta.
- Autorizzare Kinsta con il provider Git (Bitbucket, GitHub o GitLab).
- Cliccare su Applicazioni nella barra laterale di sinistra e poi su Aggiungi applicazione.
- Nel cruscotto, cliccare su Aggiungi servizio e selezionare Applicazione.
- Selezionare il repository e il branch da cui si desidera effettuare il deploy.
- Assegnare un nome unico all’applicazione e scegliere la posizione del data center.
-
- Per configurare l’ambiente di build, selezionare l’opzione per utilizzare un Dockerfile per creare l’immagine del container.
- Fornire il percorso del file Docker e il contesto.
- Infine, controllare le altre informazioni e cliccare su Crea applicazione.
Testare il microservizio
Una volta che il processo di distribuzione sarà andato a buon fine, clicchiamo sull’URL fornito per testare il microservizio effettuando delle richieste HTTP in Postman. Effettuiamo una richiesta GET
all’endpoint principale.
Per autenticarci e generare un token JWT, inviamo una richiesta POST
all’endpoint API /auth
, passando le credenziali dell’amministratore nel corpo della richiesta.
Infine, dopo esserci autenticati, eseguiramo una richiesta GET
all’endpoint /products
per recuperare i dati.
Riepilogo
Man mano che le applicazioni crescono in dimensioni e complessità, è fondamentale adottare modelli di architettura che permettano ai sistemi software di scalare senza affaticare le risorse disponibili.
L’architettura a microservizi offre scalabilità, flessibilità di sviluppo e manutenibilità, rendendo più facile la gestione di applicazioni complesse.
Kinsta semplifica il processo di hosting dei vostri microservizi. Vi permette di utilizzare senza problemi il vostro database preferito e di ospitare comodamente sia l’applicazione che il database attraverso una dashboard unificata.
Lascia un commento