docker运行容器外命令及系统监控的思路

2022-08-23 14:27:03 浏览数 (1)

一.前言

hello,everyone.

技术在不断的进步,容器化部署也已经成为了众多公司选择服务部署的首选。可移植,可以独立管理,部署方便等等优点都是选择容器化部署的理由。这里以docker为例,如果我想在docker容器内部想要获取或者执行相关宿主机上的命令。相信很多devops团队的同学对这个应该比较熟悉。比如可以让用户查看当前系统的cpu使用情况。这个操作在java服务以jar包形式直接部署的形式获取这些数据是比较简单的。可以直接使用相关的三方开源库或者代码中调用linux命令就行。但是如果部署在容器里面,容器是独立的环境,jar包与直接调用就都不可行了。

本文将给大家提供一种思路,docker部署的java应用如何进行宿主机各种环境与系统监控,如有不对之处,欢迎指出。

二.解决方案

java想要调用宿主机上的命令,单独直接使用一连串的shell脚本进行交互式的大量操作是不可能的。只能是单独命令一个个执行,那么如何跨容器执行命令呢?

2.1.sshpass

网上其实大家比较推荐的方案是使用sshpass命令,比如我想查看宿主机1.2.3.4上的根目录下文件可以使用

sshpass -p 密码 -v ssh root@1.2.3.4 -p 22 'ls /'

这种方式的弊端在于,如果是toB场景,环境是运维给出安装包,整套部署。然后机器是用户的。代码中对于上面的sshpass命令的参数肯定是通过配置文件配置的。这个时候如果用户修改了宿主机的密码,你的指令就失效了,无法拿到指令返回的结果。

这种方案在toC场景下单服务部署还算可以解决上述问题,但是如果集群化部署,配置文件就要维护特别多,也存在密码一改,配置文件跟着改的问题。

综上,我个人不太推荐这种方案,天然劣势。

2.1.ssh免密

正常我们在ssh连接远程云主机的时候都是使用ssh root@ip -p 端口号,然后再输入密码的方式,一般的ssh工具提供了记住密码的方式,可以快速连接。如果java中执行上述操作,就会进入到宿主机中,后续的命令也调用不到了。

这里ssh提供了一种免密登录的方式。docker容器中生成私钥与公钥,然后将公钥保存在宿主机的 ~/.ssh/authorized_keys

这样宿主机就可以通过ssh在宿主机上执行命令了。

免密登录方式:SSH 三步解决免密登录

如果容器的部署形态研发不可随便操作的话,可以联系部门里面的运维在初始化环境与容器的时候就可以加入这个配置。

三.实际场景

主机监控是C端运维平台或者B端的业务平台比较常见的功能。知道在docker中如何访问宿主机后我们就可以来获取宿主机上的cpu,硬盘,内存等使用情况。

下面以部署在docker容器上的java应用获取磁盘使用情况为例【前提是在docker上已经配置好了ssh免密】

代码语言:javascript复制
/**
 * 系统基础信息
 *
 * @author baiyan
 */
@ApiModel("系统基础信息")
@Data
public class SystemInfoBaseDTO {
​
    @ApiModelProperty("总量")
    private Double total;
​
    @ApiModelProperty("使用")
    private Double used;
​
    @ApiModelProperty("空闲")
    private Double free;
​
    @ApiModelProperty("使用率")
    private Double usedRate;
​
}
代码语言:javascript复制
String menCommand = "ssh root@1.2.3.4 -p 22 df -h /|sed -n "2,1p" |awk '{print $2" "$3}'";
String memInfo = CmdUtil.executeLinuxCmd(menCommand);
String[] mem = memInfo.split(" ");
Double total = this.getNumber(mem[0]);
Double used = this.getNumber(mem[1]);
代码语言:javascript复制
/**
 * 磁盘信息获取
 */
private SystemInfoBaseDTO getBaseInfo(String command){
    SystemInfoBaseDTO info = new SystemInfoBaseDTO();
    String menCommand = "ssh root@1.2.3.4 -p 22 df -h /|sed -n "2,1p" |awk '{print $2" "$3}'";
    String memInfo = CmdUtil.executeLinuxCmd(menCommand);
    String[] mem = memInfo.split(" ");
    Double total = this.getNumber(mem[0]);
    Double used = this.getNumber(mem[1]);
    info.setTotal(total);
    info.setUsed(used);
    info.setFree(total - used);
    info.setUsedRate((double) Math.round(used/total * 10000)/100);
    return info;
}
代码语言:javascript复制
public class CmdUtil {
​
    /**
     * 执行命令
     *
     * 默认情况下在当前环境执行,即当前如果为docker部署则需要在docker中ssh拨至宿主机执行
     *
     * @param cmd
     * @return
     */
    public static String executeLinuxCmd(String cmd){
        String[] cmdArray = {"sh", "-c", cmd};
        return executeShell(true, cmdArray);
    }
​
    /**
     * 执行命令
     *
     * @param withLine: 多行时是否换行
     * @param cmd: 执行指令 第一个参数是可执行命令程序,其他的是命令行执行是需要的参数
     * @return: java.lang.String
     **/
    public static String executeShell(boolean withLine, String... cmd){
        return executeShell(null, withLine, cmd);
    }
​
    /**
     * 执行shell
     *
     * @param filePath: 默认值是当前进程的当前工作目录,通常根据系统属性 user.dir 来命名
     * @param withLine: 
     * @param cmd: 
     * @return: java.lang.String
     **/
    public static String executeShell(File filePath, boolean withLine, String... cmd){
        StringBuilder cmdResult = new StringBuilder();
        try {
            ProcessBuilder processBuilder = new ProcessBuilder(cmd);
            processBuilder.directory(filePath);
            Process process = processBuilder.redirectErrorStream(true).start();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))){
                String line;
​
                while ((line = reader.readLine()) != null){
                    cmdResult.append(line);
                    if (withLine){
                        cmdResult.append(System.lineSeparator());
                    }
                }
                log.debug(cmdResult.toString());
            }
            int existCode = process.waitFor();
            log.debug("execute cmd {} success, exist code {}", Arrays.toString(cmd), existCode);
        } catch (Exception e){
            log.error(e.getMessage(),e);
        }
        if (withLine){
            return cmdResult.substring(0, cmdResult.length()).trim();
        }
        return cmdResult.toString().trim();
    }
​
}

四.扩展想法

到这里为止,就获取到了宿主机上硬盘使用情况的数据。但是这里有一个比较坑的点,命令执行的时间与ssh连接的时间有关系,ssh如果说连接时间慢,那么整条命令执行的时间也会比较久。这还只是计算一个硬盘使用率,如果一个请求中需要获取到更多的宿主机上的内容的话,串行化,接口肯定会很卡。

因此这里可以定义一个定时任务去异步执行获取数据的任务,比如每隔30秒左右执行命令将数据进行落表。前端需要加载当前或者一段时间内系统运行状态的情况,可以直接加载表中的数据或者缓存中的数据。

定时任务扫描执行发现相关的系统指标超标的时候可以触发报警,调用钉钉或者短信接口通知相关的系统负责人解决。

是不是很像spring-admin或者一些市面上常见的运营监控系统。

分布式部署场景下,是不是可以去批量拉取注册中心上的ip与配置,再进行相关的参数数据获取,完善的线上监控体系就形成了。

0 人点赞