Part 5 - Prepare a web application for EC2.

By Ben Outram / 2018-06-15

We are going to provision a web application on our EC2 web server instances when they are launched to help visually demonstrate the successful deployment of our infrastructure.

Sample web application

The Spring Boot S3 Example project is a Spring Boot sample web application written in Java. It can be built as a fully executable jar file and installed as a Linux Service. It has a MySQL database dependency to demonstrate database connectivity.

A build of this project exists in a public S3 bucket (s3://springboot-s3-example) which is available for you to use.

A script will be required to configure the application on the EC2 instances. This is what we are going to walk through next.

User data script

AWS provides the ability to perform automated configuration tasks through 'User data' scripts that are executed during the first boot cycle when an instance is launched. Here we will create a script to fetch the build file from S3 storage and install it as a service.

Later on we will be configuring our instances with an Amazon Linux machine image. This means that commands in our script need to work with this type of Linux distribution.

'User data' scripts are run as root so there is no need to use sudo.

Here's one we made earlier

Copy the source from the script which can be found here. Create this as a new shell script in the root of the project folder called provision.sh.

Let's look at how the script has been made up. First of all we start by installing Nginx and Java:

#!/bin/bash

yum install nginx -y

yum install java-1.8.0-openjdk-devel -y
yum remove java-1.7.0-openjdk -y

Next, create a new user to run the Spring Boot application as a service and disable the login shell:

useradd springboot
chsh -s /sbin/nologin springboot

Copy the Spring Boot application build file from S3. This uses variables for the S3 bucket name and AWS region that will be interpolated when we use the script as a template later.

mkdir /opt/springboot-s3-example
aws s3 cp s3://${s3_bucket_name}/ /opt/springboot-s3-example/ --no-sign-request --region=${region} --recursive --exclude "*" --include "springboot-s3-example*.jar"
mv /opt/springboot-s3-example/springboot-s3-example*.jar /opt/springboot-s3-example/springboot-s3-example.jar

Write a configuration file with our Spring Boot run arguments. This uses variables for the database endpoint, database name and database password that will also be interpolated.

cat << EOF > /opt/springboot-s3-example/springboot-s3-example.conf
RUN_ARGS="--spring.datasource.url=jdbc:mysql://${database_endpoint}/${database_name}?useSSL=false --spring.datasource.password=${database_password}"
EOF
chmod 400 /opt/springboot-s3-example/springboot-s3-example.conf
chown springboot:springboot /opt/springboot-s3-example/springboot-s3-example.conf

Write a Nginx site configuration file to redirect HTTP requests to use HTTPS. We will also map port 80 to port 8080 used by the Spring Boot application:

cat << EOF > /etc/nginx/conf.d/springboot-s3-example-nginx.conf
server {
    listen 80 default_server;

    # Redirect if the protocol used by the client of the AWS application load balancer was not HTTPS
    if (\$http_x_forwarded_proto != 'https') {
        return 301 https://\$host\$request_uri;
    }

    location / {
        proxy_set_header    X-Real-IP \$remote_addr;
        proxy_set_header    Host \$http_host;
        proxy_set_header    X-Forwarded-For \$proxy_add_x_forwarded_for;
        proxy_pass          http://127.0.0.1:8080;
    }
}
EOF

Rewrite the default Nginx configuration file to disable the default site. Here we take care to escape any '$' symbols that already exist in the Nginx file:

cat << EOF > /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '\$remote_addr - \$remote_user [\$time_local] "\$request" '
                      '\$status \$body_bytes_sent "\$http_referer" '
                      '"\$http_user_agent" "\$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    include /etc/nginx/conf.d/*.conf;

    index   index.html index.htm;
}
EOF

Set owner read and execute mode on the Spring Boot application for the new user:

chown springboot:springboot /opt/springboot-s3-example/springboot-s3-example.jar
chmod 500 /opt/springboot-s3-example/springboot-s3-example.jar

Install the Spring Boot application as an init.d service by creating a symlink:

ln -s /opt/springboot-s3-example/springboot-s3-example.jar /etc/init.d/springboot-s3-example

Set the services to start automatically. Since we are going to use an Amazon Linux machine image this will be done using chkconfig.

chkconfig nginx on
chkconfig springboot-s3-example on

Finally, start the Nginx and Spring Boot services:

service nginx start
service springboot-s3-example start

Now that we have the script, we need to create a data source for the shell script so that we can use it as a template and assign values to our variables. Add the following data source block to datasource.tf:

data "template_file" "provision" {
  template = "${file("${path.module}/provision.sh")}"

  vars {
    database_endpoint = "${aws_db_instance.default.endpoint}"
    database_name     = "${var.database_name}"
    database_password = "${var.database_password}"
    region            = "${var.region}"
    s3_bucket_name    = "${var.s3_bucket_name}"
  }
}

We have introduced one new variable for the S3 bucket name. Define it in file variables.tf:

variable "s3_bucket_name" {}

Assign a value to the variable in file terraform.tfvars. Here we use the name of the public S3 bucket s3://springboot-s3-example.

s3_bucket_name = "springboot-s3-example"

Adding the data source for the shell script template has introduced a dependency of the Terraform Template provider. Retrieve the plugin for the provider from the plugin repository:

$ terraform init

We can now try another plan. Since we haven't defined any new resources, there will be no changes to apply.

$ terraform plan -var-file="user.tfvars"
------------------------------------------------------------------------
No changes. Infrastructure is up-to-date.

You can find all the source code for this part of the lab here in GitHub.

More posts in this series.