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.pemyou 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---From the top , the names of the tasks should tell you what each one is trying to do :
- 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
- hosts refer to the hosts -or host groups in the Ansible hosts file we talked about above.
- sudo -run this as root.
- tasks simply the list of things to do
- apt the ansible module for the Ubuntu packaging system
- notify call a handler
- handlers commands that can be run on demand from tasks, typically used to do things like bouncing servers.
- register the result of a command into a variable
- 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 :
- get_url does what it says on the tin, as you can see it also checks file checksums
- changed_when tells Ansible when something has happened, in this case it's used because dpkg will succeed whether or not it installs anything
- shell runs a Linux shhell command in the raw, command samitizes it.
- cron sets up a cron job.
---
- 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
Instead of `command: tar xf conf.tar` consider using http://docs.ansible.com/unarchive_module.html
ReplyDeleteInstead of downloading debs and then `command: dpkg`, consider using http://docs.ansible.com/apt_repository_module.html with info from http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/setup-repositories.html#_apt
Both of these changes will make your playbooks more "ansible" and less brute-forcy. ;)
I don't know your experience but have you looked at https://galaxy.ansible.com/list#/roles? It has a couple of ES playbooks that you could take advantage of, rather than reinvent the wheel. :)
Thanks for those, I did have a version of the script running with unarchive, but I updated ansible and it stopped working!
ReplyDeleteI guess there's a question of how 'ansible' I want to make them, after all there are already a bunch of well tried shell commands for these operations that ansible is wrapping, that I already know.
I'll have a look at ES playbooks to see what they offer, I am still pretty much open minded as to how I want to configure ES, Ansible &c, Cloud Init, just baking images. More investigation needed.