CentOS 7下systemd管理的详解

07-24

  CentOS 7下systemd管理的详解

CentOS系统启动流程:

  POST --> Boot Sequence --> Bootloader --> kernel + initramfs(initrd) --> rootfs --> /sbin/init

  innit程序:

  CentOS 5:SysV init

  CetnOS 6: Upstart

  CentOS 7 : Systemd

  Systemd新特性:

  系统Sys V init和LSB init scripts兼容

  系统引导时实现服务并行启动;采用socket / D-Bus activation等技术启动服务;为了减少系统启动时间,systemd的目标是:尽可能启动更少的进程;尽可能将更多的进程并行启动;

  按需激活进程;Systemd可以提供按需启动的能力,只有在某个服务被真正请求的时候才启动它。当该服务结束,systemd可以关闭它,等待下次需要时再次启动它。

  能够对系统进行快照和恢复;

  启动挂载点和自动挂载点的管理:

  Systemd自助管理系统上的挂载点,以便能够在系统启动时自动挂载它们。且兼容/etc/fstab文件;

  实现事务依赖关系管理:

  systemd 维护一个"事务一致性"的概念,保证所有相关的服务都可以正常启动而不会出现互相依赖,以至于死锁的情况。

  基于内生依赖关系定义服务控制逻辑;

  system利用Linux内核的特性即CGroup来完成进程跟踪的任务。当停止服务时,通过查询CGroup,systemd可以确保找到所有的相关进程,从而干净地停止服务;

  日志服务:systemd自带日志服务journald,该日志服务的设计初衷是克服现有的syslog服务的缺点。

  System的基本概念

  单元的概念:

  系统初始化需要做的事情非常多。需要启动后台服务,比如启动 SSHD 服务;需要做配置工作,比如挂载文件系统。这个过程中的每一步都被 systemd 抽象为一个配置单元,即 unit。可以认为一个服务是一个配置单元;一个挂载点是一个配置单元;一个交换分区的配置是一个配置单元;等等。systemd 将配置单元归纳为以下一些不同的类型。然而,systemd 正在快速发展,新功能不断增加。所以配置单元类型可能在不久的将来继续增加。

  service:代表一个后台服务进程,比如mysqld。这是常用的一类;

  socket:此类配置单元封装系统和互联网中的一个套接字。当下,systemd支持流式、数据包和连续包的AF_INET、AF_INET6、AF_UNIX socket。每一个套接字配置单元都有一个相应的服务配置单元 。相应的服务在第一个"连接"进入套接字时就会启动(例如:nscd.socket 在有新连接后便启动 nscd.service)。

  device :此类配置单元封装一个存在于 Linux 设备树中的设备。每一个使用 udev 规则标记的设备都将会在 systemd 中作为一个设备配置单元出现。

  mount :此类配置单元封装文件系统结构层次中的一个挂载点。Systemd 将对这个挂载点进行监控和管理。比如可以在启动时自动将其挂载;可以在某些条件下自动卸载。Systemd 会将/etc/fstab 中的条目都转换为挂载点,并在开机时处理。

  automount :此类配置单元封装系统结构层次中的一个自挂载点。每一个自挂载配置单元对应一个挂载配置单元 ,当该自动挂载点被访问时,systemd 执行挂载点中定义的挂载行为。

  swap: 和挂载配置单元类似,交换配置单元用来管理交换分区。用户可以用交换配置单元来定义系统中的交换分区,可以让这些交换分区在启动时被激活。

  target :此类配置单元为其他配置单元进行逻辑分组。它们本身实际上并不做什么,只是引用其他配置单元而已。这样便可以对配置单元做一个统一的控制。这样就可以实现大家都已经非常熟悉的运行级别概念。比如想让系统进入图形化模式,需要运行许多服务和配置命令,这些操作都由一个个的配置单元表示,将所有这些配置单元组合为一个目标(target),就表示需要将这些配置单元全部执行一遍以便进入目标所代表的系统运行状态。 (例如:multi-user.target 相当于在传统使用 SysV 的系统中运行级别 3)

  timer:定时器配置单元用来定时触发用户定义的操作,这类配置单元取代了 atd、crond 等传统的定时服务。

  snapshot :与 target 配置单元相似,快照是一组配置单元。它保存了系统当前的运行状态。

  依赖关系:

  虽然 systemd 将大量的启动工作解除了依赖,使得它们可以并发启动。但还是存在有些任务,它们之间存在天生的依赖,不能用"套接字激活"(socket activation)、D-Bus activation 和 autofs 三大方法来解除依赖(三大方法详情见后续描述)。比如:挂载必须等待挂载点在文件系统中被创建;挂载也必须等待相应的物理设备就绪。为了解决这类依赖问题,systemd 的配置单元之间可以彼此定义依赖关系。

  Systemd 用配置单元定义文件中的关键字来描述配置单元之间的依赖关系。比如:unit A 依赖 unit B,可以在 unit B 的定义中用"require A"来表示。这样 systemd 就会保证先启动 A 再启动 B。

  Systemd事务:

  Systemd 能保证事务完整性。Systemd 的事务概念和数据库中的有所不同,主要是为了保证多个依赖的配置单元之间没有环形引用。 存在循环依赖,那么 systemd 将无法启动任意一个服务。此时systemd 将会尝试解决这个问题,因为配置单元之间的依赖关系有两种:required是强依赖;want 则是弱依赖,systemd 将去掉 wants 关键字指定的依赖看看是否能打破循环。如果无法修复,systemd会报错。

  Systemd 能够自动检测和修复这类配置错误,极大地减轻了管理员的排错负担。

  Target和运行级别:

  systemd 用目标(target)替代了运行级别的概念,提供了更大的灵活性,如您可以继承一个已有的目标,并添加其它服务,来创建自己的目标。下表列举了 systemd 下的目标和常见 runlevel 的对应关系:

CentOS 7下systemd管理的详解

  Systemd 的并发启动原理

  如前所述,在 Systemd 中,所有的服务都并发启动,比如Avahi、D-Bus、livirtd、X11、HAL 可以同时启动。乍一看,这似乎有点儿问题,比如 Avahi 需要syslog 的服务,Avahi 和syslog 同时启动,假设 Avahi 的启动比较快,所以syslog 还没有准备好,可是 Avahi 又需要记录日志,这岂不是会出现问题?

  Systemd 的开发人员仔细研究了服务之间相互依赖的本质问题,发现所谓依赖可以分为三个具体的类型,而每一个类型实际上都可以通过相应的技术解除依赖关系。

  并发启动原理之一:解决 socket 依赖

  绝大多数的服务依赖是套接字依赖。比如服务A 通过一个套接字端口S1 提供自己的服务,其他的服务如果需要服务A,则需要连接S1。因此如果服务A 尚未启动,S1就不存在,其他的服务就会得到启动错误。所以传统地,人们需要先启动服务A,等待它进入就绪状态,再启动其他需要它的服务。Systemd认为,只要我们预先把S1 建立好,那么其他所有的服务就可以同时启动而无需等待服务A 来创建S1 了。如果服务A 尚未启动,那么其他进程向S1 发送的服务请求实际上会被Linux 操作系统缓存,其他进程会在这个请求的地方等待。一旦服务A 启动就绪,就可以立即处理缓存的请求,一切都开始正常运行。

  那么服务如何使用由 init 进程创建的套接字呢?

  Linux 操作系统有一个特性,当进程调用fork 或者exec 创建子进程之后,所有在父进程中被打开的文件句柄(file descriptor) 都被子进程所继承。套接字也是一种文件句柄,进程A 可以创建一个套接字,此后当进程A 调用 exec启动一个新的子进程时,只要确保该套接字的close_on_exec 标志位被清空,那么新的子进程就可以继承这个套接字。子进程看到的套接字和父进程创建的套接字是同一个系统套接字,就仿佛这个套接字是子进程自己创建的一样,没有任何区别。

  这个特性以前被一个叫做 inetd 的系统服务所利用。Inetd 进程会负责监控一些常用套接字端口,比如Telnet,当该端口有连接请求时,inetd才启动 telnetd 进程,并把有连接的套接字传递给新的telnetd 进程进行处理。这样,当系统没有telnet 客户端连接时,就不需要启动telnetd 进程。Inetd可以代理很多的网络服务,这样就可以节约很多的系统负载和内存资源,只有当有真正的连接请求时才启动相应服务,并把套接字传递给相应的服务进程。

  和 inetd 类似,systemd 是所有其他进程的父进程,它可以先建立所有需要的套接字,然后在调用exec 的时候将该套接字传递给新的服务进程,而新进程直接使用该套接字进行服务即可。