OpenFaaS實戰之八:自制模板(maven+jdk8)


歡迎訪問我的GitHub

https://github.com/zq2599/blog_demos

內容:所有原創文章分類匯總及配套源碼,涉及Java、Docker、Kubernetes、DevOPS等;

OpenFaaS實戰系列文章鏈接

  1. 部署
  2. 函數入門
  3. Java函數
  4. 模板操作(template)
  5. 大話watchdog
  6. of-watchdog(為性能而生)
  7. java11模板解析
  8. OpenFaaS實戰之八:自制模板(maven+jdk8)
  9. OpenFaaS實戰之九:終篇,自制模板(springboot+maven+jdk8)

本篇概覽

  • 本文是《OpenFaaS實戰》系列的第八篇,經過前面的理論分析和實戰練習,咱們對OpenFaaS了解得差不多了,也該搞事情了;
  • 作為一個Java程序員,經常用到jdk8、maven、springboot這些東西,自然要關注官方模板是否支持,如下圖,官方文檔顯示對java程序員的支持力度不夠:不支持java8、用的是Gradle而非maven、不支持springboot,僅用vertx框架來支持web服務:

在這里插入圖片描述

  • 既然官方模板不支持,咱們就自制模板來支持吧,本着先易后難的原則,本篇先做一個簡單的模板:將官方的java11模板保持功能不變,jdk版本改造成java8,並將Gradle改成maven;

  • 不可否認jdk8和maven都已一大把年紀了,新版jdk和Gradle都是更好的選擇,不過本篇的重點是如何自定義模板,所以還請您給予包容...

  • 今天要做的事情,如下圖所示,咱們先做左邊藍色部分,編寫模板代碼,上傳到github模板倉庫,再做右側綠色部分,像前面文章中使用官方模板那樣去使用這個模板:

在這里插入圖片描述

  • 接下來的實戰由以下內容組成:
  1. 創建java項目,作為模板的基礎源碼
  2. 開發Dockerfile
  3. 完成模板配置並上傳
  4. 驗證模板

創建java項目

  • 制作模板時最重要的就是提供完整的模板代碼,接下來就來制作吧;
  • 我這邊用的是IDEA,建一個空maven項目,名為java8maven,用的是JDK8:
  • 如下圖,注意Language level要選擇8

在這里插入圖片描述

  • pom.xml的內容如下,要注意的幾個點稍后會說明:
<?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>
  • 上述pom.xml的內容中,有幾處需要注意:
  1. openfaas的model和entrypoint這兩個jar是整個服務可運行的基礎;
  2. 有些常用的jar依賴也被加入了,您可以酌情自行增刪;
  3. 插件maven-compiler-plugin用來指定編譯時的JDK版本;
  4. 插件maven-dependency-pluginmaven-assembly-plugin用來將整個java代碼和依賴庫打包到一個jar文件中,這樣制作Docker鏡像會方便很多;
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();

    /**
     * 獲取本機IP地址
     * @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地址獲取失敗" + e.toString());
        }
        return "";
    }

    /**
     * 返回當前進程ID
     * @return
     */
    private static String getPID() {
        return ManagementFactory
                .getRuntimeMXBean()
                .getName()
                .split("@")[0];
    }


    private String getUserName(IRequest req) {
        // 如果從請求body中取不到userName,就用
        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();
        }

        // 如果從請求body中取不到userName,就給個默認值
        if(StringUtils.isBlank(userName)) {
            userName = "anonymous";
        }

        return userName;
    }

    public IResponse Handle(IRequest req) {

        String userName = getUserName(req);

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

        // 返回信息帶上當前JVM所在機器的IP、進程號、時間
        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);

        // 響應內容也是JSON格式,所以先存入map,然后再序列化
        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;
    }
}
  • pom.xml所在目錄下,新建文件夾m2,里面增加maven的配置文件settings.xml,該文件是在FaaS開發過程中,制作鏡像時用到的(制作鏡像時會編譯構建java項目),強烈建議在里面配置好您的maven私服,或者阿里雲鏡像,這樣制作鏡像時會快很多,我這里已經配置了阿里雲鏡像,依然耗時四分多鍾(如下圖),所以如果您有nexus3私服一定要優先考慮:

在這里插入圖片描述

  • 至此,編碼工作已完成,可見這就是個普通maven工程,來試試能不能正常運行;
  • 執行命令mvn clean package -U -DskipTests,成功后會在target目錄生成文件java8maven-1.0-SNAPSHOT-jar-with-dependencies.jar
  • 運行上述jar文件,命令是java -jar java8maven-1.0-SNAPSHOT-jar-with-dependencies.jar
  • 上述jar運行起來后會監聽8082端口的POST請求,我這里用postman來試試,如下圖,可以收到后台返回的最新數據:

在這里插入圖片描述

    • 后台控制台也會打印出預期的內容:

在這里插入圖片描述

    • 代碼寫完了,接下來要考慮的如何制作Docker鏡像,即Dockerfile的編寫;

開發Dockerfile

  • 前面的實戰中咱們已經體驗過,開發FaaS的時候會將代碼編譯構建制作成鏡像,因此對應的Dockerfile也要准備好,下面是完整的Dockerfile內容,已經添加詳細的注釋,就不再贅述了:
# 用maven鏡像作為基礎鏡像,用於編譯構建java項目
FROM maven:3.6.3-openjdk-8 as builder

WORKDIR /home/app

# 將整個項目都復制到/home/app目錄下
COPY . /home/app/

# 進入pom.xml所在目錄執行構建命令,指定m2/settings.xml文件作為配置文件,
# 請在settings.xml中配置好私服,否則構建速度極慢
RUN cd function && mvn clean package -U -DskipTests --settings ./m2/settings.xml 

# of-watchdog里面有二進制文件watchdog,制作鏡像時要用到
FROM openfaas/of-watchdog:0.7.6 as watchdog

# openjdk鏡像是容器的運行環境
FROM openjdk:8-jre-slim as ship

# 為了安全起見,在生產環境運行容器時不要用指root帳號和群組
RUN addgroup --system app \
    && adduser --system --ingroup app app

# 從of-watchdog鏡像中復制二進制文件fwatchdog,這是容器的啟動進程
COPY --from=watchdog /fwatchdog /usr/bin/fwatchdog

# 賦予可執行權限
RUN chmod +x /usr/bin/fwatchdog

WORKDIR /home/app

# 前面用maven編譯構建完畢后,這里將構建結果復制到鏡像中
COPY --from=builder /home/app/function/target/java8maven-1.0-SNAPSHOT-jar-with-dependencies.jar ./java8maven-1.0-SNAPSHOT-jar-with-dependencies.jar
# 指定容器的運行帳號
user app

# 指定容器的工作目錄
WORKDIR /home/app/

# fwatchdog收到web請求后的轉發地址,java進程監聽的就是這個端口
ENV upstream_url="http://127.0.0.1:8082"

# 運行模式是http
ENV mode="http"

# 拉起業務進程的命令,這里就是啟動java進程
ENV fprocess="java -jar java8maven-1.0-SNAPSHOT-jar-with-dependencies.jar"

# 容器對外暴露的端口,也就是fwatchdog進程監聽的端口
EXPOSE 8080

# 健康檢查
HEALTHCHECK --interval=5s CMD [ -e /tmp/.lock ] || exit 1

# 容器啟動命令,這里是執行二進制文件fwatchdog
CMD ["fwatchdog"]

模板配置

  • 現在材料已經准備完畢了,再整理一下准備提交到github上,就可以作為OpenFaaS模板使用了;
  1. 新建一個文件夾,名為simplejava8
  2. simplejava8目錄下新建文件template.yml,內容如下:
language: simplejava8
welcome_message: |
  You have created a function using the java8 and maven template
  1. 將前面的Dockerfile文件復制到simplejava8目錄下;
  2. 前面咱們創建的maven工程,最外層的文件夾名為java8maven,請將此文件夾改名為function,然后將整個文件夾都復制到simplejava8目錄下;
  3. 此刻的simplejava8目錄下應該是這些內容:
[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. 將這些內容全部上傳到github上,我這里路徑是https://github.com/zq2599/openfaas-templates/tree/master/template,這里面已經有三個模板了,本次新增的如下圖紅框:

在這里插入圖片描述

  • 至此,模板制作完成,接下來驗證此模板是否可用;

驗證模板

  • 接下來要做的,就是下圖右側的綠色部分:

在這里插入圖片描述

  • 登錄一台配好OpenFaaS客戶端的電腦,找個干凈目錄執行以下命令,將github上所有模板下載下來:
faas template pull https://github.com/zq2599/openfaas-templates
  • 控制台響應如下,提示下載了三個模板,符合預期:
[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
  • faas new --list查看列表如下:
[root@hedy 07]# faas new --list
Languages available as templates:
- dockerfile
- java11extend
- simplejava8
  • 看看template/simplejava8目錄下的內容,和前面上傳的一模一樣:
[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
  • 有了模板就可以創建函數了,執行以下命令創建名為faas-simplejava8demo的函數:
faas-cli new faas-simplejava8demo --lang simplejava8 -p bolingcavalry
  • 控制台提示如下,此時當前目錄下新增文件夾faas-simplejava8demo,這就是新建函數的代碼目錄:
[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
  • 文件夾faas-simplejava8demo的內容如下,現在妥了,用IDEA等IDE工具以maven工程形式導入,然后根據業務需求修改這個工程即可:
[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
  • 現在可以開發業務了,這里為了測試,新增了一行代碼,如下圖紅框:

在這里插入圖片描述

  • 開始編譯構建吧,執行以下命令:
faas-cli build -f ./faas-simplejava8demo.yml
  • 構建完成后將鏡像推送到鏡像倉庫,以便Kubernetes可以下載到此鏡像,我這里用的是hub.docker.com,因為我的ID是bolingcavalry,所執行以下命令即可推送成功(要先執行docker login命令登錄):
docker push bolingcavalry/faas-simplejava8demo:latest
  • 執行以下命令部署函數到OpenFaaS:
faas-cli deploy -f faas-simplejava8demo.yml
  • 控制台響應如下,可見部署已經開始,並且給出了endpoint:
[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
  • 打開web端,在頁面上可見新增的函數,驗證操作如下圖所示,可見入參的JSON內容可以被正常解析:

在這里插入圖片描述

  • 也可以在控制台用curl命令測試:
[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"}

清理

  • 刪除函數的命令如下,依舊是faas-simplejava8demo.yml所在目錄:
faas-cli remove -f faas-simplejava8demo.yml
  • 至此,自制的maven+jdk8的模板,從開發到驗證咱們已經全部走了一遍,相信您對OpenFaaS的理解也已經更加全面和深入了,本篇是為開發模板練手用的,實用價值不大,接下來的文章咱們要做個實用的模板:jdk8+maven+springboot

你不孤單,欣宸原創一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 數據庫+中間件系列
  6. DevOps系列

歡迎關注公眾號:程序員欣宸

微信搜索「程序員欣宸」,我是欣宸,期待與您一同暢游Java世界...
https://github.com/zq2599/blog_demos


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM