背景
因“杂乱的管理”和“历史的沉淀”导致大量服务器的时间问题乱象丛生,然而时间的正确对业务保障的重要性是毋庸置疑的,所以很有必要来一次大检查和大整改。
现状
征对Linux系统,问题复杂如下:
- 系统版本不一样,权限不一样,登陆方式不一样;
- 有的采用ntp客户端命令ntpdate,或与已下架NTP服务器、在线NTP服务器地址等NTP目标同步;
- 采用ntp客户端命令ntpdate同步,数量众多,数千台服务器几乎集中在同一时刻和NTP服务器同步,对NTP服务器来讲,请求集中在0.00001%的时间里,另外99.9999%的时间闲置;
- 有的启用了NTP服务,或与初始默认NTP地址、本地硬件时钟、已下架NTP服务器地址、在线NTP服务器地址等NTP目标同步;
- 有的采用chrony服务,同步目标也比较乱;
- 有的不能使用yum源;
…
需求
保证所有Linux服务器在开机时和开机后定期和在线NTP服务器保持时间同步。
分析和处置思路
因配置杂乱和登陆权限不同,一台一台检查和整改的效率低下,随机考查现场配置后,整理一个可反复执行,且能照顾所有Linux系统的配置脚本,通过Ansible一并反复跑几遍,每遍采用不同的权限。
脚本实现以下功能:
- 关闭ntp服务,采用ntp客户端命令ntpdate同步;
- ntpdate同步配置在开机启动和crontab中,通过RANDOM变量实现随机性(有同行采用python实现随机性的,但发现有的系统没有python),解决“NTP服务被请求的时间集中在0.00001%”的问题;
- rhel7+启用chronyd,考虑到现场的复杂性,不一定能安装chronyd,仍保留ntpdate同步,实践发现两者兼容不冲突;
- ntpdate同步结果写入日志以便观察,由logrotate管理日志生命周期。
脚本内容
op_timesync_rhel.sh脚本内容
(因信息安全需要,对脚本内容进行了精简,敏感内容进行了替换)
#!/bin/bash
###################################
# function 服务器时间同步配置整改
# 参考创建或使用示例: mkdir -p /root/sh/log; touch /root/sh/op_timesync_rhel.sh; chmod a+x /root/sh/op_timesync_rhel.sh
#
# Change History:
# date author note
# 2024/01/04 N create
#
###################################
export LANG=C
export PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
#获取系统大版本号,以RHEL为基准,至少适用于RHEL系5-9,包括CentOS,AlmaLinux,Rocky Linux等,目前把openEuler等转换版本后视为rhel判断
if [ -f /etc/redhat-release ];then
export rhel_release=`cat /etc/redhat-release | awk -F'(' '{ print $1; }' | awk '{ print $(NF-0); }' | awk -F. '{ print $1; }'`
elif [ -f /etc/openEuler-release ];then
openEuler_release=`cat /etc/openEuler-release | awk '{ print $3; }' | awk -F. '{ print $1; }'`
if [ "$openEuler_release" == 20 ];then
export rhel_release=8
elif [ "$openEuler_release" == 22 ];then
export rhel_release=9
fi
fi
if [ "$rhel_release" -le "6" ];then
#查看服务状态
/etc/init.d/ntpd status
#服务配置
yum install -y ntp
chkconfig --level 2345 ntpd off; /etc/init.d/ntpd stop
elif [ "$rhel_release" -ge "7" ];then
#查看服务状态
systemctl status chronyd.service; systemctl status ntpd.service
#服务配置
yum install -y chrony ntp
systemctl enable chronyd.service; systemctl restart chronyd.service
systemctl disable ntpd.service; systemctl stop ntpd.service
# 配置chronyd
sed -i '/oldntpip/d' /etc/chrony.conf
if (! grep -q auto_op_mark /etc/chrony.conf); then
sed -ri '/pool.ntp.org/s/^server .*/#&/' /etc/chrony.conf
sed -i '6a### auto_op_mark_begin ###' /etc/chrony.conf
sed -i '7aserver newntp_ip_or_domain_1 iburst' /etc/chrony.conf
sed -i '8aserver newntp_ip_or_domain_2 iburst' /etc/chrony.conf
sed -i '9a### auto_op_mark_end ###' /etc/chrony.conf
elif (grep -q auto_op_mark /etc/chrony.conf); then
echo "The time service automation configuration already exists, replace"
sed -i '/auto_op_mark_begin/,/auto_op_mark_end/d' /etc/chrony.conf
sed -ri '/pool.ntp.org/s/^server .*/#&/' /etc/chrony.conf
sed -i '6a### auto_op_mark_begin ###' /etc/chrony.conf
sed -i '7aserver newntp_ip_or_domain_1 iburst' /etc/chrony.conf
sed -i '8aserver newntp_ip_or_domain_2 iburst' /etc/chrony.conf
sed -i '9a### auto_op_mark_end ###' /etc/chrony.conf
else
echo "error"
fi
systemctl restart chronyd.service
sleep 5 # 停顿5秒才可能查询到正常状态
#同步状态
chronyc tracking | egrep "Reference ID|Leap status"
else
echo "error"
fi
# NTP配置
sed -i '/ntpdate.*oldntpip/d' /var/spool/cron/root
if (! grep -q auto_op_mark /var/spool/cron/root); then
echo '### auto_op_mark_begin ###' >> /var/spool/cron/root
echo '*/30 * * * * sleep $(expr $RANDOM \% 1800 + 1); /usr/sbin/ntpdate -u newntp_ip_or_domain_1 &>> /var/log/ntpdate.log && /sbin/hwclock -w' >> /var/spool/cron/root
echo '*/30 * * * * sleep $(expr $RANDOM \% 1800 + 1); /usr/sbin/ntpdate -u newntp_ip_or_domain_2 &>> /var/log/ntpdate.log && /sbin/hwclock -w' >> /var/spool/cron/root
echo '### auto_op_mark_end ###' >> /var/spool/cron/root
elif (grep -q auto_op_mark /var/spool/cron/root); then
echo "The time service automation configuration already exists, replace"
sed -i '/auto_op_mark_begin/,/auto_op_mark_end/d' /var/spool/cron/root
echo '### auto_op_mark_begin ###' >> /var/spool/cron/root
echo '*/30 * * * * sleep $(expr $RANDOM \% 1800 + 1); /usr/sbin/ntpdate -u newntp_ip_or_domain_1 &>> /var/log/ntpdate.log && /sbin/hwclock -w' >> /var/spool/cron/root
echo '*/30 * * * * sleep $(expr $RANDOM \% 1800 + 1); /usr/sbin/ntpdate -u newntp_ip_or_domain_2 &>> /var/log/ntpdate.log && /sbin/hwclock -w' >> /var/spool/cron/root
echo '### auto_op_mark_end ###' >> /var/spool/cron/root
else
echo "error"
fi
sed -i '/ntpdate.*oldntpip/d' /etc/rc.local
if (! grep -q auto_op_mark /etc/rc.local); then
echo '### auto_op_mark_begin ###' >> /etc/rc.local
echo 'echo "$(date) Ntpdate is executed when the system starts." &>> /var/log/ntpdate.log' >> /etc/rc.local
echo "/usr/sbin/ntpdate -u newntp_ip_or_domain_1 &>> /var/log/ntpdate.log && /sbin/hwclock -w" >> /etc/rc.local
echo "/usr/sbin/ntpdate -u newntp_ip_or_domain_2 &>> /var/log/ntpdate.log && /sbin/hwclock -w" >> /etc/rc.local
echo '### auto_op_mark_end ###' >> /etc/rc.local
elif (grep -q auto_op_mark /etc/rc.local); then
echo "The time service automation configuration already exists, replace"
sed -i '/auto_op_mark_begin/,/auto_op_mark_end/d' /etc/rc.local
echo '### auto_op_mark_begin ###' >> /etc/rc.local
echo 'echo "$(date) Ntpdate is executed when the system starts." &>> /var/log/ntpdate.log' >> /etc/rc.local
echo "/usr/sbin/ntpdate -u newntp_ip_or_domain_1 &>> /var/log/ntpdate.log && /sbin/hwclock -w" >> /etc/rc.local
echo "/usr/sbin/ntpdate -u newntp_ip_or_domain_2 &>> /var/log/ntpdate.log && /sbin/hwclock -w" >> /etc/rc.local
echo '### auto_op_mark_end ###' >> /etc/rc.local
else
echo "error"
fi
chmod a+x /etc/rc.d/rc.local
#立即同步
/usr/sbin/ntpdate -u newntp_ip_or_domain_1 && /sbin/hwclock -w
/usr/sbin/ntpdate -u newntp_ip_or_domain_2 && /sbin/hwclock -w
#日志管理
cat <<\EOF> /etc/logrotate.d/ntpdate
/var/log/ntpdate.log {
monthly
copytruncate
rotate 6
minsize 20M
}
EOF
ansible操作
inventory配置示例
cat <<EOF>inventory_20240104
[servers1]
1.2.3.[1:200]
1.2.4.[1:200]
1.2.5.[1:200]
1.2.6.[1:200]
1.2.7.[1:200]
1.2.8.[1:200]
1.2.9.[1:200]
[servers2]
2.2.3.[1:200]
2.2.4.[1:200]
2.2.5.[1:200]
2.2.6.[1:200]
2.2.7.[1:200]
2.2.8.[1:200]
2.2.9.[1:200]
EOF
#检验inventory配置
ansible -i inventory_20240104 all[0:] --list-hosts
ansible -i inventory_20240104 all[0:] -m shell -a ‘ip a | egrep “scope global” | egrep “eth|ens|eno|bond”’ -u username -k -b -K #查询系统IP,可选操作
#其中username是系统普通用户名
评估的对业务的影响
ansible -i inventory_20240104 all[0:] -m shell -a ‘date’ -u username -k -b -K
核对服务器时区时间,和北京时间接近,便判断为无影响。
一些因时间同步影响业务的案例
- 前不久看到一遍文档,讲修改下系统时间就导致数百的用户账号无法登陆,领导随后调集各部门同事分析,确定了原因是账号过期,因系统时间回归正常导致,DBA批量修改账号过期时间才得已解决;
- 一游戏服务器,因时间同步频率较低,和客户端时间精确度每超过几秒就会导致游戏场景里的走位异常。
大批量执行
ansible -i inventory_20240104 all[0:] -m shell -a "mkdir -p /root/sh/20240104/" -u username -k -b -K
ansible -i inventory_20240104 all[0:] -m copy -a "src=op_timesync_rhel.sh dest=/root/sh/20240104/" -u username -k -b -K
ansible -i inventory_20240104 all[0:] -m shell -a "bash /root/sh/20240104/op_timesync_rhel.sh" -u username -k -b -K
#查看
ansible -i inventory_20240104 all[0:] -m shell -a 'cat /etc/rc.local;crontab -l;systemctl status chronyd.service; systemctl status ntpd.service;chronyc tracking | egrep "Reference ID|Leap status"' -u username -k -b -K
统计变更数量
把上面的执行结果导入日志中便可统计,例:
grep CHANGED ansible.log | uniq -c | wc -l
工作结果
有个别服务器因特殊原因未能整改的,可单独执行脚本;
能顺利变更的数千台,大概1小时内就能完成,比“一台一台手动检查和修改每个配置”的效率快上千倍以上,并且有更可靠的保障,如果需要再次变更,改下脚本便可执行;
试想一下,如果手动刚修改完一千台,NTP地址又需要变更了,会是一种什么心情?