When you are looking to reduce your AWS costs, you might consider removing the Load balancer. At $20 per month it accounts for a large proportion of the hosting costs for a small application. Is it really needed right now? Down the line when your application grows, you can easily bring it back - right?
Elastic beanstalk is handy for provisioning and deployment, and thankfully the load balancer is not compulsory. So the good news is we can keep using EB for provisioning and deployment. But what about SSL ? and what alternative is there to the health_check “heartbeat” we use to drive delayed jobs?
Before proceeding, it is worth considering what we lose when we drop the load balancer: This article describes the commonly ignored or misunderstood features of Load balancers: “ELB Internals, Security and Troubleshooting” by Ruggero Tonelli, 31 January 2014
So here is how we flipped to “single instance” Elastic Beanstalk for a site with light traffic, and how we deal with SSL and found a “heartbeat” alternative.
Use Cron to provide the health_check “heartbeat”
Linux comes with cron, so this isnt AWS-specific. To keep things simple, we just use cron to ping the health_check URL every minute. See Running cron in elastic beanstalk auto-scaling environment by Paulo Poiati, 25 August 2013
The schedule logic is inside the application itself. So our .ebextensions/cron.config looks like this:
# .ebextensions/cron.config
files:
"/tmp/cron_jobs" :
mode: "000777"
content: |
* * * * * curl http://0.0.0.0/health_check/custom
encoding: plain
container_commands:
01_remove_old_cron_jobs:
command: "crontab -r || exit 0"
02_cronjobs:
command: "crontab /tmp/cron_jobs"
leader_only: true
Handle SSL on the EC2 instance
The AWS documentation is here: Configuring SSL for Single-Instance Environments, however it is not complete, and after several hours we gave up trying to get ssl to work on passenger standalone, and eventually got success with puma.
With Passenger - 64bit Amazon Linux 2014.09 v1.0.9 running Ruby 2.1 (Passenger Standalone)
- we succeeded in opening port 443 for https on a Amazon EC2 instance Security Group (SG) but failed to get nginx to listen on port 443 and use the certificate and private key. We got net::ERR_CONNECTION_REFUSED when browsing to the site.
To create the puma environment with the “64bit Amazon Linux 2014.09 v1.1.0 running Ruby 2.1 (Puma)” AMI, we used the EB cli v3.1 tool. In a one line command you can provision and deploy a single instance puma environment:
$ eb create dev-env -p "64bit Amazon Linux 2014.09 v1.1.0 running Ruby 2.1 (Puma)" --single -i t2.micro --envvars key1=value1,key2=value2...
While that is running, you still have to manually add the new SG to the DB SG and open port 443 to HTTPS. We got net::ERR_TOO_MANY_REDIRECTS when browsing to the site, but this was easily fixed by adding this to the ssl.config:
// ssl.config
...
proxy_set_header X-Forwarded-Proto https;
...
So our .ebextensions/ssl.config now looks like this:
// ssl.config
Resources:
sslSecurityGroupIngress:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: example_id
IpProtocol: tcp
ToPort: 443
FromPort: 443
CidrIp: 0.0.0.0/0
files:
"/etc/nginx/conf.d/ssl.conf" :
content: |
# HTTPS server
server {
listen 443;
server_name localhost example.co.uk www.example.co.uk;
ssl on;
ssl_certificate /etc/pki/tls/certs/server.crt;
ssl_certificate_key /etc/pki/tls/certs/server.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS;
ssl_prefer_server_ciphers on;
add_header Strict-Transport-Security "max-age=31536000";
location / {
proxy_pass http://my_app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
location /assets {
alias /var/app/current/public/assets;
gzip_static on;
gzip on;
expires max;
add_header Cache-Control public;
}
location /public {
alias /var/app/current/public;
gzip_static on;
gzip on;
expires max;
add_header Cache-Control public;
}
}
"/etc/pki/tls/certs/server.crt" :
content: |
-----BEGIN CERTIFICATE-----
MTYwMTE4MjIzOTM4WjBIMSEwHwYDVV...
-----END CERTIFICATE-----
"/etc/pki/tls/certs/server.key" :
content: |
-----BEGIN RSA PRIVATE KEY-----
6JqCpm3OYCIzx4fNsecDUoA+Varg+s5yHC...
-----END RSA PRIVATE KEY-----
container_commands:
01restart_nginx:
command: "service nginx restart"
After swapping the CNAME’s we terminated the Load balanced environment and are now running on a leaner single instance Elastic Beanstalk!