走马观花看Puppet

Puppet是目前最流行的一套配置管理(Configuration Management,简称CM)系统。它提供了一套简洁、强大的框架,使系统管理的重用、分享更加简单,让系统配置更加自动化。在云计算时代,动辄需要配置大量主机,它的作用更加明显。

Puppet使用一种声明式的语言,和传统的脚本相比,你只需指定目标,而不必关注具体的执行细节。举个例子,比如我们要建立一个文件/tmp/foobar.txt,其内容为Hello World!,在Puppet中这么写就行了:

file { '/tmp/foobar.txt':
  ensure  => present,
  content => 'Hello World!',
}

将上述内容保存为test.pp(称为manifest),然后执行puppet apply test.pp,就会确保存在一个文件/tmp/foobar.txt,其中的内容为「Hello World!」。在这个过程中,Puppet会检查我们声明的条件是否满足,如果满足就什么也不做;如果不满足就执行相应的操作以满足我们的要求,而这个过程对用户来说是完全透明的,你不用写各种脚本去执行各种检测和修改。

在Puppet中,file是一种资源(resource)资源是一个相当广的概念,用户、软件包、文件、服务甚至Git的库都是资源,用puppet describe -l可以查看系统中所有的资源类型。每种资源都有一个类型(如file),一个名字(如/tmp/foobar.txt)以及一系列的属性(如ensurecontent等),可以通过puppet describe <resource_type>得到资源的具体描述。

通过资源声明就可以完成一些基本的任务,比如要配置一个ssh服务:

# /root/examples/break_ssh.pp
file { '/etc/ssh/sshd_config':
  ensure => file,
  mode   => 600,
  source => '/root/examples/sshd_config',
}

service { 'sshd':
  ensure     => running,
  enable     => true,
  subscribe  => File['/etc/ssh/sshd_config'],
}

其中声明了两个资源:一个配置文件,权限为600,内容从/root/examples/sshd_config复制;一个服务sshd,确保其处于运行状态。值得注意的是,上述sshd资源的最后一项是subscribe,这是什么东西呢?原来在Puppet中,执行顺序并不是按照资源的声明顺序来的,在上述例子中,就有可能sshd服务先启动起来,然后配置文件才生成,这种情况配置文件就不起作用了。

Puppet提供了一系列方法来确保资源的执行顺序,上述的subscribe属于metaparameter,它表示当配置文件/etc/ssh/sshd_config修改后,自动重启sshd。其中File['/etc/ssh/sshd_config']是对资源的引用。除了metaparameter之外,还可以用箭头来表示顺序,上述例子也可以这么写:

# /root/examples/break_ssh.pp
file { '/etc/ssh/sshd_config':
  ensure => file,
  mode   => 600,
  source => '/root/examples/sshd_config',
}
~>
service { 'sshd':
  ensure     => running,
  enable     => true,
}

当资源不多时,把所有东西都写到一个巨大的manifest里面还可以接受;随着你维护的东西越来越多,这种方法会让你的代码越来越难维护。Puppet提供了class和module使得我们能够更加模块化地管理代码。

如果你像我一样从其它编程语言过来,那么很可能会被class这个关键字所迷惑:Puppet中的class不像C++或Java之类语言中的class,它只是一段有名字的代码。你可以用class把相关的代码包装起来,使其重用起来更加方便。比如上述代码可以修改为:

class ssh {
  file { '/etc/ssh/sshd_config':
    ensure => file,
    mode   => 600,
    source => '/root/examples/sshd_config',
  }
  service { 'sshd':
    ensure     => running,
    enable     => true,
    subscribe  => File['/etc/ssh/sshd_config'],
  }
}

include ssh

需要注意的是,class只是定义了一段代码,只有include之后才会声明其中的资源。同一个class可以include多次,效果和include一次一样。然而,即使有了class,我们的代码仍然在一个巨大的manifest里面啊!这里就要提到module了。

Module其实就是目录,它根据特定的结构来组织目录,并且其中的manifest符合一定的命名规则。Puppet在modulepath中搜索module,如果一个类在module中出现了,那么你可以在任何其它的manifest中声明它。比如要配置一个Apache服务器,可以把apache作为一个模块,而mod、proxy、vhost的设置都可以作为其中的manifest。

Puppet中可以使用变量,变量以$开头。变量是有作用域的,子作用域可以访问父作用域的变量,但访问其它作用域的变量就要加上module和class前缀(如$apache::params::confdir)。此外,变量支持双引号字符串插值:"The value is ${variable}"

上述例子中,ssh配置文件的模板位置是固定的。但是,实际应用中往往根据不同的情况做修改,我们不可能针对每种情况写一个class。为此,可以使用带参数的class,上述例子可以修改为:

class ssh ($config_path = '/root/examples/ssd_config') {
  file { '/etc/ssh/sshd_config':
    ensure => file,
    mode   => 600,
    source => "${config_path}",
  }
  service { 'sshd':
    ensure     => running,
    enable     => true,
    subscribe  => File['/etc/ssh/sshd_config'],
  }
}

class { 'ssh':
  config_path => '/home/jack/ssd_config',
}

注意要使用带参数的class,就不能直接用include ssh(否则会使用默认参数),而是要用资源式的class声明方式,将参数作为属性传递进去,在这种情况下应该仔细组织manifest文件,不要将一个class声明两次。参数类的另外一种使用方法是通过Hiera来设置参数,这种方式能够尽量把代码和数据分开,是一种推荐的使用方式。

即使class可以设置参数,但是我们也只能设置一组参数,如果我们需要同时设置多组参数呢?比如Apache的vhost,我们希望能够设置多个vhost,比如:

apache::vhost {'users.example.com':
  port    => 80,
  docroot => '/var/www/personal',
  options => 'Indexes MultiViews',
}
apache::vhost {'projects.example.com':
  port    => 80,
  docroot => '/var/www/project',
  options => 'Indexes MultiViews',
}

这时候我们就需要define类型了,它所定义的类型和系统提供的资源类似,可以同时声明多个不同参数的资源。举个例子:

define planfile ($user = $title, $content) {
  file {"/home/${user}/.plan":
    ensure  => file,
    content => $content,
    mode    => 0644,
    owner   => $user,
    require => User[$user],
  }
}
planfile {'nick':
  content => "working on foobar"
}
planfile {'katie':
  content => "working on cookies"
}

要发挥Puppet最大的优势,必须了解Agent/Master Puppet(为什么现在都不用Master/Slave了?看这里)。不过我暂时都是单机用用,就先不去了解了。

当然了,题目就叫走马观花,看这篇Blog你是不可能完全掌握Puppet的啦,感兴趣就去看看tutorialreference吧!