PUPPET, DOCKER, ICINGA AND AWS AUTOMATION – DEPLOYING INSTANCES, CONTAINERS AND MONITORING

Hi there!

 

I have received one task recently that basically asked to create 01 instance with 02 containers (Apache and MySQL) and monitoring all using Icinga.

I could create it manually – once Icinga Server was already up and running, but I decided to be little more challenging: What if automate this process using Puppet and centralized the Apache/MySQL logs into a S3 bucket? Wouldn’t be awesome? Yeah.. it would.

So I did it! And I proudly share it with you all. I know that some methods that I’m using may not be the most used one. But I decided to do it by myself instead use external recipes to see how far I could go.

Assumptions

I can assume that you know how to create VPC, subnets and so on and I’m also assuming you know the basic of IAM Roles.

Another thing is that you must to be comfortable with Puppet and your Server must to be communicating with client. There are a lot of tutorials out there explaining how to perform it. It’s not a big deal, for sure.

Existing environment

This is what I already have:

  • 01 VPC – 10.0.0.0/16
  • 01 Public Subnet with Auto-Sign Public IP
  • 01 Security Group allowing: HTTP only to my IP and all protocols only to 10.0.0.0/16
  • 01 Puppet Server
  • 01 Icinga Server
  • 01 IAM S3 Role
  • 01 Github Repository
  • 01 S3 bucket

 

Icinga Server

Icinga is an open source computer system and network monitoring application. It was originally created as a fork of the Nagios system monitoring application in 2009.

The install and configuration method can be followed here.

Puppet Server

The install and configuration of Puppet server is quite standard and can be followed here.

On my approach, I’m using Puppet AWS module in order to create/configure instances.

 

Solution Created

That’s what I’ve made:

Creation of Puppet Manifest to creation of new instances (create.pp)

Creation of script (crossover.sh) to prepare the new instance on the fly doing:

  • Update the instances package’s list;
  • Install Docker;
  • Deploy docker mysql container and configure;
  • Deploy apache container and configure;
  • Link apache and mysql;
  • Pull php files (index.php, db.php, logout.php) from Github to apache webdir;
  • Download script (cronlogs.sh) responsible to upload docker container’s log files to S3 and configure it on cron.

Running the Solution

1 – ssh to Puppet Server

2 – Create a new instance calling “create.pp

ubuntu@ip-10-0-0-22:~$ sudo puppet apply create.pp

Notice: Compiled catalog for ip-10-0-0-22.ec2.internal in environment production in 0.02 seconds

Notice: /Stage[main]/Main/Ec2_instance[Puppet-Client-1]/ensure: changed absent to running

Notice: Finished catalog run in 12.71 seconds

3 – Following the creation on the console (Optional)

Screenshot from 2016-05-27 13-27-26

4 – Meanwhile, let’s get the internal instance IP and add it to our Icinga Manifest file (the Icinga Server acting as Puppet Client will push this file, publish to Icinga config dir and reload the service configuration)

ubuntu@ip-10-0-0-22:~$ sudo aws ec2 describe-instances --filters "Name=tag:Name,Values=Puppet-Client-1" |grep PrivateIpAddress

                    "PrivateIpAddress": "10.0.0.231",

 ubuntu@ip-10-0-0-22:~$ sudo sed -i "s/address.*/address         10.0.0.231/g" /etc/puppet/manifests/site.pp

 

5 – Test the new client webserver accessing the PublicDNSName (At this point, both containers should be up and running)

ubuntu@ip-10-0-0-22:~$ sudo aws ec2 describe-instances --filters "Name=tag:Name,Values=Puppet-Client-1" |grep PublicDnsName

                    "PublicDnsName": "ec2-54-175-55-36.compute-1.amazonaws.com",

Screenshot from 2016-05-27 13-47-26

6 – After login, you’ll see the database test page

Screenshot from 2016-05-27 14-00-13

And you can notice Isinga monitoring the new instance automatically

Screenshot from 2016-05-27 14-05-02.png

Screenshot from 2016-05-27 14-05-55.png

Explaining the Solution

This is the create.pp used by puppet:

Screenshot from 2016-05-27 14-25-09

Its using an default Ubuntu image and as you can see, it’s sending an script called “setup.sh” as user-data:

Screenshot from 2016-05-27 14-28-06.png

The script is self explained. At the end, there is another script called “cronlogs.sh“. This guy is responsible to copy the Apache and MySQL logs from the containers and send to a S3 bucket – that’s the reason to create an S3 IAM Role:

Screenshot from 2016-05-27 14-35-41

And the last part is the Icinga automatic update. Basically it is only a cfg file where the target ip has been previous changed as I mentioned earlier:

Screenshot from 2016-05-27 14-38-05

And that’s all.

All of there files (scripts, Puppet recipes, etc) are available on my public GitHub repository.

Test it when you have time, try to have fun and give me some feedbacks!

Cheers!

ANSIBLE AND AWS – DYNAMICALLY MANAGING AND DEPLOYING EC2 INSTANCES

Hi there!

Today there are endless options for configuration management. Each one with their advantages and disadvantages. As AWS Engineer and most exclusively working with Open Sources solutions, I’d like to use a tool that can fit my profile and be flexible enough to handle any other OS.

I found on Ansible this answer. Its provides a good option for configuration management due to its simplicity, agentless architecture, and ability to interact easily with your ever-changing, scaling, and dynamic AWS architecture.

There are a lot of tutorials regarding AWS and Ansible as well. But its hard to find something like a ‘read and do yourself’ document based on a common AWS environment: VPC, Public and Private subnets and Ansible orchestrating it all *dynamically*.

That’s the objective here! And let’s start…

I wont describe here the creation of a VPC, subnets, instances, etc. I’m supposing that you can do it. But if you don’t, check this.

Current environment:

  • 01 VPC
  • 02 subnets (Public / Private)
  • 01 NAT instance
  • 01 Ansible Server based on ami-1e159872 (Amazon Linux AMI)
  • 01 Ansible Client based on ami-1e159872 (Amazon Linux AMI)

The Ansible Client is not really required, but I created to have a target to test at the end.

Security

  • 01 Security Group allowing SSH to my IP attached to Ansible Server
  • 01 Security Group allowing SSH over the VPC address only
  • 01 IAM role attached to Ansible Server
  • 01 keypair

Ansible is based on SSH calls, that’s why SSH ports must to be opened internally *ONLY* for instances placed on Private subnet (well.. even if it was world wide opened, no one can reach it out without public ip).

The most important thing here is the IAM role. We are going to use Ansible to manage instances but we don’t want to save our Access Keys on the instance. This is not good.

Instead, we are use IAM Role and attach the role on the Ansible Server instance.

Attention: You cannot attach an IAM Rule to a existing instance. Create it first and attach to the instance during the deploy process.

I created a Role called “Ansible” (yeah, quite predictable name) and attached the “PowerUserAccess” Policy on it:

2016-04-28_0940

Then create the instance attaching this Role:

2016-04-28_0944

I should not describe this step, but this is critical. If you miss that, all process will fall down 😉

Installing Ansible on Ansible Server

I have tried several ways to install Ansible (pip, yum, git) and honestly the only one that worked well was GIT:

sudo su
yum update
yum install -y git
cd /usr/local/src
yum -y install git python-jinja2 python-paramiko PyYAML make MySQL-python
git clone git://github.com/ansible/ansible.git
cd ansible
git submodule update --init --recursive
make install
CTRL + D

Test it simply calling “ansible-playbooks” command. If you have a list of parameters followed by “Error! You must specify a playbook file to run”, you did it right!

2016-04-28_0955

Ansible Dynamic Inventory Management

Ansible configuration requires a file called “hosts” that contains – obviously – the list of hosts that you’ll manage. But we are talking about AWS. Maintaining an inventory file might not be the best approach, because hosts may come and go over time, be managed by external applications, or you might even be using AWS autoscaling. So… Things can change horizontally anytime. Would be possible to manage dynamically the hosts, wouldn’t?

For this reason, we’ll use the EC2 external inventory script. To get started with dynamic inventory management, you’ll need to grab the EC2.py script and the EC2.ini config file. The EC2.py script is written using the Boto EC2 library and will query AWS for your running Amazon EC2 instances. The EC2.ini file is the config file for EC2.py, and can be used to limit the scope of Ansible’s reach.

Create the “/ansible/inventory/” directory on Ansible Server and give to it the right permissions and download the inventory files and set the permission to the script:

sudo mkdir /ansible
sudo chown -R ec2-user:ec2-user /ansible
mkdir /ansible/inventory
cd /ansible/inventory
wget https://raw.githubusercontent.com/ansible/ansible/devel/contrib/inventory/ec2.py
wget https://raw.githubusercontent.com/ansible/ansible/devel/contrib/inventory/ec2.ini
chmod +x ec2.py

Set the variables on the system:

export ANSIBLE_HOSTS=/ansible/inventory/ec2.py
export EC2_INI_PATH=/ansible/inventory/ec2.ini

One important thing here that makes me loose several hours is the ec2.ini configuration to suit our environment setting up the following variables:

destination_variable = private_dns_name
vpc_destination_variable = private_ip_address

Without this, Ansible will find only the instances that has public ips. We don’t wanna this.

Also, download the basic configuration of Ansible and save it on /ansible. You can have my config file from here. The full config file (with parameters that we wont use now) can be downloaded here.

2016-04-28_1035

Setting up SSH keys

Ansible is based on SSH calls (again)  due to this concept, we need to provide to our server the pem file. Transfer it using scp, winscp or any other tool to your Ansible Server and complete the setup:

cp /tmp/training.pem ~/.ssh/
chmod 600 ~/.ssh/training.pem
ssh-agent bash
ssh-add ~/.ssh/training.pem

At this stage, you should be ready to communicate with your instances. Let’s test it:

/ansible/inventory/ec2.py --list

You’ll see your Amazon EC2 inventory broken down and grouped by a variety of factors:

2016-04-28_1050

Lets call Ansible using the inventory script to make sure its working well:

ansible -i /ansible/inventory/ec2.py -m ping all

2016-04-28_1058

As you can see, its listing correctly my 03 instances (including NAT)

2016-04-28_1110

You can also use tags to call your instances:

ansible -i /ansible/inventory/ec2.py -m ping tag_Name_Ansible*

2016-04-28_1113

2016-04-28_1114

Using the same concept, you can use Ansible to update your instances, using remote yum calls:

ansible -i /ansible/inventory/ec2.py -m ping tag_Name_Ansible*

2016-04-28_1120

And this is pretty awesome! You can manage groups of instances updating, sending files, etc just using tags, which is a very good practice by the way.

Spinning up new Instances

To use Ansible, you must to be able to fully understand Playbooks.

Using Playbooks you can run multiple Tasks and provide some more advanced functionality that you’d miss out on using ad-hoc commands. I wont explain in deep all of these concepts, but I’ve found a very clear article explaining this in details.

 

Ansible Role

First, create a directory: “/ansible/roles/provision-ec2/tasks/”

Name the file below as “main.yml” and save it on the previous created directory:

---
 - name: Provision EC2
 local_action:
 module: ec2
 key_name: "{{ ec2_keypair }}"
 group_id: "{{ ec2_security_group }}"
 instance_type: "{{ ec2_instance_type }}"
 image: "{{ ec2_image }}"
 vpc_subnet_id: "{{ ec2_subnet_ids|random }}"
 region: "{{ ec2_region }}"
 instance_tags: '{"Name":"{{ec2_tag_Name}}","Type":"{{ec2_tag_Type}}","Environment":"{{ec2_tag_Environment}}"}'
 assign_public_ip: no
 wait: true
 count: 1
 volumes:
 - device_name: /dev/xvda
 device_type: gp2
 volume_size: "{{ ec2_volume_size }}"
 delete_on_termination: true
 register: ec2

 - debug: var=item
 with_items: ec2.instances

 - add_host: name={{ item.public_ip }} >
 groups=tag_Type_{{ec2_tag_Type}},tag_Environment_{{ec2_tag_Environment}}
 ec2_region={{ec2_region}}
 ec2_tag_Name={{ec2_tag_Name}}
 ec2_tag_Type={{ec2_tag_Type}}
 ec2_tag_Environment={{ec2_tag_Environment}}
 ec2_ip_address={{item.public_ip}}
 with_items: ec2.instances

 - name: Wait for the instances to boot by checking the ssh port
 wait_for: host={{item.public_ip}} port=22 delay=60 timeout=320 state=started
 with_items: ec2.instances

These are the instructions to create a new instance and check it before finish the task ssh’ng it.

Next, well create the variable file, that will feed the role with detailed information regarding our environment and number of instances to be created, for example.

Create the directory “/ansible/ec2-vars/” and save the file “instances.yml” into that:

ec2_keypair: "training"
ec2_security_group: "sg-718d9014"
ec2_instance_type: "t2.micro"
ec2_image: "ami-1e159872"
ec2_subnet_ids: ['subnet-bd9925e4']
ec2_region: "sa-east-1"
ec2_tag_Name: "Ansible_Client"
ec2_tag_Type: "Ansible_Instance2"
ec2_tag_Environment: "testing"
ec2_volume_size: 8

Tip: All of these variables and possible values can be found here.

And the last file is the Playbook file that will call the others. Create it on “/ansible/provision-ec2.yml” with this content:

---
 - hosts: localhost
 connection: local
 gather_facts: false
 user: root
 pre_tasks:
 - include_vars: /ansible/ec2-vars/{{type}}.yml
 roles:
 - provision-ec2

Basically Playbook will call the Role instructions and Role will get the required information from variable file. Easy.

Lets run it and see the magic happens:

ansible-playbook -vv -i localhost, -e "type=instances" /ansible/provision-ec2.yml

After couple minutes, you’ll have your new instance up and running. It can be easily checked by Ansible or by “old-school” method: AWS Console:

2016-04-28_1154

2016-04-28_1155_001

Successfully created and tagged. Awesome isn’t it?

Last but not least, let’s update the packages of the new instance, once it was recently created:

ansible -i /ansible/inventory/ec2.py -s -m yum -a "name=* state=latest" tag_Name_Ansible_Client2

You’ll see a big output:

2016-04-28_1201

If you run again, you’ll see that everything was updated successfully:

2016-04-28_1202

That’s all!

I’m preparing my lab to go little bit deeper on Ansible (ELB, Code deployment, etc) and I’ll share with you all in soon.

Thanks!