Если вы серьёзно используете 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, типичное описание ресурса выглядит так:
"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, то переписать это можно так:
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 шаблоне и выглядело это примерно так:
"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/
Добавить комментарий