属性

Capistrano 中的服务器对象本质上由名称和哈希组成:名称是 DNS 名称(或 IP 地址),哈希包含服务器的“属性”。这些属性分为两种:Capistrano 需要的属性(Capistrano 属性)和应用程序可用的属性(自定义属性)。它们共享相同的命名空间(只有一个底层哈希!),因此自定义属性的名称受到限制。

Capistrano 属性

Capistrano 属性是用于 SSH 登录服务器的属性,以及支持基本角色功能的属性。它们是

  • :user - 服务器的 SSH 用户名
  • :password - SSH 用户的密码
  • :port - 服务器上 SSH 守护进程的端口号
  • :roles - 角色名称数组
  • :ssh_options - SSH 参数的哈希(见下文)
  • :primary - 一个布尔值,指示服务器是否应被视为主服务器。

可以按以下方式指定 :user:port:password

  • 作为主机名的一部分,格式为 'user@host:port',不带密码。
  • 在属性 :user:password:port 中。
  • 在属性 :ssh_options 中(使用相同的键)。

优先级

SSH 相关属性的设置优先级如下,从最高优先级开始。

  • 服务器或角色上的属性声明。最后一个属性声明会覆盖所有之前的服务器或角色声明。
  • 在主机名字符串中指定的数值。
  • 服务器或角色 :ssh_options 属性中的数值。
  • 阶段全局变量 :ssh_options
  • SSHKit 后端 ssh_options
  • 本地 ~/.ssh/config 文件中的设置。

但是请注意,从这些地方获取的默认值 *不会* 反映回服务器属性,因此如果使用的是较低优先级的默认值,则 host.user 将为 nil。

自定义属性

当使用 Capistrano 作为通用部署框架(超出其在 Rails 部署中的传统用途)时,能够存储额外的参数变得很重要。您可以将 Capistrano 视为部署的 *MVC* 框架,其中阶段文件(表示所有应用程序组件之间的关系)是 *模型*,任务(使模型更改能够被执行)是 *控制器*,而实际的物理体现(通常是运行服务器上的配置文件)是 *视图*。

从任务中访问属性

可以从 Capistrano 任务中以编程方式访问 Capistrano 服务器上的属性。*Capistrano* 属性可以通过主机对象本身的方法访问,而 *自定义* 属性可以通过主机 properties 属性的方法访问。

这些方法具有预期的名称:userport 等等。例外情况是 ssh_config,它可以通过 netssh_options 方法访问。

以下功能是 Capistrano 3.3.6 及更高版本中的新功能。

on() 块的范围内,所产生的主机是底层主机的 *副本*,这允许您通过调用 setter 方法来临时覆盖任何属性。例如:

on roles(:all) do |host|
  host.user = 'root'
  host.password = 'supersecret'
  execute :yum, 'makecache'
end

这会将 SSH 用户临时设置为 'root'(并使用相应的密码),而不会影响在配置中为服务器定义的 SSH 用户。

复杂配置中的属性设置

当配置涉及更多服务器时,能够在角色级别定义一组属性,并让这些属性在服务器级别被后来的定义覆盖,这将有助于保持配置尽可能地 DRY。一个典型的需求是定义一组 Redis 服务器,这些服务器都具有相同的端口参数,并且除了一个主服务器之外,其他服务器都是从服务器。

为了允许这样做,属性可以在服务器角色级别设置。指导原则是在合并属性,并且最后定义优先。在实践中,我们根据属性值的类型略微调整这一点。

  • 标量值将被覆盖。
  • 哈希值的键将被合并,重复的键将采用最后一个键的值。
  • 数组值将把后续条目追加到数组中。

服务器和角色属性示例

以上 Redis 需求可以使用阶段文件中的以下声明来满足。

role :redis, %w{ r1.example.com r2.example.com r3.example.com }, redis: { port: 6379, master: false },
server 'r1.example.com', redis: { port: 6380, master: true }

角色属性约定

这会因为一台机器可能服务于多个角色,实际上一台机器可能需要执行两次相同的角色而变得复杂!例如,在开发环境中,您可能希望一台机器同时作为数据库服务器、主 Redis 服务器和从 Redis 服务器。

为了解决这个问题,我们采用了一种关于服务器属性使用的约定。

  • 给定角色的服务器属性应使用与角色相同的键名存储。属性的内容可以是标量、数组或哈希。

  • 同一服务器上角色的多次出现应将内容设置为数组,其中后续元素表示每个实例。

以下示例显示了在同一服务器上具有多个 Redis 和 Sentinel 角色的配置。

server 'dev.local', roles: %w{db web redis sentinel worker}, primary: true,
    redis: [ { name: 'resque', port: 6379, db: 0, downtime: 10, master: true },
             { name: 'resque', port: 6380, db: 0, downtime: 10 } ],
    sentinel: [ { port: 26379 }, { port: 26380 }, { port: 26381 } ]

这些属性可以通过普通方式访问,但为了帮助获取它们,您可以使用 role_properties() 函数(见下文)。

设置属性

属性可以在角色和服务器级别设置。

角色属性

角色的声明接受一个服务器名称数组和一个属性的尾部哈希。按照惯例,角色声明中的第一个服务器被视为主服务器,但 :primary 属性在这种情况下实际上不会被设置。

服务器属性

服务器的声明需要一个服务器名称和一个属性的尾部哈希。其中一个属性必须是 :role,并且其值必须是一个角色名称数组。

访问属性

roles() 方法

roles() 方法接受一个或多个角色名称(或角色数组),后面跟着一个可选的 属性过滤器,并返回一个包含属于这些角色的 Capistrano::Configuration::Server 对象的数组。这些对象具有以下有用的属性

  • hostname - 字符串
  • properties.keys - 可用属性的名称
  • properties - 一个类似哈希的对象,用于存储属性。它使用 Ruby 的“method_missing”为每个有效键提供一个方法。
  • roles - 一组角色名称,以符号表示

通过此方法检索的服务器不受任何主机或角色过滤器的影响。

role_properties() 方法

此方法接受一个角色列表(后面跟着一个可选的 属性过滤器),并返回一个包含属性的哈希数组,其中添加了 :hostname:role 键。

task :props do
  rps = role_properties(:redis, :sentinel)
  rps.each do |props|
    puts props.inspect
  end
end

# Produces...

{:name=>"resque", :port=>6379, :db=>0, :downtime=>10, :master=>true, :role=>:redis, :hostname=>"dev.local"}
{:name=>"resque", :port=>6380, :db=>0, :downtime=>10, :role=>:redis, :hostname=>"dev.local"}
{:port=>26379, :role=>:sentinel, :hostname=>"dev.local"}
{:port=>26380, :role=>:sentinel, :hostname=>"dev.local"}
{:port=>26381, :role=>:sentinel, :hostname=>"dev.local"}

或者,您可以提供一个代码块,它将生成主机名、角色和属性。

task :props_block do
  role_properties(:sentinel) do |hostname, role, props|
    puts "Host: #{hostname}, Role: #{role}, #{props.inspect}"
  end
end

# Produces...

Host: dev.local, Role: sentinel, {:port=>26379}
Host: dev.local, Role: sentinel, {:port=>26380}
Host: dev.local, Role: sentinel, {:port=>26381}

请注意,与 on() 不同,此函数不会导致任何远程执行,它纯粹用于配置目的。

Fork me on GitHub