IT/DevSecOps

Trivy Operator 도입 후 CVE 알림이 홍수처럼 쏟아진 이야기

gfrog 2026. 7. 7. 18:47
SMALL

지난달에 팀 내부에서 "이미지 취약점 스캔 좀 제대로 붙여야 하지 않냐"는 얘기가 나왔다. 그 전까진 CI 파이프라인에서 trivy image 한 줄로 빌드 시점에만 스캔하고 있었는데, 클러스터에 이미 떠 있는 워크로드는 아무도 안 챙기고 있었다. 이미지 태그가 latest인 것도 있고, 6개월 전에 배포하고 그대로인 것도 있었다. 그래서 Trivy Operator를 붙였다. 그리고 다음날 새벽에 슬랙 알림이 몇백 개 쏟아졌다.

왜 Trivy Operator였나

선택지는 몇 가지 있었다. Trivy Operator, Kubescape, Falco, StackRox 같은 것들. 우리는 이미 CI에서 Trivy를 쓰고 있었고 룰이 익숙했다. 그리고 Aqua가 계속 유지보수하고 있어서 커뮤니티가 가장 활발했다. 최근 0.33.2 릴리즈까지 SBOM 생성이나 config audit report 같은 CRD들이 계속 정리되고 있어서, 표준 CRD로 결과를 뽑아 쓸 수 있다는 점이 컸다. Prometheus로 메트릭 export도 되고 ValidatingWebhook에 붙여서 admission control도 가능해서, 나중에 확장할 여지가 많았다.

Helm으로 설치하는 건 간단했다. namespace 하나 만들고 chart 배포. 기본 설정으로 띄우니까 클러스터 전체 워크로드를 스캔하기 시작했다. 여기까진 좋았다.

알림 홍수

문제는 알림 파이프라인이었다. 나는 Prometheus Alertmanager로 VulnerabilityReport CRD에서 Critical이 하나라도 있으면 슬랙 채널에 쏘게 해뒀다. 이게 왜 문제였냐면, 우리 클러스터에 워크로드가 대략 300개쯤 되는데, 각 워크로드마다 컨테이너가 여러 개 있고, 그 안에 base image가 있고, 그 base image 안에 openssl, glibc, curl 같은 게 다 들어있다.

한 워크로드에서 Critical CVE 하나만 잡혀도 알림이 하나. 300개 워크로드에 평균 3-4개씩 잡히면 1000개가 넘는다. 그것도 새벽에 한꺼번에.

아침에 슬랙 열어보니 채널 하나가 통째로 빨간불이었다. 팀원 한 명이 "이거 뭐야"라고 DM을 보냈고, 나는 그때부터 알림을 어떻게 줄일지 고민하기 시작했다.

뭘 잘못했나

첫 번째는 심각도 필터링을 CVE 개수 기준으로만 했다는 것. Trivy는 CVSS 점수를 그대로 심각도에 매핑하는데, Critical(9.0+)이라도 실제로 exploit 가능한 게 아닌 경우가 많다. 예를 들어 컨테이너 안에 curl이 있고 거기 CVE가 있어도, 그 컨테이너가 curl을 실제로 호출하지 않으면 사실 무의미하다.

두 번째는 base image를 통일 안 한 상태였다는 것. 팀마다 alpine, debian, ubuntu, distroless 다 섞어 쓰고 있어서, 같은 CVE가 base image 종류마다 다르게 잡혔다. 어떤 건 fixed version이 있고 어떤 건 없고. 이걸 다 개별 알림으로 받으니까 노이즈가 엄청났다.

세 번째는 fixed version 필드를 안 봤다는 것. Trivy 리포트에는 FixedVersion 필드가 있고, 이게 비어있으면 아직 패치가 안 나온 CVE다. 즉 지금 당장 뭘 할 수도 없다. 이런 걸 알림으로 받아봐야 대응할 방법이 없다.

어떻게 줄였나

며칠 걸려서 알림 파이프라인을 다시 짰다. 핵심은 세 가지였다.

첫째, fixed version이 있는 것만 알림 대상으로. PromQL 쿼리에서 trivy_image_vulnerabilities{fixed_version!=""} 조건을 넣어서, 패치 가능한 것만 필터링했다. 이것만으로도 절반 넘게 줄었다.

- alert: FixablePodVulnerability
  expr: |
    sum by (namespace, name, severity) (
      trivy_image_vulnerabilities{
        severity=~"Critical|High",
        fixed_version!=""
      }
    ) > 0
  for: 6h
  labels:
    severity: warning
  annotations:
    summary: "{{ $labels.namespace }}/{{ $labels.name }} 에 패치 가능한 취약점"

둘째, for: 6h 조건. CVE는 급하게 대응할 게 아니다. 6시간 동안 유지되는 경우에만 알림을 쏘게 했다. 스캔 타이밍 이슈로 잠깐 잡혔다가 사라지는 것들도 있어서, 이것도 노이즈를 많이 줄여줬다.

셋째, 워크로드 단위로 aggregate. 컨테이너/CVE 단위가 아니라 워크로드 단위로 묶어서 알림. Deployment 이름 하나에 "N개의 fixable critical/high"가 있다는 식으로. 이러니까 하루에 알림이 10-20개 수준으로 떨어졌다.

지금 상태

일주일 정도 돌려보니 팀에서 실제로 대응하는 흐름이 만들어졌다. 매주 월요일에 리포트 채널에서 fixable한 것만 확인하고, base image 업데이트 PR을 돌린다. trivy k8s cluster 명령어로 클러스터 전체 스냅샷을 뽑아서 대시보드로 보는 것도 붙였는데, 이건 아직 초기 단계다.

한 가지 남은 고민은 accepted risk를 어떻게 관리하냐는 것. 어떤 CVE는 우리 환경에서 exploit이 불가능한 게 확실한데, 이걸 매번 알림에서 걸러내려면 exception list가 필요하다. Trivy는 .trivyignore 파일이나 VulnerabilityReport에 어노테이션 붙이는 방식으로 예외 처리를 지원하는데, 이걸 팀 전체 정책으로 어떻게 관리할지는 아직 정리 중이다.

배운 것

보안 도구를 붙일 때 "잡는 것"보다 "알림 파이프라인"이 훨씬 중요하다는 걸 다시 느꼈다. 스캔 자체는 어렵지 않은데, 그걸 팀이 실제로 대응할 수 있는 수준으로 만드는 게 어렵다. 알림이 많으면 사람들이 무시하기 시작하고, 무시하기 시작하면 진짜 중요한 게 묻힌다.

혹시 Trivy Operator 붙이려는 분 있으면, 첫날부터 알림을 다 켜지 말고 우선 리포트만 쌓아두고 며칠 관찰하는 걸 추천한다. 어떤 CVE가 얼마나 잡히는지 감을 잡고 나서 알림 룰을 짜는 게 훨씬 낫다.

다음에는 Trivy에서 SBOM을 뽑아서 supply chain 관점으로 관리하는 것도 다뤄보려고 한다.

BIG