DevOps with Fabric

39
DevOps with Fabric Pycon5 Simone Federici

Transcript of DevOps with Fabric

Page 1: DevOps with Fabric

DevOps with FabricPycon5

Simone Federici

Page 2: DevOps with Fabric

$ fab taskA taskBfrom fabric.api import run, env

env.hosts = ['host1', 'host2']def taskA(): run('ls')def taskB(): run('whoami')

Page 3: DevOps with Fabric

$ fab mytask:roles=role1from fabric.api import env

env.roledefs = { 'web': ['www1', 'www2', 'www3'], 'dns': ['ns1', 'ns2']}

def mytask(): run('ls /var/www')

Page 4: DevOps with Fabric

$ fab mytask:roles=role1,exclude_hosts="a;c"from fabric.api import env, hosts, roles, run

env.roledefs = {'role1': ['b', 'c']}

@hosts('a', 'b')@roles('role1')def mytask(): run('ls /var/www')

Page 5: DevOps with Fabric

$ fab migrate updatefrom fabric.api import run, roles

env.roledefs = { 'db': ['db1', 'db2'], 'web': ['web1', 'web2', 'web3'],}

@roles('db')def migrate(): # Database stuff here. pass

@roles('web')def update(): # Code updates here. pass

Page 6: DevOps with Fabric

fab deployfrom fabric.api import run, roles, executedef deploy():

execute(migrate) execute(update)

migrate on db1migrate on db2

update on web1update on web2update on web3

Page 7: DevOps with Fabric

$ fab deploy:app or $ fab deploy:dbfrom fabric.api import run, execute, task# For example, code talking to an HTTP API, or a database, or ...from mylib import

external_datastore# This is the actual algorithm involved. It does not care about host# lists at all.def do_work(): run("something interesting on a host")# This is the user-facing task invoked on the command line.@taskdef deploy(lookup_param): # This is the magic you don't get with @hosts or @roles. # Even lazy-loading roles require you to declare available roles # beforehand. Here, the sky is the limit. host_list = external_datastore.query(lookup_param) # Put this dynamically generated host list together with the work to be # done. execute(do_work, hosts=host_list)

Page 8: DevOps with Fabric

$ fab set_hosts:app do_workfrom fabric.api import run, taskfrom mylib import external_datastore# Marked as a publicly visible task, but otherwise unchanged:

still just# "do the work, let somebody else worry about what hosts to run on".@taskdef do_work(): run("something interesting on a host")

@taskdef set_hosts(lookup_param): # Update env.hosts instead of calling execute() env.hosts = external_datastore.query(lookup_param)

Page 9: DevOps with Fabric

Combining stdout and stderr

run("cmd", pty=False, combine_stderr=True):

run("cmd", pty=False, combine_stderr=False):

run("cmd", pty=True, combine_stderr=False):

Page 10: DevOps with Fabric

$ fab -H host1,host2,host3 runs_in_parallel runs_serially

from fabric.api import *

@paralleldef runs_in_parallel(): passdef runs_serially(): pass

runs_in_parallel on host1, host2, and host3runs_serially on host1runs_serially on host2runs_serially on host3

Page 11: DevOps with Fabric

$ fab -P -z 5 heavy_taskfrom fabric.api import *

@parallel(pool_size=5)def heavy_task(): # lots of heavy local lifting or lots of IO here

Page 12: DevOps with Fabric

@task(alias=’short’)from fabric.api import task, run

@taskdef mytask(): run("a command")

@task(alias='dwm')def deploy_with_migrations(): pass

Page 13: DevOps with Fabric

Submodule deploy.py@task(default=True)def full_deploy():

pass

$ fab --listAvailable commands:

deploy deploy.full_deploy deploy.migrate deploy.provision deploy.push

Page 14: DevOps with Fabric

Class Taskclass MyTask(Task):

name = "deploy" def run(self, environment, domain="whatever.com"): run("git clone foo") sudo("service apache2 restart")

instance = MyTask()

VS

@taskdef deploy(environment, domain="whatever.com"): run("git clone foo") sudo("service apache2 restart")

Page 15: DevOps with Fabric

Colorsfrom fabric.colors import greenprint(green("This text is green!"))

fabric.colors.blue(text, bold=False)fabric.colors.cyan(text, bold=False)fabric.colors.green(text, bold=False)fabric.colors.magenta(text, bold=False)fabric.colors.red(text, bold=False)fabric.colors.white(text, bold=False)fabric.colors.yellow(text, bold=False)

Page 16: DevOps with Fabric

Context managersdef mytask():

with cd('/path/to/app'), prefix('workon myvenv'): run('./manage.py syncdb') run('./manage.py loaddata myfixture')

with cd('/var/www'): run('ls') # cd /var/www && ls with cd('website1'): run('ls') # cd /var/www/website1 && ls

with hide('running', 'stdout', 'stderr'): run('ls /var/www')

# Map localhost:6379 on the server to localhost:6379 on the client, # so that the remote 'redis-cli' program ends up speaking to the local # redis-server. with remote_tunnel(6379): run("redis-cli -i")

Page 17: DevOps with Fabric

ContribDjango IntegrationRsync ProjectUpload ProjectConsole Confirm y/nFiles and Directory

Page 18: DevOps with Fabric

So… what is Fabric?

● Deploy● Manage multiple server● Clustering● Multiplatform● Parallel● Testing● SSH Authentication

Page 19: DevOps with Fabric

SSH + Bash Power + Python = Rocks**3 =

Fabric

Page 20: DevOps with Fabric

fab release:master…

Page 21: DevOps with Fabric

django integrationfrom fabric.contrib import django

django.settings_module('myproject.settings')from django.conf import settingsdef dump_production_database(): run(pg_dump -U %s -w %s > /bck/prod-db.sql' % ( settings.DATABASE_USER, settings.DATABASE_NAME ))

Page 22: DevOps with Fabric

fab deploy@hosts('[email protected]')def deploy(): with cd("/opt/myproject"): run("git pull") run("django-admin.py collectstatic --noinput") run("django-admin.py migrate --noinput") run("/etc/init.d/uwsgi stop || echo 'done'") run("/etc/init.d/uwsgi start")

Page 23: DevOps with Fabric

fab backup_and_publish@task@hosts('[email protected]')def backup_and_publish():

run('''tar cjvf /var/wwwbackups/www.%s.tar.bz2 --exclude=/var/www/download --exclude=/var/www/backup* /var/www''' % today().strftime("%Y%m%d%H%M%S"))

run('rsync -avz --checksum --ignore-times /var/wwwstage/ /var/www') #--delete

Page 24: DevOps with Fabric

fab static_generation@taskdef static_generation(): execute(remote_generation)

local("wget --user=admin --password=rootme --recursive --page-requisites --html-extension --convert-links --restrict-file-names=windows --domains example.com --no-parent http://wwwstage.example.com/ -o dump.log || echo 'Looking for 404 on wwwstage.example.com'")

local("cat dump.log | grep -B 2 '404 Not Found' | grep 'http://wwwstage.example.com/' || echo 'OK no 404 found...'")

Page 25: DevOps with Fabric

fab upload_release_note:2.10.0@task@hosts('[email protected]')def upload_release_note(version): release_note_file_name = "RELEASE_NOTE_%s.TXT" % version with open(release_note_file_name,"w") as out_file: notes = jira.Query().render(version=version) out_file.write(notes.encode('ascii', 'ignore')) out_file.close() put(release_note_file_name, "/cygdrive/d/%s/" % version)

Page 26: DevOps with Fabric

fab cleanup_nexus:10.0.2-RC2@taskdef cleanup_nexus(version): for module in [ "core-api", "core-client-rest", "core-manual", "core-web"]:

local("curl -X DELETE -u user:rootme http://nexus.example.com:8180/nexus/service/local/repositories/releases/content/example/%s/%s/" % (module, version))

Page 27: DevOps with Fabric

LOCK_FILE = "~/.lockfile.release.core.lock"class Lock(): def __enter__(self): if os.access(os.path.expanduser(LOCK_FILE), os.F_OK): pidfile = open(os.path.expanduser(LOCK_FILE), "r") pidfile.seek(0) old_pid = pidfile.readline() print "There is an already a process running with pid: %s," % old_pid sys.exit(1)

pidfile = open(os.path.expanduser(LOCK_FILE), "w") pidfile.write("%s" % os.getpid()) pidfile.close

def __exit__(self, type, value, traceback): os.remove(os.path.expanduser(LOCK_FILE))

Locks

Page 28: DevOps with Fabric

fab send_email_candidate:2.12,me@...@taskdef send_mail_candidate(version, *receivers): sender = '[email protected]'

body = """From: Core Team <[email protected]>To: Development <[email protected]>Subject: New Release CANDIDATE %(version)s\nNew Release CANDIDATE %(version)savailable on: * smb://example.com/myproject/%(version)s """ % dict(version=version) try: message = smtplib.SMTP('example.com') message.sendmail(sender, receivers, body) print "Successfully sent email" except smtplib.SMTPException: print "Error: unable to send email"

Page 29: DevOps with Fabric

XML parser@taskdef get_pom_version(): src=os.path.dirname(__file__) pom_file=os.path.abspath(os.path.join(src, 'pom.xml')) from xml.dom.minidom import parse pom = parse(pom_file) version = pom.getElementsByTagName("version")[1].firstChild.nodeValue find = re.compile(r'^\d+.\d+.\d+-([a-zA-Z-]+)\d*-SNAPSHOT$').findall(version) if not find: abort(version + " is not a valid development version") versions = re.compile(r'\d+').findall(version) if len(versions) is 3: versions.append(1) if len(versions) is not 4: abort(version + " is not a valid development version") versions.extend(find) return versions

Page 30: DevOps with Fabric

fab sed_poms_version:2.13@taskdef sed_poms_version(new_version): major, minor, patch, rev, rc = get_pom_version() version = "%s.%s.%s-%s%s-SNAPSHOT" % (major, minor, patch, rc, rev) local("sed -i '' 's@%s@%s@g' pom.xml */pom.xml" % (version, new_version))

Page 31: DevOps with Fabric

fab create_manuals:2.10ACTIVATE="source /home/installer/.sphinx_env/bin/activate"

@hosts('[email protected]')def create_manuals(version): with cd(DEPLOY_DIR + "/myproject"): name = "manual-%s-doc" % version run("wget http://nexus.example.com:8180/nexus/service/local/repo_groups/public/content/com/myproject/manual/%s/%s.zip" % (version, name))

with cd(name): run(ACTIVATE + "&& make docs") run("mv target/en/latex/MyProject*.pdf docs/" % version) run("tar cjvf docs/MyCDDS-en-%s.tar.bz2 target/docs/" % version) run("scp -r docs [email protected]:/cygdrive/d/%s/docs" % version)

Page 32: DevOps with Fabric

fab create_installer:2.10 (innosetup)@hosts('[email protected]')def create_installer(version): name = "installer-%s-app" % version run("wget http://nexus.geniusbytes.com:8180/nexus/service/local/repo_groups/public/content/myproject/installer/%s/%s.zip" % (version, name))

with cd(name): run("tar xjvf /cygdrive/d/%(version)s/MyCDDS-en-%(version)s.tar.bz2" % dict(version=version))

run(ANT) run("mkdir -p /cygdrive/d/%s" % version) run("cp build/*.exe /cygdrive/d/%s/" % version) run("cp build-update/*.exe /cygdrive/d/%s/" % version) run("rm -rf %s" % name)

Page 33: DevOps with Fabric

fab release_core:master,2.10,2.11@hosts('[email protected]')def release_core(branch, version, next_version): with cd(CORE_DEPLOY_DIR): run('git clone ssh://[email protected]/srv/git/myproject.git') with cd(CORE_DEPLOY_DIR + "/myproject"): run("git checkout %s" % branch) run("mvn clean install")

run("mvn --batch-mode release:clean release:prepare -DreleaseVersion=%s -DdevelopmentVersion=%s" % (version, next_version)) run("mvn release:perform")

Page 34: DevOps with Fabric

fab release:masterdef release(branch): with Lock(): major, minor, patch, rev, rc = check_current_version(branch) if 'RC' != rc: abort("RC not found, not possible release a final version")

version = "%s.%s.%s" % (major, minor, patch) next_version = "%s.%s.%s-RC%s-SNAPSHOT" % (major, minor, int(patch)+1, 1)

puts("preparing to release %s (next will be %s)" % (version, next_version)) execute(release_core, branch, version, next_version) execute(create_manuals, version) execute(create_installer, version) execute(upload_release_note, version) execute(send_mail_final_release, version, '[email protected]', '[email protected]') local("git pull") execute(labels.missing_translations, '[email protected]')

Page 35: DevOps with Fabric

fab cluod_remote_control...

Page 36: DevOps with Fabric

EC2 Testing with 200 micro serverEC2 + Fabric + Funkload

● EC2 use all ubuntu standard AMI● Fabric as remote control, move files, aggregate.● Funkload in order to stress an internet application. (not

on EC2)

Page 37: DevOps with Fabric

Performance Testing Architecture

Target

Cloud CTRL

Tester

TargetTarget

FunkLoad

Fabric(nmon+pefmon)

Fabric + EC2

Fabric + EC2

Page 38: DevOps with Fabric

Testing phasesI. Prepare MonitoringII. Prepare Cloud

1. Start Monitoring2. Start Parallel Testing3. Collecting Test Results4. Collecting Perf Results5. ReportingTarget

Cloud CTRL

Tester

Target

Target

FunkLoad

Fabric(nmon+pefmon)

Fabric + EC2

Fabric + EC2

Page 39: DevOps with Fabric

Testing Consolefab prepare_monitoringfab prepare_cloud

fab start_monitoringfab start_testing:ciccio,100,5000fab collecting_test_results:cicciofab collecting_perf_results:cicciofab reporting:ciccio