
왜 Operator인가
이미지 스캔(Trivy)이랑 시그니처 검증(cosign)까지는 잘 깔아뒀는데, 정작 "이 컨테이너가 지금 이상한 짓을 하고 있는지"는 안 보이더라. 빌드 타임 보안과 런타임 보안은 다른 문제다. 컨테이너 안에서 nc -e /bin/sh가 떠도 우리는 모른다. 그래서 팀에서 Falco를 본격적으로 깔았다. 헬름 차트로 한 번 깔아본 적은 있는데 이번에 Falco Operator(0.43 기준)로 새로 정비하면서 정리한 가이드다.
대상 독자는 EKS/온프렘 K8s에 Falco를 처음 도입하거나, 옛날 헬름 설치를 Operator로 옮기려는 사람.
지난해까진 우리도 falcosecurity/falco 헬름 차트로 깔았다. 근데 룰을 ConfigMap으로 관리하다 보니 룰 한 줄 바꾸면 DaemonSet rollout이 돌고, 룰 검증은 적용 후에야 알 수 있는 구조였다. 적용했더니 syntax 오류로 Falco가 CrashLoop 돌면서 노드 보안이 통째로 빈 시간이 5분 정도 생긴 적도 있다.
Falco 0.40부터 정식 권장된 Falco Operator는 다음을 CR(Custom Resource)로 관리한다.
Falco— Falco 인스턴스 자체 (DaemonSet 스펙, 드라이버, 리소스)FalcoRules— 탐지 룰 묶음FalcoPlugins— k8saudit, cloudtrail 같은 플러그인
룰만 따로 CR로 분리되니까 룰 변경이 Falco 재시작을 트리거하지 않는다. 그리고 webhook으로 룰 syntax를 미리 검증한다. 이게 사실 가장 큰 이유였다.
설치
helm repo add falcosecurity https://falcosecurity.github.io/charts
helm repo update
helm install falco-operator falcosecurity/falco-operator \
--namespace falco-system --create-namespace \
--set webhook.enabled=true
웹훅이 뜨는지 확인.
kubectl get validatingwebhookconfigurations | grep falco
그 다음 Falco 인스턴스를 CR로 띄운다. 드라이버는 modern_ebpf를 추천한다. 커널 6.x 노드면 모듈 빌드 없이 동작한다.
apiVersion: install.falcosecurity.dev/v1alpha2
kind: Falco
metadata:
name: falco
namespace: falco-system
spec:
driver:
kind: modern_ebpf
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 512Mi
outputs:
rate: 1
maxBurst: 1000
jsonOutput: true
logLevel: info
modern_ebpf은 노드 커널 5.8 이상이 필요하다. Bottlerocket 1.20대나 AL2023이면 그냥 동작한다. 옛날 CentOS 7 노드면 kmod로 떨어지는데 컴파일러 의존성이 골치아프니까 가급적 노드 커널 올리는 게 낫다.
룰 작성 — 정말 중요한 건 노이즈 줄이기
기본 룰만 켜두면 하루에 수천 건이 뜬다. Read sensitive file untrusted, Write below etc 같은 게 정상 부트스트랩 과정에서도 잔뜩 잡힌다. 우리 팀은 첫 일주일을 룰 튜닝에만 썼다.
진짜 잡고 싶은 건 결국 이 정도다.
- 컨테이너 안에서 shell 실행 (
spawned process in container) - 평소 안 쓰던 바이너리 실행 (
unexpected outbound connection) - 시크릿 마운트 경로 외 읽기
- 권한 상승 시도 (
setuid,chmod +s)
커스텀 룰을 CR로 분리해서 관리한다.
apiVersion: install.falcosecurity.dev/v1alpha2
kind: FalcoRules
metadata:
name: team-custom-rules
namespace: falco-system
spec:
rules: |
- macro: prod_namespace
condition: k8s.ns.name in (prod, payment, order)
- rule: Shell in prod container
desc: 운영 네임스페이스 컨테이너에서 shell이 떴다
condition: >
spawned_process and container and prod_namespace and
proc.name in (sh, bash, zsh, ash, dash) and
not proc.pname in (entrypoint.sh, init)
output: >
prod container shell (user=%user.name ns=%k8s.ns.name
pod=%k8s.pod.name container=%container.name cmd=%proc.cmdline)
priority: WARNING
tags: [prod, shell, T1059]
- rule: Outbound to non-allowlisted IP
desc: 외부로 나가면 안 되는 컨테이너가 외부 IP로 connect
condition: >
outbound and container and prod_namespace and
k8s.pod.label[network-egress] = "deny" and
not fd.sip in (allowed_outbound_ips)
output: >
unexpected outbound (pod=%k8s.pod.name dst=%fd.sip:%fd.sport)
priority: CRITICAL
tags: [prod, network, T1041]
k8s.pod.label[network-egress] = "deny" 같은 식으로 Pod 레이블 기반 조건을 거니까 NetworkPolicy랑 일관되게 관리할 수 있다. 우리는 거의 모든 룰을 prod_namespace 매크로로 묶어서 dev/stg는 알람 안 가게 했다. 안 그러면 정말 알람만 보다가 하루가 간다.
Falcosidekick으로 알람 보내기
Falco 자체는 stdout에 JSON을 쏟아낼 뿐이라 Slack/PagerDuty로 보내려면 Falcosidekick이 필요하다. 같은 Operator가 사이드킥도 같이 관리한다.
apiVersion: install.falcosecurity.dev/v1alpha2
kind: Falco
metadata:
name: falco
spec:
# ... 위 설정 ...
falcosidekick:
enabled: true
config:
slack:
webhookurl_secret:
name: slack-webhook
key: url
minimumpriority: warning
outputformat: fields
pagerduty:
routingkey_secret:
name: pagerduty-key
key: routing_key
minimumpriority: critical
minimumpriority를 다르게 줘서 WARNING은 Slack, CRITICAL은 PagerDuty로 보내는 게 정석이다. 처음에 둘 다 CRITICAL로 묶어놨다가 새벽 3시에 stg 환경 shell 알람으로 oncall이 깨서 욕먹은 적이 있다.
추가로 Falcosidekick-UI를 켜면 최근 이벤트가 웹 대시보드로 보인다. 우리는 사내 망에만 노출시켜서 보안팀이 자체 모니터링하는 용도로 쓴다.
운영하면서 알게 된 것들
modern_ebpf 드라이버 메모리 사용량이 노드당 평균 200~300MB 정도 나온다. 200대 클러스터면 무시 못 할 양이다. 처음에 limit 128Mi로 줬다가 OOMKill 폭주했다. 256~512Mi가 현실적이다.
룰 적용 후 syscall throughput이 높은 워커(예: 인그레스 컨트롤러 노드)에선 CPU가 살짝 튄다. P99 기준 1~2% 정도라 무시할 만하지만, latency-sensitive 워크로드 노드면 outputs.rate 낮춰서 throttle 거는 게 안전하다.
플러그인 중에 k8saudit은 audit 로그를 받아서 Falco 룰로 평가한다. 이걸 켜면 exec, pod-exec, secret read 같은 컨트롤 플레인 레벨 행위까지 잡힌다. 노드 syscall만 보는 것보다 시야가 훨씬 넓어진다. EKS면 audit 로그를 CloudWatch로 보낸 다음 Falco의 cloudtrail/cloudwatch 플러그인으로 다시 읽는 식으로 구성할 수 있다.
그래서
Falco 자체는 옛날부터 있었지만 Operator로 가면서 운영 부담이 확 줄었다. 룰 검증 webhook이 진짜 좋다. 다음 글에선 우리가 만든 룰 셋을 어떻게 GitOps로 관리하는지 정리해볼까 한다.
혹시 Falco 룰 튜닝 노하우 가지신 분 있으면 댓글로 공유 부탁드린다.
'IT > DevSecOps' 카테고리의 다른 글
| External Secrets Operator vs SOPS, 1년 같이 써본 후기 (0) | 2026.05.12 |
|---|---|
| Cosign + Kyverno로 컨테이너 이미지 서명 검증, 클러스터에 강제하기 (0) | 2026.05.07 |
| Sealed Secrets 마스터 키 백업 안 해놓고 클러스터 옮겼다가 시크릿 47개 복구한 이야기 (0) | 2026.05.03 |
| Trivy로 CVE 1,400개 알림 폭탄 맞은 후, 우리 팀이 한 일 (0) | 2026.04.29 |
| External Secrets Operator vs Vault Agent Injector, 우리 팀은 결국 둘 다 쓰기로 했다 (1) | 2026.04.28 |