Автоматическое конфигурирование виртуальных машин в облаказ при помощи метаданных

от автора

image

In God we trust, the rest we automate
— unknown DevOps Engineer


Использование виртуализации и облачных платформ позволяет в десятки раз сократить время затрачиваемое на запуск и обслуживание IT инфраструктуры. Один человек может манипулировать десятками, сотнями и даже тысячами виртуальных серверов, с легкостью их запускать, останавливать, клонировать, изменять конфигурацию оборудования и создавать на их основе готовые образы систем. Если все ваши сервера имеют одинаковую конфигурацию, то особых проблем нет, можно один раз вручную настроить сервер, сделать на его основе образ и запускать столько машин, сколько вам необходимо. Если же у вас большое количество разных операционных систем с разным набором программного обеспечения или если вам необходимо быстро запускать и останавливать сложные кластерные конфигурации, то обслуживание даже нескольких десятков таких серверов будет занимать очень много времени. Можно, конечно иметь набор разных скриптов и образов на все случаи жизни, которые необходимо будет сопровождать и обновлять, но более рационально использовать один скрипт и несколько образов, а все необходимые параметры передавать при старте системы. Многие платформы для облачных вычислений предлагают, так называемый, механизм метаданных (metadata) или пользовательских данных (user-data), используя этот механизм, вы можете передать скрипту все необходимые данные по настройке конкретной виртуальной машины или даже передать сам скрипт, чтобы он выполнился при старте.

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

  • Amazon EC2
  • Eucalyptus
  • Nimbula Director
  • VMWare vCloud Director

1. Обзор принципа работы пользовательских данных для разных платформ и примеры их использование через CLI или простые скрипты

1.1 Amazon EC2

В Amzon параметр user-data может быть задан в свободной форме, при запуске виртуальной машины и потом его можно получить по определенной ссылке:

curl 169.254.169.254/latest/user-data

IP адрес 169.254.169.254 является виртуальным и все запросы к нему перенаправляются на внутренний API EC2 сервиса в соответствии с IP адресом источника.

Стандартные образы систем, предоставляемые Amazon, имеют встроенную возможность выполнять Bash и Power Shell скрипты переданные через user-data. Если user-data начинается shebang (#!), то система попытается выполнить скрипт, используя указанный в нем интерпретатор. Изначально такая возможность была реализована в отдельном пакете cloud init для Ubuntu, но сейчас он входит во все стандартные образы систем, включая Windows.

Для Windows систем можно указать, как выполнение обычных консольных команд,

<script>      netsh advfirewall set allprofiles state off </script> 

так и код на Power Shell:

<powershell>        $source = "http://www.example.com/myserverconfig.xml"        $destination = "c:\myapp\myserverconfig.xml"        $wc = New-Object System.Net.WebClient        $wc.DownloadFile($source, $destination) </powershell> 

Эту функциональность можно использовать вместе с шаблонами Cloud Formation и запускать целые стеки серверов, указав необходимые user-data:

{  "AWSTemplateFormatVersion" : "2010-09-09",  "Parameters" : {      "AvailabilityZone" : {       "Description" : "Name of an availability zone to create instance",       "Default" : "us-east-1c",       "Type" : "String"     },     "KeyName" : {       "Description" : "Name of an existing EC2 KeyPair to enable SSH access to the instance",       "Default" : "test",       "Type" : "String"     },     "InstanceSecurityGroup" : {       "Description" : "Name of an existing security group",       "Default" : "default",       "Type" : "String"     }   },    "Resources" : {     "autoconftest" : {       "Type" : "AWS::EC2::Instance",       "Properties" : {       "AvailabilityZone" : { "Ref" : "AvailabilityZone" },       "KeyName" : { "Ref" : "KeyName" },       "SecurityGroups" : [{ "Ref" : "InstanceSecurityGroup" }],       "ImageId" : "ami-31308xxx",       "InstanceType" : "t1.micro",       "UserData" : { "Fn::Base64" : { "Fn::Join" : ["",[             "#!/bin/bash","\n",             "instanceTag=WebServer","\n",             "confDir=/etc/myconfig","\n",             "mkdir $confDir","\n",             "touch $confDir/$instanceTag","\n",             "IPADDR=$(ifconfig eth0 | grep inet | awk '{print $2}' | cut -d ':' -f 2)","\n",             "echo $IPADDR myhostname","\n",             "hostname myhostname","\n" ]]       }   } } 

Если же запуск скрипта при старте машины вам не подходит, к примеру, вы хотите чтобы вашими образами пользовались другие люди, и они не хотят разбираться в вашем коде, то можно свой скрипт установить в систему, добавить в автозагрузку, и создать образ системы. И предоставить пользователям образа описание возможных параметров, которые можно задать в user-data. Например, список параметров ключ=значение разделенных точкой с запятой:

graylogserver=«192.168.1.1»;chefnodename=«chef_node_name1»;chefattributes=«recipe1.attribute1=value1,recipe1.attribute2=value2,customparameter1=value1»;chefserver=«192.168.1.38:4000»;chefrole=«apache,mysql,php»;

получать всю строку на Bash можно так:

function get_userdata { 	user_data=$(curl -w "%{http_code}" -s http://169.254.169.254/latest/user-data) 	result_code=${user_data:(-3)} 	if [ -z "$user_data" ] || [ $result_code != "200" ] 	then 		echo "$CurrentDate: Couldn't receive user-data. Result code: $result_code" 		return 1 	else 		export user_data=${user_data%%$result_code} 		return 0 	fi } 

и затем, из полученного списка получить нужнее значение:

function get_userdata_value { 	IFS=';' 	for user_data_list in $user_data 	do 		user_data_name=${user_data_list%%=*} 		if [ $user_data_name = $1 ] 		then 			user_data_value=${user_data_list#*=}  			user_data_value=$(echo $user_data_value | tr -d '\"') 			return 0 		fi 	done 	return 1 } 

После этого можно продолжить настройку системы в соответствии с полученными данными. Необязательно хранить все скрипты внутри образа, достаточно иметь простой стартовый скрипт, который считывает user-data и затем скачивает и запускает всё необходимое или же передает управление Chef или Puppet.

Аналогичную функциональность можно реализовать на Power Shell.

1.2 Eucaliptus
Этот продукт совместим с Amazon AWS, и механизм user-data в нем реализован так же.

1.3 Nimbula
Этот продукт относительно молодой, но быстроразвивающийся, он предназначен для создания приватных облачных систем и использует KVM виртуализацию. Его основатели выходцы из Amazon и у них заявлена совместимость с Amazon, но, несмотря на это, совместимость не полная. У них есть поддержка механизма user-data, через виртуальный IP, но задаются они в виде ключ=значение.
Список всех ключей можно по ссылке:
192.0.0.192/latest/attributes или 169.254.169.254/latest/attributes

пример:
curl 169.254.169.254/latest/attributes
nimbula_compressed_size
nimbula_decompressed_size
chefserver
hostname

Полученить значение конктерного ключа:
curl 169.254.169.254/latest/attributes/chefserver
192.168.1.45:4000

Таким образом, передать целый скрипт для выполнения через user-data нельзя, необходимо создавать свой образ системы со встроенным стартовым скриптом.

Пример кода на Bash:

function get_value {    user_data_value=$(curl curl -w "%{http_code}" -s http://169.254.169.254/latest/attributes/"$1")    result_code=${user_data_value:(-3)}    if [ -z "$user_data_value" ] || [ $result_code != "200" ]    then         echo "$CurrentDate: $1 variable is not set, skip it, return code: $result_code" >> $LogFile         return 1    else         user_data_value=${user_data_value%%$result_code}        return 0    fi } 

1.4 VMWare vCloud Director
Начиная с версии 1.5 в vCloud Director появился механизм использование метаданных в рамках vApp (контейнера для виртуальных машин). Данные задаются в формате ключ=значение. Чтобы задать метаданные, необходимо создать XML с их описанием:

<Metadata xmlns="http://www.vmware.com/vcloud/v1.5">     <MetadataEntry>               <Key>app-owner</Key>               <Value>Foo Bar</Value>     </MetadataEntry>     <MetadataEntry>               <Key>app-owner-contact</Key>               <Value>415-123-4567</Value>     </MetadataEntry>     <MetadataEntry>               <Key>system-owner</Key>               <Value>John Doe</Value>     </MetadataEntry> </Metadata> 

И, затем, выполнить POST запрос по URL для соответствующего vApp:
$ curl -i -k -H «Accept:application/*+xml;version=1.5» -H «x-vcloud-authorization: jmw43CwPAKdQS7t/EWd0HsP0+9/QFyd/2k/USs8uZtY=» -H «Content-Type:application/vnd.vmware.vcloud.metadata+xml» -X POST 10.20.181.101/api/vApp/vapp-1468a37d-4ede-4cac-9385-627678b0b59f/metadata -d @metadata-request.

Прочитать все метаданные можно GET запросом:
$ curl -i -k -H «application/*+xml;version=1.5» -H «x-vcloud-authorization: jmw43CwPAKdQS7t/EWd0HsP0+9/QFyd/2k/USs8uZtY=» -X GET 10.20.181.101/api/vApp/vapp-1468a37d-4ede-4cac-9385-627678b0b59f/metadata.

Для того чтобы прочитать значение конкретного ключа, запрос должен быть вида:
$ curl -i -k -H «application/*+xml;version=1.5» -H «x-vcloud-authorization: jmw43CwPAKdQS7t/EWd0HsP0+9/QFyd/2k/USs8uZtY=» -X GET 10.20.181.101/api/vApp/vapp-1468a37d-4ede-4cac-9385-627678b0b59f/metadata/asset-tag

Ответ выдается в виде XML.

Подробнее о работе метаданных в vCloud можно узнать здесь: blogs vmware

2. Работа с пользовательскими данными при помощи систем управление, таких как: Chef и Puppet

2.1 Chef
Выбор того, как Chef клиент попадает на машину за вами, вы можете его устанавливать вручную и затем создавать свои образы систем, либо вы можете его устанавливать автоматически при старте системы. Оба способа имеют свои преимущества и недостатки: первый способ уменьшает время затрачиваемое на конфигурацию машины во время старта, второй способ дает вам возможность всегда устанавливать актуальную версию клиента или устанавливать строго необходимую вам версию клиента, в зависимости от потребностей. В любом случае мы должны передать список ролей и рецептов, которые должны быть выполнены на машине, этот список мы можем получить через used-data и сконфигурировать Chef клиент при старте системы. Так же в случае, если мы устанавливаем и конфигурируем клиент при старте системы, нам необходимо скачать ключ validation.pem для соответствующего Chef сервера (данные о котором тоже можно передать через user-data)

Пример Bash скрипта, который получает список ролей:

rolefile="/etc/chef/role.json"  function get_role {     get_value "chefrole"     if [ $? = 0 ]     then 		chefrole=$user_data_value     else 		echo "$CurrentDate: Couldn't get any Chef role, use base role only." 		chefrole="base"     fi 	commas_string=${chefrole//[!,]/} 	commas_count=${#commas_string} 	echo '{ "run_list": [ ' > $rolefile 	IFS="," 	for line in $ep_chefrole 	do 		if [ $commas_count = 0 ] 		then 		echo "\"role[$line]\" " >> $rolefile 		else 		echo "\"role[$line]\", " >> $rolefile 		fi 		commas_count=$(($commas_count-1)) 	done 	echo ' ] }' >> $rolefile }  

А затем создает конфигурационный файл для клиента:

function set_chef {     if [ -d $chef_dir ] && [ -e $chef_bin ]     then 		service $chef_service stop 		sleep 10 		echo -e "chef_server_url \"http://$1\"" > $chef_dir/client.rb 		echo -e "log_location \"$chef_log\"" >> $chef_dir/client.rb 		echo -e "json_attribs \"$rolefile\"" >> $chef_dir/client.rb 		echo -e "interval $chef_interval" >> $chef_dir/client.rb 		echo "$CurrentDate: Writing $chef_dir/client.rb" 		service $chef_service start     else 		echo "$CurrentDate: Chef directory $chef_dir or chef binary $chef_bin does not exist. Exit." 		exit 1     fi }  

Параметр json_attributes задает путь к JSON файлу со списком ролей и рецептов.

После того, как мы передали управление Chef клиенту, он зарегистрируется на сервере, скачает список рецептов и начнет их выполнение, но есть несколько нюансов:

  • выполнение некоторых рецептов может занять много времени, нам необходимо знать когда конфигурация системы закончится и как она закончилась успешно или нет
  • что если мы не хотим выполнять рецепты с атрибутами по умолчанию, а хотим изменить какие-то атрибуты, например, хотим установить LAMP, но так, чтобы Apache работал на порту 8080, а не 80

Для решения первой проблемы существует cookbook от Opscode, который называется chef_handler. Он предоставляет механизм, называемый Exception and Report Handlers, который вызывается после того как Chef клиент закончил выполнение рецептов. Используя этот cookbook мы можем проверять результат последнего выполнения клиента и выполнять какие-либо действия. Можно отправлять письмо о результатах выполнения (пример описанный в документации Opscode) или записывать результат выполнения на Chef сервер, чтобы проверять это значение своими приложениями и отображать статус выполнения.

Пример рецепта:

устанавливаем значение атрибутов по умолчанию

default['lastrun']['state'] = "unknown" default['lastrun']['backtrace'] = "none" 

указываем, что нужно выполнять

include_recipe "chef_handler" chef_handler "NodeReportHandler::LastRun" do     source "#{node.chef_handler.handler_path}/nodereport.rb"     action :nothing end.run_action(:enable)  

то, что выполняем

module NodeReportHandler     class LastRun < Chef::Handler 	def report 	    if success? then 		node.override[:lastrun][:state] = "successful" 		node.override[:lastrun][:backtrace] = "none" 	    else 		node.override[:lastrun][:state] = "failed" 		node.override[:lastrun][:backtrace] = "#{run_status.formatted_exception}" 	    end 	node.save       end     end end 

В результате, у нас изначально задается значения атрибутов lastrun.state и lastrun.backtrace как ‘unknown’ и ‘none’ и затем, по резльтутам завершения выполнения клиента, мы получим либо запись ‘successfull’ либо ‘failed’ с описанием ошибки в lastrun.backtrace.
Этот рецепт должен быть в списке выполнения первым, чтобы охватывать ошибки при выполнении любых рецептов.

Для того, чтобы изменить атрибуты по умолчанию мы их должны как-то получить, затем сохранить и затем начать выполнять рецепты. Получить мы их можем опять же, через user-data.

Рецепт для получения user-data, на примере Amazon:
получаем целую строку

# Get whole user-data string     def GetUserData(url)       uri = URI.parse(url)       http = Net::HTTP.new(uri.host, uri.port)       http.open_timeout = 5       http.read_timeout = 5       proto = url.split(":", 2)       if proto[0] == "https" 	    http.use_ssl = true 	    http.verify_mode = OpenSSL::SSL::VERIFY_NONE       end       request = Net::HTTP::Get.new(uri.request_uri) 	begin             http.open_timeout = 5             http.read_timeout = 5             request = Net::HTTP::Get.new(uri.request_uri)             response = http.request(request)             result = response.body.to_s 	    if response.is_a?(Net::HTTPSuccess) 		Chef::Log.info("Successfuly get user-data.") 		return result 	    else 		return false 	    end 	rescue Exception => e 	    Chef::Log.info("HTTP request failed.") 	    return false 	end     end 

получаем значение конкретного параметра

# Get specified user-data value     def GetValue(user_data,attribute)              user_data.split(";").each do |i| 	attribute_name=i.split("=", 2) 	if attribute_name[0] == attribute 		return attribute_name[1].strip 	end 	end 	return false    end  

Теперь, когда мы можем получить значение для конкретного параметра из переданных данных, мы можем их задать:
сhefnodename=«chef_node_name1»;chefattributes=«recipe1.attribute1=value1,recipe1.attribute2=value2,customparameter1=value1»;chefserver=«192.168.1.38:4000»;chefrole=«apache,mysql,php»

В параметре chefattributes мы передали список атрибутов, которые мы хотим изменить, задаются они в формате «cookbookname.attributename=value». Если мы хотим поменять порт по умолчанию для Apache нам нужно задать chefattributes=apache.port=8080.

Рецепт, который считывает это значение и сохраняет его:

chefattributes = GetValue("#{node[:user_data]}","chefattributes") if chefattributes != false        сhefattributes.split(",").each do |i|        attribute_name=i.split("=")        recipe_name=attribute_name[0].split(".", 2)        node.override[:"#{recipe_name[0]}"][:"#{recipe_name[1].strip}"]="#{attribute_name[1].strip}"       Chef::Log.info("Save node attributes.")       node.save else     Chef::Log.info("Couldn't get Chef attributes. Skip.") end  

Этот рецепт нужно выполнять перед выполнением остальных рецептов.

Недостатки описанных выше рецептов. Операция node.save отправляет на сервер для сохранения весь JSON массив для конкретной ноды, включая информацию собранную Ohai. Если у вас тысячи машин и все они постоянно будут пытаться перезаписать свои атрибуты на сервере, это может плохо сказаться на его производительности. Это же относится к использованию гибкого и мощного поиска, предоставляемого Chef, операция поиска очень трудоемкая и в случае обслуживания тысяч машин, это создаст большую нагрузку на сервер. В таком случае нужно использовать другие способы, которые здесь описываться не будут.

2.2 Puppet
Использование Puppet для получения user-data аналогично использованию Chef. Адрес Puppet сервера и другие необходимые данные для конфигурирования агента мы получаем при помощи стартового скрипта. Для передачи своих фактов на сервер удобно использовать дополнение Facter.

Вот пример скрипта на ruby, который получает из user-data необходимые данные и отправляет их на сервер в виде дополнительных фактов, для данной машины:

require 'facter'   user_data = `curl http://169.254.169.254/latest/user-data` user_data = user_data.split(";")   user_data.each do |line|     user_data_key_value = line.split('=', 2)     user_data_key = user_data_key_value[0]     user_data_value = user_data_key_value[1]     Facter.add(user_data_key) do         setcode { user_data_value }     end end   instance_id = `curl http://169.254.169.254/latest/meta-data/instance-id` Facter.add('instance-id') do     setcode { instance_id } end 

Конечно, может показаться, что это все просто, и незачем городить какие-то сложные схемы, можно создать необходимый набор образов с предустановленным софтом, а все изменения выполнять своими скриптами, которые ходят по SSH и вонсят изменения в конфигурационные файлы. В данной статье описаны элементарные шаги. Если нам необходимо запустить кластер Hadoop, MySQL или кластер из Front-End, Back-End, App, DB серверов, чтобы все машины автоматически сконфигурировались и чтобы можно было динамически удалять или добавлять произвольное количество машин в кластер при автоматическом масштабировании, без описанных выше приемов не обойтись.

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

ссылка на оригинал статьи http://habrahabr.ru/company/epam_systems/blog/166639/


Комментарии

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

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