file_cache_path "/var/chef-solo"
cookbook_path ["/var/chef-solo/site-cookbooks", "/var/chef-solo/cookbooks"]
# Patch Monit cookbook problems
mkdir -p /etc/monit/conf.d/
rm -f /etc/monit.conf
touch /etc/monit/monitrc
chmod 700 /etc/monit/monitrc
ln -nfs /etc/monit/monitrc /etc/monit.conf
# Patch Nginx cookbook problems
mkdir -p /etc/nginx/sites-available/
useradd -s /bin/sh -u 33 -U -d /var/www -c Webserver www-data
echo -e "\n*******************************************************************************\n" \
"Patching finished" \
"\n*******************************************************************************\n"{
"run_list": [ "recipe[monit]",
"recipe[elasticsearch]",
"recipe[elasticsearch::plugins]",
"recipe[elasticsearch::ebs]",
"recipe[elasticsearch::data]",
"recipe[elasticsearch::aws]",
"recipe[elasticsearch::nginx]",
"recipe[elasticsearch::proxy]",
"recipe[elasticsearch::monit]" ],
"elasticsearch" : {
"cluster_name" : "elasticsearch_test_with_chef",
"bootstrap" : { "mlockall" : false },
"discovery" : { "type": "ec2" },
"data_path" : "/usr/local/var/data/elasticsearch/disk1",
"data" : {
"devices" : {
"/dev/sda2" : {
"file_system" : "ext3",
"mount_options" : "rw,user",
"mount_path" : "/usr/local/var/data/elasticsearch/disk1",
"format_command" : "mkfs.ext3",
"fs_check_command" : "dumpe2fs",
"ebs" : {
"size" : 25,
"delete_on_termination" : true,
"type" : "io1",
"iops" : 100
}
}
}
},
"cloud" : {
"aws" : {
"access_key" : "<REPLACE>",
"secret_key" : "<REPLACE>",
"region" : "us-east-1"
},
"ec2" : {
"security_group": "elasticsearch-test"
}
},
"plugins" : {
"karmi/elasticsearch-paramedic" : {}
},
"nginx" : {
"users" : [ { "username" : "USERNAME", "password" : "PASSWORD" } ],
"allow_cluster_api" : true
}
},
"monit" : {
"notify_email" : "<REPLACE WITH YOUR E-MAIL>",
"mail_format" : { "from" : "monit@amazonaws.com", "subject" : "[monit] $SERVICE $EVENT on $HOST", "message" : "$SERVICE $ACTION: $DESCRIPTION" }
}
}
echo -e "\nInstalling development dependencies, Ruby and essential tools..." \
"\n===============================================================================\n"
yum install gcc gcc-c++ make automake install ruby-devel libcurl-devel libxml2-devel libxslt-devel vim curl git -y
echo -e "\nInstalling Rubygems..." \
"\n===============================================================================\n"
yum install rubygems -y
gem install json --no-ri --no-rdoc
echo -e "\nInstalling and bootstrapping Chef..." \
"\n===============================================================================\n"
test -d "/opt/chef" || curl -# -L http://www.opscode.com/chef/install.sh | sudo bash -s -- -v 11.6.0
mkdir -p /etc/chef/
mkdir -p /var/chef-solo/site-cookbooks
mkdir -p /var/chef-solo/cookbooks
if test -f /tmp/solo.rb; then mv /tmp/solo.rb /etc/chef/solo.rb; fi
echo -e "\nDownloading cookbooks..." \
"\n===============================================================================\n"
test -d /var/chef-solo/site-cookbooks/monit || curl -# -L -k http://s3.amazonaws.com/community-files.opscode.com/cookbook_versions/tarballs/915/original/monit.tgz | tar xz -C /var/chef-solo/site-cookbooks/
test -d /var/chef-solo/site-cookbooks/ark || git clone git://github.com/bryanwb/chef-ark.git /var/chef-solo/site-cookbooks/ark
if [ ! -d /var/chef-solo/cookbooks/elasticsearch ]; then
git clone git://github.com/elasticsearch/cookbook-elasticsearch.git /var/chef-solo/cookbooks/elasticsearch
else
cd /var/chef-solo/cookbooks/elasticsearch
git fetch
git reset origin/master --hard
fi
echo -e "\n*******************************************************************************\n" \
"Bootstrap finished" \
"\n*******************************************************************************\n"
The code in this repository bootstraps and configures a fully managed Elasticsearch installation on a EC2 instance with EBS-based local persistence.
Download or clone the files in this gist:
curl -# -L -k https://gist.github.com/2050769/download | tar xz --strip 1 -C .
First, in the downloaded node.json file, replace the access_key and secret_key
values with proper AWS credentials.
Second, create a dedicated security group
in the AWS console for ElasticSearch nodes. We will be using group named elasticsearch-test.
Make sure the security groups allows connections on following ports:
0.0.0.0/0)0.0.0.0/0)sg-1a23bcd)Third, launch a new instance in the AWS console:
elasticsearch-test-chef-1.elasticsearch-test.ami-1b814f72). Amazon Linux comes with Ruby and Java pre-installed.m1.large instance type. You may use the small or even micro instance type, but the process will take very long, due to AWS constraints (could be hours instead of minutes).elasticsearch-test).Copy the SSH key downloaded from AWS console to the tmp/ directory of this project and change its permissions:
cp ~/Downloads/elasticsearch-test.pem ./tmp
chmod 600 ./tmp/elasticsearch-test.pem
Once the instance is ready, copy its "Public DNS" in the AWS console
(eg. ec2-123-40-123-50.compute-1.amazonaws.com).
We can begin the "bootstrap and install" process now.
Let's setup the connection details, first:
HOST=<REPLACE WITH YOUR PUBLIC DNS>
SSH_OPTIONS="-o User=ec2-user -o IdentityFile=./tmp/elasticsearch-test.pem -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
Let's copy the files to the machine:
scp $SSH_OPTIONS bootstrap.sh patches.sh node.json solo.rb $HOST:/tmp
Let's bootstrap the machine (ie. install neccessary packages, download cookbooks, etc):
time ssh -t $SSH_OPTIONS $HOST "sudo bash /tmp/bootstrap.sh"
time ssh -t $SSH_OPTIONS $HOST "sudo bash /tmp/patches.sh"
Let's launch the Chef run with the chef-solo command to provision the system:
time ssh -t $SSH_OPTIONS $HOST "sudo chef-solo -N elasticsearch-test-1 -j /tmp/node.json"
Once the Chef run successfully finishes, you can check whether ElasticSearch is running on the machine (leave couple of seconds for ElasticSearch to have a chance to start...):
ssh -t $SSH_OPTIONS $HOST "curl localhost:9200/_cluster/health?pretty"
You can also connect to the Nginx-based proxy:
curl http://USERNAME:PASSWORD@$HOST:8080
And use it for indexing some data:
curl -X POST "http://USERNAME:PASSWORD@$HOST:8080/test_chef_cookbook/document/1" -d '{"title" : "Test 1"}'
curl -X POST "http://USERNAME:PASSWORD@$HOST:8080/test_chef_cookbook/document/2" -d '{"title" : "Test 2"}'
curl -X POST "http://USERNAME:PASSWORD@$HOST:8080/test_chef_cookbook/document/3" -d '{"title" : "Test 3"}'
curl -X POST "http://USERNAME:PASSWORD@$HOST:8080/test_chef_cookbook/_refresh"
Or performing searches:
curl "http://USERNAME:PASSWORD@$HOST:8080/_search?pretty"
You can also use the provided service to check ElasticSearch status:
ssh -t $SSH_OPTIONS $HOST "sudo service elasticsearch status -v"
Of course, you can check the ElasticSearch status with Monit:
ssh -t $SSH_OPTIONS $HOST "sudo monit reload && sudo monit status -v"
(If the Monit daemon is not running, start it with sudo service monit start first. Notice the daemon has a startup delay of 2 minutes by default.)
The provisioning scripts will configure the following on the target instance:
This repository comes with a collection of Rake tasks which automatically create the server in Amazon EC2,
and perform all the provisioning steps. Install the required Rubygems with bundle install and run:
time bundle exec rake create NAME=elasticsearch-test-from-cli
http://www.elasticsearch.org/tutorials/2012/03/21/deploying-elasticsearch-with-chef-solo.html
require 'rubygems'
require 'json'
require 'fog'
require 'ansi'
module Provision
class Server
attr_reader :name, :options, :node, :ui
def initialize(options = {})
@options = options
@name = @options.delete(:name)
end
def create!
create_node
tag_node
msg "Waiting for SSH...", :yellow
wait_for_sshd and puts
end
def destroy!
servers = connection.servers.select { |s| s.tags["Name"] == name && s.state != 'terminated' }
if servers.empty?
msg "[!] No instance named '#{name}' found!", :red
exit(1)
end
puts "Will terminate #{servers.size} server(s): ",
servers.map { |s| "* #{s.tags["Name"]} (#{s.id})" }.join("\n"),
"Continue? (y/n)"
exit(0) unless STDIN.gets.strip.downcase == 'y'
servers.each do |s|
@node = s
msg "Terminating #{node.tags["Name"]} (#{node.id})...", :yellow
connection.terminate_instances(node.id)
msg "Done.", :green
end
end
def connection
@connection ||= Fog::Compute.new(
:provider => 'AWS',
:aws_access_key_id => options[:aws_access_key_id],
:aws_secret_access_key => options[:aws_secret_access_key],
:region => options[:aws_region]
)
end
def node
@node ||= connection.servers.select do |s|
s.tags["Name"] =~ Regexp.new(name) && s.state != 'terminated'
end.first
end
def create_node
msg "Creating EC2 instance #{name} in #{options[:aws_region]}...", :bold
msg "-"*ANSI::Terminal.terminal_width
@node = connection.servers.create(:image_id => options[:aws_image],
:groups => options[:aws_groups].split(",").map {|x| x.strip},
:flavor_id => options[:aws_flavor],
:key_name => options[:aws_ssh_key_id],
:block_device_mapping => options[:block_device_mapping] || [ { "DeviceName" => "/dev/sde1", "VirtualName" => "ephemeral0" }]
)
msg_pair "Instance ID", node.id
msg_pair "Flavor", node.flavor_id
msg_pair "Image", node.image_id
msg_pair "Region", options[:aws_region]
msg_pair "Availability Zone", node.availability_zone
msg_pair "Security Groups", node.groups.join(", ")
msg_pair "SSH Key", node.key_name
msg "Waiting for instance...", :yellow
@node.wait_for { print "."; ready? }
puts
msg_pair "Public DNS Name", node.dns_name
msg_pair "Public IP Address", node.public_ip_address
msg_pair "Private DNS Name", node.private_dns_name
msg_pair "Private IP Address", node.private_ip_address
end
def tag_node
msg "Tagging instance in EC2...", :yellow
custom_tags = options[:tags].split(",").map {|x| x.strip} rescue []
tags = Hash[*custom_tags]
tags["Name"] = @name
tags.each_pair do |key, value|
connection.tags.create :key => key, :value => value, :resource_id => @node.id
msg_pair key, value
end
end
def wait_for_sshd
hostname = node.dns_name
loop do
begin
print(".")
tcp_socket = TCPSocket.new(hostname, 22)
readable = IO.select([tcp_socket], nil, nil, 5)
if readable
msg "\nSSHd accepting connections on #{hostname}, banner is: #{tcp_socket.gets}", :green
return true
end
rescue SocketError
sleep 2
retry
rescue Errno::ETIMEDOUT
sleep 2
retry
rescue Errno::EPERM
return false
rescue Errno::ECONNREFUSED
sleep 2
retry
rescue Errno::EHOSTUNREACH
sleep 2
retry
ensure
tcp_socket && tcp_socket.close
end
end
end
def ssh(command)
host = node.dns_name
user = options[:ssh_user]
key = options[:ssh_key]
opts = "-o User=#{user} -o IdentityFile=#{key} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
system "ssh -t #{opts} #{host} #{command}"
end
def scp(files, params={})
host = node.dns_name
user = options[:ssh_user]
key = options[:ssh_key]
opts = "-o User=#{user} -o IdentityFile=#{key} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -r"
path = params[:path] || '/tmp'
__command = "scp #{opts} #{files} #{host}:#{path}"
puts __command
system __command
end
def msg message, color=:white
puts message.ansi(color)
end
def msg_pair label, value, color=:cyan
puts (label.to_s.ljust(25) + value.to_s).ansi(color)
end
end
end
desc "Create, bootstrap and configure an instance in EC2"
task :create => :setup do
@server = Provision::Server.new @args
@server.create!
Rake::Task[:upload].execute
Rake::Task[:provision].execute
end
desc "Terminate an EC2 instance"
task :destroy => :setup do
@server = Provision::Server.new @args
@server.destroy!
end
desc "(Re-)provision an instance"
task :provision => :setup do
@server ||= Provision::Server.new @args
@server.ssh "sudo bash /tmp/bootstrap.sh"
@server.ssh "sudo bash /tmp/patches.sh"
@server.ssh "sudo chef-solo -N #{@server.name} -j /tmp/#{@args[:node_json]}"
exit(1) unless $?.success?
puts "_"*ANSI::Terminal.terminal_width
puts "\nOpen " + "http://#{@args[:http_username]}:#{@args[:http_password]}@#{@server.node.dns_name}:8080".ansi(:bold) + " in your browser"
end
task :upload => :setup do
@server ||= Provision::Server.new @args
@server.scp "bootstrap.sh patches.sh #{@args[:node_json]} solo.rb", :path => '/tmp'
exit(1) unless $?.success?
end
task :setup do
ENV['AWS_ACCESS_KEY'] || ( puts "\n[!] Missing AWS_ACCESS_KEY environment variable...".ansi(:red) and exit(1) )
ENV['AWS_SECRET_ACCESS_KEY'] || ( puts "\n[!] Missing AWS_SECRET_ACCESS_KEY environment variable...".ansi(:red) and exit(1) )
node_json = ENV['NODE'] || 'node.json'
json = JSON.parse(File.read( File.expand_path("../#{node_json}", __FILE__) ))
name = json['elasticsearch']['node_name'] rescue nil
aws_access_key = json['elasticsearch']['cloud']['aws']['access_key'] rescue nil
aws_secret_access_key = json['elasticsearch']['cloud']['aws']['secret_key'] rescue nil
aws_region = json['elasticsearch']['cloud']['aws']['region'] rescue nil
aws_group = json['elasticsearch']['cloud']['ec2']['security_group'] rescue nil
http_username = json['elasticsearch']['nginx']['users'][0]['username'] rescue nil
http_password = json['elasticsearch']['nginx']['users'][0]['password'] rescue nil
@args = {}
@args[:name] = ENV['NAME'] || name || 'elasticsearch-test'
@args[:node_json] = node_json
@args[:aws_ssh_key_id] = ENV['AWS_SSH_KEY_ID'] || 'elasticsearch-test'
@args[:aws_access_key_id] = ENV['AWS_ACCESS_KEY_ID'] || aws_access_key
@args[:aws_secret_access_key] = ENV['AWS_SECRET_ACCESS_KEY'] || aws_secret_access_key
@args[:aws_region] = ENV['AWS_REGION'] || aws_region || 'us-east-1'
@args[:aws_groups] = ENV['GROUP'] || aws_group || 'elasticsearch-test'
@args[:aws_flavor] = ENV['FLAVOR'] || 't1.micro'
@args[:aws_image] = ENV['IMAGE'] || 'ami-1624987f'
@args[:ssh_user] = ENV['SSH_USER'] || 'ec2-user'
@args[:ssh_key] = ENV['SSH_KEY'] || File.expand_path('../tmp/elasticsearch-test.pem', __FILE__)
@args[:http_username] = http_username
@args[:http_password] = http_password
end
source :rubygems
gem 'rake'
gem 'json'
gem 'fog'
gem 'ansi'