GoogleContainerTools / jib

🏗 Build container images for your Java applications.
Apache License 2.0
13.68k stars 1.44k forks source link

Spring Boot Application containerizes but Tomcat Server doesn't start #640

Closed saturnism closed 6 years ago

saturnism commented 6 years ago

Description of the issue:

  1. Create a Rest service w/ Spring Boot, and add Jib to Maven pom.xml.
  2. Validate that local run, default port 8080 is exposed.
  3. Run `jib:build
  4. Run the created container image
  5. Notice the port isn't opened. Tomcat didn't start.

Expected behavior: Created image should behave like the JAR.

Steps to reproduce:

  1. Build application here: https://github.com/saturnism/spring-cloud-gcp-guestbook/tree/master/11-kubernetes/guestbook-frontend
  2. Build container w/ Jib.
  3. Notice the port isn't opened.

Environment: MacOS, Maven

jib-maven-plugin Configuration:

<plugin>
  <groupId>com.google.cloud.tools</groupId>
  <artifactId>jib-maven-plugin</artifactId>
  <version>0.9.6</version>
  <configuration>
    <to>
      <image>gcr.io/.../guestbook-frontend</image>
    </to>
  </configuration>
</plugin>

Log output: Normal Startup w/o Jib

2018-07-17 14:09:02.191  INFO [-,,,] 60312 --- [           main] o.s.i.endpoint.EventDrivenConsumer       : started _org.springframework.integration.errorLogger
2018-07-17 14:09:02.485  INFO [-,,,] 60312 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2018-07-17 14:09:02.488  INFO [-,,,] 60312 --- [           main] c.example.frontend.FrontendApplication   : Started FrontendApplication in 9.774 seconds (JVM running for 10.318)

Failed startup w/ Jib

springframework.integration.errorLogger} as a subscriber to the 'errorChannel' channel
2018-07-17 18:05:09.937  INFO [-,,,] 1 --- [           main] o.s.i.channel.PublishSubscribeChannel    : Channel 'application-1.errorChannel' has 1 subscriber(s).
2018-07-17 18:05:09.937  INFO [-,,,] 1 --- [           main] o.s.i.endpoint.EventDrivenConsumer       : started _org.springframework.integration.errorLogger
2018-07-17 18:05:09.967  INFO [-,,,] 1 --- [           main] c.example.frontend.FrontendApplication   : Started FrontendApplication in 9.085 seconds (JVM running for 9.892)

Additional Information:

chanseokoh commented 6 years ago

I noticed that your application requires Google Application-default Credentials (ADC). So, one possible workaround is

  1. $ ./mvnw clean compile jib:exportDockerContext
  2. $ cd target/jib-docker-context
  3. $ cp <your service account.json> . # give a service account with all the roles that your application requires
  4. Edit Dockerfile to add something along the line of
    COPY <your service account.json> /app/cred.json
    ENV GOOGLE_APPLICATION_CREDENTIALS /app/cred.json
  5. $ docker build . -t my-tag
  6. $ docker run -t my-tag

output:

2018-07-17 19:26:49.700  INFO [-,,,] 1 --- [           main] o.s.i.endpoint.EventDrivenConsumer       : Adding {logging-channel-adapter:_org.springframework.integration.errorLogger} as a subscriber to the 'errorChannel' channel
2018-07-17 19:26:49.700  INFO [-,,,] 1 --- [           main] o.s.i.channel.PublishSubscribeChannel    : Channel 'application-1.errorChannel' has 1 subscriber(s).
2018-07-17 19:26:49.700  INFO [-,,,] 1 --- [           main] o.s.i.endpoint.EventDrivenConsumer       : started _org.springframework.integration.errorLogger
2018-07-17 19:26:49.715  INFO [-,,,] 1 --- [           main] c.example.frontend.FrontendApplication   : Started FrontendApplication in 4.056 seconds (JVM running for 4.558)
chanseokoh commented 6 years ago

So, this use case is currently not supported in Jib. An application requires Google ADC, and the container which must be self-contained to run the application should be able to get the ADC from somewhere somehow. But I think this can be solved once we have the features to add arbitrary files and define environment variables.

chanseokoh commented 6 years ago

Oh, BTW, I first had to add the spring-milesontes Maven artifact repository to my ~/.m2/settings.xml for the org.springframework.cloud:spring-cloud-gcp-dependencies

  <profiles>
    <profile>
      <id>spring-milestones</id>
      <repositories>
        <repository>
          <id>spring-milestones</id>
          <name>Spring Milestones</name>
          <url>https://repo.spring.io/milestone</url>
        </repository>
      </repositories>
    </profile>
  </profiles>
  <activeProfiles>
    <activeProfile>spring-milestones</activeProfile>
  </activeProfiles>

and also had to downgrade the version of org.springframework.cloud:spring-cloud-gcp-dependencies:

                        <dependency>
                                <groupId>org.springframework.cloud</groupId>
                                <artifactId>spring-cloud-gcp-dependencies</artifactId>
-                               <version>1.1.0.BUILD-SNAPSHOT</version>
+                               <version>1.0.0.M4</version>
                                <type>pom</type>
                                <scope>import</scope>
                        </dependency>
coollog commented 6 years ago

So, this use case is currently not supported in Jib.

@chanseokoh So we do have the src/main/jib extra files feature which could probably be used for this. @saturnism I'll look more into this as well.

chanseokoh commented 6 years ago

Oh, is src/main/jib already usable? But I guess we don't have the feature to define arbitrary env variables yet (but will soon)?

coollog commented 6 years ago

@chanseokoh Yes it is usable, but in an incubating state (hence why there is no documentation).

saturnism commented 6 years ago

oops, is that default credential causing your start up issue?

the root issue is unrelated to adc. i.e., even w/ credential in place, the tomcat server didn't start.

saturnism commented 6 years ago

Easier to test w/ 1-bootstrap/guestbook-frontend and add Jib to it.

  1. ./mvnw spring-boot:run <-- server starts and listens to port 8080
  2. ./mvnw jib:build then, run the container <-- tomcat server does not start, container exits
chanseokoh commented 6 years ago

the root issue is unrelated to adc. i.e., even w/ credential in place, the tomcat server didn't start.

Oops... ADC was causing a problem, but yeah, you're right, the tomcat does not start with Jib, and I think I know why. If I look at the META-INF/MANIFEST.MF, the main class of this Spring-Boot-crafted excutable WAR (and probably JAR too?) that does some kind of black magic is supposed to be org.springframework.boot.loader.WarLauncher. UPDATE: what I said is true, but directly running the main com.example.frontend.FrontendApplication should still work.

Main-Class: org.springframework.boot.loader.WarLauncher
Start-Class: com.example.frontend.FrontendApplication
...

What we instead have in Dockerfile is

ENTRYPOINT ["java","-cp","/app/libs/*:/app/resources/:/app/classes/","com.example.frontend.FrontendApplication"]
coollog commented 6 years ago

So, adding Jib to https://github.com/spring-projects/spring-petclinic and doing ./mvnw compile jib:build generates a container that starts Tomcat fine. I'm looking at what difference there could be in this case.

chanseokoh commented 6 years ago

FYI, in the case of Spring-Boot-craft executable JAR (i.e., <packaing>jar</packaing> instead of <packaing>war</packaing>), the main class is set to the JAR launcher:

Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.example.frontend.FrontendApplication

Explicitly setting org.springframework.boot.loader.WarLauncher (or JarLauncher) as the main class in Jib's configuration won't work, because these launchers require additional classes and library that Spring Boot injects at the time of packaging a WAR or a JAR.

saturnism commented 6 years ago

I have been able to create a Jar archive w/ custom manifest w/ the app as the main class. See https://github.com/saturnism/spring-petclinic-gcp/blob/master/pom.xml#L146

chanseokoh commented 6 years ago

So, adding Jib to https://github.com/spring-projects/spring-petclinic and doing ./mvnw compile jib:build generates a container that starts Tomcat fine. I'm looking at what difference there could be in this case.

The difference I think is that, spring-petclinic is a pure Java application (an usual Java application with main()), whereas @saturnism's application basically retains the WAR structure from which Spring Boot has the ability to create either an (executable) WAR or a JAR with some black magic. This WAR-structure application has this ServletInitializer.

chanseokoh commented 6 years ago

whereas @saturnism's application basically retains the WAR structure from which Spring Boot has the ability to create either an (executable) WAR or a JAR with some black magic.

I might be wrong with the "WAR structure" terminology... but I am still thinking it is this ServletInitializer that makes the different, which enables Spring Boot to create a magical executable WAR.

I have been able to create a Jar archive w/ custom manifest w/ the app as the main class. See https://github.com/saturnism/spring-petclinic-gcp/blob/master/pom.xml#L146

@saturnism FYI, Jib does not take a JAR from somewhere or even attempt to package a JAR. It doesn't use a JAR at all, but puts individual class files into the container; there is no JAR packaging at all. That could explain why we have problems with Spring Boot here.

saturnism commented 6 years ago

Removing servlet initializer doesn't nor should change anything. The ServletInitializer is for initialization inside of an application server, like Jetty.

Spring Petclinic also packages the Manfiest w/ JarLauncher.

coollog commented 6 years ago

So I've found that the tomcat-embed-core JARs (like tomcat-embed-core-8.5.31.jar) are not being added to the guestbook image, which I believe are necessary for the embedded tomcat startup. They do appear in the petclinic image, which is why that starts.

coollog commented 6 years ago

Adding

        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
        </dependency>

Made it work for me.

coollog commented 6 years ago

Not sure why it wasn't being added since spring-boot-starter-web includes spring-boot-starter-tomcat, which includes tomcat-embed-core.

coollog commented 6 years ago

Ah I found it: @saturnism

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>

The scope is set to provided, but should be none/compile.

malithsumuditha commented 1 year ago

Ah I found it: @saturnism

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-tomcat</artifactId>
          <scope>provided</scope>
      </dependency>

The scope is set to provided, but should be none/compile.

Thank you very much for this clarification