systemd socket 实现按需启动

本篇文章所使用的的环境为:

CentOS Linux release 7.9.2009 (Core)

systemd乃是一款极为强大之"工具箱",具备众多卓越功能,例如以PID1初始化操作系统,以及管理系统中的进程服务,涵盖的范围包括但不仅限于,启动、停止、重启、设定开机自启动等操作。此外,它还兼顾了其他多种功能,如维护挂载点及自动挂载,根据需求启动守护进程服务,设定主机名、时间、时区等,以及管理系统中的账户等。本文将着重解析其中的一个非常小的功能:按需启动,我们也称之为systemd socket

什么是按需启动

不知道你们是否曾遭遇过如此困境,即在系统上存在着一项服务,然其必须对外开放访问权限,然而,此服务启动极其缓慢且访问效率极度低下,可能系统开机后,可能相当长时间也不会访问该服务。

在此情况下,身为系统管理员的我们,是否需要将该服务设定为开机自启动呢?这确实可以满足需求,但却会延长开机时间,甚至在无需对外提供访问的时刻,仍占据服务器资源。

那么,我们能否设想利用一个附加服务来完成这项工作呢?具体方法就是,由该额外服务来对外监测套接字,每当有连接建立时,首先接管该服务,在后台启动真正的服务,然后将请求转发到真实的服务之上,是用完毕后,systemd再销毁该服务。这样既可以完成请求,又能够节约系统资源。

当使用systemd按需启动某套接字进程后,其图示大致如下:

当需要访问该服务时候,systemd会接收请求流量,而后启动后端真实的服务,最后转发该流量,并且关闭原始套接字,图示如下:

实现一个socket步骤

所谓的按需启动,其实是systemd下的socket配置单元,其命名规则以.socket为后缀,主要服务于套接字模式的启动。

我们配置socket后,systemd会替我们监听端口,当访问该端口的时候,systemd会启动后端实例,并且将请求转发至该实例,后端完成需求后,由systemd销毁该实例。已达到按需启动的效果。

注意,我们想接入socket,编写的业务要符合该就不能再对外监听套接字了,虽然systemd不会卡这个,但是不符合按需启动的逻辑,所以,我们自己写的业务,想要接入systemd需要修改其代码,修改为符合systemd socket规则的,换句话说,服务器监听由systemd来替我们做了,我们只用关心其业务逻辑即可。甚至要从文件或者标准输入中获取客户端发送的数据。

创建简单的socket

所以,我们不仅需要配置socket,还需要配置其后端模板实例,先来创建一个socket,例如:

ini 复制代码
[Unit]
Description=a test sample web socket

[Socket]
ListenStream=9999
Accept=yes

我们将其命名为 testSampleWeb.socket,并且存放到/usr/lib/systemd/system目录下。

这段配置文件意思是:

  • Description: 该服务的简介
  • ListenStream: 对外监听的tcp端口
  • Accept: 为true表示为每个连接传入单独的服务

在此,我们仅需reloadsystemd配置档:

bash 复制代码
systemctl daemon-reload

在使用systemctl status testSampleWeb.socket查看当前状态:

可以看到,目前状态是关闭状态的,可以使用start操作先开启该服务:

此时,若查看9999端口占用情况,会发现是systemd占用的,且pid为1,例如:

创建后端实例

注意,此时我们还没有编写属于该socket背后的实例,所以,请求该端口,会抛错,例如:

而在systemd日志中,会详细的描述,找不到该socket的后端实例配置,如:

所以,我们需要在此目录下,创建后端实例配置文件@.service结尾,如socket配置文件为: testSampleWeb.socket, 则该实例service的配置文件是[email protected],其内容如下:

ini 复制代码
[Unit]
Description=sample python webs %i

[Service]
ExecStart=-/usr/bin/python3 /root/sampleWeb.py
StandardInput=socket

这个要注意,为什么不是创建.service结尾的配置呢,这是因为此前创建socket的时候,Accept设置为True,所以要创建以@.service结尾的配置文件。

该配置的含义是:

  • ExecStart: 执行的命令
  • StandardInput: 标准输入方式为socket

接着,我们需要编写该后端服务代码,其内容如下:

python 复制代码
import sys

msgData = ""

while True:
    datas = sys.stdin.read(1)
    msgData += datas

    if msgData.endswith("\r\n\r\n"):
        break

httpInfo = msgData.split("\r\n")

rHeader = httpInfo[0]

httpDicts = {}
if 1 < len(httpInfo):
    httpDicts = dict([x.split(":",1) for x in httpInfo[1:] if ":" in x])

contentLen = 0
if "Content-Length" in httpDicts:
    contentLen = int(httpDicts["Content-Length"])

body = sys.stdin.read(contentLen)

sys.stdout.write("请求报文为:\n")
sys.stdout.write("\n请求行:\n" + str(httpInfo[0]))
sys.stdout.write("\n\n首部行:\n")
[print(k,":",httpDicts[k]) for k in httpDicts]
sys.stdout.write("\n\n报文主体:\n")
sys.stdout.write(body)
sys.stdout.write("\n\n")

上述代码,是一个简单的拆分http报文协议的代码,你可能很好奇,为什么没有调用socket呢?从哪儿来的客户端信息呢?正如上面所述,systemd socket已经帮我们把台架子搭好了,我们仅需要从标准输入流获取数据,要想发送给客户端,只需要往标准输出吐出信息即可。

测试

需要先加载配置文件,而后重启socket,命令如下:

bash 复制代码
systemctl daemon-reload
systemctl start testSampleWeb.socket 

使用netstat查看端口为9999的监听信息

bash 复制代码
netstat -tulnp | grep 9999

使用curl工具,向该接口发送一个post请求,例如:

bash 复制代码
curl -X POST -d "site=juejin,name=pdudo" 127.0.0.1:9999

输出的结果是我们预测的分析http报文的结果:

总结

systemd提供了非常多的功能,其中socket只是其中的一小块,但是确实是非常有意思的,试想一下,服务器上有一个对外的服务,非常重要,但是访问的很少,可能一年也访问不了几次,如果有这样一个工具,当监听到有客户端进行访问的时候,再启动服务,进而响应客户端需求,处理完毕后,再销毁该后端服务,这样既能够解决业务访问需求,又能够避免占用过多的资源,恰恰好。

sshd就是一个非常好的例子,你可以在虚拟机上安装一个centos 7,先将sshd服务先关闭,而后在终端中输入:

sql 复制代码
systemctl start sshd.socket

恭喜你,你已经学会了systemd socket了。

相关推荐
新加坡内哥谈技术27 分钟前
Meta计划借助AI实现广告创作全自动化
运维·人工智能·自动化
zyjyyds11334 分钟前
win11系统 Docker Desktop 突然提示Docker Engine stopped解决情况之一
运维·docker·容器
Altairr35 分钟前
Docker基础(一)
运维·docker·容器·eureka
文牧之1 小时前
PostgreSQL 的扩展pageinspect
运维·数据库·postgresql
小兔子酱#1 小时前
【Docker 01】Docker 简介
运维·docker·容器
jugt3 小时前
CentOS 7.9安装Nginx1.24.0时报 checking for LuaJIT 2.x ... not found
linux·运维·centos
21号 15 小时前
9.进程间通信
linux·运维·服务器
搬码临时工10 小时前
电脑同时连接内网和外网的方法,附外网连接局域网的操作设置
运维·服务器·网络
藥瓿亭10 小时前
K8S认证|CKS题库+答案| 3. 默认网络策略
运维·ubuntu·docker·云原生·容器·kubernetes·cks
Gaoithe10 小时前
ubuntu 端口复用
linux·运维·ubuntu