{"id":3497,"date":"2016-07-28T09:16:35","date_gmt":"2016-07-28T08:16:35","guid":{"rendered":"https:\/\/www.devco.net\/?p=3497"},"modified":"2016-08-31T16:49:39","modified_gmt":"2016-08-31T15:49:39","slug":"a-look-at-the-puppet-application-orchestration-feature","status":"publish","type":"post","link":"https:\/\/www.devco.net\/archives\/2016\/07\/28\/a-look-at-the-puppet-application-orchestration-feature.php","title":{"rendered":"A look at the Puppet 4 Application Orchestration feature"},"content":{"rendered":"
Puppet 4 got some new language constructs that let you model multi node applications and it assist with passing information between nodes for you. I recently wrote a open source orchestrator for this stuff which is part of my Choria suite<\/a>, figured I’ll write up a bit about these multi node applications since they are now useable in open source.<\/p>\n The basic problem this feature solves is about passing details between modules. Lets say you have a LAMP stack, you’re going to have Web Apps that need access to a DB and that DB will have a IP, User, Password etc. Exported resources never worked for this because it’s just stupid to have your DB exporting web resources, there are unlimited amount of web apps and configs, no DB module support this. So something new is needed.<\/p>\n The problem is made worse by the fact that Puppet does not really have something like a Java interface<\/em>, you can’t say that foo-mysql module implements a standard interface called database<\/em> so that you can swap out one mysql module for another, they’re all snowflakes. So a intermediate translation layer is needed.<\/p>\n In a way you can say this new feature brings a way to create an interface – lets say SQL – and allows you to hook random modules into both sides of the interface. On one end a database and on the other a webapp. Puppet will then transfer the information across the interface for you, feeding the web app with knowledge of port, user, hosts etc.<\/p>\n These interfaces are called capabilities<\/em>, here’s an example of a SQL one:<\/p>\n This is a generic interface to a database, you can imagine Postgres or MySQL etc can all satisfy this interface, perhaps you could add here a field to confer the type of database, but lets keep it simple. The capability provides a translation layer between 2 unrelated modules. <\/p>\n It’s a pretty big deal conceptually, I can see down the line there be some blessed official capabilities and we’ll see forge modules starting to declare their compatibility. And finally we can get to a world of interchangeable infrastructure modules.<\/p>\n Now I’ll create a defined type to make my database for my LAMP stack app, I’m just going to stick a notify in instead of the actual creating of a database to keep it easy to demo:<\/p>\n I need to tell Puppet this defined type exist to satisfy the producing side of the interface, there’s some new language syntax to do this, it feels kind of out of place not having a logical file to stick this in, I just put it in my lamp\/manifests\/mysql.pp<\/em>:<\/p>\n Here you can see the mapping from the variables in the defined type to those in the capability above. $db_user<\/em> feeds into the capability property $user<\/em> etc.<\/p>\n With this in place if you have a lamp::mysql<\/em> or one based on some other database, you can always query it’s properties based on the standard user<\/em> etc, more on that below.<\/p>\n So we have a database, and we want to hook a web app onto it, again for this we use a defined type and again just using notifies to show the data flow:<\/p>\n As this is the other end of the translation layer enabled by the capability we tell Puppet that this defined type consumes<\/em> a Sql<\/em> capability:<\/p>\n This tells Puppet to read the value of user<\/em> from the capability and stick it into db_user<\/em> of the defined type. Thus we can plumb arbitrary modules found on the forge together with a translation layer between their properties!<\/p>\n So you have a data producer<\/em> and a data consumer<\/em> that communicates across a translation layer called a capability<\/em>.<\/p>\n The final piece of the puzzle that defines our LAMP application stack is again some new language features:<\/p>\n Pay particular attention to the application<\/em> bit and export<\/em> and consume<\/em> meta parameters here. This tells the system to feed data from the above created translation layer between these defined types.<\/p>\n You should kind of think of the lamp::mysql<\/em> and lamp::webapp<\/em> as node roles, these define what an individual node will do in this stack. If I create this application and set $instances = 10<\/em> I will need 1 x database machine and 10 x web machines. You can cohabit some of these roles but I think that’s a anti pattern. And since these are different nodes – as in entirely different machines – the magic here is that the capability based data system will feed these variables from one node to the next without you having to create any specific data on your web instances.<\/p>\n Finally, like a traditional node<\/em> we now have a site<\/em> which defines a bunch of nodes and allocate resources to them.<\/p>\n Here we are creating two instances of the LAMP application stack, each with it’s own database and with 3 web servers assigned to the cluster.<\/p>\n You have to be super careful about this stuff, if I tried to put my Mysql for app1 on dev1 and the Mysql for app2 on dev2 this would basically just blow up, it would be a cyclic dependency across the nodes. You generally best avoid sharing nodes across many app stacks or if you do you need to really think this stuff through. It’s a pain.<\/p>\n You now have this giant multi node monolith with order problems not just inter resource but inter node too.<\/p>\n There’s a problem though, traditionally Puppet runs every 30 minutes and gets a new catalog every 30 minutes. We can’t have these nodes randomly get catalogs in random order since there’s no giant network aware lock\/ordering system. So Puppet now has a new model, nodes are supposed to run cached catalogs for ever and only get a new catalog when specifically told so. You tell it to deploy this stack and once deployed Puppet goes into a remediation cycle fixing the stack as it is with an unchanging catalog. If you want to change code, you again have to run this entire stack in this specific order.<\/p>\n This is a nice improvement for release management and knowing your state, but without tooling to manage this process it’s a fail, and today that tooling is embryonic and PE only.<\/p>\nLAMP Stack Walkthrough<\/H3>
\nLets walk through creating a standard definition for a multi node LAMP stack, and we’ll create 2 instances of the entire stack. It will involve 4 machines sharing data and duties.<\/p>\n\r\nPuppet::Type.newtype :sql, :is_capability => true do\r\n newparam :name, :is_namevar => true\r\n newparam :user\r\n newparam :password\r\n newparam :host\r\n newparam :database\r\nend\r\n<\/pre>\n
\r\ndefine lamp::mysql (\r\n $db_user,\r\n $db_password,\r\n $host = $::hostname,\r\n $database = $name,\r\n) {\r\n notify{\"creating mysql db ${database} on ${host} for user ${db_user}\": }\r\n}\r\n<\/pre>\n
\r\nLamp::Mysql produces Sql {\r\n user => $db_user,\r\n password => $db_password,\r\n host => $host,\r\n database => $database,\r\n}\r\n<\/pre>\n
\r\ndefine lamp::webapp (\r\n $db_user,\r\n $db_password,\r\n $db_host,\r\n $db_name,\r\n $docroot = '\/var\/www\/html'\r\n) {\r\n notify{\"creating web app ${name} with db ${db_user}@${db_host}\/${db_name}\": }\r\n}\r\n<\/pre>\n
\r\nLamp::Webapp consumes Sql {\r\n db_user => $user,\r\n db_password => $password,\r\n db_host => $host,\r\n db_name => $database,\r\n}\r\n<\/pre>\n
\r\napplication lamp (\r\n String $db_user,\r\n String $db_password,\r\n Integer $web_instances = 1\r\n) {\r\n lamp::mysql { $name:\r\n db_user => $db_user,\r\n db_password => $db_password,\r\n export => Sql[$name],\r\n }\r\n\r\n range(1, $web_instances).each |$instance| {\r\n lamp::webapp {\"${name}-${instance}\":\r\n consume => Sql[$name],\r\n }\r\n }\r\n}\r\n<\/pre>\n
\r\nsite {\r\n lamp{'app2':\r\n db_user => 'user2',\r\n db_password => 'secr3t',\r\n web_instances => 3,\r\n nodes => {\r\n Node['dev1.example.net'] => Lamp::Mysql['app2'],\r\n Node['dev2.example.net'] => Lamp::Webapp['app2-1'],\r\n Node['dev3.example.net'] => Lamp::Webapp['app2-2'],\r\n Node['dev4.example.net'] => Lamp::Webapp['app2-3']\r\n }\r\n }\r\n\r\n lamp{'app1':\r\n db_user => 'user1',\r\n db_password => 's3cret',\r\n web_instances => 3,\r\n nodes => {\r\n Node['dev1.example.net'] => Lamp::Mysql['app1'],\r\n Node['dev2.example.net'] => Lamp::Webapp['app1-1'],\r\n Node['dev3.example.net'] => Lamp::Webapp['app1-2'],\r\n Node['dev4.example.net'] => Lamp::Webapp['app1-3']\r\n }\r\n }\r\n}\r\n<\/pre>\n
Deployment<\/H3>
\nDeploying these stacks with the abilities the system provide is pretty low tech. If you take a look at the site above you can infer dependencies. First we have to run dev1.example.net<\/em>. It will both produce<\/em> the data needed and install the needed infrastructure, and then we can run all the web nodes in any order or even at the same time.<\/p>\n