batou - multi(component|host|environment|.*) deployment

66
BATOU multi-(component|host|environment|.*) deployment Wednesday, 3.July 13

description

Talk given at EuroPython 2013

Transcript of batou - multi(component|host|environment|.*) deployment

Page 1: batou - multi(component|host|environment|.*) deployment

BATOUmulti-(component|host|environment|.*)

deployment

Wednesday, 3.July 13

Page 2: batou - multi(component|host|environment|.*) deployment

@theuni

Wednesday, 3.July 13

Page 3: batou - multi(component|host|environment|.*) deployment

Wednesday, 3.July 13

Page 4: batou - multi(component|host|environment|.*) deployment

Wednesday, 3.July 13

Page 5: batou - multi(component|host|environment|.*) deployment

Wednesday, 3.July 13

Page 6: batou - multi(component|host|environment|.*) deployment

AUTOMATING DEPLOYMENTS IS HARD

Wednesday, 3.July 13

Page 7: batou - multi(component|host|environment|.*) deployment

HOW DOES

CONVERGENCE HELP?

Wednesday, 3.July 13

Page 8: batou - multi(component|host|environment|.*) deployment

HOW DOES THIS WORK WITH BATOU?

Wednesday, 3.July 13

Page 9: batou - multi(component|host|environment|.*) deployment

SOME PERSPECTIVE

Wednesday, 3.July 13

Page 10: batou - multi(component|host|environment|.*) deployment

Wednesday, 3.July 13

Page 11: batou - multi(component|host|environment|.*) deployment

IT'S NOT THAT BAD.

Wednesday, 3.July 13

Page 12: batou - multi(component|host|environment|.*) deployment

Wednesday, 3.July 13

Page 13: batou - multi(component|host|environment|.*) deployment

service deployment

Fabric, Capistrano, ...

system configuration Puppet, Chef, ...

provisioning kickstart, Razor, imaging ...

Wednesday, 3.July 13

Page 14: batou - multi(component|host|environment|.*) deployment

FTP

bashmkzopeinstance

zc.buildoutfabric

Wednesday, 3.July 13

Page 15: batou - multi(component|host|environment|.*) deployment

CONVERGENCE

Wednesday, 3.July 13

Page 16: batou - multi(component|host|environment|.*) deployment

"Everything that follows is a result of what you see here."(Dr. Alfred Lanning; I, Robot)

Wednesday, 3.July 13

Page 17: batou - multi(component|host|environment|.*) deployment

SIMPLEos.mkdir('foo')with open('foo/bar', 'w') as myfile: myfile.write('asdf')os.chmod('foo/bar', 0755)

Wednesday, 3.July 13

Page 18: batou - multi(component|host|environment|.*) deployment

•unexpected system state

•can't resume

•unnecessary updates

os.mkdir('foo')with open('foo/bar', 'w') as myfile: myfile.write('asdf')os.chmod('foo/bar', 0755)

SIMPLISTIC

Wednesday, 3.July 13

Page 19: batou - multi(component|host|environment|.*) deployment

CORRECT(?)if not os.path.isdir('foo'): os.unlink('foo')if not os.path.exists('foo'): os.mkdir('foo')try: os.lstat('foo/bar')except OSError: passelse: if os.path.isdir('foo/bar'): shutil.rmtree('foo/bar') else: os.unlink('foo/bar')if (os.path.exists('foo/bar') and open('foo/bar', 'r').read() != 'asdf'): open('foo/bar', 'w').write('asdf'):current = os.stat('foo/bar').st_modeif stat.S_IMODE(current) != 0755: os.chmod('foo', 0755)

Wednesday, 3.July 13

Page 20: batou - multi(component|host|environment|.*) deployment

SIMPLE

File('foo/bar', content='asdf', mode=0755, leading=True)

Wednesday, 3.July 13

Page 21: batou - multi(component|host|environment|.*) deployment

class File(Component):

namevar = 'path'

def configure(self): self += Presence( self.path, leading=self.leading) self += Mode(self.path, self.mode) self += Content(self.path, self.content)

File('foo/bar', content='asdf', mode=0755, leading=True)

Wednesday, 3.July 13

Page 22: batou - multi(component|host|environment|.*) deployment

class File(Component):

namevar = 'path'

def configure(self): self += Presence( self.path, leading=self.leading) self += Mode(self.path, self.mode) self += Content(self.path, self.content)

File('foo/bar', content='asdf', mode=0755, leading=True)

compute target state (no touching!)

Wednesday, 3.July 13

Page 23: batou - multi(component|host|environment|.*) deployment

class File(Component):

namevar = 'path'

def configure(self): self += Presence( self.path, leading=self.leading) self += Mode(self.path, self.mode) self += Content(self.path, self.content)

File('foo/bar', content='asdf', mode=0755, leading=True)

compute target state (no touching!)

composition operator

Wednesday, 3.July 13

Page 24: batou - multi(component|host|environment|.*) deployment

class File(Component):

namevar = 'path'

def configure(self): self += Presence( self.path, leading=self.leading) self += Mode(self.path, self.mode) self += Content(self.path, self.content)

File('foo/bar', content='asdf', mode=0755, leading=True)

compute target state (no touching!)

composition operator

order matters

Wednesday, 3.July 13

Page 25: batou - multi(component|host|environment|.*) deployment

class Presence(Component):

namevar = 'path' leading = False

def configure(self): if self.leading: self += Directory( os.path.dirname(self.path), leading=self.leading)

def verify(self): if not os.path.isfile(self.path): raise batou.UpdateNeeded()

def update(self): ensure_path_nonexistent(self.path) with open(self.path, 'w'): pass

Wednesday, 3.July 13

Page 26: batou - multi(component|host|environment|.*) deployment

class Presence(Component):

namevar = 'path' leading = False

def configure(self): if self.leading: self += Directory( os.path.dirname(self.path), leading=self.leading)

def verify(self): if not os.path.isfile(self.path): raise batou.UpdateNeeded()

def update(self): ensure_path_nonexistent(self.path) with open(self.path, 'w'): pass

run "anywhere"

Wednesday, 3.July 13

Page 27: batou - multi(component|host|environment|.*) deployment

class Presence(Component):

namevar = 'path' leading = False

def configure(self): if self.leading: self += Directory( os.path.dirname(self.path), leading=self.leading)

def verify(self): if not os.path.isfile(self.path): raise batou.UpdateNeeded()

def update(self): ensure_path_nonexistent(self.path) with open(self.path, 'w'): pass

run on target

run "anywhere"

Wednesday, 3.July 13

Page 28: batou - multi(component|host|environment|.*) deployment

class Presence(Component):

namevar = 'path' leading = False

def configure(self): if self.leading: self += Directory( os.path.dirname(self.path), leading=self.leading)

def verify(self): if not os.path.isfile(self.path): raise batou.UpdateNeeded()

def update(self): ensure_path_nonexistent(self.path) with open(self.path, 'w'): pass

run on target

run "anywhere"

after all sub-components

Wednesday, 3.July 13

Page 29: batou - multi(component|host|environment|.*) deployment

class Presence(Component):

namevar = 'path' leading = False

def configure(self): if self.leading: self += Directory( os.path.dirname(self.path), leading=self.leading)

def verify(self): if not os.path.isfile(self.path): raise batou.UpdateNeeded()

def update(self): ensure_path_nonexistent(self.path) with open(self.path, 'w'): pass

run on target

only if needed

run "anywhere"

after all sub-components

Wednesday, 3.July 13

Page 30: batou - multi(component|host|environment|.*) deployment

class Presence(Component):

namevar = 'path' leading = False

def configure(self): if self.leading: self += Directory( os.path.dirname(self.path), leading=self.leading)

def verify(self): if not os.path.isfile(self.path): raise batou.UpdateNeeded()

def update(self): ensure_path_nonexistent(self.path) with open(self.path, 'w'): pass

run on target

only if needed

run "anywhere"

after all sub-components

keep delegating!

Wednesday, 3.July 13

Page 31: batou - multi(component|host|environment|.*) deployment

class Directory(Component):

namevar = 'path' leading = False

def verify(self): if not os.path.isdir(self.path): raise batou.UpdateNeeded()

def update(self): ensure_path_nonexistent(self.path) if self.leading: os.makedirs(self.path) else: os.mkdir(self.path)

Wednesday, 3.July 13

Page 32: batou - multi(component|host|environment|.*) deployment

class Directory(Component):

namevar = 'path' leading = False

def verify(self): if not os.path.isdir(self.path): raise batou.UpdateNeeded()

def update(self): ensure_path_nonexistent(self.path) if self.leading: os.makedirs(self.path) else: os.mkdir(self.path)

could be done with recursive composition

Wednesday, 3.July 13

Page 33: batou - multi(component|host|environment|.*) deployment

class Directory(Component):

namevar = 'path' leading = False

def verify(self): if not os.path.isdir(self.path): raise batou.UpdateNeeded()

def update(self): ensure_path_nonexistent(self.path) if self.leading: os.makedirs(self.path) else: os.mkdir(self.path)

could be done with recursive composition

refactor with sub-components if too

complex

Wednesday, 3.July 13

Page 34: batou - multi(component|host|environment|.*) deployment

class Directory(Component):

namevar = 'path' leading = False

def verify(self): if not os.path.isdir(self.path): raise batou.UpdateNeeded()

def update(self): ensure_path_nonexistent(self.path) if self.leading: os.makedirs(self.path) else: os.mkdir(self.path)

all methods optional: no configure()

could be done with recursive composition

refactor with sub-components if too

complex

Wednesday, 3.July 13

Page 35: batou - multi(component|host|environment|.*) deployment

class Directory(Component):

namevar = 'path' leading = False

def verify(self): if not os.path.isdir(self.path): raise batou.UpdateNeeded()

def update(self): ensure_path_nonexistent(self.path) if self.leading: os.makedirs(self.path) else: os.mkdir(self.path)

all methods optional: no configure()

could be done with recursive composition

pattern: just wipe out what's wrong

refactor with sub-components if too

complex

Wednesday, 3.July 13

Page 36: batou - multi(component|host|environment|.*) deployment

CONVERGENCE

resume where needed

handle many system states transparently avoid

unnecessary updates

Wednesday, 3.July 13

Page 37: batou - multi(component|host|environment|.*) deployment

COMPONENTS

composition of simple components

no magic bullet, just a lot easier to factor

your code

configure - verify - update

Wednesday, 3.July 13

Page 38: batou - multi(component|host|environment|.*) deployment

Wednesday, 3.July 13

Page 39: batou - multi(component|host|environment|.*) deployment

SINGLE-COMMAND

Wednesday, 3.July 13

Page 40: batou - multi(component|host|environment|.*) deployment

REPEATABLERELIABLE

Wednesday, 3.July 13

Page 41: batou - multi(component|host|environment|.*) deployment

SIMPLE

Wednesday, 3.July 13

Page 42: batou - multi(component|host|environment|.*) deployment

ENTROPY

Wednesday, 3.July 13

Page 43: batou - multi(component|host|environment|.*) deployment

EXPRESSIVENESSREADABILITY

Wednesday, 3.July 13

Page 44: batou - multi(component|host|environment|.*) deployment

REUSABLE

Wednesday, 3.July 13

Page 45: batou - multi(component|host|environment|.*) deployment

PLATFORM INDEPENDENCE

Wednesday, 3.July 13

Page 46: batou - multi(component|host|environment|.*) deployment

DOMAIN AGNOSTIC

Wednesday, 3.July 13

Page 47: batou - multi(component|host|environment|.*) deployment

NO ADDITIONAL RUNTIME

DEPENDENCIES

Wednesday, 3.July 13

Page 48: batou - multi(component|host|environment|.*) deployment

CONTINUITY

Wednesday, 3.July 13

Page 49: batou - multi(component|host|environment|.*) deployment

MINIMAL DOWNTIMES

Wednesday, 3.July 13

Page 50: batou - multi(component|host|environment|.*) deployment

Wednesday, 3.July 13

Page 51: batou - multi(component|host|environment|.*) deployment

PRACTICAL USAGE

Wednesday, 3.July 13

Page 52: batou - multi(component|host|environment|.*) deployment

REQUIREMENTS

Python 2.7

SSH

virtualenvMercurial

Wednesday, 3.July 13

Page 53: batou - multi(component|host|environment|.*) deployment

ENVIRONMENTS

[environment]service_user = myservicehost_domain = flyingcircus.iobranch = production

[hosts]multikarl00 = nginx, haproxymultikarl01 = postgres, redis, memcached, crontabmultikarl12 = supervisor, logrotate, doctotext, myappmultikarl13 = supervisor, logrotate, doctotext, myapp

Wednesday, 3.July 13

Page 54: batou - multi(component|host|environment|.*) deployment

LOCAL

$ bin/batou-local dev localhostUpdating Hello > File(hello) > Presence(hello)Updating Hello > File(hello) > Content(hello)$ bin/batou-local dev localhost$

Wednesday, 3.July 13

Page 55: batou - multi(component|host|environment|.*) deployment

REMOTE$ bin/batou-remote prodtest02.gocept.net: connectingtest01.gocept.net: connectingtest01.gocept.net: bootstrappingtest02.gocept.net: bootstrappingOKOKDeploying test01.gocept.net/helloUpdating Hello > File(hello) > Presence(hello)Updating Hello > File(hello) > Content(hello)OKDeploying test02.gocept.net/helloUpdating Hello > File(hello) > Presence(hello)Updating Hello > File(hello) > Content(hello)OK

Wednesday, 3.July 13

Page 56: batou - multi(component|host|environment|.*) deployment

OVERRIDES

class Hello(Component):

hostname = "foo"

[environment]...

[component:hello]hostname = bar

Wednesday, 3.July 13

Page 57: batou - multi(component|host|environment|.*) deployment

SECRETS

class Hello(Component):

db_password = none

secrets/production.cfg

[hello]db_password = reallysecretstuff

Wednesday, 3.July 13

Page 58: batou - multi(component|host|environment|.*) deployment

SECRETS

class Hello(Component):

db_password = none

secrets/production.cfg

[hello]db_password = reallysecretstuff

SciFibut close

Wednesday, 3.July 13

Page 59: batou - multi(component|host|environment|.*) deployment

PROVIDE/REQUIREclass MyApp(Component):

def configure(self): self.provide('appserver', self.host.fqdn)

class HAProxy(Component):

def configure(self): self.backends = \ self.require('appserver')

Wednesday, 3.July 13

Page 60: batou - multi(component|host|environment|.*) deployment

PLATFORMSclass HAProxy(Component): ...

@platform('flyingcircus.io', HAProxy)class SystemWideHAProxy(Component):

def configure(self): self += File('/etc/haproxy', ensure='symlink', link_to=self.parent.haproxy_cfg.path)

Wednesday, 3.July 13

Page 61: batou - multi(component|host|environment|.*) deployment

VFS MAPPING

./

...

./work

./work/_/etc/haproxy.cfg

class HAProxy(Component):

def configure(self): self += File('/etc/haproxy')

[environment]...[vfs]sandbox = Developer

Wednesday, 3.July 13

Page 62: batou - multi(component|host|environment|.*) deployment

FEATURESclass MyApp(Component):

features = ['instance', 'jobrunner']

def configure(self): if 'instance' in self.features: ...

[hosts]hosta = myapp:instancehostb = myapp:jobrunnerhostc = myapp:instance, myapp:jobrunnerhostd = myapp

Wednesday, 3.July 13

Page 63: batou - multi(component|host|environment|.*) deployment

Wednesday, 3.July 13

Page 64: batou - multi(component|host|environment|.*) deployment

CONVERGENCE

COMPOSITION

DETAILSWednesday, 3.July 13

Page 65: batou - multi(component|host|environment|.*) deployment

QUESTIONS?

Wednesday, 3.July 13

Page 66: batou - multi(component|host|environment|.*) deployment

batou.readthedocs.org

pypi.python.org/pypi/batou

bitbucket.org/gocept/batou

Wednesday, 3.July 13