Search code examples
google-app-enginespring-bootapp-engine-flexible

Jetty fails to start spring boot application in appengine flexible


Note: This is a cross post because I wasn't sure if this was a technical issue or a bug so the bug can be found here

A couple weeks ago I wrote up a prototype for adding a flexible environment service to our appengine project that is primarily standard environment services. The prototype was based on the java quickstart sample "HelloworldSpringBoot" with some minor modifications.

After my success with the prototype I moved over to a more production set of code for this service, developed it for a couple weeks and attempted to deploy on Jan 12th, 4 days ago. The app refused to start and after several attempts at simplifying the app to just a single route I could not get the app to start.

I switch back to the prototype code, deployed to the original project that code was working in and am seeing the same behavior where the app is not starting.

There is no one error message to point to so I have a couple that I'll list here that may be the culprit.

The first is the most obvious, but may be a generic catch-all:

Exception in thread "main" java.lang.IllegalStateException: No Available Context
        at com.google.cloud.runtimes.jetty9.DeploymentCheck.lifeCycleStarted(DeploymentCheck.java:46)
        at org.eclipse.jetty.util.component.AbstractLifeCycle.setStarted(AbstractLifeCycle.java:179)
        at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:69)
        at org.eclipse.jetty.xml.XmlConfiguration$1.run(XmlConfiguration.java:1511)
        at org.eclipse.jetty.xml.XmlConfiguration$1.run(XmlConfiguration.java:1438)
        at java.security.AccessController.doPrivileged(Native Method)
        at org.eclipse.jetty.xml.XmlConfiguration.main(XmlConfiguration.java:1437)

The next happens much earlier in the startup, but seems benign:

javax.servlet.ServletException: Not running on Jetty, JSR-356 support unavailable at org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer.onStartup(WebSocketServerContainerInitializer.java:193) at org.eclipse.jetty.plus.annotation.ContainerInitializer.callStartup(ContainerInitializer.java:140) at org.eclipse.jetty.annotations.ServletContainerInitializersStarter.doStart(ServletContainerInitializersStarter.java:63) at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68) at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:329) at org.eclipse.jetty.webapp.WebAppContext.startWebapp(WebAppContext.java:1501) at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1463) at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:785) at org.eclipse.jetty.servlet.ServletContextHandler.doStart(ServletContextHandler.java:261) at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:545) at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68) at org.eclipse.jetty.deploy.bindings.StandardStarter.processBinding(StandardStarter.java:41) at org.eclipse.jetty.deploy.AppLifeCycle.runBindings(AppLifeCycle.java:188) at org.eclipse.jetty.deploy.DeploymentManager.requestAppGoal(DeploymentManager.java:502) at org.eclipse.jetty.deploy.DeploymentManager.addApp(DeploymentManager.java:150) at org.eclipse.jetty.deploy.providers.ScanningAppProvider.fileAdded(ScanningAppProvider.java:180) at org.eclipse.jetty.deploy.providers.WebAppProvider.fileAdded(WebAppProvider.java:447) at org.eclipse.jetty.deploy.providers.ScanningAppProvider$1.fileAdded(ScanningAppProvider.java:64) at org.eclipse.jetty.util.Scanner.reportAddition(Scanner.java:610) at org.eclipse.jetty.util.Scanner.reportDifferences(Scanner.java:529) at org.eclipse.jetty.util.Scanner.scan(Scanner.java:392) at org.eclipse.jetty.util.Scanner.doStart(Scanner.java:313) at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68) at org.eclipse.jetty.deploy.providers.ScanningAppProvider.doStart(ScanningAppProvider.java:150) at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68) at org.eclipse.jetty.deploy.DeploymentManager.startAppProvider(DeploymentManager.java:564) at org.eclipse.jetty.deploy.DeploymentManager.doStart(DeploymentManager.java:239) at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68) at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:131) at org.eclipse.jetty.server.Server.start(Server.java:452) at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:113) at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:113) at org.eclipse.jetty.server.Server.doStart(Server.java:419) at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68) at org.eclipse.jetty.xml.XmlConfiguration$1.run(XmlConfiguration.java:1511) at org.eclipse.jetty.xml.XmlConfiguration$1.run(XmlConfiguration.java:1438) at java.security.AccessController.doPrivileged(Native Method) at org.eclipse.jetty.xml.XmlConfiguration.main(XmlConfiguration.java:1437)

Then there is this guy that happens just after the previous:

org.eclipse.jetty.webapp.WebAppContext: Failed startup of context o.e.j.w.WebAppContext@7e0b0338{/,file:///var/lib/jetty/webapps/root/,UNAVAILABLE}{/root.war}

Also, here are the pertinent files:

app.yaml:

runtime: java
env: flex

runtime_config:  # Optional
  jdk: openjdk8

service: service2

handlers:
- url: /.*
  script: this field is required, but ignored

manual_scaling:
  instances: 1

build.gradle:

buildscript {      // Configuration for building
    ext {
        springBootVersion = '1.5.9.RELEASE'
    }
    repositories {
        jcenter()      // Bintray's repository - a fast Maven Central mirror & more
        mavenCentral()
        maven { url 'https://repo.spring.io/libs-snapshot' }
    }
    dependencies {
        classpath 'com.google.cloud.tools:appengine-gradle-plugin:+'
        classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
    }
}

repositories {   // repositories for JARs you access in your code
    maven {
        url 'https://maven-central.storage.googleapis.com'             // Google's mirror of Maven Central
    }

    maven {
        url "http://repo.spring.io/libs-snapshot"
    }

    jcenter()
    mavenCentral()
}

apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'com.google.cloud.tools.appengine'
apply plugin: 'org.springframework.boot'

dependencies {

    // Add your dependencies here.
    compile "org.springframework.boot:spring-boot-starter-web", { exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat' }
    compile (
            "org.springframework.boot:spring-boot-starter-actuator",
            "org.springframework.boot:spring-boot-starter-jetty"
    )
    testCompile (
            "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
    )

}

appengine {

    deploy {   // deploy configuration
        stopPreviousVersion = true  // default - stop the current version
        promote = true              // default - & make this the current version
    }

}

group = 'com.example'   // Generated output GroupId
version = '1.0-SNAPSHOT'          // Version in generated output

sourceCompatibility = 1.8
targetCompatibility = 1.8

HelloworldApplication.java:

package com.example.java.gettingstarted;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class HelloworldApplication {
  @RequestMapping("/service2/")
  public String home() {
    return "Helloooooooo Nurse!";
  }

  /**
   * (Optional) App Engine health check endpoint mapping.
   * @see <a href="https://cloud.google.com/appengine/docs/flexible/java/how-instances-are-managed#health_checking"></a>
   * If your app does not handle health checks, a HTTP 404 response is interpreted
   *     as a successful reply.
   */
  @RequestMapping("/_ah/health")
  public String healthy() {
    // Message body required though ignored
    return "Still surviving.";
  }

  public static void main(String[] args) {
    SpringApplication.run(HelloworldApplication.class, args);
  }
}

I'm also running gcloud version 184.0.0


Solution

  • The solution ended up being a little bit magical, but the SpringBoot documentation was telling once the appengine standard documentation pointed me in the right direction.

    Note that a WebApplicationInitializer is only needed if you are building a war file and deploying it. If you prefer to run an embedded container then you won't need this at all.

    We were deploying a war and locally it was running with the embedded container. After setting jetty as provided and adding the magical SpringBootServletInitializer to the root, things started working. I don't know why this was not needed two weeks ago, but it is definitely needed now.

    import org.springframework.boot.builder.SpringApplicationBuilder;
    import org.springframework.boot.web.support.SpringBootServletInitializer;
    
    public class ServletInitializer extends SpringBootServletInitializer {
        @Override
        protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
            System.out.println("ServletInitializer called.");
            return application.sources(HelloworldApplication.class);
        }
    } 
    

    Placing this file at the root did the trick.

    Thanks for the help.