Sign up for free to use this document yourself.
  • Automation strategies 
for deploying Grails 
from Dev to Prod

    Eric Helgeson

    @nulleric

  • Who am I?

  • About this Talk

  • Everything as code

  • Building for Production

  • Infrastructure Testing

    You wouldn’t make an app change without testing it.

  • Operating in Production

  • Summary

    • Everything is code (lint-able, testable, shareable)
    • Keep adding automation as you go
    • Be more confident in changes to app and infra
    • Deliver software more quickly
  • Agenda

    • Everything as Code
    • Building for Production
    • Infrastructure testing
    • Operating in production
  • Information share

    • Give you some new ideas to try
      • Incremental & Continuous improvements
    • Hear ideas & experiences from you
  • Poll - Show of hands

  • Building

  • Jars are our containers

  • Configuration

  • Sessions

  • Logger config

  • Database Changes

  • Lint & Unit

    • Fail your CI pipelines early before you spin up a VM
    • ShellCheck && BATS (if you still love bash)
    • Foodcritic/Rubocop
    • Chef Spec
  • Integration

    (You’re already doing this, codify it!)

    $ ps aux | grep grails-app

    vs.

    describe service('grails-app') do
      it { should be_running }
    end
  • serverspec

    Can run inside of your Chef run
  • We love Groovy though!

    • Just a DSL
    • Spock + sshoogar
    • sshoogar + gradle
    • Runnnable jar

    Tons of options, use tools you’re comfortable with and get value from.

  • Build a test anytime deploy fails

    • Service X didn’t start? Test it.
  • Other ideas on what to test

    describe service('nginx') do
      it { should be_enabled }
      it { should be_running }
    end
    
    describe port(80) do
      it { should be_listening }
    end
    
    describe command("/usr/bin/nginx -t") do
      it "nginx syntax ok" do
        expect(subject.exit_status).to eq 0
      end
    end
    
    describe file('/opt/grailsapp/config.groovy') do
      it { should be_file }
      it { should be_owned_by 'grailsapp' }
    end
  • Centralized Logging

  • Centralized Stats

    • Allow developers and operations to add metrics easily (statsd mentality)
    • Measure everything, graph many, alert actionable
  • Sentry Exception Logging

    • Log all log.error and exceptions w/ request
    • Send them to Slack, create GH issues
    • Ties into Grails with grails-sentry plugin
    • Plugins for everything
      (log4j, logback, js frontend, ruby, python, …)
  • Logs vs. Metrics

    • Logs have lots of detail but must parse out stats
    • Metrics reduce noise, but less detail
    • Have both - cut down logs where metrics suffice
  • Things you forget to monitor

    • dns
    • https expire
  • Deployment

  • Stop writing bash scripts

  • Continuously Build (Push code, it builds)

  • Continuously Deploy (master == prod)

  • Infrastructure improvements are something:

    • Everyone on the team helps with
    • Other people’s jobs
  • Codify your build

    • Lots of options!
  • Code reviews as code

  • Dev & Test as code

  • Create single executable artifact

    • ./gradlew assemble
    • java -jar myapp.jar
  • External Config

    • External config plugin
      • Render .groovy file from Config Management
    • Use K/V store such as Consul, etc, spring-cloud, zk
    • Inject ENV vars
      environments {
      production {
        dataSource {
            dbCreate = "none"
            driverClassName = "org.postgresql.Driver"
            dialect = "org.hibernate.dialect.PostgreSQL9Dialect"
            url = "jdbc:postgresql://${System.getenv('DB_HOST')}:${System.getenv('DB_PORT')}/${System.getenv('DB_NAME')}"
            username = "${System.getenv('DB_USER')}"
            password = “${System.getenv(‘DB_PASS')}"
      ...
        }
      }
      }
  • Secrets as Code

  • Database Migrations Plugin

    • d-b-m - helpful tool, but review all changes.
  • Flyway

    • Alternative to d-b-m
    • Uses version sql
    • Can easily run standalone
  • Backwards compatible DB schema

    • Add new
    • Use new
    • Remove old

    • Utilize GORM getter/setters

      String getPhoneNumber() {
      phoneNumbers.first()
      }
  • Demo

    db/dbm
    db/flyway

    • Match your lower envs

      • Docker/Vagrant/SaaS - easy
      • Others - …
    • Blue/Green

      • Deploy a new instance
      • Verify instance can serve traffic
      • Switch to new instance
    • Chat Ops

      • Keep team in the know when deployments are happening
      • Who started the deploy workflow, when
      • /commands for on stats
      • 0 Exceptions in production
    • Why?

      • Hard to test
      • Less known on team
      • Obtuse syntax for complex
      • JSON? ha
    • Groovy CLI Tasks

      • Good CLI arg parsers
      • Very testable
      • Understood by entire team
      • Can parse JSON
    • Demo

      • Simple Trait based framework with tests
      • Shows common tasks & tests for them

      g3summit/admin-script

    • Takeaway

      • Use Groovy where it makes sense/adds value
      • Use bash when it’s easier
        • psql, etc
    • SaaS

      • Easy to setup & use
      • Doesn’t fit everyone’s needs
      • Travis, CircleCi
    • Jenkins (of course)

    • Building a workflow

    • Things to stop manually reviewing

      • Indentation
      • Spaces vs. Tabs
      • Unimportant things!

      Things to review

      • Functionality
    • Linters

      • CodeNarc
        • eg: java.util.Date == bad? - make a rule!
    • Build steps

      Abstract specifics

      Good ./gradlew test integrationTest jaco assemble
      Better ./gradlew assemble
      Best ./build.sh - Builds change - allow devs to change build steps on their branch.

    • Artifacts

    • Allow devs to deploy a prod like env

      • locally (Vagrant/Docker/etc)
      • On demand SaaS (EB/AWS/etc)
      • Document what is different
        • 1 hadoop node vs 3, etc
      • Living datasets, easy access
    • Make it a service

      Grails can build an fully executable jar

      $ ln -s /var/myapp/myapp.jar /etc/init.d/myapp
      $ service myapp status
    • Why?

      • Isolate JVM (class loader, etc)
      • Single artifact
      • Bundle different containers for different apps
      • Can be executable!
      • Deployment
        • Easy (s)cp && restart
        • Blue/Green
        • Hot deploys never work
    • Demo!

      executable

      Grails notes

    • Demo!

      logging/logapi

      • Requires
        • external session-store
        • configurable load balancer
      • Warm up your new env
        • JVMs can serve the first few req slowly
      • ChatDev?

        • Keep development flow going, push notifications when action required.
        • Notify dev in Slack when:
          • Build fails
          • Build passes
            • Ping you in slack with deployment options
          • Someone assigns you a ticket/bug/review
          • Take availability to your own hands

            • Can you deploy if maven central/grails repo is down?
          • Demo!

            Lets setup an artifactory server.

          • Versioning

            • Version based off git branch & short-sha

            Demo!

            info/info

          • Demo!

            Vagrant

          {"cards":[{"_id":"7ba7c77ab13d5752b6000032","treeId":"7ba7c788b13d5752b6000030","seq":11760632,"position":1,"parentId":null,"content":"## Automation strategies 
for deploying Grails 
from Dev to Prod\n### Eric Helgeson\n#### @nulleric"},{"_id":"5a2155cf47ebd2c179f65b7d","treeId":"7ba7c788b13d5752b6000030","seq":11754512,"position":0.625,"parentId":"7ba7c77ab13d5752b6000032","content":"### Who am I?"},{"_id":"716ebda9a35bbfb46400014d","treeId":"7ba7c788b13d5752b6000030","seq":13693283,"position":0.5,"parentId":"5a2155cf47ebd2c179f65b7d","content":"### Eric Helgeson\n\nerichelgeson@gmail.com\n\n@nulleric"},{"_id":"5a2155cf47ebd2c179f65b7e","treeId":"7ba7c788b13d5752b6000030","seq":11754503,"position":1,"parentId":"5a2155cf47ebd2c179f65b7d","content":"### Grails Consulting at Agile Orbit \n\nhttp://www.agileorbit.com\n"},{"_id":"5a2155cf47ebd2c179f65b80","treeId":"7ba7c788b13d5752b6000030","seq":13707386,"position":1.5,"parentId":"5a2155cf47ebd2c179f65b7d","content":"### Sproutary.com\nOnline software for Pre-Schools and Day Cares.\nhttps://sproutary.com"},{"_id":"5a2155cf47ebd2c179f65b7f","treeId":"7ba7c788b13d5752b6000030","seq":11754504,"position":2,"parentId":"5a2155cf47ebd2c179f65b7d","content":"### Practical Grails 3\n![](https://www.filepicker.io/api/file/oyIbKAfTbKRRQNkHizOg)\nhttps://www.grails3book.com"},{"_id":"715c3f98d16303104200007c","treeId":"7ba7c788b13d5752b6000030","seq":13707407,"position":3.5,"parentId":"5a2155cf47ebd2c179f65b7d","content":"### Join Groovy/Grails Slack\n\n* https://groovycommunity.com/\n* https://grails-slack.cfapps.io/"},{"_id":"7b25a8f079dae267b4000133","treeId":"7ba7c788b13d5752b6000030","seq":11683857,"position":0.75,"parentId":"7ba7c77ab13d5752b6000032","content":"### About this Talk"},{"_id":"7b25a17a385426df94000054","treeId":"7ba7c788b13d5752b6000030","seq":11686382,"position":1,"parentId":"7b25a8f079dae267b4000133","content":"### Agenda\n* Everything as Code\n* Building for Production\n* Infrastructure testing\n* Operating in production"},{"_id":"7ab1ef39c1281a64cf00019d","treeId":"7ba7c788b13d5752b6000030","seq":11757151,"position":2,"parentId":"7b25a8f079dae267b4000133","content":"### Information share\n* Give you some new ideas to try\n - Incremental & Continuous improvements\n* Hear ideas & experiences from you"},{"_id":"7ab1ec52c1281a64cf00019e","treeId":"7ba7c788b13d5752b6000030","seq":11755625,"position":3,"parentId":"7b25a8f079dae267b4000133","content":"### Poll - Show of hands\n\n"},{"_id":"7ab1e91bc1281a64cf00019f","treeId":"7ba7c788b13d5752b6000030","seq":11755626,"position":1,"parentId":"7ab1ec52c1281a64cf00019e","content":"Continuously Build (Push code, it builds)\n"},{"_id":"7ab1e8bdc1281a64cf0001a0","treeId":"7ba7c788b13d5752b6000030","seq":11756863,"position":2,"parentId":"7ab1ec52c1281a64cf00019e","content":"Continuously Deploy (`master == prod`)\n"},{"_id":"7ab1e87bc1281a64cf0001a1","treeId":"7ba7c788b13d5752b6000030","seq":11755630,"position":3,"parentId":"7ab1ec52c1281a64cf00019e","content":"Infrastructure improvements are something:\n - Everyone on the team helps with\n - Other people's jobs"},{"_id":"7ba7c64fb13d5752b6000036","treeId":"7ba7c788b13d5752b6000030","seq":11683856,"position":2,"parentId":"7ba7c77ab13d5752b6000032","content":"### Everything as code"},{"_id":"7b682c397652e4d8b5000012","treeId":"7ba7c788b13d5752b6000030","seq":11755615,"position":0.5,"parentId":"7ba7c64fb13d5752b6000036","content":"### Building"},{"_id":"7b682b3f7652e4d8b5000013","treeId":"7ba7c788b13d5752b6000030","seq":11686384,"position":2,"parentId":"7b682c397652e4d8b5000012","content":"### Codify your build\n* Lots of options!"},{"_id":"7b6829b17652e4d8b5000016","treeId":"7ba7c788b13d5752b6000030","seq":11756877,"position":2,"parentId":"7b682b3f7652e4d8b5000013","content":"### SaaS\n* Easy to setup & use\n* Doesn't fit everyone's needs\n* Travis, CircleCi"},{"_id":"7b6829677652e4d8b5000017","treeId":"7ba7c788b13d5752b6000030","seq":11757243,"position":1,"parentId":"7b6829b17652e4d8b5000016","content":"### Demo!\n[Travis](https://github.com/GR8conf/website/blob/master/.travis.yml)\n\n\n\n"},{"_id":"7b6826c37652e4d8b5000019","treeId":"7ba7c788b13d5752b6000030","seq":11686396,"position":3,"parentId":"7b682b3f7652e4d8b5000013","content":"### Jenkins (of course)"},{"_id":"7b6826797652e4d8b500001a","treeId":"7ba7c788b13d5752b6000030","seq":11757245,"position":1,"parentId":"7b6826c37652e4d8b5000019","content":"### Jenkins\n* JenkinsJob file\n - Similar to a .travis.yml \n* JenkinsJob DSL\n - https://job-dsl.herokuapp.com/"},{"_id":"7b6825007652e4d8b500001d","treeId":"7ba7c788b13d5752b6000030","seq":11756899,"position":4,"parentId":"7b682b3f7652e4d8b5000013","content":"### Building a workflow"},{"_id":"7b6824907652e4d8b500001e","treeId":"7ba7c788b13d5752b6000030","seq":11686342,"position":1,"parentId":"7b6825007652e4d8b500001d","content":"### ChatDev?\n\n* Keep development flow going, push notifications when action required."},{"_id":"7b6823c27652e4d8b500001f","treeId":"7ba7c788b13d5752b6000030","seq":11686323,"position":2,"parentId":"7b6825007652e4d8b500001d","content":"* Notify dev in Slack when:\n - Build fails\n - Build passes\n - Ping you in slack with deployment options \n - Someone assigns you a ticket/bug/review"},{"_id":"7ab0de0dc1281a64cf0001a2","treeId":"7ba7c788b13d5752b6000030","seq":11756907,"position":3,"parentId":"7b6825007652e4d8b500001d","content":"![](https://www.filepicker.io/api/file/Kbf0Kki2QnyezGwyH2xQ)"},{"_id":"7ab0dafbc1281a64cf0001a3","treeId":"7ba7c788b13d5752b6000030","seq":11756910,"position":4,"parentId":"7b6825007652e4d8b500001d","content":"![](https://www.filepicker.io/api/file/wO8KwVb2R7GLOu7CsZNk)"},{"_id":"7ab0da67c1281a64cf0001a4","treeId":"7ba7c788b13d5752b6000030","seq":11756913,"position":5,"parentId":"7b6825007652e4d8b500001d","content":"![](https://www.filepicker.io/api/file/7te3XG8wRX2lMafVFaxO)"},{"_id":"7ab0d9fbc1281a64cf0001a5","treeId":"7ba7c788b13d5752b6000030","seq":11756917,"position":6,"parentId":"7b6825007652e4d8b500001d","content":"![](https://www.filepicker.io/api/file/bBjGF0jJSdeNMq57Y9QL)"},{"_id":"7b68307b8fb5a652f10000e0","treeId":"7ba7c788b13d5752b6000030","seq":11755119,"position":3,"parentId":"7b682c397652e4d8b5000012","content":"### Code reviews as code"},{"_id":"7b6830428fb5a652f10000e1","treeId":"7ba7c788b13d5752b6000030","seq":11756922,"position":1,"parentId":"7b68307b8fb5a652f10000e0","content":"### Things to stop manually reviewing\n* Indentation\n* Spaces vs. Tabs\n* Unimportant things!\n\nThings to review\n* Functionality"},{"_id":"7b682dbf7652e4d8b5000011","treeId":"7ba7c788b13d5752b6000030","seq":11756930,"position":3,"parentId":"7b68307b8fb5a652f10000e0","content":"### Linters\n* CodeNarc\n - eg: java.util.Date == bad? - make a rule!"},{"_id":"7b681c0b7652e4d8b5000021","treeId":"7ba7c788b13d5752b6000030","seq":11755468,"position":5,"parentId":"7b68307b8fb5a652f10000e0","content":"### Build steps\n#### Abstract specifics\nGood `./gradlew test integrationTest jaco assemble`\nBetter `./gradlew assemble`\nBest `./build.sh` - Builds change - allow devs to change build steps on their branch."},{"_id":"7b680ea97652e4d8b5000027","treeId":"7ba7c788b13d5752b6000030","seq":11755131,"position":6,"parentId":"7b68307b8fb5a652f10000e0","content":"## Artifacts"},{"_id":"7b680e487652e4d8b5000028","treeId":"7ba7c788b13d5752b6000030","seq":11755158,"position":1,"parentId":"7b680ea97652e4d8b5000027","content":"### Take availability to your own hands\n* Can you deploy if maven central/grails repo is down?"},{"_id":"7b680c617652e4d8b500002a","treeId":"7ba7c788b13d5752b6000030","seq":11755482,"position":1.5,"parentId":"7b680ea97652e4d8b5000027","content":"### Demo!\nLets setup an artifactory server."},{"_id":"7ab27566c1281a64cf000197","treeId":"7ba7c788b13d5752b6000030","seq":11755484,"position":2.5,"parentId":"7b680ea97652e4d8b5000027","content":"### Versioning\n* Version based off git branch & short-sha\n\n### Demo! \n`info/info`"},{"_id":"7b6811a37652e4d8b5000023","treeId":"7ba7c788b13d5752b6000030","seq":11682528,"position":4,"parentId":"7b682c397652e4d8b5000012","content":"### Dev & Test as code"},{"_id":"7b68114e7652e4d8b5000024","treeId":"7ba7c788b13d5752b6000030","seq":11756893,"position":1,"parentId":"7b6811a37652e4d8b5000023","content":"### Allow devs to deploy a prod like env\n- locally (Vagrant/Docker/etc)\n- On demand SaaS (EB/AWS/etc)\n- Document what is different\n - 1 hadoop node vs 3, etc\n- Living datasets, easy access"},{"_id":"7b680f717652e4d8b5000025","treeId":"7ba7c788b13d5752b6000030","seq":11756884,"position":1,"parentId":"7b68114e7652e4d8b5000024","content":"### Demo!\n\nVagrant"},{"_id":"7b274e4379dae267b400010f","treeId":"7ba7c788b13d5752b6000030","seq":11683855,"position":3,"parentId":"7ba7c77ab13d5752b6000032","content":"### Building for Production"},{"_id":"7b274db079dae267b4000110","treeId":"7ba7c788b13d5752b6000030","seq":11755497,"position":1,"parentId":"7b274e4379dae267b400010f","content":"### Jars are our containers"},{"_id":"7b274b4779dae267b4000111","treeId":"7ba7c788b13d5752b6000030","seq":11755496,"position":1,"parentId":"7b274db079dae267b4000110","content":"### Create single executable artifact\n\n* `./gradlew assemble`\n* `java -jar myapp.jar`"},{"_id":"7153c29f111635f287000153","treeId":"7ba7c788b13d5752b6000030","seq":13712096,"position":0.5,"parentId":"7b274b4779dae267b4000111","content":"### Make it a service\n\nGrails can build an fully executable jar\n\n```\n$ ln -s /var/myapp/myapp.jar /etc/init.d/myapp\n$ service myapp status\n```\n"},{"_id":"7b274b1479dae267b4000112","treeId":"7ba7c788b13d5752b6000030","seq":11757168,"position":1,"parentId":"7b274b4779dae267b4000111","content":"### Why?\n\n* Isolate JVM (class loader, etc)\n* Single artifact\n* Bundle different containers for different apps\n* Can be [executable](https://docs.spring.io/spring-boot/docs/current/reference/html/deployment-install.html)!\n* Deployment\n - Easy (s)cp && restart\n - Blue/Green\n - Hot deploys never work"},{"_id":"7ab0846dc1281a64cf0001a6","treeId":"7ba7c788b13d5752b6000030","seq":11757211,"position":2,"parentId":"7b274b4779dae267b4000111","content":"### Demo!\n`executable`\n\n[Grails notes](http://mrhaki.blogspot.com/2016/06/grails-goodness-creating-fully.html)"},{"_id":"7b27475279dae267b4000113","treeId":"7ba7c788b13d5752b6000030","seq":11754521,"position":2,"parentId":"7b274e4379dae267b400010f","content":"### Configuration"},{"_id":"7b27470e79dae267b4000114","treeId":"7ba7c788b13d5752b6000030","seq":11682523,"position":1,"parentId":"7b27475279dae267b4000113","content":"### External Config\n* External config plugin\n - Render `.groovy` file from Config Management\n* Use K/V store such as Consul, etc, spring-cloud, zk\n* Inject ENV vars\n```\nenvironments {\n production {\n dataSource {\n dbCreate = \"none\"\n driverClassName = \"org.postgresql.Driver\"\n dialect = \"org.hibernate.dialect.PostgreSQL9Dialect\"\n url = \"jdbc:postgresql://${System.getenv('DB_HOST')}:${System.getenv('DB_PORT')}/${System.getenv('DB_NAME')}\"\n username = \"${System.getenv('DB_USER')}\"\n password = “${System.getenv(‘DB_PASS')}\"\n ...\n }\n }\n}\n```"},{"_id":"7b273c4a79dae267b4000115","treeId":"7ba7c788b13d5752b6000030","seq":11682522,"position":2,"parentId":"7b27475279dae267b4000113","content":"### Secrets as Code\n* https://square.github.io/keywhiz/ \n - Java, familiar, uses FuseFS to present secrets\n* https://vaultproject.io/\n - Go, API driven, ties in with other HashiCorp tools\n* Amazon KMS/IAM Profiles/etc"},{"_id":"7b27390779dae267b4000116","treeId":"7ba7c788b13d5752b6000030","seq":11754534,"position":3,"parentId":"7b274e4379dae267b400010f","content":"### Sessions"},{"_id":"7b2737f979dae267b4000118","treeId":"7ba7c788b13d5752b6000030","seq":11754527,"position":0.25,"parentId":"7b27390779dae267b4000116","content":"### Move sessions out of container \n\n* Memcached Session Manager \n - https://github.com/erichelgeson/grails3-memcached-session
\n* `spring-session`\n - Redis, JDBC, Hazelcast, etc\n* Cookie Session\n\n* Zero Downtime Deployment much easier\n"},{"_id":"7b272b6179dae267b400011f","treeId":"7ba7c788b13d5752b6000030","seq":11754532,"position":0.5,"parentId":"7b27390779dae267b4000116","content":"### Demo!\n`session/springsession`"},{"_id":"7b27341679dae267b4000119","treeId":"7ba7c788b13d5752b6000030","seq":11755613,"position":4,"parentId":"7b274e4379dae267b400010f","content":"### Logger config"},{"_id":"7b27335479dae267b400011a","treeId":"7ba7c788b13d5752b6000030","seq":11682544,"position":1,"parentId":"7b27341679dae267b4000119","content":"### logback\n\nAllow changes to config to be picked up\n* `scan('30 seconds')`"},{"_id":"7b27322f79dae267b400011b","treeId":"7ba7c788b13d5752b6000030","seq":11759026,"position":2,"parentId":"7b27341679dae267b4000119","content":"### log actuator\n\n* Rest API to change logging config\n - Grails 3.3.x\n\nDetails: https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-endpoints.html"},{"_id":"7ab29cd9c1281a64cf000196","treeId":"7ba7c788b13d5752b6000030","seq":11755084,"position":1,"parentId":"7b27322f79dae267b400011b","content":"### Demo!\n`logging/logapi`"},{"_id":"7b2730b679dae267b400011c","treeId":"7ba7c788b13d5752b6000030","seq":11755614,"position":5,"parentId":"7b274e4379dae267b400010f","content":"### Database Changes"},{"_id":"7b27306179dae267b400011d","treeId":"7ba7c788b13d5752b6000030","seq":11683707,"position":1,"parentId":"7b2730b679dae267b400011c","content":"### Database Migrations Plugin\n\n* d-b-m - helpful tool, but review all changes."},{"_id":"7b263b0479dae267b4000130","treeId":"7ba7c788b13d5752b6000030","seq":11683709,"position":1.5,"parentId":"7b2730b679dae267b400011c","content":"### Flyway\n* Alternative to d-b-m\n* Uses version sql\n* Can easily run standalone"},{"_id":"7b272f5f79dae267b400011e","treeId":"7ba7c788b13d5752b6000030","seq":11683706,"position":2,"parentId":"7b2730b679dae267b400011c","content":"### Backwards compatible DB schema\n* Add new\n* Use new\n* Remove old\n\n* Utilize GORM getter/setters\n```\nString getPhoneNumber() {\n phoneNumbers.first()\n}\n```"},{"_id":"7ab2197dc1281a64cf000198","treeId":"7ba7c788b13d5752b6000030","seq":11755501,"position":3,"parentId":"7b2730b679dae267b400011c","content":"### Demo\n`db/dbm`\n`db/flyway`"},{"_id":"7b2729bc79dae267b4000120","treeId":"7ba7c788b13d5752b6000030","seq":11683851,"position":4,"parentId":"7ba7c77ab13d5752b6000032","content":"### Infrastructure Testing\nYou wouldn't make an app change without testing it."},{"_id":"7b27289579dae267b4000121","treeId":"7ba7c788b13d5752b6000030","seq":11682576,"position":1,"parentId":"7b2729bc79dae267b4000120","content":"### Lint & Unit \n\n* Fail your CI pipelines early before you spin up a VM\n* ShellCheck && BATS (if you still love bash)\n* Foodcritic/Rubocop\n* Chef Spec"},{"_id":"7b27242479dae267b4000122","treeId":"7ba7c788b13d5752b6000030","seq":11682578,"position":2,"parentId":"7b2729bc79dae267b4000120","content":"### Integration\n#### (You're already doing this, codify it!)\n\n```\n$ ps aux | grep grails-app\n```\n\nvs.\n\n```\ndescribe service('grails-app') do\n it { should be_running }\nend\n```"},{"_id":"7b27221c79dae267b4000123","treeId":"7ba7c788b13d5752b6000030","seq":11682587,"position":3,"parentId":"7b2729bc79dae267b4000120","content":"### serverspec\n\n* Ruby DSL\n* Can be run over ssh\n - http://serverspec.org/resource_types.html\n\n##### Can run inside of your Chef run"},{"_id":"7b27209279dae267b4000124","treeId":"7ba7c788b13d5752b6000030","seq":11682604,"position":4,"parentId":"7b2729bc79dae267b4000120","content":"### We love Groovy though!\n\n* Just a DSL\n* Spock + sshoogar\n* sshoogar + gradle\n* Runnnable jar\n\nTons of options, use tools you're comfortable with and get value from."},{"_id":"7b271bba79dae267b4000125","treeId":"7ba7c788b13d5752b6000030","seq":11682592,"position":5,"parentId":"7b2729bc79dae267b4000120","content":"### Build a test anytime deploy fails\n\n* Service X didn't start? Test it."},{"_id":"7b2711bf79dae267b4000126","treeId":"7ba7c788b13d5752b6000030","seq":11682606,"position":6,"parentId":"7b2729bc79dae267b4000120","content":"### Other ideas on what to test\n```\ndescribe service('nginx') do\n it { should be_enabled }\n it { should be_running }\nend\n\ndescribe port(80) do\n it { should be_listening }\nend\n\ndescribe command(\"/usr/bin/nginx -t\") do\n it \"nginx syntax ok\" do\n expect(subject.exit_status).to eq 0\n end\nend\n\ndescribe file('/opt/grailsapp/config.groovy') do\n it { should be_file }\n it { should be_owned_by 'grailsapp' }\nend```"},{"_id":"7b27106279dae267b4000127","treeId":"7ba7c788b13d5752b6000030","seq":11683852,"position":5,"parentId":"7ba7c77ab13d5752b6000032","content":"### Operating in Production"},{"_id":"7b270ff379dae267b4000128","treeId":"7ba7c788b13d5752b6000030","seq":11682621,"position":1,"parentId":"7b27106279dae267b4000127","content":"### Centralized Logging\n\n* Centralized view of what is going on\n* Tons of tools (Splunk, ELK, paper trail, etc)\n - https://github.com/erichelgeson/simple-elk-docker"},{"_id":"7ab217ffc1281a64cf000199","treeId":"7ba7c788b13d5752b6000030","seq":11755526,"position":1,"parentId":"7b270ff379dae267b4000128","content":"### Demo!\n\nSaaS - [papertrail](https://papertrailapp.com/groups/6961142/events?q=program%3Ajava%20AuthenticationFailure)"},{"_id":"7b27038a79dae267b4000129","treeId":"7ba7c788b13d5752b6000030","seq":11682630,"position":2,"parentId":"7b27106279dae267b4000127","content":"### Centralized Stats\n* Allow developers and operations to add metrics easily (statsd mentality)\n* Measure everything, graph many, alert actionable"},{"_id":"7b2701e379dae267b400012a","treeId":"7ba7c788b13d5752b6000030","seq":11682627,"position":1,"parentId":"7b27038a79dae267b4000129","content":"### Influxdb + Grafana + collectd\n\n* statsd/graphite/jmx/* adapters\n* Add/remove stats in code easily and graph them\n* As Code\n - https://github.com/JonathanTron/chef-grafana\n - https://github.com/SimpleFinance/chef-influxdb "},{"_id":"7ab212b8c1281a64cf00019a","treeId":"7ba7c788b13d5752b6000030","seq":11755533,"position":2,"parentId":"7b27038a79dae267b4000129","content":"### Demo!\n\n[DataDog](https://app.datadoghq.com/dash/58806/sproutary-prod?live=true&page=0&is_auto=false&from_ts=1511533150340&to_ts=1512137950340&tile_size=s)"},{"_id":"7b27008579dae267b400012b","treeId":"7ba7c788b13d5752b6000030","seq":11682631,"position":3,"parentId":"7b27106279dae267b4000127","content":"### Sentry Exception Logging\n\n* Log all `log.error` and exceptions w/ request\n* Send them to Slack, create GH issues\n* Ties into Grails with `grails-sentry` plugin \n* Plugins for everything\n(log4j, logback, js frontend, ruby, python, ...)"},{"_id":"7b26fbaa79dae267b400012c","treeId":"7ba7c788b13d5752b6000030","seq":11755543,"position":1,"parentId":"7b27008579dae267b400012b","content":"### Demo\n\n[sentry.io](https://sentry.io/sproutary/sproutary-grails/issues/400322059/)\n"},{"_id":"7b26fb2479dae267b400012d","treeId":"7ba7c788b13d5752b6000030","seq":11683843,"position":4,"parentId":"7b27106279dae267b4000127","content":"### Logs vs. Metrics\n\n* Logs have lots of detail but must parse out stats\n* Metrics reduce noise, but less detail\n* Have both - cut down logs where metrics suffice"},{"_id":"7ba7c70fb13d5752b6000033","treeId":"7ba7c788b13d5752b6000030","seq":11755547,"position":4.5,"parentId":"7b27106279dae267b4000127","content":"### Things you forget to monitor\n* dns\n* https expire"},{"_id":"7b263c5b79dae267b400012f","treeId":"7ba7c788b13d5752b6000030","seq":11685688,"position":6,"parentId":"7b27106279dae267b4000127","content":"### Deployment"},{"_id":"7b243784385426df9400005a","treeId":"7ba7c788b13d5752b6000030","seq":11757252,"position":1,"parentId":"7b263c5b79dae267b400012f","content":"### Match your lower envs\n\n* Docker/Vagrant/SaaS - easy\n* Others - ..."},{"_id":"7b243496385426df9400005b","treeId":"7ba7c788b13d5752b6000030","seq":11686307,"position":2,"parentId":"7b263c5b79dae267b400012f","content":"### Blue/Green\n* Deploy a new instance\n* Verify instance can serve traffic\n* Switch to new instance"},{"_id":"7b24329d385426df9400005c","treeId":"7ba7c788b13d5752b6000030","seq":11686355,"position":1,"parentId":"7b243496385426df9400005b","content":"* Requires \n - external session-store\n - configurable load balancer\n* Warm up your new env \n - JVMs can serve the first few req slowly"},{"_id":"7b242a4f385426df9400005d","treeId":"7ba7c788b13d5752b6000030","seq":11755605,"position":3,"parentId":"7b263c5b79dae267b400012f","content":"### Chat Ops\n\n* Keep team in the know when deployments are happening\n* Who started the deploy workflow, when\n* `/commands` for on stats\n* `0` Exceptions in production\n"},{"_id":"7b244375385426df94000055","treeId":"7ba7c788b13d5752b6000030","seq":11685744,"position":7,"parentId":"7b27106279dae267b4000127","content":"### Stop writing bash scripts"},{"_id":"7b244325385426df94000056","treeId":"7ba7c788b13d5752b6000030","seq":11685691,"position":1,"parentId":"7b244375385426df94000055","content":"### Why?\n* Hard to test\n* Less known on team\n* Obtuse syntax for complex\n* JSON? ha"},{"_id":"7b24408a385426df94000057","treeId":"7ba7c788b13d5752b6000030","seq":11685742,"position":2,"parentId":"7b244375385426df94000055","content":"### Groovy CLI Tasks\n* Good CLI arg parsers\n* Very testable\n* Understood by entire team\n* Can parse JSON"},{"_id":"7b243dd5385426df94000058","treeId":"7ba7c788b13d5752b6000030","seq":11755606,"position":3,"parentId":"7b244375385426df94000055","content":"### Demo\n* Simple Trait based framework with tests\n* Shows common tasks & tests for them\n\n`g3summit/admin-script`"},{"_id":"7b243d63385426df94000059","treeId":"7ba7c788b13d5752b6000030","seq":11686353,"position":4,"parentId":"7b244375385426df94000055","content":"### Takeaway\n* Use Groovy where it makes sense/adds value\n* Use bash when it's easier\n - `psql`, etc"},{"_id":"7b26041b79dae267b4000131","treeId":"7ba7c788b13d5752b6000030","seq":11756961,"position":6,"parentId":"7ba7c77ab13d5752b6000032","content":"### Summary\n\n* Everything is code (lint-able, testable, shareable) \n* Keep adding automation as you go\n* Be more confident in changes to app and infra\n* Deliver software more quickly"}],"tree":{"_id":"7ba7c788b13d5752b6000030","name":"Automation strategies 
for deploying Grails 
from Dev to Prod","publicUrl":"automation-strategies-for-deploying-grails-from-dev-to-prod"}}