zhangguanzhang's Blog

inotifywait 和 confd 在一起踩的坑

字数统计: 1.1k阅读时长: 5 min
2024/11/24

记录最近碰到的一次 confd 和 inotifywait 配合踩的坑

由来

版本快发版的时候,测试测出来经常部署应用阶段 sql 初始化服务偶现连接数据库 mysql 超时,导致没有创建库或者表,而业务功能不正常。然后临近发版测试人员上报了高风险。

过程

排查

复现非常麻烦,不是一直超时是偶先。没办法询问了 sql 初始化业务的开发人员,和多个环境得到以下对比信息:

  • 最近无改动,初始化服务回退到上个版本依旧
  • 每次超时是不同的库,并不是固定的,慢 sql 看了没有
  • k8s/docker 部署均会发生

就在一筹莫展之际,另一个部门的测试人员找过来,他们部署后,浏览器上测试很多接口报错超时(主要是内部涉及到的调用链长),有了稳定复现的环境真好。上去排查了下,发现 ipset 的条目被频繁清空和创建导致的。

背景

我们 toB 和 toG 为了避免客户现场漏扫和安全相关,每台机器起了容器,利用 ipset 和 iptables 做白名单端口策略。相关规则为如下:

1
2
3
4
5
iptables -w -N BASE-RULE
iptables -w -A BASE-RULE -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -w -A BASE-RULE -m set ! --match-set whiteiplist src -m set --match-set whiteportlist dst -j DROP
iptables -w -A BASE-RULE -j RETURN
iptables -A INPUT -j BASE-RULE

默认行为是 DROP,也就是白名单实现。在每个节点机器都部署有 ipset 容器,内部 entrypoint.sh 内容大致如下:

1
2
3
4
5
6
exec inotifywait -mrq \
--timefmt '%d/%m/%y/%H:%M' \
--format '%T %w %f' \
-e modify,delete,close_write,move /data/kube/ | while read line;do
bash /iptables.sh
done

/iptables.sh 内部就是检查链和每次清空 ipset 条目和创建。之前的逻辑是 ansible 分发 rule 文件,上个版本被其他同事修改成使用 confd 从 redis 内读取更新到该文件:

1
2
3
4
5
6
7
8
# confd 容器内目录
[template]
src = "ipsets-whiteiplist.tmpl"
dest = "/root/kube/rule/whiteiplist.txt" # 配置文件地址
keys = [ # 监听的key
"/ipsets/whiteiplist",
"/ipsets/mark",
]

根源

然后我在 ipset 容器内使用 inotifywait 观察了下发现一直产生临时文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ inotifywait -mrq     --timefmt '%d/%m/%y/%H:%M'     --format '%T %w %f' -e modify,delete,close_write,move /data/kube/
26/11/24/14:51 /data/kube/ .whiteportlist.txt3298417101
26/11/24/14:51 /data/kube/ .whiteportlist.txt3298417101
26/11/24/14:51 /data/kube/ .whiteportlist.txt3298417101
26/11/24/14:51 /data/kube/ .whiteportlist.txt3298417101
26/11/24/14:51 /data/kube/ .whiteportlist.txt3298417101
26/11/24/14:51 /data/kube/ .whiteportlist.txt3298417101
26/11/24/14:51 /data/kube/ .whiteportlist.txt3298417101
26/11/24/14:51 /data/kube/ .whiteportlist.txt3298417101
26/11/24/14:51 /data/kube/ .whiteportlist.txt3298417101
26/11/24/14:51 /data/kube/ .whiteportlist.txt3298417101
26/11/24/14:51 /data/kube/ .whiteportlist.txt3298417101
26/11/24/14:51 /data/kube/ .whiteportlist.txt3298417101
26/11/24/14:51 /data/kube/ .whiteportlist.txt3298417101
26/11/24/14:51 /data/kube/ .whiteportlist.txt3298417101
26/11/24/14:51 /data/kube/ .whiteportlist.txt3298417101

看这个文件名就是 confd 生成的对比临时文件,查看了下文档并没有发现有设置临时文件的 dir 相关参数,然后找到了相关逻辑:

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
36
37
38
39
40
41
42
43
// https://github.com/kelseyhightower/confd/blob/master/resource/template/resource.go#L138
func (t *TemplateResource) createStageFile() error {
log.Debug("Using source template " + t.Src)

if !util.IsFileExist(t.Src) {
return errors.New("Missing template: " + t.Src)
}

log.Debug("Compiling source template " + t.Src)

tmpl, err := template.New(filepath.Base(t.Src)).Funcs(t.funcMap).ParseFiles(t.Src)
if err != nil {
return fmt.Errorf("Unable to process template %s, %s", t.Src, err)
}

// create TempFile in Dest directory to avoid cross-filesystem issues
temp, err := ioutil.TempFile(filepath.Dir(t.Dest), "."+filepath.Base(t.Dest))
if err != nil {
return err
}

if err = tmpl.Execute(temp, nil); err != nil {
temp.Close()
os.Remove(temp.Name())
return err
}
defer temp.Close()

// Set the owner, group, and mode on the stage file now to make it easier to
// compare against the destination configuration file later.
os.Chmod(temp.Name(), t.FileMode)
os.Chown(temp.Name(), t.Uid, t.Gid)
t.StageFile = temp
return nil
}

func (t *TemplateResource) sync() error {
staged := t.StageFile.Name()
if t.keepStageFile {
log.Info("Keeping staged file: " + staged)
} else {
defer os.Remove(staged)
}

主要是这行 ioutil.TempFile(filepath.Dir(t.Dest), "."+filepath.Base(t.Dest)) 相当于 目录下 + .文件名字 + TempFile随机后缀,在下面的 sync 方法里对比后再删掉 stageFile。

从源码了解到并没有相关 StageFileDir 参数,没办法让同事把文件放其他地方, 在 reload_cmd cp 过去,大概类似这样:

1
2
3
4
5
6
7
8
9
[template]
src = "ipsets-whiteiplist.tmpl"
dest = "/root/kube/whiteiplist.txt" # 配置文件地址
keys = [ # 监听的key
"/ipsets/whiteiplist",
"/ipsets/mark",
]
check_cmd = "" # 服务配置检查命令
reload_cmd = "cp -f /root/kube/whiteiplist.txt /root/kube/rule/whiteiplist.txt"

后续

重写了那个 while 内过滤掉.开头的文件,然后 ipset 清空逻辑使用 swap 方式。

CATALOG
  1. 1. 由来
  2. 2. 过程
    1. 2.1. 排查
    2. 2.2. 背景
    3. 2.3. 根源
    4. 2.4. 后续