openfaas / templates

OpenFaaS Classic templates
https://www.openfaas.com
MIT License
276 stars 228 forks source link

Support issue for Java11 and local libraries #221

Closed fakoe closed 3 years ago

fakoe commented 3 years ago

When using the java11 template and implementing my locally stored external jars, they exist at compile-time, since faas-cli build runs without error. But after deploying the function to the OpenFaaS-Server, there is only the function-1.0 folder with the function-1.0.jar and the following libs in a lib folder:

-rw-r--r--. 1 app app    3482 Oct 14 07:56 animal-sniffer-annotations-1.14.jar
-rw-r--r--. 1 app app 2213560 Oct 14 07:56 commons-math3-3.6.1.jar
-rw-r--r--. 1 app app    5451 Oct 14 07:56 entrypoint-0.1.0.jar
-rw-r--r--. 1 app app   12078 Oct 14 07:56 error_prone_annotations-2.0.18.jar
-rw-r--r--. 1 app app 2614708 Oct 14 07:56 guava-23.0.jar
-rw-r--r--. 1 app app    8782 Oct 14 07:56 j2objc-annotations-1.1.jar
-rw-r--r--. 1 app app   33015 Oct 14 07:56 jsr305-1.3.9.jar
-rw-r--r--. 1 app app    5379 Oct 14 07:56 model-0.1.1.jar

Where are my declared, external libraries and how can I implement them into the docker container correctly? I followed your tutorials, but it doesn't work (well, it works at compile-time but not at runtime).

Expected Behaviour

Declaring external libraries in the build.gradle script should implement them within the docker container. Invoking my function should execute the Handler correctly.

Current Behaviour

My custom written handler, which depends on these libraries isn't able to run, because there is a NoClassDefFoundError, when I call my classes:

java.lang.NoClassDefFoundError: my/custom/dependency/http/HttpClient

At compile-time the classes are existing, since the faas-cli build command successfully builds the function. But at runtime, the function runs into an error and stops, because there aren't any of my dependencies within the created docker container.

My build.gradle script within the function folder looks like this (according to your template documentation):

dependencies {
    // This dependency is exported to consumers, that is to say found on their compile classpath.
    api 'org.apache.commons:commons-math3:3.6.1'

    // This dependency is used internally, and not exposed to consumers on their own compile classpath.
    implementation 'com.google.guava:guava:23.0'

    // Use JUnit test framework
    testImplementation 'junit:junit:4.12'

    compile 'com.openfaas:model:0.1.1'
    compile 'com.openfaas:entrypoint:0.1.0'

    implementation 'my.custom.dependency:my-custom-jar:'

}

// In this section you declare where to find the dependencies of your project
repositories {
    // Use jcenter for resolving your dependencies.
    // You can declare any Maven/Ivy/file repository here.
    jcenter()

    flatDir {
        dirs '../libs'
    }

}

I saved my-custom-jar.jar under ../libs, as the folder was already declared within the gradle script. So I did everything, that you said in your template documentation and blog post about java11.

Possible Solution

I have no idea, but I assume either adjusting the Dockerfile to copy the libraries or explaining step-by-step how to correctly implement the dependencies within the build.gradle script (Your documentation and blog entry still has code examples from the java8 template, like compile project(':model') instead of compile 'com.openfaas:model:0.1.1').

Steps to Reproduce (for bugs)

  1. faas-cli new --lang java11 java11
  2. Add local jar to dependencies under the function/build.gradle script
  3. Call a class from the jar within the Handler.class
  4. faas-cli up -f java11.yml
  5. Invoke the function

Context

I work at a software company and we want to use OpenFaaS for exporting longer running tasks of our software. For that, we customized the Handler.class and we use use locally stored, external dependencies, which we declared in the build.gradle script. But when we have the function running in a docker container on our openshift cluster, our dependencies aren't there and we get a NoClassDefFoundError. Strangely, we still get a Response Code of "200" when we invoke the function. Nothing else happens, though.

Your Environment

alexellis commented 3 years ago

Hi thank you for your interest in OpenFaaS.

Contributions are welcome, feel free to work on and propose a solution.

Alternatively, we offer consulting and a [Premium Subscription]](https://openfaas.com/support) if you want us to look at this as a priority.

Alex

fakoe commented 3 years ago

I resolved the issue with the following two solutions:

  1. In the gradle.build script, I use "compile" instead of "implementation" so an external, local dependency would look like this:
dependencies {
    // This dependency is exported to consumers, that is to say found on their compile classpath.
    api 'org.apache.commons:commons-math3:3.6.1'

    // This dependency is used internally, and not exposed to consumers on their own compile classpath.
    implementation 'com.google.guava:guava:23.0'

    // Use JUnit test framework
    testImplementation 'junit:junit:4.12'

    compile 'com.openfaas:model:0.1.1'
    compile 'com.openfaas:entrypoint:0.1.0'

    compile 'my.custom.dependency:my-custom-jar:'

}

I'm fairly new to gradle, so I had no clue that it works like this.

The second solution would be to change the Dockerfile and copy the external libraries from the builder image into the ship image: (The following is a snippet from the Dockerfile)

...
WORKDIR /home/app
COPY --from=builder /home/app/function/build/distributions/function-1.0.zip ./function-1.0.zip
USER app
RUN unzip ./function-1.0.zip
COPY --from=builder /home/app/libs/* ./function-1.0/lib/
...

Problem with the second solution would be, that external jars have to be in a folder called "libs" under the root directory ./libs Since it makes more sense to include external dependencies into the ./function folder, I'd change the Dockerfile to:

...
WORKDIR /home/app
COPY --from=builder /home/app/function/build/distributions/function-1.0.zip ./function-1.0.zip
USER app
RUN unzip ./function-1.0.zip
COPY --from=builder /home/app/function/lib/* ./function-1.0/lib/
...

(There might be problems with permissions due to USER app, I haven't tested that)

I think the first solution is more than enough, but if, for any reason, somebody can't use "compile" instead of "implementation", the second approach might come in handy (even if it is hard-coded).

alexellis commented 3 years ago

Thanks for the reply. Can you re-iterate your first solution? I didn't quite follow it.

alexellis commented 3 years ago

/set title: Support issue for Java11 and local libraries

fakoe commented 3 years ago

I think I just failed to understand the build.gradle script. I'm new to gradle and thought that I need to declare local libaries in the dependencies like this:

dependencies {
    implementation 'my.custom.dependency:my-custom-jar:'
}

When I actually have to do this:

dependencies {
    compile 'my.custom.dependency:my-custom-jar:'
}

implementation lets you execute gradle build without any errors, but the jars aren't added to the final, compiled function.jar, whereas compile does add them. And since your dockerfile executes gradle build in another image than the function.jar is used in, the local libraries are staying in the untagged image.