美文网首页
OpenFaaS实战之八:自制模板(maven+jdk8)

OpenFaaS实战之八:自制模板(maven+jdk8)

作者: 程序员欣宸 | 来源:发表于2021-05-26 08:14 被阅读0次

    欢迎访问我的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项目,名为<font color="blue">java8maven</font>,用的是JDK8:
    • 如下图,注意<font color="blue">Language level</font>要选择<font color="red">8</font>:
    在这里插入图片描述
    • 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. 插件<font color="blue">maven-compiler-plugin</font>用来指定编译时的JDK版本;
    4. 插件<font color="blue">maven-dependency-plugin</font>和<font color="blue">maven-assembly-plugin</font>用来将整个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所在目录下,新建文件夹<font color="blue">m2</font>,里面增加maven的配置文件settings.xml,该文件是在FaaS开发过程中,制作镜像时用到的(制作镜像时会编译构建java项目),强烈建议在里面配置好您的maven私服,或者阿里云镜像,这样制作镜像时会快很多,我这里已经配置了阿里云镜像,依然耗时四分多钟(如下图),所以如果您有nexus3私服一定要优先考虑:
    在这里插入图片描述
    • 至此,编码工作已完成,可见这就是个普通maven工程,来试试能不能正常运行;
    • 执行命令<font color="blue">mvn clean package -U -DskipTests</font>,成功后会在<font color="blue">target</font>目录生成文件<font color="red">java8maven-1.0-SNAPSHOT-jar-with-dependencies.jar</font>;
    • 运行上述jar文件,命令是<font color="blue">java -jar java8maven-1.0-SNAPSHOT-jar-with-dependencies.jar</font>;
    • 上述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. 新建一个文件夹,名为<font color="blue">simplejava8</font>;
    2. <font color="blue">simplejava8</font>目录下新建文件<font color="red">template.yml</font>,内容如下:
    language: simplejava8
    welcome_message: |
      You have created a function using the java8 and maven template
    
    1. 将前面的<font color="red">Dockerfile</font>文件复制到<font color="blue">simplejava8</font>目录下;
    2. 前面咱们创建的maven工程,最外层的文件夹名为<font color="blue">java8maven</font>,请将此文件夹改名为<font color="red">function</font>,然后将整个文件夹都复制到<font color="blue">simplejava8</font>目录下;
    3. 此刻的<font color="blue">simplejava8</font>目录下应该是这些内容:
    [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上,我这里路径是<font color="blue">https://github.com/zq2599/openfaas-templates/tree/master/template</font>,这里面已经有三个模板了,本次新增的如下图红框:
    在这里插入图片描述
    • 至此,模板制作完成,接下来验证此模板是否可用;

    验证模板

    • 接下来要做的,就是下图右侧的绿色部分:
    在这里插入图片描述
    • 登录一台配好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
    
    • 用<font color="blue">faas new --list</font>查看列表如下:
    [root@hedy 07]# faas new --list
    Languages available as templates:
    - dockerfile
    - java11extend
    - simplejava8
    
    • 看看<font color="blue">template/simplejava8</font>目录下的内容,和前面上传的一模一样:
    [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
    
    • 有了模板就可以创建函数了,执行以下命令创建名为<font color="blue">faas-simplejava8demo</font>的函数:
    faas-cli new faas-simplejava8demo --lang simplejava8 -p bolingcavalry
    
    • 控制台提示如下,此时当前目录下新增文件夹<font color="blue">faas-simplejava8demo</font>,这就是新建函数的代码目录:
    [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
    
    • 文件夹<font color="blue">faas-simplejava8demo</font>的内容如下,现在妥了,用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"}
    

    清理

    • 删除函数的命令如下,依旧是<font color="blue">faas-simplejava8demo.yml</font>所在目录:
    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

    相关文章

      网友评论

          本文标题:OpenFaaS实战之八:自制模板(maven+jdk8)

          本文链接:https://www.haomeiwen.com/subject/ntghsltx.html