DevOps:Puppet,Docker,and Kubernetes
上QQ阅读APP看书,第一时间看更新

Using virtual resources

Virtual resources in Puppet might seem complicated and confusing but, in fact, they're very simple. They're exactly like regular resources, but they don't actually take effect until they're realized (in the sense of "made real"); whereas a regular resource can only be declared once per node (so two classes can't declare the same resource, for example). A virtual resource can be realized as many times as you like.

This comes in handy when you need to move applications and services between machines. If two applications that use the same resource end up sharing a machine, they would cause a conflict unless you make the resource virtual.

To clarify this, let's look at a typical situation where virtual resources might come in handy.

You are responsible for two popular web applications: WordPress and Drupal. Both are web apps running on Apache, so they both require the Apache package to be installed. The definition for WordPress might look something like the following:

class wordpress {
  package {'httpd':
    ensure => 'installed',
  }
  service {'httpd':
    ensure => 'running',
    enable => true,
  }
}

The definition for Drupal might look like this:

class drupal {
  package {'httpd':
    ensure => 'installed',
  }
  service {'httpd':
    ensure => 'running',
    enable => true,
  }
}

All is well until you need to consolidate both apps onto a single server:

node 'bigbox' {
  include wordpress
  include drupal
}

Now Puppet will complain because you tried to define two resources with the same name: httpd.

Using virtual resources

You could remove the duplicate Apache package definition from one of the classes, but then nodes without the class including Apache would fail. You can get around this problem by putting the Apache package in its own class and then using include apache everywhere it's needed; Puppet doesn't mind you including the same class multiple times. In reality, putting Apache in its own class solves most problems but, in general, this method has the disadvantage that every potentially conflicting resource must have its own class.

Virtual resources can be used to solve this problem. A virtual resource is just like a normal resource, except that it starts with an @ character:

@package { 'httpd': ensure => installed }

You can think of it as being like a placeholder resource; you want to define it but you aren't sure you are going to use it yet. Puppet will read and remember virtual resource definitions, but won't actually create the resource until you realize the resource.

To create the resource, use the realize function:

realize(Package['httpd'])

You can call realize as many times as you want on the resource and it won't result in a conflict. So virtual resources are the way to go when several different classes all require the same resource, and they may need to coexist on the same node.

How to do it...

Here's how to build the example using virtual resources:

  1. Create the virtual module with the following contents:
    class virtual {
      @package {'httpd': ensure => installed }
      @service {'httpd': 
        ensure  => running,
        enable  => true,
        require => Package['httpd']
      }
    }
  2. Create the Drupal module with the following contents:
    class drupal {
      include virtual
      realize(Package['httpd'])
      realize(Service['httpd'])
    }
    
  3. Create the WordPress module with the following contents:
    class wordpress {
      include virtual
      realize(Package['httpd'])
      realize(Service['httpd'])
    }
  4. Modify your site.pp file as follows:
    node 'bigbox' {
      include drupal
      include wordpress
    }
  5. Run Puppet:
    bigbox# puppet agent -t
    Info: Caching catalog for bigbox.example.com
    Info: Applying configuration version '1413179615'
    Notice: /Stage[main]/Virtual/Package[httpd]/ensure: created
    Notice: /Stage[main]/Virtual/Service[httpd]/ensure: ensure changed 'stopped' to 'running'
    Info: /Stage[main]/Virtual/Service[httpd]: Unscheduling refresh on Service[httpd]
    Notice: Finished catalog run in 6.67 seconds
    

How it works...

You define the package and service as virtual resources in one place: the virtual class. All nodes can include this class and you can put all your virtual services and packages in it. None of the packages will actually be installed on a node or services started until you call realize:

class virtual {
  @package { 'httpd': ensure => installed }
}

Every class that needs the Apache package can call realize on this virtual resource:

class drupal {
  include virtual
  realize(Package['httpd'])
}

Puppet knows, because you made the resource virtual, that you intended to have multiple references to the same package, and didn't just accidentally create two resources with the same name. So it does the right thing.

There's more...

To realize virtual resources, you can also use the collection spaceship syntax:

Package <| title = 'httpd' |>

The advantage of this syntax is that you're not restricted to the resource name; you could also use a tag, for example:

Package <| tag = 'web' |>

Alternatively, you can just specify all instances of the resource type, by leaving the query section blank:

Package <| |>