KEMBAR78
Python Deployment with Fabric | PPT
Prodution Architecture and Deployment with Fabric - Andy McCurdy - @andymccurdy
Whiskey Media
Whiskey Sites
Your First Django App
Basic Config (web) Apache mod_wsgi use daemon mode threads more efficient processes if you're unsure of thread safety WSGIDaemonProcess my-site python-path=/home/code/ processes=2 threads=150 maximum-requests=5000 WSGIProcessGroup my-site WSGIScriptAlias / /home/code/my-site/deploy/wsgi/my-site.wsgi
Basic Config (media) Nginx Use Nginx to proxy traffic to Apache Meanwhile Nginx serves media upstream my-site { server 127.0.0.1:8000; } server { listen 80; location ~ ^/media/ { root /home/code/my-site; expires 30d; } location / { proxy_pass http://my-site; proxy_set_header X-Real-IP $remote_addr; } }
Basic Config (db) Databases PostgreSQL Frank Wiles @ www.revsys.com MySQL Percona @ www.mysqlperformanceblog.com
Basic Config (cache) Use Memcached!  Even 16mb will significantly help against a Digg or being Slashdot'ed It's incredibly easy... # settings.py MIDDLEWARE_CLASSES = ( 'django.middleware.cache.UpdateCacheMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.cache.FetchFromCacheMiddleware') CACHE_BACKEND = 'memcached://127.0.0.1:11211/' CACHE_MIDDLEWARE_SECONDS = 60*5  # 5 minutes CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True
Basic Deployment Copy Code & Media (rsync or scp) Run DB migrations / syncdb Bounce wsgi daemons
Managing Growth (db) Move DB to its own server As much RAM as possible Write-heavy (>10%)? Get fast disks Tune your config file
Managing Growth (web) Add more web servers Use a resource monitoring tool like Munin to understand if your app is CPU or memory bound
Even More Growth Replicated or sharded Databases Multiple load balancers for redundancy Message queues Crons Search daemons (Solr, Sphinx) etc...
(not so) Basic Deployment image credit: Brad Fitzpatrick
Deployment Reqs Copy media to CDN Maintenance splash page Run DB migrations Install/Upgrade Python dependencies Add a new web server to the cluster Execute arbritrary commands for sysadmin maintenance tasks
Deployment Options Capistrano + Out of box support for common use cases + Hooks to customize tasks + Source control integration + Threaded deployment to multiple hosts - Ruby :(
Deployment Options Fabric + Very simple, tasks are just Python functions + Easy to chain together tasks to create complex scripts out of bite size pieces - No source control integration - No out of box support - Some bugs, although fairly easy to work around, and new maintainer is working on fixes
Fabric Basics sudo easy_install fabric need a fabfile.py from fabric.api import * be mindful of tasks that may fail each remote command starts fresh changing directories
Core Functionality local()  -  Run a command locally run()  -  Run a command remotely sudo()  -  Run a command remotely as another user put()  -  Copy a file from local to remote get()  -  Copy a file from remote to local many more helper-ish commands
Authentication Relies on SSH model Use SSH keys Control access to root user via sudoers When you have to revoke access, you just turn off their SSH account
Configuration Fabric environment (env) -- it's just a dictionary Hosts and Roles Code Repositories Whatever you need ~/fabricrc Global settings or all Fabric deployments SSH username
Example Config # fabfile.py from fabric.api import * env.roledefs = { 'web' : ['10.1.1.1', '10.1.1.2'], 'db' : ['10.1.1.3'], 'lb' : ['10.1.1.4'], } env.repositories = {...}
Tasks # fabfile.py def uptime(): run('uptime') $> fab uptime -H 10.1.1.3 [10.1.1.3] run: uptime [10.1.1.3] out:  05:20:39 up 88 days, 12:00,  0 users,  load average: 0.03, 0.03, 0.00
Mapping Roles to Tasks # fabfile.py @roles('web') def uptime(): run('uptime') $> fab uptime [10.1.1.1] run: uptime [10.1.1.1] out:  05:20:39 up 88 days...  [10.1.1.2] run: uptime [10.1.1.2] out:  05:20:39 up 88 days...
Decorator Problems Some problems with Fabric's role management Can't override decorated tasks at command line as docs suggest def default_roles(*role_list): def selectively_attach(func): if not env.roles and not env.hosts: return roles(*role_list)(func) else: if env.hosts: func = hosts(*env.hosts)(func) if env.roles: func = roles(*env.roles)(func) return func return selectively_attach
All better now #fabfile.py @default_roles('web', 'db') def uptime(): run('uptime') $> fab uptime # runs on all hosts in the 'web' and 'db' roles $> fab uptime --roles lb # runs only on hosts in the 'lb' role
Dealing with Failures By default Fabric dies if a task fails Use a context manager when failures are anticipated # fabfile.py from __future__ import with_statement  # py2.5 def symlink_me(): with settings(warn_only=True): run('rm /path/to/symlink') run('ln -s /home/andy /path/to/symlink')
Easy sys-admin Make an "invoke" command Great for sys-admin and one-off tasks # fabfile.py @default_roles('all') def invoke(command): "Invoke an arbritrary command" sudo(command) # install new packages on all hosts in one command $> fab invoke:"apt-get install git-core"
Real World Tasks $> fab --list Available commands: bounce_wsgi_procs   Bounce the WSGI procs by touching the files deploy   Full deployment deploy_media   Push media to S3 invoke   Invoke an arbritrary command migrate   Run any migrations via South reload_nginx   Update Nginx's running config splash_off   Configure Nginx to serve the site splash_on   Configure Nginx to serve a downed-site page update_repositories   Push code to servers update_dependencies   Update dependencies to third party libs
Whiskey's Deployment def deploy(splash='no'): "Full deployment" deploy_media() update_cached_repositories() update_dependencies() generate_releases() if splash == 'yes': splash_on() _symlink_code() migrate() bounce_wsgi_procs() if splash == 'yes': splash_off() $> fab deploy:splash=yes
Questions?

Python Deployment with Fabric

  • 1.
    Prodution Architecture andDeployment with Fabric - Andy McCurdy - @andymccurdy
  • 2.
  • 3.
  • 4.
  • 5.
    Basic Config (web)Apache mod_wsgi use daemon mode threads more efficient processes if you're unsure of thread safety WSGIDaemonProcess my-site python-path=/home/code/ processes=2 threads=150 maximum-requests=5000 WSGIProcessGroup my-site WSGIScriptAlias / /home/code/my-site/deploy/wsgi/my-site.wsgi
  • 6.
    Basic Config (media)Nginx Use Nginx to proxy traffic to Apache Meanwhile Nginx serves media upstream my-site { server 127.0.0.1:8000; } server { listen 80; location ~ ^/media/ { root /home/code/my-site; expires 30d; } location / { proxy_pass http://my-site; proxy_set_header X-Real-IP $remote_addr; } }
  • 7.
    Basic Config (db)Databases PostgreSQL Frank Wiles @ www.revsys.com MySQL Percona @ www.mysqlperformanceblog.com
  • 8.
    Basic Config (cache)Use Memcached! Even 16mb will significantly help against a Digg or being Slashdot'ed It's incredibly easy... # settings.py MIDDLEWARE_CLASSES = ( 'django.middleware.cache.UpdateCacheMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.cache.FetchFromCacheMiddleware') CACHE_BACKEND = 'memcached://127.0.0.1:11211/' CACHE_MIDDLEWARE_SECONDS = 60*5 # 5 minutes CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True
  • 9.
    Basic Deployment CopyCode & Media (rsync or scp) Run DB migrations / syncdb Bounce wsgi daemons
  • 10.
    Managing Growth (db)Move DB to its own server As much RAM as possible Write-heavy (>10%)? Get fast disks Tune your config file
  • 11.
    Managing Growth (web)Add more web servers Use a resource monitoring tool like Munin to understand if your app is CPU or memory bound
  • 12.
    Even More GrowthReplicated or sharded Databases Multiple load balancers for redundancy Message queues Crons Search daemons (Solr, Sphinx) etc...
  • 13.
    (not so) BasicDeployment image credit: Brad Fitzpatrick
  • 14.
    Deployment Reqs Copymedia to CDN Maintenance splash page Run DB migrations Install/Upgrade Python dependencies Add a new web server to the cluster Execute arbritrary commands for sysadmin maintenance tasks
  • 15.
    Deployment Options Capistrano+ Out of box support for common use cases + Hooks to customize tasks + Source control integration + Threaded deployment to multiple hosts - Ruby :(
  • 16.
    Deployment Options Fabric+ Very simple, tasks are just Python functions + Easy to chain together tasks to create complex scripts out of bite size pieces - No source control integration - No out of box support - Some bugs, although fairly easy to work around, and new maintainer is working on fixes
  • 17.
    Fabric Basics sudoeasy_install fabric need a fabfile.py from fabric.api import * be mindful of tasks that may fail each remote command starts fresh changing directories
  • 18.
    Core Functionality local() - Run a command locally run() - Run a command remotely sudo() - Run a command remotely as another user put() - Copy a file from local to remote get() - Copy a file from remote to local many more helper-ish commands
  • 19.
    Authentication Relies onSSH model Use SSH keys Control access to root user via sudoers When you have to revoke access, you just turn off their SSH account
  • 20.
    Configuration Fabric environment(env) -- it's just a dictionary Hosts and Roles Code Repositories Whatever you need ~/fabricrc Global settings or all Fabric deployments SSH username
  • 21.
    Example Config #fabfile.py from fabric.api import * env.roledefs = { 'web' : ['10.1.1.1', '10.1.1.2'], 'db' : ['10.1.1.3'], 'lb' : ['10.1.1.4'], } env.repositories = {...}
  • 22.
    Tasks # fabfile.pydef uptime(): run('uptime') $> fab uptime -H 10.1.1.3 [10.1.1.3] run: uptime [10.1.1.3] out: 05:20:39 up 88 days, 12:00, 0 users, load average: 0.03, 0.03, 0.00
  • 23.
    Mapping Roles toTasks # fabfile.py @roles('web') def uptime(): run('uptime') $> fab uptime [10.1.1.1] run: uptime [10.1.1.1] out: 05:20:39 up 88 days... [10.1.1.2] run: uptime [10.1.1.2] out: 05:20:39 up 88 days...
  • 24.
    Decorator Problems Someproblems with Fabric's role management Can't override decorated tasks at command line as docs suggest def default_roles(*role_list): def selectively_attach(func): if not env.roles and not env.hosts: return roles(*role_list)(func) else: if env.hosts: func = hosts(*env.hosts)(func) if env.roles: func = roles(*env.roles)(func) return func return selectively_attach
  • 25.
    All better now#fabfile.py @default_roles('web', 'db') def uptime(): run('uptime') $> fab uptime # runs on all hosts in the 'web' and 'db' roles $> fab uptime --roles lb # runs only on hosts in the 'lb' role
  • 26.
    Dealing with FailuresBy default Fabric dies if a task fails Use a context manager when failures are anticipated # fabfile.py from __future__ import with_statement # py2.5 def symlink_me(): with settings(warn_only=True): run('rm /path/to/symlink') run('ln -s /home/andy /path/to/symlink')
  • 27.
    Easy sys-admin Makean "invoke" command Great for sys-admin and one-off tasks # fabfile.py @default_roles('all') def invoke(command): "Invoke an arbritrary command" sudo(command) # install new packages on all hosts in one command $> fab invoke:"apt-get install git-core"
  • 28.
    Real World Tasks$> fab --list Available commands: bounce_wsgi_procs Bounce the WSGI procs by touching the files deploy Full deployment deploy_media Push media to S3 invoke Invoke an arbritrary command migrate Run any migrations via South reload_nginx Update Nginx's running config splash_off Configure Nginx to serve the site splash_on Configure Nginx to serve a downed-site page update_repositories Push code to servers update_dependencies Update dependencies to third party libs
  • 29.
    Whiskey's Deployment defdeploy(splash='no'): "Full deployment" deploy_media() update_cached_repositories() update_dependencies() generate_releases() if splash == 'yes': splash_on() _symlink_code() migrate() bounce_wsgi_procs() if splash == 'yes': splash_off() $> fab deploy:splash=yes
  • 30.

Editor's Notes

  • #4 Launched 3 sites very quickly. Spent some time working on platform, making it very robust. Tools. Going back to site mode soon.
  • #5 Almost everyone starts out with Django writing a Blog.
  • #6 Webserver: Use Apache, mod_wsgi, daemon mode. Threads more efficient, but make sure your code is thread safe. If in doubt, use processes.
  • #7 put nginx in front of apache. nginx serves media directory. forwards app requests to apache. load balancing becomes easy
  • #8 Probably need data storage, probably a relational DB: PostgreSQL or MySQL For tips on tuning, visit Frank Wiles @revsys for PostgreSQL or Percona (mysqlperformanceblog.com) for MySQL.
  • #9 Do yourself a favor and run Memcached. Having a small server, even as little as 16MB will help tremendously with Django's Cache middleware.
  • #10 With a single server, you're able to get away with simple deployment. Manual copy of files via SCP or Rsync. Manual restart the web server or touch the wsgi file. Manual DB migrations.
  • #11 Moving beyond a single server. Move the database to it's own box. Lots of RAM. As much as you can afford. If you do a lot of writes, get fast disks, too. SCSI 15k RPM. Take advantage of RAID if you can afford it. Tune your DB setup.
  • #12 Add more web servers. Your app will either be CPU or memory bound. Characteristics different per app. Use a tool like Munin to visualize and understand your app's resource usage.
  • #13 All sorts of additional boxes that need some or all of your code base to operate.
  • #14 Basic deployment isn't so basic anymore.
  • #15 More deployment requirements - deploy media to S3/Cloudfront or your favorite CDN - splash page to indicate site maintenance - db migrations - add a new web server to the cluster - install/upgrade python package dependencies - execution of arbitrary commands (invoke) for easy sys-admin tasks
  • #16 Two options... Capistrano or Fabric Capistrano more mature. + Out of the box support for a number of common tasks. + Before / After hooks built in for each task to allow for extending. + Interfaces for all common source control mgmt systems + Supports simultaneous deployment to all hosts at once. - It's in Ruby
  • #17 Fabric is rather new, not nearly as mature. Doesn't have a lot of stuff built in besides command invocation. Everyone's deployment needs vary because everyone has slightly different production architectures. Not the end of the world that Fabric doesn't have everything built in.
  • #19 Core functions: local() run() sudo()
  • #20 Fabric's auth model is based on the underlying SSH model.
  • #21 Fabric stores config info in it's environment object. just a dictionary, can add any info you want
  • #22 Use env.roledefs dictionary to configure roles-to-hosts. Since it's just a dictionary, you can store whatever config data you want
  • #23 The nuts and bolts of the command execution model. Essentially you invoke one or more tasks. Tasks can in turn invoke other tasks. Tasks are just Python functions. Any function within fabfile.py that doesn't begin with an _ is considered a task. TIP: If you're importing functions into your fabfile.py for use, import the module they're contained in instead. That way they don't show up as tasks.
  • #24 Decorators
  • #25 There's some problems with Fabric's role system out of the box. Attempting to override the defaults from the command line don't really work well. The maintainers acknowledge this -- and they're waiting on a fix until they nail down the syntax. But they're easy enough to fix now by writing our own decorators. These decorators extend Fabric's built in @roles and @hosts. With these, If you don't specify a host or role on the command line, the tasks will be executed with whatever the default is provided in the decorator. This is how the docs suggest Fabric is supposed to work -- but I didn't have luck with it.
  • #26 Once we have a sane execution model, we can continue writing some tasks.
  • #29 List of Whiskey's Fabric tasks
  • #30 Chaining of tasks is as simple as one Python function calling another. Each function being referenced here is a separate Fabric task, each of which could be called independently. 95% of the time, this function is used.