0% found this document useful (1 vote)
437 views

Microservices With Python and Flask

This document provides an overview of a class project to develop MicroFlack, a microservices version of the Flack chat application. The project uses Python and Flask to develop five microservices that handle different aspects of the chat application, such as users, tokens, and the interface. It describes deploying the services using Docker containers, load balancing with HAProxy, service registration with Etcd, and centralized logging with Logspout. The document discusses strategies for breaking a monolithic application into microservices and establishing an underlying microservices platform and common code library. It also proposes switching the authentication method to use JSON Web Tokens.

Uploaded by

webacct
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (1 vote)
437 views

Microservices With Python and Flask

This document provides an overview of a class project to develop MicroFlack, a microservices version of the Flack chat application. The project uses Python and Flask to develop five microservices that handle different aspects of the chat application, such as users, tokens, and the interface. It describes deploying the services using Docker containers, load balancing with HAProxy, service registration with Etcd, and centralized logging with Logspout. The document discusses strategies for breaking a monolithic application into microservices and establishing an underlying microservices platform and common code library. It also proposes switching the authentication method to use JSON Web Tokens.

Uploaded by

webacct
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 66

Microservices with Python and Flask

Miguel Grinberg
@miguelgrinberg
Agenda
● First hour
○ Introduction & demo
○ Microservices concepts
○ Class project design discussion
● Remaining time
○ Step-by-step development of the class project
○ (Focus is on techniques more than on a particular implementation)
About the MicroFlack Project
● MicroFlack is a microservices version of Flack
○ Flack is the chat server I used in the “Flask at Scale” class I gave at PyCon 2016
● The application lives on 7 (yes, seven!) GitHub repositories
● Runs on Python 3.4+ and Docker
● Not tied to any specific cloud or microservice vendor technology
Deploying MicroFlack to your Laptop
● Requirements
○ 4GB RAM (8GB recommended)
○ Vagrant
○ VirtualBox
○ Everything is installed in an Ubuntu 16.04 VM (Windows, Mac, Linux laptops are all OK!)
● Deployment commands:
git clone https://github.com/miguelgrinberg/microflack_admin
cd microflack_admin
vagrant up # to create the VM or restart it after shutdown
vagrant ssh # to open a shell session on the VM
vagrant halt # to shutdown the VM (without destroying it)
vagrant snapshot save clean # to save a snapshot with name “clean”
vagrant snapshot restore clean --no-provision # to restore the snapshot
vagrant destroy # to delete the VM
Monolithic Flack
MicroFlack
MicroFlack Features
● Five microservices, four HTTP/REST, one WebSocket
● Each service is a standalone Flask app
● Two of the services use MySQL databases
● Services run in Docker containers
● Services communicate over HTTP, message queue or service registry
● All services are load balanced
● Services scale independently of each other
● Upgrades can be done without downtime
Microservice Benefits
● Less complexity (maybe)
○ Awesome for teams with varying degrees of experience
● Scaling flexibility
● More reliability
● Less coupling
● More choice
● Deploy/upgrade while running

So where is performance in this list?


Breaking up a Monolith
● Going from monolith to microservices is very hard
● Several strategies
○ Microservices only going forward
○ Break pieces of functionality into microservices over time
○ Refactor the entire monolith into microservices
● In all cases, a base platform needs to be put in place before refactoring work
begins
● Good automated tests are crucial in avoiding bugs when refactoring
functionality into a microservice
The Microservices Platform
Load Balancer
● All services are load balanced
● You think you don’t need load balancing?
○ Upgrades without downtime require a load balancer for a seamless transition
○ Autoscaling, A/B testing, green/blue deployments, etc. become possible
● Many options
○ Open source: Nginx, HAProxy
○ As a service: AWS, OpenStack, Azure, Google Cloud, etc.
○ Serverless: Load balancing and scaling are implicitly done by the cloud operator
■ The Lambda and API Gateway services on AWS are by far the best in this category
○ Hardware: F5
Service Registry
● A distributed data store that keeps track of running services
● All running services maintain an entry in the service registry
● The load balancer’s configuration is generated and updated from the registry
contents
● Many great open source projects to choose from:
○ Etcd (CoreOS)
○ Consul (HashiCorp)
○ Zookeeper (Apache)
○ Eureka (Netflix)
○ SmartStack (Airbnb)
○ and more!
Logging
● Logs from all the services are typically consolidated to a single stream to avoid
the complexity of maintaining lots of individual log files
● Open source:
○ ELK stack (elasticsearch + logstash + kibana)
○ Logspout
● Several commercial options:
○ Papertrail
○ Splunk
○ Loggly
○ and more!
Containers
● Provide an additional layer of isolation over processes
● Each container runs a virtualized host
○ You can have containers using different Linux distros on the same host
○ Dependencies that would conflict if installed on the same host can be installed on containers
○ Virtualized network ports
● A container maps to one main process, but there can be additional tasks
● Not a required component of the stack, but very convenient
Application State
● Stateful services are hard to manage
○ No way to avoid them for service registry, databases, message queues, etc.
● Stateless services are easily scalable, replaceable and disposable
● Application-level services should ideally be stateless, and should use stateful
services for storage
● The state associated with a service should be private
○ Each service must use its own database
○ This prevents coupled services that are difficult to upgrade independently of each other
○ Database joins across services must be done in the application
Retries
● Distributed systems have a lot of moving parts
● It is always a good idea to implement retries for all network calls
● The “exponential backoff” strategy works nicely in this environment
The “Backwards-Compatible” Rule
● Changes to services must be backwards compatible
○ Why? Because a distributed system cannot be updated atomically without downtime
● Database migrations must not break any code that may still be deployed
○ Phased micro-upgrades can help with column renames or deletes, constraints, etc.
● API changes must not break any code that may still be deployed
○ Why? Need a way to upgrade the API consumer and producer independently
● Complex changes that span several services must be “orchestrated” so that
they can be applied as micro-deployments without breaking the system
Lifecycle of a Microservice
● On startup, the microservice registers with the service registry, or is
“discovered” by it
● The load balancer watches the registry and updates itself to include the new
microservice
● The new service starts receiving traffic from the load balancer
● If more than one instance of the service exist, the traffic is split among them
● The service sends “keep-alive” signals, or responds to periodic health checks
● When the service is stopped, or stops sending keep-alives, or fails a health
check, it is removed from the registry, and in turn from the load balancer
Recommended reading: The Twelve-Factor App
https://12factor.net

● Codebase ● Port binding


● Dependencies ● Concurrency
● Config ● Disposability
● Backing services ● Dev/prod parity
● Build, release, run ● Logs
● Processes ● Admin Processes
MicroFlack Design
Choosing a Stack
● For the services:
○ Flask and friends
● For the platform:
○ Load balancer: HAProxy
○ Service registry: Etcd
○ Confd (http://www.confd.io/) configures the load balancer dynamically
○ Docker containers
○ Logspout log consolidation
○ MySQL databases
○ Redis message queue
Leveraging Public Container Images
● miguelgrinberg/easy-etcd
○ Container image that deploys etcd clusters
● miguelgrinberg/easy-lb-haproxy
○ Load balancer + confd preconfigured
● gliderlabs/logspout
○ Consolidated log output
● mysql:5.7
○ Official Docker image for MySQL 5.7
● redis:3.2-alpine
○ Official Docker image for Redis 3.2
Administration Scripts: microflack_admin
● Vagrantfile: deploy to a single-node Vagrant VM
● setup-host.sh, setup-all-in-one.sh, make-db-passwords.sh: deploy scripts
● mfvars: common environment variables
● mfclone: clone the repositories for all the services
● mfbuild: build Docker images for services
● mfrun: start services
● mfkill: stop services
● mflogs: consolidated log stream of all services
● mfupgrade: upgrade services
● mfenv: generate a .env file with environment needed for development
● mfdev: attach a locally running service to a deployed system for debugging
● etcd-dump: dump the contents of the service registry to the console
Common Code: microflack_common
● There is some functionality that all microservices need
○ Service registration
○ Unit testing helpers
○ Inter-service communication
○ Authentication handlers
● We’ll use a Python package that services can install with pip
● Easy option: install from pypi (if you don’t mind making it public)
● Less easy option: private package installed from a local file
○ We’ll use the Python wheel format for this (pip install wheel)
○ The --find-links option in pip can install packages from a local file system directory
○ The mkwheel script builds the wheel packages
Authentication: Let’s switch to JWTs
● Tokens stored in a database are inconvenient
○ Services would need to send a request to the tokens service for verification
● JSON Web Tokens (JWTs) can be verified just with cryptography
○ A JWT token stores data inside it, such as a username or id
○ When the token is generated, a cryptographic signature is added to it
○ Signature can only be generated or verified if you have a secret key
○ The data in a token can be trusted only if the token has a valid signature
○ Not everything is great with JWTs: token revocations become harder
● Since tokens are opaque, switching to JWT is not a breaking change
● Beware of JWT exploits: always set and check signing algorithm
MicroFlack Service Boundaries

Microservice URL(s)

User interface /
/static/...

Users /api/users
/api/users/:id

Tokens /api/tokens

Messages /api/messages
/api/messages/:id

Socket.IO /socket.io
From Flack to MicroFlack
MicroFlack v0.1: Just the UI
UI Service Summary
● Endpoints

Method Endpoint Authentication Description

GET / None Client HTML page

GET /static/app.js None Main client application code

GET /static/*.js None Client application code

GET /static/*.css None Client application stylesheets


UI Service: microflack_ui
● app.py, config.py, templates/, static/, requirements.txt
○ Ordinary Flask app that serves the index HTML page plus all the JavaScript and CSS files that
make up the client application
○ To ease the transition, at this stage we’ll use an older version of the UI that does not use
Socket.IO (we’ll add Socket.IO later)
● .env
○ Environment variables
○ This file should not be added to source control, as it can contain secrets
● tests.py, tox.ini
○ Unit tests, code coverage and linting
● Dockerfile, boot.sh, build.sh
○ Docker support
Running the UI service
● vagrant ssh (connect to the VM)
● mfkill all (reset your VM to an initial state without any services)
○ Watch the load balancer at http://192.168.33.10/stats
● cd ~/microflack_ui
● git checkout 1 (get version 1 of the UI service)
● ./build.sh (build the service)
● mfrun ui (run the service)
● Connect to the application at http://192.168.33.10
○ Browser errors are expected, as no other services are yet running
Incorporating the Flack Monolith
● The service registry has a simple tree structure (use etcd-dump to see it)
● You can register the Flack monolith with the load balancer in the VM:
○ curl -X PUT $ETCD/v2/keys/services/monolith/location -d value="/api"
○ curl -X PUT $ETCD/v2/keys/services/monolith/upstream/server -d
value="10.0.2.2:5000"
○ Note: 10.0.2.2 is the IP address the host machine has inside a vagrant VM
● Now the UI is served by the new microservice, while everything else comes
from the old Flack+Celery application
● To remove:
○ curl -X DELETE $ETCD/v2/keys/services/monolith?recursive=true
MicroFlack v0.2: Users service
Users Service Summary
● Endpoints (new endpoints in red)

Method Endpoint Authentication Description

POST /api/users None Register a new user

GET /api/users Token Optional Get list of users

GET /api/users/:id Token Optional Get user by id

PUT /api/users/:id Token Modify user by id

GET /api/users/me Basic Authenticate user


Users service: microflack_users
● Same basic structure as the ui microservice
● Includes User model and all /api/users endpoints from original Flack
● Token authentication imported from microflack_common
● Ported existing unit tests and used them as a guide to fix everything up
● Add database migration support (Flask-Migrate)
○ Databases are created by mfrun if they don’t exist yet
○ Migrations are executed in the container startup script
● Add new /api/users/me endpoint to validate username and password and
return user information
Running the Users service
● cd ~/microflack_users
● git checkout 1 (select version 1 of the service)
● ./build.sh (build the service)
● mfrun users (run the service)
○ The /api/users family of endpoints should now be working!
MicroFlack v0.3: Tokens service
Tokens Service Summary
● Endpoints (postponed endpoints grayed out)

Method Endpoint Authentication Description

POST /api/tokens Basic Request a token

DELETE /api/tokens Token Revoke a token


Token service: microflack_tokens
● Just one endpoint copied from the monolithic app: /api/tokens
● Authentication is relayed to the users service /me endpoint
● Generated JWT token contains the numeric user id
● No token revocations for now
Running the Tokens service
● cd ~/microflack_tokens
● git checkout 1 (select version 1 of the service)
● ./build.sh (build the service)
● mfrun tokens (run the service)
○ The /api/tokens endpoint should now be working
MicroFlack v0.4: Messages service
Messages Service Summary
● Endpoints

Method Endpoint Authentication Description

POST /api/messages Token Post a new message

GET /api/messages Token Optional Get list of messages

GET /api/messages/:id Token Optional Get message by id

PUT /api/messages/:id Token Modify message by id


Messages Service: microflack_messages
● Structure based on the user service
● Models and endpoints copied from original Flack code
● Removed all asynchronous functions for now
○ We want a basic app up and running from which we can build on
● Ported unit tests to verify the code works
Running the Messages service
● cd ~/microflack_messages
● git checkout 1 (select version 1 of the service)
● ./build.sh (build the service)
● mfrun messages (run the service)
● The application should be fully functional (though not very performant yet)
MicroFlack v0.5: Async message rendering
● Original Flack used Celery for asynchronous message rendering
○ Unfortunately, Celery workers are by design tightly coupled with the caller process
● Instead of Celery, we will use background threads for rendering
○ Our render task is not CPU intensive, so this works very well
○ For CPU intensive tasks, the multiprocessing module can be used instead
○ If a very high volume of tasks must be supported, an asynchronous server can be used
Development Workflow: Common Package
● Build the common packages locally:
○ cd microflack_admin; source mfvars; cd ..
○ git clone https://github.com/miguelgrinberg/microflack_common
○ cd microflack_common
○ ./mkwheel all
○ cd ..
Development Workflow: Running a Service
● Set up the source code for the desired microservice:
○ cd microflack_admin; source mfvars; cd ..
○ git clone https://github.com/miguelgrinberg/microflack_messages
○ cd microflack_messages
○ python3 -m venv venv
○ source venv/bin/activate
○ pip install -r requirements.txt
○ flask run
● Test by sending requests with curl, httpie, postman, etc.
● For integration testing with an actual system running in a VM:
○ Create .env file (run mfenv inside VM to get the variables you need)
○ mfdev start messages
○ flask run
Upgrading the Messages service
● cd ~/microflack_messages
● git checkout 2 (select version 2 of the service)
● ./build.sh (build the service)
● mfupgrade try messages (start upgrade)
○ After watching the log for a few seconds, hit Ctrl-C
● mfupgrade roll (rolling upgrade)
MicroFlack v0.6: Token revocation
Tokens Service Summary
● Endpoints (new endpoints in red)

Method Endpoint Authentication Description

POST /api/tokens Basic Request a token

GET /api/tokens Token Check if a token is revoked

DELETE /api/tokens Token Revoke a token


Token revocation
● We need to maintain a list of revoked tokens
○ The best place to implement this is the tokens service
○ Revoked tokens need to be kept in a list only until they expire
○ We can keep the list in etcd, and write all entries with the appropriate expiration
● Services need to check tokens against that list
○ We can encapsulate this inside the verify_token function in microflack_common
○ Option 1 (more correct): send a request to the tokens service to check revocation status
○ Option 2 (more performant): check the list in etcd directly
○ Improvement for both options: cache calls to verify_token
Upgrading the Tokens service
● cd ~/microflack_tokens
● git checkout 2 (select version 2 of the service)
● ./build.sh (build the service)
● mfupgrade roll tokens (rolling upgrade, skipping the “try” step)
● Upgrade the services that work with tokens:
○ users to version 2
○ messages to version 3
MicroFlack v1.0: Socket.IO service
Socket.IO Service Summary
● Socket.IO client to server events
Event Authentication Description

ping_user Token Mark a user as online

post_message Token Post a message

disconnect Token (from session) Mark the user as offline

● Socket.IO server to client events (new in red)


Event Description

updated_model Render updated user or message

expired_token Ask user to log in again


System changes for Socket.IO
● Task list for the Socket.IO service
○ Implement “ping_user”, “post_message” and “disconnect” events
○ Push “expired_token” notifications to clients when appropriate
● Task list for the common package
○ Add support for setting sticky sessions in the load balancer
● Task list for the UI service
○ Add Socket.IO support
○ Handle expired tokens in Socket.IO calls (bug in old version)
● Task list for users service
○ Add “ping” and “user offline” endpoints
○ Push “updated_model” notifications to message queue
● Task list for messages service
○ Push “updated_model” notifications to message queue
Socket.IO service: microflack_socketio
● Same structure as a no-database service
● No HTTP endpoints, only the three Socket.IO events
● Needs to be an async service due to the long term WebSocket connections
○ Don’ t understand why? Come to my talk “Asynchronous Python” on Sunday!
○ We have several options
■ python-socketio supports WSGI-compatible async frameworks (eventlet, gevent) and also
asyncio
■ Flask-SocketIO builds on python-socketio, but drops asyncio support
○ While we don’t need Flask for this service, having access to Flask’s user session is handy
○ We’ll go with Flask-SocketIO and eventlet for this service
Users Service Summary
● Endpoints (new endpoints in red)
Method Endpoint Authentication Description

POST /api/users None Register a new user

GET /api/users Token Optional Get list of users

GET /api/users/:id Token Optional Get user by id

PUT /api/users/:id Token Modify user by id

GET /api/users/me Basic Authenticate user

PUT /api/users/me Token Set user online

DELETE /api/users/me Token Set user offline


Running the Socket.IO service
● cd ~/microflack_socketio
● git checkout 1 (select version 1 of the service)
● ./build.sh (build the service)
● mfrun socketio (run the service)
● Upgrade the services that work with Socket.IO (order is important!):
○ users to version 3
○ messages to version 4
○ ui to version 2
MicroFlack on Other Platforms
Kubernetes
● Open-source container orchestration, from Google
● Manages a cluster of nodes (container hosts) transparently
● Works with Docker images
● Has its own service registry and load balancer
● Gives each service a DNS name (i.e. http://users connects to the Users service)
● Stores secrets securely
● Handles service replication, and does rolling upgrades
● microflack_admin includes example Kubernetes deployment scripts in
install/kubernetes
Amazon ECS
● A cluster of Docker hosts running on EC2 instances
● Access to the AWS ecosystem
● MicroFlack platform and application containers can run without change
● Some effort required in configuring roles and security groups (as with
everything done on AWS)
AWS Serverless (Lambda & API Gateway)
● Very different paradigm, only the application logic is uploaded to AWS
○ No need for gunicorn, just the application code that handles the endpoints
○ Tools like Zappa (or my own Slam) enable transparent support for WSGI apps
● AWS Lambda provides automatic load balancing and auto-scaling
● Access to the AWS ecosystem
● Cons:
○ No WebSocket support, since there is no server running all the time
○ Response times are not great
Good ol’ Processes
● Deploying MicroFlack on a host without Docker is possible
● All the core components of the platform can be installed without Docker:
○ etcd, haproxy, confd, mysql, redis
● The MicroFlack application services can run as regular gunicorn processes
○ But a network port assignment strategy needs to be implemented
Homework!
Improvement Ideas
● Revoke all tokens for a user, and revoke all tokens for everybody
● Use multiple chat rooms instead of just one (this will require some client-side work as well)
● Protect the /stats and /logs endpoints with authentication
● Add SSL termination to (or in front of) the load balancer
● Deploy the ELK stack and configure the logspout container to forward logs to it
● Add a secrets store component (maybe HashiCorp’s Vault)
● Create a multi-host deployment, possibly with redundant load balancers
● Replace the single-node Redis deployment with a Redis or RabbitMQ cluster
● Replace the single-node MySQL with a Galera cluster
● Replace MySQL with NoSQL databases of your choice
● With all services running 3+ instances, create a “chaos monkey” script
● Implement a platform service that recycles application services that die or are unresponsive, and
maybe even auto-scales them based on load
Questions?

You might also like