资讯动态

多台Ubuntu服务器批量初始化脚本实战:SSH自动化部署与幂等设计

发布时间:2026/10/22 7:45:55 来源:尧图企业网站定制
这次我们来看一个多台 Ubuntu 服务器初始化脚本的实战方案。场景非常常见新到一批 Ubuntu 服务器要统一做基础配置——设置时区、安装常用工具、装 Docker、配置 Zabbix Agent、加固 SSH。如果一台一台登录去敲命令几台还能接受几十台就非常浪费时间而且每台的执行结果对不对也没有统一记录。更麻烦的是同一套步骤在不同机器上执行经常会因为漏了一条命令产生差异后面排查起来很头疼。这个项目的核心不是框架而是两个 bash 脚本一个控制端脚本run-remote-init.sh读取hosts.ini里的主机清单通过 scp 把初始化脚本上传到目标机器再通过 ssh 批量执行另一个是目标端脚本ubuntu-node-init.sh负责在 Ubuntu 上完成分阶段初始化。从设计上重点解决四个问题主机清单驱动、幂等可重跑、并发可控、日志与摘要完整。硬件门槛很低。控制端一台能跑 bash 的 Linux 或 macOS 主机就行一般 2 核 4G 足够目标主机建议 Ubuntu 18.04 或 20.04 以上开启 SSH 服务并且有一个具备 sudo 权限的账号。磁盘上脚本只占几 MB真正的开销主要在 ssh、scp 连接和 apt 更新时目标主机的 CPU、磁盘 IO。这篇文章会给出完整可复制的脚本代码按“环境准备 → SSH 免密配置 → 编写脚本 → 单机验证 → 批量执行 → 并发控制 → 结果复核 → 问题排查”的顺序展开。运维、DevOps、实验室管理员和手里有多台 Ubuntu 机器需要批量初始化的人都可以直接照做。1. 核心能力速览能力项说明项目类型基于 bash ssh scp 自建初始化脚本无外部框架依赖目标系统Ubuntu 18.04 / 20.04 / 22.04建议 20.04 以上控制端要求Linux 或 macOSbash 4.3 以上能访问目标主机 22 端口目标主机要求开启 SSH 服务具备 root 或 sudo 权限磁盘剩余空间建议 5GB 以上执行方式控制端读取主机清单scp 上传脚本ssh 远程执行主机清单hosts.ini 每行一个userhost支持注释和空行并发控制通过环境变量CONCURRENCY设置默认 4避免一次性压垮网络幂等重跑脚本内每个任务函数都做存在性判断重复执行不会重复添加配置日志与摘要每台主机独立日志文件执行结束输出 OK/FAIL 汇总清单接口 API不提供 HTTP API批量任务由脚本直接驱动如需 Web 调用可自行封装适用场景新机器批量初始化、统一安全基线、批量安装 Docker/监控 Agent这个项目的核心价值在于“可维护、可重跑、可验证”。初始化不是一次性操作后续很多机器会补货或者某台机器初始化到一半失败需要重新跑。如果脚本不具备幂等性重跑就会产生重复配置、重复安装、配置漂移所以设计时把幂等放在第一位。2. 适用场景与使用边界这个方案最适合三类场景。第一类是一次性批量初始化大量同规格 Ubuntu 主机比如云上新开 30 台机器需要统一装基础软件、统一时区、统一用户名第二类是构建统一安全基线例如关闭 SSH 密码登录、开启 UFW、配置自动安全更新第三类是实验室或内部环境没有现成的 Ansible/SaltStack 体系临时需要一套轻量批量执行手段。不适合的场景也很明确。如果团队已经在用 Ansible、SaltStack、Puppet 这类配置管理工具并且有完善的 inventory 和 playbook就没必要用这个脚本替代直接用配置管理工具才是正确选择。另外如果目标主机异构程度很高有 Ubuntu、CentOS、Debian 混用这套脚本还需要按发行版拆分不建议在一个脚本里硬塞太多分支条件否则后期维护成本会上升。安全边界需要特别强调。脚本会在目标主机上用 root 权限执行所有代码必须经过审查才能进入生产环境。涉及 SSH 加固的步骤一定要确认所有管理入口都已经配置好公钥否则关闭密码登录后可能把自己锁在外面。涉及防火墙开启的步骤先放行当前管理网段再执行ufw enable。如果脚本里包含服务器 IP、密钥、账号等敏感信息不要明文写在仓库里建议用变量注入或单独管理。任何时候都不要在脚本中硬编码真实生产环境的密码。另外目标主机可能不是完全空白的系统。如果机器上已经运行了业务跑初始化脚本前必须确认脚本不会覆盖现有配置比如不会重置/etc/ssh/sshd_config、不会卸载已有软件、不会修改正在使用的网络配置。建议先在测试机跑通再灰度到生产。3. 环境准备与前置条件3.1 控制端检查清单控制端不需要安装额外软件系统自带 OpenSSH 客户端即可。先确认本机 ssh 可用并记录 bash 版本which ssh which scp bash --version | head -n 1建议 bash 4.3 以上。后面批量并发控制里会用到wait -n这个参数要求 bash 4.3。如果控制端 bash 版本偏低要么升级 bash要么把并发控制改成轮询jobs -r的方式。确认控制端能够访问目标主机的 22 端口。不要等到批量执行时才发现网络不通提前用一个简单的 ssh 连通性测试扫一遍所有主机while read -r host; do [ -z $host ] continue [[ $host \#* ]] continue if ssh -o BatchModeyes -o ConnectTimeout5 $host echo ok /dev/null 21; then echo $host 连通 else echo $host 不通 fi done hosts.ini这个命令要求 hosts.ini 已经存在并且已经配置好免密登录。如果还没配先不要批量测试逐台执行也可以。3.2 目标主机检查清单目标主机需要满足几个条件系统是 UbuntuSSH 服务已启动当前登录用户有 sudo 权限磁盘剩余空间足够 apt 更新和软件安装。逐台登录确认的耗时比较长可以在初始化脚本里加一个前置检查任务自动判断环境是否满足要求。下面是目标主机常见的预检查命令# 查看系统版本 cat /etc/os-release # 查看磁盘空间 df -h / # 确认 sudo 权限 sudo -n true echo sudo ok # 确认 SSH 服务状态 systemctl is-active ssh || systemctl is-active sshd3.3 网络与端口要求如果目标主机在云环境安全组或防火墙需要放行控制端来源 IP 的 22 端口访问。如果目标主机在机房要确认机房网络策略允许控制端主动建立 SSH 连接。整个初始化过程是控制端主动连目标主机不需要在目标主机上开放额外入站端口除非你选择在目标主机上开启 HTTP 回调等自定义功能。初始化脚本本身会在目标主机上执行 apt 更新、下载软件包因此目标主机还需要能访问外网的 apt 源。对于无法直连外网的内网环境需要提前配置好内网 apt 源或者把离线 deb 包准备好再调整脚本中的安装逻辑。4. SSH 免密登录与主机清单准备4.1 生成密钥并推送公钥批量执行的前提是控制端到目标主机实现 SSH 免密登录。如果还没有密钥先生成一对ssh-keygen -t ed25519 -C ubuntu-init-control -f ~/.ssh/id_ed25519生成后不要直接对几十台机器逐台输入密码先用ssh-copy-id把公钥推送到第一批测试机上ssh-copy-id -i ~/.ssh/id_ed25519.pub root192.168.1.101如果目标主机只允许普通用户登录先推给这个普通用户后续通过 sudo 执行初始化ssh-copy-id -i ~/.ssh/id_ed25519.pub ops192.168.1.101推送完成后验证一条命令能免密执行ssh ops192.168.1.101 sudo -n true echo ok这一条通过后后续所有批量执行都基于同一个认证通道不会再出现交互式密码提示。4.2 hosts.ini 主机清单格式主机清单文件是控制端脚本的输入。推荐每行一个目标主机格式为userhost支持注释和空行。这样scp和ssh命令不需要额外解析字段直接用整行作为连接目标。# hosts.ini # 每行一个目标主机格式为 userhost # 先用 # 注释掉暂时不想执行的主机 ops192.168.1.101 ops192.168.1.102 ops192.168.1.103 rootnode-04.example.com如果目标主机的 SSH 端口不是默认 22建议不要直接把端口写进userhost而是通过~/.ssh/config配置别名。例如在控制端~/.ssh/config中写Host node-01 HostName 192.168.1.101 Port 2222 User ops IdentityFile ~/.ssh/id_ed25519然后 hosts.ini 里直接写node-01控制端脚本就无需处理端口解析逻辑更加简洁可靠。批量初始化这种场景统一用默认 22 端口最简单特殊端口的主机单独走 SSH config 别名。5. 控制端批量执行脚本 run-remote-init.sh控制端脚本负责三件事读取主机清单、上传目标端脚本、远程执行并收集结果。下面是完整实现。#!/usr/bin/env bash # run-remote-init.sh # 多台 Ubuntu 服务器批量初始化控制端脚本 set -uo pipefail HOST_FILE${HOST_FILE:-hosts.ini} REMOTE_SCRIPT${REMOTE_SCRIPT:-ubuntu-node-init.sh} REMOTE_PATH/tmp/ubuntu-node-init.sh # 并发数环境变量可覆盖默认 4 CONCURRENCY${CONCURRENCY:-4} LOGS_DIRlogs SUMMARY_DIRsummary mkdir -p $LOGS_DIR $SUMMARY_DIR summary_file$SUMMARY_DIR/summary-$(date %Y%m%d-%H%M%S).txt : $summary_file running0 run_one_host() { local host$1 local tag tag$(echo $host | tr -c a-zA-Z0-9._- _) local log_file$LOGS_DIR/${tag}.log echo [$(date %F %T)] 开始 $host | tee -a $log_file if scp -q $REMOTE_SCRIPT $host:$REMOTE_PATH $log_file 21; then if ssh -o ConnectTimeout10 -o BatchModeyes $host sudo bash $REMOTE_PATH $log_file 21; then echo [$(date %F %T)] OK $host | tee -a $log_file echo OK $host $summary_file else echo [$(date %F %T)] FAIL $host | tee -a $log_file echo FAIL $host $summary_file fi else echo [$(date %F %T)] SCP_FAIL $host | tee -a $log_file echo FAIL $host $summary_file fi } while IFS read -r host; do [[ -z $host ]] continue [[ $host \#* ]] continue run_one_host $host running$((running 1)) if [ $running -ge $CONCURRENCY ]; then wait -n running$((running - 1)) fi done $HOST_FILE wait echo 批量初始化汇总 cat $summary_file echo 脚本逻辑说明如下。第一set -uo pipefail开启未定义变量检查和管道失败检测但不开启set -e。原因在于批量执行时一台主机失败不应该中断整个脚本失败信息要进入日志和汇总文件而不是直接退出。第二run_one_host函数在子 shell 中后台执行因此 OK/FAIL 结果写入$summary_file而不是通过变量返回避免丢失子进程状态。第三SSH 参数使用-o BatchModeyes如果某台主机免密没配好会直接失败而不是卡在密码交互界面。这个设计很重要批量执行时不希望有任何交互等待。第四wait -n是并发控制的核心。后台任务数达到CONCURRENCY后等待任意一个任务结束再继续拉起下一个从而把同时执行的 SSH 进程数量限制在设定值内。如果控制端 bash 低于 4.3可以把这段替换成if [ $running -ge $CONCURRENCY ]; then while [ $(jobs -r | wc -l) -ge $CONCURRENCY ]; do sleep 1 done running$((running - 1)) fi两种写法都能限制并发实际效果差不多wait -n更接近事件驱动轮询方式兼容性更好。6. 目标端初始化脚本 ubuntu-node-init.sh目标端脚本是整个方案的核心。它被 scp 上传到/tmp后由 sudo 执行。脚本采用“任务函数 分阶段运行”的结构每个任务函数都必须是幂等的。#!/usr/bin/env bash # ubuntu-node-init.sh # 在目标 Ubuntu 主机上执行的初始化脚本 set -uo pipefail LOG_FILE/var/log/ubuntu-node-init.log exec (tee -a $LOG_FILE) 21 # 阶段执行器 run_stage() { local stage$1 shift echo [$stage] 开始 for task_func in $; do echo ----- 任务: $task_func ----- if $task_func; then echo 任务成功: $task_func else echo 任务失败: $task_func fi done echo [$stage] 结束 }这种设计的好处是新增一个任务只需要写一个函数然后在对应阶段里加上函数名。某个阶段失败不会中断后续阶段最终可以通过日志看出哪一个任务失败。6.1 阶段一基础配置基础配置阶段处理时区、系统更新、时间同步。时区设置先判断当前值再修改避免脚本重复执行时每次都会触发timedatectl。task_set_timezone() { local current current$(timedatectl show -p Timezone --value 2/dev/null || echo UTC) if [ $current ! Asia/Shanghai ]; then timedatectl set-timezone Asia/Shanghai fi echo 当前时区: $(timedatectl show -p Timezone --value) } task_update_apt() { export DEBIAN_FRONTENDnoninteractive apt-get update -y apt-get upgrade -y --no-install-recommends || true } task_enable_timesync() { timedatectl set-ntp true systemctl enable --now systemd-timesyncd || true }task_update_apt中apt-get upgrade加|| true是因为某些环境有包被 hold 或者内核升级需要重启如果因为单个包失败导致整个脚本中断后续阶段全部无法执行。初始化阶段的目标是尽量推进到可交付状态而不是追求单次升级零失败。6.2 阶段二基础软件安装基础软件安装要逐个判断包是否已经存在。dpkg -s返回 0 表示包已安装此时跳过安装这就是幂等性的基础。task_install_packages() { local packages(curl wget git vim htop tree jq net-tools unzip ca-certificates gnupg lsb-release) export DEBIAN_FRONTENDnoninteractive for pkg in ${packages[]}; do if dpkg -s $pkg /dev/null 21; then echo 包 $pkg 已存在跳过 else echo 安装 $pkg apt-get install -y $pkg fi done }有些环境里 unzip 未安装会导致后续脚本解压失败因此基础工具包的清单里尽量覆盖常见运维工具。读者可以按自己团队的软件标准增删。创建统一运维用户是批量初始化的常见需求。下面这个函数在 root 用户下执行时会创建一个ops用户并加入 sudo 组同时把 root 的authorized_keys复制过去确保运维可以通过同一把公钥登录。task_create_ops_user() { local user${OPS_USER:-ops} if id $user /dev/null 21; then echo 用户 $user 已存在跳过 else useradd -m -s /bin/bash $user usermod -aG sudo $user fi mkdir -p /home/$user/.ssh cp /root/.ssh/authorized_keys /home/$user/.ssh/authorized_keys 2/dev/null || true chown -R $user:$user /home/$user/.ssh chmod 700 /home/$user/.ssh chmod 600 /home/$user/.ssh/authorized_keys }6.3 阶段三Docker 安装Docker 安装需要先判断是否已经安装。已经安装的机器直接返回避免重复拉取仓库和安装包。安装流程参考 Docker 官方 Ubuntu 安装文档使用/etc/os-release中的版本代号动态生成 apt 源适配不同 Ubuntu 大版本。task_install_docker() { if command -v docker /dev/null 21; then echo Docker 已安装: $(docker --version) return 0 fi export DEBIAN_FRONTENDnoninteractive # 清理可能存在的旧版本 for pkg in docker docker-engine docker.io containerd runc; do apt-get remove -y $pkg 2/dev/null || true done install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc chmod ar /etc/apt/keyrings/docker.asc source /etc/os-release echo deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $VERSION_CODENAME stable /etc/apt/sources.list.d/docker.list apt-get update apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin systemctl enable --now docker }这里有一个细节需要关注Docker 官方安装文档在不同时期的 GPG key 路径写法有变化较早版本用的是/etc/apt/keyrings/docker.gpg新版可能用/etc/apt/keyrings/docker.asc。实际部署时以目标系统对应的 Docker 官方文档为准如果出现 GPG 校验失败可以先检查 keyring 文件是否存在再确认 apt 源里的signed-by路径是否匹配。6.4 阶段四SSH 加固与防火墙SSH 加固是整个初始化脚本里风险最高的步骤。执行前必须确保公钥登录已经验证过。下面的函数用sed修改sshd_config而不是直接覆盖整个文件这样不会影响目标主机原有的其他 SSH 配置。task_harden_ssh() { local sshd_config/etc/ssh/sshd_config # PermitRootLogin 设置为 prohibit-password if grep -qE ^PermitRootLogin $sshd_config; then sed -i s/^PermitRootLogin .*/PermitRootLogin prohibit-password/ $sshd_config else echo PermitRootLogin prohibit-password $sshd_config fi # 关闭密码登录 if grep -qE ^PasswordAuthentication $sshd_config; then sed -i s/^PasswordAuthentication .*/PasswordAuthentication no/ $sshd_config else echo PasswordAuthentication no $sshd_config fi systemctl restart ssh }注意如果目标主机只有密码登录一种方式关闭PasswordAuthentication之后当前登录会话虽然没有断开但下次登录会失败。因此这个函数必须放在公钥免密配置验证之后或者先用ops用户的公钥登录测试通过再执行加固。防火墙函数同样要注意不要把管理通道断掉。下面示例只放行 22 端口实际使用时把管理网段先加白名单再放行业务端口。task_setup_ufw() { if ! command -v ufw /dev/null 21; then echo ufw 未安装跳过 return 0 fi # 先放行管理网段不要直接 enable # ufw allow from 203.0.113.0/24 to any port 22 ufw allow 22/tcp # ufw allow 80,443/tcp ufw --force enable }ufw --force enable用于非交互式启用不加--force时 UFW 会询问是否确认批量执行时会卡住。6.5 阶段五Zabbix Agent 扩展模板Zabbix Agent 不是每台机器都需要的因此这里只给出函数模板。不同 Zabbix 大版本的官方源地址差异较大不做统一硬编码需要按实际版本替换仓库地址。task_install_zabbix_agent() { if command -v zabbix_agentd /dev/null 21; then echo Zabbix Agent 已安装跳过 return 0 fi # 按 Zabbix 官方文档添加对应 Ubuntu 版本的仓库 # 示例先下载并安装 zabbix-release 包 # 再 apt-get install -y zabbix-agent # 最后修改 /etc/zabbix/zabbix_agentd.conf 中的 Server 和 ServerActive apt-get install -y zabbix-agent || { echo Zabbix Agent 安装失败请检查官方源 return 1 } systemctl enable --now zabbix-agent }Zabbix Agent 的仓库配置涉及具体版本号建议先在一台测试机上手动完成安装把确认可用的仓库地址写进脚本再批量推广。6.6 主函数组装主函数按阶段调用便于单独跳过或重新执行某个阶段。main() { run_stage 阶段1 基础配置 \ task_set_timezone \ task_update_apt \ task_enable_timesync run_stage 阶段2 基础软件 \ task_install_packages \ task_create_ops_user run_stage 阶段3 Docker 安装 \ task_install_docker run_stage 阶段4 安全加固 \ task_harden_ssh \ task_setup_ufw # 按需启用 # run_stage 阶段5 Zabbix Agent \ # task_install_zabbix_agent } main需要说明的是这里的主函数编排只是一个示例顺序。实际部署时建议把安全加固放到靠后阶段等确认基础环境全部可用后再执行。这样即使某个基础软件安装失败也不会因为 SSH 被加固后无法远程登录而陷入被动。7. 单台验证与批量执行7.1 先用一台机器走通批量执行前必须先用单台机器验证。把 hosts.ini 临时改成只剩一台测试机并发数设为 1CONCURRENCY1 ./run-remote-init.sh执行过程中可以实时看日志tail -f logs/ops_192.168.1.101.log日志会同时输出到终端和/var/log/ubuntu-node-init.log的远程日志因为远程脚本里做了tee输出。第一次执行时重点看三个东西脚本有没有出现交互式卡住、apt 更新是否成功、SSH 加固后当前连接是否还能保持。7.2 单机结果验证脚本执行完成后在控制端直接通过 SSH 检查目标主机状态ssh ops192.168.1.101 timedatectl | grep Time zone id ops docker --version systemctl is-active docker ufw status | head -n 5 判断标准如下时区显示Asia/Shanghai说明基础配置生效。id ops能显示用户信息说明运维用户创建成功。docker --version能输出版本号systemctl is-active docker输出active说明 Docker 安装正常。ufw status显示Status: active说明防火墙已启用。7.3 检查幂等性单机验证完成后再执行一次同样的脚本确认第二次执行不会产生异常。幂等判断的要点包括软件包不会重复安装、配置文件不会重复追加、SSH 配置不会重复修改、Docker 不会重复安装。第二次执行如果日志里出现大量“已存在跳过”字样说明幂等设计在起作用。下面用命令快速检查是否有重复配置ssh ops192.168.1.101 grep -c PasswordAuthentication no /etc/ssh/sshd_config这个命令输出应该是1如果输出大于 1 说明脚本追加逻辑有问题需要检查是否缺少 grep 判断。7.4 批量执行与并发调整单机验证通过后把 hosts.ini 还原成完整主机清单设置一个合理的并发数。初次批量执行建议用CONCURRENCY4执行命令如下CONCURRENCY4 ./run-remote-init.sh批量执行期间不用一直盯着终端可以隔一段时间查看summary/目录下的汇总文件while true; do clear cat summary/summary-*.txt sleep 5 done汇总文件里会不断追加 OK 或 FAIL 行。全部执行结束后重点看 FAIL 主机的日志定位失败原因。对于失败主机可以在确认问题修复后单独重新执行。因为脚本是幂等的失败主机重跑不会影响已经完成的部分这是这类脚本相比手工逐台操作的核心优势。8. 资源占用与性能观察8.1 控制端资源占用控制端本身不会占用太多资源。每个并发任务对应一个 scp 进程和一个 ssh 进程一般 2 核 4G 的机器跑并发 8 到 10 台也没有压力。真正的瓶颈通常不在控制端 CPU而在网络带宽和控制端到目标主机之间的 SSH 握手速率。如果 hosts.ini 里包含大量主机建议先分成小批次执行例如每批 10 台执行完一批确认汇总结果后再跑下一批。一次跑 50 台虽然也能工作但网络抖动导致中间某几台失败时日志排查会稍微麻烦。8.2 目标主机资源占用目标主机的资源峰值主要出现在 apt update 和 apt upgrade 阶段。apt 会启动多个 dpkg 进程磁盘 IO 和 CPU 占用会明显上升。如果目标主机是生产环境初始化脚本应该避开业务高峰或者在 apt upgrade 时手动控制并发数。Docker 安装阶段需要下载较多软件包网络延迟高时单台机器可能耗时几分钟。批量并发时控制端不等待某台完成再执行下一台而是同时发起多台因此如果 20 台机器同时 apt update总带宽消耗会比较大。对于网络环境一般的机房CONCURRENCY2或CONCURRENCY4是更稳妥的选择。8.3 日志与磁盘占用远程初始化脚本会把日志写入/var/log/ubuntu-node-init.logapt upgrade 的输出非常长一台机器全量更新后日志可能达到几 MB。批量几十台的情况下控制端logs/目录累积的日志量也需要关注。建议定期清理旧日志或者在上传脚本时加入简单的日志压缩逻辑。控制端日志命名已经避免了特殊字符冲突。如果同一台机器分多次执行日志文件会被覆盖所以建议在关键时间点执行完批量任务后把整个logs/和summary/目录打包归档tar -czf init-$(date %Y%m%d-%H%M%S).tar.gz logs summary hosts.ini归档文件里包含主机清单、每台机器的日志和最终汇总后续排查问题时可以直接对照。9. 常见问题与排查方法问题现象可能原因排查方式解决方案ssh 连接超时目标主机 22 端口不通、安全组未放行ssh -v userhost查看连接日志检查安全组、路由和 sshd 服务状态SSH 连接被拒绝目标主机 sshd 未启动或端口配置变化systemctl status ssh或ss -lntp启动 sshd 服务免密登录未生效公钥未写入目标主机 authorized_keys手动执行 ssh 观察是否要密码重新执行 ssh-copy-idscp 上传失败目标主机 /tmp 目录权限异常或磁盘满df -h /tmp清理临时文件sudo 执行失败登录用户不在 sudo 组或 sudoers 配置受限执行sudo -n true先用 root 用户执行或调整 sudoersapt 锁冲突目标主机有 apt 进程残留ps auxgrep apt使用wait -n报错控制端 bash 版本过低bash --version升级 bash或替换成 jobs 轮询并发控制关闭密码登录后无法登录加固步骤执行时公钥未配置好检查 authorized_keys 是否存在通过云控制台或带外管理恢复登录Docker GPG 校验失败keyring 路径或源地址不匹配ls -l /etc/apt/keyrings/按 Docker 官方文档重新配置脚本重复执行后配置重复任务函数缺少 grep 判断检查 sshd_config 等配置文件行数修改函数增加存在性判断某台主机初始化中断网络抖动或 apt 下载失败查看该主机独立日志修复后重跑脚本幂等逻辑会自动跳过已完成任务汇总文件里出现 FAIL阶段中某个任务函数返回非 0查看对应日志中“任务失败”之前的内容定位具体任务函数并修复最常遇到的其实是两类问题一类是免密登录没有真正打通导致 ssh 批量执行时直接失败另一类是 apt 源问题比如目标主机内网环境无法访问外网或 Docker 官方源被网络策略阻断。这两类问题建议在环境准备阶段提前验证不要等到批量跑的时候才发现。10. 最佳实践与使用建议批量初始化脚本要成功落地而不是写完就扔建议从以下几个方面约束使用方式。第一先小后大。第一次使用无论如何都要先在一台测试机跑通然后把 hosts.ini 扩展为 3 到 5 台小批量验证确认无异常后再扩大到全量。不要一上来就 50 台并发执行否则一旦某个函数存在缺陷所有机器都会受影响。第二分阶段灰度。如果初始化脚本包含安全加固和 Docker 安装可以先把主函数里的安全加固阶段注释掉跑完基础配置后再手动放开。尤其是 SSH 加固必须确认公钥登录已经验证过再执行这一阶段。第三保留最小可运行配置。脚本目录里放一份 hosts.ini.example 和 README记录清楚每个环境变量、每个阶段的作用避免三个月后自己都记不清当时的脚本逻辑。脚本里的变量尽量用环境变量注入比如OPS_USER、CONCURRENCY不要在脚本内部写死。第四批量任务必须有日志和重跑机制。本文的脚本已经具备每台独立日志和汇总输出实际使用中如果发现某台失败修复后单独重跑即可。不要删除已有日志重新全量执行否则会丢失历史记录。第五权限和敏感信息管理。脚本本身不写密码所有认证依赖 SSH 密钥。密钥的私钥文件要严格保护公钥内容要确保只分发到授权的目标主机。涉及生产环境的服务器 IP、账号名建议通过变量文件单独管理不要提交到公共仓库。第六效果复核不能省。初始化脚本执行完不等于交付完成。建议在汇总结果全部 OK 后抽样几台机器检查时区、用户、Docker、防火墙、SSH 登录状态。批量操作越顺利越要警惕“看起来都成功但实际配置不一致”的情况。第七版权与合规提醒。如果脚本里涉及从第三方源下载软件包比如 Docker、Zabbix请确认这些软件的使用范围和授权合规。目标主机上的数据、账号、密钥等都属于敏感信息操作前应确保符合所在团队或机构的运维规范。11. 总结与下一步这套多台 Ubuntu 服务器初始化脚本从架构上看就是一个控制端加一个目标端中间用 SSH 串联。真正值得借鉴的地方不是某个安装命令而是“任务函数化、阶段可裁剪、过程有日志、结果有汇总、重跑不重复”的设计方式。把这套思路复制到 CentOS 初始化、Windows 服务器初始化甚至网络设备配置备份上同样适用。最优先要验证的功能优先级是先把 SSH 免密登录打通再用单台机器跑通基础配置和 Docker 安装最后才放行安全加固和全量并发。最容易踩的坑也是这两处免密登录没有真正生效以及 SSH 加固后无法再次登录。前者可以通过BatchModeyes提前暴露后者需要依赖云控制台或带外管理作为兜底。如果后续要继续扩展可以考虑把这个脚本封装成带 Web 界面的执行器或者接入现有的配置管理平台把每台主机的初始化状态登记到 CMDB。现阶段先用这版脚本把批量初始化流程跑起来把日志和汇总机制沉淀下来后续无论迁移到 Ansible 还是其他自动化工具这套“执行前有检查、执行中有日志、执行后有复核”的运维习惯都能直接复用。建议收藏备用下次新机器到了直接打开这篇文章照跑一遍。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑