首先要申明,rex 和 puppet 本质上是不同的,puppet 追求的是状态,rex 追求的是操作。puppet 用户经常关心的是 agent 运行了没,而 rex 用户关心的是怎么写 Rexfile 能让中控运行 rex 时的命令参数更简洁漂亮(个人感受==!)。所以哪怕在本文中列举的这些资源写法很类似,也请读者们注意:rex 的资源关键词命名,都是带有动作性的,比如 createaddinstalluploaddownloadsync 等等。甚至精确的说,rex 里这些不是资源(Puppet::Types::***),他们是 Rex::Commands::***

因为 rex 基于并发 ssh 连接,所以它有一些操作是 puppet 所没有的,比如 tailfile_appendfdisksysctliptables 等等,这里暂时不列举。总的来说,本文目的是总结类似的部分,而不是不同的用法……

Cron 资源

puppet 写法

    cron { 'check_starttime':
        ensure  => present,
        minute  => 30,
        hour    => '*/2',
        user    => 'root',
        command => 'sh /usr/local/bin/check_start_time.sh',
        require => File['/usr/local/bin/check_start_time.sh'],
    }

rex 写法

    cron add => "root", {
        minute       => '5',
        hour         => '*',
        day_of_month => '*',
        month        => '*',
        day_of_week  => '*',
        command      => '/path/to/your/cronjob',
    };

File 资源

puppet 写法

    file { '/etc/squid/squid.conf':
        ensure    => file,
        mode      => '0755',
        content   => template('squid/squid.conf.erb'),
        require   => Package['squid'],
        subscribe => Service['squid'],
    }

rex 写法

    file "/etc/squid/squid.conf",
        content   => template("templates/squid.tpl", vars => \%var ),
        owner     => "root",
        group     => "root",
        mode      => 700,
        needs     => SquidPkgTask,
        on_change => sub { service squid => 'restart' };

这里的 on_change 是 File 资源独有的。

通用资源方面,rex 中在同一个 task 内,是按照书写顺序执行;在 task 之间,通过 needs 可以定义依赖。

另外 rex 还有 beforeafteraround 三个关键字作用于 task 上。不过这三个是在 rex 控制端执行,不是在远端主机上执行。

注意这里,这个 file 看起来没有使用操作性的动词,但其实他是下面这个写法的简写而已:

    install file  => 'templates/etc/hosts.tpl', {
        source    => "/etc/hosts",
        owner     => "root",
        group     => "root",
        mode      => 700,
        on_change => sub { say "Something was changed." },
        template  => {
                        greeting => "hello",
                        name     => "Ben",
                     },
    };

另外,还有一个通过 SFTP 接口上传的写法:

    upload "hosts" => "/etc/";

Package 资源

puppet 写法

    package { 'ganglia-gmond-modules-python-plugin':
        ensure  => installed,
        require => Class['repos'],
    }

rex 写法

    repository add => myrepo,
        url => 'http://rex.linux-files.org/CentOS/$releasever/rex/$basearch/';
    update_package_db;
    install package => 'vim';

Class 定义

puppet 写法

    class squid {
        include squid::install
    }

rex 写法

rex 执行的 Rexfile 其实就是 perl 的模块文件,所以写法就是标准的 perl 写法。

    package Squid {
        require Squid::Install;
    }

呼呼,新版本的 Perl 中可以用 {} 来包裹 package 定义的内容,看起来是不是更像一些?不过 CentOS6 的 5.10 版还不支持,所以通用起见,还是这样写吧:

    package Squid;
    require Squid::Install;
    1;

Directory 资源

puppet 写法

    file { 'murder-client':
        ensure  => 'directory',
        path    => '/usr/local/murder',
        recurse => true,
        purge   => true,
        source  => 'puppet:///modules/murder/dist',
    }

rex 写法

rex 中采用 rsync 来完成目录文件的同步:

    mkdir('/usr/local/murder');
    sync 'dist/*' => '/usr/local/murder', {
        exclude    => "*.sw*",
        parameters => '--backup --delete',
    };

Shell 资源

puppet 写法

    exec {'init-reload':
        command     => '/sbin/initctl reload-configuration && /sbin/initctl start svscan',
        subscribe   => File['/etc/init/svscan.conf'],
        refreshonly => true,
    }

rex 写法

    run "cmd", sub {
        my ($out, $err) = @_;
    };

这个回调函数可以不要,那么 run 命令返回输出到变量。这种用法在单行命令中最常用,比如这样:

    rex -H '192.168.0.[10..30]' -e 'say run "df -h"'

User/Group 资源

puppet 写法

    group {'puppet':
        ensure => present,
        gid    => 501,
        system => true,
    }
    user {'puppet':
        ensure => present,
        uid    => 501,
        system => true,
        groups => ['puppet', '...'],
        expiry => '2013-05-30',
        managehome => false,
    }

rex 写法

    create_group 'puppet', {
        gid    => 501,
        system => 1,
    }
    create_user 'puppet',
       uid => 501,
       home => '/etc/puppet',
       expire => '2013-05-30',
       groups  => ['puppet', '...'],
       password => 'blahblah',
       system => 1,
       no_create_home => TRUE,
       ssh_key => "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQChUw...";

Service 资源

puppet 写法

    service {'nginx':
        ensure => true,
        enable => true,
    }

rex 写法

    service apache2 => ensure => "started";
    service apache2 => "start";

再次可见,rex 认为 service 命令和 chkconfig/update-rc.d 命令是两件事情,所以要分开两个写法。

Mount 资源

puppet 写法

    mount {'/mnt/sda6':
        ensure  => present;
        device  => '/dev/sda6',
        fstype  => 'ext3',
        options => 'noatime,async';
    }

rex 写法

    mount "/dev/sda6", "/mnt/sda6",
       fs => "ext3",
       options => [qw/noatime async/];

Facts 变量和模板

puppet 写法

在 puppet 中,Facts 变量有两种用法,一个是 *.pp 里的写法:

    $::lsbdistid

另一种是在 *.erb 里的写法,值得注意的是变量的作用域:

    <%= scope::lookupvar('ipaddress') %>
    <%= scope::lookupvar('nginx::name') %>

rex 写法

在 rex 中,远端主机的系统状态有多种获取方式,比如:

    # 全部,这些变量默认会传递给 template
    my $sysinfo = Rex::Helper::System::info;
    # 实际就是从上面info里取具体的变量
    my $lsd = get_operating_system;
    # 这个慎用,会死人的
    my @ns = netstat;

也可以使用 set 指令,这种变量和使用 perl 标准 my $name 方式不同的是它可以直接在模板中读取:

    set name => 'CDN';

至于 rex 的模板,它默认没有使用 CPAN 上任何一种现成的模块,而是自己实现了一个,写法如下:

    template('your.tpl', yourvars => \%hash );

然后在模板中这样引用:

    My variable is <%= $::yourvars->{key} %>
    My name is <%= $::name %>
    My lsd is <%= $::operatingsystem %>

明显有模仿 puppet 的痕迹,传递进模版的变量以 $:: 开头,个人比较汗……

所以个人建议还是更换成 CPAN 上的流行模板,比如 Text::Xslate 或者 Text::MicroTemplate 等等,使用 set_template_option 即可。