浅谈Linux系统配置和最佳实践

  • 2020 年 2 月 14 日
  • 筆記

原文发布于微信公众号 – 云服务与SRE架构师社区(ai-cloud-ops)

1. 前言

最近在梳理Linux服务的基准配置,发现很多系统配置是多年流传下来的,可能不是非常合理。下面以几个点为例,介绍我理解的最佳实践,希望能起抛砖引玉的作用。

2. 主机名

有些业务的运维并不会去设置主机名,而是根据IP去区分,登上去之后shell命令提示符显示的都是同样的默认主机名,这有其原因–机器的数量实在太多了。但是设置主机名一个有明显的好处,我们可以把业务相关的信息放进去,一目了然的知道这台机器的用途,特别是有些时候在登录多台机器做操作的时候,不用反复通过ifconfig查看ip确定是哪台机器。但是话说回来,前些年开始流行去Console登录,如果已经做到了这个程度,那么主机名或许是可有可无了。但我始终觉得,偶尔还是会有终端登录的需求。

CentOS7设置主机名只需要运行systemctl set-hostname <FQDN>;CentOS6需要修改/etc/sysconfig/network/etc/hosts两个文件,然后执行hostname <FQDN>使新的主机名立即生效。

如果设置了主机名,还申请了dns,可以在/etc/resolv.conf 中加入domain参数用来快速查询。比如a.example.com要访问 b.example.com,设置domain example.com之后可以直接使用b作为主机名。

3. DNS

我厂的DNS很多,每个地域都有不一样的DNS服务器。DNS服务器一多,带来的一个问题就是,很多人会有意无意的在/etc/resolv.conf里面设置一大堆nameserver。这是有问题的,很多人没有注意到,GNU libc 只支持3个nameserver,第4个开始是不起作用的,如果前3个nameserver都挂了,配再多也没用。man resolv.conf中提到:

Internet address of a name server that the resolver should query, either an IPv4 address (in dot notation), or an IPv6 address in colon (and possibly dot) notation as per RFC 2373. Up to MAXNS (currently 3, see <resolv.h>) name servers may be listed, one per keyword.

这个问题很容易测试和验证。

4. 模块化配置

现代的软件大多提供了模块化的配置能力,以nginx为例,我们一般不直接编辑它的主配置文件/etc/nginx/nginx.conf,而是按需在/etc/nginx/conf.d目录中加入自己的配置文件。

在使用脚本进行自动配置的时候,模块化能够带来冥等性,某个配置文件要么在存在,要么不存在。假如要修改某个主配置文件的一个配置项,我常用的方式是,先用sed删掉这个配置项(因为这个配置项可能明文存在,也可能不存在因而使用了默认值),然后再用另一个sed把新的配置项和值加进去,有时候这个配置项的位置还不能随便加。

如果使用模块化的配置,直接在.d目录中写入一个配置文件就好,如果这个文件存在,说明这应该符合预期的配置;如果文件不存在,说明这大概不符合预期的配置。可是我们怎么知道是不是有人修改了这些配置文件?我的做法是把所有自定义的配置文件做成rpm包,一方面方便直接安装,另一方面,如果有人改了这里的内容,rpm -V便能够发现。

这里特别要注意主配置文件和.d配置文件的先后顺序,确定哪个文件的优先级更高。

4.1 sysctl

内核参数我们习惯配置在/etc/sysctl.conf中,然后通过sysctl -p加载。CentOS7中提供了/etc/sysctl.d目录,我们可以把自己的配置文件放到这个目录中,按照<两位数字>-<文件名>的格式命名,比如CentoS7默认放了一个99-sysctl.conf。这些文件前面的两位数字决定了配置文件的加载顺序,如果同一个配置项出现在多个配置中,后者的值会覆盖前者的。99-sysctl.conf这个文件其实是指向/etc/sysctl.conf的一个软链接,这大概也是想表明,/etc/sysctl.conf的优先级高于/etc/sysctl.d中的文件。

怎么加载这些配置项呢?systemctl restart systemd-sysctl,必须用restart。

4.2 limits

我们习惯在/etc/security/limits.conf中配置limits,但其实系统中也提供了/etc/security/limits.d的目录作为模块化的配置。这里的文件同样按照<两位数字>-<文件名>.conf的格式命名,比如自带的90-nproc.conf。同样,这些文件前面的两位数字决定了配置文件的加载顺序,如果同一个配置项出现在多个配置中,后者的值会覆盖前者的。但是跟sysctl不同的是,/etc/security/limits.d的优先级高于/etc/security/limits.conf。修改limits配置后,需要重新登录才能生效。

4.3 Shell Profile

Bash的全局变量我们习惯在/etc/profile中修改,但是系统同样提供了更好的/etc/profile.d。这里的文件没有严格的命名方式,比如vim.sh(对应的还有vim.csh,那是用于C Shell的)。/etc/profile中有一段代码专门用来加载这些配置:

for i in /etc/profile.d/*.sh ; do      if [ -r "$i" ]; then          if [ "${-#*i}" != "$-" ]; then              . "$i"          else              . "$i" >/dev/null 2>&1          fi      fi  done  

可以看到,它们的优先级顺序是由shell通配符展开顺序决定的,在Bash中,这是按照字母表顺序排序的,以下内容来自man bash:

Pathname Expansion After word splitting, unless the -f option has been set, bash scans each word for the characters *, ?, and [. If one of these characters appears, then the word is regarded as a pattern, and replaced with an alphabetically sorted list of file names matching the pattern.

同样,这些配置要重新登录后才能生效。

4.4 模块化配置小结

项目

优先级(从低到高)

如何生效

sysctl

/etc/sysctl.d/*.conf(按数字从小到大), /etc/sysctl.conf

systemctl restart systemd-sysctl

limits

/etc/security/limits.conf, /etc/security/limits.d/*.conf(按数字从小到大)

重新登录

profile

/etc/profile, /etc/profile.d/*.conf(按文件名排序)

重新登录

5. NTP

不知道为什么大家喜欢在crontab面配一个ntpdate来代替ntpd,我猜原因是服务器的硬件时钟很不准的时候,尤其是在虚拟机环境中,ntpd经常会同步失败。Red Hat有一篇Blog《Avoiding clock drift on VMs》描述了这些问题,并提供了对应的建议。

回过头来聊聊为什么不建议用ntpdate代替ntpd,ntpd对系统时间的修改是单调递增的。而在硬件时钟特别不准的情况下,通过crontab使用ntpdate每分钟更新一次的时候,系统时钟可能会出现一种这样的情况:

序号

服务器时间

ntp server 时间

说明

1

10:00:00

10:00:00

第一次ntpdate同步

2

10:01:00

10:00:59

服务器的硬件时钟时间跑得太快了

3

10:01:01

10.01:00

crontab马上要开始下一次时间同步

4

10:01:00

10:01:00

第二次ntpdate同步

这个例子只是为了展示这种情况,硬件时钟不太可能在1分钟之内就达到1秒钟的偏差。“正常”的范围可以参考NTP的文档:http://www.ntp.org/ntpfaq/NTP-s-sw-clocks-quality.htm#AEN1220

这里存在两个问题:

  1. 时间不再是单调递增的,第二次ntpdate导致了时间的回退
  2. 第2和第4个序号的服务器时间是一样的

有些数据库和分布式id生成算法会依赖系统时间的单调递增性,因为这种问题出bug的话,简直没法查原因。我不喜欢ntpdate的另一个原因是,在crontab里面调ndpate实在太不优雅了。

但是ntpd其实也是需要配合ntpdate来使用的,如果仅启用了ntpd,可能会由于主板电池没电,或者关机时没有写入硬件时钟等原因,导致机器重启后系统时间跟ntp server差距过大同步失败。系统中还有一个叫ntpdate的服务,这个服务会在启动时调用一次ntpdate把直接把系统时间同步到位,这样接下来启动ntpd就不会有问题了。

6. 故障服务重启

有些开发喜欢在crontab里面加一个定时任务去监控后台服务的状态,如果服务挂了就重新拉起来。这首先暴露了对程序质量的不自信,其次这未必是最好的做法。CentOS7里面可以用systemd实现类似的目标,只要在systemd的配置文件中加入Restart=always即可,如果程序故障退出,systemd会重启这个服务,例如:

[Unit]  Description=my service    [Service]  Type=simple  WorkingDirectory=/tmp  ExecStart=/path/to/myprog  ExecReload=/bin/kill -HUP $MAINPID  KillMode=process  Restart=always  User=root  Group=root    [Install]  WantedBy=multi-user.target  

这样做的另一个好处是,如果因为某些原因我们需要停止这些进程,通过systemctl stop <service>即可。如果用crontab的方式,必须先注释掉crontab,然后kill掉对应的进程;相信我,这时候肯定会有人有忘记注释就直接kill进程,更不要说kill进程本身就是个高危操作。

CentOS6的sysvinit并没有提供类似的能力,或许可以考虑supervisord, pm2这些进程管理工具?

7. 开机自启项

RedHat系的linux提供了/etc/rc.local文件,用来设置把开机启动项。但是这个文件被滥用了,很多不该放到这里的东西都放到这里来了,随便举几个例子:

  • 配置内核参数
  • 配置网卡IP
  • 有人在这里启动后台服务
  • 还有人在这里配置iptables

这些东西其实都有更好的去处:

  • 内核参数配置,使用/etc/systcl.d/etc/systctl.conf
  • 网卡配置,使用/etc/sysconfig/network-scripts/ifcfg-<nic>
  • 后台服务,使用sysvinit或systemd
  • iptables,使用iptables-save并启用iptable服务

把这些东西放到rc.local的另一个问题是,假如我们修改了其中的配置,没有很好的办法去重新加载这些配置。比如修改了网络配置,我们可以通过systemctl restart network重新加载。但如果放到/etc/rc.local中的话,修改完之后bash /etc/rc.local?不仔细检查这个文件的话,我们不知道这会带来什么副作用,比如有人在这里写了个ntpdate,这会引起时间跳变,导致前面提到的问题。

那么什么东西是适合放到rc.local中的?我想是一些设置后就正常不会变动的配置,但是我也没想到哪些东西非放到这里不可。

关于作者

不怎么务正业的程序员,BUG制造者、CPU0杀手。从事过开发、运维、SRE、技术支持等多个岗位。原Oracle系统架构和性能服务团队成员,目前在腾讯从事运营系统开发。