一台机器上要同时连三个 frp 服务端,网上大部分教程的做法是复制三份 frpc.service,改三次路径。改一次参数要同步改三个文件,迟早出错。
systemd 自带的**模板单元(template unit)**就是为这种场景准备的:写一份 frpc@.service,后面想开几个实例就开几个。这篇先用 frp 走一遍完整流程,再讲怎么把同一套写法套到 xray、socat、Python 脚本等任何需要多开的程序上。
模板单元是什么
普通单元文件叫 frpc.service,模板单元文件名多一个 @,叫 frpc@.service。
它本身不会运行,只有在你启动 frpc@azure_hk.service 的时候,systemd 才会拿这个模板生成一个真正的实例,并且把 @ 后面的那串字符串(这里是 azure_hk)作为实例名,替换掉单元文件里的 %i。
所以「一份模板 + N 个实例名 = N 个互相独立的服务」,每个实例有独立的进程、独立的日志、独立的启停状态,但共用同一份单元文件。
frp 实战
1. 写模板单元
新建 /etc/systemd/system/frpc@.service:
[Unit]
Description=Frp Client Service - %i
After=network.target syslog.target
Wants=network.target
[Service]
Type=simple
Restart=on-failure
RestartSec=5s
# %i 会被替换为 @ 后面的实例名
ExecStart=/usr/local/frp/frpc -c /usr/local/frp/frpc-%i.toml
[Install]
WantedBy=multi-user.target
2. 按命名约定放配置文件
模板能生效的前提是配置文件名和实例名一一对应。这里约定为 /usr/local/frp/frpc-<实例名>.toml:
frpc-azure_hk.toml→ 实例frpc@azure_hkfrpc-aliyun.toml→ 实例frpc@aliyunfrpc-vps2.toml→ 实例frpc@vps2
以后想新增一条隧道,只要丢一个配置文件进去,不用再碰 service 文件。
3. 启动
sudo systemctl daemon-reload
sudo systemctl enable --now frpc@azure_hk
sudo systemctl enable --now frpc@aliyun
enable --now 等于 enable(设置开机自启)加 start(立刻启动),一步到位。
4. 批量管理
实例名支持通配符,可以一条命令管住所有实例。引号一定要加,否则 * 会先被 shell 展开成当前目录下的文件名:
systemctl status "frpc@*"
sudo systemctl restart "frpc@*"
sudo systemctl stop "frpc@*"
看日志同理,-f 持续跟踪:
journalctl -u "frpc@*" -f
只看某一个实例:
journalctl -u frpc@azure_hk -n 100 --no-pager
推广到任何程序
上面这套写法跟 frp 没有任何绑定关系。只要一个程序满足「同一个二进制,靠不同参数/配置跑多份」,就能用模板单元。
可用的说明符
模板里能用的占位符不止 %i,常用的这几个:
| 说明符 | 含义 | frpc@azure_hk.service 里的值 |
|---|---|---|
%i | 实例名(转义后的原始形式) | azure_hk |
%I | 实例名(反转义,- 还原成 /) | azure_hk |
%p | 前缀,即 @ 之前的部分 | frpc |
%n | 完整单元名 | frpc@azure_hk.service |
%N | 单元名去掉后缀 | frpc@azure_hk |
%h | 运行用户的家目录 | /root |
%u | 运行用户名 | root |
%i 和 %I 的区别只在实例名里带特殊字符时才体现出来。因为实例名不能直接包含 /,需要用 - 代替,%I 会把它还原回去。如果你的实例名要从路径生成,用 systemd-escape 转一下:
systemd-escape -p /srv/app/site-a
例子一:多配置的 xray / v2ray
[Service]
ExecStart=/usr/local/bin/xray run -c /usr/local/etc/xray/%i.json
配置放 /usr/local/etc/xray/hk.json、tokyo.json,然后 systemctl enable --now xray@hk xray@tokyo。
例子二:多条 socat 端口转发
这类程序参数不在配置文件里,而是直接写在命令行上。做法是给每个实例配一个环境变量文件:
/etc/systemd/system/socat-fwd@.service
[Unit]
Description=socat forward - %i
After=network.target
[Service]
Type=simple
Restart=on-failure
RestartSec=5s
EnvironmentFile=/etc/socat-fwd/%i.conf
ExecStart=/usr/bin/socat TCP4-LISTEN:${LPORT},fork,reuseaddr TCP4:${RHOST}:${RPORT}
[Install]
WantedBy=multi-user.target
/etc/socat-fwd/nas.conf
LPORT=8080
RHOST=192.168.1.50
RPORT=5000
然后 sudo systemctl enable --now socat-fwd@nas。这一段可以和端口转发那篇配合起来看,那篇讲的是单条转发怎么写,这里解决的是转发条目一多就管不过来的问题。
小技巧:
EnvironmentFile=-/etc/socat-fwd/%i.conf,路径前面加一个-,表示文件不存在时不报错、继续启动。适合把环境变量文件当成可选覆盖用。
例子三:多开的 Python 脚本 / 机器人
[Service]
Type=simple
User=vinkey
WorkingDirectory=/opt/bots/%i
ExecStart=/opt/bots/%i/.venv/bin/python main.py
Restart=on-failure
RestartSec=10s
每个机器人一个目录、一个虚拟环境,bot@alpha、bot@beta 各跑各的。
几个容易踩的坑
改完模板一定要 daemon-reload。 模板文件改动不会自动生效,而且已经在跑的实例需要 restart 才会用上新配置:
sudo systemctl daemon-reload && sudo systemctl restart "frpc@*"
不能直接 enable 模板本身。 systemctl enable frpc@.service 会报错,因为 systemd 不知道该启哪个实例。如果确实想要一个「不写实例名时的默认值」,在 [Install] 段加一行:
[Install]
WantedBy=multi-user.target
DefaultInstance=azure_hk
给单个实例开小灶。 某个实例需要跟别人不一样的参数,不用为它单独复制一份单元文件,用 systemctl edit 生成 drop-in 覆盖即可:
sudo systemctl edit frpc@aliyun
它会生成 /etc/systemd/system/frpc@aliyun.service.d/override.conf,只写你要改的那几行,其余仍然继承模板。
通配符不要漏引号。 systemctl stop frpc@* 在当前目录恰好有同名文件时会被 shell 展开成别的东西,systemctl stop "frpc@*" 才是稳的。
查当前开了哪些实例:
systemctl list-units "frpc@*" --all