Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

More Decks by Benjamin Muschko

Other Decks in Programming


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

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

    change code reload bytecode start reload ‣ built-in capabilities ‣
  9. 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
  10. "Cargo is a thin wrapper that allows you to manipulate

    various type of application containers (Java EE and others) in a standard way." CARGO
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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