剖析组件应用市场的安装

java运维管理平台怎么支持组件的安装呢?怎么能够达到和应用市场一样的效果呢?效果图如下:
应用商店支持的组件列表:
在这里插入图片描述
应用商店组件的安装:
在这里插入图片描述
在这里插入图片描述

1.组件包的制作

1.1 组件包结构

package:用于存放实际组件包安装包,必须有xxx-版本号.tar.gz安装包
scripts:用于存放安装脚本,脚本必须包含install.sh,可以包含初始化脚本init.sh
xxx.svg:应用组件的svg图,如:jdk.svg
xxx.yaml:应用组件基本配置项
xxx.yaml参数说名:

kind(必填): 类型定义,发布基础组件时 ,指定类型为 component (类型:string)
name(必填): 组件在平台显示的名称,请与组件目录名称保持一致,建议字符:英文、数字、_ (类型:string)
version(必填): 上传后显示的组件版本,建议字符: 数字、字母、_ 、. (类型:string)
description: 组件描述信息,建议长度256字符之内,请针对组件书写贴切的描述文字 (类型:string)
labels(必填): 组件所属标签,请针对组件功能设置准确标签,平台会针对该标签对组件进行分类,(类型:list[string,string...]),目前只支持【数据库 中间件 环境组件 服务协调】
auto_launch: 指定该服务安装后是否需要启动 (类型:boolean)
base_env: 指定组件是否为基础环境组件,如 jdk, 该类组件以基础环境方式安装 (类型:boolean)
ports: 定义组件所需端口号,如不启用端口,可留空 (类型:list[map,map...])
monitor: 组件监控相关配置,定义该组件在安装后如何监控 ,如果不需要监控可留空 (类型: map)
install(必填): 定义安装组件时所需参数,该参数会传入到 安装脚本中 (类型:list[map,map...]name: 传入参数中文描述名称,该名称会在用户安装组件时显示到表单中 (类型: string)
  key: 传入参数key值,会将该key与值 传入到安装脚本中 (类型:string)
  default: 上面key默认值 (类型: stirng)

1.2 package下xx-版本号.tar.gz结构

conf:存放组件包自定义的配置
lib:存放组件包jar包
app.sh(必须存在):用于启动、停止、重启服务命令
在这里插入图片描述

1.3 组件包制作示例

1.3.1 package改造

安装包需要打包成tar.gz安装包,安装包必须包含app.sh和lib目录里。
lib是组件的jar包
conf是组件所需的配置信息
app.sh是启动组件的执行脚本;(此脚本示例只适用于java -jar的启动,其他非springboot项目不适用,如果有自己自动脚本的可以参考这个进行改造app.sh的内容)

#!/bin/sh
PRG="$0"

while [ -h "$PRG" ] ; do
  ls=`ls -ld "$PRG"`
  link=`expr "$ls" : '.*-> \(.*\)$'`
  if expr "$link" : '/.*' > /dev/null; then
    PRG="$link"
  else
  PRG=`dirname "$PRG"`/"$link"
  fi
done

PRGDIR=`dirname "$PRG"`

SERVICE_HOME=`cd "$PRGDIR" >/dev/null; pwd`
SERVICE_PID=$SERVICE_HOME/pid
JAVA_OPTS="-Xms128m -Xmx128m"

start() {
   
    echo "Waiting for service start ...."
    nohup java $JAVA_OPTS -jar $JAVA_OPTS $SERVICE_HOME/lib/agent-*.jar --spring.config.location=$SERVICE_HOME/config/application.properties > /dev/null 2>&1 &
    pid=$!
    echo $pid > $SERVICE_HOME/pid
    sleep 5
    checkPidAlive
    if [[ (( $? -eq 0 )) ]]; then
        echo "Service started(pid=$pid)!"
        exit 0
    else
        echo "Service failed to start!"
        exit 1
    fi
}

stop() {
   
  echo "Waiting for service stop ...."  
  for i in `ls -t $SERVICE_PID 2>/dev/null`
    do
        read pid < $i
        # 判断进程号是否存在
        if ps -p $pid > /dev/null 2>&1; then
            kill  "$pid"
            sleep 5
            checkPidAlive
            if [[ (( $? -eq 0 )) ]]; then
                echo "Service failed to stop!"
                exit 0
            else
                echo "" > $i
                echo "Service stoped(pid=$pid)!"
                exit 1
            fi
        else
            echo "" > $i
            echo "Service has bean stoped!"
            return 0
        fi
    done
}

status() {
   
    checkPidAlive
    if [[ (( $? -eq 0 )) ]]; then
        echo '{"status":"UP"}'
        exit 0
    else
        echo '{"status":"DOWN"}'
        exit 1
    fi
}

checkPidAlive() {
   
    for i in `ls -t $SERVICE_PID 2>/dev/null`
    do
        read pid < $i
        # 判断进程号是否存在
        if ps -p $pid > /dev/null 2>&1; then
            return 0
        else
            return 1
        fi
    done
    return 1
}

# See how we were called.
case "$1" in
  start)
        start
        ;;
  stop)
        stop
        ;;
  restart)
        stop
        start
        ;;
  status)
        status
        ;;
  *)
        echo $"Usage: $0 {start|stop|restart|status}"
        exit 1
esac
1.3.2 scripts改造

存放install.sh主要是解压package中的安装包到对应目录

#!/bin/bash

cd "$(dirname $0)"

arg="$1"
if test $# -lt 1; then
    echo -e "缺少参数"
    exit 1
fi

# 获取解压后文件名
Get_untar_name() {
   
    local src_tar_path=$1

    local untar_raw_install_package_name
    if [ ! -f "$src_tar_path" ]; then
        echo -e "压缩文件${src_tar_path}不存在"
        exit 1
    fi
    untar_raw_install_package_name=$(timeout 2 tar tf "$src_tar_path"  | tail -n 1 | tr '/' ' '| awk '{print $1}')
    if [ "$untar_raw_install_package_name" = "" ]; then
        echo "解压${src_tar_path}错误"
    fi
    echo "$untar_raw_install_package_name"
}


IFS="," read -r -a all_param <<< "$arg"
for param in "${all_param[@]}"; do 
    param=$(echo "$param" | sed 's/^[ \t]*//g' | sed 's/[ \t]*$//g')
    if [ "$param" = "" ]; then
        continue
    fi
    value=$(echo "${param}" | sed -n "s/^[ |        ]*base_dir[ | ]*=[ |   ]*\(.*\)[ |     ]*/\1/p")    
    if [ "$value" != "" ]; then 
        base_dir="${value}";
        break
    fi
done

mkdir -p "${base_dir}"
tar_file="../package/agent.tar.gz"
untar_name=$(Get_untar_name $tar_file)
install_dir="${base_dir}/${untar_name}"
tar xf $tar_file -C "$base_dir" 
echo "install_dir=$install_dir"
1.3.3 xxx.yaml改造
# 类型定义,发布基础组件时 ,指定类型为 component (类型:string)
kind: component
# 组件在平台显示的名称,请与组件目录名称保持一致,建议字符:英文、数字、_ (类型:string)
name: adfs-server
# 上传后显示的组件版本,建议字符: 数字、字母、_ 、. (类型:string)
version: "2.6.1"
# 组件描述信息,建议长度256字符之内,请针对组件书写贴切的描述文字 (类型:string)
description: "文件网关组件,主要用于文件节点间的传输功能"
# 组件所属标签,请针对组件功能设置准确标签,平台会针对该标签对组件进行分类,(类型:list[string,string...])
labels:
  - "环境组件"
# 指定该服务安装后是否需要启动 (类型:boolean)
auto_launch: false
# 指定组件是否为基础环境组件,如 jdk, 该类组件以基础环境方式安装 (类型:boolean)
base_env: 
# 定义组件所需端口号,如不启用端口,可留空 (类型:list[map,map...])
ports:
# 组件监控相关配置,定义该组件在安装后如何监控 ,如果不需要监控可留空 (类型: map)
monitor:
# 定义安装组件时所需参数,该参数会传入到 安装脚本中 (类型:list[map,map...])
install:
    # 传入参数中文描述名称,该名称会在用户安装组件时显示到表单中 (类型: string)
  - name: "安装目录"
    # 传入参数key值,会将该key与值 传入到安装脚本中 (类型:string)
    key: base_dir
    # 上面key默认值 (类型: stirng)
    default: "{data_path}/adfs"  # 注: {data_path} 为主机数据目录占位符,请勿使用其他代替
# 定义该组件安装所需依赖组件名称与版本,如不需其他组件依赖,可留空 (类型: list[map,map..])
dependencies:
  - name: java
    version: 1.8.0_201
# 该组件所需最小资源需求 (类型:map)
# 定义安装组件时所需参数,该参数会传入到 安装脚本中 (类型:list[map,map...])
install:
    # 传入参数中文描述名称,该名称会在用户安装组件时显示到表单中 (类型: string)
  - name: "安装目录"
    # 传入参数key值,会将该key与值 传入到安装脚本中 (类型:string)
    key: base_dir
    # 上面key默认值 (类型: string)
    default: "{data_path}/app/adfs"  # 注: {data_path} 为主机数据目录占位符,请勿使用其他代替
  - name: "日志目录"
    key: log_dir
    default: "{data_path}/logs/adfs"
  - name: "数据目录"
    key: data_dir
    default: "{data_path}/appData/adfs"

1.4 打包成组件包

将制作的包用cmd命令在组件包外层执行tar 打成tar.gz包,上传到应用市场即可

tar -zcvf xxx.tar.gz xxx

1.5 组件包安装核心代码

	boolean executeInstallShell(ComponentConfigRecordPO record, ComponentInstallPO install, ComponentPO componentPO) {
   
        //TODO 这里主机密码需要从主机表中获取并解密
        FileInfoPO fileInfoPO = fileInfoService.getBaseMapper().selectById(componentPO.getFileId());
        if (fileInfoPO == null) {
   
            throw new BusinessException(AppComponentResponseCode.FILE_NOT_FOUND, componentPO.getFileId());
        }
        File temp = fileInfoService.loadFileToTemp(fileInfoPO.getFileId(), fileInfoPO.getName());
        record.setDataChangeCreatedTime(LocalDateTimeUtil.now());

        String remoteFilePath = JschUtils.formatUserPath(record.getInstallUser()) + record.getInstallPath() + StrUtil.C_SLASH + componentPO.getInstallPackageName();
        Session session = JschUtil.openSession(record.getIp(), 22, record.getInstallUser(), record.getInstallPassword());
        log.info("成功连接到主机{}", record.getIp());
        String result = JschUtil.exec(session, "mkdir -p " + JschUtils.formatMkdirPath(record.getInstallPath()), Charset.defaultCharset());
        log.info("开始发送组件包到主机{}", record.getIp());
        try {
   
            JschUtils.uploadFile(record.getIp(), record.getInstallUser(), record.getInstallPassword(), 22, temp.getAbsolutePath(), remoteFilePath);
        } catch (Exception e) {
   
            log.info("开始发送组件包到主机{}发生异常,安装失败", record.getIp());
            record.setDataChangeLastModifiedTime(LocalDateTimeUtil.now());          
            configRecordMapper.updateById(record);
            return false;
        }
        log.info("接收组件安装包到主机{}成功", record.getIp());
        result = JschUtil.exec(session, "tar -zxvf " + JschUtils.formatUserPath(record.getInstallUser()) + record.getInstallPath() + StrUtil.C_SLASH + componentPO.getInstallPackageName(), Charset.defaultCharset());
        log.info("开始解压组件安装包{}成功", componentPO.getInstallPackageName());
        log.info("开始执行组件安装脚本");
        result = JschUtil.exec(session, "sh " + JschUtils.formatUserPath(record.getInstallUser()) + StrUtil.C_SLASH + componentPO.getComponentName() + StrUtil.C_SLASH + Const.APP_COMPONENT_INSTALL + " base_dir=" + JschUtils.formatUserPath(record.getInstallUser()) + record.getInstallPath(), Charset.defaultCharset());
        log.info("执行组件安装脚本成功");
        String cmd = "source /etc/profile && sh cmd start;sh " + JschUtils.formatUserPath(record.getInstallUser()) + record.getInstallPath() + StrUtil.C_SLASH + componentPO.getComponentName() + StrUtil.C_SLASH + Const.APP_INSTACE_RESTART_SHELL;
        log.info("开始执行组件启动脚本:{}", cmd);
        result = JschUtil.exec(session, cmd, Charset.defaultCharset());
        log.info("执行组件启动脚本成功");
        log.info("主机{}安装组件【{}】结束", record.getIp(), install.getComponentName());
        JschUtil.close(session);
        record.setDataChangeLastModifiedTime(LocalDateTimeUtil.now());
        //安装完成之后更新安装状态
        record.setStatus(ComponentInstallStatusEnum.INSTALL_SUCCESS.getValue());
        configRecordMapper.updateById(record);
        //4 安装实例表入库  【注意】安装成功才有实例,失败没有实例
        createServiceInstance(record, install, componentPO);
        return true;
    }

完整代码需要后续整理之后会上传码云,到时候也会同步更新到文章中,尽请期待!

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-01-23 07:52:03       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-01-23 07:52:03       101 阅读
  3. 在Django里面运行非项目文件

    2024-01-23 07:52:03       82 阅读
  4. Python语言-面向对象

    2024-01-23 07:52:03       91 阅读

热门阅读

  1. 笨蛋学设计模式行为型模式-命令模式【19】

    2024-01-23 07:52:03       46 阅读
  2. Spring 使用@Value注解读取配置文件中的数组

    2024-01-23 07:52:03       58 阅读
  3. 【leetcode100-051到054】【图论】四题合集

    2024-01-23 07:52:03       63 阅读
  4. 笨蛋学设计模式行为型模式-状态模式【20】

    2024-01-23 07:52:03       60 阅读
  5. NIO和netty的常用类

    2024-01-23 07:52:03       52 阅读
  6. ES模糊查询不区分大写

    2024-01-23 07:52:03       59 阅读
  7. EGL + GBM + OPENGLES 最简实例

    2024-01-23 07:52:03       51 阅读
  8. 分布式 ID 的几种实现方式

    2024-01-23 07:52:03       60 阅读
  9. 如何利用PyTorch?

    2024-01-23 07:52:03       40 阅读