Ansible tomcat installation + .war deploy

25/06/2015 Wannes Van Causbroeck

Hi all, new employee here.
To start learning about ansible and AWS, I created a small setup that does the following:

  • create an aws instance
  • install tomcat on it
  • deploy a .war file from S3 and edit its contents

While this is a very simple exercise, it does touch on some interesting topics about ansible and aws.
To start, we need something like this:
setup

 

  • an S3 bucket to store our .war files
  • a nat host so ansible tower can manage servers from our vpc in the remote vpc
  • (and in our case an S3 read-only IAM policy as our version of ansible doesn’t support policy creation yet)

Tower config

To allow our tower to connect through the nat host we need some extra config.
In the root of our project we need an ansible.cfg file containing:


[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=16m -F /opt/ansible/ssh/

This allows ansible to use the ssh configuration stored on the tower server. This is what it looks like:


Host [nat instance public ip]
  User [nat instance user]
  IdentityFile [nat instance_pem file]
Host [sandbox private subnet].*
  User [sandbox user]
  StrictHostKeyChecking no
  IdentityFile [sandbox instance pem file]
  ProxyCommand ssh -i [nat instance pem file] -o StrictHostKeyChecking=no [nat_instance_user]@[nat_instance_public_ip] nc %h %p

I won’t detail the tower configuration of the project/inventory/job/… here in detail as it’s quite straightforward.

Ansible playbook

So, we need two playbooks. The first one sets up a node and installs tomcat.


## site.yml
---
- name: Setup EC2
  hosts: tower-via-local
  gather_facts: no
  roles:
    - infrastructure
- name: install tomcat
  remote_user: ec2-user
  sudo: yes
  hosts: launched_servers
  roles:
    - tomcat

The second one deploys the war and edits a file.


### deploy.yml
---
- name: deploy app
  remote_user: ec2-user
  sudo: yes
  hosts: tag_Name_tomcat
  roles:
    - deploy_app

We need to split these up as both playbooks need a different inventory to work with:

  • provision & install: ‘tower-via-local’ which is located in our VPC
  • configuration: here we get the sandbox vpc ec2 instances and select a specific tagname we gave during provisioning

In theory, you could work with a callback to tower for the second part, but then you need to set up a way for the client to reach the server through the nat instance. As we don’t need this functionality right now, we’re skipping this step.

One of the nice things about tower is you can create simple forms provide variables to our playbooks. In this case I made two forms to provide the number of instances and the name of the war file.


# roles/infrastructure/tasks/main.yml
---
- name: Create the tomcat security group
  ec2_group:
    description: "Open ssh and tomcat ports"
    name: "tomcat-sg"
    region: "{{ region }}"
    rules:
      # only allow ssh access from the nat instance
      - proto: tcp
        from_port: 22
        to_port: 22
        cidr_ip: "{{ nat_ip }}/32"
      # open tomcat to the world
      - proto: tcp
        from_port: 8080
        to_port: 8080
        cidr_ip: 0.0.0.0/0
    purge_rules: yes
    state: present
    vpc_id: "{{ vpc_id }}"
  register: tomcat_sg 

# NOTE: only from ansible > 2.0
# ansible can't create a role from an existing policy, so we're obligated to upload a new one (stored in roles/infrastructure/files)
#- name: Create S3 read-only access
#  iam_policy:
#    iam_name: s3-tomcat
#    iam_type: role
#    policy_document: s3-ro.json
#    policy_name: s3-ro
#    state: present

- name: Launch base server
  ec2:
    assign_public_ip: yes
    group_id: "{{ tomcat_sg.group_id }}"
    image: "{{ tomcat_ami }}"
    instance_type: "{{ tomcat_instance_type }}"
    instance_profile_name: s3-tomcat
    exact_count: "{{ tomcat_instance_count }}"
    count_tag: { "Name": "tomcat" }
    key_name: "{{ tomcat_kp }}"
    region: "{{ region }}"
    vpc_subnet_id: "{{ poc_subnet }}"
    wait: no
    assign_public_ip: yes
    instance_tags: {
      "Name": "tomcat",
    }
  register: base_server 

# because wait_for doesn't use the ssh config we have to delegate this taks to the nat host. Otherwise tower wil try to connect to port 22 directly
- name: Check if we can ssh to the instance
  wait_for:
    host: "{{ item.private_ip }}"
    port: 22
    state: started
  with_items: base_server.instances
  when: item.state != "terminated"
  delegate_to: "{{ nat_ip }}"

# add all servers to a temporary group we can use to install tomcat. We need this group as in the current playbook we have 'tower-via-local' as inventory
- name: Add servers to temporary group
  add_host:
    hostname: "{{ item.private_ip }}"
    groupname: launched_servers
  with_items: base_server.instances
  when: item.state != "terminated"

The next role just installs tomcat


---
- name: Install basic software
  yum:
    name: "{{ item }}"
    state: present
  with_items:
    - java-1.8.0-openjdk
    - tomcat8

Ok, so now we have some servers ready to go. The next playbook uses a different inventory


# roles/deploy_app/tasks/main.yml
---
# our war file is 
- name: Deploy war file
  s3: 
    bucket: "{{ war_bucket }}"
    object: "{{ war_file }}" 
    dest: "{{ war_deploy_path }}/{{ war_file }}"
    mode: get 
    overwrite: no
  register: war_downloaded

- name: Set correct permissions
  file: 
    path: "{{ war_deploy_path }}/{{ war_file }}"
    owner: tomcat
    group: tomcat
  when: war_downloaded.changed
  register: war_deployed

- name: Restart tomcat
  service:
    name: tomcat8
    state: restarted
  when: war_deployed.changed

# here we cheat a little. The sample.war I'm deploying contains an index.html that we want to edit. We just wait untill the war is unpacked and the file is available
- name: Wait until war is deployed
  wait_for:
    path: "{{ war_deploy_path }}/{{ app_name }}/index.html"

- name: Edit index file
  lineinfile:
    dest: "{{ war_deploy_path }}/{{ app_name }}/index.html"
    regexp: '^Sample "Hello, World" Application'
    line: 'Sample "Hello, from cloudar" Application'   
  lineinfile:
    dest: "{{ war_deploy_path }}/{{ app_name }}/index.html"
    regexp: '^

Sample "Hello, World" Application

' line: '

Sample "Hello, from cloudar" Application

' when: war_deployed.changed

This concludes our little exercise! Feel free to leave comments, I’m sure there is still room for improvement.

Share this AWSome post

Comments (2)

  1. Jaypal

    How to deploy ear,war and jar files with ansible playbooks

    Thanks
    Jaypal

    • Bert Mertens

      Hi Jaypal,

      The blog post you responded to describes how to deploy WAR files on Apache Tomcat.

      The site.yml playbook first creates and prepares an EC2 instance using the infrastructure role and then installs Tomcat using the tomcat role.

      When the server has been prepared, you can run the deploy.yml playbook.

      This will then deploy the WAR file from an S3 bucket.

      Concerning JAR and EAR files, please see for example following link (https://stackoverflow.com/questions/1594667/war-vs-ear-file) describing the differences.

      You’ll be able to find plenty more guides online on setting up infrastructure and deployment guides.

      Feel free to reach out to our sales department if you need any assistance.

      Kind regards,
      Bert

Leave a Reply

Your email address will not be published. Required fields are marked *