Showing posts with label ec2. Show all posts
Showing posts with label ec2. Show all posts

Tuesday, 28 October 2014

Early Days with Ansible for Nginx and Elastic Search on EC2

As our new recruitment site, sorted.jobs  edges towards production I have to start thinking more about the infrastructure side of things. The search part of the site uses ElasticSearch which is, by design, insecure if you can find an installation you can use it.  Current best practice seems to be to hide it behind a web server running https, normally Nginx, and use that to control access.

This gives me two sets of machines to configure, the Nginx proxy and the Elasticsearch server, since we're trying to be a grown up company we don't want to do all this by hand every time, so it make sense to script it. In the bad old days we used to do this with the Unix shell, see I said Unix not Linux -that's how old those bad old days were! Now we don't need to do that we can have centralised deployments using a variety of tools  such as Chef, Puppet and johnny-come-lately Ansible.

So why choose Ansible? I have briefly played with Chef, and looked at Puppet for another company, and I seem to remember them being fairly complicated. I did a web search to compare the two and Ansible popped up as well in several cases.

Ansible had a few things going for it ;configuration files are in standard YAML, no client to install, Jinja2 templates -which we are already using- and the words 'easy', 'simple' and 'uncomplicated' came up a lot. So I decided to give it a whirl.

Getting it going

Ansible uses a hosts file (held in /etc/ansible/hosts) to define the servers it wants to talk to. As well as defining hosts you can group them for use in playbooks.

We are running sorted.jobs ElasticSearch on EC2 so the definitions look like :
54.123.123.123 ansible_ssh_user=ansible_user ansible_ssh_private_key_file=ansible_key.pem
you can use either IP addresses or domain names to set up the server.

Running a simple command like `ansible all -m ping`or `ansible all -a "/bin/echo hello"`  will let you test out the definitions.

Once you have the definitions sorted out it is time to get Ansible to actually do something useful, you do this with playbooks. A playbook is basically just a script to tell Ansible what to do when. You run them with ansible-playbook  (e.g. ansible-playbook -v elasticsearch.yml). This caught me out initially as I was looking for an option to pass the playbook to the 'ansible' command.

First Playbook Nginx

This playbook installs Nginx uploads the certificates configuration and password files for https
---
- hosts: es_proxys
  sudo: yes

  tasks:
    - name: Installs nginx web server
      apt: pkg=nginx state=installed update_cache=true
      notify:
        - start nginx

    - name: Upload default ngix certs and conf
      copy: src=./es_proxys/conf.tar dest=/tmp

    - name: Untar
      command: tar xf conf.tar
      register: untarred
      ignore_errors: True

    - name: move to nginx etc
      command: mv conf /etc/nginx

    - name: move to nginx etc
      command: `mv .htpasswd /etc/nginx
      register: https_conf

    - name: Upload proxy vhost
      copy: src=es_proxys/es_proxy dest=/etc/nginx/sites-enabled
      when: https_conf|success
      notify:
        - restart nginx
     
     
  handlers:
    - name: start nginx
      service: name=nginx state=started

    - name: restart nginx
      action: service name=nginx state=restarted
From the top , the names of the tasks should tell you what each one is trying to do :
  1. hosts refer to the hosts -or host groups in the Ansible hosts file we talked about above.
  2. sudo -run this as root.
  3. tasks simply the list of things to do
  4. apt the ansible module for the Ubuntu packaging system
  5. notify call a handler
  6. handlers commands that can be run on demand from tasks, typically used to do things like bouncing servers.
  7. register the result of a command into a variable
  8. when conditionally run a task based on the value of a variable. In the example above  the `mv .htpasswd /etc/nginx` command must have succeeded (and, by implication, the earlier tasks) for the proxy upload to be run.

Basic ElasticSearch

This playybook installs Elasticsearch and sets it up with some extra Elasticsearch plugins and a backup configuration.

As well as the things we saw in the proxy  Playbook there are some new features :
  1. get_url  does what it says on the tin, as you can see it also checks file checksums
  2. changed_when tells Ansible when something has happened, in this case it's used because dpkg will succeed whether or not it installs anything
  3. shell runs a Linux shhell command in the raw, command samitizes it.
  4. cron sets up a cron job.
Note in one case I had to use a raw command (curl in the backup config) as I couldn't get the builtin (get_url) to work for me (horrendous quoting issues.
---
- hosts: es_servers
  sudo: yes

  tasks:
  - name: Installs java JRE
    apt: pkg=openjdk-7-jre-headless state=installed update_cache=true
    register: jre
 
  - name: Download ES
    get_url: url=https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.3.4.deb dest=/tmp/es.deb sha256sum=6a15ab0f8c13574162e98828d7ec0e1155e6136f9d45c54b88e39222bbbd53ca
    register: es_dl
 
  - name: Install ES
    command: dpkg --skip-same-version -i /tmp/es.deb
    register: dpkg_result
    changed_when: "dpkg_result.stdout.startswith('Selecting')"
    when: jre|success and es_dl|success
    notify:
      - start es
   
  - name: Remove ES Attachment plugin
    shell: /usr/share/elasticsearch/bin/plugin -r elasticsearch-mapper-attachments || /bin/true
    register: es_plug_result
    changed_when: "'Removed' in es_plug_result.stdout"
    when: dpkg_result|success
   
  - name: Install ES Attachment plugin
    command: /usr/share/elasticsearch/bin/plugin -i  elasticsearch/elasticsearch-mapper-attachments/2.3.0
    register: es_plug_result
    changed_when: "'Installed' in es_plug_result.stdout"
    when:
    notify:
      - restart es
   
  - name: Remove ES S3 plugin
    shell: /usr/share/elasticsearch/bin/plugin -r elasticsearch/elasticsearch-cloud-aws || /bin/true
    register: es_plug_result
    changed_when: "'Removed' in es_plug_result.stdout"
    when: dpkg_result|success
   
  - name: Install ES S3 plugin
    command: /usr/share/elasticsearch/bin/plugin -i  elasticsearch/elasticsearch-cloud-aws/2.3.0
    register: es_plug_result
    changed_when: "'Installed' in es_plug_result.stdout"
    when:
    notify:
      - restart es
   
  - name: Upload s3 config
    copy: src=./s3_config.json dest=/home/ubuntu
 
  - name: Configure backup for s3
    command: curl -XPUT 'http://localhost:9200/_snapshot/s3_live' -d @/home/ubuntu/s3_config.json
    register: s3_result
    changed_when: "'acknowledged' in s3_result.stdout"
 
  - name: Remove s3 config
    command: rm /home/ubuntu/s3_config.json
 
  - name: S3 cron
    cron: name=s3_bup hour=1 minute=50 job='curl -XPUT "http://localhost:9200/_snapshot/s3_live/snapshot_$(date +\%Y\%m\%d)"'
 
   
  handlers:
  - name: start es
    service: name=elasticsearch state=started
 
  - name: restart es
    service: name=elasticsearch state=restarted

 The Book 

If you want a book there's Ansible Configuration Management I did buy this, but I think you will do just as well with the Ansible documentation.

Tuesday, 7 August 2012

Apache Logging to MongoDB Using a Named Pipe

Following on from the last article on remote logging where I collected all the logs into one place, I wanted to be able to query them -so that I can ask questions like "which pages take longer than  5 seconds to produce", 'how many of our redirects fail?" and the like.

I believe that there are already MySQL modules for rsyslogd, but I had written a script in Python to interpret logs and put them into MongoDB, so I wanted to use that.

The obvious solution to join the two programs together seemed to be to used a named pipe, a vaery basic type of interprocess communictation where one program chucks bits down the pipe and the other pulls them out.

Chucking them in is easy enough, firsty create your pipe with mkfifo , then  just change the line is the rsyslog server config from :

:programname, isequal, "apache2" /var/log/oneGiantHeapOfLogs.log
to
:programname, isequal, "apache2" |/tmp/logger_pipe



Reading from the pipe was pretty easy too as this Python snippet shows :

#Open the pipe
in_pipe = open(sys.argv[1], "r")
#Loop forever
while True :
  try:
    line = in_pipe.readline()[:-1]    #Supposedly makes this blocking
    if len(line) == 0 :                       #Happens in test
      time.sleep(5)
      continue


In short, open the pipe, loop forever reading lines. We want this to keep going so the program is wrapped in an exception handler to catch any log parsing errors.

Having got the input to the program it's just a problem or parsing that input -we can take a look at that in another post.

Tuesday, 31 July 2012

Remote logging for Apache and Amazon AWS

One of the changes that I have started in my current job has been to move the environment to Amazon Web Services. By and large this suits us well, but one of the issues we do have is that, as instances spin up and down, we lose logs.

My current solution to this is to set up a logging server and route the logs from the web heads to that, if you're not interested in Linux plumbing it's time to go away and read something else.

There are 3 areas that need configuring Apache, the web head's logging system and the logging server. Our systems are running Ubuntu 12.04 LTS (hey, the Rezillos on Last.fm :) ), and that uses rsyslog rather than stock syslog -so YMMV

Apache

This is just an edit of the httpd.conf file (or a file it includes) to repoint ErrorLog and CustomLog. We also set up a new blackbox logging format to help debugging.

ErrorLog "|/usr/bin/logger -p local1.info -t apache2"
LogFormat "%v:%a/0 %X %t \"%r\" %s/%>s %{pid}P/%{tid}P %T/%D %I/%O/%B" blackbox
CustomLog "|/usr/bin/logger -p local1.info -t apache2" blackbox

As you can see this uses a pipe into logger(-p sets the priotity and -t the tag), when you reload your Apache you should find the log messages from it going into the local syslog.

Local Rsyslog


I basically trashed /etc/rsyslog.conf to send everything from local.1 to the logging server, local.1 probably isn't the right channel for this -but it's working.

$ModLoad imuxsock # provides support for local system logging
$ModLoad imklog   # provides kernel logging support (previously done by rklogd)
local1.* @ec2-46-137-82-138.eu-west-1.compute.amazonaws.com


Messages are sent via UDP -as we just want a 'fire and forget' system, if you want to be sure your messages arrive you can tell rsyslog to use TCP

One thing to note is the use of the DNS for the Elastic IP of the server, rather than the IP address itself -this should ensure that traffic is internal to Amazon -and thus free, using the IP address will route it externally, which costs.

Another poi ntis the use of the Elastic IP -rather than just the IP address of the server. This is another AWS funny, servers aren't guranteed to keep their IP if they shutdown and restart, wheras the Elastic IP should always be ther,e using it saves you having to update all your clients if the server IP changes.

Server Rsyslog

Uncomment UDP syslog reception in rsyslog.conf
# provides UDP syslog reception
$ModLoad imudp
$UDPServerRun 514
 

In /etc/rsyslog.d I created a .conf file containing :


:programname, isequal, "apache2" /var/log/oneGiantHeapOfLogs.log

This tests for the tag name 'apache2' that we set using logger in the apache.conf file, if it finds it the message goes to the big log file.


Job Done.


Debugging

  • you can call logger directly on the commandline with a message and tail the various log files you are looking at to see if the message arrives.
  • On the server 'netstat -nlp' will show you if udp is listening on the logging port (514)
  • On the client you can write directly to the port on the server with netcat -u 10.229.70.230 514
  • Put rsyslog messages into debug mode with *.* /var/log/rsyslog_debug.log;RSYSLOG_DebugFormat early on in rsyslog.conf
Eliza Carthy playing  'Moss Meg' now.

linkedin