【Linux】【部署】主机初始化

news/2024/7/8 2:19:11 标签: linux, python, 前端

初始化服务器

#python3 init_host.py init 【主机名】 【主机ip】
python3 init_host.py init NODE01 192.168.199.10

init_host.py 文件内容

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import json
import os
import sys
import logging
import time
import logging.config
import subprocess

PYTHON_VERSION = sys.version_info.major
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))

if PYTHON_VERSION == 2:
    reload(sys)
    sys.setdefaultencoding('utf-8')

# ---- 日志定义部分 ----
# 日志配置 建议输入绝对路径,默认生成日志会添加 时间字段
LOG_PATH = "/tmp/init_host_standalone.log"
# 屏幕输出日志级别
CONSOLE_LOG_LEVEL = logging.INFO
# 文件日志级别
FILE_LOG_LEVEL = logging.DEBUG


def generate_log_filepath(log_path):
    """生成日志名称"""
    time_str = time.strftime('%Y-%m-%d-%H-%M-%S', time.localtime(time.time()))
    dirname = os.path.dirname(log_path)
    name_split = os.path.basename(log_path).split('.')
    if len(name_split) == 1:
        name = "{0}_{1}".format(name_split[0], time_str)
        file_path = os.path.join(dirname, name)
    else:
        name_split.insert(-1, time_str)
        file_path = os.path.join(dirname, '.'.join(name_split))
    return file_path


# 日志配置
log_path = generate_log_filepath(LOG_PATH)
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
    datefmt='%a, %d %b %Y %H:%M:%S',
    filename=log_path,
    filemode='a')
console = logging.StreamHandler()
console.setLevel(logging.INFO)
formatter = logging.Formatter(
    '%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s')
console.setFormatter(formatter)
logger = logging.getLogger()
logger.addHandler(console)

KERNEL_PARAM = """# Disable IPv6
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
# ARP
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.all.rp_filter = 0
net.ipv4.neigh.default.gc_stale_time = 120
net.ipv4.conf.default.arp_announce = 2
net.ipv4.conf.all.arp_announce = 2
net.ipv4.conf.lo.arp_announce = 2
# TCP Memory
net.core.rmem_default = 2097152
net.core.wmem_default = 2097152
net.core.rmem_max = 4194304
net.core.wmem_max = 4194304
net.ipv4.tcp_rmem = 4096 8192 4194304
net.ipv4.tcp_wmem = 4096 8192 4194304
net.ipv4.tcp_mem = 524288 699050 1048576
# TCP SYN
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_synack_retries = 1
net.ipv4.tcp_syn_retries = 1
net.ipv4.tcp_max_syn_backlog = 16384
net.core.netdev_max_backlog = 16384
# TIME_WAIT
net.ipv4.route.gc_timeout = 100
net.ipv4.tcp_max_tw_buckets = 5000
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_timestamps = 0
net.ipv4.tcp_fin_timeout = 2
net.ipv4.ip_local_port_range = 20000 50000
# TCP keepalive
net.ipv4.tcp_keepalive_probes = 3
net.ipv4.tcp_keepalive_time = 60
net.ipv4.tcp_keepalive_intvl = 10
# Other TCP
net.ipv4.tcp_max_orphans = 65535
net.core.somaxconn = 16384
net.ipv4.tcp_sack = 1
net.ipv4.tcp_window_scaling = 1
vm.max_map_count=262144
vm.min_free_kbytes=512000
vm.swappiness = 0"""

KERNEL_KEYWORD = [
    "net.ipv6.conf.all.disable_ipv6",
    "net.ipv6.conf.default.disable_ipv6",
    "net.ipv4.conf.default.rp_filter",
    "net.ipv4.conf.all.rp_filter",
    "net.ipv4.neigh.default.gc_stale_time",
    "net.ipv4.conf.default.arp_announce",
    "net.ipv4.conf.all.arp_announce",
    "net.ipv4.conf.lo.arp_announce",
    "net.core.rmem_default",
    "net.core.wmem_default",
    "net.core.rmem_max",
    "net.core.wmem_max",
    "net.ipv4.tcp_rmem",
    "net.ipv4.tcp_wmem",
    "net.ipv4.tcp_mem",
    "net.ipv4.tcp_syncookies",
    "net.ipv4.tcp_synack_retries",
    "net.ipv4.tcp_syn_retries",
    "net.ipv4.tcp_max_syn_backlog",
    "net.core.netdev_max_backlog",
    "net.ipv4.route.gc_timeout",
    "net.ipv4.tcp_max_tw_buckets",
    "net.ipv4.tcp_tw_reuse",
    "net.ipv4.tcp_timestamps",
    "net.ipv4.tcp_fin_timeout",
    "net.ipv4.ip_local_port_range",
    "net.ipv4.tcp_keepalive_probes",
    "net.ipv4.tcp_keepalive_time",
    "net.ipv4.tcp_keepalive_intvl",
    "net.ipv4.tcp_max_orphans",
    "net.core.somaxconn",
    "net.ipv4.tcp_sack",
    "net.ipv4.tcp_window_scaling",
    "vm.max_map_count",
    "vm.swappiness",
    "vm.min_free_kbytes"
]
TO_MODIFY_HOST_NAME = [
    "localhost",
    "localhost.localhost",
    "localhost.domain",
]


class BaseInit(object):
    """ Base class 检查权限 / 执行命令方法 """

    def check_permission(self):
        """ 检查权限 """
        logger.info("开始检查当前用户执行权限")
        if not os.getuid() == 0:
            self.__check_is_sodu()
            if not self.is_sudo:
                logging.error('当前执行用户不是root,且此用户没有sudo NOPASSWD 权限,无法初始化!')
                exit(1)
        logger.info('当前用户权限正常,开始执行脚本')

    def __check_is_sodu(self):
        """ 是否具有 sodu 权限 """
        logger.info("检查是否具有sodu免密码权限")
        _cmd = "sudo -n 'whoami' &>/dev/null"
        _, _, _code = self.cmd(_cmd)
        self.is_sudo = _code == 0
        logger.info("是否具有sodu免密码权限: {}".format(self.is_sudo))

    def cmd(self, command):
        """ 执行shell 命令 """
        if hasattr(self, 'is_sudo'):
            if command.lstrip().startswith("echo"):
                command = "sudo sh -c '{0}'".format(command)
            else:
                command = "sudo {0}".format(command)
        logger.debug("Exec command: {0}".format(command))
        p = subprocess.Popen(
            command,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            shell=True,
        )
        stdout, stderr = p.communicate()
        _out, _err, _code = stdout, stderr, p.returncode
        logger.debug(
            "Get command({0}) stdout: {1}; stderr: {2}; ret_code: {3}".format(
                command, _out, _err, _code
            )
        )
        return _out, _err, _code

    @staticmethod
    def read_file(path, mode='r', res='str'):
        """
        :param path 路径
        :param mode 模式
        :param res 返回数据类型 str/list
        """
        if not os.path.exists(path):
            logger.error('读取文件失败,文件路径错误:{}'.format(path))
            exit(1)
        with open(path, mode) as f:
            data = f.read() if res == 'str' else f.readlines()
        return data

    def __get_os_version(self):
        logging.debug('开始获取系统版本信息')
        # match = False
        _, _, _code = self.cmd('systemctl --version')
        if _code != 0:
            logger.error("执行失败,当前操作系统不支持本脚本")
            exit(1)
        self.os_version = 7
        logging.debug('获取系统版本信息完成')

    def set_opts(self, **kwargs):
        """ 根据kwargs 设置参数"""
        raise Exception("程序错误,需实现set_opts方法")

    def run(self):
        # 检查权限
        if sys.argv[1] != 'valid':
            self.check_permission()
        self.__get_os_version()
        logging.info("开始执行脚本")

        self.run_methods()

    def run_methods(self):
        try:
            assert isinstance(self.m_list, list), "m_list 类型错误 方法错误,请检查脚本"
            assert len(self.m_list) > 0, "m_list 为空,请检查脚本"
            for func_info in self.m_list:
                assert isinstance(func_info, tuple) and len(
                    func_info) == 2, "todo_list 方法错误,请检查脚本:{}".format(func_info)
                method_name, method_note = func_info
                if hasattr(self, method_name):
                    f = getattr(self, method_name)
                    logger.info("开始 执行: {}".format(method_note))
                    f()
                    logger.info("执行 完成: {}".format(method_note))
                else:
                    logger.warn("安装方法列表错误,{} 方法不存在".format(method_note))
            else:
                logging.info("执行结束, 完整日志保存在 {}".format(log_path))
        except TypeError:
            logger.error("脚本配置错误,TypeError:")
        except Exception as e:
            logger.error(e)
            logging.info("执行结束, 完整日志保存在 {}".format(log_path))
            exit(1)


class InitHost(BaseInit):
    """ 初始化节点信息 """

    def __init__(self, host_name, local_ip):
        self.m_list = [
            ('env_set_timezone', '设置时区'),
            ('env_set_firewall', '关闭防火墙'),
            ('env_set_disable_ipv6', '设置关闭ipv6'),
            ('env_set_language', '设置语言'),
            ('env_set_file_limit', '设置文件句柄数'),
            ('env_set_kernel', '设置内核参数'),
            ('env_set_disable_selinux', '关闭selinux'),
            ('set_hostname', '设置主机名'),
        ]
        # TODO
        self.hostname = host_name
        self.local_ip = local_ip

    def env_set_timezone(self):
        """ 设置时区 """
        timezone = "PRC"
        self.cmd("test -f /etc/timezone && rm -f /etc/timezone")
        self.cmd("rm -f /etc/localtimze")
        self.cmd(
            "ln -sf /usr/share/zoneinfo/{0} /etc/localtime".format(timezone))

    def env_set_firewall(self):
        """ 关闭 firewall """
        _, _, _code = self.cmd(
            "systemctl status firewalld.service | egrep -q 'Active: .*(dead)'"
        )
        if _code != 0:
            self.cmd("systemctl stop firewalld.service >/dev/null 2>&1")
            self.cmd("systemctl disable firewalld.service >/dev/null 2>&1")

    def env_set_disable_ipv6(self):
        """ 关闭ipv6 """
        _, _, _code = self.cmd("grep -q 'ipv6.disable' /etc/default/grub")
        if _code == 0:
            self.cmd(
                "sed -i 's/ipv6.disable=[0-9]/ipv6.disable=1/g' /etc/default/grub"
            )
        else:
            self.cmd(
                """sed -i '/GRUB_CMDLINE_LINUX/ s/="/="ipv6.disable=1 /' /etc/default/grub"""
            )
        self.cmd("sysctl -w net.ipv6.conf.all.disable_ipv6=1")

    def env_set_language(self):
        """ 设置语言 """
        self.cmd("localectl set-locale LANG=en_US.UTF-8")

    def env_set_file_limit(self):
        """ 设置打开的文件句柄数 """
        _file_max_out, _, _ = self.cmd("cat /proc/sys/fs/file-max")
        file_max = int(_file_max_out)
        _nr_open_out, _, _ = self.cmd("cat /proc/sys/fs/nr_open")
        nr_open = int(_nr_open_out)
        if file_max < 655350:
            self.cmd("sed -i '/fs.file-max/d' /etc/sysctl.conf")
            self.cmd("echo 'fs.file-max = 655350' >>/etc/sysctl.conf")
            self.cmd("sysctl -p 1>/dev/null")
            file_max = 655350
        else:
            file_max = 655350

        self.cmd("sed -i '/nofile/d' /etc/security/limits.conf")
        self.cmd(
            'echo "*               -       nofile          {0}" >>/etc/security/limits.conf'.format(
                file_max
            )
        )
        if os.path.exists("/etc/security/limits.d/20-nproc.conf"):
            self.cmd(
                "sed -i 's#4096#unlimited#g' /etc/security/limits.d/20-nproc.conf"
            )
        self.cmd("sed -i '/^DefaultLimitCORE/d' /etc/systemd/system.conf")
        self.cmd("sed -i '/^DefaultLimitNOFILE/d' /etc/systemd/system.conf")
        self.cmd("sed -i '/^DefaultLimitNPROC/d' /etc/systemd/system.conf")
        c = 'echo -e "DefaultLimitCORE=infinity\\nDefaultLimitNOFILE={0}\\nDefaultLimitNPROC={0}" >>/etc/systemd/system.conf'.format(
            file_max)
        self.cmd(
            c
        )
        self.cmd("ulimit -SHn {0}".format(file_max))

    def env_set_kernel(self):
        """ 设置内核参数 """
        for item in KERNEL_KEYWORD:
            self.cmd('sed -i "/{0}/d" /etc/sysctl.conf'.format(item.strip()))
        self.cmd('sed -i "/tables/d" /etc/sysctl.conf')
        self.cmd('echo "{0}" >>/etc/sysctl.conf'.format(KERNEL_PARAM))
        self.cmd("sysctl -p 1>/dev/null")

    def env_set_disable_selinux(self):
        """ 禁用 selinux """
        if os.path.exists("/etc/selinux/config"):
            self.cmd(
                "sed -i 's#^SELINUX=.*#SELINUX=disabled#g' /etc/selinux/config")
            self.cmd("setenforce 0")

    def set_hostname(self):
        """设置主机名"""
        _out, _err, _code = self.cmd("echo $(hostname)")
        if _out.strip().lower() in TO_MODIFY_HOST_NAME or _out.strip().isdigit():
            self.cmd('echo "{0}" >/etc/hostname'.format(self.hostname))
            self.cmd('echo "{0}" > /proc/sys/kernel/hostname'.format(self.hostname))
            self.cmd("hostname {0}".format(self.hostname))
            self.cmd('echo "{0}    {1}" >> /etc/hosts'.format(self.local_ip, self.hostname))


class ValidInit(BaseInit):
    def __init__(self):
        self.m_list = [
            ('valid_env_timezone', '校验时区'),
            ('valid_env_firewall', '校验防火墙'),
            ('valid_env_language', '校验语言'),
            ('valid_env_file_limit', '校验文件具柄数'),
            ('valid_env_kernel', '校验内核参数'),
            ('valid_env_disable_selinux', '校验selinux'),
            ('valid_host_name', '校验host_name'),
        ]

    def valid_env_timezone(self):
        """ 校验时区 """
        assert os.readlink(
            '/etc/localtime') == "/usr/share/zoneinfo/PRC", "时区校验失败"

    def valid_env_firewall(self):
        """ 校验防火墙 """
        _, _, _code = self.cmd(
            "systemctl status firewalld.service | egrep -q 'Active: .*(dead)'"
        )
        assert _code == 0, "防火墙校验失败"

    def valid_env_language(self):
        """ 校验语言 """
        assert self.cmd(
            "localectl status |grep LANG=en_US.UTF-8")[2] == 0, "语言环境校验失败"

    def valid_env_file_limit(self):
        """ 校验文件具柄数 """
        _err = ""
        _file_max_out, _, _ = self.cmd("cat /proc/sys/fs/file-max")
        file_max = int(_file_max_out)
        _nr_open_out, _, _ = self.cmd("cat /proc/sys/fs/nr_open")
        nr_open = int(_nr_open_out)
        if file_max < 655350:
            _err = "文件句柄数校验失败"
        else:
            file_max = 655350

        if self.cmd(
                'grep "*               -       nofile          {0}" /etc/security/limits.conf'.format(
                    file_max
                )
        )[2] != 0:
            _err = "文件 /etc/security/limits.conf 校验失败"

        if os.path.exists("/etc/security/limits.d/20-nproc.conf"):
            if self.cmd(
                    "grep unlimited /etc/security/limits.d/20-nproc.conf"
            )[2] != 0:
                _err = "文件 /etc/security/limits.d/20-nproc.conf 校验失败"
        if self.cmd('grep "DefaultLimitCORE=infinity" /etc/systemd/system.conf')[2] != 0:
            _err = "文件 /etc/systemd/system.conf DefaultLimitCORE 校验失败"
        if self.cmd('grep DefaultLimitNOFILE={0} /etc/systemd/system.conf'.format(file_max))[2] != 0:
            _err = "文件 /etc/systemd/system.conf DefaultLimitNOFILE 校验失败"
        if self.cmd('grep DefaultLimitNPROC={0} /etc/systemd/system.conf'.format(file_max))[2] != 0:
            _err = "文件 /etc/systemd/system.conf DefaultLimitNPROC 校验失败"

        assert _err == '', _err

    def valid_env_kernel(self):
        """ 校验内核参数 """
        _list = [i.strip() for i in self.read_file('/etc/sysctl.conf',
                                                   res='list') if not i.strip().startswith('#')]
        for i in KERNEL_PARAM.split('\n'):
            if i.startswith('#'):
                continue
            assert i.strip() in _list, "内核参数校验失败: {}".format(i)

    def valid_env_disable_selinux(self):
        """ 校验selinux """
        assert "SELINUX=disabled" in [
            i.strip() for i in self.read_file('/etc/selinux/config', res='list') if
            not i.strip().startswith('#')
        ], "selinux 校验失败"

    def valid_host_name(self):
        """校验host_name不含localhost"""
        _out, _err, _code = self.cmd("echo $(hostname)")
        assert _out.strip().lower() not in TO_MODIFY_HOST_NAME and not _out.strip().isdigit(), "校验主机名失败"


def add_hostname_analysis(hostname_str):
    logger.debug("传入主机信息:\n{}".format(hostname_str))
    hostnames = json.loads(hostname_str or '[]')
    with open("/etc/hosts", "r") as f:
        hosts = f.read()
    logger.debug("获取主机解析:\n{}".format(hosts))
    hosts_analysis_dict = {}
    for analysis_str in hosts.split("\n"):
        if analysis_str.lstrip().startswith("#"):
            continue
        analysis_list = list(
            filter(
                lambda x: x,
                analysis_str.strip().replace("\t", " ").split(" ")
            )
        )
        if not analysis_list:
            continue
        hosts_analysis_dict[analysis_list[0]] = analysis_str
    for hostname_dict in hostnames:
        ip = hostname_dict.get("ip")
        hostname = hostname_dict.get("hostname")
        host_analysis_str = hosts_analysis_dict.get(ip, "")
        if not host_analysis_str:
            hosts += "{} {}\n".format(ip, hostname)
        elif hostname in host_analysis_str:
            continue
        else:
            host_analysis_str_new = "{} {}".format(host_analysis_str, hostname)
            hosts = hosts.replace(host_analysis_str, host_analysis_str_new)
    logger.debug("对比获得最新主机信息:\n{}".format(hosts))
    with open("/etc/hosts", "w") as f:
        f.write(hosts)
    logger.debug("写入最新主机信息成功!")


def usage(error=None):
    script_full_path = os.path.join(CURRENT_DIR, os.path.basename(__file__))
    print("""{0} 脚本 功能为初始化节点,职能包括: 设置时区、关闭防火墙、设置文件具柄和内核参数等
    Command:
            init        <host_name>  <local_ip>  初始化节点
            valid                                校验初始化结果
            init_valid  <host_name>  <local_ip>  初始化节点,在完成初始化后执行校验

    Use "python {0} <command>" for more information about a given command.
    """.format(script_full_path))
    if error is not None:
        print("Error: {}".format(error))
        exit(1)
    exit(0)


def main():
    command_list = ('init', 'valid', 'init_valid', 'help', 'write_hostname')
    try:
        if sys.argv[1] not in command_list:
            usage(error='参数错误: {}'.format(sys.argv[1:]))
        if sys.argv[1] in ['init', 'init_valid']:
            if len(sys.argv) != 4:
                usage(error='参数错误: {}'.format(sys.argv[1:]))
            host_name = sys.argv[2]
            local_ip = sys.argv[3]
            init = InitHost(host_name, local_ip)
            init.run()
            logger.info("init success")
            if sys.argv[1] == 'init_valid':
                check = ValidInit()
                check.run()
                logger.info("valid success")
        elif sys.argv[1] == 'valid':
            check = ValidInit()
            check.run()
            logger.info("valid success")
        elif sys.argv[1] == "write_hostname":
            hosts_info = sys.argv[2]
            # '[{"ip":"10.0.9.18","hostname":"docp-9-18"}]'
            add_hostname_analysis(hosts_info)
        else:
            usage()
    except Exception as e:
        usage(error="参数错误, {}".format(e))


if __name__ == '__main__':
    main()

http://www.niftyadmin.cn/n/5536070.html

相关文章

【计算机毕业设计】061互助学习微信小程序

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

【MySQL】 NDB 集群概述

MySQL NDB&#xff08;Network Database&#xff09;是MySQL的一个存储引擎&#xff0c;也称为NDB Cluster存储引擎。它主要用于构建高可用性、高可扩展性和高性能的分布式数据库集群。NDB Cluster是MySQL的一个特殊版本&#xff0c;专门设计用于处理大规模的分布式数据存储和处…

行业洞察 | 2024应用程序安全领域现状报告

在信息爆炸的时代&#xff0c;我们每天都在使用各种应用&#xff0c;从社交娱乐到工作学习&#xff0c;应用已经成为我们生活中不可或缺的一部分。然而&#xff0c;你是否知道&#xff0c;在这些便捷的背后&#xff0c;隐藏着巨大的安全风险&#xff1f; 近年来&#xff0c;应用…

卡尔曼滤波Q和R怎么调

卡尔曼滤波器是一种有效的估计算法&#xff0c;主要用于在存在噪声的环境中估计动态系统的状态。它通过结合预测模型&#xff08;系统动态&#xff09;和观测数据&#xff08;包括噪声&#xff09;来实现这一点。在卡尔曼滤波中&#xff0c;调整过程噪声协方差矩阵 ( Q ) 和测量…

C++ 学习笔记一

C 学习笔记 使用空参数列表而不是void来指示函数没有参数。现代机器上的内存通常被组织成字节大小的单元&#xff0c;每个内存字节都有一个唯一的地址。为了确定特定机器上数据类型的大小&#xff0c;C 提供了一个名为sizeof的运算符。sizeof运算符是一个一元运算符&#xff0…

【Linux】GNU是什么

GNU是一个自由软件项目&#xff0c;其全称是“GNUs Not Unix!”&#xff0c;由理查德斯托曼&#xff08;Richard Stallman&#xff09;于1983年发起。GNU项目的目标是创建一个完全自由的操作系统&#xff0c;包括一整套完全自由的软件工具&#xff0c;以取代Unix系统。这里的“…

BeanUtils拷贝List数据

工具类&#xff1a; package com.ssdl.baize.pub;import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; import org.springframework.beans.BeanUtils;public class BeanConvertUti…

顶级10大AI测试工具

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…