SparkleFormation — генератор CloudFormation шаблонов с радугами и единорогами

от автора

SparkleFormation

Если вы серьёзно используете AWS (Amazon Web Services), то наверняка знаете про возможность описать инфраструктуру с помощью JSON шаблонов. В AWS этот сервис называется CloudFormation. По сути это решение позволяет вам описать желаемое состояние любых ресурсов, доступных в AWS (инстансы, слои opsworks, ELB, security groups и т.д.). Набор ресурсов называется стеком. После загрузки CloudFormation шаблона система сама либо создаст необходимые ресурсы в стеке, если их ещё нет, либо попытается обновить существующие до желаемого состояния.

Это хорошо работает если у вас есть небольшое количество ресурсов, но как только инфраструктура разрастается появляются проблемы:

  • В JSON нет возможности использовать циклы и для похожих ресурсов приходится повторять одни и те же параметры и в случае изменения тоже (не DRY)
  • Для записи конфигурации для cloud-init нужен двойной escaping
  • В JSON нет комментариев и он имеет плохую человеко-читаеммость

Для того чтобы избежать подобных проблем инженеры из Heavy Water написали на ruby DSL и CLI для генерации и работы с этими шаблонами под названием SparkleFormation (github).

DRY

Когда я пришёл на свой текущий проект у нас был CloudFormation шаблон, содержащий около 1500 строк описания ресурсов и около 0 строк комментариев. После использования SparkleFormation шаблон стал занимать 300 строк, многие из которых комментарии. Как мы это добились? Для начала посмотрим как работает CloudFormation, типичное описание ресурса выглядит так:

Создание ELB

    "AppsElb": {       "Type": "AWS::ElasticLoadBalancing::LoadBalancer",       "Properties": {         "Scheme": "internal",         "Subnets": [           {"Ref": "Subnet1"},           {"Ref": "Subnet2"}         ],         "SecurityGroups": [           {"Ref": "SG"}         ],         "HealthCheck": {           "HealthyThreshold": "2",           "Interval": "5",           "Target": "TCP:80",           "Timeout": "2",           "UnhealthyThreshold": "2"         },         "Listeners": [           {             "InstancePort": "80",             "LoadBalancerPort": "80",             "Protocol": "TCP",             "InstanceProtocol": "TCP"           },           {             "InstancePort": "22",             "LoadBalancerPort": "2222",             "Protocol": "TCP",             "InstanceProtocol": "TCP"           },           {             "InstancePort": "8500",             "LoadBalancerPort": "8500",             "Protocol": "TCP",             "InstanceProtocol": "TCP"           }         ]       }     } 

Поскольку SparkleFormation позволяет использовать обычный ruby код внутри DSL, то переписать это можно так:

Создание ELB в SparkleFormation

  resources(:AppsElb) do     type 'AWS::ElasticLoadBalancing::LoadBalancer'     properties do       scheme 'internal'       subnets [PARAMS[:Subnet1], PARAMS[:Subnet2]]       security_groups [ref!(:SG)]       # port mapping 80->80, 22 -> 2222, etc.       listeners = { :'80' => 80, :'2222' => 22, :'8500' => 8500 }.map do |k, v|         { 'LoadBalancerPort' => k.to_s,           'InstancePort' => v,           'Protocol' => 'TCP',           'InstanceProtocol' => 'TCP' }       end       listeners listeners       health_check do         target 'TCP:80'         healthy_threshold '2'         unhealthy_threshold '2'         interval '5'         timeout '2'       end     end   end 

Как можно заметить мы больше не повторяемся в описании каждого порта и добавление нового займёт у нас только одну строчку. Более того если у нам необходимо создать много почти однотипных ресурсов, но отличающихся по 1-2 параметрам, SparkleFormation предоставляет такую сущность как dynamics, где вы можете описать абстрактный ресурс, которому передают параметры:

Пример из документации

# dynamics/node.rb SparkleFormation.dynamic(:node) do |_name, _config={}|   unless(_config[:ssh_key])     parameters.set!("#{_name}_ssh_key".to_sym) do       type 'String'     end   end   dynamic!(:ec2_instance, _name).properties do     key_name _config[:ssh_key] ? _config[:ssh_key] : ref!("#{_name}_ssh_key".to_sym)   end end 

А потом мы можем вызвать этот абстрактный ресурс в шаблоне:

SparkleFormation.new(:node_stack) do   dynamic!(:node, :fubar)   dynamic!(:node, :foobar, :ssh_key => 'default') end 

Таким образом мы можем повторно использовать нужные нам ресурсы и при необходимости изменения поменять все в 1 месте.

Cloud-init

Мы часто пользуемся возможностью передавать инстансу при загрузке cloud-init конфиг в виде yaml-файла и с помощью него выполнять установку пакетов, конфигурацию CoreOS, отдельных сервисов и других настроек. Проблема в том, что yaml должен передавать инстансу в user-data в CloudFormation шаблоне и выглядело это примерно так:

Безумный escaping

        "UserData": {           "Fn::Base64": {             "Fn::Join": [               "",               [                 "#cloud-config\n",                 "\n",                 "coreos:\n",                 "  etcd:\n",                 "    discovery: ", {"Ref": "AppDiscoveryURL"}, "\n",                 "    addr: $private_ipv4:4001\n",                 "    peer-addr: $private_ipv4:7001\n",                 "  etcd2:\n",  ... 

Как можно видеть это абсолютно не читаемо, уродливо и плохо поддерживаемо, не говоря уже о том что о подсветке синтаксиса можно забыть. Благодаря тому что внутри DSL можно использовать ruby код, то весь yaml можно вынести в отдельный файл и просто вызывать:

user_data Base64.encode64(IO.read('files/cloud-init.yml')) 

Как видно это намного приятнее чем редактировать его внутри JSON. Вместо IO.read можно использовать и HTTP вызов для любых параметров, если вам это нужно.

CLI

У себя в проекте мы используем собственную обёртку для управления шаблонами, но эта же команда предоставляет CLI (Command Line Interface) для управления шаблонами, называемый sfn. С помощью него можно загружать, удалять и обновлять CloudFormation стеки, командами sfn create, sfn destroy и sfn update. Там так же реализована интеграция с knife.

В целом после 4 месяцев использования SparkleFormation я им доволен и надеюсь больше не вернусь на plain JSON для описания инфраструктуры. В планах попробовать весь workflow, включая sfn, предлагаемый командой Heavy Water.

ссылка на оригинал статьи http://habrahabr.ru/post/269501/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *