Технические заметки одного Евтуховича

Рассказ о серых трудовых буднях инженера программных систем

Атрибуты в шефе и DeepMerge

| Комментарии

Многие знают, что в шефе есть большое количество типов атрибутов, необходимые, чтобы гибко управлять инфраструктурой. Для того, чтобы эти атрибуты работали вместе, есть разные правила их объединения. О них мы сегодня и поговорим.

В шефе есть 15 типов атрибутов, и если вы будете использовать их все, у вас взорвется мозг. При использовании нашего подхода к использованию шефа (об этом я рассказывал на последнем DevOps митапе, который прошел в Яндексе, видео моего доклада можно посмотреть здесь), нам, за редким исключением, удается обойтись всего тремя типами:

  • A default attribute located in a cookbook attribute file (1)
  • A default attribute located in an environment (3)
  • A default attribute located in role (4)

Это позволяет хранить все атрибуты в git. Для примера можно посмотреть наш тестовый репозиторий тесто.

Следующий момент, на который стоит обратить внимание при работе с атрибутами, — это «глубокое слияние» или deep merge в английском варианте. Поставим небольшой эксперимент с помощью chef-shell (это такой irb с загруженными классами шеф клиента).

➜  chef-shell
loading configuration: none (standalone session)
Session type: standalone
Loading....done.

This is the chef-shell.
 Chef Version: 11.10.0
 http://www.opscode.com/chef
 http://docs.opscode.com/

run `help' for help, `exit' or ^D to quit.

Ohai2u brun@Ivans-MacBook-Pro.local!
chef > dm = Chef::Mixin::DeepMerge
 => Chef::Mixin::DeepMerge
chef > a = { :x => [1, 2] }
 => {:x=>[1, 2]}
chef > b = { :x => [3] }
 => {:x=>[3]}
chef > dm.merge a, b
 => {"x"=>[1, 2, 3]}

Или в сухом остатке

dm = Chef::Mixin::DeepMerge
a = { :x => [1, 2] }
b = { :x => [3] }
dm.merge a, b
# => {"x"=>[1, 2, 3]}

Если значениями являются массивы, то они объединяются. Возможно, иногда это очень полезно, но про эту особенность надо помнить, чтобы не случилось ситуации, о которой я сейчас расскажу.

В файле атрибутов были заданы приватные сети следующим образом:

# inhouse-cookbooks/base/attributes/default.rb

default['base']['private_networks'] = ["192.168.0.0/16", "172.16.0.0/12", "10.0.0.0/8"]

Боевая среда находилась в облаке Rackspace. У них сеть 10.0.0.0/8 является внутренней сетью, в которую «торчат» различные сервисы, в том числе Load Balancer. Поэтому в атрибутах боевого окружения было прописано следующее (environments/production.json):

{
  "name": "production",
  "description": "Produciton environment",
  "cookbook_versions": {
  },
  "json_class": "Chef::Environment",
  "chef_type": "environment",
  "default_attributes": {
    "base": {
      "private_networks": ["192.168.0.0/16", "172.16.0.0/12"]
    }
  }
}

В результате deep merge публичных ip адресов на сервисе не стало.

Я решил эту проблему по-простому, стал хранить список в одной строке с разделителем-запятой, потому что строки при deep merge заменяются.

default['base']['private_networks'] = "192.168.0.0/16,172.16.0.0/12,10.0.0.0/8"

Подробности про deep merge можно прочитать в официальной документации.

Стоит также обратить внимание, что атрибуты из файлов атрибутов загружаются в алфавитном порядке. Например, совсем недавно, наткнулся на ошибку, связанную с этим (и, более того, чуть раньше на нее наткнулся мой коллега Никита на другом проекте).

$ ls inhouse-cookbooks/project/attributes
front.rb
riemann.rb
# inhouse-cookbooks/project/attributes/front.rb

default['project']['front']['application_user'] = 'project'

# inhouse-cookbooks/project/attributes/riemann.rb

default['project']['riemann']['application_directory'] =
    "/home/#{node['project']['front']['application_user']}/riemann"

default['project']['riemann-dash']['application_directory'] =
    "/home/#{node['project']['front']['application_user']}/riemann-dash"

В какой-то момент я переименовал рецепт front в ruby, и все сломалось, потому что ruby идет после riemann в алфавитной сортировке.

И третий момент, на который стоит обратить внимание, это то, что атрибуты одного типа (с одинаковым приоритетом) не объединяются, а перетирают друг друга. Вот вам умозрительный пример (спасибо Никите, который наткнулся на это в реальной жизни и рассказал мне).

# inhouse-cookbooks/project/attributes/app_one.rb

default['logrotate']['log_files'] = ['/var/log/app_one.log']

# inhouse-cookbooks/project/attributes/app_two.rb

default['logrotate']['log_files'] = ['/var/log/app_two.log']

Казалось бы, очевидный способ собрать массив из разных элементов, но конструктивные особенности шефа сделают так, что нужный нам атрибут будет содержать массив с одним элементом '/var/log/app_two.log' — последним.

Если бы спросили меня про атрибуты в шефе, я бы сказал, что они сделаны, гм, весьма странно, что ли, в них не хватает инженерной красоты.

Надеюсь, эта статья убережет вас от хождения по совсем неинтересным граблям.

chef

Комментарии