Skip to content

服务器初始化脚本

服务器初始化脚本更适合做成工具箱:脚本入口是一个文件,里面提供多个子命令,比如 checkset-hostnamewrite-hostsconfig-timeinstall-toolsconfig-sysctlconfig-limits。日常维护时按需要执行单个动作;全新机器交付时,再用 init-all 把常用动作串起来。

脚本内部用正常的 Bash 写法,函数名、变量名、子命令保持英文。中文放在帮助信息、日志、错误提示和检查输出里。这样既方便平时阅读输出,也不影响编辑器、shellcheck、代码搜索和团队维护。

一、工具箱结构

工具箱脚本的核心是“单项动作能独立执行”。初始化不是每次都从零开始,很多时候只是补一个配置、复查一次状态,或者批量扩容节点时重复执行固定动作。

子命令作用示例
help显示帮助bash server-toolbox.sh help
check只读检查系统状态bash server-toolbox.sh check
set-hostname设置主机名bash server-toolbox.sh set-hostname k3s-server-01
write-hosts追加 hosts 记录bash server-toolbox.sh write-hosts hosts.cluster
config-time配置 chrony 时间同步bash server-toolbox.sh config-time 192.168.10.1
install-tools安装常用排障工具bash server-toolbox.sh install-tools
config-sysctl写入基础内核参数bash server-toolbox.sh config-sysctl
config-limits写入文件句柄和进程限制bash server-toolbox.sh config-limits
init-all串联常用初始化动作bash server-toolbox.sh init-all k3s-server-01 hosts.cluster

全局参数保持简单:

参数作用
--dry-run只打印将要执行的动作,不修改系统
--log-file <path>指定日志文件,默认 /tmp/server-toolbox.log

示例:

bash
# 查看帮助
bash server-toolbox.sh help

# 只读检查系统状态
bash server-toolbox.sh check

# 预览时间同步配置动作,不修改系统
bash server-toolbox.sh --dry-run config-time 192.168.10.1

# 正式初始化时把日志写入 /var/log
bash server-toolbox.sh --log-file /var/log/server-toolbox.log init-all k3s-server-01 hosts.cluster

二、通用函数

通用函数负责日志、错误退出、命令执行、配置备份、root 权限检查。日志输出用中文,执行状态能直接保存到变更记录里。

bash
#!/usr/bin/env bash
set -euo pipefail

dry_run=0
log_file="/tmp/server-toolbox.log"
os_id=""
os_like=""
command_name="help"
command_args=()

log_info() {
    printf '[信息] %s %s\n' "$(date '+%F %T')" "$*" | tee -a "$log_file"
}

log_error() {
    printf '[错误] %s %s\n' "$(date '+%F %T')" "$*" | tee -a "$log_file" >&2
}

die() {
    log_error "$*"
    exit 1
}

run_cmd() {
    log_info "执行命令: $*"

    # dry-run 只打印动作,用于批量执行前确认脚本会改哪些地方
    if [ "$dry_run" -eq 1 ]; then
        return 0
    fi

    "$@"
}

require_root() {
    # dry-run 不实际修改系统,可以在普通用户下预览
    if [ "$dry_run" -eq 1 ]; then
        return 0
    fi

    [ "$(id -u)" -eq 0 ] || die "当前操作需要 root 权限"
}

backup_file() {
    local file_path="$1"
    local backup_path

    [ -e "$file_path" ] || return 0

    backup_path="${file_path}.$(date '+%F-%H%M%S').bak"
    log_info "备份文件: $file_path -> $backup_path"

    if [ "$dry_run" -eq 0 ]; then
        cp -a "$file_path" "$backup_path"
    fi
}

run_cmd 接收命令和参数,不接收拼好的字符串,能减少引号和转义问题。需要重定向写文件时,单独在函数里处理,不把 >> 这种 shell 语法塞进 run_cmd

三、参数解析

参数解析分成全局参数和子命令。全局参数只在命令前面出现,解析到第一个非全局参数时,后面的内容都交给子命令。

bash
show_help() {
    cat <<'EOF'
用法:
  bash server-toolbox.sh [全局参数] <子命令> [参数]

全局参数:
  --dry-run              只打印将要执行的动作,不修改系统
  --log-file <路径>      指定日志文件,默认 /tmp/server-toolbox.log
  -h, --help, help       显示帮助

子命令:
  check                         只读检查系统状态
  set-hostname <名称>            设置主机名
  write-hosts <文件>             将文件内容追加到 /etc/hosts,已存在的行会跳过
  config-time [NTP服务器]        安装并配置 chrony,默认 ntp.aliyun.com
  install-tools                 安装常用排障工具
  config-sysctl                 写入基础 sysctl 参数
  config-limits                 写入 limits 文件句柄和进程数限制
  init-all <主机名> [hosts文件]  执行常用初始化动作

示例:
  bash server-toolbox.sh check
  bash server-toolbox.sh --dry-run config-time 192.168.10.1
  bash server-toolbox.sh set-hostname k3s-server-01
  bash server-toolbox.sh write-hosts hosts.cluster
  bash server-toolbox.sh init-all k3s-server-01 hosts.cluster
EOF
}

parse_args() {
    while [ "$#" -gt 0 ]; do
        case "$1" in
            --dry-run)
                dry_run=1
                shift
                ;;
            --log-file)
                log_file="${2:-}"
                [ -n "$log_file" ] || die "--log-file 缺少路径"
                shift 2
                ;;
            -h|--help|help)
                command_name="help"
                shift
                break
                ;;
            *)
                command_name="$1"
                shift
                break
                ;;
        esac
    done

    command_args=("$@")
}

参数解析不追求复杂 CLI 框架。Shell 脚本只要能把固定子命令分清楚,错误信息说清楚,就够日常初始化使用。

四、发行版识别

软件包名称和服务名称在不同发行版上不完全一致。脚本先识别系统,再决定使用 yumdnf 还是 apt

bash
detect_os() {
    [ -r /etc/os-release ] || die "未找到 /etc/os-release,无法识别系统"

    # shellcheck disable=SC1091
    . /etc/os-release

    os_id="${ID:-}"
    os_like="${ID_LIKE:-}"
    log_info "识别系统: ID=$os_id ID_LIKE=$os_like"
}

is_rhel_like() {
    [[ "$os_id" =~ ^(rhel|centos|rocky|almalinux)$ || "$os_like" == *"rhel"* ]]
}

is_debian_like() {
    [[ "$os_id" =~ ^(debian|ubuntu)$ || "$os_like" == *"debian"* ]]
}

ID_LIKE 能覆盖一部分衍生发行版。脚本只适配当前环境里会用到的系统,比写一堆很少验证的兼容分支更可靠。

五、只读体检

check 不修改系统,适合初始化前、初始化后、交接机器和变更复查时执行。输出保留真实状态,而不是只打印“正常”。

bash
cmd_check() {
    log_info "开始系统体检"

    echo "== 系统信息 =="
    hostnamectl --static 2>/dev/null || hostname
    cat /etc/os-release | grep -E '^(NAME|VERSION)=' || true
    uname -r

    echo
    echo "== 时间同步 =="
    timedatectl status --no-pager | grep -E 'Time zone|System clock synchronized|NTP service' || true
    chronyc tracking 2>/dev/null | grep -E 'Reference ID|Leap status' || true

    echo
    echo "== 网络信息 =="
    ip -brief addr
    ip route | head -n 5

    echo
    echo "== 磁盘和内存 =="
    df -h
    free -h
    swapon --show || true

    echo
    echo "== 监听端口 =="
    ss -lntup

    echo
    echo "== 系统参数 =="
    sysctl net.ipv4.ip_forward vm.swappiness 2>/dev/null || true
    ulimit -n
}

输出中包含主机名、时间同步、IP、磁盘、端口和关键参数,回看日志时能判断当时机器处在什么状态。

六、配置子命令

配置子命令按同一套规则处理:校验参数、确认当前状态、必要时备份配置、执行修改、输出复查方式。

设置主机名:

bash
cmd_set_hostname() {
    local hostname_value="${1:-}"
    local current

    [ -n "$hostname_value" ] || die "缺少主机名,例如:set-hostname k3s-server-01"

    current="$(hostnamectl --static 2>/dev/null || hostname)"
    if [ "$current" = "$hostname_value" ]; then
        log_info "主机名已经是 $hostname_value"
        return 0
    fi

    require_root
    run_cmd hostnamectl set-hostname "$hostname_value"
    log_info "主机名已设置为 $hostname_value"
}

追加 hosts:

bash
cmd_write_hosts() {
    local hosts_file="${1:-}"
    local line

    [ -n "$hosts_file" ] || die "缺少 hosts 文件,例如:write-hosts hosts.cluster"
    [ -r "$hosts_file" ] || die "hosts 文件不可读: $hosts_file"

    require_root
    backup_file /etc/hosts

    while IFS= read -r line; do
        [ -n "$line" ] || continue
        [[ "$line" == \#* ]] && continue

        if grep -Fxq "$line" /etc/hosts; then
            log_info "hosts 记录已存在: $line"
            continue
        fi

        log_info "追加 hosts 记录: $line"
        if [ "$dry_run" -eq 0 ]; then
            printf '%s\n' "$line" >> /etc/hosts
        fi
    done < "$hosts_file"

    log_info "hosts 写入完成,可用 getent hosts <主机名> 复查解析结果"
}

安装 chrony:

bash
install_chrony_if_needed() {
    if command -v chronyc >/dev/null 2>&1; then
        log_info "chrony 已安装"
        return 0
    fi

    log_info "安装 chrony"
    if is_rhel_like; then
        if command -v dnf >/dev/null 2>&1; then
            run_cmd dnf install -y chrony
        else
            run_cmd yum install -y chrony
        fi
        return 0
    fi

    if is_debian_like; then
        run_cmd apt update
        run_cmd apt install -y chrony
        return 0
    fi

    die "当前系统暂未适配 chrony 安装: $os_id"
}

配置时间同步:

bash
cmd_config_time() {
    local ntp_server="${1:-ntp.aliyun.com}"
    local conf
    local service

    require_root
    detect_os

    if is_rhel_like; then
        conf="/etc/chrony.conf"
        service="chronyd"
    elif is_debian_like; then
        conf="/etc/chrony/chrony.conf"
        service="chrony"
    else
        die "当前系统暂未适配 chrony 配置: $os_id"
    fi

    install_chrony_if_needed
    backup_file "$conf"

    log_info "写入 chrony 配置: $conf"
    if [ "$dry_run" -eq 0 ]; then
        cat > "$conf" <<EOF
# 由 server-toolbox.sh 管理
server ${ntp_server} iburst
makestep 1.0 3
rtcsync
EOF
    fi

    run_cmd systemctl enable --now "$service"
    run_cmd systemctl restart "$service"
    log_info "时间同步已配置,复查命令: timedatectl status; chronyc sources -v"
}

makestep 1.0 3 是比较常见的生产写法:服务启动初期允许大偏差快速校正,后续运行中尽量通过微调保持时间稳定。测试环境频繁恢复快照时,时间偏差可能更明显,恢复后用 timedatectl statuschronyc tracking 复查即可。

安装基础工具:

bash
cmd_install_tools() {
    require_root
    detect_os

    if is_rhel_like; then
        if command -v dnf >/dev/null 2>&1; then
            run_cmd dnf install -y vim curl wget tar unzip lsof net-tools bind-utils bash-completion chrony
        else
            run_cmd yum install -y vim curl wget tar unzip lsof net-tools bind-utils bash-completion chrony
        fi
        return 0
    fi

    if is_debian_like; then
        run_cmd apt update
        run_cmd apt install -y vim curl wget tar unzip lsof net-tools dnsutils bash-completion chrony
        return 0
    fi

    die "当前系统暂未适配基础工具安装: $os_id"
}

写 sysctl:

bash
cmd_config_sysctl() {
    local conf="/etc/sysctl.d/99-base.conf"

    require_root
    backup_file "$conf"
    log_info "写入内核参数: $conf"

    if [ "$dry_run" -eq 0 ]; then
        cat > "$conf" <<'EOF'
# 由 server-toolbox.sh 管理
# 开启 IPv4 转发;容器网络、路由转发、LVS 等场景会用到
net.ipv4.ip_forward = 1

# 降低系统主动使用 swap 的倾向,具体值仍需结合业务内存情况
vm.swappiness = 10
EOF
    fi

    run_cmd sysctl --system
}

写 limits:

bash
cmd_config_limits() {
    local conf="/etc/security/limits.d/99-base.conf"

    require_root
    backup_file "$conf"
    log_info "写入资源限制: $conf"

    if [ "$dry_run" -eq 0 ]; then
        cat > "$conf" <<'EOF'
# 由 server-toolbox.sh 管理
# nofile 控制最大打开文件数,高并发服务经常需要调高
* soft nofile 65535
* hard nofile 65535

# nproc 控制最大进程数,避免默认值过小影响批量任务或服务进程
* soft nproc 65535
* hard nproc 65535
EOF
    fi

    log_info "limits 已写入,新登录会话生效;systemd 服务还要看 unit 里的 LimitNOFILE"
}

七、完整脚本

完整脚本如下,文件名示例为 server-toolbox.sh

bash
#!/usr/bin/env bash
set -euo pipefail

dry_run=0
log_file="/tmp/server-toolbox.log"
os_id=""
os_like=""
command_name="help"
command_args=()

show_help() {
    cat <<'EOF'
用法:
  bash server-toolbox.sh [全局参数] <子命令> [参数]

全局参数:
  --dry-run              只打印将要执行的动作,不修改系统
  --log-file <路径>      指定日志文件,默认 /tmp/server-toolbox.log
  -h, --help, help       显示帮助

子命令:
  check                         只读检查系统状态
  set-hostname <名称>            设置主机名
  write-hosts <文件>             将文件内容追加到 /etc/hosts,已存在的行会跳过
  config-time [NTP服务器]        安装并配置 chrony,默认 ntp.aliyun.com
  install-tools                 安装常用排障工具
  config-sysctl                 写入基础 sysctl 参数
  config-limits                 写入 limits 文件句柄和进程数限制
  init-all <主机名> [hosts文件]  执行常用初始化动作

示例:
  bash server-toolbox.sh check
  bash server-toolbox.sh --dry-run config-time 192.168.10.1
  bash server-toolbox.sh set-hostname k3s-server-01
  bash server-toolbox.sh write-hosts hosts.cluster
  bash server-toolbox.sh init-all k3s-server-01 hosts.cluster
EOF
}

log_info() {
    printf '[信息] %s %s\n' "$(date '+%F %T')" "$*" | tee -a "$log_file"
}

log_error() {
    printf '[错误] %s %s\n' "$(date '+%F %T')" "$*" | tee -a "$log_file" >&2
}

die() {
    log_error "$*"
    exit 1
}

run_cmd() {
    log_info "执行命令: $*"

    # dry-run 只打印动作,用于批量执行前确认脚本会改哪些地方
    if [ "$dry_run" -eq 1 ]; then
        return 0
    fi

    "$@"
}

require_root() {
    # dry-run 不实际修改系统,可以在普通用户下预览
    if [ "$dry_run" -eq 1 ]; then
        return 0
    fi

    [ "$(id -u)" -eq 0 ] || die "当前操作需要 root 权限"
}

backup_file() {
    local file_path="$1"
    local backup_path

    [ -e "$file_path" ] || return 0

    backup_path="${file_path}.$(date '+%F-%H%M%S').bak"
    log_info "备份文件: $file_path -> $backup_path"

    if [ "$dry_run" -eq 0 ]; then
        cp -a "$file_path" "$backup_path"
    fi
}

parse_args() {
    while [ "$#" -gt 0 ]; do
        case "$1" in
            --dry-run)
                dry_run=1
                shift
                ;;
            --log-file)
                log_file="${2:-}"
                [ -n "$log_file" ] || die "--log-file 缺少路径"
                shift 2
                ;;
            -h|--help|help)
                command_name="help"
                shift
                break
                ;;
            *)
                command_name="$1"
                shift
                break
                ;;
        esac
    done

    command_args=("$@")
}

detect_os() {
    [ -r /etc/os-release ] || die "未找到 /etc/os-release,无法识别系统"

    # shellcheck disable=SC1091
    . /etc/os-release

    os_id="${ID:-}"
    os_like="${ID_LIKE:-}"
    log_info "识别系统: ID=$os_id ID_LIKE=$os_like"
}

is_rhel_like() {
    [[ "$os_id" =~ ^(rhel|centos|rocky|almalinux)$ || "$os_like" == *"rhel"* ]]
}

is_debian_like() {
    [[ "$os_id" =~ ^(debian|ubuntu)$ || "$os_like" == *"debian"* ]]
}

cmd_check() {
    log_info "开始系统体检"

    echo "== 系统信息 =="
    hostnamectl --static 2>/dev/null || hostname
    cat /etc/os-release | grep -E '^(NAME|VERSION)=' || true
    uname -r

    echo
    echo "== 时间同步 =="
    timedatectl status --no-pager | grep -E 'Time zone|System clock synchronized|NTP service' || true
    chronyc tracking 2>/dev/null | grep -E 'Reference ID|Leap status' || true

    echo
    echo "== 网络信息 =="
    ip -brief addr
    ip route | head -n 5

    echo
    echo "== 磁盘和内存 =="
    df -h
    free -h
    swapon --show || true

    echo
    echo "== 监听端口 =="
    ss -lntup

    echo
    echo "== 系统参数 =="
    sysctl net.ipv4.ip_forward vm.swappiness 2>/dev/null || true
    ulimit -n
}

cmd_set_hostname() {
    local hostname_value="${1:-}"
    local current

    [ -n "$hostname_value" ] || die "缺少主机名,例如:set-hostname k3s-server-01"

    current="$(hostnamectl --static 2>/dev/null || hostname)"
    if [ "$current" = "$hostname_value" ]; then
        log_info "主机名已经是 $hostname_value"
        return 0
    fi

    require_root
    run_cmd hostnamectl set-hostname "$hostname_value"
    log_info "主机名已设置为 $hostname_value"
}

cmd_write_hosts() {
    local hosts_file="${1:-}"
    local line

    [ -n "$hosts_file" ] || die "缺少 hosts 文件,例如:write-hosts hosts.cluster"
    [ -r "$hosts_file" ] || die "hosts 文件不可读: $hosts_file"

    require_root
    backup_file /etc/hosts

    while IFS= read -r line; do
        [ -n "$line" ] || continue
        [[ "$line" == \#* ]] && continue

        if grep -Fxq "$line" /etc/hosts; then
            log_info "hosts 记录已存在: $line"
            continue
        fi

        log_info "追加 hosts 记录: $line"
        if [ "$dry_run" -eq 0 ]; then
            printf '%s\n' "$line" >> /etc/hosts
        fi
    done < "$hosts_file"

    log_info "hosts 写入完成,可用 getent hosts <主机名> 复查解析结果"
}

install_chrony_if_needed() {
    if command -v chronyc >/dev/null 2>&1; then
        log_info "chrony 已安装"
        return 0
    fi

    log_info "安装 chrony"
    if is_rhel_like; then
        if command -v dnf >/dev/null 2>&1; then
            run_cmd dnf install -y chrony
        else
            run_cmd yum install -y chrony
        fi
        return 0
    fi

    if is_debian_like; then
        run_cmd apt update
        run_cmd apt install -y chrony
        return 0
    fi

    die "当前系统暂未适配 chrony 安装: $os_id"
}

cmd_config_time() {
    local ntp_server="${1:-ntp.aliyun.com}"
    local conf
    local service

    require_root
    detect_os

    if is_rhel_like; then
        conf="/etc/chrony.conf"
        service="chronyd"
    elif is_debian_like; then
        conf="/etc/chrony/chrony.conf"
        service="chrony"
    else
        die "当前系统暂未适配 chrony 配置: $os_id"
    fi

    install_chrony_if_needed
    backup_file "$conf"

    log_info "写入 chrony 配置: $conf"
    if [ "$dry_run" -eq 0 ]; then
        cat > "$conf" <<EOF
# 由 server-toolbox.sh 管理
server ${ntp_server} iburst
makestep 1.0 3
rtcsync
EOF
    fi

    run_cmd systemctl enable --now "$service"
    run_cmd systemctl restart "$service"
    log_info "时间同步已配置,复查命令: timedatectl status; chronyc sources -v"
}

cmd_install_tools() {
    require_root
    detect_os

    if is_rhel_like; then
        if command -v dnf >/dev/null 2>&1; then
            run_cmd dnf install -y vim curl wget tar unzip lsof net-tools bind-utils bash-completion chrony
        else
            run_cmd yum install -y vim curl wget tar unzip lsof net-tools bind-utils bash-completion chrony
        fi
        return 0
    fi

    if is_debian_like; then
        run_cmd apt update
        run_cmd apt install -y vim curl wget tar unzip lsof net-tools dnsutils bash-completion chrony
        return 0
    fi

    die "当前系统暂未适配基础工具安装: $os_id"
}

cmd_config_sysctl() {
    local conf="/etc/sysctl.d/99-base.conf"

    require_root
    backup_file "$conf"
    log_info "写入内核参数: $conf"

    if [ "$dry_run" -eq 0 ]; then
        cat > "$conf" <<'EOF'
# 由 server-toolbox.sh 管理
# 开启 IPv4 转发;容器网络、路由转发、LVS 等场景会用到
net.ipv4.ip_forward = 1

# 降低系统主动使用 swap 的倾向,具体值仍需结合业务内存情况
vm.swappiness = 10
EOF
    fi

    run_cmd sysctl --system
}

cmd_config_limits() {
    local conf="/etc/security/limits.d/99-base.conf"

    require_root
    backup_file "$conf"
    log_info "写入资源限制: $conf"

    if [ "$dry_run" -eq 0 ]; then
        cat > "$conf" <<'EOF'
# 由 server-toolbox.sh 管理
# nofile 控制最大打开文件数,高并发服务经常需要调高
* soft nofile 65535
* hard nofile 65535

# nproc 控制最大进程数,避免默认值过小影响批量任务或服务进程
* soft nproc 65535
* hard nproc 65535
EOF
    fi

    log_info "limits 已写入,新登录会话生效;systemd 服务还要看 unit 里的 LimitNOFILE"
}

cmd_init_all() {
    local hostname_value="${1:-}"
    local hosts_file="${2:-}"

    [ -n "$hostname_value" ] || die "缺少主机名,例如:init-all k3s-server-01 hosts.cluster"

    cmd_set_hostname "$hostname_value"

    if [ -n "$hosts_file" ]; then
        cmd_write_hosts "$hosts_file"
    fi

    cmd_install_tools
    cmd_config_time "ntp.aliyun.com"
    cmd_config_sysctl
    cmd_config_limits
    cmd_check
}

main() {
    parse_args "$@"

    case "$command_name" in
        help)
            show_help
            ;;
        check)
            cmd_check "${command_args[@]}"
            ;;
        set-hostname)
            cmd_set_hostname "${command_args[@]}"
            ;;
        write-hosts)
            cmd_write_hosts "${command_args[@]}"
            ;;
        config-time)
            cmd_config_time "${command_args[@]}"
            ;;
        install-tools)
            cmd_install_tools "${command_args[@]}"
            ;;
        config-sysctl)
            cmd_config_sysctl "${command_args[@]}"
            ;;
        config-limits)
            cmd_config_limits "${command_args[@]}"
            ;;
        init-all)
            cmd_init_all "${command_args[@]}"
            ;;
        *)
            show_help >&2
            die "未知子命令: $command_name"
            ;;
    esac
}

main "$@"

八、批量执行

节点清单 nodes.txt

text
192.168.10.11 k3s-server-01
192.168.10.12 k3s-server-02
192.168.10.13 k3s-server-03

hosts 片段 hosts.cluster

text
192.168.10.11 k3s-server-01
192.168.10.12 k3s-server-02
192.168.10.13 k3s-server-03

批量执行示例:

bash
#!/usr/bin/env bash
set -euo pipefail

node_file="nodes.txt"
hosts_file="hosts.cluster"

while read -r ip hostname_value; do
    [ -n "${ip:-}" ] || continue
    [[ "$ip" == \#* ]] && continue

    echo "===== 初始化 $ip / $hostname_value ====="

    # 分发工具箱脚本和 hosts 片段
    scp server-toolbox.sh "$hosts_file" "root@${ip}:/root/"

    # 远端执行组合命令;BatchMode 避免认证失败时卡在交互输入
    ssh -o BatchMode=yes -o ConnectTimeout=5 "root@${ip}" \
        "bash /root/server-toolbox.sh init-all '$hostname_value' /root/$hosts_file"
done < "$node_file"

批量执行前适合跑 dry-run:

bash
ssh -o BatchMode=yes -o ConnectTimeout=5 root@192.168.10.11 \
    "bash /root/server-toolbox.sh --dry-run init-all k3s-server-01 /root/hosts.cluster"

dry-run 只打印动作,不真正修改系统。确认主机名、hosts 文件、NTP 源都对上后,再执行正式初始化。

九、和系统基线的关系

脚本只是把固定动作做成入口。每个配置项背后的含义仍然要能看懂,比如 chrony 的 makestep 为什么只在启动初期做大偏差校正,limits.d 为什么不一定影响 systemd 服务,sysctl.d 为什么比直接改 /etc/sysctl.conf 更好管理。对应的系统层说明放在 系统初始化基线