webservice_vm

Description

This example creates a load balanced https web farm with a fixed number of pay-as-you-go virtual machines running nginx. The front end of the balancer listens to ports 80 and 443, and creates the necessary structures to store and use a certificate (it can be self-signed).

To make the example portable, use a 2048-bit length private key. If you use a larger length private key on AWS you will receive a "certificate not found" error, which is due to an AWS configuration limitation of ELBv2 load balancers against ACM.

Concepts

The following concepts are present in this example:

  • Certificates

  • HTTPS

  • Linux

  • Load Balancing

  • Nginx

  • Secrets

  • Userdata

  • Variables

  • Virtual Machine

Venues

This example is regularly tested against:

awsazure

Release Notes

1.0

  • Initial release.

2.0

  • Added variables for controlling vm, zone counts.

  • Added https support for existing certificates.

3.0

  • Removed the prerequisite for certificates (and on Azure, a managed identity) to be created manually before applying this example.

  • Added support for importing certificates into the venue.

  • Removed azure-specific variables.

Blueprint

#
# This example demonstrates how to make a portable, secure load balanced
# web service using virtual machines in multiple availability zones for
# resiliency.
#
---
variables:
  admin_username:
    description: >-
      The administrative username for SSH access to the linux virtual machines.
      Note that the web servers are not publicly accessible however they still
      require an administrative user configuration.
    type: string
    default: adminuser
  admin_public_key:
    description: >-
      The administrative public key for SSH access to the linux virtual machines.
      Note that the web servers are not publicly accessible however they still
      require an administrative user configuration.
    type: string
  availability_zones:
    description: >-
      Indicates how many availability zones to spread the virtual machines across.
    type: integer
    default: 2
    min: 1
  balancer_cert_body:
    description: >-
      The balancer certificate in PEM format.
    type: string
  balancer_cert_private_key:
    description: >-
      The balancer certificate's private key in PEM format.
    type: secret
  web_servers:
    description: >-
      The number of web servers to create.
    type: integer
    default: 2
    min: 1

location:
  region:
    my-region:
      country: USA
      area: northwest
  folder:
    tuono-webservice:
      region: my-region

security:
  certificate:
    webservice-cert:
      body: (( balancer_cert_body ))
      private_key: (( balancer_cert_private_key ))

networking:
  network:
    webservice:
      range: 10.0.0.0/16
      scope: public
  subnet:
    appzone-(( count )):
      count: (( availability_zones ))
      range: 10.0.10(( count )).0/24
      network: webservice
      firewall: backend-access
      zone: (( count ))
  firewall:
    backend-access:
      rules:
        - services: internal-http-dev
          from: networking.network.webservice
          to: self
        - services: internal-http-dev
          from: self
          to: networking.network.webservice

  balancer:
    webservice-balancer:
      network: webservice
      scope: public
      purpose: testing
      routes:
        # redirect port 80 to port 443
        - from: external-http
          to: external-https
        # service port 443
        - from: external-https
          to: internal-http-dev

  service:
    external-http:
      # traffic coming in from the internet
      port: 80
      protocol: http
    external-https:
      # secure traffic coming in from the internet
      port: 443
      protocol: https
      certificate: webservice-cert
      security_policy:
        aws: ELBSecurityPolicy-TLS-1-2-Ext-2018-06
        azure: AppGwSslPolicy20170401S
    internal-http-dev:
      # traffic for the web service internally
      port: 8080
      protocol: http

compute:
  image:
    bitnami:
      publisher: bitnami
      product: nginxstack
      sku: 1-9
      venue:
        aws:
          image_id: ami-0e789678bc78bb87e

  vm:
    webserver:
      # We place two webservers in each of two availability zones for durability.
      count: (( web_servers ))
      cores: 1
      memory: 1 GB
      image: bitnami
      nics:
        default:
          ips:
            - private:
                type: dynamic
          firewall: backend-access
          provides: internal-http-dev
          subnet: appzone-(( 1 + ((count - 1) % availability_zones) ))
      tags:
        wicked: awesome
      # until ch3883 is resolved on azure you also need to specify the zone here
      zone: (( 1 + ((count - 1) % availability_zones) ))
      configure:
        admin:
          # required to initiate a deploy on Azure, but ignored on AWS
          # when userdata is present
          username: (( admin_username ))
          public_key: (( admin_public_key ))
        userdata:
          # why not use cloud-init?
          # answer: the azure bitnami nginx marketplace image has disabled cloud-init
          #         so we have to use a shell script to make a portable example
          type: shell
          content: |
            #!/bin/sh

            ### debugging mode so we can see what is happening in the logs
            set -x

            ### set up administrative user (idempotent)
            userid=$(id -u (( admin_username )))
            if [ -z "$userid" ]; then
                set -e
                adduser --gecos "" --disabled-password (( admin_username ))
                cd ~(( admin_username ))
                mkdir .ssh
                chmod 700 .ssh
                echo "(( admin_public_key ))" > .ssh/authorized_keys
                chmod 600 .ssh/authorized_keys
                chown -R (( admin_username )).(( admin_username )) .ssh
                usermod -aG sudo (( admin_username ))
                echo "(( admin_username ))   ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
                set +e
            fi

            ### set up nginx by replacing the home page
            echo 'webserver-(( count ))' > /opt/bitnami/nginx/html/index.html

            ### make it run on port 8080
            sed -i 's/listen  80;/listen  8080;/' /opt/bitnami/nginx/conf/nginx.conf

            ### restart nginx due to the port change
            /opt/bitnami/ctlscript.sh restart nginx

Last updated

Was this helpful?