Ask Your Question
1

What's best practice when combining Roles, Profiles, Containment and Ordering?

asked 2015-01-20 00:56:34 -0600

neilfromit gravatar image

updated 2015-01-20 13:26:34 -0600

ramindk gravatar image

I'm attempting to build some custom application modules using the profiles, roles combined with hiera workflow described by Gary Larizza. I'm using Puppet 3.7 open source variant on centos6 nodes but intend to deploy as an enterprise customer once my configuration is in a good enough shape. The problem I'm having is with understanding containment and puppet ordering in this context.

The applications I have are for the most part individual applications and can be used interchangeably in different roles. However several of those applications have dependencies on others under certain use cases.

One use case would be:

  1. install configure and start app1
  2. install and configure more stuff

However in a different use case I need to do the following:

  1. install configure and start app1
  2. install configure and start app2
  3. restart app1's service
  4. install and configure more stuff

I've attempted to implement this as my code example illustrates. Problem is, I can't do that as it creates a dependency loop between app1 and app2. I'm trying to achieve this in a single puppet run as further apps may or may not be dependent on the previous apps being up. I'm trying not do require inside the main app1,app2 modules but abstract them as single services.

I suppose my question(s) here is, am I doing this right? How should I be doing this? Is it even possible to refresh a service twice in a run whilst maintaining order?

roles/manifests/appserver.pp

class role::appserver {
  contain profile::app1
  contain profile::app2

  Class['::app1'] -> Class['::app2'] -> Service['app1']
}

profiles/manifest/app1.pp

class profile::app1 {
  class { '::app1': var => 'value' }
  contain app1::
}

profiles/manifest/app2.pp

class profile::app2 {
  class { '::app2': var => 'value' }
  contain app2::
}

modules/app1/manifests/init.pp

class app1 ( $var = app1::params::var ) inherits app1::params {
  contain ::app1::install, ::app1::config, ::app1::service
  Class['::app1::install'] -> Class['::app1::config'] -> Class['::app1::service'] 
}

class app1::install inherits app1 {
  package { 'app1': ensure => installed }
}

class app1::config inherits app1 {
  file { 'app1.conf': ensure => file }
}

class app1::service inherits app1 {
  service{ 'app1': ensure => running }
}

modules/app2/manifests/init.pp

class app2 ( $var = app2::params::var ) inherits app2::params {
  contain ::app2::install, ::app2::config, ::app2::service
  Class['::app2::install'] -> Class['::app2::config'] -> Class['::app2::service'] 
}

class app2::install inherits app2 {
  package {'app2': ensure => installed }
}

class app2::config inherits app2 {
  file {'app2.conf': ensure => file }
}

class app2::service inherits app2 {
  service{'app2': ensure => running }
}
edit retag flag offensive close merge delete

Comments

In your 2nd use case, does app2's installation/startup depend on app1 running? Also, does installing/starting app2 then modify a file that app1 depends on and must be reloaded before the subsequent installations can take place?

GregLarkin gravatar imageGregLarkin ( 2015-01-20 14:58:08 -0600 )edit

App2 depends on app1 being installed, but not necessarily started. Subsequent modules apps do depend on app1 and app2 being started. For now what I've done to work around the dependency loop is simply write a custom exec to bounce app1's service once app2 is up. Not ideal

neilfromit gravatar imageneilfromit ( 2015-01-21 16:30:53 -0600 )edit

I think the answer from @cbarbour will be key to your solution. You can make app2's profile class depend on app1's profile class, then make other classes depend on those app services starting up.

GregLarkin gravatar imageGregLarkin ( 2015-01-21 21:31:36 -0600 )edit

1 Answer

Sort by ยป oldest newest most voted
1

answered 2015-01-20 17:22:17 -0600

cbarbour gravatar image

updated 2015-01-20 17:26:45 -0600

As a general rule, you should never create direct relationships with the resources contained within a module; this violates the principles of interface driven design. You need to define and document the interfaces into your module, and create relationships with those interfaces.

The best approach, if at all possible is to design your modules so that app2 can be configured before app1. Use a simple notify relationship between the two modules. Inside App1, you would pass this notification to your service class. This side-steps the ordering problem you're experiencing.

If it is absolutely not possible to redesign the modules, the next best approach would be to modify app1 so that it has the following properties:

  1. App1's service is subclassed.
  2. App1's service is not contained.

I would use this general approach:

roles/manifests/usecase1.pp

class role::usecase1 {
  contain profile::app1

  Class['::app1'] ->
}

roles/manifests/usecase2.pp

class role::usecase2 {
  contain profile::app1
  contain profile::app2

  Class['::app1'] ->
  Class['::app2']
}

profiles/manifest/app1.pp

class profile::app1 {
  class { '::app1': var => 'value' }
  contain app1::
}

profiles/manifest/app2.pp

class profile::app2 {
  class { '::app2': var => 'value' }
  contain app2::

  Class['::app2'] ~>
  Class['::app1::service']

}

modules/app1/manifests/init.pp

class app1 ( $var = app1::params::var ) inherits app1::params {
  contain ::app1::install, ::app1::config

  #::app1::service is not contained in ::app1 by design.
  include ::app1::service
  Class['::app1::install'] -> Class['::app1::config'] -> Class['::app1::service'] 
}

class app1::install inherits app1 {
  package { 'app1': ensure => installed }
}

class app1::config inherits app1 {
  file { 'app1.conf': ensure => file }
}

class app1::service inherits app1 {
  #app1::service is an interface into the app1 class.
  #This class must not be renamed for compatibility purposes.
  service{ 'app1': ensure => running }
}

modules/app2/manifests/init.pp

class app2 ( $var = app2::params::var ) inherits app2::params {
  contain ::app2::install, ::app2::config, ::app2::service
  Class['::app2::install'] -> Class['::app2::config'] -> Class['::app2::service'] 
}

class app2::install inherits app2 {
  package {'app2': ensure => installed }
}

class app2::config inherits app2 {
  file {'app2.conf': ensure => file }
}

class app2::service inherits app2 {
  service{'app2': ensure => running }
}

I haven't tested this code. You may have to remove the inheritance relationship between app1 and app1::service to avoid dependencies.

Again, remember to document your code. App1 needs to clearly document its parameters, and must explain that ::app1::service is a point of entry into the module, as well as the reasons for using this approach. Again, this situation isn't ideal.

As an aside, I don't personally like the inheretence pattern. I recognize it's value in dealing with the params class. But outside that specific case, it's mostly being used to re-implement the inheritance that went away with Puppet 2.x. I realize that PuppetLabs uses this in their NTP module, but I personally prefer to make variable passing explicit rather than implicit between classes.

edit flag offensive delete link more

Comments

Thanks for this detailed answer. Plan B is the only way to go due to app2's install actually depending on an app1 library. It can't be uncommon for multiple applications to be installed on a platform to then be reloaded later in an install. Can you point to any modules which do this elegantly?

neilfromit gravatar imageneilfromit ( 2015-01-21 16:47:16 -0600 )edit

Another option would be to split out the dependency into it's own module, and handle the dependency ordering inside your profiles.

cbarbour gravatar imagecbarbour ( 2015-01-21 17:24:14 -0600 )edit

Also... If App2 depends on app1, app1 should probably be in app2's profile. A good way to handle this is to not create an explicit relationship between app2 and app1 in your role. It's safe to contain or include a class more than once. You can handle ordering at a profile level.

cbarbour gravatar imagecbarbour ( 2015-01-21 17:26:20 -0600 )edit

Your Answer

Please start posting anonymously - your entry will be published after you log in or create a new account.

Add Answer

Question Tools

3 followers

Stats

Asked: 2015-01-20 00:56:34 -0600

Seen: 2,041 times

Last updated: Jan 20 '15