OpenFaaS practice 8: self made template (maven+jdk8)

OpenFaaS actual combat series article link

  1. deploy
  2. Introduction to functions
  3. Java functions
  4. Template operation (template)
  5. Big talk watchdog
  6. Of watchdog (born for performance)
  7. java11 template parsing
  8. Self made template (maven+jdk8)
  9. Final, self-made template (springboot+maven+jdk8)

Overview of this article

  • This article is the eighth in the series of OpenFaaS actual combat. After the previous theoretical analysis and practical practice, we have almost understood OpenFaaS, and it's time to do something;
  • As a Java programmer, he often uses jdk8, maven and springboot. Naturally, he should pay attention to whether the official template is supported, as shown in the figure below, Official documents It shows that the support for java programmers is not enough: java 8 is not supported, Gradle is used instead of maven, springboot is not supported, and only vertx framework To support web services:
  • Since the official template is not supported, let's make our own template to support it. Based on the principle of easy before difficult, this article first makes a simple template: keep the function of the official java11 template unchanged, change the jdk version to java8, and change Gradle to maven;
  • It is undeniable that both jdk8 and maven are old. The new version of JDK and Gradle are better choices, but the focus of this article is how to customize the template, so please be tolerant
  • What we need to do today is shown in the figure below. Let's first do the blue part on the left, write the template code, upload it to the github template warehouse, and then do the green part on the right, using the template like the official template in the previous article:
  • The next actual combat consists of the following contents:
  1. Create a java project as the basic source code of the template
  2. Develop Dockerfile
  3. Complete template configuration and upload
  4. Validation template

Create a java project

  • When making a template, the most important thing is to provide complete template code. Let's make it next;
  • I use IDEA to build an empty Maven project called java8maven, using JDK8:
  • As shown in the following figure, note that Language level should select 8:
  • The contents of pom.xml are as follows. The points to be noted will be explained later:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bolingcavalry</groupId>
    <artifactId>java8maven</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-math3</artifactId>
            <version>3.6.1</version>
        </dependency>

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>23.0</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

        <dependency>
            <groupId>com.openfaas</groupId>
            <artifactId>model</artifactId>
            <version>0.1.1</version>
        </dependency>

        <dependency>
            <groupId>com.openfaas</groupId>
            <artifactId>entrypoint</artifactId>
            <version>0.1.0</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.11.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.10</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.10</version>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/lib</outputDirectory>
                            <overWriteReleases>false</overWriteReleases>
                            <overWriteSnapshots>false</overWriteSnapshots>
                            <overWriteIfNewer>true</overWriteIfNewer>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.0.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>com.openfaas.entrypoint.App</mainClass>
                        </manifest>
                        <manifestEntries>
                            <Class-Path>.</Class-Path>
                        </manifestEntries>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
  • There are several points to note in the above pom.xml:
  1. The model and entrypoint jar s of openfaas are the basis for the whole service to run;
  2. Some common jar dependencies have also been added, and you can add or delete them at your discretion;
  3. The plugin Maven compiler plugin is used to specify the JDK version at compile time;
  4. The plug-ins Maven dependency plugin and Maven assembly plugin are used to package the entire java code and dependency library into a jar file, which makes it much easier to create Docker images;
package com.openfaas.function;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.openfaas.model.IRequest;
import com.openfaas.model.IResponse;
import com.openfaas.model.Response;
import org.apache.commons.lang3.StringUtils;

import java.lang.management.ManagementFactory;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

public class Handler extends com.openfaas.model.AbstractHandler {

    private static final String PARAM_USER_NAME = "name";

    private static final String RESPONSE_TEMPLETE = "Hello %s, response from [%s], PID [%s], %s";

    private ObjectMapper mapper = new ObjectMapper();

    /**
     * Get native IP address
     * @return
     */
    public static String getIpAddress() {
        try {
            Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
            InetAddress ip = null;
            while (allNetInterfaces.hasMoreElements()) {
                NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement();
                if (netInterface.isLoopback() || netInterface.isVirtual() || !netInterface.isUp()) {
                    continue;
                } else {
                    Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
                    while (addresses.hasMoreElements()) {
                        ip = addresses.nextElement();
                        if (ip != null && ip instanceof Inet4Address) {
                            return ip.getHostAddress();
                        }
                    }
                }
            }
        } catch (Exception e) {
            System.err.println("IP Address acquisition failed" + e.toString());
        }
        return "";
    }

    /**
     * Returns the current process ID
     * @return
     */
    private static String getPID() {
        return ManagementFactory
                .getRuntimeMXBean()
                .getName()
                .split("@")[0];
    }


    private String getUserName(IRequest req) {
        // If the userName cannot be obtained from the request body, it is used
        String userName = null;

        try {
            Map<String, Object> mapFromStr = mapper.readValue(req.getBody(),
                    new TypeReference<Map<String, Object>>() {});

            if(null!=mapFromStr && mapFromStr.containsKey(PARAM_USER_NAME)) {
                userName = String.valueOf(mapFromStr.get(PARAM_USER_NAME));
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        // If the userName cannot be retrieved from the request body, a default value is given
        if(StringUtils.isBlank(userName)) {
            userName = "anonymous";
        }

        return userName;
    }

    public IResponse Handle(IRequest req) {

        String userName = getUserName(req);

        System.out.println("1. ---" + userName);

        // The return information takes the IP, process number and time of the machine where the current JVM is located
        String message = String.format(RESPONSE_TEMPLETE,
                userName,
                getIpAddress(),
                getPID(),
                new SimpleDateFormat( "yyyy-MM-dd hh:mm:ss" ).format(new Date()));

        System.out.println("2. ---" + message);

        // The response content is also in JSON format, so it is stored in the map first and then serialized
        Map<String, Object> rlt = new HashMap<>();
        rlt.put("success", true);
        rlt.put("message", message);

        String rltStr = null;

        try {
            rltStr = mapper.writeValueAsString(rlt);
        } catch (Exception e) {
            e.printStackTrace();
        }

        Response res = new Response();
        res.setContentType("application/json;charset=utf-8");
        res.setBody(rltStr);

	    return res;
    }
}
  • In the directory where pom.xml is located, create a new folder m2 and add maven's configuration file settings.xml, which is used when making images during FaaS development (java projects will be compiled and built when making images). It is strongly recommended to configure your maven private server or alicloud image in it, which will make the image much faster. I have configured alicloud image here, It still takes more than four minutes (as shown in the figure below), so if you have nexus3 private server, you must give priority to it:
  • So far, the coding work has been completed. It can be seen that this is an ordinary maven project. Let's try whether it can run normally;
  • Execute the command mvn clean package -U -DskipTests. After success, the file java8maven-1.0-SNAPSHOT-jar-with-dependencies.jar will be generated in the target directory;
  • Run the above jar file. The command is java -jar java8maven-1.0-SNAPSHOT-jar-with-dependencies.jar;
  • After the above jar runs, it will listen to the POST request of port 8082. I'll try postman here. As shown in the figure below, you can receive the latest data returned from the background:
  • The background console will also print the expected content:
  • After the code is written, the next thing to consider is how to create a Docker image, that is, how to write a Dockerfile;

Develop Dockerfile

  • In the previous actual combat, we have experienced that the code compilation and construction will be made into an image when developing FaaS, so the corresponding Dockerfile should also be prepared. The following is the complete Dockerfile content. Detailed comments have been added, so we won't repeat it:
# maven image is used as the basic image to compile and build java projects
FROM maven:3.6.3-openjdk-8 as builder

WORKDIR /home/app

# Copy the entire project to the / home/app directory
COPY . /home/app/

# Enter the directory where pom.xml is located, execute the build command, and specify m2/settings.xml file as the configuration file,
# Please configure the private server in settings.xml, otherwise the construction speed is very slow
RUN cd function && mvn clean package -U -DskipTests --settings ./m2/settings.xml 

# There is a binary file watchdog in of watchdog, which is used when making an image
FROM openfaas/of-watchdog:0.7.6 as watchdog

# The openjdk image is the running environment of the container
FROM openjdk:8-jre-slim as ship

# For security reasons, do not refer to the root account and group when running the container in the production environment
RUN addgroup --system app \
    && adduser --system --ingroup app app

# Copy the binary fwatchdog from the of watchdog image, which is the starting process of the container
COPY --from=watchdog /fwatchdog /usr/bin/fwatchdog

# Give executable permission
RUN chmod +x /usr/bin/fwatchdog

WORKDIR /home/app

# After the previous build is compiled with maven, the build results are copied to the image
COPY --from=builder /home/app/function/target/java8maven-1.0-SNAPSHOT-jar-with-dependencies.jar ./java8maven-1.0-SNAPSHOT-jar-with-dependencies.jar
# Specify the running account of the container
user app

# Specifies the working directory of the container
WORKDIR /home/app/

# fwatchdog is the forwarding address after receiving the web request. The java process listens on this port
ENV upstream_url="http://127.0.0.1:8082"

# The operating mode is http
ENV mode="http"

# Pull up the command of the business process. Here is to start the java process
ENV fprocess="java -jar java8maven-1.0-SNAPSHOT-jar-with-dependencies.jar"

# The exposed port of the container, that is, the port on which the fwatchdog process listens
EXPOSE 8080

# health examination
HEALTHCHECK --interval=5s CMD [ -e /tmp/.lock ] || exit 1

# The container starts the command. Here, execute the binary file fwatchdog
CMD ["fwatchdog"]

Template configuration

  • Now that the materials have been prepared, sort them out and submit them to github, and they can be used as OpenFaaS templates;
  1. Create a new folder named simplejava8;
  2. Create a new file template.yml in the simplejava8 directory, as follows:
language: simplejava8
welcome_message: |
  You have created a function using the java8 and maven template
  1. Copy the previous Dockerfile file to the simplejava8 directory;
  2. For the Maven project we created earlier, the outermost folder is called java8maven. Please change the name of this folder to function, and then copy the whole folder to the simplejava8 directory;
  3. These contents should be in the simplejava8 directory at the moment:
[root@hedy 002]# tree simplejava8
simplejava8
├── Dockerfile
├── function
│   ├── java8maven.iml
│   ├── m2
│   │   └── settings.xml
│   ├── pom.xml
│   └── src
│       ├── main
│       │   ├── java
│       │   │   └── com
│       │   │       └── openfaas
│       │   │           └── function
│       │   │               └── Handler.java
│       │   └── resources
│       └── test
│           └── java
└── template.yml

11 directories, 6 files
  1. Upload all these contents to GitHub. My path here is https://github.com/zq2599/openfaas-templates/tree/master/template , there are already three templates. The new ones are shown in the red box below:
  • At this point, the template is completed. Next, verify whether the template is available;

Validation template

  • The next thing to do is the green part on the right of the figure below:
  • Log in to a computer equipped with OpenFaaS client, find a clean directory and execute the following command to download all templates on github:
faas template pull https://github.com/zq2599/openfaas-templates
  • The console responds as follows, prompting that three templates have been downloaded, which is in line with expectations:
[root@hedy 07]# faas template pull https://github.com/zq2599/openfaas-templates
Fetch templates from repository: https://github.com/zq2599/openfaas-templates at 
2021/03/07 08:44:29 Attempting to expand templates from https://github.com/zq2599/openfaas-templates
2021/03/07 08:44:32 Fetched 3 template(s) : [dockerfile java11extend simplejava8] from https://github.com/zq2599/openfaas-templates
  • Use faas new --list to view the list as follows:
[root@hedy 07]# faas new --list
Languages available as templates:
- dockerfile
- java11extend
- simplejava8
  • As like as two peas in the template/simplejava8 directory,
[root@hedy 07]# tree template/simplejava8/
template/simplejava8/
├── Dockerfile
├── function
│   ├── java8maven.iml
│   ├── m2
│   │   └── settings.xml
│   ├── pom.xml
│   └── src
│       └── main
│           └── java
│               └── com
│                   └── openfaas
│                       └── function
│                           └── Handler.java
└── template.yml

8 directories, 6 files
  • With a template, you can create a function. Execute the following command to create a function named FAAS simplejava8demo:
faas-cli new faas-simplejava8demo --lang simplejava8 -p bolingcavalry
  • The console prompts as follows. At this time, a new folder FAAS simplejava8demo is added in the current directory, which is the code directory of the new function:
[root@hedy 07]# faas-cli new faas-simplejava8demo --lang simplejava8 -p bolingcavalry
Folder: faas-simplejava8demo created.
  ___                   _____           ____
 / _ \ _ __   ___ _ __ |  ___|_ _  __ _/ ___|
| | | | '_ \ / _ \ '_ \| |_ / _` |/ _` \___ \
| |_| | |_) |  __/ | | |  _| (_| | (_| |___) |
 \___/| .__/ \___|_| |_|_|  \__,_|\__,_|____/
      |_|


Function created in folder: faas-simplejava8demo
Stack file written: faas-simplejava8demo.yml

Notes:
You have created a function using the java8 and maven template
[root@hedy 07]# ls
faas-simplejava8demo  faas-simplejava8demo.yml  template
  • The contents of the folder FAAS simplejava8demo are as follows. Now it's ready. Use IDE tools such as IDEA to import it in the form of maven project, and then modify the project according to business needs:
[root@hedy 07]# tree faas-simplejava8demo
faas-simplejava8demo
├── java8maven.iml
├── m2
│   └── settings.xml
├── pom.xml
└── src
    └── main
        └── java
            └── com
                └── openfaas
                    └── function
                        └── Handler.java

7 directories, 4 files
  • Now the business can be developed. For testing, a new line of code is added, as shown in the red box below:
  • Start compiling the build and execute the following command:
faas-cli build -f ./faas-simplejava8demo.yml
  • After the construction is completed, push the image to the image warehouse so that Kubernetes can download the image. I use hub.docker.com here because my ID is bolingcavalry. The push can be successful by executing the following commands (log in by executing the docker login command first):
docker push bolingcavalry/faas-simplejava8demo:latest
  • Execute the following command to deploy the function to OpenFaaS:
faas-cli deploy -f faas-simplejava8demo.yml
  • The console response is as follows. It can be seen that the deployment has started and the endpoint is given:
[root@hedy 07]# faas-cli deploy -f faas-simplejava8demo.yml
Deploying: faas-simplejava8demo.
WARNING! You are not using an encrypted connection to the gateway, consider using HTTPS.

Deployed. 202 Accepted.
URL: http://192.168.50.75:31112/function/faas-simplejava8demo.openfaas-fn
  • Open the web side, and the newly added function can be seen on the page. The verification operation is shown in the figure below. It can be seen that the JSON content of the input parameter can be parsed normally:
  • You can also test with curl command on the console:
[root@hedy 07]# curl \
> -H "Content-Type: application/json" \
> -X POST \
> --data '{"name":"Jerry}' \
> http://192.168.50.75:31112/function/faas-simplejava8demo
{"success":true,"foo":"bar","message":"Hello anonymous, response from [10.244.0.168], PID [14], 2021-03-07 03:32:15"}

clear

  • The command to delete the function is as follows, which is still the directory of faas-simplejava8demo.yml:
faas-cli remove -f faas-simplejava8demo.yml
  • So far, we have gone through the self-made maven+jdk8 template from development to verification. I believe your understanding of OpenFaaS has been more comprehensive and in-depth. This article is for developing templates and has little practical value. In the next article, we will make a practical template: jdk8+maven+springboot

Posted by goodtimeassured on Mon, 06 Dec 2021 18:59:16 -0800