Deployment is not the End

Vladimír Oraný

Test Facilitator @ Agorapulse

@musketyr

 

 

 

Agenda

  • Deplyoment Automation (Beanstalk & Gradle)
  • Log Monitoring (Sentry)
  • Application Performance Monitoring (NewRelic)

Sample Application for Deployment

curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"

sdk install grails

grails create-app dinte2018
cd dinte2018

# inside grails-app/conf/application.yml
#  1) remove environments.production.dataSource.properties
#  2) update environments.production.dataSource.url to use in-memory database
#  3) set environments.production.dataSource.dbCreate to update
# before creating the WAR

grails war

AWS Elastic Beanstalk

AWS Elastic Beanstalk

 

AWS Elastic Beanstalk is an easy-to-use service for deploying and scaling web applications and services developed with Java, .NET, PHP, Node.js, Python, Ruby, Go, and Docker on familiar servers such as Apache, Nginx, Passenger, and IIS.

Beanstalk Application

Beanstalk Application

Beanstalk Application

Beanstalk Application

Beanstalk Application

Beanstalk Application

Beanstalk Application

Beanstalk Application

Deployment Automation

Gradle Beanstalk Plugin

Gradle Beanstalk Plugin

plugins {
    id "fi.evident.beanstalk" version "0.2.0"
}

// ...

// version needs to be unique during deployment but plugin supports snapshots
version = "0.1-SNAPSHOT"

// ...

beanstalk {
    s3Endpoint = "s3-eu-west-1.amazonaws.com"
    beanstalkEndpoint = "elasticbeanstalk.eu-west-1.amazonaws.com"

    deployments {
        gr8war {
            file = tasks.war
            application = 'dinte2018'
            environment = 'Dinte2018-env'
        }
    }
}

Gradle Beanstalk Plugin

./gradlew deployGr8war

AWS Credentials

AWS Credentials

AWS Credentials

AWS Credentials

AWS Credentials

pip install awscli --upgrade --user

aws configure

# AWS Access Key ID [****************C7YQ]: AAAAAAA
# AWS Secret Access Key [****************ci+o]: BBBBBBBBBBBB
# Default region name [eu-west-1]: eu-west-1
# Default output format [None]: 

./gradlew deployGr8war

JAR Deployments

JAR Deployments

    // uncomment/remove "war" plugin
    // apply plugin:"war"
    
    // ...

    deployments {
        gr8jar {
            file = tasks.jar
            application = 'dinte2018'
            environment = 'gr8jar'
        }
    }

    // run "assemble" before deployment to create fat jar
    tasks.withType(fi.evident.gradle.beanstalk.DeployTask) {
        dependsOn assemble
    }

JAR Deployments

# add following into grails-app/conf/application.yml
---
environments:
    production:
        server:
          port: 5000

JAR Deployments

JAR Deployments

JAR Deployments

JAR Deployments

JAR Deployments

JAR Deployments

./gradlew deployGr8jar

ZIP Deployments

Grails with File Upload

Grails with File Upload

Grails with File Upload

Grails with File Upload

Grails with File Upload

Grails with File Upload

Grails with File Upload

Grails with File Upload

AWS Elastic Beanstalk

 

AWS Elastic Beanstalk is an easy-to-use service for deploying and scaling web applications and services developed with Java, .NET, PHP, Node.js, Python, Ruby, Go, and Docker on familiar servers such as Apache, Nginx, Passenger, and IIS.

NGINX EVERYWHERE

# Elastic Beanstalk Nginx Configuration File in src/main/eb/.ebextensions/nginx/nginx.conf

user                    nginx;
error_log               /var/log/nginx/error.log warn;
pid                     /var/run/nginx.pid;
worker_processes        auto;
worker_rlimit_nofile    19200;

events {
    worker_connections  1024;
}

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

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

    include       conf.d/*.conf;

    map $http_upgrade $connection_upgrade {
        default     "upgrade";
    }

    server {
        listen        80 default_server;
        access_log    /var/log/nginx/access.log main;

        client_header_timeout 60;
        client_body_timeout   60;
        keepalive_timeout     60;
        gzip                  off;
        gzip_comp_level       4;
        gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;

        # Include the Elastic Beanstalk generated locations
        include conf.d/elasticbeanstalk/*.conf;
        # Increase upload body size
        client_max_body_size 25m;
    }
}

Elastic Beanstalk Extensions

Elastic Beanstalk Extensions

task beanstalkArchive(type: Zip, dependsOn: assemble) {
    from 'src/main/eb'
    from tasks.jar
}

beanstalk {
    s3Endpoint = "s3-eu-west-1.amazonaws.com"
    beanstalkEndpoint = "elasticbeanstalk.eu-west-1.amazonaws.com"

    deployments {
        gr8jar {
            file = tasks.beanstalkArchive
            application = 'dinte2018'
            environment = 'gr8jar'
        }
    }
}

Log Monitoring

Log Monitoring

Log Monitoring

What is your favorite way how to break the application?

String Constraints Test

String Constraints Test

String Constraints Test

Sentry

Sentry

Sentry

Grails Sentry Plugin

Grails Sentry Plugin

dependencies {
    compile 'org.grails.plugins:sentry:11.7.4'
}
---
environments:
    production:
        server:
          port: 5000

        grails:
            plugin:
                sentry:
                    dsn: ${SENTRY_DSN}

Grails Sentry Plugin

Grails Sentry Plugin

Grails Sentry Plugin

String Constraints Test

Sentry with Spring Security

# grails-app/conf/application.yml
---
environments:
    production:
        server:
          port: 5000
        grails:
            plugin:
                sentry:
                    dsn: ${SENTRY_DSN}
                    # enables Spring Security integration
                    springSecurityUser: true

Sentry with Spring Security

Application Performance Monitoring

NewRelic

NewRelic

NewRelic

NewRelic & Grails

Grails NewRelic Plugin

Grails NewRelic Plugin

<!doctype html>
<!-- grails-app/views/layouts/main.gsp -->
<html lang="en" class="no-js">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
        <newrelic:browserTimingHeader/>
        <!-- ... -->
    </head>
    <body>
        <!-- ... -->
        <newrelic:browserTimingFooter/>
    </body>
</html>

NewRelic & Beanstalk

NewRelic on Beanstalk

└── src
    └── main
        └── eb
            ├── .ebextensions
            │   ├── app.config
            │   ├── files
            │   │   ├── newrelic.jar
            │   │   └── newrelic.yml.sh
            │   ├── newrelic.sh
            │   └── nginx
            │       └── nginx.conf
            └── Procfile

NewRelic on Beanstalk

Grails NewRelic Plugin

NewRelic on Beanstalk

NewRelic on Beanstalk

# src/main/eb/.ebextensions/files/newrelic.yml.sh
cat << EOF
# apply defaults
common: &default_settings
  # apply custom settings
  license_key: '$NR_LICENSE'
  enable_auto_transaction_naming: false
  app_name: $NR_APPNAME
EOF

NewRelic on Beanstalk

#!/bin/sh
# src/main/eb/.ebextensions/newrelic.sh
# New Relic (Application monitoring)
mkdir /var/lib/newrelic
mv ./.ebextensions/files/newrelic*.jar /var/lib/newrelic/
bash ./.ebextensions/files/newrelic.yml.sh > /var/lib/newrelic/newrelic.yml

# New Relic deployment event
export AP_VERSION=`cat ./version`
java -Xms64m -Xmx124m -jar /var/lib/newrelic/newrelic.jar deployment --revision=$AP_VERSION
task prepareBeanstalkFiles() {
    inputs.property('version', project.version)
    outputs.file("$buildDir/beanstalk")
    doLast {
        file("$buildDir/beanstalk").mkdirs()
        file("$buildDir/beanstalk/version").text = project.version
    }
}

task beanstalkArchive(type: Zip, dependsOn: [assemble, prepareBeanstalkFiles]) {
    from 'src/main/eb'
    from "$buildDir/beanstalk" // include generated version file
    from tasks.jar
}

NewRelic on Beanstalk

# src/main/eb/.ebextensions/app.config
container_commands:
  newrelic:
    command: "bash -x .ebextensions/newrelic.sh"

NewRelic on Beanstalk

# src/main/eb/Procfile
web: java -javaagent:/var/lib/newrelic/newrelic.jar -jar dinte2018.jar
jar {
    archiveName = 'dinte2018.jar'
}

NewRelic on Beanstalk

NewRelic on Beanstalk

NewRelic on Beanstalk

NewRelic on Beanstalk

NewRelic on Beanstalk

NewRelic & Infrastructure

NewRelic Infrastructure

# src/main/eb/.ebextensions/newrelic-infra.sh

echo "license_key: $NR_LICENSE" | sudo tee -a /etc/newrelic-infra.yml
echo "display_name: `hostname`" | sudo tee -a /etc/newrelic-infra.yml

sudo curl -o /etc/yum.repos.d/newrelic-infra.repo \ 
    https://download.newrelic.com/infrastructure_agent/linux/yum/el/6/x86_64/newrelic-infra.repo
    
sudo yum -q makecache -y --disablerepo='*' --enablerepo='newrelic-infra'
sudo yum install newrelic-infra -y
# src/main/eb/.ebextensions/app.config
container_commands:
  newrelic:
    command: "bash -x .ebextensions/newrelic.sh"
  newrelic-infra:
    command: "bash -x .ebextensions/newrelic-infra.sh"

NewRelic Infrastructure

NewRelic & NGINX

NewRelic NGINX Plugin

# src/main/eb/.ebextensions/app.config
files:
  "/etc/yum.repos.d/nginx.repo":
    content: |
      [nginx]
      name=nginx repo
      baseurl=http://nginx.org/packages/rhel/6/$basearch/
      gpgcheck=0
      enabled=1

container_commands:
  newrelic:
    command: "bash -x .ebextensions/newrelic.sh"
  newrelic-infra:
      command: "bash -x .ebextensions/newrelic-infra.sh"
  nginx-nr-agent:
      command: "bash -x .ebextensions/nginx-nr-agent.sh"

NewRelic NGINX Plugin

# src/main/eb/.ebextensions/nginx-nr-agent.sh

sudo yum install nginx-nr-agent -y
bash ./.ebextensions/files/nginx-nr-agent.ini.sh > /etc/nginx-nr-agent/nginx-nr-agent.ini
sudo service nginx-nr-agent start

NewRelic NGINX Plugin

# src/main/eb/.ebextensions/files/nginx-nr-agent.ini.sh

cat << EOF
# global settings
[global]
newrelic_license_key=$NR_LICENSE
poll_interval=60
# logging settings
[loggers]
keys=root
[handlers]
keys=consoleHandler,fileHandler
[formatters]
keys=simpleFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler,fileHandler
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
[handler_fileHandler]
class=FileHandler
level=DEBUG
formatter=simpleFormatter
args=('/var/log/nginx-nr-agent.log','a',)
[formatter_simpleFormatter]
format=%(asctime)s %(name)s [%(levelname)s]: %(message)s
datefmt=
# data sources settings
[$NR_APPNAME]
name=$NR_APPNAME
url=http://127.0.0.1/nginx_status
EOF

NewRelic NGINX Plugin

# src/main/eb/.ebextensions/nginx/nginx.conf

# ...
    server {
        # ...

        client_max_body_size 25m;

        location /nginx_status {
            stub_status on;

            access_log off;
            allow 127.0.0.1;
            deny all;
        }
    }
# ...

NewRelic NGINX Plugin

NewRelic & Metrics

Dropwizard Metrics Plugin

Dropwizard Metrics Plugin

dependencies {
    // ...

    compile 'org.grails.plugins:newrelic:4.0.1'

    compile 'com.palominolabs.metrics:metrics-new-relic:1.0.5'
    compile 'org.grails.plugins:dropwizard-metrics:1.0.0.M3'
}

Dropwizard Metrics Plugin

// grails-app/init/dinte2018/BootStrap.groovy

package dinte2018

import com.codahale.metrics.MetricFilter
import com.codahale.metrics.MetricRegistry
import com.palominolabs.metrics.newrelic.AllEnabledMetricAttributeFilter
import com.palominolabs.metrics.newrelic.NewRelicReporter

import java.util.concurrent.TimeUnit

class BootStrap {

    MetricRegistry metricRegistry

    def init = { servletContext ->    
        new NewRelicReporter(
                metricRegistry,
                'Deployment is not the End Metrics',
                MetricFilter.ALL,
                new AllEnabledMetricAttributeFilter(),
                TimeUnit.SECONDS,
                TimeUnit.MILLISECONDS,
                'dinte2018.'
        ).start(1, TimeUnit.MINUTES)
    }

    def destroy = { }
}

Dropwizard Metrics Plugin

// grails-app/services/dinte2018/UploadRestaurantFeaturedImageService.groovy

package dinte2018

import grails.plugin.dropwizard.metrics.meters.Metered

class UploadRestaurantFeaturedImageService {

    def restaurantGormService

    @Metered(value='uploadFeatureImage', useClassPrefix = true)
    Restaurant uploadFeatureImage(FeaturedImageCommand cmd) {
        byte[] bytes = cmd.featuredImageFile.bytes
        String contentType = cmd.featuredImageFile.contentType
        restaurantGormService.updateRestaurantFeaturedImage(
            cmd.id, 
            cmd.version, 
            bytes, 
            contentType
        )
    }
}

Dropwizard Metrics Plugin

Dropwizard Metrics Plugin

Dropwizard Metrics Plugin

Dropwizard Metrics Plugin

Dropwizard Metrics Plugin

On recrute!

Deployment is not the End

By musketyr

Deployment is not the End

  • 2,578