failed to create fsnotify watcher: too many open files
Loki 로그 수집 중 발생한 fsnotify watcher 생성 오류를 바탕으로 inotify 한도와 파일 감시 동작을 설명하고, sysctl 설정 및 Ansible 일괄 적용 방법을 정리합니다.
Loki를 통해 Kubernetes Cluster 로그를 확인하던 중 다음과 같은 에러를 확인했습니다.
1
failed to create fsnotify watcher: too many open files
해당 에러로 인해 Loki가 새로운 로그 파일을 감시하지 못하고 기존 로그 파일의 변경 사항도 수집하지 못하는 상황이 발생했습니다.
1. 개념 설명
1.1. fsnotify 개요
fsnotify는 Go에서 파일/디렉터리 변경 사항을 감지하는 라이브러리입니다. Loki/Promtail 같은 로그 수집 도구는 이를 활용해 로그 파일 변경을 실시간으로 추적합니다.
1.2. 동작 흐름 (간단)
- 애플리케이션이 watcher를 생성합니다.
- 감시할 경로(파일 또는 디렉터리)를 등록합니다.
- 커널 이벤트 큐에 쌓인 변경 이벤트가 애플리케이션으로 전달됩니다.
- 애플리케이션은 이벤트를 해석해 필요한 처리를 수행합니다(재시도, 재스캔 등).
1.3. 이벤트 특성 및 주의사항
- 이벤트는 짧은 시간에 많이 발생하면 합쳐지거나 중복될 수 있습니다.
- 이벤트 누락 가능성을 고려해 주기적 재스캔이나 오프셋 기반 처리를 함께 설계하는 것이 안전합니다.
- 로그 로테이션처럼 파일이 교체되는 시나리오에서는 감시 대상 재등록이 필요할 수 있습니다.
1.4. 에러 발생 원인
Linux 시스템에서는 프로세스가 열 수 있는 파일 수에 제한이 있습니다. too many open files 에러는 다음 두 가지 제한과 관련이 있습니다.
| 제한 종류 | 설명 | 확인 명령어 |
|---|---|---|
fs.inotify.max_user_watches | 사용자당 inotify watch 최대 개수 | cat /proc/sys/fs/inotify/max_user_watches |
fs.inotify.max_user_instances | 사용자당 inotify 인스턴스 최대 개수 | cat /proc/sys/fs/inotify/max_user_instances |
Loki가 많은 수의 로그 파일이나 디렉터리를 감시해야 할 때, 이 제한을 초과하면 위 에러가 발생합니다.
1.5. 현재 설정 확인
1
2
3
4
5
6
7
8
9
10
11
# inotify watch 제한 확인
❯ cat /proc/sys/fs/inotify/max_user_watches
61348
# inotify 인스턴스 제한 확인
❯ cat /proc/sys/fs/inotify/max_user_instances
128
# 현재 노드에서 열려있는 inotify handle 개수 확인
❯ find /proc/*/fd -lname anon_inode:inotify 2>/dev/null | wc -l
141
2. 주요 개념/구성 요소
| 구성 요소 | 설명 | 운영 시 체크 포인트 |
|---|---|---|
| Watcher | 변경 이벤트를 수신하는 감시 객체 | watcher/FD 사용량, 오류 로그 |
| Watch Target | 감시 대상 경로(파일/디렉터리) | 대상 수 증가 시 한도 초과 위험 |
| Event | 변경이 감지되었음을 나타내는 이벤트 | 중복/누락 가능성 고려 |
| Event Queue | 이벤트가 버퍼링되는 큐 | 이벤트 폭주 시 처리 지연 |
fsnotify는 “변경 감지 트리거” 역할에 강하며, 실제 처리 기준은 파일의 현재 상태를 재확인하는 방식이 안정적입니다.
3. 해결 방법
3.1. 임시 해결 (재부팅 시 초기화)
1
2
3
# 예시 값이며 환경에 맞게 조정하세요.
sudo sysctl fs.inotify.max_user_watches=524288
sudo sysctl fs.inotify.max_user_instances=1024
3.2. 영구 해결 (권장)
/etc/sysctl.conf 또는 /etc/sysctl.d/ 디렉터리에 설정 파일을 추가합니다.
1
2
# 설정 파일 생성
sudo vi /etc/sysctl.d/99-inotify.conf
1
2
3
4
# filepath: /etc/sysctl.d/99-inotify.conf
# Increase inotify limits for Loki/Promtail
fs.inotify.max_user_watches = 524288
fs.inotify.max_user_instances = 1024
1
2
3
4
5
# 설정 적용
sudo sysctl --system
# 적용 확인
cat /proc/sys/fs/inotify/max_user_watches
4. 일괄 적용 (Ansible 예시)
- Ansible Inventory 파일 (
/etc/ansible/hosts) hosts 그룹 (k8s_nodes)
4.1. Ansible Playbook 생성
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
## apply-inotify.yml
---
- name: Apply /etc/sysctl.d/99-inotify.conf to k8s_nodes
hosts: k8s_nodes
become: true
gather_facts: false
vars:
inotify_conf_path: /etc/sysctl.d/99-inotify.conf
inotify_settings:
fs.inotify.max_user_watches: "524288"
fs.inotify.max_user_instances: "1024"
tasks:
- name: Install 99-inotify.conf (persistent)
ansible.builtin.copy:
dest: ""
owner: root
group: root
mode: "0644"
content: |
# Managed by Ansible: Increase inotify limits for Loki/Promtail
fs.inotify.max_user_watches =
fs.inotify.max_user_instances =
register: inotify_conf
- name: Apply sysctl values immediately (only the keys we manage)
ansible.builtin.command: "sysctl -w ="
loop: ""
register: sysctl_apply
changed_when: false
- name: Read back max_user_watches
ansible.builtin.command: "sysctl -n fs.inotify.max_user_watches"
register: watches_value
changed_when: false
- name: Read back max_user_instances
ansible.builtin.command: "sysctl -n fs.inotify.max_user_instances"
register: instances_value
changed_when: false
- name: Assert inotify settings are applied
ansible.builtin.assert:
that:
- watches_value.stdout == inotify_settings['fs.inotify.max_user_watches']
- instances_value.stdout == inotify_settings['fs.inotify.max_user_instances']
fail_msg: "Inotify sysctl values did not apply correctly."
- name: Count inotify fds on node (observability)
ansible.builtin.shell: "find /proc/*/fd -lname 'anon_inode:inotify' 2>/dev/null | wc -l"
register: inotify_fd_count
changed_when: false
- name: Show inotify fd count
ansible.builtin.debug:
msg: "inotify_fds= (host=)"
4.2. Ansible Playbook 실행
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
❯ ansible-playbook -i /etc/ansible/hosts apply-inotify.yml
###
PLAY [Apply /etc/sysctl.d/99-inotify.conf to k8s_nodes] ************************************************************
TASK [Install 99-inotify.conf (persistent)] ************************************************************************
changed: [k8s-w2]
changed: [k8s-m1]
changed: [k8s-w1]
TASK [Apply sysctl values immediately (only the keys we manage)] ***************************************************
ok: [k8s-w1] => (item={'key': 'fs.inotify.max_user_watches', 'value': '524288'})
ok: [k8s-w2] => (item={'key': 'fs.inotify.max_user_watches', 'value': '524288'})
ok: [k8s-m1] => (item={'key': 'fs.inotify.max_user_watches', 'value': '524288'})
ok: [k8s-w2] => (item={'key': 'fs.inotify.max_user_instances', 'value': '1024'})
ok: [k8s-w1] => (item={'key': 'fs.inotify.max_user_instances', 'value': '1024'})
ok: [k8s-m1] => (item={'key': 'fs.inotify.max_user_instances', 'value': '1024'})
...
PLAY RECAP *********************************************************************************************************
k8s-m1 : ok=7 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
k8s-w1 : ok=7 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
k8s-w2 : ok=7 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
4.3. 적용 확인
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
❯ ansible k8s_nodes -i /etc/ansible/hosts -b -m shell -a '
echo "host=$(hostname)";
echo -n "max_user_watches="; cat /proc/sys/fs/inotify/max_user_watches;
echo -n "max_user_instances="; cat /proc/sys/fs/inotify/max_user_instances;
'
###
k8s-w1 | CHANGED | rc=0 >>
host=k8s-w1
max_user_watches=524288
max_user_instances=1024
k8s-m1 | CHANGED | rc=0 >>
host=k8s-m1
max_user_watches=524288
max_user_instances=1024
k8s-w2 | CHANGED | rc=0 >>
host=k8s-w2
max_user_watches=524288
max_user_instances=1024
5. 운영/설정 가이드
- 감시 대상(디렉터리/파일) 수를 먼저 파악합니다.
- 로그 로테이션, 동적 디렉터리 생성 등 “감시 대상 증가 요인”을 고려해 여유를 확보합니다.
- 오류 로그와 이벤트 처리 지연을 모니터링해 한도/성능 문제를 조기에 감지합니다.
max_user_watches값을 지나치게 높이면 커널 메모리 사용량이 증가할 수 있습니다.
6. 요약
failed to create fsnotify watcher: too many open files 에러는 Linux의 inotify 제한으로 인해 발생합니다. fsnotify의 동작 특성과 이벤트 특성을 이해하고, 커널 한도 및 파일 디스크립터 한도를 적절히 조정하면 재발을 줄일 수 있습니다.
7. Reference
궁금하신 점이나 추가해야 할 부분은 댓글이나 아래의 링크를 통해 문의해주세요.
Written with KKamJi

