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


  1. Web application deployments with Gradle From the developer's machine to

    Continuous Deployment Benjamin Muschko Principal Engineer, Gradleware @bmuschko
  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
  3. Is

  4. deployment is a multifaceted problem

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

  6. Standalone container Dev Machine deploy JVM Process JVM Process

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

  8. Embedded container Gradle plugins

  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
  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
  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
  12. Typical development workflow gradle tomcatRun Hello Wolrd! Spotting a typo

    gradle compileJava change code restart start reload
  13. Rapid development workflow gradle tomcatRun Hello World! Hurray! gradle compileJava

    change code reload bytecode start reload ‣ built-in capabilities ‣
  14. Arquillian plugin features arquillian reads configures container dependencies

  15. arquillian { debug = true deployable = file('my/path/arbitraryWebApp.war') containers {

    jetty { version = '8' type = 'embedded' config = ['bindHttpPort': 8085, 'bindAddress': '', 'jettyPlus': false] } glassfish { version = '3' type = 'embedded' } } } Path to WAR file Arquillian plugin DSL gradlew arquillianRun<ContainerName> Declarative configuration of multiple containers arquillian
  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
  17. Supported containers ...and many more

  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
  19. Deploying to a local container JVM Process spawn process deploy

    1 2 JVM Process
  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
  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
  22. File Server extract download Downloading & using container archive local

    Tomcat installation 1 2
  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
  24. tomcat1 tomcat1:8888 Deploying to a remote container username/password Remote Machine

  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
  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
  27. tomcat3 tomcat2 tomcat1 tomcat1:9090 tomcat2:8050 tomcat3:8888 Rolling deployments 1 2

  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
  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
  30. Test UAT Prod –Penv=prod –Penv=uat –Penv=test Deploying to multiple environments

  31. Problem con$nuous  hot  deployment   will  inevitability  lead  to  an

    OutOfMemoryError: PermGen space
  32. tomcat1 Managing container lifecycle stop container start container 1 2

  33. Remote daemon process Remote Machine configure send command

  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
  35. Deployment is often more than just copying a file

  36. This is where Cargo leaves you stranded!

  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
  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
  39. Deployment as part of a build pipeline

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

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

    times a day
  42. Build once, deploy many Test UAT Prod publish deploy

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

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

  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
  46. > gradle askQuestions :askQuestions BUILD SUCCESSFUL Total time: 300 secs