iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Java对接ansible自动运维化平台方式
  • 318
分享到

Java对接ansible自动运维化平台方式

Java对接ansible自动运维化平台Javaansible 2023-05-14 09:05:41 318人浏览 薄情痞子

Python 官方文档:入门教程 => 点击学习

摘要

目录Java对接ansible自动运维化平台实现文件采集分发场景说明及ansible yum安装Java代码实现文件分发POI创建文件工具类创建主机组配置文件实现文件分发文件采集总结

Java对接ansible自动运维化平台实现文件采集分发

经过大量查阅,网上使用Java对接ansible自动运维化平台的示例代码几乎没有,为了方便自己后期巩固以及有需要的小伙伴,特以记录!!!

此次对接主要为以下两个功能:

  • 文件采集(对文件进行批量操作,包括批量从多台主机中采集共性文件如日志文件)
  • 文件分发(对文件进行批量操作,包括批量从多台主机中分发共性文件如日志文件)

场景说明及ansible yum安装

因ansible没有windows的安装包,所以为了方便测试,搭建了一套linux环境进行后续工作。

此次采用yum方式安装,在采用yum方式安装Ansible,首先安装EPEL源。

yum install -y Http://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm

查看EPEL源中的Ansible版本

yum info ansible

直接安装此版本,如果有其他要求,请调整源,安装其他ansible版本

yum install -y ansible

安装完成之后,查看ansible版本信息

ansible --version

配置Ansible服务器清单

清单文件/etc/ansible/hosts,在此文件中编写节点主机的对应IP地址和端口

我这里只是做一个演示,其中IP后面可以添加节点真实的ssh的端口,在定义的内容上面有一个[]列表,里面的内容为自定义内容,方面为了操作绑定的节点主机,我习惯称之为分组列表

简单的认证一下,Ping一下添加的主机

成功安装ansible !!

Java代码实现文件分发

顾名思义,文件分发就是把本机的文件分发到多个主机。

这时候就需要 Apache POI(大家可以去导入对应的包)来创建本机的文件了(ansible Host配置文件也通过POI创建)

POI创建文件工具类

package com.tiduyun.cmp.operation.utils;

import com.tiduyun.cmp.common.model.operation.HostInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;


@Slf4j
@Component
public class AnsibleCreateFileUtils {
    private final static String filename = "hosts";

    public static String passWordConnect(List<HostInfo> hostInfo, String hostGroup , String directory) throws IOException{
        


        // 创建文件夹对象 创建文件对象
        File folder = new File(directory);
        // 如果文件夹不存在 就创建一个空的文件夹
        if (!folder.exists()) {
            log.info("创建了文件夹{}" , folder);
            folder.mkdirs();
        }
        File file = new File(directory, filename);
        // 如果文件不存在 就创建一个空的文件
        if (!file.exists()) {
            try {
                log.info("创建了文件{}" , file);
                file.createNewFile();
            } catch (IOException e) {
                log.error("error data{}" , e);
            }
        }
        // 写入数据
        // 创建文件字节输出流
        FileOutputStream fos = new FileOutputStream(file);
        try {
            List<String> list = new ArrayList<>();
            for (HostInfo data : hostInfo) {
                // 开始写
                String string = data.getHost() + " ansible_ssh_pass=" + data.getPasswd() + " ansible_ssh_user="
                    + data.getAccount() + " ansible_ssh_port=" + data.getPort();
                list.add(string);
            }
            String splicingData = StringUtils.join(list, "\n");
            String str = "[" + hostGroup + "]" + "\n" + splicingData;
            byte[] bytes = str.getBytes();
            // 将byte数组中的所有数据全部写入
            fos.write(bytes);
            fos.flush();
            log.info("文件内容{}" , str);
            // 删除文件
            // deleteFile(file);
            // 关闭流

        } catch (IOException e) {
            log.error("error data{}" , e);
            throw e;
        }finally {
            if (fos != null) {
                fos.close();
            }
        }
        return directory;
    }

    public static void deleteFile(File file) {
        if (file.exists()) {// 判断路径是否存在
            if (file.isFile()) {// boolean isFile():测试此抽象路径名表示的文件是否是一个标准文件。
                file.delete();
            } else {// 不是文件,对于文件夹的操作
                    // 保存 路径D:/1/新建文件夹2 下的所有的文件和文件夹到listFiles数组中
                File[] listFiles = file.listFiles();// listFiles方法:返回file路径下所有文件和文件夹的绝对路径
                for (File file2 : listFiles) {
                    
                    deleteFile(file2);
                }
            }
            file.delete();
        } else {
            log.error("该file路径不存在!!");
        }

    }
}

创建主机组配置文件

:ansible分为两种连接方式,这里采用的是密钥连接,生成的文件已拼接密钥!!!后续的采集与分发都要用到这个。(如有不懂的小伙伴,可以去查找一下ansible的连接方式)

    @Override
    public void ansibleCreateHost(HostInfo hostInfo, String Key) {
        ParamCheckUtils.notNull(hostInfo, "hostInfo");

        List<HostInfo> HostIp = Arrays.asList(hostInfo);
        for (HostInfo data : HostIp) {
            String ansiblePassWd = data.getPasswd();
            String PassWd = hostInfoService.decode(ansiblePassWd);
            data.setPasswd(PassWd);
        }
        try {
            AnsibleCreateFileUtils.passWordConnect(HostIp, ansibleConfigurationItemVo.getHostGroup(),
                ansibleConfigurationItemVo.getDirectory());
        } catch (IOException e) {
            log.error("Failed to create host configuration{}", e);
        }
    }

实现文件分发

主机配置文件已经配置好,接下来就是执行ansible对应的命令,通过Java拼接ansible命令。

执行命令工具类


package com.tiduyun.cmp.operation.utils;

import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.NIO.charset.Charset;

import static cn.hutool.db.DbUtil.close;


@Slf4j
public class AnsibleExecuteTheOrderUtils {

    private final static String commandBin = "/bin/sh";

    private final static String commandC = "-c";


    
    public static void createRemoteDirectory(String hostGroup, String remotePath, String directory) throws IOException {
        Runtime run = Runtime.getRuntime();
        String[] cmds = new String[3];
        cmds[0] = commandBin;
        cmds[1] = commandC;
        cmds[2] =
                "ansible " + hostGroup + " -m command -a " + "\"mkdir " + remotePath + "\"" + " -i " + directory + "/hosts";

        // 执行CMD命令
        Process p = run.exec(cmds);
        log.info("ansible远程执行命令为{}", cmds[2]);

        BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream(), Charset.forName("UTF-8")));
        try {
            String lineMes;
            while ((lineMes = br.readLine()) != null)
                log.info(lineMes);// 打印输出信息
            try {
                // 检查命令是否执行失败。
                if (p.waitFor() != 0) {
                    if (p.exitValue() == 1)// 0表示正常结束,1:非正常结束
                        log.error("命令执行失败");
                }
            } catch (InterruptedException e) {
                log.error("error data{}", e);
            }
        } catch (IOException e) {
            log.error("fail to carry out command{}", e);
            throw e;
        } finally {
            if (br != null) {
                br.close();
            }
        }

    }

    
    public static void upload(String hostGroup, String localPath, String remotePath, String directory)
        throws IOException {
        Runtime run = Runtime.getRuntime();
        String[] cmds = new String[3];
        cmds[0] = commandBin;
        cmds[1] = commandC;
        cmds[2] = "ansible " + hostGroup + " -m copy -a " + "\"src=" + localPath + " dest=" + remotePath + "\"" + " -i "
            + directory + "/hosts";
        // 执行CMD命令
        Process p = run.exec(cmds);
        log.info("ansible命令为{}", cmds[2]);

        BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream(), Charset.forName("UTF-8")));
        try {
            String lineMes;
            while ((lineMes = br.readLine()) != null)
                log.info("ansible输出信息为 :" + lineMes);// 打印输出信息
            try {
                // 检查命令是否执行失败。
                if (p.waitFor() != 0) {
                    if (p.exitValue() == 1)// 0表示正常结束,1:非正常结束
                        log.error("命令执行失败");
                }
            } catch (InterruptedException e) {
                log.error("error data{}", e);
            }
        } catch (IOException e) {
            log.error("fail to carry out command{}", e);
            throw e;
        } finally {
            if (br != null) {
                br.close();
            }
        }

    }

    
    public static void fileCollection(String hostGroup, String remotePath, String localPath , String directory) throws IOException {
        Runtime run = Runtime.getRuntime();
        String[] cmds = new String[3];
        cmds[0] = commandBin;
        cmds[1] = commandC;
        cmds[2] = "ansible " + hostGroup + " -m fetch -a " + "\"src=" + remotePath + " dest=" + localPath + " force=yes backup=yes\"" + " -i "
                + directory + "/hosts";

        // 执行CMD命令
        Process p = run.exec(cmds);
        log.info("ansible远程采集文件命令为{}", cmds[2]);

        BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream(), Charset.forName("UTF-8")));
        try {
            String lineMes;
            while ((lineMes = br.readLine()) != null)
                log.info(lineMes);// 打印输出信息
            try {
                // 检查命令是否执行失败。
                if (p.waitFor() != 0) {
                    if (p.exitValue() == 1)// 0表示正常结束,1:非正常结束
                        log.error("命令执行失败");
                }
            } catch (InterruptedException e) {
                log.error("error data{}", e);
            }
        } catch (IOException e) {
            log.error("fail to carry out command{}", e);
            throw e;
        } finally {
            if (br != null) {
                br.close();
            }
        }

    }

    public static void ExecuteTheOrder(String command) throws IOException {
        log.info("start execute cmd {}", command);

        String[] cmd = new String[] {"/bin/bash", "-c", command};
        Runtime run = Runtime.getRuntime();
        Process p = run.exec(cmd); // 执行CMD命令

        BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream(), Charset.forName("UTF-8")));
        try {
            String lineMes;
            while ((lineMes = br.readLine()) != null)
                log.info("输出信息为 {}", lineMes);// 打印输出信息
            try {
                // 检查命令是否执行失败。
                if (p.waitFor() != 0) {
                    if (p.exitValue() == 1)// 0表示正常结束,1:非正常结束
                        log.error("命令执行失败");
                }
            } catch (InterruptedException e) {
                log.error("error data{}", e);
            }

        } catch (IOException e) {
            log.error("fail to carry out command{}", e);
            throw e;
        } finally {
            if (br != null) {
                br.close();
            }
        }
    }

    public static void disconnect() {
        try {
            close();
        } catch (Exception ex) {
            // Ignore because disconnection is quietly
        }
    }

    // public void execute(String command) throws Exception {
    // log.info("start execute cmd {}", command);
    // try (Session session = sshClient.startSession()) {
    // Session.Command exec = session.exec(command);
    //
    // Integer readLineCount = 0;
    // InputStream in = exec.getInputStream();
    // log.info(IOUtils.readFully(in).toString());
    // String errORMessage = IOUtils.readFully(exec.getErrorStream(), LoggerFactory.DEFAULT).toString();
    // log.info(errorMessage);
    // if (exec.getExitStatus() != null && exec.getExitStatus() != 0) {
    // throw new RuntimeException(
    // "exec " + command + " error,error message is " + errorMessage + ",error code " + exec.getExitStatus());
    // }
    // log.info("exec result code {}", exec.getExitStatus());
    //
    // }
    //
    // }
}

接下来就是调用

package com.tiduyun.cmp.operation.service.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.tiduyun.cmp.common.model.flow.UploadFile;
import com.tiduyun.cmp.common.model.operation.ComponentInfo;
import com.tiduyun.cmp.common.model.operation.HostInfo;
import com.tiduyun.cmp.common.provider.service.ExceptionBuildService;
import com.tiduyun.cmp.operation.constant.OperationExceptionCode;
import com.tiduyun.cmp.operation.constant.StartCmdSeparate;
import com.tiduyun.cmp.operation.model.AnsibleConfigurationItemVo;
import com.tiduyun.cmp.operation.model.vo.FileQueryVo;
import com.tiduyun.cmp.operation.service.AnsibleTaskRecordService;
import com.tiduyun.cmp.operation.service.ComposerDeployService;
import com.tiduyun.cmp.operation.service.HostInfoService;
import com.tiduyun.cmp.operation.service.UploadFileService;
import com.tiduyun.cmp.operation.utils.AnsibleExecuteTheOrderUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Slf4j
@Service
public class AnsibleDeployServiceImpl implements ComposerDeployService {

    @Value(value = "${cmp.operation.commandHeader:cmd /c}")
    private String commandHeader;

    @Value(value = "${cmp.operation.filePath:/data/cmp/file}")
    private String filePath;

    @Value(value = "${cmp.operation.remoteFilePath:/tmp}")
    private String remoteFilePath;

    @Autowired
    private AnsibleTaskRecordService ansibleTaskRecordService;

    @Autowired
    private AnsibleConfigurationItemVo ansibleConfigurationItemVo;

    @Autowired
    private UploadFileService uploadFileService;

    @Autowired
    private HostInfoService hostInfoService;

    @Autowired
    private ExceptionBuildService exceptionBuildService;

    @Override
    public void deploy(HostInfo hostInfo, ComponentInfo componentInfo, String cpmposerName) {
        ansibleTaskRecordService.ansibleCreateHost(hostInfo, null);

        try {
            String remotePath = StringUtils.join(remoteFilePath, "/", cpmposerName, "-", componentInfo.getName(), "-",
                RandomUtil.randomString(3));
            log.info("remote file path = {}", remotePath);

            List<Integer> fileIds = getFileIds(componentInfo.getFileUrl());
            if (CollectionUtils.isNotEmpty(fileIds)) {
                FileQueryVo uploadFileQueryVo = new FileQueryVo();
                uploadFileQueryVo.setIds(fileIds);
                List<UploadFile> uploadFiles = uploadFileService.query(uploadFileQueryVo);
                for (UploadFile uploadFile : uploadFiles) {
                    String path = StringUtils.join(filePath, uploadFile.getFilePath());
                    File file = new File(path);
                    if (!file.exists()) {
                        log.error("file url is {}", file.getPath());
                        throw exceptionBuildService.buildException(OperationExceptionCode.FILE_NOT_EXIST,
                            new Object[] {uploadFile.getFileName()});
                    }
                    // 创建远程目录
                    AnsibleExecuteTheOrderUtils.createRemoteDirectory(ansibleConfigurationItemVo.getHostGroup(),
                        StringUtils.join(remotePath), ansibleConfigurationItemVo.getDirectory());

                    // 分发文件
                    AnsibleExecuteTheOrderUtils.upload(ansibleConfigurationItemVo.getHostGroup(), path,
                        StringUtils.join(remotePath, "/", uploadFile.getFileName()),
                        ansibleConfigurationItemVo.getDirectory());
                }
            }
            List<String> startCmds = getStartCmds(componentInfo.getStartCmd());
            if (CollectionUtils.isNotEmpty(startCmds)) {
                String cdCmd = StringUtils.join("cd ", remotePath);
                String execCmd = StringUtils.join(startCmds, ";");
                execCmd = StringUtils.join(cdCmd, ";", execCmd);
                log.info("execCmd= " + execCmd);
                // sshClient.execute(execCmd);
                AnsibleExecuteTheOrderUtils.ExecuteTheOrder(execCmd);

            } else {
                log.error("parse startCmd fail {}", componentInfo.getStartCmd());
            }

        } catch (Exception e) {
            log.error("主机[{}]部署[{}]组件失败,主机ID[{}],组件ID[{}]:", hostInfo.getHost(), componentInfo.getName(),
                hostInfo.getId(), componentInfo.getId(), e);
            throw exceptionBuildService.buildException(OperationExceptionCode.EXECUTE_CMD_ERROR,
                new Object[] {e.getMessage()});

        } finally {
            AnsibleExecuteTheOrderUtils.disconnect();
        }

    }

    @Override
    public boolean isSupport(HostInfo hostInfo) {
        return true;
    }

    private List<Integer> getFileIds(String fileIds) {
        List<Integer> ids = new ArrayList<>();
        if (fileIds == null) {
            return null;
        }
        String[] split = StringUtils.split(fileIds, ",");
        for (String s : split) {
            ids.add(Integer.parseInt(s));
        }
        return ids;
    }

    private List<String> getStartCmds(String startCmd) {
        List<String> cmd = new ArrayList<>();
        if (startCmd == null) {
            return cmd;
        }
        String[] split = StrUtil.split(startCmd, StartCmdSeparate.SIGN);
        cmd.addAll(Arrays.asList(split));
        return cmd;

    }

    public static Boolean needCd(String s) {
        String[] splits = StrUtil.split(s, "&&");
        int maxIndex = splits.length - 1;
        String cmd = splits[maxIndex];
        if (StrUtil.startWith(cmd, "cd")) {
            return false;
        } else {
            return true;
        }

    }
}

文件采集

同上,调用两个工具类

@Override
    public void fileCollection(HostInfo hostInfo, String remotePath, String localPath) {
        ansibleCreateHost(hostInfo, null);
        try {
            log.info("remote file path = {}", remotePath);
            log.info("local file path = {}", localPath);

            // 文件采集
            AnsibleExecuteTheOrderUtils.fileCollection(ansibleConfigurationItemVo.getHostGroup(), remotePath,
                localPath , ansibleConfigurationItemVo.getDirectory());
        } catch (Exception e) {
            log.error("主机[{}]文件采集失败,主机ID[{}]:", hostInfo.getHost(), hostInfo.getId(), e);
            throw exceptionBuildService.buildException(OperationExceptionCode.EXECUTE_CMD_ERROR,
                new Object[] {e.getMessage()});

        } finally {
            AnsibleExecuteTheOrderUtils.disconnect();
        }

    }

总结

以上代码如大家有需要,请自行更改!!!

这些仅为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。

--结束END--

本文标题: Java对接ansible自动运维化平台方式

本文链接: https://www.lsjlt.com/news/203017.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Java对接ansible自动运维化平台方式
    目录Java对接ansible自动运维化平台实现文件采集分发场景说明及ansible yum安装Java代码实现文件分发POI创建文件工具类创建主机组配置文件实现文件分发文件采集总结...
    99+
    2023-05-14
    Java对接ansible 自动运维化平台 Java ansible
  • Java对接ansible自动运维化平台的方法是什么
    这篇文章主要介绍“Java对接ansible自动运维化平台的方法是什么”,在日常操作中,相信很多人在Java对接ansible自动运维化平台的方法是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java对...
    99+
    2023-07-05
  • Ansible自动化运维工具
    文章目录 一.Ansible自动化运维工具介绍1.Ansible简介2.Ansible特点及优势(1)特点(2)优点 3.Ansible核心程序4.Ansible工作原理及流程 二.部署Ansible自动化运维工具1.管理...
    99+
    2023-08-30
    运维 ansible 自动化
  • 自动化运维工具Ansible之Pytho
    Ansible 的Python API使用起来相当简单快捷,使用API可以将某些运维操作封装成一个带有WEB界面的操作,免去了每次执行某个操作的时候都需要SSH运行Ansible命令。官方给出的一个简单示例:import ansible.r...
    99+
    2023-01-31
    工具 Ansible Pytho
  • 自动化运维工具ansible怎么用
    自动化运维工具ansible怎么用,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。正文回到顶部Ansible playbook 简介  playbook 是 an...
    99+
    2023-06-06
  • 如何理解自动化运维工具ansible
    这篇文章给大家介绍如何理解自动化运维工具ansible,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。ansible 简介ansible 是什么?  ansible是新出现的自动化运维工具,基于Python开发,集合了众...
    99+
    2023-06-06
  • 如何理解ansible自动化运维数据库
    这期内容当中小编将会给大家带来有关如何理解ansible自动化运维数据库,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。   &nb...
    99+
    2024-04-02
  • Linux中如何部署自动化运维工具ansible
    小编给大家分享一下Linux中如何部署自动化运维工具ansible,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!ansible是新出现的自动化运维工具,基于Pyt...
    99+
    2023-06-27
  • python自动化运维的连接方法
    这篇文章给大家分享的是有关python自动化运维的连接方法的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。python是什么意思Python是一种跨平台的、具有解释性、编译性、互动性和面向对象的脚本语言,其最初的设...
    99+
    2023-06-14
  • python中自动化运维工具ansible的示例分析
    这篇文章主要介绍python中自动化运维工具ansible的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!ansible 简介ansible 是什么?  ansible是新出现的自动化运维工具,基于Pytho...
    99+
    2023-06-20
  • 如何使用Linux下的自动化运维工具ansible
    本篇内容介绍了“如何使用Linux下的自动化运维工具ansible”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、什么样的情形需要批量部署...
    99+
    2023-06-12
  • 如何使用Inception进行MySQL自动化运维平台建设
    本篇文章给大家分享的是有关如何使用Inception进行MySQL自动化运维平台建设,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。MySQL自...
    99+
    2024-04-02
  • Linux Shell交互式自动化运维程序
    Expect是Linux操作系统下的一个用来处理交互操作,系统默认是没有安装expect库,所以执行expect会提示找不到命令,需要手动安装,其它安装也比较简单,可以通过二进制源码包编译配置进行安装,也可以通过网络借助yum源来安装,yu...
    99+
    2023-06-05
  • golang自动化运维测试的方法有哪些
    Rate limit reached for gpt-3.5-turbo in organization org-68Lqb4iD2...
    99+
    2023-10-18
    golang
  • 大型企业中如何批量管理千万台服务器之ansible自动化运维工具详解 [⭐建议收藏⭐]
    文章目录 ansible 自动化运维工具 详解关于作者作者介绍一、ansible 概述1.1 ansible 概述1.2 是什么要使用 ansible1.3 ansible 功能1.4 ansible 优点1.5 ansible工作...
    99+
    2023-08-30
    运维 python ansible 自动化运维 linux
  • 数据库自动化运维的挑战:应对变革,掌握核心技术
    持续的技术更新:数据库技术不断更新迭代,从传统关系型数据库到云数据库、NoSQL数据库等新兴技术,数据库自动化运维需要与时俱进,不断更新和优化系统以满足技术变化带来的新需求。 业务需求的多样性:企业业务日益复杂多元,对数据库系统的...
    99+
    2024-04-02
  • web自动化平台开发元数据的设计方法是是什么
    这篇“web自动化平台开发元数据的设计方法是是什么”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“web自动化平台开发元数据的...
    99+
    2023-06-04
  • 高效运维:CentOS上搭建web服务器的自动化部署方法
    在CentOS上搭建web服务器的自动化部署方法可以使用工具如Ansible或Docker来实现。以下是使用Ansible的步骤:1...
    99+
    2023-10-09
    CentOS
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作