$30 off During Our Annual Pro Sale. View Details »

Web application deployments with Gradle - From the developer's machine to Continuous Deployment

Web application deployments with Gradle - From the developer's machine to Continuous Deployment

Web applications live and breathe in their runtime environment, the web container. But what is the best way to install the relevant artifacts during development or as part of your deployment pipeline? In this talk, we will discuss the techniques and tooling required to implement efficient, flexible, and most importantly, automated deployment strategies to local and remote web containers with Gradle.

Starting with the developer's machine, we will look at rapid software development techniques to embedded and standalone containers. Remote containers pose more complex requirements to the deployment process; to mention just a few: explicit container lifecycle control (like starting and stopping the container), rolling or parallel deployments, writing and executing deployment tests to verify the application's operability. Gradle can help you tackle these challenges in a pragmatic way. This session mainly focusses on JVM-based applications and runtime environments.

Benjamin Muschko

June 13, 2014
Tweet

More Decks by Benjamin Muschko

Other Decks in Programming

Transcript

  1. Web application
    deployments with Gradle
    From the developer's machine to Continuous Deployment
    Benjamin Muschko
    Principal Engineer, Gradleware
    @bmuschko

    View Slide

  2. Java Build Tools – Part 2: A Decision Maker’s
    Comparison of Maven, Gradle and Ant + Ivy
    "However, as Gradle is still quite new, it does not have such a
    wide variety of application server integrations as Maven or Ant."
    January 2014

    View Slide

  3. Is

    View Slide

  4. deployment is a multifaceted problem

    View Slide

  5. Embedded container
    Dev Machine
    bundle
    JVM Process
    target
    Embedded Impl.

    View Slide

  6. Standalone container
    Dev Machine
    deploy
    JVM Process JVM Process

    View Slide

  7. Remote container
    Dev Machine
    JVM Process JVM Process
    Remote Machine
    deploy

    View Slide

  8. Embedded container Gradle plugins

    View Slide

  9. Tomcat plugin composition
    tomcat
    tomcat-base
    ‣ adds custom task types
    ‣ pre-configures task types
    ‣ adds standard tasks to project
    ‣ configures default conventions
    ‣ exposes DSL
    applies

    View Slide

  10. apply plugin: 'tomcat'
    repositories {
    mavenCentral()
    }
    dependencies {
    def tomcatVersion = '6.0.29'
    tomcat "org.apache.tomcat:catalina:${tomcatVersion}",
    "org.apache.tomcat:coyote:${tomcatVersion}",
    "org.apache.tomcat:jasper:${tomcatVersion}"
    }
    tomcat {
    httpPort = 8090
    httpsPort = 8091
    enableSSL = true
    }
    Declare Tomcat dependencies
    Using & configuring standard tasks
    gradlew tomcatRun
    Configure tasks via extension
    tomcat

    View Slide

  11. apply plugin: 'tomcat-base'
    ext {
    tomcatStopPort = 8081
    }
    task functionalTomcatRun(type: org.gradle.api.plugins.tomcat.tasks.TomcatRun) {
    stopPort = tomcatStopPort
    daemon = true
    }
    task functionalTomcatStop(type: org.gradle.api.plugins.tomcat.tasks.TomcatStop) {
    stopPort = tomcatStopPort
    }
    task functionalTest(type: Test) {
    dependsOn functionalTomcatRun
    finalizedBy functionalTomcatStop
    }
    Create enhanced task
    Using & configuring custom tasks
    gradlew functionalTest
    Use embedded container for testing
    tomcat-base

    View Slide

  12. Typical development workflow
    gradle tomcatRun
    Hello Wolrd!
    Spotting a typo
    gradle compileJava
    change code
    restart
    start reload

    View Slide

  13. Rapid development workflow
    gradle tomcatRun
    Hello World!
    Hurray!
    gradle compileJava
    change code
    reload bytecode
    start reload
    ‣ built-in capabilities

    View Slide

  14. Arquillian plugin features
    arquillian
    reads
    configures
    container dependencies

    View Slide

  15. arquillian {
    debug = true
    deployable = file('my/path/arbitraryWebApp.war')
    containers {
    jetty {
    version = '8'
    type = 'embedded'
    config = ['bindHttpPort': 8085, 'bindAddress': '127.0.0.1',
    'jettyPlus': false]
    }
    glassfish {
    version = '3'
    type = 'embedded'
    }
    }
    }
    Path to WAR file
    Arquillian plugin DSL
    gradlew arquillianRun
    Declarative configuration of
    multiple containers
    arquillian

    View Slide

  16. "Cargo is a thin wrapper that allows you to manipulate various type
    of application containers (Java EE and others) in a standard way."
    CARGO

    View Slide

  17. Supported containers
    ...and many more

    View Slide

  18. Cargo plugin composition
    cargo
    cargo-base
    ‣ adds custom task types
    ‣ pre-configures Ant dependencies
    ‣ adds standard tasks to project
    ‣ configures default conventions
    ‣ exposes DSL
    applies

    View Slide

  19. Deploying to a local container
    JVM Process
    spawn process
    deploy
    1
    2
    JVM Process

    View Slide

  20. apply plugin: 'cargo'
    cargo {
    containerId = 'tomcat7x'
    port = 9090
    local {
    homeDir = file('/home/dev/tools/apache-tomcat-7.0.42')
    timeout = 60000
    containerProperties {
    property 'cargo.tomcat.ajp.port', 9099
    }
    }
    }
    Configure local container
    Declare container
    Using Cargo plugin for local deployments
    gradlew cargoRunLocal

    View Slide

  21. apply plugin: 'cargo'
    cargo {
    containerId = 'tomcat7x'
    port = 9090
    deployable {
    file = file('/home/foo/bar/web-services.war')
    context = 'web-services'
    }
    deployable {
    file = file('/home/foo/bar/enterprise-app.ear')
    context = 'enterprise-app'
    }
    local {
    homeDir = file('/home/dev/tools/jboss-as-web-7.0.2.Final')
    }
    }
    Declaring a WAR
    Deploying multiple artifacts
    gradlew cargoRunLocal
    Declaring an EAR

    View Slide

  22. File Server
    extract
    download
    Downloading & using container archive
    local Tomcat installation
    1
    2

    View Slide

  23. apply plugin: 'cargo'
    cargo {
    containerId = 'tomcat7x'
    local {
    installer {
    installUrl = 'http://apache.osuosl.org/tomcat/
    tomcat-7/v7.0.42/bin/apache-
    tomcat-7.0.42.zip'
    downloadDir = file("$buildDir/download")
    extractDir = file("$buildDir/extract")
    }
    }
    }
    Configure container installer
    Declare container
    Bootstrapping a container installation
    gradlew cargoRunLocal

    View Slide

  24. tomcat1
    tomcat1:8888
    Deploying to a remote container
    username/password
    Remote Machine

    View Slide

  25. apply plugin: 'cargo'
    cargo {
    containerId = 'tomcat7x'
    port = 9090
    remote {
    hostname = 'cloud.internal.it'
    username = 'superuser'
    password = 'secretpwd'
    }
    }
    Configure remote container
    Using Cargo plugin for remote deployments
    gradlew cargoDeployRemote
    Declare container

    View Slide

  26. apply plugin: 'cargo-base'
    import org.gradle.api.plugins.cargo.tasks.local.CargoRunLocal
    import org.gradle.api.plugins.cargo.tasks.remote.CargoDeployRemote
    task myLocalTomcatRun(type: CargoRunLocal) {
    containerId = 'tomcat7x'
    homeDir = file('/home/dev/tools/apache-tomcat-7.0.42')
    }
    task myLocalJettyRun(type: CargoRunLocal) {
    containerId = 'jetty9x'
    homeDir = file('/home/dev/tools/jetty-distribution-9.0.4.v20130625')
    }
    task deployToRemoteTomcat(type: CargoDeployRemote) {
    containerId = 'tomcat7x'
    hostname = 'cloud.internal.it'
    port = 9090
    username = 'superuser'
    password = 'secretpwd'
    }
    Import plugin custom tasks
    Create and configure enhanced
    local container task
    Developing and configuring your own Cargo tasks
    gradlew deployToRemoteTomcat
    Create and configure enhanced
    remote container task

    View Slide

  27. tomcat3
    tomcat2
    tomcat1
    tomcat1:9090
    tomcat2:8050
    tomcat3:8888
    Rolling deployments
    1
    2
    3

    View Slide

  28. class RemoteContainer {
    String name
    String hostname
    Integer port
    String username
    String password
    }
    def containers = [new RemoteContainer(name: 'tomcat1', hostname: 'remote-tomcat1',
    port: 9090, username: 'admin', password:
    's3cr3t'),
    new RemoteContainer(name: 'tomcat2', hostname: 'remote-tomcat2',
    port: 8050, username: 'deployer', password:
    'qwerty'),
    new RemoteContainer(name: 'tomcat3', hostname: 'remote-tomcat3',
    port: 8888, username: 'su', password:
    'powerful')]
    Declaring remote container configuration
    Container class representation
    List of container instances
    Avoid defining passwords in
    build script

    View Slide

  29. apply plugin: 'cargo-base'
    import org.gradle.api.plugins.cargo.tasks.remote.CargoDeployRemote
    containers.each { config ->
    task "deployRemote${config.name.capitalize()}"(type: CargoDeployRemote) {
    description = "Deploys WAR to remote Tomcat '${config.name}'."
    containerId = 'tomcat7x'
    hostname = config.hostname
    port = config.port
    username = config.username
    password = config.password
    }
    }
    task deployToAllRemoteTomcats {
    dependsOn containers.collect { "deployRemote${it.name.capitalize()}" }
    description = 'Deploys to all remote Tomcat containers.'
    group = 'deployment'
    }
    Dynamic deployment task creation
    Dynamic creation of tasks
    Use container configuration
    Rolling deployment
    aggregation task

    View Slide

  30. Test
    UAT
    Prod
    –Penv=prod
    –Penv=uat
    –Penv=test
    Deploying to multiple environments

    View Slide

  31. Problem
    con$nuous  hot  deployment  
    will  inevitability  lead  to  an
    OutOfMemoryError:
    PermGen space

    View Slide

  32. tomcat1
    Managing container lifecycle
    stop container
    start container
    1
    2
    3

    View Slide

  33. Remote daemon process
    Remote Machine
    configure
    send command

    View Slide

  34. import org.gradle.api.plugins.cargo.tasks.daemon.CargoDaemonStop
    import org.gradle.api.plugins.cargo.tasks.daemon.CargoDaemonStart
    ext {
    tomcat7HandleId = 'tomcat7'
    tomcat7Hostname = 'remote-tomcat1'
    }
    task cargoDaemonStop(type: CargoDaemonStop) {
    handleId = tomcat7HandleId
    hostname = tomcat7Hostname
    }
    task cargoDaemonStart(type: CargoDaemonStart) {
    handleId = tomcat7HandleId
    hostname = tomcat7Hostname
    }
    cargoDaemonStart.mustRunAfter cargoDaemonStop
    cargoRedeployRemote.dependsOn cargoDaemonStop, cargoDaemonStart
    Restarting container before deployment
    Unique identifier configured in
    daemon web application
    Make container restart a task
    dependency of a redeployment
    gradlew cargoRedeployRemote

    View Slide

  35. Deployment is often more
    than just copying a file

    View Slide

  36. This is where Cargo leaves you stranded!

    View Slide

  37. Fine-grained control with SSH
    ext.tomcatRemoteDir = '/opt/apache-tomcat-7.0.42'
    task shutdownTomcat(type: SshExec) {
    commands = "sudo -u tomcat $tomcatRemoteDir/bin/shutdown.sh"
    }
    task deleteTomcatWebappsDir(type: SshExec, dependsOn: shutdownTomcat) {
    command = "sudo -u tomcat rm -rf $tomcatRemoteDir/webapps/myapp"
    }
    task deleteTomcatWorkDir(type: SshExec, dependsOn: shutdownTomcat) {
    command = "sudo -u tomcat rm -rf $tomcatRemoteDir/work"
    }
    ...
    task startupTomcat(type: SshExec, dependsOn: copyWarToWebappsDir) {
    command = "sudo -u tomcat $tomcatRemoteDir/bin/startup.sh"
    }
    Custom task wrapping
    Ant SSH
    Chaining of individual commands

    View Slide

  38. Remote command execution with plugin
    apply plugin: 'ssh'
    remotes {
    localhost {
    host = 'localhost'
    user = System.properties['user.name']
    identity = file("${System.properties['user.home']}/.ssh/id_rsa")
    }
    }
    task deploy(type: SshTask) {
    session(remotes.localhost) {
    execute('sudo -u tomcat $tomcatRemoteDir/bin/shutdown.sh')
    execute('sudo -u tomcat rm -rf $tomcatRemoteDir/webapps/myapp')
    ...
    }
    }
    ssh
    Define hosts
    configuration
    Execute SSH commands
    against remote

    View Slide

  39. Deployment as part of a build pipeline

    View Slide

  40. Prod
    Continuous Delivery
    UAT
    Automated
    Acceptance  Tests
    manual
    deployment
    push-button release

    View Slide

  41. Prod
    Continuous Deployment
    UAT
    Automated
    Acceptance  Tests
    automated
    deployment
    ...many times a day

    View Slide

  42. Build once, deploy many
    Test
    UAT
    Prod
    publish
    deploy

    View Slide

  43. Push  deployment
    1.0.34
    1.0.32 1.0.33 1.0.34
    1.0.34
    Test
    UAT
    Prod

    View Slide

  44. Pull  deployment
    1.0.34
    1.0.32 1.0.33 1.0.34
    Test
    UAT
    Prod
    trigger

    View Slide

  45. References
    Gradle Tomcat Plugin
    https://github.com/bmuschko/gradle-tomcat-plugin
    Gradle Cargo Plugin
    https://github.com/bmuschko/gradle-cargo-plugin
    Gradle Arquillian Plugin
    https://github.com/arquillian/arquillian-gradle-plugin
    Gradle SSH Plugin
    https://github.com/int128/gradle-ssh-plugin

    View Slide

  46. > gradle askQuestions
    :askQuestions
    BUILD SUCCESSFUL
    Total time: 300 secs

    View Slide