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

Managing users with virtual resources

Users are a great example of a resource that may need to be realized by multiple classes. Consider the following situation. To simplify administration of a large number of machines, you defined classes for two kinds of users: developers and sysadmins. All machines need to include sysadmins, but only some machines need developers:

node 'server' { 
  include user::sysadmins 
}

node 'webserver' {
  include user::sysadmins 
  include user::developers 
}

However, some users may be members of both groups. If each group simply declares its members as regular user resources, this will lead to a conflict when a node includes both developers and sysadmins, as in the webserver example.

To avoid this conflict, a common pattern is to make all users virtual resources, defined in a single class user::virtual that every machine includes, and then realizing the users where they are needed. This way, there will be no conflict if a user is a member of multiple groups.

How to do it...

Follow these steps to create a user::virtual class:

  1. Create the file modules/user/manifests/virtual.pp with the following contents:
    class user::virtual {
      @user { 'thomas':  ensure => present }
      @user { 'theresa': ensure => present }
      @user { 'josko':   ensure => present }
      @user { 'nate':    ensure => present }
    }
  2. Create the file modules/user/manifests/developers.pp with the following contents:
    class user::developers {
      realize(User['theresa'])
      realize(User['nate'])
    }
  3. Create the file modules/user/manifests/sysadmins.pp with the following contents:
    class user::sysadmins {
      realize(User['thomas'])
      realize(User['theresa'])
      realize(User['josko'])
    }
  4. Modify your nodes.pp file as follows:
    node 'cookbook' {
      include user::virtual
      include user::sysadmins
      include user::developers
    }
  5. Run Puppet:
    cookbook# puppet agent -t
    Info: Caching catalog for cookbook.example.com
    Info: Applying configuration version '1413180590'
    Notice: /Stage[main]/User::Virtual/User[theresa]/ensure: created
    Notice: /Stage[main]/User::Virtual/User[nate]/ensure: created
    Notice: /Stage[main]/User::Virtual/User[thomas]/ensure: created
    Notice: /Stage[main]/User::Virtual/User[josko]/ensure: created
    Notice: Finished catalog run in 0.47 seconds
    

How it works...

When we include the user::virtual class, all the users are declared as virtual resources (because we included the @ symbol):

  @user { 'thomas':  ensure => present }
  @user { 'theresa': ensure => present }
  @user { 'josko':   ensure => present }
  @user { 'nate':    ensure => present }

That is to say, the resources exist in Puppet's catalog; they can be referred to by and linked with other resources, and they are in every respect identical to regular resources, except that Puppet doesn't actually create the corresponding users on the machine.

In order for that to happen, we need to call realize on the virtual resources. When we include the user::sysadmins class, we get the following code:

  realize(User['thomas'])
  realize(User['theresa'])
  realize(User['josko'])

Calling realize on a virtual resource tells Puppet, "I'd like to use that resource now". This is what it does, as we can see from the run output:

Notice: /Stage[main]/User::Virtual/User[theresa]/ensure: created

However, Theresa is in both the developers and sysadmins classes! Won't that mean we end up calling realize twice on the same resource?

realize(User['theresa'])
...
realize(User['theresa'])

Yes, it does, and that's fine. You're explicitly allowed to realize resources multiple times, and there will be no conflict. So long as some class, somewhere, calls realize on Theresa's account, it will be created. Unrealized resources are simply discarded during catalog compilation.

There's more...

When you use this pattern to manage your own users, every node should include the user::virtual class, as a part of your basic housekeeping configuration. This class will declare all users (as virtual) in your organization or site. This should also include any users who exist only to run applications or services (such as Apache, www-data, or deploy, for example). Then, you can realize them as needed on inpidual nodes or in specific classes.

For production use, you'll probably also want to specify a UID and GID for each user or group, so that these numeric identifiers are synchronized across your network. You can do this using the uid and gid parameters for the user resource.

Note

If you don't specify a user's UID, for example, you'll just get whatever is the next ID number available on a given machine, so the same user on different machines will have a different UID. This can lead to permission problems when using shared storage, or moving files between machines.

A common pattern when defining users as virtual resources is to assign tags to the users based on their assigned roles within your organization. You can then use the collector syntax instead of realize to collect users with specific tags applied.

For example, see the following code snippet:

@user { 'thomas':  ensure => present, tag => 'sysadmin' }
@user { 'theresa': ensure => present, tag => 'sysadmin' }
@user { 'josko':   ensure => present, tag => 'dev' }
User <| tag == 'sysadmin' |>

In the previous example, only users thomas and theresa would be included.

See also

  • The Using virtual resources recipe in this chapter
  • The Managing users' customization files recipe in this chapter