子进程调用etcd并把输出内容重定向到支持切割的日志文件

由于新的项目需要用到分布式配置管理系统,在调研zookeeper及etcd后,觉得etcd相对轻量级,并且之前对raft协议也有所了解,因此最终选择使用etcd。公司使用的系统自身带有守护进程,因此需要考虑编写一个工具启动etcd,然后由守护进程启动该工具,这样可以不用考虑etcd挂后自动恢复的问题。

1.编写工具通过子进程调用etcd

网上提供的etcd启动脚本比较复杂,并且涉及到多机配置及启动问题,一般网上提供的启动例子为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/bin/bash

NAME_1=infra0
NAME_2=infra1
NAME_3=infra2
HOST_1=192.168.202.46
HOST_2=192.168.200.54
HOST_3=192.168.200.2
CLUSTER=${NAME_1}=http://${HOST_1}:2380,${NAME_2}=http://${HOST_2}:2380,${NAME_3}=http://${HOST_3}:2380

THIS_NAME=${NAME_1}
THIS_IP=${HOST_1}

BASEDIR="/var/lib/etcd"
DATADIR="/var/lib/etcd/data"
LOGDIR="${BASEDIR}/logs"
LOGFILE="${LOGDIR}/etcd.log"
mkdir -p "${LOGDIR}"

while [ 1 ];
do
echo "try to start etcd"
/usr/local/bin/etcd --data-dir=${DATADIR} --name ${THIS_NAME} \
--initial-advertise-peer-urls http://${THIS_IP}:2380 \
--listen-peer-urls http://${THIS_IP}:2380 \
--listen-client-urls http://${THIS_IP}:2379,http://127.0.0.1:2379 \
--advertise-client-urls http://${THIS_IP}:2379 \
--initial-cluster-token etcd-cluster-1 \
--initial-cluster ${CLUSTER} \
--initial-cluster-state new >> $LOGFILE 2>&1

echo "sleep 10 seconds"
sleep 10

done

如果通过工具创建子进程方式去启动etcd,则只需在公司系统的配置文件中加上哪些机器需要启动etcd,然后由工具自动生成各项参数,最终生成调用etcd命令。

当通过子进程使用execve方式调用etcd后,会遇到输出日志的问题,由于etcd默认只输出到stdout,虽然通过systemd方式启动时可以把日志输出到journald对应的日志,但是我们的程序是通过守护进程启动,因此这些日志会直接输出到stdout,当时考虑有两个解决方案

  1. 工具不是通过子进程方式调用etcd,而是通过system方式调用,然后把etcd日志重定向到一个日志文件中
    在这种方式下,虽然可以把日志重定向到一个文件,但是无法与公司系统自动切割的日志兼容,并且还需要定时运行程序把etcd的输出文件进行改名

  2. 工具通过子进程中使用execve方式调用etcd,但是在执行前时,通过dup2系统调用,把stdout/stderr重定向到一个日志文件
    通过execve方式执行命令后,该子进程已经完成进入etcd的执行流程,虽然可以把日志输出到文件,但是一旦其他程序把该日志文件改名,etcd的日志还是输出到该文件。如果把日志文件删除,则etcd不会输出日志到新的文件

2. 通过管道方式把etcd的输出内容发送回父进程,由父进程输出日志

由于父进程是使用相关的代码编写工具,因此只要在工具运行的进程输出日志即可自动支持切割功能。

工具最后的业务流程图如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 +------------------------+                                                                     
| 读取配置并生成etcd |
| 运行参数 |
+------------|-----------+
|
+------------v------------+
| 创建匿名管道 |
+------------|------------+
|
+-------------v------------+ +-------------+ +-------------------+
| 使用fork创建子进程 |--- --->| 关闭读管道 |----> |重定向stdout/ |
+-------------|------------+ | | |stderr到写管道 |
| +-------+-----+ +---------|---------+
| |
+-------------v-------------+ +---------v---------+
| 创建线程从管道读取 | | 使用execve |
| etcd日志,并输出到日志文件| | 调用etcd |
+---------------------------+ +-------------------+