13년차의 서버실

서버, 인프라, 홈랩 기술 블로그

[태그:] Trivy

  • [보안] Trivy, Aqua Security, Snyk: 컨테이너 보안 스캐너 비용 및 기능 분석

    [보안] Trivy, Aqua Security, Snyk: 컨테이너 보안 스캐너 비용 및 기능 분석

    안녕하세요, 13년차 서버실 지킴이입니다. 🤓

    요즘 인프라 이야기에서 컨테이너(Container)를 빼놓을 수 없죠? Docker(도커)와 Kubernetes(쿠버네티스)가 대세가 되면서 개발 속도는 엄청나게 빨라졌지만, 그만큼 보안(Security)에 대한 고민도 깊어졌습니다. 제가 직접 여러 환경을 구축하고 운영하면서 느낀 건데, ‘개발 속도만 빠르면 장땡이지!’ 했다가 크게 데인 적이 한두 번이 아니거든요. 😅

    특히 컨테이너 이미지는 한 번 만들면 여러 환경에서 재사용되기 때문에, 이미지 안에 숨겨진 취약점은 나중에 큰 사고로 이어질 수 있습니다. 그래서 오늘은 컨테이너 이미지의 취약점을 미리 스캔하고 관리해주는 도구들, 그중에서도 Trivy, Aqua Security, Snyk 세 가지 솔루션의 비용(Cost)과 기능(Feature)을 제 경험을 바탕으로 심층 분석해보려고 합니다. 어떤 도구가 우리 환경에 가장 적합할지 함께 고민해보시죠!

    ⚠️ 면책 조항: 본 글은 보안 학습과 본인이 관리하는 시스템의 방어를 위한 교육 목적입니다. 타인의 시스템에 대한 무단 접근이나 공격은 불법이며 법적 처벌 대상이 될 수 있습니다. 모든 실습은 반드시 허가된 환경에서만 진행해주세요.

    컨테이너 보안 스캔 워크플로우 다이어그램: 개발부터 CI/CD, 스캐너를 통한 취약점 발견까지

    컨테이너 보안 스캐너는 개발-배포 파이프라인의 핵심 방어선입니다.

    컨테이너 보안 스캐너, 왜 필요할까요? (Concept Explanation)

    쉽게 말해, 컨테이너 보안 스캐너(Container Security Scanner)는 우리가 만든 도커 이미지(Docker Image) 안에 혹시 모를 취약점(Vulnerability)이나 잘못된 설정(Misconfiguration)이 없는지 꼼꼼히 검사해주는 도구입니다. 운영체제 라이브러리, 애플리케이션 종속성(dependencies), 심지어 설정 파일까지 싹 다 뒤져서 알려진 문제점들을 찾아주죠.

    이런 스캐너가 중요한 이유는 다음과 같거든요:

    • 조기 발견 및 수정(Early Detection & Remediation): 개발 단계에서 미리 취약점을 찾아서 고치면, 나중에 운영 환경에서 문제가 터지는 것보다 훨씬 적은 비용과 노력으로 해결할 수 있어요.
    • 규정 준수(Compliance): 특정 산업군에서는 보안 규정 준수가 필수인데, 스캐너를 활용하면 이런 요구사항을 더 쉽게 만족시킬 수 있거든요.
    • DevSecOps(데브섹옵스) 구현: 개발(Dev), 보안(Sec), 운영(Ops)을 통합하는 DevSecOps 문화에서 보안 스캐닝은 CI/CD 파이프라인에 필수적으로 통합되어야 할 요소입니다.

    Trivy로 컨테이너 이미지 스캔하기 (Hands-on Implementation)

    자, 그럼 제가 홈랩에서 가장 즐겨 쓰는 오픈소스 스캐너인 Trivy(트리비)를 직접 써보면서 어떻게 동작하는지 알아볼까요? Trivy는 Aqua Security에서 만든 오픈소스 취약점 스캐너인데, 빠르고 사용하기 쉽다는 장점 때문에 많은 개발자/운영자들에게 사랑받고 있습니다. 저도 처음엔 ‘이거 정말 공짜 맞아?’ 싶을 정도로 만족하며 쓰고 있거든요. 👍

    1. Trivy 설치

    macOS 기준으로 Homebrew(홈브루)를 사용하면 아주 간단하게 설치할 수 있습니다.

    
    # Trivy 설치 (macOS 예시)
    brew install aquasecurity/trivy/trivy
    
    # 설치 확인
    trivy --version
    # Output 예시:
    # Version: 0.49.1
    # ...
    

    다른 운영체제나 Docker 컨테이너로 실행하는 방법은 Trivy 공식 문서를 참고해주세요!

    2. Docker 이미지 스캔

    이제 설치된 Trivy로 특정 Docker 이미지를 스캔해볼까요? 저는 안정적인 버전의 Nginx(엔진엑스) 이미지를 예시로 사용하겠습니다. `nginx:1.21.6`은 비교적 널리 알려진 버전이라, 학습 데이터 컷오프 이전에도 충분히 존재했던 이미지입니다.

    
    # Nginx 1.21.6 이미지 스캔
    trivy image nginx:1.21.6
    

    명령어를 실행하면 Trivy가 해당 이미지의 레이어들을 분석하고, 발견된 취약점들을 severity(심각도)별로 정리해서 보여줄 겁니다. 처음엔 이게 뭔가 싶었는데, 자세히 보면 CVE(Common Vulnerabilities and Exposures) 번호와 함께 어떤 패키지에 어떤 문제가 있는지, 그리고 권고되는 해결책까지 친절하게 알려줘요. 정말 편하더라고요!

    Trivy 컨테이너 이미지 스캔 결과 예시: 심각도별 취약점 목록과 해결책

    Trivy 스캔 결과는 심각도와 함께 취약점 정보, 해결책을 명확하게 보여줍니다.

    삽질 경험: 너무 많은 경고, 어떻게 다루지? (Troubleshooting & Best Practices)

    Trivy를 처음 써봤을 때, ‘와, 세상에 이렇게 취약점이 많았다고?!’ 하면서 놀랐던 기억이 있습니다. 특히 공식 이미지인데도 CRITICAL(치명적), HIGH(높음) 등급의 경고가 수십 개씩 쏟아져 나오는 걸 보고 멘붕이 오기도 했었죠. 🤯

    여기서 중요한 포인트! 모든 경고를 당장 다 해결할 수는 없습니다. 중요한 건 우선순위(Priority)를 정하고, 우리 서비스에 미치는 영향(Impact)을 분석하는 겁니다.

    1. 미해결 취약점 필터링 (`–ignore-unfixed`)

    간혹 어떤 취약점은 아직 패치(patch)가 나오지 않아 해결할 수 없는 경우가 있습니다. 이런 경우까지 경고로 계속 뜨면 노이즈가 너무 심해져요. 이럴 땐 `–ignore-unfixed` 옵션을 사용해서 아직 수정되지 않은 취약점은 제외하고 볼 수 있습니다.

    2. 심각도별 필터링 (`–severity`)

    모든 MEDIUM(중간)이나 LOW(낮음) 레벨의 취약점을 당장 해결하기는 어렵습니다. 보통은 CRITICAL이나 HIGH 레벨부터 우선 처리하는 게 일반적이거든요. `–severity` 옵션으로 원하는 심각도만 지정해서 볼 수 있어요.

    
    # CRITICAL, HIGH 심각도의 미해결 취약점 제외 스캔
    trivy image --severity HIGH,CRITICAL --ignore-unfixed nginx:1.21.6
    

    이렇게 옵션을 조합해서 사용하면, 정말 중요한 취약점만 집중해서 볼 수 있고, 불필요한 노이즈를 줄여서 실제 액션으로 이어질 가능성이 훨씬 높아지더라고요. 제가 직접 해보니, 이 필터링 작업이 엄청 중요하더라고요. 삽질 좀 했습니다 ㅎㅎ

    스캔 결과 해석 및 후속 조치 (Verification & Results)

    Trivy 스캔 결과는 보통 다음과 같은 정보를 포함합니다.

    • CVE ID: 고유한 취약점 식별 번호 (예: CVE-2023-XXXX)
    • Severity: 취약점의 심각도 (CRITICAL, HIGH, MEDIUM, LOW, UNKNOWN)
    • Package: 취약점이 발견된 패키지 이름 및 버전
    • Installed Version: 현재 이미지에 설치된 패키지 버전
    • Fixed Version: 취약점이 해결된 패키지 버전 (이 버전 이상으로 업데이트해야 함)
    • Title/Description: 취약점에 대한 간략한 설명

    스캔 결과를 보고 나면 어떻게 해야 할까요?

    1. 가장 먼저 Fixed Version 확인: 해당 패키지를 Fixed Version 이상으로 업데이트할 수 있는지 확인하고, Dockerfile(도커파일)을 수정해서 이미지를 다시 빌드(rebuild)합니다.
    2. 영향 분석: 패치 적용이 어렵다면, 해당 취약점이 우리 서비스에 실제로 어떤 영향을 미칠 수 있는지 심층 분석합니다. 예를 들어, 특정 포트가 외부에 노출되지 않는다면 일부 네트워크 관련 취약점은 우선순위가 낮아질 수 있겠죠.
    3. 예외 처리(Suppression): 불가피하게 당장 해결할 수 없거나, 우리 서비스에 영향이 없다고 판단되면, 문서화된 근거를 가지고 해당 취약점을 일시적으로 무시(suppress)할 수 있습니다. 하지만 이는 최후의 수단이며, 지속적으로 재평가해야 합니다.

    이런 과정을 통해 저는 ‘보안은 개발의 한 부분’이라는 것을 다시 한번 깨달았습니다. 단순히 스캔만 하고 끝내는 게 아니라, 결과를 이해하고 적절한 조치를 취하는 것이 중요하더라고요.

    Trivy, Aqua Security, Snyk 컨테이너 보안 솔루션 기능 및 비용 모델 비교 인포그래픽

    세 가지 컨테이너 보안 스캐너의 핵심 기능과 비용 모델을 한눈에 비교할 수 있습니다.

    Trivy, Aqua Security, Snyk: 비용 및 기능 비교 (Cost & Feature Analysis)

    이제 Trivy 외에 다른 상용 솔루션인 Aqua Security와 Snyk는 어떤 특징을 가지고 있는지, 그리고 비용(Cost) 측면에서는 어떻게 다른지 비교해보겠습니다. 이들은 단순 취약점 스캔을 넘어, 더 넓은 범위의 컨테이너 보안(Container Security) 기능을 제공합니다.

    컨테이너 보안 스캐너 주요 솔루션 비교

    제가 직접 사용해보고, 또 주변 동료들의 경험담을 들으면서 느낀 점들을 바탕으로 비교표를 만들어봤습니다.

    항목 Trivy Aqua Security (Aqua Cloud Native Security Platform) Snyk (스닉)
    주 사용 목적 개발/CI/CD 단계의 빠른 취약점 스캔 및 SBOM 생성 엔터프라이즈급 통합 Cloud Native 보안 플랫폼 (스캔, 런타임, 네트워크, 워크로드 보호 등) 개발자 중심의 코드, 종속성, 컨테이너, IaC 보안 및 취약점 자동 수정 제안
    라이선스 오픈소스 (Apache 2.0) 상용 솔루션 (유료 구독) 상용 솔루션 (유료 구독, 제한적 무료 플랜 제공)
    통합 용이성 CLI, Docker, Kubernetes, CI/CD 파이프라인 (GitHub Actions, GitLab CI 등) CI/CD, Kubernetes, Cloud Platform, Registry 등 전방위 통합 IDE, Git Repositories, CI/CD, Container Registries 등 개발자 워크플로우 중심 통합
    스캔 범위 OS 패키지, 프로그래밍 언어 종속성, IaC(Infrastructure as Code), 설정 파일, SBOM 이미지, 컨테이너, 서버리스, Kubernetes, 런타임 환경, 클라우드 자산 등 광범위 코드, 오픈소스 종속성, 컨테이너 이미지, IaC 설정
    부가 기능 SBOM(Software Bill of Materials) 생성, 이미지 서명 검증 런타임 보호, 네트워크 방화벽, KSPM(Kubernetes Security Posture Management), 데이터 보호, 통합 대시보드 및 보고서 자동 취약점 수정 제안, 라이선스 준수 검사, 코드 보안, 개발자 교육 자료
    비용 모델 무료 (오픈소스) 구독 기반 (스캔 대상 이미지 수, 보호하는 노드/워크로드 수, 사용량 기반 등 복합적) 구독 기반 (개발자 수, 월별 테스트 횟수, 프로젝트 수 등 복합적)

    비용 측면에서 보면, Trivy는 오픈소스이므로 직접 운영하는 데 드는 인프라 비용 외에는 무료입니다. 하지만 Aqua Security나 Snyk 같은 상용 솔루션은 기업 규모와 사용량에 따라 상당한 구독료가 발생할 수 있습니다. 예를 들어, 수백 개의 컨테이너 이미지를 관리하고 수십 대의 Kubernetes 노드에서 런타임 보호까지 필요하다면, 연간 수천만 원에서 억대까지도 비용이 들 수 있더라고요. 물론 이 금액은 제품마다, 계약 조건마다 천차만별입니다. 제가 특정 가격을 확언할 수는 없지만, ‘오픈소스와 상용 솔루션의 가격 차이는 크다’는 점은 분명합니다.

    따라서 컨테이너 보안 스캐너를 선택할 때는 단순히 기능만 볼 것이 아니라, 우리 조직의 규모, 예산, 기존 인프라와의 통합 용이성, 그리고 필요한 보안 범위 등을 종합적으로 고려해야 합니다. 무조건 비싼 솔루션이 좋다고 할 수는 없거든요. “우리 팀은 개발 속도가 생명인데, 보안 스캐너 때문에 속도가 느려지면 안 돼!” 같은 고민도 충분히 할 수 있습니다. 이럴 땐 Snyk처럼 개발자 워크플로우에 녹아드는 솔루션이 더 효과적일 수 있고요.

    Trivy, Aqua Security, Snyk 장단점 및 추천 사용 사례 요약 비교

    Trivy, Aqua Security, Snyk 각 솔루션의 장단점과 추천 사용 시나리오를 요약한 비교표입니다.

    마무리: 우리에게 맞는 컨테이너 보안 스캐너는? (Conclusion)

    오늘은 컨테이너 보안의 중요한 축인 Trivy, Aqua Security, Snyk 세 가지 솔루션에 대해 기능과 비용(Cost) 관점에서 자세히 알아봤습니다. 제가 직접 써보고, 삽질하면서 느낀 점들을 솔직하게 공유해드렸는데요.

    결론적으로 어떤 도구를 선택할지는 여러분의 상황에 따라 달라집니다.

    • 작은 프로젝트, 개인 홈랩, 또는 제한된 예산으로 기본적인 취약점 스캔을 시작하고 싶다면? ✅ Trivy가 최고의 선택입니다. CLI 기반으로 가볍게 시작하고, CI/CD에 통합하기도 쉽습니다.
    • 개발 초기 단계부터 보안을 녹여내고 싶고, 개발자 중심의 자동화된 워크플로우와 코드 레벨의 빠른 피드백이 중요하다면? 💡 Snyk를 고려해보세요. IDE 통합과 자동 수정 제안 기능이 개발 생산성을 해치지 않으면서 보안을 강화하는 데 도움을 줄 겁니다.
    • 대규모 엔터프라이즈 환경에서 통합적인 Cloud Native 보안 플랫폼이 필요하고, 취약점 스캔을 넘어 런타임 보호, 네트워크 보안, 규정 준수까지 전방위적인 관리가 필요하다면? 🚀 Aqua Security가 적합합니다. 물론 그만큼 비용 투자는 필요하겠죠.

    어떤 도구를 선택하든 중요한 건, 보안은 한 번 하고 끝나는 게 아니라 지속적인 관심과 개선이 필요하다는 점입니다. 스캐너는 도구일 뿐, 그 결과를 이해하고 적절한 조치를 취하는 것이 가장 중요합니다. 여러분의 컨테이너 환경이 더욱 안전해지기를 바라며, 다음 글에서는 컨테이너 런타임 보안에 대해 더 자세히 다뤄볼게요. 궁금한 점이 있다면 언제든 댓글 남겨주세요! 😉

  • [보안] Trivy 이미지 스캔 시 흔히 발생하는 오류와 해결 방법

    [보안] Trivy 이미지 스캔 시 흔히 발생하는 오류와 해결 방법

    [컨테이너 보안] Trivy 이미지 스캔 오류 해결, 삽질 끝에 찾은 해법

    안녕하세요, 13년차 인프라 엔지니어 ’13년차의 서버실’ 주인장입니다. 오늘은 컨테이너 보안의 필수 도구인 Trivy를 사용하다가 흔히 겪을 수 있는 오류들과 제가 직접 삽질하며 찾아낸 해결 방법들을 공유해 보려고 합니다. 사실 처음에는 Trivy가 ‘딱 이거다!’ 싶을 정도로 간편하고 강력해 보였거든요. 그런데 막상 CI/CD 파이프라인에 물려서 쓰려니 예상치 못한 오류들이 툭툭 튀어나오더라고요. 꽤나 골머리를 앓았는데, 저와 같은 경험을 하신 분들이 분명 있을 거라 생각합니다. 혹시 Trivy 이미지 스캔 오류 때문에 밤잠 설치고 계신가요? 그렇다면 이 글이 큰 도움이 될 겁니다.

    ⚠️ 면책 문구: 본 글은 보안 학습과 본인이 관리하는 시스템의 방어를 위한 교육 목적입니다. 타인의 시스템에 대한 무단 접근이나 공격은 불법이며, 법적 처벌 대상이 될 수 있습니다. 모든 기술 활용은 윤리적이고 합법적인 범위 내에서 이루어져야 합니다.

    Trivy 컨테이너 이미지 보안 스캔의 전체적인 흐름을 보여주는 다이어그램입니다.

    Trivy, 컨테이너 보안의 든든한 파수꾼

    Trivy(트리비)는 Aqua Security에서 개발한 오픈소스 취약점 스캐너예요. 컨테이너 이미지뿐만 아니라 파일 시스템, Git 레포지토리, Kubernetes 클러스터, 심지어 IaC(Infrastructure as Code) 설정 파일까지 다양한 대상에서 취약점이나 설정 오류를 찾아내죠. 쉽게 말해, 우리가 만든 컨테이너 이미지가 혹시 모를 보안 구멍을 가지고 있지는 않은지, 혹은 개발 과정에서 실수로 취약한 라이브러리를 사용하진 않았는지 꼼꼼하게 검사해 주는 도구라고 보시면 됩니다.

    요즘처럼 컨테이너 기반의 애플리케이션 개발이 대세인 시대에, DevSecOps(데브섹옵스)는 선택이 아닌 필수가 되었어요. 개발 초기 단계부터 보안을 고려하지 않으면 나중에 훨씬 큰 비용과 시간을 들여야 하는 상황이 생기거든요. Trivy는 이런 DevSecOps를 실현하는 데 아주 효과적인 도구입니다. CI/CD 파이프라인에 Trivy를 통합하면, 이미지가 빌드되자마자 자동으로 취약점을 스캔하고, 문제가 발견되면 배포를 막거나 경고를 줄 수 있어요. 제가 홈랩에서 여러 프로젝트를 진행할 때도 Trivy 덕분에 심각한 보안 문제를 미리 잡아낼 수 있었던 경험이 꽤 많습니다.

    Trivy, 직접 설치하고 스캔해보기

    Trivy 설치도 진짜 간단해요. 저는 주로 macOS 환경에서 Homebrew를 사용하는데, 다른 OS에서도 금방 설치할 수 있습니다. 우분투 서버에서 apt를 이용하기도 하고, Docker 컨테이너로 실행하기도 하는데, 정말 편하더라고요!

    
    # macOS (Homebrew)
    brew install aquasecurity/trivy/trivy
    
    # Debian/Ubuntu (권장)
    curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
    
    # Docker 컨테이너로 실행 (가장 유연함!)
    docker run --rm aquasecurity/trivy:latest --help
    

    설치가 완료되었으면, 이제 간단한 이미지 스캔을 해볼까요? 저는 보통 Nginx 공식 이미지를 많이 사용하는데, 이걸로 한번 테스트해봅시다.

    
    trivy image nginx:latest
    

    이 명령어를 실행하면 Nginx 이미지의 취약점들이 주르륵 나올 거예요. 처음에는 결과가 너무 많아서 당황할 수도 있는데, CRITICAL(치명적)이나 HIGH(높음) 등 심각도가 높은 것들부터 우선적으로 살펴보는 게 좋습니다. 저도 처음엔 수많은 결과에 압도돼서 뭘 먼저 봐야 할지 몰라 헤맸던 기억이 나네요. 하하.

    CI/CD 파이프라인에 Trivy가 통합된 워크플로우 다이어그램

    CI/CD 파이프라인 내 Trivy 스캔 단계를 시각적으로 보여주는 다이어그램입니다.

    ⚠️ 삽질 경험담: Trivy 이미지 스캔 시 흔히 겪는 오류와 해결책

    자, 이제 본론입니다. 제가 13년 동안 서버실에서 겪었던 수많은 삽질 중, Trivy 관련해서 가장 기억에 남는 오류들과 그 해결 방법들을 공유해 드릴게요. 정말 이거 때문에 밤샘도 몇 번 했었습니다. 여러분은 저처럼 고생하지 마시라고 자세히 알려드립니다.

    1. Docker 데몬 연결 오류 (`failed to analyze image: analyze image failed: …`)

    Trivy가 컨테이너 이미지를 스캔하려면 Docker 데몬(Daemon)에 접근할 수 있어야 합니다. 그런데 이 데몬이 제대로 실행되지 않았거나, Trivy를 실행하는 사용자에게 접근 권한(Permission)이 없을 때 이 오류가 발생하곤 해요.

    • 문제 상황: Error: failed to analyze image: analyze image failed: GET http://unix/v1.24/images/nginx:latest/json: dial unix /var/run/docker.sock: connect: permission denied
    • 원인: Docker 소켓 파일(/var/run/docker.sock)에 대한 권한 부족 또는 Docker 데몬 미실행.
    • 해결 방법:
      1. Docker 데몬 실행 확인: sudo systemctl status docker 명령으로 Docker 서비스가 활성화되어 있는지 확인합니다. 만약 실행 중이 아니라면 sudo systemctl start docker로 시작하세요.
      2. 사용자에게 Docker 그룹 권한 부여: 현재 사용자를 docker 그룹에 추가하여 소켓 파일에 접근할 수 있도록 합니다.
      3. 
        sudo usermod -aG docker $USER
        newgrp docker  # 또는 로그아웃 후 다시 로그인
        

    2. 이미지 Pull 오류 (`failed to analyze image: analyze image failed: GET https://registry-1.docker.io/v2/…`)

    Trivy는 스캔하기 전에 대상 이미지를 로컬로 가져옵니다(Pull). 이때 프라이빗 레지스트리(Private Registry)에 접근해야 하거나, 네트워크 방화벽(Firewall) 등의 문제로 이미지를 가져오지 못할 때 발생해요.

    • 문제 상황: Error: failed to analyze image: analyze image failed: GET https://registry-1.docker.io/v2/my-private-repo/my-image/manifests/latest: denied: requested access to the resource is denied
    • 원인: 레지스트리 인증 정보 누락, 네트워크 문제(프록시, 방화벽), 이미지 이름 오타.
    • 해결 방법:
      1. 레지스트리 로그인: 프라이빗 레지스트리인 경우 docker login <your-registry> 명령으로 로그인 정보를 Trivy가 사용할 수 있도록 해줍니다.
      2. 네트워크 설정 확인: 기업 환경에서는 프록시 서버(Proxy Server)를 통해 인터넷에 접속해야 하는 경우가 많아요. Trivy는 HTTP_PROXY, HTTPS_PROXY 환경 변수를 지원하므로 이를 설정해 주세요.
      3. 
        export HTTP_PROXY="http://proxy.example.com:8080"
        export HTTPS_PROXY="http://proxy.example.com:8080"
        trivy image my-private-repo/my-image:latest
        
      4. 방화벽 규칙 검토: 필요한 포트(예: 443, 80)가 열려 있는지 확인합니다.

    3. 취약점 데이터베이스(DB) 초기화 오류 (`failed to initialize vulnerability DB: …`)

    Trivy는 최신 취약점 정보를 스캔하기 위해 자체 데이터베이스를 사용합니다. 이 DB를 업데이트하거나 초기화하는 과정에서 문제가 생기면 스캔을 시작할 수 없어요.

    • 문제 상황: FATAL: failed to initialize vulnerability DB: failed to download vulnerability DB: failed to download DB from ...
    • 원인: 네트워크 연결 불량, Trivy DB 서버 접근 불가, 디스크 공간 부족.
    • 해결 방법:
      1. 네트워크 연결 확인: Trivy DB를 다운로드하는 URL(보통 GitHub 또는 Aqua Security CDN)에 접근 가능한지 ping이나 curl로 확인합니다.
      2. 디스크 공간 확인: df -h 명령으로 Trivy가 설치된 디렉토리(보통 ~/.cache/trivy)의 디스크 공간이 충분한지 확인해요.
      3. 수동 DB 업데이트 시도: 문제가 지속되면 DB를 수동으로 업데이트해 보세요.
      4. 
        trivy sbom --download-db-only
        

    4. WSL2 환경에서 Docker Desktop 없이 Trivy 사용 시 주의사항

    Windows Subsystem for Linux (WSL) 2 환경에서 Docker Desktop을 사용하지 않고 Trivy를 실행할 때, 저도 처음엔 이게 뭔가 싶었는데 이런 오류 메시지를 만날 수 있어요.

    • 문제 상황: FATAL: Trivy is not supported on Windows Subsystem for Linux (WSL) without Docker Desktop. Please install Docker Desktop or run Trivy in a container.
    • 원인: Trivy가 이미지 스캔을 위해 WSL2의 호스트 Docker 데몬에 접근해야 하는데, Docker Desktop이 설치되어 있지 않아 데몬을 찾지 못하는 경우예요.
    • 해결 방법:
      1. Docker Desktop 설치: 가장 권장되는 방법입니다. Docker Desktop을 설치하면 WSL2와 원활하게 통합되어 Trivy가 Docker 데몬을 쉽게 사용할 수 있어요.
      2. WSL2 내에 Docker Engine 직접 설치: 좀 더 복잡하지만, WSL2 배포판 안에 직접 Docker Engine을 설치하고 실행하는 방법도 있습니다. 이 경우 sudo service docker start 등으로 데몬을 수동으로 시작해야 할 수 있어요.
      3. Trivy를 Docker 컨테이너로 실행: 이 방법은 호스트의 Docker 데몬에 의존하지 않고 Trivy 자체를 컨테이너 안에서 실행하는 방식이라 유용합니다.
      4. 
        docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasecurity/trivy:latest image nginx:latest
        

    이 외에도 `–skip-update` 옵션을 사용하여 DB 업데이트를 건너뛸 수 있지만, 이는 오래된 취약점 정보로 스캔하게 되므로 꼭 필요한 경우가 아니면 권장하지 않아요. 보안은 최신 정보가 생명이니까요!

    오류 해결 후, 스캔 결과 검증 및 해석

    숱한 삽질 끝에 드디어 Trivy 스캔이 성공적으로 완료되었다면, 이제 그 결과를 제대로 해석하는 것이 중요합니다. Trivy는 기본적으로 콘솔에 결과를 출력하지만, JSON, SARIF 등 다양한 포맷으로도 출력할 수 있어서 CI/CD 파이프라인에서 자동화된 분석에 아주 유용하게 쓰여요.

    
    trivy image --format json -o results.json my-image:latest
    
    Trivy 컨테이너 이미지 취약점 스캔 결과 예시

    Trivy 스캔 결과 보고서의 심각도별 취약점 목록을 시각화한 이미지입니다.

    결과를 볼 때는 다음 기준들을 중점적으로 살펴보세요.

    항목 설명 해석 및 권고
    Severity (심각도) CRITICAL, HIGH, MEDIUM, LOW, UNKNOWN CRITICAL, HIGH는 즉시 조치 필요. 심각한 보안 위협으로 이어질 수 있어요.
    Vulnerability ID (취약점 ID) CVE-YYYY-XXXXX 등의 고유 식별자 해당 ID로 NVD(National Vulnerability Database) 등에서 상세 정보를 검색하면 돼요.
    Installed Version (설치된 버전) 현재 이미지에 포함된 패키지 버전 취약점이 발견된 패키지의 버전입니다.
    Fixed Version (수정된 버전) 취약점이 패치된 패키지 버전 이 값이 존재한다면, 해당 버전으로 업데이트해야 해요. Fixed Version이 없으면 대안을 찾아야 합니다.
    Title (제목) 취약점에 대한 간략한 설명 취약점의 내용을 빠르게 파악할 수 있어요.

    특히 Fixed Version이 존재하는지 여부가 중요합니다. 만약 Fixed Version이 있다면 해당 패키지를 업데이트하는 것으로 대부분의 취약점을 해결할 수 있어요. 예를 들어, Nginx 이미지에서 OpenSSL 취약점이 발견되고 Fixed Version이 특정 버전이라면, Dockerfile에서 Nginx를 빌드할 때 OpenSSL을 해당 버전으로 명시하거나, 베이스 이미지를 최신으로 업데이트하는 등의 조치를 취하면 됩니다.

    Dockerfile 예시:

    
    FROM debian:bullseye-slim
    
    # 보안 패치를 포함한 패키지 업데이트
    RUN apt-get update && apt-get install -y --no-install-recommends \
        openssl \
        && rm -rf /var/lib/apt/lists/*
    
    # ... 나머지 Dockerfile 내용 ...
    

    또한, Trivy가 너무 많은 취약점을 보고하여 혼란스러울 때는 .trivyignore 파일을 활용하여 의도적인 False Positive(오탐)를 무시할 수도 있어요. 하지만 정말 필요한 경우에만 사용해야 하며, 신중하게 결정해야 합니다.

    마무리하며: 지속적인 관심이 최고의 보안입니다

    오늘은 Trivy 이미지 스캔 시 흔히 발생하는 오류와 해결 방법에 대해 제가 직접 겪었던 경험을 바탕으로 이야기해 봤습니다. 컨테이너 보안은 한 번 설정해두면 끝나는 게 아니라, 새로운 취약점이 끊임없이 발견되므로 지속적인 관심과 관리가 필요해요. Trivy는 이런 지속적인 보안 관리를 위한 훌륭한 도구임이 분명합니다.

    Trivy 오류 유형 및 해결 방법 요약 인포그래픽

    Trivy 사용 중 발생할 수 있는 주요 오류 유형과 그 해결책을 요약한 인포그래픽입니다.

    만약 여러분의 CI/CD 파이프라인에서 Trivy가 자꾸 에러를 뿜어낸다면, 이 글에서 제시된 해결 방법들을 하나씩 적용해 보세요. 대부분의 문제는 Docker 데몬 권한, 네트워크 설정, 또는 Trivy DB 업데이트 문제에서 비롯됩니다. 특히, Docker 데몬 연결 문제와 레지스트리 인증 문제는 제가 가장 많이 마주쳤던 케이스들이니, 이 부분들을 먼저 확인해 보시는 것을 추천합니다.

    저도 여전히 홈랩에서 새로운 기술을 실험하며 삽질을 거듭하고 있습니다. 그 과정에서 얻은 소중한 경험들은 앞으로도 ’13년차의 서버실’ 블로그를 통해 꾸준히 공유해 드릴게요. 다음번에는 Trivy를 이용한 Kubernetes 클러스터 보안 스캔에 대한 이야기를 다뤄볼까 합니다. 그때까지 모두 안전한 컨테이너 환경을 만드시길 바랍니다! 궁금한 점이 있다면 언제든지 댓글로 남겨주세요!

  • [CI/CD 보안] Trivy로 컨테이너 보안 취약점 자동 탐지 구축하기

    [CI/CD 보안] Trivy로 컨테이너 보안 취약점 자동 탐지 구축하기

    [CI/CD 보안] Trivy로 컨테이너 보안 취약점 자동 탐지 구축하기

    안녕하세요, 13년차 인프라 엔지니어입니다. 요즘 같은 DevOps 환경에서는 CI/CD 파이프라인이 선택이 아닌 필수가 되었죠. 그런데 이렇게 빠르게 빌드하고 배포하는 과정에서 보안(Security)은 제대로 챙기고 계신가요?

    현업과 홈랩에서 일하다 보니 느끼는 게, 보안은 항상 뒷전으로 밀리거나 나중에 터지고 나서야 허둥지둥 해결하는 경우가 많더라고요. 특히 컨테이너 이미지는 한 번 빌드되면 어떤 취약점이 있는지 제대로 확인하지 않고 프로덕션 환경에 배포되는 일이 허다합니다. 그러다 터지면… 상상하기도 싫죠.

    이런 문제를 미리 막고 싶어서, CI/CD 파이프라인에 보안 취약점 자동 탐지(Automated Vulnerability Scanning)를 도입하는 방법을 계속 고민해왔습니다. 그리고 찾은 게 바로 Trivy(트리비)입니다. 오늘은 Trivy를 활용해서 CI/CD 파이프라인에 컨테이너 이미지 보안 스캔을 자동화하는 방법을 제 경험을 바탕으로 솔직하게 풀어보려 합니다.

    참고: 본 글은 보안 학습과 자신이 관리하는 시스템 방어를 위한 교육 목적입니다. 타인의 시스템에 무단 접근하는 행위는 법률상 불법이며 처벌 대상이니 꼭 기억해두세요.

    CI/CD 파이프라인에 Trivy가 통합되어 컨테이너 이미지의 보안 취약점을 자동 탐지하는 아키텍처 다이어그램

    그림 1: Trivy를 활용한 CI/CD 보안 파이프라인 개요

    Trivy란 무엇인가? 컨테이너 보안 스캔 도구 개론

    Trivy는 Aqua Security에서 개발한 오픈소스 도구로, 컨테이너 이미지(Container Image), 파일 시스템(Filesystem), Git 저장소(Git Repository) 등 다양한 대상에서 보안 취약점(Security Vulnerabilities)과 잘못된 설정(Misconfigurations)을 찾아줍니다. 가볍고 빠르면서도 정확도가 높아서 많은 개발팀이 애용하고 있거든요.

    쉽게 말해, 우리가 만든 컨테이너 이미지 안에 혹시 오래된 라이브러리나 알려진 취약점이 있는 패키지가 포함되어 있지는 않은지, 혹은 Dockerfile이나 Kubernetes 설정 파일에 보안상 위험한 설정이 있지는 않은지 꼼꼼하게 검사해주는 보안 스캐너라고 생각하시면 됩니다. 저도 처음엔 반신반의했는데, 써보고 나서는 정말 감탄했어요.

    왜 CI/CD 파이프라인에 보안 스캔을 넣어야 할까요?

    DevOps 환경에서는 개발 단계에서부터 보안을 고려하는 Shift-Left Security(시프트 레프트 보안)가 중요합니다. 나중에 터지고 나서 고치려면 시간과 비용이 훨씬 많이 들거든요. 저도 예전에 프로덕션에 배포된 서비스에서 심각한 취약점이 발견돼서 밤샘 작업을 한 기억이 생생합니다.

    CI/CD 파이프라인에 Trivy 같은 도구를 넣으면:

    • 조기 발견 및 대응: 개발 초기에 취약점을 발견해서 빠르게 수정할 수 있습니다.
    • 자동화된 검증: 매번 수동으로 검사할 필요 없이, 코드가 푸시될 때마다 자동으로 보안 검증이 이루어집니다.
    • 보안 수준 향상: 잠재적인 보안 위협을 줄여 전체 시스템의 보안 견고성(Security Robustness)을 높일 수 있습니다.
    • 규제 준수: PCI-DSS, HIPAA 같은 특정 산업군의 보안 규제를 준수하는 데 도움이 됩니다.

    Trivy 실전 구축! CI/CD 파이프라인에 녹여내기

    이제 가장 중요한 실전 구현입니다. 저는 주로 GitLab CI/CD를 사용하는데요, 여기서는 GitLab CI를 예시로 보여드리겠습니다. 다른 CI/CD 도구(GitHub Actions, Jenkins 등)에서도 원리는 비슷하니 응용하시면 됩니다.

    1. Trivy 설치 및 기본 스캔 (로컬 환경)

    먼저 로컬에서 Trivy가 잘 작동하는지 확인해봐야겠죠? 설치는 정말 간단합니다. 저는 주로 Homebrew를 쓰지만, 다양한 설치 방법이 있어요.

    # macOS (Homebrew) 또는 Linux (apt, yum 등 각 배포판 패키지 매니저 활용)
    brew install aquasecurity/trivy/trivy
    
    # 또는 Docker로 실행 (설치 없이 바로 사용 가능)
    docker run --rm aquasecurity/trivy:latest --version
    

    설치가 완료되면, 이제 컨테이너 이미지를 스캔해봅시다. 저는 테스트용으로 NGINX 공식 이미지를 스캔해볼게요.

    trivy image nginx:latest
    

    명령어를 실행하면 NGINX 이미지에 포함된 패키지들의 취약점 목록이 쭉 나올 겁니다. 심각도(Severity)별로 분류되어 있어서 어떤 것부터 고쳐야 할지 한눈에 파악하기 좋더라고요. 처음엔 이 많은 취약점들을 어떻게 다 봐야 하나 당황했는데, 실제로는 Critical이나 High 레벨부터 우선순위를 두고 보면 됩니다.

    2. GitLab CI/CD 파이프라인에 Trivy 통합

    이제 로컬에서 잘 작동하는 Trivy를 CI/CD 파이프라인에 넣어봅시다. 제 경험상, 컨테이너 이미지를 빌드한 직후, 그리고 레지스트리(Registry)로 푸시하기 전에 스캔하는 것이 가장 효율적이었습니다. 이렇게 하면 취약한 이미지가 레지스트리에 올라가는 것을 사전에 차단할 수 있거든요.

    `.gitlab-ci.yml` 파일에 다음과 같은 내용을 추가할 수 있습니다.

    stages:
      - build
      - scan
      - deploy
    
    variables:
      DOCKER_IMAGE_NAME: my-app
      DOCKER_IMAGE_TAG: $CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA
      # CI_REGISTRY는 GitLab 내장 Registry 주소입니다.
      FULL_IMAGE_NAME: $CI_REGISTRY/$CI_PROJECT_PATH/$DOCKER_IMAGE_NAME:$DOCKER_IMAGE_TAG
    
    build_image:
      stage: build
      image: docker:latest
      services:
        - docker:dind
      script:
        - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
        - docker build -t $FULL_IMAGE_NAME .
        - docker push $FULL_IMAGE_NAME
      only:
        - main
        - merge_requests
    
    scan_image_with_trivy:
      stage: scan
      image: 
        name: aquasecurity/trivy:latest
        entrypoint: [""]
      variables:
        # Trivy가 취약점 DB를 다운로드 받을 디렉토리. CI/CD 캐시 활용을 위해 설정
        TRIVY_CACHE_DIR: ".trivycache"
      script:
        # 빌드된 이미지를 스캔하기 위해 Docker Registry에 로그인
        - trivy --version
        - trivy image --ignore-unfixed --severity CRITICAL,HIGH --exit-code 1 $FULL_IMAGE_NAME
      cache:
        key: "$CI_COMMIT_REF_SLUG-trivy-cache"
        paths:
          - "$TRIVY_CACHE_DIR"
        policy: pull-push
      only:
        - main
        - merge_requests
    
    deploy:
      stage: deploy
      script:
        - echo "Deploying $FULL_IMAGE_NAME"
        # 여기에 실제 배포 로직 (Kubernetes, Ansible 등)을 작성합니다.
      only:
        - main
    

    위 YAML 코드를 보시면, `scan_image_with_trivy`라는 새로운 stage를 추가했습니다. 여기서 주목할 부분은:

    • image: aquasecurity/trivy:latest: Trivy 공식 Docker 이미지를 사용해서 별도 설치 없이 바로 실행합니다.
    • --ignore-unfixed: 아직 패치가 나오지 않은 취약점은 결과에서 제외합니다. (이걸 안 하면 리포트가 너무 길어져서 피로도가 높더라고요.)
    • --severity CRITICAL,HIGH: Critical(치명적)과 High(높음) 심각도의 취약점만 보고합니다. 처음부터 모든 취약점을 잡으려다가는 배보다 배꼽이 더 커질 수 있습니다. 현실적으로 가장 위험한 것부터 처리하는 게 중요하더라고요.
    • --exit-code 1: Critical 또는 High 심각도의 취약점이 발견되면, 파이프라인을 실패(Exit Code 1)시킵니다. 이게 핵심입니다! 자동으로 취약한 이미지가 다음 단계로 넘어가지 못하게 막는 거죠.
    • cache: Trivy가 취약점 데이터베이스(DB)를 다운로드하는 시간을 줄이기 위해 캐시를 활용했습니다. CI/CD 환경에서는 캐시 활용이 빌드 시간을 줄이는 데 아주 중요하거든요.
    GitLab CI/CD에서 Trivy 스캔 작업이 실행되고 CRITICAL, HIGH 취약점이 표시된 결과 화면

    그림 2: GitLab CI/CD에서 Trivy 스캔 작업 실행 및 결과

    Trivy 도입 시 주의사항 및 트러블슈팅

    Trivy CI/CD 파이프라인을 구축하면서 겪었던 몇 가지 삽질 경험과 해결책을 공유합니다. 혹시 비슷한 문제를 겪으신다면 도움이 될 거예요!

    1. False Positive (오탐) 문제

    Trivy도 완벽하지 않습니다. 때로는 실제로는 문제가 없는데 취약점으로 보고하는 오탐(False Positive)이 발생하기도 합니다. 특히 개발 초기 단계에서는 이런 오탐 때문에 파이프라인이 계속 실패하면 개발자들의 불만이 커질 수 있거든요.

    • 해결책: .trivyignore 파일을 사용해서 특정 취약점 ID를 무시하거나, --ignore-unfixed 옵션을 활용하여 아직 패치되지 않은 취약점을 제외할 수 있습니다. 예를 들어, CVE-2023-12345라는 특정 취약점 ID를 무시하고 싶다면 .trivyignore 파일에 해당 ID를 한 줄에 하나씩 작성하면 됩니다.

    2. 너무 많은 취약점 보고서

    처음에는 모든 심각도를 스캔했더니 보고서가 너무 길고, 뭘 먼저 고쳐야 할지 막막하더라고요. 모든 취약점을 한 번에 다 고치려는 건 현실적으로 어렵습니다.

    • 해결책: 앞서 보여드린 것처럼 --severity CRITICAL,HIGH 옵션을 사용해서 가장 위험한 취약점부터 우선적으로 처리하도록 정책을 세우는 것이 좋습니다. 점진적으로 심각도 기준을 높여가는 거죠.

    3. Trivy DB 업데이트 실패

    CI/CD 환경에서 네트워크 문제나 프록시 설정 때문에 Trivy가 취약점 데이터베이스를 업데이트하지 못하는 경우가 있었습니다. 최신 DB가 아니면 정확한 스캔이 불가능하죠.

    • 해결책: CI/CD Runner가 외부 인터넷에 접근 가능한지 확인하고, 필요한 경우 프록시 환경 변수(HTTP_PROXY, HTTPS_PROXY)를 설정해줘야 합니다. GitLab CI의 경우, variables 섹션에서 설정할 수 있습니다.
    • 또한, trivy image 명령 전에 trivy sbom으로 SBOM(Software Bill Of Materials)을 생성하고, 이를 기반으로 trivy image --input sbom.json 형태로 스캔하는 방식도 고려해볼 수 있습니다. 이는 네트워크 제한 환경에서 유용합니다.

    CI/CD 파이프라인 보안 검증: 실제 스캔 결과 및 적용

    위 설정대로 파이프라인을 구축하고 나면, 이제 새로운 코드가 푸시되거나 Merge Request(머지 리퀘스트)가 생성될 때마다 자동으로 Trivy 스캔이 실행됩니다. 만약 Critical, High 심각도의 취약점이 발견되면, 스캔 단계에서 파이프라인이 실패하고, 개발자에게 알림이 갑니다. 개발자는 이 알림을 보고 취약점을 수정하거나, 정당한 오탐(False Positive)인 경우 무시 규칙을 추가할 수 있습니다.

    실제 GitLab CI/CD 파이프라인에서 성공적으로 스캔이 완료된 모습이나, 혹은 취약점 때문에 파이프라인이 실패한 모습을 보면 정말 뿌듯하더라고요. 저는 주로 이런 식으로 결과를 확인합니다.

    Trivy 스캔 결과가 심각도별로 요약되고 해결 방안을 제시하는 대시보드 리포트

    그림 3: Trivy 스캔 결과 대시보드 예시

    아래는 Trivy의 주요 스캔 대상과 그 특징을 비교한 표입니다. 상황에 따라 적절한 스캔 대상을 선택하는 것이 중요하죠.

    스캔 대상 (Scan Target) 설명 (Description) 주요 활용 사례 (Key Use Cases) 장점 (Pros) 고려사항 (Considerations)
    image (컨테이너 이미지) Docker 이미지, OCI 이미지 등 컨테이너 이미지 내부의 패키지 취약점 스캔 CI/CD 파이프라인에서 이미지 빌드 후 즉시 검사, 배포 전 최종 검증 가장 일반적이고 강력한 컨테이너 보안, 배포 전 위험 제거 스캔 시간이 다소 길 수 있음 (레이어 분석), 이미지 레이어 최적화 필요
    fs (파일 시스템) 로컬 파일 시스템 또는 압축 파일 내의 취약점 및 설정 오류 스캔 개발 중인 프로젝트 코드 스캔, 특정 디렉토리/파일 검사 빠른 스캔, 개발 초기 단계에서 피드백 제공, CI/CD 전 로컬 검증 전체 컨테이너 환경을 반영하기 어려움, 의존성 설치 환경에 따라 결과 상이
    repo (Git 저장소) Git 저장소의 설정 파일(Dockerfile, Kubernetes YAML)에서 잘못된 설정 스캔 IaC(Infrastructure as Code) 보안 검증, 초기 개발 단계에서 설정 오류 방지 코드 레벨에서 보안 취약점 조기 발견, 설정 파일 검증 코드 내부의 라이브러리 취약점은 탐지 불가, 설정 파일 문법 의존적
    Trivy의 컨테이너 이미지, 파일 시스템, Git 저장소 스캔 대상별 특징 비교 인포그래픽

    그림 4: Trivy 스캔 대상별 특징 비교

    마무리하며: Trivy를 통한 지속적인 보안 확보

    Trivy를 CI/CD 파이프라인에 통합하는 것은 컨테이너 기반 환경에서 보안을 강화하고, 개발 및 운영 효율성을 높이는 중요한 단계라고 생각합니다. 저도 처음엔 ‘이걸 언제 다 구축하나’ 싶었는데, 한 번 구축해두니 정말 든든하더라고요. 든든한 방패를 얻은 기분이랄까요?

    물론 Trivy 하나만으로 모든 보안 위협을 막을 수는 없습니다. 하지만 가장 기본적인 방어선을 구축하고, 자동화된 방식으로 지속적인 보안 검증을 수행한다는 점에서 그 가치는 충분하다고 봅니다. Critical, High 레벨의 취약점만이라도 배포 전에 걸러낼 수 있다면, 야간 비상 호출(On-call) 횟수를 확 줄일 수 있을 거예요. 저도 덕분에 잠을 좀 더 잘 수 있게 됐습니다.

    만약 여러분의 CI/CD 파이프라인에 아직 자동화된 보안 스캔이 없다면, 지금 당장 Trivy를 도입해보시길 강력히 추천합니다. 초기에는 오탐이나 너무 많은 보고서 때문에 조금 번거로울 수도 있지만, 꾸준히 규칙을 개선하고 피드백을 반영하다 보면 훨씬 견고하고 효율적인 보안 프로세스를 만들 수 있을 겁니다. 다음번에는 Trivy를 활용한 SBOM(Software Bill Of Materials) 생성 및 관리 방법에 대해 이야기해볼까 합니다. 기대해주세요!

  • [보안] Trivy 도입 1년 회고: DevSecOps 워크플로우 변화와 개선점 분석

    [보안] Trivy 도입 1년 회고: DevSecOps 워크플로우 변화와 개선점 분석

    본 글은 보안 학습과 본인이 관리하는 시스템의 방어를 위한 교육 목적입니다. 타인의 시스템에 대한 무단 접근·공격은 불법이며 법적 처벌 대상입니다.

    Trivy 도입 회고를 1년치로 다시 적어보면, 제가 얻은 가장 큰 변화는 탐지율보다도 의사결정 방식의 변화였습니다. 전에는 취약점 공지가 뜨면 운영팀이 먼저 불안해했고, 개발팀은 “우리 코드 문제인지 베이스 이미지 문제인지”부터 감으로 추정했었죠. Trivy를 붙인 뒤에는 최소한 그 대화가 구조화됐습니다. 어떤 단계에서 막을지, 무엇을 예외로 둘지, 어떤 결과는 즉시 수정이고 어떤 결과는 추적 대상으로 둘지 기준선이 생겼거든요.

    중요한 건 여기입니다. Trivy는 보안을 완성해주는 도구가 아니라, 컨테이너와 IaC 영역에서 팀의 판단을 표준화해주는 도구에 가깝습니다. 이걸 잘못 기대하면 “스캔은 도는데 사고는 왜 나지?”라는 반응이 나오고, 반대로 위치를 정확히 잡으면 릴리스 직전의 불필요한 공황을 꽤 줄여주더라고요. 저도 홈랩과 업무 환경에서 비슷한 패턴으로 굴려보면서, Trivy를 도입한 팀과 그렇지 않은 팀의 차이는 기능 수보다 운영 규칙의 유무에서 갈린다고 느꼈습니다.

    이번 글은 단순 사용기가 아니라, 1년 동안 실제로 남은 습관과 버린 습관을 같이 적는 회고입니다. 특히 CI에서 보안 스캔이 형식화되기 쉬운 팀, 결과는 쌓이는데 누구도 우선순위를 정하지 못하는 팀이라면 바로 적용 가능한 형태로 정리해봤습니다.

    Trivy 도입 회고 기반 DevSecOps 워크플로우 개요 이미지

    Trivy 도입 회고와 DevSecOps 워크플로우 변화를 한눈에 보여주는 개요 이미지입니다.

    1. 왜 Trivy를 넣었는지: 제가 원한 건 “더 많은 경고”가 아니라 “더 빠른 판별”이었습니다

    제가 처음 Trivy를 넣은 이유는 단순했습니다. 컨테이너 이미지를 배포하는 속도는 빨라졌는데, 취약점 확인은 여전히 사람 손을 많이 탔기 때문입니다. 패키지 목록을 보고 CVE를 대조하는 식의 확인은 운영 주기가 짧아질수록 바로 무너지더라고요. 그때 필요한 건 화려한 플랫폼이 아니라, 이미지와 설정을 같은 문법으로 반복 점검할 수 있는 스캐너였습니다.

    Trivy가 실무에서 먹히는 이유는 범위가 넓어서가 아니라 도입 순서를 잘게 쪼갤 수 있어서입니다. 이미지 스캔만 먼저 넣고, 이후에 IaC와 secret 탐지로 넓혀도 워크플로우가 크게 흔들리지 않습니다. 반면 SAST나 DAST는 언어, 프레임워크, 런타임, 테스트 환경에 더 강하게 묶이는 경우가 많아서 첫 진입 비용이 큽니다.

    다만 여기서 선을 분명히 그어야 합니다. 애플리케이션 로직 취약점, 예를 들면 인증 우회나 권한 검증 실수 같은 건 Trivy가 대신 찾아주지 않습니다. 제 경험상 Trivy는 아래 상황에서 특히 강하고, 아래 상황에서는 과도한 기대를 버리는 편이 맞았습니다.

    상황 Trivy를 우선 쓸 때 다른 도구를 같이 봐야 할 때 제 판단
    컨테이너 배포 파이프라인 베이스 이미지, OS 패키지, 언어 패키지 점검 런타임 행위 분석이 필요할 때 Trivy를 기본 게이트로 둡니다
    Kubernetes / Terraform 운영 매니페스트, Terraform, Dockerfile 오설정 점검 조직 정책 강제와 예외 승인 체계가 별도일 때 Trivy config 또는 fs 스캔을 먼저 붙입니다
    애플리케이션 코드 보안 의존성 수준의 위험 신호 확인 인증, 권한, 비즈니스 로직 취약점 검토 SAST, 코드리뷰, 위협 모델링이 필요합니다
    비밀값 관리 저장소 내 하드코딩 secret 탐지 이미 유출된 키 회전, 감사 추적 탐지 후 운영 절차가 더 중요합니다

    한마디로 말하면, Trivy는 “전체 보안 솔루션”이 아니라 배포 경로에 가까운 위험을 빠르게 분류하는 도구로 볼 때 가장 실용적이었습니다.

    2. Trivy를 1년 써보니 바뀐 DevSecOps 워크플로우

    도입 전에는 릴리스 직전에 한 번 몰아서 확인하는 습관이 있었습니다. 이 방식의 문제는 취약점 자체보다 발견 시점이 늦다는 데 있습니다. 이미지 하나에서 HIGH나 CRITICAL이 나와도, 그게 애플리케이션 의존성인지 베이스 이미지 레이어인지 구분하는 데 시간이 들고, 그다음에는 재빌드와 회귀 테스트가 줄줄이 따라옵니다. 보안 이슈가 일정 이슈로 번지는 전형적인 패턴이죠.

    Trivy를 붙인 뒤에는 스캔 위치를 늘린 게 아니라, 질문이 다른 세 지점으로 나눴습니다.

    1. 개발 중: 지금 만든 변경이 새 위험을 들여왔는가
    2. CI 빌드 단계: 이 커밋은 배포 금지선에 걸리는가
    3. 배포 전 또는 정기 점검: 허용한 예외가 아직도 타당한가

    이 세 지점을 구분하고 나니 팀 대화가 훨씬 현실적으로 바뀌었습니다. 예전에는 “보안팀이 막았다”는 식으로 받아들였는데, 지금은 “이건 신규 유입이라 지금 고쳐야 한다”, “이건 기존 부채라 추적하되 이번 배포는 진행한다”처럼 이야기할 수 있게 됐습니다.

    특히 제가 강하게 느낀 건, 모든 HIGH/CRITICAL을 일괄 차단하는 정책은 생각보다 오래 못 간다는 점입니다. 신규 서비스라면 가능하지만, 오래된 베이스 이미지와 레거시 패키지가 섞인 환경에서는 첫날부터 파이프라인이 전부 빨갛게 변합니다. 그러면 팀이 배우는 게 아니라 우회 방법을 먼저 찾습니다. 그래서 저는 보통 이렇게 권했습니다.

    • 신규 서비스: HIGH, CRITICAL을 바로 게이트로 겁니다
    • 기존 서비스: 우선 JSON 리포트를 남기고 신규 유입분만 차단합니다
    • IaC 점검: 즉시 차단보다 반복되는 오설정 패턴을 템플릿에서 제거합니다

    이 접근이 좋은 이유는 단순합니다. 보안 게이트는 강도보다 지속 가능성이 먼저이기 때문입니다.

    3. 실전 구현: 1년 뒤에도 남은 기본 스캔 패턴

    처음엔 옵션을 과하게 붙였는데, 시간이 지나고 보니 오래 살아남는 건 단순한 패턴이었습니다. 다만 단순하다고 해서 대충 쓰면 안 됩니다. Trivy는 같은 명령처럼 보여도 어디서 어떤 플래그를 붙이느냐에 따라 해석이 꽤 달라집니다.

    3-1. 로컬 이미지 점검: “지금 만든 이미지가 배포 금지선에 걸리는지”만 빠르게 확인

    # 사람이 읽기 좋은 표 형태로 확인
    trivy image --severity HIGH,CRITICAL --ignore-unfixed --format table my-app:local
    
    # 보안 게이트 용도: 보안 이슈가 있으면 종료 코드 1
    trivy image --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 my-app:local
    
    # 결과를 남겨서 이전 스캔과 비교
    trivy image --severity HIGH,CRITICAL --format json --output trivy-image-report.json my-app:local

    여기서 제가 실제로 중요하게 본 건 세 가지입니다. --severity는 우선순위 절삭용이고, --exit-code 1은 자동화 제어용이며, --format json --output은 운영 대화용입니다. 스캔 결과를 눈으로만 보고 끝내면 회고가 안 남습니다. 반대로 JSON을 남기면 “원래 있던 위험인지”, “이번 커밋에서 새로 생긴 위험인지”를 구분하기가 쉬워집니다.

    또 하나, --ignore-unfixed는 편의 옵션처럼 보이지만 팀 피로도를 크게 좌우합니다. 패치가 아직 없는 항목까지 동일한 톤으로 경고하면 개발자는 금방 무감각해지기 마련입니다. 제가 운영에서 얻은 결론은 이렇습니다. 수정 가능한 위험과 당장 수정 불가능한 위험을 같은 줄에 세우지 마세요. 이 둘은 우선순위가 다릅니다.

    3-2. 저장소와 IaC 점검: 여기서 많이 놓치는 건 “기본값의 함정”입니다

    Trivy를 처음 붙일 때 많은 팀이 놓치는 포인트가 하나 있습니다. image, fs, repo 계열 스캔에서 misconfiguration 탐지는 기본 활성화가 아닐 수 있다는 점입니다. 즉, 취약점 스캔이 성공했다고 해서 Kubernetes 매니페스트나 Terraform 설정까지 본 게 아닐 수도 있더라고요. 저는 이 부분에서 한 번 크게 헷갈렸고, 이후에는 의도적으로 스캐너 종류를 명시했습니다.

    # 파일시스템 기준으로 취약점, 오설정, 비밀값을 같이 확인
    trivy fs --scanners vuln,misconfig,secret --severity HIGH,CRITICAL .
    
    # 설정 자체를 집중적으로 볼 때
    trivy config .
    
    # JSON 결과를 남겨 후처리
    trivy fs --scanners misconfig,secret --format json --output trivy-fs-report.json .

    제 경험상 운영 사고는 애플리케이션 코드보다 YAML과 Terraform에서 먼저 터지는 경우가 많았습니다. 예를 들어 보안 컨텍스트 누락, 과도한 capability 허용, 퍼블릭 노출 보안 그룹, 암호화 누락 같은 항목은 코드리뷰에서 지나가도 설정 스캔에서는 반복적으로 잡힙니다. 그래서 Kubernetes나 Terraform 비중이 큰 팀이라면 이미지 스캔보다 trivy config .가 체감 효용이 더 클 수도 있습니다.

    Trivy 도입 회고를 위한 CI 이미지 스캔과 설정 스캔 구성도

    빌드, 스캔, 배포 승인 단계로 이어지는 Trivy 기반 CI 흐름을 설명하는 이미지입니다.

    3-3. CI 파이프라인 예시: 속도와 신뢰도를 같이 잡으려면 캐시 전략이 먼저입니다

    초기에는 매 파이프라인마다 DB를 처음부터 내려받아서 체감 속도가 꽤 나빴습니다. 나중에 알게 된 건, 느린 원인이 Trivy 자체의 스캔 엔진보다도 DB cold start와 캐시 운영 방식에 있는 경우가 많다는 점입니다. 저는 이후에 “업데이트 단계”와 “실제 스캔 단계”를 분리하는 식으로 바꿨습니다.

    stages:
      - build
      - security
    
    variables:
      TRIVY_CACHE_DIR: .trivycache
    
    trivy_db_warmup:
      stage: security
      image: aquasec/trivy:latest
      script:
        - trivy image --download-db-only --cache-dir "$TRIVY_CACHE_DIR"
      cache:
        key: trivy-cache
        paths:
          - .trivycache
    
    trivy_scan:
      stage: security
      image: aquasec/trivy:latest
      script:
        - trivy image --cache-dir "$TRIVY_CACHE_DIR" --skip-db-update --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 --format json --output trivy-report.json "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA"
        - trivy config --severity HIGH,CRITICAL .
      cache:
        key: trivy-cache
        paths:
          - .trivycache
      artifacts:
        when: always
        paths:
          - trivy-report.json

    여기서 핵심은 세 가지입니다. 첫째, --download-db-only로 DB 준비를 분리합니다. 둘째, 실제 스캔에서는 --skip-db-update로 매번 네트워크 병목을 만들지 않습니다. 셋째, 캐시 디렉터리를 명시해 러너별 편차를 줄입니다. 이 패턴은 파이프라인 속도를 위해서도 중요하지만, 더 본질적으로는 팀이 우회하지 않게 만드는 장치입니다.

    주의할 점도 있습니다. 로컬 파일 시스템 캐시는 편하지만, 같은 캐시를 여러 프로세스가 동시에 잡는 환경에서는 잠금 문제가 생길 수 있습니다. 현업에서는 이걸 Trivy 오류로 오해하기 쉬운데, 실제 원인은 병렬 러너와 공유 캐시 구조인 경우가 많습니다. 그래서 병렬 잡이 많은 CI라면 캐시 공유 방식을 먼저 점검하는 편이 좋았습니다.

    3-4. 예외는 파일이 아니라 계약입니다

    예외 처리도 초반에 대충 시작하면 나중에 제일 큰 부채가 됩니다. 제가 권하는 방식은 단순합니다. 예외를 남길 때 ID, 만료일, 이유를 같이 기록하세요. 특히 YAML 기반 ignore 파일은 이 문맥을 남기기에 좋았습니다.

    vulnerabilities:
      - id: CVE-2023-29491
        expired_at: 2026-12-31
        statement: Upstream fix is not available yet; tracked in internal patch queue
    
    misconfigurations:
      - id: AVD-DS-0002
        paths:
          - "docs/Dockerfile"
        statement: Documentation image intentionally runs as root for example output

    제가 이 방식을 선호한 이유는, 예외를 기술 부채로 보이게 만들기 때문입니다. 그냥 .trivyignore에 ID만 추가해두면 몇 달 뒤에는 아무도 왜 허용했는지 기억하지 못합니다. 반면 만료일과 설명이 남아 있으면, 분기 점검 때 예외를 다시 심사할 수 있습니다.

    4. 어떤 기준으로 운영했는지: 무조건 차단보다 “신규 유입 차단”이 먼저였습니다

    1년 돌리고 나서 가장 확신하게 된 건, 보안 스캔의 성패는 탐지 정확도보다 운영 규칙의 해상도에 달려 있다는 점입니다. 리포트만 잘 뽑아서는 아무 일도 안 일어납니다. 누가 무엇을 보고 언제 막고 언제 예외로 넘길지 정해져야 비로소 도구가 시스템이 됩니다.

    운영 단계 스캔 대상 차단 기준 추천 방식 쓰지 말아야 할 방식
    초기 도입 컨테이너 이미지 경고만, 차단 없음 리포트 해석 루틴부터 정착 첫날부터 전체 서비스 일괄 차단
    안정화 단계 이미지 + IaC CRITICAL 또는 신규 HIGH 신규 서비스 우선 적용 기존 부채와 신규 유입을 같은 기준으로 처리
    정착 이후 이미지 + IaC + secret 정책 위반, 신규 HIGH/CRITICAL, 만료된 예외 예외 만료 관리와 추세 분석 병행 예외를 영구 허용처럼 방치

    제가 현장에서 자주 내린 판단은 아래와 같습니다.

    • exit code 1이 발생했다: 무조건 “보안팀 이슈”가 아니라, 우선 신규 유입인지 기존 누적인지부터 구분합니다
    • HIGH/CRITICAL이 많다: 애플리케이션 패키지보다 베이스 이미지 교체 가능성을 먼저 봅니다
    • misconfig 경고가 반복된다: 개별 서비스 수정 대신 템플릿과 헬름 차트 기본값을 손봅니다
    • secret 탐지가 나왔다: 파일 삭제로 끝내지 말고 키 회전, 배포 환경, Git 히스토리 노출까지 확인합니다

    이 기준이 실무적으로 유용했던 이유는 숫자에 덜 매달리게 해주기 때문입니다. 결과 개수는 서비스 성격에 따라 달라집니다. 반면 신규 유입, 반복 패턴, 만료된 예외는 팀의 운영 건강도를 훨씬 잘 보여줍니다.

    5. 실제로 겪은 문제들: 속도, 노이즈, 예외 관리, 그리고 가장 흔한 오해

    여기부터가 회고의 핵심입니다. 도입 자체보다 오래 가는 실패 모드를 알아야 팀이 덜 지칩니다.

    5-1. DB 업데이트 때문에 느려지는 문제

    표면적으로는 “Trivy가 느리다”로 보이지만, 근본 원인은 대개 매 잡마다 DB를 새로 받는 구조입니다. 특히 ephemeral runner를 쓰면 이 비용이 더 자주 드러납니다. 제 기준에서는 매 커밋 전체 스캔보다, DB 준비를 분리하고 실제 게이트는 핵심 브랜치나 배포 직전 단계에 두는 쪽이 훨씬 오래 갔습니다.

    5-2. 오설정을 본 줄 알았는데 사실 안 본 문제

    이건 실무에서 꽤 위험합니다. 취약점 스캔 결과가 잘 나오니까 팀은 “IaC도 같이 봤겠지”라고 생각합니다. 그런데 fs나 image 계열에서 misconfig 스캐너를 명시하지 않으면 기대한 범위를 다 보지 못할 수도 있더라고요. 근본 원인은 도구 성능이 아니라 스캔 범위에 대한 잘못된 가정입니다.

    5-3. 수정 불가능한 항목이 너무 많이 보이는 문제

    --ignore-unfixed 없이 모든 항목을 그대로 내보내면 개발자 입장에서는 행동 기준이 흐려집니다. “지금 고칠 수 없는 항목”과 “지금 당장 베이스 이미지 업데이트로 줄일 수 있는 항목”을 분리하지 않으면 리포트는 금방 소음이 됩니다. 저는 그 뒤로 “행동 가능한 결과 우선” 원칙을 고정했습니다.

    5-4. 예외 처리 파일이 영구 창고가 되는 문제

    예외는 필요합니다. 문제는 예외 자체가 아니라 재검토 주기가 없는 예외입니다. 파일 하나에 ID가 계속 쌓이기 시작하면, 그 순간부터 스캔 결과의 신뢰도는 빠르게 떨어집니다. 제 경험상 예외 항목은 추가보다 제거가 더 어렵기 때문에, 처음부터 만료일과 이유를 강제하는 편이 낫습니다.

    # 현재 결과 저장
    trivy image --severity HIGH,CRITICAL --format json --output current-scan.json my-app:latest
    
    # 새로 유입된 취약점 식별에 필요한 최소 필드만 추출
    jq '.Results[]?.Vulnerabilities[]? | {VulnerabilityID, PkgName, InstalledVersion, FixedVersion, Severity}' current-scan.json

    이 출력에서 제가 보는 건 단순 개수가 아닙니다. PkgName과 InstalledVersion, FixedVersion 조합을 보면 “패키지 업그레이드로 해결 가능한지”, “베이스 이미지 교체가 먼저인지” 판단이 빨라집니다. 리포트는 많아도, 실제로 손대야 하는 지점은 보통 몇 군데로 수렴합니다.

    Trivy 도입 회고 결과 검증과 예외 관리 대시보드 이미지

    스캔 결과, 심각도 분류, 예외 검토 흐름을 보여주는 결과 화면 이미지입니다.

    6. 재현 가능한 시나리오 하나: 코드보다 베이스 이미지가 먼저인 경우

    실제 현장에서 자주 본 장면을 하나 말씀드리겠습니다. 애플리케이션 코드는 크게 바뀌지 않았는데, 어느 날 이미지 스캔에서 OS 패키지 쪽 HIGH/CRITICAL이 갑자기 눈에 띄게 늘어납니다. 이때 개발팀이 바로 애플리케이션 의존성을 뒤지기 시작하면 시간이 꽤 낭비됩니다. 제가 먼저 확인하는 순서는 거의 항상 같습니다.

    1. 취약점이 애플리케이션 패키지인지 OS 패키지인지 구분합니다
    2. 현재 베이스 이미지 태그가 오래됐는지 확인합니다
    3. 가능하면 공식 베이스 이미지를 최신 태그 계열로 교체해 재빌드합니다
    4. 재스캔 후에도 남는 항목만 애플리케이션 레벨에서 봅니다

    예를 들어 JSON 결과에서 동일한 OS 계열 패키지가 여러 항목에 반복 등장하면, 그때는 코드보다 베이스 레이어를 먼저 의심하는 편이 맞았습니다. 반대로 언어 패키지 매니저 쪽 결과가 중심이면 애플리케이션 의존성 정리가 먼저일 때가 많았습니다. 이 판단을 빨리 내리면 보안 대응이 코드 수리전이 아니라 레이어 선택 문제로 바뀝니다.

    다만 베이스 이미지 교체가 만능은 아닙니다. 베이스를 올리면 libc나 openssl 같은 런타임 동작 차이로 회귀가 날 수 있습니다. 그래서 저는 Trivy 결과를 보고 베이스 이미지를 바꿀 때는, 보안 수정과 기능 검증을 분리하지 않고 같은 변경 세트로 다루는 편이 낫다고 봅니다. 취약점 수치만 줄이고 서비스가 깨지면 운영 관점에서는 실패니까요.

    7. 1년 운영 결과: 좋아진 점과 아직 아쉬운 점

    좋아진 점은 분명했습니다. 첫째, 보안 이슈가 배포 막판에야 드러나는 빈도가 줄었습니다. 둘째, 컨테이너 보안 검토가 특정 담당자의 기억이나 감에 덜 의존하게 됐습니다. 셋째, IaC 오설정이 템플릿 수준에서 반복되는 패턴으로 보이기 시작했습니다. 이건 단순히 경고를 더 본다는 의미가 아니라, 조직이 같은 실수를 덜 반복한다는 쪽에 가깝습니다.

    반면 한계도 뚜렷했습니다.

    • Trivy만으로 애플리케이션 보안이 완성되지는 않습니다
    • 예외 관리가 느슨해지면 스캔 결과는 빠르게 형식화됩니다
    • 캐시와 업데이트 전략을 안 잡으면 CI 병목이 바로 눈에 띕니다
    • 팀 성숙도보다 앞선 강한 차단 정책은 우회를 부릅니다

    그래서 지금의 제 결론은 꽤 명확합니다. Trivy는 만능 도구가 아니라 컨테이너와 설정 검증의 기본 레일로 두는 게 가장 잘 맞습니다. 이 위치를 벗어나면 기대가 과해지고, 이 위치를 정확히 잡으면 투자 대비 효용이 좋습니다.

    Trivy 도입 회고의 도입 전후 비교와 개선 포인트 인포그래픽

    도입 전 수동 점검과 도입 후 자동화된 DevSecOps 워크플로우를 비교하는 요약 이미지입니다.

    8. 마무리: 이런 팀이라면 저는 이렇게 적용하라고 권합니다

    제 추천은 상황별로 꽤 분명합니다. 이제 막 시작하는 팀이라면 이미지 스캔부터 붙이세요. 처음부터 모든 경고를 막지 말고, HIGH/CRITICAL 결과를 읽고 분류하는 루틴부터 만드시는 편이 낫습니다. 이미 CI가 있는 팀이라면 --exit-code 1과 심각도 기준을 먼저 명시하세요. 배포 차단 기준이 불명확하면 스캔은 돌아도 운영은 바뀌지 않습니다.

    Kubernetes나 Terraform 비중이 큰 팀이라면 trivy config . 또는 trivy fs --scanners vuln,misconfig,secret .를 같이 두는 편이 맞습니다. 실무에서 더 자주 사고를 내는 건 코드보다 설정인 경우가 많기 때문입니다. 반대로 애플리케이션 인증, 권한, 비즈니스 로직 취약점까지 Trivy 하나로 해결하려고 하시면 방향이 틀어집니다. 그건 다른 검토 체계가 필요합니다.

    장기 운영 팀이라면 선택이 더 단순합니다. 신규 유입 위험은 차단하고, 기존 부채는 추적하되, 예외는 만료시켜 다시 심사하세요. 이 세 가지만 지켜도 Trivy는 충분히 값어치를 합니다. 결국 Trivy 도입 회고는 도구 평가라기보다 운영 습관 평가에 가까웠습니다. 저에게 남은 교훈도 하나였습니다. 스캐너는 많이 도는 것보다, 팀이 어떤 기준으로 멈추고 어떤 기준으로 진행할지를 분명히 할 때 비로소 도움이 됩니다.

    마지막으로 다시 적어둡니다. 본 글은 보안 학습과 본인이 관리하는 시스템의 방어를 위한 교육 목적입니다. 타인의 시스템에 대한 무단 접근·공격은 불법이며 법적 처벌 대상입니다. Trivy를 도입하실 때는 기능 목록보다 운영 규칙부터 설계해보세요. 그 순서가 맞아야 1년 뒤에도 파이프라인에 남습니다.

  • [보안] Kubernetes 보안 체크리스트: 컨테이너 이미지 스캔 자동화

    [보안] Kubernetes 보안 체크리스트: 컨테이너 이미지 스캔 자동화

    본 글은 보안 학습과 본인이 관리하는 시스템의 방어를 위한 교육 목적입니다. 타인의 시스템에 대한 무단 접근·공격은 불법이며 법적 처벌 대상입니다.

    Kubernetes 보안 체크리스트: 컨테이너 이미지 스캔 자동화

    Kubernetes 보안 체크리스트를 운영하다 보면 병목이 꽤 자주 이미지 스캔에서 드러납니다. 배포 파이프라인은 분 단위로 빨라졌는데 보안 판정이 여전히 사람 손에 걸려 있으면, 속도와 통제가 같이 무너지더라고요. 저도 홈랩이든 사내 테스트 클러스터든 처음에는 릴리스 직전 일회성 스캔으로 시작했는데, 운영으로 넘어가면 금방 한계가 보였습니다. 이미지 변경 속도, 베이스 이미지 교체 주기, 레지스트리 정책, 클러스터 반입 기준이 따로 놀면 자동화는 이름만 자동화가 되거든요.

    실무에서는 스캐너를 한 번 붙이는 것으로 끝나지 않습니다. 빌드 시점, 레지스트리 저장 시점, 클러스터 반입 시점, 운영 중 재평가 시점을 나눠서 봐야 합니다. 이유는 단순합니다. 배포 당시엔 통과한 이미지라도, 며칠 뒤 취약점 데이터베이스가 갱신되면 같은 다이제스트가 다른 판정을 받을 수 있습니다. 이 차이를 놓치면 팀은 바로 “어제는 괜찮았는데 왜 오늘 막히지?”라는 혼란에 빠집니다.

    CI, 레지스트리, Kubernetes 클러스터, 보고 체계를 한눈에 보여주는 전체 구성도입니다.

    Kubernetes 보안 체크리스트에서 이미지 스캔이 중요한 이유

    현장에서 필요한 건 CVE 숫자 나열이 아닙니다. 무엇을 기준으로 배포를 막을지, 무엇은 추적만 하고 무엇은 즉시 조치할지, 그 판정을 누가 재현할 수 있는지가 핵심입니다. 실제 스캔 결과를 보면 문제는 대체로 세 부류로 갈립니다. 베이스 이미지 교체로 끝나는 항목, 애플리케이션 의존성 업데이트가 필요한 항목, 이미지가 아니라 배포 매니페스트나 실행 권한 설정을 손봐야 하는 항목입니다. 같은 High라도 대응 경로가 완전히 다릅니다.

    그래서 체크리스트를 만들 때 제가 먼저 적는 건 도구 이름이 아니라 다음 네 가지입니다. 첫째, 어떤 시점에 검사할지. 둘째, 어떤 대상을 검사할지. 셋째, 어떤 심각도와 조건에서 실패로 볼지. 넷째, 예외를 누구 승인으로 얼마나 오래 둘지. 이 네 줄이 빠지면 결과는 늘 많고, 책임은 늘 흐려집니다.

    Kubernetes 보안 체크리스트로 정리한 컨테이너 이미지 스캔 자동화 체크리스트

    1. 이미지 태그보다 다이제스트를 기준으로 기록: repo:tag만 저장하지 말고 repo@sha256:...를 남겨야 스캔 결과와 배포 대상을 정확히 연결할 수 있습니다.
    2. 빌드 직후 스캔: CI에서 이미지 생성 직후 취약점과 시크릿 혼입 여부를 확인합니다.
    3. 실패 기준을 코드로 고정: CRITICAL 즉시 차단, HIGH는 팀 정책에 따라 차단 또는 예외 관리 등으로 명시합니다.
    4. 데이터베이스 갱신 시점 기록: 로컬, CI, 클러스터의 스캔 결과가 다른 흔한 원인입니다.
    5. 레지스트리 인증 분리: 빌드용 자격 증명과 스캔용 읽기 전용 자격 증명을 분리해 두면 사고 범위를 줄이기 좋습니다.
    6. 클러스터 내 재스캔: 이미 떠 있는 워크로드를 주기적으로 다시 평가해야 운영 중 신규 공개 취약점을 따라잡을 수 있습니다.
    7. 예외 처리 만료일 강제: 예외는 허가가 아니라 부채입니다. 사유, 승인자, 만료일이 없으면 결국 영구 면제가 됩니다.
    8. 결과 저장 위치 통일: CI 로그만 믿지 말고 리포트 시스템이나 Kubernetes 리소스로 남겨 반복 조회 가능하게 해야 합니다.
    9. 로컬과 CI 판정 일치: 같은 옵션, 같은 대상, 가능하면 같은 DB 갱신 흐름을 써야 팀이 결과를 신뢰합니다.
    10. 최소 이미지 우선: Distroless나 slim 계열 검토는 보안 때문이기도 하지만, 스캔 노이즈와 패치 범위를 줄이는 데도 효과가 큽니다.
    11. Admission 단계 연계 여부 결정: 규정이 엄격하면 배포 허가 단계에서 차단하고, 초기 도입기라면 관찰 모드부터 들어가는 편이 덜 부서집니다.
    12. 성능 예산 확보: 대형 이미지 재스캔은 노드 자원과 레지스트리 호출을 먹습니다. 운영 클러스터에서 무작정 스캔 빈도를 높이면 다른 워크로드가 피해를 볼 수 있습니다.

    도구 선택 전에 먼저 정할 기준

    Trivy, Grype처럼 널리 쓰이는 스캐너는 출발점으로 충분히 좋습니다. 저는 빠르게 붙일 때는 Trivy를 자주 쓰는 편입니다. 로컬 점검, CI, Kubernetes 연계까지 흐름을 만들기 편해서 이거 진짜 실무에서 손이 덜 가더라고요. 다만 도구보다 먼저 정해야 할 것이 있습니다. 결과 해석 단위가 이미지인지, 레포지토리인지, 실제 클러스터 워크로드인지부터 정해야 합니다. 이 기준이 흔들리면 스캐너를 바꿔도 운영은 그대로 혼란스럽습니다.

    선택 포인트 A를 택할 때 B를 택할 때 제가 실제로 권하는 기준
    스캔 시점 CI만 스캔: 배포 속도와 단순성이 우선일 때 CI + 클러스터 재스캔: 운영 중 노출 추적이 중요할 때 운영 클러스터가 있으면 병행이 맞습니다. CI만으로는 신규 공개 취약점을 놓치기 쉽습니다.
    식별 방식 태그 기준: 초기에 단순하게 시작할 때 다이제스트 기준: 결과 재현성과 감사 추적이 필요할 때 실서비스는 다이제스트 기준으로 가는 편이 안전합니다. 태그는 너무 쉽게 바뀝니다.
    실패 정책 Critical만 차단: 도입 초기에 반발을 줄여야 할 때 Critical + High 차단: 규정과 배포 품질 기준이 이미 성숙했을 때 처음부터 전부 막기보다 Critical 차단, High는 만료일 있는 예외 관리가 현실적입니다.
    스캔 대상 OS 패키지 위주: 베이스 이미지 관리가 주 이슈일 때 OS + 애플리케이션 의존성 + 시크릿: 공급망 전체를 봐야 할 때 실무에서는 최소한 취약점과 시크릿은 같이 보는 편이 낫습니다.
    운영 방식 관찰 모드: 현재 상태 파악이 먼저일 때 차단 모드: 배포 기준을 강제해야 할 때 새로 도입하는 팀은 1~2주 정도 관찰 모드로 데이터부터 모으는 편이 덜 아픕니다.

    실전 구현 1: Kubernetes 보안 체크리스트 기준으로 로컬과 CI 맞추기

    가장 먼저 할 일은 개발자 로컬과 CI가 가능한 한 같은 판정을 내도록 맞추는 것입니다. 로컬에선 통과했는데 CI에서만 실패하면 팀은 금방 스캐너보다 파이프라인을 더 싫어하게 됩니다. 그래서 이 단계에서는 “옵션을 많이 주는 것”보다 “같은 옵션을 고정하는 것”이 더 중요합니다. 이 기준만 맞춰도 운영 피로도가 꽤 줄어듭니다.

    1. 이미지 빌드 후 Trivy로 즉시 검사

    아래 예시는 로컬에서 이미지 빌드 직후 취약점과 시크릿을 같이 보는 가장 단순한 흐름입니다. 팀 공통 기준을 잡을 때는 이런 명령부터 통일하는 게 훨씬 편합니다.

    docker build -t myapp:scan-test .
    trivy image \
      --severity HIGH,CRITICAL \
      --ignore-unfixed \
      --scanners vuln,secret \
      --format table \
      myapp:scan-test

    여기서 중요한 건 옵션 의미보다 운영상의 함정입니다. --ignore-unfixed는 노이즈를 줄이는 데 유용하지만, “아직 수정본이 없으니 위험이 없다”는 뜻은 아닙니다. 패치가 없어도 우회책이나 완화 조치가 필요한 경우가 있습니다. 그래서 저는 이 옵션을 쓰더라도 예외 문서에는 “미조치”보다 “공급사 수정 대기”처럼 상태를 분리해 적는 편입니다.

    --scanners vuln,secret 조합도 실무 효율이 좋습니다. 이미지 레이어에 테스트 토큰, 오래된 인증서, 샘플 키가 남아 있는 경우가 의외로 자주 나옵니다. 이건 CVE보다 발견 즉시 우선순위를 높게 잡아야 하는 유형입니다. 공격 난도가 낮고 노출 즉시 영향이 커질 수 있어서 그렇습니다.

    2. CI에서 실패 기준을 강제하고 결과를 아티팩트로 남기기

    CI에서는 사람 눈으로만 보는 표 형식과, 후처리용 JSON 결과를 같이 남겨 두는 편이 좋습니다. 나중에 추세 비교나 예외 검토할 때 이 차이가 꽤 크게 느껴집니다.

    IMAGE_REF="registry.example.com/team/myapp:${GIT_COMMIT}"
    
    trivy image \
      --severity CRITICAL,HIGH \
      --exit-code 1 \
      --ignore-unfixed \
      --scanners vuln,secret \
      --format json \
      --output trivy-report.json \
      "$IMAGE_REF"
    
    trivy image \
      --severity CRITICAL,HIGH \
      --ignore-unfixed \
      --scanners vuln,secret \
      --format table \
      "$IMAGE_REF"

    --exit-code 1이 없으면 자동화는 반쪽입니다. 로그에 경고가 많아도 배포가 계속되면 운영팀은 곧 “보안 경고는 참고용”으로 받아들이게 됩니다. 그 순간부터 정책은 문서에만 남습니다. 그리고 JSON 결과를 별도 파일로 남기는 이유도 분명합니다. 사람이 읽기 쉬운 표와 기계가 다시 처리하기 쉬운 형식을 분리해야 나중에 재검증이 편해집니다.

    초기에 자주 보는 실패 모드는 “로컬은 어제 DB로 검사했고, CI는 오늘 DB로 검사했다”는 불일치입니다. 겉으로는 같은 이미지, 같은 명령처럼 보여도 결과가 달라집니다. 그래서 운영 문서에는 최소한 이미지 다이제스트, 스캔 시각, 스캐너 버전, DB 갱신 시각 네 가지를 같이 남겨 두는 게 좋습니다.

    컨테이너 이미지 스캔이 포함된 Kubernetes 보안 체크리스트 CI 흐름

    빌드, 스캔, 실패 기준 적용, 배포 차단으로 이어지는 파이프라인 흐름 예시입니다.

    실전 구현 2: Kubernetes 클러스터 안에서 재스캔 자동화

    CI 스캔은 배포 직전의 사진 한 장에 가깝습니다. 운영은 동영상에 더 가깝고요. 이미지 자체는 그대로인데 취약점 데이터베이스가 갱신되거나, 오래된 워크로드가 남아 있거나, 수동 롤백으로 이전 태그가 다시 떠 있을 수 있습니다. 그래서 운영 클러스터에서는 주기 재평가가 꼭 필요합니다.

    1. Helm으로 Trivy Operator 설치

    Trivy Operator는 Kubernetes 안에서 보안 리포트를 CRD 형태로 남겨 주기 때문에, 클러스터 관점 추적이 필요한 팀에는 꽤 잘 맞습니다. 별도 콘솔 없이도 kubectl로 상태를 확인할 수 있다는 점도 편합니다.

    helm repo add aqua https://aquasecurity.github.io/helm-charts/
    helm repo update
    helm upgrade --install trivy-operator aqua/trivy-operator \
      --namespace trivy-system \
      --create-namespace

    이 방식의 장점은 결과가 Kubernetes 리소스로 남는다는 점입니다. CI 로그는 흩어지기 쉽지만 리소스는 조회 경로가 고정됩니다. 운영팀과 플랫폼팀이 같은 화면을 본다는 점도 생각보다 큽니다. 인수인계할 때 특히 편하더라고요.

    2. 스캔 결과 조회

    설치 후에는 먼저 실제로 어떤 리포트가 생성되는지 확인하는 게 좋습니다. 환경과 활성화된 기능에 따라 보이는 리포트 종류가 조금씩 다를 수 있습니다.

    kubectl get vulnerabilityreports -A
    kubectl describe vulnerabilityreport -n default <report-name>

    결과를 읽을 때는 숫자보다 맥락을 먼저 봅니다. 저는 보통 세 가지를 바로 확인합니다. 첫째, 어떤 워크로드의 어떤 컨테이너인지. 둘째, 문제가 베이스 이미지 계층인지 애플리케이션 의존성인지. 셋째, 지금 당장 수정 가능한지입니다. 같은 High라도 베이스 이미지 교체로 끝나는 항목과 코드 호환성 검토가 필요한 라이브러리 문제는 대응 일정이 다릅니다.

    3. 실제 배포 중인 이미지 목록부터 확인

    보고서가 아니라 실제 실행 대상을 먼저 보는 습관이 중요합니다. 선언상 최신 이미지가 적혀 있어도, 운영 클러스터에는 예전 이미지가 남아 있는 경우가 꽤 있습니다.

    kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{range .spec.containers[*]}{.image}{" "}{end}{"\n"}{end}'

    이 명령은 단순해 보여도 중요합니다. Git 저장소 기준으로는 최신 태그를 본다고 해도, 실제 클러스터에선 예전 태그나 수동 롤백된 이미지가 남아 있을 수 있습니다. 스캔 체계가 배포 선언만 보고 실제 실행 대상을 안 보면, 보고서는 깨끗한데 운영은 더러운 상태가 됩니다.

    4. 사설 레지스트리 인증 문제가 있는지 먼저 분리 진단

    리포트가 안 생길 때는 스캐너 자체를 의심하기 전에 인증 흐름부터 확인하는 편이 빠릅니다. 여기서 시간 많이 쓰는 팀이 정말 많습니다.

    kubectl get events -A --sort-by=.lastTimestamp
    kubectl get secrets -n trivy-system
    kubectl describe serviceaccount -n trivy-system trivy-operator

    클러스터 내 스캐너가 리포트를 못 만드는 가장 흔한 이유는 인증 연결 누락입니다. 이미지 풀은 애플리케이션 서비스 계정이 잘하는데, 스캐너는 다른 서비스 계정이라 접근을 못 하는 경우가 잦습니다. 표면 증상은 “리포트가 안 생긴다”인데 근본 원인은 권한 경계가 다른 데 있습니다. 이건 설정 몇 줄 더 넣는다고 해결되지 않고, 누가 어느 레지스트리를 어떤 자격 증명으로 읽는지 모델을 분리해서 봐야 합니다.

    재현 가능한 시나리오로 보는 운영 포인트

    이런 시나리오는 실제 운영에서 자주 나옵니다. 내부 API 서비스가 registry.example.com/team/api:2026-09-01로 배포됐고, 배포 당시 CI 스캔은 통과했습니다. 며칠 뒤 베이스 이미지에 대한 신규 취약점 정보가 스캐너 DB에 반영됩니다. 코드도, 이미지 다이제스트도 바뀌지 않았는데 클러스터 리포트에는 새로운 Critical이 생길 수 있습니다. CI만 보는 팀은 “왜 갑자기 숫자가 늘었지?”에서 멈추고, 클러스터 재스캔이 있는 팀은 “지금 떠 있는 워크로드 중 어느 네임스페이스가 영향권인지”까지 바로 확인합니다.

    이 지점에서 중요한 것은 억울함을 줄이는 기록입니다. 결과가 바뀌었는데 이미지가 안 바뀌었을 때 스캐너 오작동으로 몰아가는 경우를 여러 번 봤습니다. 실제로는 취약점 DB 갱신이나 메타데이터 해석 차이인 경우가 많았습니다. 그래서 운영 기준에는 반드시 스캔 시각, DB 갱신 시각, 대상 다이제스트를 함께 남겨 두는 게 좋습니다.

    실제로 많이 막히는 문제와 해결법

    Private Registry 인증이 안 붙는 경우

    증상은 단순합니다. 리포트가 생성되지 않거나 이벤트에 인증 실패가 남습니다. 그런데 근본 원인은 보통 “앱 배포에 쓰는 이미지 풀 자격 증명”과 “스캐너가 읽는 자격 증명”을 같은 것으로 착각한 데 있습니다. 해결은 권한을 더 크게 주는 것이 아니라, 스캐너 전용 읽기 권한이 어디에 연결돼 있는지 확인하는 것입니다.

    취약점이 너무 많이 떠서 운영이 멈추는 경우

    초기 도입기에는 거의 반드시 겪습니다. 이때 모든 심각도를 한 번에 차단하면 실제로는 보안 수준이 올라가는 게 아니라 파이프라인이 멈춥니다. 저는 보통 Critical 즉시 차단, High는 예외 문서와 만료일 부여, Medium 이하는 추세 관찰로 시작합니다. 운영이 감당할 수 있는 속도로 올라가야 자동화도 습관으로 남습니다.

    태그만 보고 이미지를 추적하는 경우

    latest나 재사용되는 릴리스 태그는 감사 추적에 불리합니다. 스캔 결과가 어느 바이너리 집합을 가리키는지 불명확해집니다. 실제 배포 검증이나 사고 대응까지 생각하면 다이제스트 기준 저장이 맞습니다. 태그는 사람이 보기 좋고, 다이제스트는 시스템이 책임지기 좋습니다.

    운영 판단 없이 숫자만 보는 경우

    스캔 수치는 우선순위의 단서일 뿐, 우선순위 그 자체는 아닙니다. 외부에 노출된 서비스의 런타임 라이브러리 문제와 내부 배치 컨테이너의 개발 도구 패키지 문제는 같은 High여도 대응 순서가 달라집니다. 저는 아래 네 항목을 같이 봅니다.

    • 노출 면적: Ingress 뒤 서비스인지, 내부 잡인지 구분합니다.
    • 권한 수준: root 실행 여부, 추가 capability, privileged 여부를 확인합니다.
    • 수정 가능성: 베이스 이미지 교체로 끝나는지, 코드 수정과 검증이 필요한지 나눕니다.
    • 예외 만료: 지금 못 고치는 항목은 다시 볼 날짜가 없으면 영구 미해결이 됩니다.

    스캔이 클러스터 자원을 갉아먹는 경우

    운영 규모가 커지면 이것도 무시하기 어렵습니다. 큰 이미지가 많고 네임스페이스가 많으면 스캔 자체가 CPU, 메모리, 레지스트리 호출량을 소모합니다. 증상은 오퍼레이터가 느려지거나 리포트 생성이 밀리는 식으로 나타납니다. 재스캔은 많이 돌린다고 무조건 좋은 게 아니라, 업데이트 빈도, 클러스터 자원, 조치 가능한 인력에 맞춰야 합니다.

    클러스터 보안 관점의 Kubernetes 보안 체크리스트 리포트 구성도

    오퍼레이터가 워크로드를 스캔하고 리포트를 남기는 클러스터 내부 흐름 예시입니다.

    검증: 자동화가 제대로 작동했는지 확인하는 방법

    자동화 검증은 명령 성공 여부만 보면 부족합니다. 실패해야 할 때 진짜 실패하는지, 운영 중 판정 변화가 반영되는지, 팀원이 바뀌어도 같은 경로로 확인 가능한지가 핵심입니다. 제가 점검할 때는 다음 순서로 봅니다.

    1. 테스트용 이미지를 하나 빌드하고 로컬 결과를 저장합니다.
    2. 같은 이미지를 CI에서 같은 심각도 기준으로 스캔해 동일하게 실패 또는 통과하는지 확인합니다.
    3. 가능하면 배포 시점의 이미지 다이제스트를 기록해 로컬 결과와 CI 결과를 같은 대상으로 묶습니다.
    4. 클러스터에 배포한 뒤 vulnerabilityreports 리소스가 생성되는지 확인합니다.
    5. 사설 레지스트리 이미지는 인증 실패 없이 조회되는지 이벤트와 서비스 계정 기준으로 검증합니다.
    6. 예외 처리한 항목이 문서, CI 정책, 클러스터 결과에서 같은 의미로 반영되는지 비교합니다.

    환경에 따라 추가 리포트가 생성될 수 있으니, 아래처럼 어떤 CRD가 실제로 보이는지 함께 확인하면 좋습니다. 다만 활성화된 기능과 Trivy Operator 설정에 따라 결과는 달라질 수 있습니다.

    kubectl get vulnerabilityreports -A
    kubectl get configauditreports -A
    kubectl get clustercompliancereports -A

    중요한 것은 결과를 반복 조회 가능한 형태로 남기는 것입니다. 사람이 한 번 보고 지나가는 로그는 운영 자산이 아닙니다. 조회 경로가 고정되고 같은 명령으로 다시 확인할 수 있어야 팀 지식이 됩니다. 그리고 Pod Security Standards, RBAC, NetworkPolicy 관련 글도 함께 보면 Kubernetes 보안 체크리스트를 더 입체적으로 잡는 데 도움이 됩니다.

    Kubernetes 보안 체크리스트 결과 검증용 이미지 스캔 대시보드

    배포된 워크로드별 취약점 현황과 확인 포인트를 보여주는 결과 화면 예시입니다.

    FAQ: 현장에서 자주 받는 질문

    Q. 스캔 결과가 바뀌었는데 이미지는 안 바뀌었습니다.

    A. 충분히 가능한 일입니다. 가장 흔한 원인은 취약점 데이터베이스 갱신입니다. 그래서 스캔 시각, DB 갱신 시각, 이미지 다이제스트를 같이 기록해야 나중에 원인 추적이 쉬워집니다.

    Q. 모든 High를 바로 차단해야 하나요?

    A. 팀 상태에 따라 다릅니다. 초기 도입기라면 Critical 우선 차단, High는 예외와 만료일 관리가 현실적입니다. 이미 배포 품질 게이트가 정착된 조직이라면 High까지 차단해도 됩니다. 중요한 건 기준을 문서가 아니라 파이프라인에 넣는 것입니다.

    Q. Kubernetes 보안 체크리스트에서 이미지 스캔만 하면 충분한가요?

    A. 부족합니다. 이미지 스캔은 공급망과 배포 전 검증의 한 축일 뿐입니다. Pod Security Standards, RBAC, NetworkPolicy, Secret 관리, 감사 로그까지 함께 봐야 운영 리스크가 줄어듭니다. 다만 시작점으로는 이미지 스캔이 체감 효과가 빠른 편입니다.

    운영 기준은 이렇게 잡으시면 됩니다

    제가 오래 운영을 보면서 내린 기준은 단순합니다. 팀이 아직 수동 검토에 기대고 있다면 Trivy CLI로 CI 차단부터 붙이세요. 이 단계에서는 로컬과 CI 판정 일치, 다이제스트 기록, 예외 만료일 관리가 핵심입니다. 운영 클러스터가 여러 개이거나 롤백이 잦다면 클러스터 재스캔도 같이 가져가세요. 배포 당시엔 멀쩡했던 이미지가 나중에 문제로 바뀌는 순간을 잡아내려면 이 경로가 필요합니다.

    반대로 아직 팀이 결과 해석도 못 따라가는데 Admission 단계에서 전부 막는 것은 추천하지 않습니다. 그건 통제가 아니라 마찰이 됩니다. 이럴 땐 관찰 모드로 현재 상태 파악, 그다음 Critical 차단, 마지막으로 High까지 확대 순서가 덜 부서집니다. 규정이 엄격하고 변경 이력 감사가 중요하다면, 그때 배포 허가 단계 연계까지 가면 됩니다.

    제 권고를 한 줄로 줄이면 이렇습니다. 작게 시작하는 팀은 CI 스캔 + 실패 코드 적용, 운영 가시성이 필요한 팀은 클러스터 재스캔 추가, 강제 통제가 필요한 조직은 Admission 정책 연계. 이 순서가 실무에서 가장 덜 아프고, 결과가 남고, 다음 담당자도 이해하기 쉽습니다.

    본 글은 보안 학습과 본인이 관리하는 시스템의 방어를 위한 교육 목적입니다. 타인의 시스템에 대한 무단 접근·공격은 불법이며 법적 처벌 대상입니다.

    CI 스캔, 클러스터 재스캔, 예외 관리, 정책 연계를 한 장으로 정리한 요약 이미지입니다.

  • [보안] Trivy False Positive 줄이기: VEX 활용 및 설정 최적화 전략

    [보안] Trivy False Positive 줄이기: VEX 활용 및 설정 최적화 전략

    본 글은 보안 학습과 본인이 관리하는 시스템의 방어를 위한 교육 목적입니다. 타인의 시스템에 대한 무단 접근·공격은 불법이며 법적 처벌 대상입니다. 오늘 내용도 어디까지나 제가 관리하는 이미지와 파이프라인에서 Trivy False Positive와 취약점 오탐을 줄이기 위해 정리한 방식입니다.

    Trivy False Positive, 왜 이렇게 피곤할까요

    CI에 Trivy를 붙여두면 초반에는 꽤 속이 시원합니다. 그런데 조금 지나면 다른 문제가 생기더라고요. 실제 서비스 경로에서는 쓰이지 않는 라이브러리인데도 계속 빨간 줄이 뜨고, 배포판 패치가 아직 없는 이슈까지 매번 반복해서 올라옵니다. 바로 이런 지점에서 Trivy False Positive 대응 기준이 필요합니다.

    저도 처음에는 .trivyignore에 CVE를 바로 넣어버렸습니다. 그런데 나중에 보니 이 방식은 너무 거칠더라고요. 왜 무시했는지 근거가 남지 않고, 이미지 태그가 바뀌어도 예외가 그대로 살아남는 경우가 있었습니다. 실제 운영에서는 VEX(Vulnerability Exploitability eXchange)와 .trivyignore.yaml을 역할별로 나눠 쓰는 쪽이 훨씬 관리하기 편했습니다.

    Trivy False Positive를 VEX와 ignore 정책으로 정리하는 전체 흐름 이미지

    Trivy 스캔 결과가 바로 차단으로 이어지지 않고, VEX CSAF와 ignore 정책을 거쳐 검증된 이슈만 남는 흐름을 보여주는 이미지입니다.

    Trivy False Positive를 줄이기 위한 VEX CSAF 이해

    쉽게 말해 VEX는 "이 취약점이 존재하는 건 맞지만, 우리 제품이나 패키지 조합에서는 실제 영향이 없다"를 표준 형식으로 설명하는 문서입니다. 그냥 메모가 아니라 스캐너가 읽을 수 있는 구조화된 예외죠.

    Trivy는 로컬 VEX 파일로 CycloneDX, OpenVEX, CSAF를 지원합니다. 이 중 CSAF(Common Security Advisory Framework)는 SBOM 형식에 덜 종속적인 편이라 컨테이너 이미지, 파일시스템, 저장소, Kubernetes, SBOM 대상에 두루 적용하기 좋습니다. 여러 팀이 함께 보는 환경에서는 CSAF가 설명력도 좋고, 감사 대응 때도 근거를 정리하기 수월했습니다.

    여기서 중요한 포인트가 하나 있습니다. VEX는 단순 숨김이 아니라 왜 영향이 없는지를 기록하는 레이어라는 점입니다. 반대로 --ignore-unfixed나 --ignore-status는 결과를 빠르게 줄이는 운영 옵션에 가깝습니다. 둘을 같은 개념으로 다루면 나중에 기준이 꼬이기 쉽습니다.

    옵션부터 정리해보겠습니다: 무엇을 언제 쓰면 되나

    방법 적합한 상황 장점 주의점
    --ignore-unfixed 배포판 패치가 아직 없는 이슈를 우선 제외할 때 가장 빠르게 노이즈를 줄임 영향도 근거까지 대신해주지는 않습니다
    --ignore-status under_investigation, will_not_fix, fix_deferred 같은 상태 기반 필터가 필요할 때 공급자 메타데이터를 운영 정책에 반영하기 쉬움 패키지별 상세 설명은 약합니다
    .trivyignore.yaml 특정 CVE를 경로나 PURL 단위로 제한해서 무시할 때 만료일, statement 기록이 가능 실험적 기능이라 --ignorefile를 명시하는 편이 안전합니다
    --vex + CSAF 영향 없음의 근거를 문서화해야 할 때 감사 대응과 재사용에 유리함 문서 구조를 이해해야 하고 실험적 기능입니다

    제가 권장하는 순서는 이렇습니다. 1) unfixed 정리, 2) 상태 기반 필터, 3) 그래도 남는 케이스를 VEX CSAF로 설명, 4) 정말 로컬 예외만 필요한 건 .trivyignore.yaml. 이 순서를 지켜야 정책이 덜 엉킵니다.

    실전 1단계: 기준 스캔 결과를 먼저 저장합니다

    처음부터 예외부터 만들면 안 됩니다. 기준 보고서가 있어야 나중에 무엇이 줄었는지, 무엇이 실수로 사라졌는지 비교할 수 있거든요. 저는 보통 JSON과 테이블 출력을 둘 다 남깁니다.

    1. 대상 이미지를 취약점 스캐너만으로 먼저 스캔합니다.
    2. JSON 결과를 저장합니다.
    3. 사람이 확인하기 좋은 테이블 결과도 따로 봅니다.
    trivy image --scanners vuln --format json --output result.json debian:11.8
    trivy image --scanners vuln --severity HIGH,CRITICAL debian:11.8

    여기서 판단 기준은 단순 개수보다 같은 CVE가 어떤 패키지와 어떤 상태로 반복되는지입니다. 패치 버전이 비어 있고 배포판 공급자 이슈만 남아 있는 경우는 --ignore-unfixed 후보일 수 있고, 특정 패키지가 실제 실행 경로에 없으면 VEX 후보가 됩니다.

    제가 홈랩에서 자주 헷갈렸던 건 "발견됨"과 "영향 있음"을 같은 뜻으로 보는 부분이었습니다. 스캐너는 존재 여부를 잘 찾지만, 실행 경로나 제품 조합까지 자동으로 다 판단해주지는 않더라고요. 이 차이를 구분해야 보안 스캔 최적화가 제대로 됩니다.

    실전 2단계: VEX CSAF 문서로 오탐 근거를 남깁니다

    Trivy는 --vex /path/to/file 형식으로 로컬 VEX 파일을 읽습니다. CSAF에서는 product_tree에 패키지 식별자(PURL)를 두고, vulnerabilities 아래에서 어떤 제품이 known_not_affected인지 지정하면 됩니다.

    아래 예시는 Trivy 공식 문서 흐름을 바탕으로, Debian 패키지 하나를 영향 없음(not affected)으로 설명하는 구조입니다.

    {
      "document": {
        "category": "csaf_vex",
        "csaf_version": "2.0",
        "publisher": {
          "category": "vendor",
          "name": "Example Company ProductCERT",
          "namespace": "https://psirt.example.com"
        },
        "title": "Example VEX document",
        "tracking": {
          "id": "2024-EVD-UC-01-A-001",
          "status": "final",
          "version": "1",
          "initial_release_date": "2024-01-01T11:00:00.000Z",
          "current_release_date": "2024-01-01T11:00:00.000Z",
          "revision_history": [
            {
              "number": "1",
              "date": "2024-01-01T11:00:00.000Z",
              "summary": "Initial version."
            }
          ]
        }
      },
      "product_tree": {
        "branches": [
          {
            "category": "vendor",
            "name": "Debian",
            "branches": [
              {
                "category": "product_name",
                "name": "Database Libraries",
                "branches": [
                  {
                    "category": "product_version",
                    "name": "5.3",
                    "product": {
                      "name": "Database Libraries 5.3",
                      "product_id": "LIBDB-5328",
                      "product_identification_helper": {
                        "purl": "pkg:deb/debian/[email protected]%2Bdfsg1-0.8?arch=amd64&distro=debian-11.8"
                      }
                    }
                  }
                ]
              }
            ]
          }
        ]
      },
      "vulnerabilities": [
        {
          "cve": "CVE-2019-8457",
          "product_status": {
            "known_not_affected": [
              "LIBDB-5328"
            ]
          },
          "threats": [
            {
              "category": "impact",
              "details": "Vulnerable code not in execute path.",
              "product_ids": [
                "LIBDB-5328"
              ]
            }
          ]
        }
      ]
    }

    실제로 써보면 핵심은 두 가지입니다. PURL 매칭이 정확해야 하고, 영향 없음의 이유를 threats/details에 남겨야 나중에 팀원들이 봐도 납득이 됩니다. 그냥 CVE를 가리는 문서가 아니어야 하거든요.

    Trivy False Positive 대응을 위한 VEX CSAF 구조 다이어그램

    CSAF VEX에서 product_tree와 vulnerabilities가 어떻게 연결되는지, product_id와 PURL이 어떤 역할을 하는지 설명하는 이미지입니다.

    문서를 만들었다면 스캔에 바로 붙여봅니다.

    trivy image debian:11.8 --vex ./debian11.vex.csaf
    trivy image debian:11.8 --vex ./debian11.vex.csaf --show-suppressed

    --show-suppressed를 같이 쓰면 사라진 항목과 적용된 source를 확인하기 편합니다. 이 단계에서 CVE가 없어졌다고 바로 안심하지 마시고, 왜 suppress 되었는지를 꼭 보셔야 합니다. 저도 예전에 PURL 범위를 너무 넓게 잡아서 다른 버전까지 같이 가려버린 적이 있었는데, 그때 이 옵션이 꽤 도움이 됐습니다.

    실전 3단계: .trivyignore.yaml과 상태 필터를 같이 최적화합니다

    VEX만으로 모든 노이즈를 처리하려고 하면 오히려 관리가 무거워집니다. 반대로 모든 걸 ignore 파일에 몰아넣으면 근거가 약해지고요. 그래서 저는 용도를 분리합니다.

    • VEX CSAF: 영향 없음의 근거가 있는 취약점
    • .trivyignore.yaml: 특정 경로나 패키지에만 한정한 임시 예외
    • –ignore-unfixed: 배포판 패치가 아직 없는 이슈를 줄이는 운영 옵션
    • –ignore-status: 상태가 명확한 공급자 메타데이터 기반 정리

    예를 들어 임시 예외가 필요하면 이렇게 갑니다.

    vulnerabilities:
      - id: CVE-2022-40897
        paths:
          - "usr/local/lib/python3.9/site-packages/setuptools-58.1.0.dist-info/METADATA"
        statement: "Accept the risk during image transition"
      - id: CVE-2023-3817
        purls:
          - "pkg:deb/debian/libssl1.1"
      - id: CVE-2023-29491
        expired_at: 2026-09-30
        statement: "Temporary exception until base image refresh"

    그리고 스캔은 이렇게 묶습니다.

    trivy image \
      --ignore-unfixed \
      --ignore-status under_investigation,will_not_fix,fix_deferred \
      --ignorefile ./.trivyignore.yaml \
      --vex ./debian11.vex.csaf \
      --show-suppressed \
      debian:11.8

    여기서 중요한 포인트가 있습니다. .trivyignore.yaml은 실험적 기능이라 명시적으로 --ignorefile를 붙이는 습관이 좋습니다. 그리고 expired_at을 꼭 넣어두세요. 안 그러면 예외가 영구화되기 쉽습니다. 이런 게 나중에 제일 무섭더라고요.

    Trivy False Positive 트러블슈팅

    1. VEX를 넣었는데 결과가 안 줄어드는 경우

    대부분은 PURL 불일치입니다. 패키지 버전, distro qualifier, arch qualifier가 맞는지 다시 보셔야 합니다. 특히 Debian/Ubuntu 계열은 qualifier가 빠지면 다른 패키지 변형과 매칭이 달라질 수 있습니다.

    2. 너무 넓게 suppress 되는 경우

    PURL에서 버전을 뺀 예외는 여러 버전에 넓게 적용될 수 있습니다. 편할 때도 있지만 운영 환경에서는 위험할 때가 더 많습니다. 저는 배포 이미지에는 가능하면 버전까지 넣는 편입니다.

    3. ignore 파일과 VEX가 섞여서 이유를 모르겠는 경우

    --show-suppressed를 켜면 어느 소스에서 suppress 되었는지 확인하기 쉬워집니다. 테이블 출력의 source를 먼저 보고, JSON 보고서를 쓰는 파이프라인이면 수정된 findings도 같이 보관하는 편이 좋습니다.

    4. unfixed를 너무 믿는 경우

    --ignore-unfixed는 노이즈를 줄이는 데는 좋지만, 리스크가 사라진다는 뜻은 아닙니다. 배포판 패치가 아직 없을 뿐이고, 실제 영향 분석은 여전히 필요합니다. 특히 외부 공개 서비스용 이미지는 여기서 한 번 더 눈으로 확인하는 게 안전합니다.

    Trivy False Positive 검증을 위해 suppressed 결과를 확인하는 터미널 이미지

    스캔 결과에서 suppressed vulnerabilities와 source 정보가 보여서 어떤 규칙이 적용됐는지 검증하는 장면의 이미지입니다.

    검증은 이렇게 보시면 됩니다

    완성된 결과를 볼 때는 총 개수만 보면 안 됩니다. 저는 아래 순서로 읽습니다.

    1. 기준 보고서 대비 어떤 CVE가 빠졌는지 확인합니다.
    2. 빠진 이유가 VEX인지 ignorefile인지 구분합니다.
    3. 남아 있는 HIGH/CRITICAL 중 실제 조치 대상만 추립니다.
    4. 예외 문서의 만료일과 변경 이력을 같이 봅니다.

    판단 기준도 명확해야 합니다. 같은 CVE가 결과에서 사라졌다고 끝내면 안 되고, suppressed source가 기대한 정책인지를 확인해야 합니다. VEX로 처리하려던 항목이 ignorefile에서 먼저 가려졌다면 정책 계층이 뒤집힌 상태거든요.

    제가 직접 해보니 가장 안정적인 검증 방식은 기준 JSON 보고서 + suppress 확인용 테이블 출력을 함께 남기는 방식이었습니다. 사람은 테이블로 읽고, 파이프라인 비교는 JSON으로 하는 거죠. 이거 진짜 편하더라고요.

    운영 추천안: 이런 경우엔 이렇게 가시면 됩니다

    Trivy False Positive 줄이기 전략을 비교한 요약 인포그래픽

    오탐 성격에 따라 어떤 옵션을 먼저 선택해야 하는지 한눈에 보여주는 요약 이미지입니다.

    • 배포판 패치가 아직 없는 경우: --ignore-unfixed를 먼저 적용하고, 주기적으로 재검토하세요.
    • 공급자 상태 정보로 충분한 경우: --ignore-status로 운영 정책을 단순화하세요.
    • 영향 없음의 근거를 남겨야 하는 경우: VEX CSAF로 문서화하세요. 감사 대응이나 팀 간 공유에 특히 강합니다.
    • 특정 파일 경로나 패키지에만 임시 예외가 필요한 경우: .trivyignore.yaml에 expired_at과 statement를 꼭 넣으세요.

    제 추천은 분명합니다. 조직 차원의 표준 예외는 VEX CSAF, 팀 로컬의 짧은 유예는 .trivyignore.yaml입니다. 여기에 --ignore-unfixed와 --ignore-status를 운영 보조 수단으로 얹는 방식이 가장 덜 망가집니다.

    다음 글에서는 Trivy 결과를 CI에서 fail-open / fail-close로 나누는 기준도 다뤄보겠습니다. 이전 글에서 다뤘던 컨테이너 보안 글과 함께 보면 더 잘 연결됩니다.

    FAQ처럼 자주 받는 질문 몇 가지

    VEX만 쓰면 ignore 파일은 필요 없나요

    아닙니다. VEX는 설명 가능한 예외에 강하고, ignore 파일은 로컬 임시 예외에 강합니다. 둘의 목적이 다릅니다.

    CSAF와 OpenVEX 중 무엇을 먼저 볼까요

    팀이 작고 단순하면 OpenVEX도 좋습니다. 다만 여러 대상과 제품 구조를 설명해야 하면 CSAF가 더 잘 맞는 경우가 많습니다.

    Trivy False Positive를 완전히 없앨 수 있나요

    완전히 없애는 건 어렵습니다. 대신 오탐을 문서화 가능한 예외와 정말 처리해야 할 취약점으로 나누는 건 충분히 가능합니다. 그 차이가 운영 품질을 바꿉니다.

    본 글은 보안 학습과 본인이 관리하는 시스템의 방어를 위한 교육 목적입니다. 실제 운영 환경에서는 변경 이력과 승인 절차를 함께 관리하시길 권합니다.

  • [보안] Trivy 성능 최적화로 대규모 컨테이너 스캔 가속하기

    [보안] Trivy 성능 최적화로 대규모 컨테이너 스캔 가속하기

    [보안] Trivy 성능 최적화로 대규모 컨테이너 스캔 가속하기

    대규모 컨테이너 환경을 운영하다 보면 Trivy 성능 최적화가 생각보다 빨리 중요한 과제가 됩니다. 처음에는 이미지 하나, 둘 스캔할 때는 괜찮거든요. 근데 마이크로서비스가 늘고, CI/CD 파이프라인마다 이미지 스캔이 붙기 시작하면 갑자기 빌드 시간이 확 늘어납니다. 저도 홈랩이랑 실무 환경에서 비슷한 상황을 꽤 겪었는데요. 처음엔 “보안 검사니까 원래 느린가 보다” 하고 넘겼다가, 나중엔 배포 병목이 되더라고요. 특히 컨테이너 보안 정책과 이미지 스캔을 체계적으로 관리하려다 보니, 취약점 관리의 효율성까지 함께 챙겨야 하는 팀들이라면 이 부분을 그냥 두면 안 됩니다.

    오늘은 제가 직접 적용하면서 효과를 봤던 방향 위주로, Trivy를 더 빠르게 돌리는 방법을 정리해보겠습니다. 핵심은 무작정 옵션을 많이 붙이는 게 아니라, 어디서 시간이 쓰이는지 구간을 나눠서 보는 것입니다. 데이터베이스(DB) 다운로드, 레이어 분석, 불필요한 스캐너 실행, CI/CD 캐시 미활용, 중복 스캔. 보통 여기서 시간 대부분이 나갑니다.

    대규모 컨테이너 환경에서 Trivy 스캔이 어디서 느려지는지 한눈에 보여주는 개요 이미지입니다.

    1. 왜 Trivy 성능 최적화가 필요한가

    쉽게 말해 Trivy는 취약점 데이터베이스를 내려받고, 이미지 레이어를 분석하고, 패키지 정보를 대조해서 취약점을 찾는 도구입니다. 이 과정 자체는 합리적이죠. 문제는 서비스 수가 많아질수록 같은 작업을 너무 자주 반복한다는 데 있더라고요.

    • 같은 베이스 이미지를 여러 서비스가 반복 스캔
    • CI/CD 러너(runner, 빌드 실행기)가 매번 새로 떠서 캐시가 없음
    • 필요 없는 스캐너까지 같이 실행
    • 모든 브랜치에서 동일한 깊이로 검사

    저도 처음엔 각 저장소마다 Trivy를 독립 실행하게 해뒀었는데, 실제로 써보니까 DB 다운로드와 캐시 미스(cache miss) 때문에 시간이 꽤 날아가더라고요. 특히 ephemeral runner(에페메럴 러너, 작업 후 사라지는 실행 환경) 쓰는 곳에서는 더 체감이 큽니다.

    2. Trivy가 느려지는 구간을 먼저 이해해보죠

    여기서 중요한 포인트가 있습니다. Trivy를 빠르게 만들려면 먼저 느린 구간을 분리해서 봐야 하더라고요. 보통 아래 네 가지입니다.

    1. DB 준비 시간: 취약점 데이터베이스를 매번 새로 가져오면 느립니다.
    2. 이미지 분석 시간: 레이어가 많거나 패키지 종류가 다양하면 분석 시간이 늘어납니다.
    3. 스캐너 범위: vuln(취약점), secret(시크릿), config(설정)까지 한 번에 다 돌리면 당연히 무거워집니다.
    4. 중복 실행: 같은 이미지를 브랜치마다, 잡(job)마다 반복 스캔하면 비효율이 크더라고요.

    그래서 제가 권하는 접근은 이렇습니다. “DB 캐시 최적화 → 스캔 범위 축소 → 실행 구조 개선 → 서버 모드 검토” 순서로 보시면 됩니다. 이 순서가 좋은 이유는, 앞 단계일수록 적용 난이도 대비 체감 효과가 큰 경우가 많기 때문입니다.

    3. 실전 구현 1: DB 캐시와 캐시 디렉터리부터 잡습니다

    Trivy 성능 최적화에서 제일 먼저 손볼 건 캐시입니다. 이건 진짜 기본인데, 의외로 놓치는 팀이 많아요. 러너가 매번 깨끗한 상태로 시작하면 Trivy 입장에서는 매번 처음부터 다시 해야 하거든요.

    제가 직접 해보니 가장 단순하고 효과적인 방법은 캐시 디렉터리(cache directory)를 고정하고, CI 캐시와 연결하는 거더라고요.

    export TRIVY_CACHE_DIR=.trivycache
    trivy image --cache-dir .trivycache my-registry.example.com/sample-app:latest

    파이프라인에서는 보통 이 디렉터리를 캐시 대상으로 잡습니다. 예를 들면 이런 식이죠.

    steps:
      - name: Restore Trivy cache
        uses: actions/cache@v4
        with:
          path: .trivycache
          key: trivy-cache-${{ runner.os }}-${{ hashFiles('**/Dockerfile') }}
          restore-keys: |
            trivy-cache-${{ runner.os }}-
    
      - name: Scan image
        run: |
          export TRIVY_CACHE_DIR=.trivycache
          trivy image --cache-dir .trivycache my-image:latest

    여기서 팁 하나 더 있습니다. 빌드 잡과 스캔 잡을 분리했다면, 스캔 직전에 DB를 미리 받아두는 방식도 꽤 유용하더라고요.

    trivy image --download-db-only
    trivy image my-image:latest

    이렇게 해두면 네트워크 상태가 애매한 환경에서 시간 분산이 되더라고요. “왜 어떤 날은 빠르고 어떤 날은 느리지?” 싶은 분들, 사실 DB 다운로드 타이밍 영향이 큽니다.

    4. 실전 구현 2: 필요한 스캐너만 돌리고 스캔 범위를 줄입니다

    Trivy는 기능이 많은 만큼, 아무 생각 없이 다 켜면 무거워집니다. 물론 보안 관점에서는 이것저것 다 보고 싶죠. 저도 처음엔 그랬습니다. 근데 CI/CD 전체 시간을 생각하면 목적별 분리가 필요하더라고요.

    예를 들어 PR 단계에서는 취약점(vulnerability, 취약성) 위주로 빠르게 보고, 야간 배치나 메인 브랜치에서는 secret(시크릿, 노출된 비밀정보)나 config(설정 검사)까지 확장하는 식입니다.

    trivy image \
      --scanners vuln \
      --severity HIGH,CRITICAL \
      --ignore-unfixed \
      my-image:latest

    이렇게 하면 노이즈가 꽤 줄어듭니다. 특히 취약점 관리를 실무에서 운영할 때 당장 조치 가능한 항목 위주로 보는 데 도움이 되더라고요.

    또 하나 많이 쓰는 방법이 skip 옵션입니다. 파일 시스템(fs, 파일 시스템) 스캔이나 저장소(repo, 리포지토리) 스캔에서는 불필요한 디렉터리를 건너뛰는 것만으로도 시간이 줄 수 있습니다.

    trivy fs \
      --skip-dirs node_modules \
      --skip-dirs vendor \
      --skip-files '*.log' \
      .

    물론 무턱대고 제외하면 안 돼요. 여기서 중요한 포인트! 실제 배포 산출물에 포함되는 경로인지 먼저 확인해야 합니다. 예전에 제가 빌드 컨텍스트(build context)를 제대로 안 보고 디렉터리 제외했다가, 필요한 패키지 경로까지 날려서 결과가 이상하게 나온 적이 있었어요. 삽질 좀 했습니다 ㅎㅎ

    Trivy 성능 최적화를 위한 CI/CD 캐시 및 스캐너 분리 구성 이미지

    CI 파이프라인에서 Trivy 캐시, 스캐너 범위, 스캔 경로를 어떻게 나누는지 설명하는 구성 이미지입니다.

    5. 실전 구현 3: 서버 모드로 중복 작업을 줄입니다

    이미지가 많고 팀이 여러 파이프라인에서 Trivy를 동시에 쓰는 환경이라면, server mode(서버 모드)도 검토할 만하더라고요. 이 방식은 공용 Trivy 서버가 DB와 캐시를 들고 있고, 클라이언트가 거기로 요청을 보내는 구조입니다. 제가 실제로 여러 서비스 이미지를 반복 검사하던 환경에서 이 구조를 써보니까, 매번 러너마다 준비 작업을 다시 하는 비용이 줄어서 운영이 한결 편해졌어요.

    trivy server --listen 0.0.0.0:4954
    trivy image --server http://trivy-server.internal:4954 my-image:latest

    이 구조의 장점은 명확합니다.

    • DB와 캐시를 중앙에서 관리 가능
    • 여러 CI/CD 잡이 같은 준비 상태를 재활용
    • 에페메럴 러너 환경에서도 일관된 속도 확보

    다만 모든 환경에 정답은 아닙니다. 소규모 팀이나 저장소 수가 적은 경우에는 오히려 운영 포인트만 늘어날 수 있거든요. 그래서 저는 보통 아래 기준으로 판단합니다.

    환경 추천 방식 이유
    소규모 단일 저장소 로컬 캐시 중심 구성이 단순하고 관리가 쉽습니다
    여러 서비스가 있는 CI/CD 공용 캐시 + 잡 분리 중복 다운로드를 줄이기 좋습니다
    대규모 멀티 프로젝트 Trivy 서버 모드 중앙 캐시 재사용 효과가 큽니다

    6. ⚠️ 실제로 자주 만나는 문제와 트러블슈팅

    이 섹션은 정말 중요해요. 문서만 보면 다 쉬워 보이는데, 실제 운영에서는 예상 밖의 병목이 꼭 나옵니다. 제가 겪었던 것과 많이 상담받았던 케이스를 정리해보겠습니다.

    6-1. 캐시를 붙였는데도 체감이 없다

    대부분은 캐시 경로가 매 실행마다 달라지거나, 러너가 캐시 복원을 제대로 못 하고 있는 경우였어요. CI 로그에서 실제로 캐시 restore가 성공했는지 꼭 보셔야 합니다. 경로만 같다고 끝이 아니더라고요.

    6-2. secret 스캔 때문에 예상보다 오래 걸린다

    이건 자주 있습니다. secret 스캔은 용도가 분명하지만, 모든 PR 단계에서 항상 필요한 건 아닐 수 있어요. 빠른 피드백이 우선인 구간과 정밀 검사가 필요한 구간을 분리해보세요.

    6-3. 같은 베이스 이미지를 계속 스캔한다

    이 경우는 이미지 계층(layer) 구조를 먼저 보셔야 합니다. 베이스 이미지가 공통이라면 변경 없는 브랜치에서 풀스캔을 반복하는 구조가 맞는지 점검해보는 게 좋아요. 실제로 써보니까 애플리케이션 변경이 거의 없는데도 이미지만 다시 빌드해서 전체 스캔하는 경우가 많았거든요.

    6-4. 네트워크가 느린 날만 유독 전체 시간이 튄다

    DB 다운로드와 외부 레지스트리 접근 시간이 흔들리면 전체 결과도 흔들린답니다. 이럴 때는 사전 DB 준비, 사내 레지스트리 미러, 스캔 전용 실행 구간 분리가 도움이 돼요.

    • 캐시가 복원되는지 로그로 확인
    • 스캐너 범위를 목적별로 나눌 것
    • 불필요한 디렉터리 제외는 실제 배포 경로 기준으로 검토
    • 중앙 캐시가 필요할 정도인지 운영 복잡도와 함께 판단

    7. 결과 검증: 빨라졌는지 어떻게 확인할까

    최적화는 느낌으로 하면 안 돼요. “조금 빨라진 것 같아요”는 운영에서 별 의미가 없거든요. 최소한 아래 정도는 비교해보시는 걸 권합니다.

    1. DB 다운로드 포함/미포함 전체 소요 시간
    2. PR 스캔과 메인 브랜치 스캔의 평균 실행 시간
    3. 이미지 크기별 스캔 편차
    4. 동시 실행 시 대기 시간 증가 여부

    예를 들어 간단하게는 CI 로그에서 전후 시간을 모아 비교할 수 있어요. 더 여유가 있으면 잡 실행 시간 추이를 대시보드로 모아보세요. 이거 진짜 편하더라고요. 어느 날 갑자기 느려졌을 때 원인 찾는 속도가 달라집니다.

    time trivy image --cache-dir .trivycache my-image:latest

    결과를 볼 때는 단순 평균보다 최악의 경우(worst case)도 같이 보셔야 합니다. 실무에서는 평소 2분이어도 가끔 9분 튀는 게 더 문제거든요. 특히 배포 직전 파이프라인에서 그러면 팀 전체가 멈춘 느낌이 납니다.

    Trivy 성능 최적화 적용 전후 스캔 성능 결과 대시보드

    Trivy 최적화 적용 전후의 스캔 시간, 캐시 적중률, 병목 감소를 시각화한 결과 이미지입니다.

    8. 정리: 상황별 추천 전략과 다음 단계

    지금까지 내용을 정리하면, Trivy 성능 최적화는 거창한 튜닝보다 기본기에서 시작돼요. 캐시를 제대로 쓰고, 필요한 스캐너만 돌리고, 중복 작업을 줄이는 것. 이 세 가지만 해도 체감 차이가 꽤 큽니다. 제가 직접 해보니 특히 CI/CD에서 빌드 대기열이 긴 팀일수록 효과가 빨리 보였어요.

    상황별로 간단히 추천하면 이렇습니다.

    상황 우선 적용할 것 비고
    러너가 매번 새로 생성됨 캐시 디렉터리 고정 + CI 캐시 복원 가장 먼저 볼 포인트입니다
    PR이 너무 느림 severity, scanners 범위 축소 빠른 피드백용 정책 분리
    서비스 수가 많음 공용 캐시 또는 서버 모드 중복 작업 절감 효과가 큽니다
    노이즈가 많음 ignore-unfixed와 정책 재정비 속도와 운영 피로를 함께 줄입니다

    혹시 지금 Trivy를 돌리고 있는데 “느리긴 한데 어디서부터 손대야 할지 모르겠다” 싶으시면, 오늘 글 기준으로는 1) 캐시 확인, 2) 스캐너 범위 분리, 3) 중복 스캔 구조 점검 이 세 가지부터 해보시면 돼요. 이 순서가 실패 확률도 낮고, 결과 확인도 쉽습니다.

    다음 글에서는 CI/CD 파이프라인에서 이미지 스캔 정책을 단계별로 분리하는 방법도 다뤄볼 예정입니다. 이전 글에서 다뤘던 레지스트리 구조 설계와 함께 보시면 훨씬 이해가 쉬우실 거예요.

    Trivy 성능 최적화 핵심 전략을 정리한 요약 인포그래픽

    Trivy 성능 최적화 핵심 포인트를 빠르게 복습할 수 있도록 정리한 요약 인포그래픽입니다.

    9. 자주 묻는 질문

    Q1. Trivy 성능 최적화는 캐시만 잘 써도 충분한가요?

    아닙니다. 캐시는 시작점이에요. 대규모 환경에서는 스캔 범위 조정과 중복 실행 구조 개선까지 같이 봐야 합니다.

    Q2. 모든 CI 단계에서 secret 스캔을 돌려야 할까요?

    반드시 그럴 필요는 없어요. 보안 요구사항에 따라 다르지만, 빠른 피드백이 중요한 구간과 정밀 검사가 필요한 구간을 나누는 편이 현실적입니다.

    Q3. 서버 모드는 언제 고려하면 좋을까요?

    여러 프로젝트가 동일한 취약점 데이터와 캐시를 반복해서 쓰는 환경이라면 검토 가치가 크더라고요. 반대로 작은 팀이라면 운영 복잡도가 더 클 수도 있습니다.

    결국 핵심은 하나입니다. 보안을 포기하지 않으면서도 배포 속도를 지키는 구조를 만드는 것. 이 균형을 맞추는 게 인프라 엔지니어 일의 재미이기도 하죠.

  • [보안] Trivy vs Clair: 우리 팀에 맞는 컨테이너 이미지 보안 스캐너는?

    [보안] Trivy vs Clair: 우리 팀에 맞는 컨테이너 이미지 보안 스캐너는?

    [보안] Trivy vs Clair: 우리 팀에 맞는 컨테이너 이미지 보안 스캐너는?

    컨테이너 이미지 보안 이야기를 하면 결국 한 번은 취약점 스캐너(vulnerability scanner, 알려진 보안 취약점을 찾아주는 도구) 선택 문제로 돌아오게 됩니다. 특히 팀에서 CI/CD를 굴리기 시작하면, 이미지 빌드까지는 잘 되는데 배포 직전에 “이 이미지를 그냥 올려도 되나?” 하는 순간이 꼭 오거든요. 저도 홈랩하고 회사 환경에서 이것저것 붙여보면서 삽질을 꽤 했습니다. 처음엔 스캐너면 다 비슷한 거 아닌가 싶었는데, 실제로 써보니까 Trivy와 Clair는 접근 방식이 꽤 다르더라고요. 이번 글에서는 컨테이너 이미지 보안 관점에서 두 도구를 비교하고, 어떤 팀에 어떤 선택이 맞는지 경험 기반으로 정리해보겠습니다.

    컨테이너 이미지 보안 아키텍처에서 Trivy와 Clair의 역할을 보여주는 개요 이미지

    Trivy와 Clair가 개발 파이프라인과 레지스트리 사이에서 어디에 붙는지 한눈에 보여주는 개요 이미지입니다.

    1. 왜 컨테이너 이미지 보안이 먼저냐

    쉽게 말해 컨테이너 이미지는 애플리케이션 실행에 필요한 파일 묶음입니다. 여기 안에는 애플리케이션 코드만 들어있는 게 아니라, 베이스 이미지(base image, 실행 기반 운영체제 레이어), 패키지 관리자 설치물, 언어별 라이브러리까지 같이 들어가죠. 그러다 보니 한 줄의 애플리케이션 코드보다 이미지 안에 포함된 오래된 패키지가 더 큰 리스크인 경우도 많습니다.

    제가 직접 해보니 현업에서 자주 놓치는 포인트는 딱 두 가지였습니다. 첫째, 개발팀은 애플리케이션 dependency(디펜던시, 의존 패키지)는 보는데 OS 패키지는 잘 안 봅니다. 둘째, 운영팀은 이미지 태그만 보고 안심하는 경우가 있는데, 같은 애플리케이션이어도 베이스 이미지가 달라지면 결과가 완전히 달라집니다. 그래서 컨테이너 이미지 보안은 배포 뒤가 아니라 빌드 직후부터 챙겨야 하거든요.

    2. Trivy와 Clair, 이 두 스캐너는 뭐가 다를까

    Trivy는 실행이 빠르고 진입장벽이 낮은 올인원 성격의 스캐너에 가깝습니다. 공식 문서 기준으로 컨테이너 이미지뿐 아니라 파일시스템, Git 리포지토리, Kubernetes, SBOM(Software Bill of Materials, 소프트웨어 구성 명세)까지 다뤄요. 취약점뿐 아니라 misconfiguration(미스컨피규레이션, 잘못된 보안 설정)과 secret(시크릿, 노출된 비밀정보) 검사도 같이 가져갈 수 있어서 DevSecOps 도입 초반에 특히 편합니다.

    Clair는 조금 결이 다릅니다. 컨테이너 취약점 정적 분석(static analysis, 실행 없이 구조와 패키지를 분석하는 방식)에 집중한 서비스형 구조에 더 가깝습니다. 공식 문서와 프로젝트 설명을 보면 API 기반으로 이미지를 인덱싱(indexing, 패키지 정보를 수집해 저장)하고, 이후 알려진 취약점과 매칭하는 흐름이 핵심입니다. 그래서 단일 CLI를 바로 실행하는 느낌보다는, 레지스트리나 내부 플랫폼에 연결해서 쓰는 그림이 더 자연스럽습니다.

    여기서 중요한 포인트! 둘 중 누가 절대적으로 더 좋다고 보기는 어렵습니다. 우리 팀의 운영 방식이 더 중요합니다.

    3. Trivy vs Clair: 우리 팀에 중요한 비교 포인트

    비교 항목 Trivy Clair
    첫 도입 난이도 낮음. CLI 중심으로 바로 시작 가능 상대적으로 높음. 서비스와 설정 이해 필요
    운영 형태 로컬 CLI, CI, 서버 모드 모두 가능 API 기반 서비스 운용에 더 적합
    주요 강점 취약점, misconfiguration, secret, SBOM 범위가 넓음 컨테이너 이미지 취약점 분석 파이프라인에 집중
    데이터베이스 운영 자체 DB를 받아 캐시하며 사용 PostgreSQL 기반 영속 저장 구조 사용
    잘 맞는 팀 작은 팀, 플랫폼 초기 팀, 빠른 CI 통합이 필요한 팀 내부 보안 플랫폼/레지스트리 통합을 운영하는 팀
    학습 곡선 완만함 구조 이해가 필요해서 가파른 편

    실제로 써보니까 Trivy는 시작이 쉽고 범용적이었습니다. 반면 Clair는 아키텍처 안에 넣었을 때 힘을 발휘하더라고요. 그래서 팀의 질문도 달라야 합니다. “무엇이 더 강력한가”보다 “우리는 CLI 중심으로 갈 건가, 서비스 중심으로 갈 건가”가 먼저입니다.

    4. Trivy로 바로 시작하는 실전 구현

    처음 도입하는 팀이라면 저는 거의 무조건 Trivy부터 붙여봅니다. 이유가 단순합니다. 빨리 결과가 나오고, 개발팀이 거부감 없이 받아들이기 쉽거든요.

    1. 스캔 대상 이미지를 하나 정합니다. 예시는 nginx:latest처럼 공개 이미지로 해도 됩니다.
    2. 로컬에서 한 번 돌려보고 결과 형식을 익힙니다.
    3. 그다음 CI에 붙여서 High/Critical 기준으로 fail 시킵니다.
    docker run --rm \
      -v /var/run/docker.sock:/var/run/docker.sock \
      -v $HOME/.cache/trivy:/root/.cache/ \
      aquasec/trivy image nginx:latest

    이 명령은 Docker 소켓을 마운트해서 로컬 이미지 또는 엔진에서 접근 가능한 이미지를 검사합니다. 처음엔 DB를 내려받는 시간이 조금 걸릴 수 있어요. 저도 처음엔 “왜 이렇게 느리지?” 했었는데, 캐시가 잡히고 나면 훨씬 빨라지더라고요.

    trivy image --severity HIGH,CRITICAL --exit-code 1 nginx:latest

    이건 CI에 넣기 좋은 형태입니다. 지정한 심각도(severity, 위험도) 이상이 나오면 종료 코드를 1로 반환하니까 파이프라인에서 바로 실패 처리할 수 있어요. 단순하지만 실무에서는 이게 제일 중요합니다. 개발팀이 결과를 보는 것보다, 기준 이상이면 못 지나가게 막는 것이 더 효과가 크거든요.

    trivy image --format spdx-json --output sbom.json nginx:latest

    SBOM 생성도 됩니다. 여기서 DevSecOps 흐름이 한 단계 올라갑니다. 단순히 취약점 숫자만 보는 게 아니라, 이미지 안에 무엇이 들어있는지 자산 관리를 같이 할 수 있거든요.

    컨테이너 이미지 보안을 위해 Trivy 스캔이 CI에서 동작하는 모습

    Trivy를 CI에 붙여서 이미지 스캔이 자동으로 돌아가고, 기준 이상 취약점에서 빌드가 멈추는 흐름을 설명하는 이미지입니다.

    Trivy를 추천하는 경우

    • 빠르게 시작해야 하는 팀
    • 보안 전담 인력이 많지 않은 팀
    • 취약점 스캔 외에 misconfiguration, secret 검사까지 같이 보고 싶은 팀
    • 개발자 로컬 환경과 CI에서 같은 도구를 쓰고 싶은 팀

    5. Clair는 언제 빛나나: 서비스형 운영이 필요할 때

    Clair는 “개발자가 CLI 하나 실행하는 도구”라기보다, 내부 서비스처럼 다루는 쪽이 더 맞습니다. 공식 문서 기준으로 Clair는 여러 모드(indexer, matcher, notifier, combo mode)를 가지며, PostgreSQL을 사용해요. 즉, 단순 바이너리 하나보다 스캐닝 서비스를 운영한다는 관점이 필요합니다.

    저도 처음엔 이게 뭔가 싶었는데, 구조를 이해하고 나니 왜 이런 설계를 했는지 보이더라고요. 이미지 메타데이터를 인덱싱하고, 취약점 데이터와 매칭하는 과정을 API 기반으로 분리하면 레지스트리나 사내 플랫폼에 붙이기 좋습니다. 여러 팀이 공통 서비스로 재사용하기도 좋고요.

    1. PostgreSQL을 준비합니다.
    2. Clair 설정 파일을 작성합니다.
    3. combo mode로 먼저 띄워봅니다.
    4. clairctl로 이미지를 제출해서 결과를 확인합니다.
    http_listen_addr: :6060
    introspection_addr: :8089
    log_level: info
    indexer:
      connstring: host=clair-db port=5432 user=clair dbname=clair sslmode=disable
      migrations: true
    matcher:
      connstring: host=clair-db port=5432 user=clair dbname=clair sslmode=disable
      migrations: true
    notifier:
      connstring: host=clair-db port=5432 user=clair dbname=clair sslmode=disable
      migrations: true

    이 예시는 개념 설명용 최소 구성입니다. 실서비스에서는 인증, 네트워크, 스토리지, 운영 정책을 따로 잡아야 합니다. Clair 쪽은 특히 “일단 설치”보다 “운영 구조를 어떻게 가져갈지”를 먼저 생각해야 해요.

    CLAIR_MODE=combo
    CLAIR_CONF=/etc/clair/config.yaml
    clair -conf /etc/clair/config.yaml -mode combo
    clairctl report --host http://localhost:6060 ubuntu:focal

    Clair는 이렇게 API 서비스와 클라이언트 도구가 분리된 흐름을 이해하면 훨씬 덜 헷갈려요. 반대로 이 구조가 부담스럽다면, 억지로 Clair부터 시작할 필요는 없다고 봅니다.

    컨테이너 이미지 보안에서 Clair의 indexer matcher PostgreSQL 구조를 설명하는 이미지

    Clair가 단순 CLI가 아니라 indexer, matcher, 데이터 저장소가 분리된 구조라는 점을 보여주는 아키텍처 이미지입니다.

    6. ⚠️ 실제로 겪었던 주의사항과 트러블슈팅

    첫 번째, 결과 숫자만 보고 놀라지 마세요. 취약점 스캐너는 패키지 소스와 배포판 메타데이터를 해석하는 방식에 따라 결과가 달라질 수 있습니다. 같은 이미지라도 스캐너마다 개수가 다르게 보일 수 있거든요. 저도 초반엔 “왜 A는 12개인데 B는 20개지?” 하면서 한참 봤습니다. 이건 도구가 틀렸다기보다 탐지 기준과 데이터 소스 차이를 봐야 하는 경우가 많습니다.

    두 번째, 스캔 시점을 통일해야 합니다. 개발자 로컬, CI, 레지스트리, 운영 배포 후 스캔이 다 따로 놀면 팀이 금방 지쳐요. 제 경험상 가장 덜 싸우는 방식은 이렇습니다.

    • 개발자 로컬: 빠른 사전 확인
    • CI: 정책 강제
    • 레지스트리 또는 플랫폼: 중앙 모니터링

    세 번째, False Positive(오탐)와 False Negative(미탐)를 운영 규칙으로 다뤄야 합니다. 취약점 스캐너는 만능이 아니거든요. 패키지가 실제로 exploitable(익스플로이터블, 실제 공격 가능 상태)한지와는 또 다른 문제예요. 그래서 보안팀이나 플랫폼팀이 예외 처리 기준을 문서화해야 합니다.

    네 번째, 베이스 이미지 갱신 주기를 같이 가져가야 합니다. 스캐너만 붙여놓고 이미지가 매달 그대로면 결국 경고판만 늘어나요. 알림은 쌓이는데 고쳐지진 않죠. 이거 진짜 많이 봤습니다 ㅎㅎ

    7. 검증: 어떤 결과가 나오면 잘 붙은 걸까

    스캐너 도입이 끝났다고 판단하는 기준은 단순 설치 여부가 아닙니다. 아래 체크리스트가 돌아가야 합니다.

    1. 이미지 빌드 직후 자동 스캔이 실행된다.
    2. High 또는 Critical 기준으로 실패 정책이 적용된다.
    3. 어떤 패키지가 문제인지 개발자가 바로 확인할 수 있다.
    4. 재빌드 후 취약점 감소 여부를 비교할 수 있다.
    5. 예외 처리 사유가 기록된다.

    예를 들어 Trivy라면 결과 테이블에서 패키지명, 취약점 ID, 설치 버전, 수정 버전이 보여야 하고, Clair라면 제출한 이미지에 대해 보고서가 안정적으로 반환되어야 합니다. 여기까지 되면 컨테이너 이미지 보안 체계가 최소한의 자동화 단계에는 올라온 겁니다. 🎉

    컨테이너 이미지 보안 검증 결과와 취약점 감소 추이를 보여주는 대시보드 이미지

    스캔 결과를 한 번에 파악할 수 있는 대시보드와 재빌드 후 취약점 수가 줄어드는 검증 장면을 보여주는 이미지입니다.

    8. 그래서 우리 팀은 뭘 고르면 되나

    제가 멘토링하듯 정리하면 이렇습니다.

    • Trivy: 작은 팀, 스타트업, 플랫폼 초기 단계, 개발자 자율 사용이 중요한 팀에 잘 맞아요.
    • Clair: 중앙 서비스형 스캔, 레지스트리 통합, 내부 보안 플랫폼 운영이 중요한 팀에 잘 맞습니다.

    조금 더 현실적으로 말하면, 대부분의 팀은 Trivy로 시작해서 운영 성숙도가 올라가면 Clair 같은 서비스형 구성까지 검토하는 흐름이 자연스럽습니다. 물론 이미 레지스트리 중심 운영이 잘 잡혀 있고, 보안 서비스 운영 경험이 있다면 Clair도 충분히 좋은 선택이 될 수 있습니다.

    팀 상황 추천 이유
    개발팀이 직접 CLI를 돌려야 함 Trivy 배포 전 빠른 피드백이 쉬워요
    CI에서 바로 차단 정책이 필요함 Trivy 간단한 exit code 기반 통합이 쉬워요
    사내 공통 스캔 서비스를 운영함 Clair API 기반 구조가 잘 맞아요
    레지스트리와 강하게 결합한 운영을 원함 Clair 이미지 인덱싱/매칭 구조가 자연스러워요
    컨테이너 이미지 보안 도구인 Trivy와 Clair의 선택 기준을 요약한 인포그래픽

    팀 규모, 운영 방식, DevSecOps 성숙도에 따라 Trivy와 Clair 중 무엇을 고를지 빠르게 판단할 수 있게 정리한 요약 이미지입니다.

    9. 마무리: 도구보다 운영 습관이 더 오래 갑니다

    정리해보면, Trivy vs Clair 비교는 기능표 한 장으로 끝나는 문제가 아니었습니다. 직접 써보니까 결국 승부는 운영 방식에서 나더라고요. Trivy는 시작이 빠르고 범위가 넓어서 도입 속도가 좋았어요. Clair는 서비스형 구조를 이해하고 운영할 수 있을 때 더 의미가 있었습니다.

    혹시 지금 팀에서 취약점 스캐너를 막 도입하려는 상황이라면, 저는 이렇게 권하고 싶습니다. 먼저 Trivy로 CI 차단 정책 하나를 확실히 세우세요. 그다음 SBOM과 예외 처리 기준을 정리하세요. 그리고 조직 규모가 커지면서 중앙 서비스가 필요해질 때 Clair 같은 구조를 검토하면 됩니다. 이 순서가 실제로 덜 아프고, 덜 헷갈립니다.

    다음 글에서는 Trivy를 GitHub Actions 같은 CI에 붙여서 DevSecOps 파이프라인을 만드는 방법도 다뤄볼 예정입니다. 이전 글에서 다뤘던 이미지 경량화와 베이스 이미지 관리 전략을 같이 보면 더 연결이 잘 되실 겁니다. ✅

  • [보안] CI/CD 파이프라인에 Trivy 적용 1년 회고: 컨테이너 이미지 보안, 정말 개선됐을까?

    [보안] CI/CD 파이프라인에 Trivy 적용 1년 회고: 컨테이너 이미지 보안, 정말 개선됐을까?

    1년 전, 저희 팀의 컨테이너 보안 현실

    솔직히 말씀드리면, Trivy를 도입하기 전 저희 팀의 컨테이너 이미지 보안은 거의 무방비 상태에 가까웠습니다. 매주 수십 개의 이미지를 빌드하고 배포하는데, 그 이미지 안에 어떤 취약점이 숨어있는지 아무도 신경 쓰지 않았거든요. “빌드 성공 = 배포 가능” 이라는 암묵적인 공식이 있었달까요.

    그러다 어느 날 보안팀에서 연락이 왔습니다. 운영 중인 컨테이너에서 CRITICAL 등급의 CVE(Common Vulnerabilities and Exposures, 공통 취약점 목록)가 발견됐다는 거예요. 그게 아마 제가 Trivy 컨테이너 보안을 도입하게 된 결정적인 계기였을 겁니다.

    지금으로부터 딱 1년 전 이야기입니다. 이 글은 그때부터 지금까지, CI/CD 파이프라인에 Trivy를 붙여 운영하면서 겪은 경험들을 솔직하게 정리한 회고록입니다. 결론부터 말씀드리자면 — 도입 가치는 충분했지만, 생각보다 쉽지는 않았습니다.

    ▲ Trivy가 CI/CD 파이프라인에 통합된 전체 보안 흐름. 코드 커밋부터 배포까지 각 단계에서 이미지 스캔이 자동으로 수행됩니다.

    Trivy(트리비) 컨테이너 보안이 뭔지 먼저 짚고 갈게요

    Trivy는 Aqua Security에서 만든 오픈소스 취약점 스캐너예요. 쉽게 말해서, 컨테이너 이미지 안에 어떤 보안 구멍이 있는지 자동으로 찾아주는 도구입니다.

    처음 이 도구를 접했을 때 제가 놀랐던 건 스캔 대상의 다양함이었어요. 단순히 컨테이너 이미지뿐만 아니라 파일시스템, Git 리포지토리, Kubernetes 매니페스트, Helm 차트, Terraform 코드까지 스캔할 수 있거든요. “이게 진짜 취약점 스캐너 하나로 다 되는 건가?” 싶었는데, 실제로 써보니까 그게 맞더라고요.

    Trivy가 탐지하는 주요 항목은 다음과 같습니다:

    • OS 패키지 취약점 (Debian, Alpine, Ubuntu, Red Hat 등)
    • 애플리케이션 의존성 취약점 (npm, pip, maven, go modules 등)
    • 컨테이너 이미지 설정 오류 (Misconfiguration)
    • 민감 정보 노출 (Secret scanning)
    • 소프트웨어 공급망 위험 (SBOM, Software Bill of Materials)

    설치도 정말 간단해요. 저는 처음에 이렇게 테스트해봤습니다:

    # Homebrew (macOS/Linux)
    brew install aquasecurity/trivy/trivy
    
    # 또는 공식 설치 스크립트
    curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
    
    # 간단한 이미지 스캔 테스트
    trivy image nginx:latest

    첫 스캔 결과를 보고 적잖이 충격받았습니다. 평소에 “최신 이미지 쓰면 안전하다”고 막연히 생각했는데, 실제로는 그렇지 않다는 걸 데이터로 확인하게 됐거든요. 이 경험이 팀 내 보안 인식 개선에 큰 역할을 했어요.

    CI/CD 파이프라인에 Trivy 보안 스캔 통합하기

    저희 팀은 GitHub Actions를 메인 CI/CD 도구로 사용하고 있었어요. Trivy를 파이프라인에 붙이는 방법은 여러 가지인데, 저는 단계적으로 적용했습니다.

    1단계: 일단 스캔만 (경보 없음)

    처음부터 빌드를 막으면 팀원들의 반발이 심할 것 같았어요. 그래서 먼저 결과만 리포트하는 것부터 시작했습니다. 이른바 “관찰 모드”였죠.

    # .github/workflows/security-scan.yml
    name: Container Security Scan
    
    on:
      push:
        branches: [ main, develop ]
      pull_request:
        branches: [ main ]
    
    jobs:
      trivy-scan:
        runs-on: ubuntu-latest
        steps:
          - name: Checkout code
            uses: actions/checkout@v4
    
          - name: Build Docker image
            run: docker build -t my-app:${{ github.sha }} .
    
          - name: Run Trivy vulnerability scanner
            uses: aquasecurity/trivy-action@master
            with:
              image-ref: 'my-app:${{ github.sha }}'
              format: 'table'
              exit-code: '0'  # 취약점 발견해도 빌드 실패 안 함 (관찰 모드)
              severity: 'CRITICAL,HIGH'
              output: 'trivy-results.txt'
    
          - name: Upload scan results
            uses: actions/upload-artifact@v4
            with:
              name: trivy-scan-results
              path: trivy-results.txt

    2단계: CRITICAL 취약점은 빌드 차단

    약 2주간 관찰 모드로 돌리면서 팀원들한테 “이런 취약점들이 우리 이미지에 있어요” 하고 공유했어요. 데이터를 보니까 다들 위기감을 느끼더라고요. 그때부터 CRITICAL 등급 취약점이 있으면 빌드를 막는 정책을 제안했고, 팀에서 수용됐습니다.

    # CRITICAL 취약점 발견시 빌드 실패
          - name: Run Trivy (CRITICAL block)
            uses: aquasecurity/trivy-action@master
            with:
              image-ref: 'my-app:${{ github.sha }}'
              format: 'sarif'
              output: 'trivy-results.sarif'
              exit-code: '1'  # 취약점 있으면 빌드 실패
              severity: 'CRITICAL'
              ignore-unfixed: true  # 아직 패치가 없는 취약점은 무시
    
          - name: Upload SARIF to GitHub Security
            uses: github/codeql-action/upload-sarif@v3
            if: always()  # 실패해도 리포트는 업로드
            with:
              sarif_file: 'trivy-results.sarif'

    여기서 ignore-unfixed: true 옵션이 굉장히 중요한데요, 이 부분은 뒤에서 더 자세히 설명하겠습니다.

    .trivyignore 파일로 예외 관리하기

    운영하다 보니 어쩔 수 없이 특정 CVE를 임시로 예외 처리해야 할 때가 생기더라고요. 이를 위해 저희는 .trivyignore 파일을 리포지토리 루트에 관리하기 시작했습니다.

    # .trivyignore
    # 형식: CVE-ID (한 줄에 하나씩)
    # 반드시 주석으로 예외 사유와 검토 예정일을 기록할 것!
    
    # [2025-03-15 예외 처리] 업스트림 패치 대기 중. 2025-04-15 재검토 예정
    # 참고: 해당 경로에서 실제 코드 경로가 닿지 않음을 확인함
    CVE-2023-XXXXX
    
    # [2025-04-01 예외 처리] 테스트 환경 전용 이미지. 프로덕션 미배포
    CVE-2024-YYYYY

    💡 팁: .trivyignore에 사유와 재검토 날짜를 반드시 주석으로 달아두세요. 시간이 지나면 왜 예외 처리했는지 아무도 기억 못해요. 저도 처음에 이걸 안 지키다가 나중에 엄청 고생했거든요 ㅎㅎ

    ▲ GitHub Security 탭에 Trivy SARIF 결과가 업로드된 모습. 취약점별 심각도, 영향 받는 파일, 수정 제안이 한눈에 보입니다.

    ⚠️ 1년간 맞닥뜨린 진짜 문제들

    Trivy 도입이 순탄하기만 했으면 이 글이 얼마나 좋았을까요 ㅎㅎ. 현실은 달랐습니다. 솔직하게 공유해드릴게요.

    문제 1: 노이즈가 너무 많다

    초기에 가장 큰 불만이 뭐였냐면, 취약점이 너무 많이 나온다는 거였어요. 첫날 스캔 결과를 팀원들에게 공유했을 때 반응이 “이거 다 고쳐야 해요?” 였거든요.

    실제로 대부분의 컨테이너 이미지를 처음 스캔하면 HIGH, MEDIUM 등급 취약점이 수십 개씩 나와요. 문제는 이 중 상당수가 실제로 해당 취약점 경로를 사용하지 않거나, 패치가 아직 존재하지 않는 경우라는 거더라고요.

    이를 해결하기 위해 저희가 취한 전략:

    1. 단계별 적용: CRITICAL → HIGH 순으로 순차 적용. 처음부터 모든 심각도를 차단하지 않음
    2. ignore-unfixed 활용: 아직 패치 버전이 없는 취약점은 일단 무시
    3. 베이스 이미지 전략: debian 계열 대신 alpine이나 distroless 이미지로 전환해 기본 취약점 수 자체를 줄임
    4. 주기적인 리뷰: 격주로 팀에서 새로 발견된 취약점 리뷰 세션 진행

    문제 2: 베이스 이미지가 주범인 경우가 많다

    흥미로운 발견이었는데요, Trivy 스캔 결과를 분석해보니 우리가 직접 만든 코드에서 비롯된 취약점보다 베이스 이미지 자체의 취약점이 훨씬 많았어요.

    예를 들어 python:3.10 같은 공식 이미지도 내부적으로 Debian 패키지들을 포함하고 있어서 취약점이 꽤 많이 나오거든요. 이 문제를 해결하기 위해 저희는 베이스 이미지 선택 기준을 다음과 같이 정리했습니다:

    베이스 이미지 취약점 노출 수준 특징 추천 용도
    ubuntu:22.04 높음 친숙, 패키지 풍부 레거시 호환성 필요시
    debian:slim 중간 Debian 경량화 범용 서비스
    alpine:3.x 낮음 초경량 (musl libc) 단순 서비스
    gcr.io/distroless 매우 낮음 OS 도구 없음 프로덕션 Go/Java
    scratch 거의 없음 완전 빈 이미지 정적 바이너리

    문제 3: 스캔 속도로 인한 개발자 불만

    처음엔 매 PR마다 전체 스캔을 돌렸는데, 이미지가 크면 스캔 시간이 꽤 길어져요. 개발자들이 “PR 리뷰가 늦어진다”는 불만을 토로하기 시작했거든요.

    이 문제는 캐싱 전략으로 해결했습니다:

    # Trivy DB 캐싱으로 스캔 속도 개선
          - name: Cache Trivy DB
            uses: actions/cache@v4
            with:
              path: ~/.cache/trivy
              key: ${{ runner.os }}-trivy-db-${{ github.run_id }}
              restore-keys: |
                ${{ runner.os }}-trivy-db-
    
          - name: Run Trivy with cache
            uses: aquasecurity/trivy-action@master
            with:
              image-ref: 'my-app:${{ github.sha }}'
              format: 'sarif'
              output: 'trivy-results.sarif'
              exit-code: '1'
              severity: 'CRITICAL,HIGH'
              cache-dir: ~/.cache/trivy
              skip-db-update: false

    캐싱 적용 후 스캔 시간이 눈에 띄게 줄었어요. Trivy는 취약점 DB를 처음 다운로드할 때 시간이 걸리는데, 이걸 캐시해두면 이후 실행에서 훨씬 빨라지거든요.

    🎉 1년 후, 실제로 달라진 것들

    회고의 핵심 질문으로 돌아옵니다. 컨테이너 이미지 보안, 정말 개선되었을까요?

    결론부터 말씀드리면 — 네, 확실히 개선됐습니다. 다만 기대했던 방식과는 조금 달랐어요.

    Trivy 도입 후 1년간 컨테이너 이미지 CRITICAL 및 HIGH 취약점 감소 추이 그래프

    ▲ Trivy 도입 후 1년간 CRITICAL/HIGH 취약점 탐지 및 해결 추이. 초기에 취약점이 급증하는 것처럼 보이는 건 기존에 모르고 지나쳤던 것들이 가시화됐기 때문입니다.

    가시화된 변화들

    • ✅ 베이스 이미지 전략 정착: 팀 전체가 이미지 선택 시 취약점 수를 기준 중 하나로 고려하게 됨
    • ✅ 최신 이미지 업데이트 주기 단축: 예전엔 6개월씩 방치하던 베이스 이미지를 이제는 월 1회 업데이트
    • ✅ 보안 인식 향상: 개발자들이 PR 올리기 전 직접 로컬에서 trivy를 돌려보는 문화 형성
    • ✅ SBOM 생성 자동화: 각 릴리스마다 SBOM(소프트웨어 부품 목록)이 자동 생성되어 감사 대응에 활용
    • ✅ 보안팀과의 소통 개선: “우리 이미지 상태”를 데이터로 보여줄 수 있게 되어 보안팀과 대화가 훨씬 수월해짐

    솔직히 아쉬운 점들

    • ⚠️ MEDIUM 등급 취약점은 여전히 방치 중 — 우선순위에 밀려서
    • ⚠️ .trivyignore 예외 항목이 시간이 지나면서 관리가 느슨해지는 경향
    • ⚠️ 런타임(실행 중인 컨테이너)의 취약점은 별도 도구가 필요 — Trivy 컨테이너 보안만으로는 커버 안 됨
    • ⚠️ false positive(오탐)가 가끔 발생해 개발자들이 스캔 결과를 무감각하게 보는 현상

    DevSecOps 관점에서의 교훈

    1년을 운영하면서 깨달은 게 있는데요, 도구 도입보다 프로세스와 문화가 훨씬 중요하다는 거예요.

    Trivy는 훌륭한 도구예요. 설치도 쉽고, CI/CD 통합도 어렵지 않아요. 근데 막상 팀에 도입하면 이런 질문들이 쏟아집니다:

    • “취약점 발견되면 누가 고치나요?”
    • “CRITICAL인데 패치가 없으면요?”
    • “배포 일정인데 HIGH 취약점 때문에 못 올리나요?”

    이런 질문들에 대한 팀의 합의된 답변이 없으면, 아무리 좋은 도구도 형식적인 체크박스가 되어버려요.

    저희가 결국 정착시킨 정책은 이렇습니다:

    1. CRITICAL 취약점 + 수정 가능한 버전 존재 → 배포 전 반드시 수정
    2. CRITICAL 취약점 + 수정 버전 없음 → 보안팀 승인 후 임시 예외 처리, 최대 30일 유효
    3. HIGH 취약점 → 다음 스프린트 내 수정 목표 (강제 아님)
    4. MEDIUM 이하 → 분기별 리뷰에서 일괄 검토
    Trivy 취약점 심각도 등급별 DevSecOps 대응 정책 요약 인포그래픽

    ▲ Trivy 취약점 대응 정책 요약. 심각도 등급별로 대응 방식과 기한을 명확히 정의하는 것이 성공적인 DevSecOps 운영의 핵심입니다.

    자주 묻는 질문 (FAQ)

    Q. Trivy와 Snyk 중 뭐가 낫나요?

    둘 다 훌륭한 도구예요. Trivy는 오픈소스이고 완전 무료인 반면, Snyk는 SaaS 기반이라 대시보드와 팀 협업 기능이 더 강력해요. 소규모 팀이라면 Trivy로 충분하고, 엔터프라이즈 환경이라면 Snyk 같은 유료 솔루션이 운영 부담을 줄여줄 수 있습니다.

    Q. 로컬에서도 스캔을 돌려야 하나요?

    권장해요! CI/CD에서 걸리면 되돌아가는 시간이 길어지거든요. 저는 개인적으로 git pre-push 훅에 Trivy를 연결해놨어요. 푸시 전에 로컬에서 먼저 확인하는 거예요.

    Q. 스캔 결과를 어떻게 팀에 공유하나요?

    저희는 GitHub의 Security 탭(SARIF 업로드 활용)을 주로 사용하고, Slack으로 CRITICAL 발견 시 즉시 알림을 보내도록 했어요. 주간 리포트는 간단한 쉘 스크립트로 취약점 카운트를 집계해서 공유하고 있습니다.

    마무리: Trivy 도입, 해야 할까요?

    1년을 써보고 내리는 결론은 “무조건 도입하세요”입니다. 다만 기대치를 조정하세요.

    Trivy는 마법이 아니에요. 도입한다고 갑자기 이미지가 안전해지지는 않아요. 하지만 눈에 보이지 않던 것들을 보이게 만들어줍니다. 그리고 보안에서는 가시화가 첫 번째 단계예요.

    “Trivy를 붙였더니 보안이 100% 완벽해졌다”는 글은 과장이에요. 현실은 “Trivy를 붙이고 나서야 우리 이미지가 얼마나 취약했는지 알게 됐다”에 가까워요. 그리고 그걸 알게 됐기 때문에, 조금씩 나아질 수 있었습니다.

    DevSecOps는 한 번의 도구 도입으로 완성되지 않아요. 꾸준한 리뷰, 팀 합의, 프로세스 개선의 반복이 필요하죠. Trivy는 그 여정을 시작하기에 좋은 출발점입니다.

    다음 글에서는 Trivy를 활용한 SBOM(소프트웨어 부품 목록) 생성과 공급망 보안에 대해 다뤄볼 예정입니다. 요즘 화두인 소프트웨어 공급망 공격에 Trivy 컨테이너 보안이 어떻게 도움이 되는지 정리해볼게요.

    긴 글 읽어주셔서 감사합니다. 혹시 비슷한 경험 있으신 분이나 질문 있으신 분은 댓글로 남겨주세요! 🙏

  • [보안 사례] 컨테이너 이미지 보안 강화: Trivy를 활용한 CI/CD 통합 운영 후기

    [보안 사례] 컨테이너 이미지 보안 강화: Trivy를 활용한 CI/CD 통합 운영 후기

    [보안 사례] 컨테이너 이미지 보안 강화: Trivy를 활용한 CI/CD 통합 운영 후기

    컨테이너 보안 사례를 찾다 보면 다들 비슷한 이야기만 하더라고요. “스캔하세요”, “취약점 관리하세요” 같은 말은 맞는데, 막상 운영 환경에 넣으려면 어디서부터 손대야 할지 애매합니다. 저도 홈랩과 사내 비슷한 구조의 테스트 환경에서 Trivy(트리비, 오픈소스 취약점 스캐너)를 붙여서 이미지 스캔을 자동화해봤는데, 처음엔 이게 생각보다 손이 많이 가더라고요. 특히 CI/CD 보안 관점에서 “스캔은 했는데 배포는 누가 막지?”, “베이스 이미지가 바뀌면 기존 정책은 어떻게 하지?” 같은 현실적인 문제가 꼭 나옵니다.

    이번 글은 그런 분들을 위한 컨테이너 보안 사례 정리입니다. Trivy를 활용해서 CI/CD 보안 흐름에 이미지 검사를 붙이고, Docker 보안과 Kubernetes 보안까지 운영 관점에서 어떻게 연결했는지 경험 기반으로 풀어보겠습니다. 제가 직접 해보니 도구 자체보다도 언제 검사하고, 어떤 기준으로 실패 처리할지가 진짜 핵심이었습니다.

    컨테이너 보안 사례 기반 Trivy CI/CD 파이프라인 아키텍처 이미지

    CI 단계에서 이미지 빌드, Trivy 스캔, 레지스트리 푸시, Kubernetes 배포 전 검증까지 이어지는 전체 흐름을 보여주는 이미지입니다.

    1. 왜 컨테이너 이미지 보안이 운영에서 중요할까

    쉽게 말해 컨테이너 이미지는 서버의 골격을 압축해 둔 결과물입니다. 애플리케이션 코드만 들어가는 게 아니라, 베이스 OS 패키지, 런타임, 시스템 라이브러리까지 같이 들어가거든요. 그래서 이미지 안에 취약한 패키지가 하나만 있어도, 실제 운영에서는 꽤 민감한 이슈가 됩니다.

    예전엔 VM(Virtual Machine, 가상머신) 단위로 관리하던 걸 컨테이너로 옮기면서 배포 속도는 빨라졌는데, 그만큼 이미지가 더 자주 만들어지고 더 자주 배포됩니다. 문제는 속도가 빨라질수록 사람이 수동으로 체크할 여유가 없어집니다. 저도 처음엔 배포 직전에 한 번만 확인하면 되겠지 싶었는데, 실제로 써보니까 그 방식은 오래 못 갑니다. 빌드마다 자동으로 검사하고, 위험 수준이 높은 건 파이프라인에서 바로 걸러야 하더라고요.

    • 빌드 시점: 취약한 패키지가 이미지에 포함되는지 확인
    • 푸시 시점: 레지스트리(Registry, 이미지 저장소)에 위험한 이미지가 쌓이지 않게 제어
    • 배포 시점: Kubernetes(쿠버네티스, 컨테이너 오케스트레이션)에서 검증되지 않은 이미지 실행 방지

    여기서 중요한 포인트! 컨테이너 보안 사례를 보면 대부분 “도구 설치”에서 끝나는데, 운영은 그 다음부터 시작입니다.

    2. Trivy가 뭐고, 왜 많이 쓰는가

    Trivy는 Aqua Security에서 공개한 오픈소스 보안 스캐너로, 컨테이너 이미지 안의 OS 패키지와 애플리케이션 의존성 취약점을 확인할 수 있습니다. 그리고 이게 편한 이유가 뭔가 하면, 명령어가 비교적 단순하고 CI/CD에 넣기 쉽습니다. 저도 처음엔 Clair나 다른 스캐너도 같이 봤었는데, 실무에서는 빠르게 붙고 운영 부담이 적은 쪽이 오래 갑니다.

    쉽게 말해 Trivy는 “이 이미지 안에 알려진 취약점이 있는지”를 검사해주는 첫 번째 관문입니다. 그리고 필요하면 설정 파일 오탐도 잡고, 파일시스템이나 Git 저장소 스캔에도 활용할 수 있죠. 다만 오늘은 범위를 넓히기보다 이미지 스캔 중심의 CI/CD 보안 이야기에 집중하겠습니다.

    항목 Trivy 적용 전 Trivy 적용 후
    취약점 발견 시점 배포 후 또는 수동 점검 빌드 단계에서 자동 확인
    운영자 개입 높음 정책 기반 자동화 가능
    재현성 담당자 경험 의존 파이프라인 기준으로 일관됨
    Docker 보안 관리 개별 확인 공통 정책으로 통제

    3. 운영 기준 먼저 정하기: 실패 조건을 정하지 않으면 자동화가 흔들립니다

    이건 제가 꽤 삽질했던 부분입니다. 처음엔 취약점이 하나라도 나오면 실패 처리해봤거든요. 결과는 뻔했습니다. 팀이 바로 우회 플래그를 찾기 시작합니다. 왜냐하면 현실적으로 베이스 이미지 하나만 바뀌어도 Low나 Medium 수준이 여러 개 잡힐 수 있기 때문입니다.

    그래서 운영 기준을 이렇게 나눴습니다.

    1. 개발 브랜치: 결과는 남기되 즉시 차단하지 않음
    2. 메인 브랜치: High, Critical 취약점이 있으면 실패
    3. 릴리스 태그: 스캔 결과 아티팩트 보관 + 예외 승인된 항목만 통과
    4. 운영 배포: 승인된 이미지 다이제스트(Digest, 내용 기반 식별자)만 사용

    이렇게 나누니까 팀도 덜 답답해하고, 보안팀이나 인프라 담당도 기준을 설명하기 쉬워졌습니다. 결국 CI/CD 보안은 기술보다 합의가 먼저더라고요.

    4. 실전 구현: Trivy를 CI/CD 파이프라인에 붙이는 방식

    제가 가장 단순하게 시작했던 흐름은 이렇습니다. Docker 이미지 빌드 후 Trivy로 스캔하고, 결과가 기준을 넘으면 푸시나 배포를 막는 구조입니다. 여기서는 예시로 GitHub Actions 스타일 YAML을 보여드리지만, GitLab CI나 Jenkins에서도 개념은 거의 같습니다.

    4-1. 로컬에서 먼저 이미지 스캔해보기

    자동화 전에 로컬에서 결과를 보는 게 좋습니다. 그래야 나중에 파이프라인 로그를 봐도 덜 헷갈립니다.

    docker build -t sample-app:local .
    trivy image sample-app:local
    

    조금 더 실무적으로 보려면 심각도(Severity, 취약점 등급)를 제한해서 보는 게 편합니다.

    trivy image --severity HIGH,CRITICAL sample-app:local
    

    JSON 결과를 파일로 저장해두면 후처리에도 좋습니다.

    trivy image --format json --output trivy-result.json sample-app:local
    

    4-2. GitHub Actions 예시

    name: container-security
    
    on:
      push:
        branches:
          - main
      pull_request:
    
    jobs:
      build-and-scan:
        runs-on: ubuntu-latest
        steps:
          - name: Checkout
            uses: actions/checkout@v4
    
          - name: Set up Docker Buildx
            uses: docker/setup-buildx-action@v3
    
          - name: Build image
            run: docker build -t sample-app:${{ github.sha }} .
    
          - name: Run Trivy scan
            run: |
              trivy image \
                --exit-code 1 \
                --severity HIGH,CRITICAL \
                --format table \
                sample-app:${{ github.sha }}
    
          - name: Save JSON report
            if: always()
            run: |
              trivy image \
                --format json \
                --output trivy-report.json \
                sample-app:${{ github.sha }}
    
          - name: Upload report artifact
            if: always()
            uses: actions/upload-artifact@v4
            with:
              name: trivy-report
              path: trivy-report.json
    

    여기서 중요한 건 –exit-code 1입니다. 이 옵션이 있어야 기준에 걸렸을 때 파이프라인이 실제로 실패합니다. 처음엔 결과만 출력하고 끝내는 식으로 구성했었는데, 그러면 운영에서 아무도 안 봅니다. 진짜입니다. 로그는 쌓이는데 아무도 안 봐요.

    컨테이너 보안 사례에서 Trivy 이미지 스캔이 포함된 CI/CD 보안 구성 이미지

    개발 브랜치, 메인 브랜치, 릴리스 태그에 따라 서로 다른 스캔 정책이 적용되는 예시 화면 또는 구성 다이어그램용 이미지입니다.

    4-3. 무시할 취약점은 최소한으로 관리하기

    운영하다 보면 당장 패치가 어려운 항목이 나옵니다. 이럴 때 무작정 무시하지 말고, 예외를 추적 가능한 방식으로 남겨야 합니다.

    # .trivyignore 예시
    CVE-2023-00000
    CVE-2023-11111
    

    저는 이 파일을 넣을 때 리뷰 규칙을 따로 뒀습니다.

    • 왜 무시하는지 이슈 번호 남기기
    • 대체 패치 일정 적기
    • 베이스 이미지 교체 가능 여부 확인하기

    안 그러면 .trivyignore가 점점 커지고, 나중엔 형식적인 보안이 되어버립니다.

    4-4. Kubernetes 배포 전 검증 포인트

    Kubernetes 보안 관점에서는 “스캔 완료된 이미지인가”와 “검증된 이미지 버전만 쓰는가”가 핵심입니다. 가장 기본은 태그(tag)보다 다이제스트 고정입니다. latest 같은 태그는 진짜 편해 보여도 운영에서는 추적이 어려워집니다.

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: sample-app
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: sample-app
      template:
        metadata:
          labels:
            app: sample-app
        spec:
          containers:
            - name: sample-app
              image: registry.example.com/sample-app@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
              imagePullPolicy: IfNotPresent
              securityContext:
                runAsNonRoot: true
                readOnlyRootFilesystem: true
    

    이미지 스캔만으로 Kubernetes 보안이 끝나는 건 아니고요, securityContext(시큐리티 컨텍스트), 비루트 실행, 읽기 전용 파일시스템 같은 기본 하드닝(hardening, 보안 강화)도 같이 가야 합니다.

    5. 제가 실제로 겪은 문제들: 트러블슈팅과 운영 팁

    여기서부터가 좀 현실적인 이야기입니다. 도구 소개 글에는 잘 안 나오는데, 실제로 붙이면 은근히 이런 문제를 많이 겪습니다.

    5-1. 베이스 이미지가 원인인 경우가 많습니다

    애플리케이션 코드는 멀쩡한데 계속 High가 뜨는 경우가 있었습니다. 알고 보니 대부분 베이스 이미지 문제였어요. 예를 들어 오래된 패키지를 포함한 이미지라면 애플리케이션 수정으로 해결이 안 됩니다. 이럴 땐 Dockerfile부터 봐야 합니다.

    FROM python:3.11-slim
    WORKDIR /app
    COPY requirements.txt .
    RUN pip install --no-cache-dir -r requirements.txt
    COPY . .
    CMD ["python", "app.py"]
    

    여기서도 끝이 아닙니다. 같은 slim 계열이라도 시점에 따라 포함 패키지가 달라질 수 있거든요. 그래서 정기 재빌드가 중요합니다. 코드를 안 바꿔도 다시 빌드하면 해결되는 취약점이 생각보다 많습니다.

    5-2. 스캔 시간 때문에 파이프라인이 느려질 수 있습니다

    처음엔 모든 브랜치에서 전체 스캔을 돌렸더니 CI 시간이 확 늘어났습니다. 이거 진짜 불만 많이 나옵니다. 그래서 저는 전략을 나눴습니다.

    • Pull Request 단계: High, Critical 중심 빠른 검사
    • Main 병합 후: 전체 이미지 스캔 + 결과 보관
    • 야간 배치: 주요 이미지 일괄 재스캔

    이렇게 하면 개발 흐름을 너무 막지 않으면서도 운영 기준은 유지할 수 있습니다.

    5-3. 취약점 수보다 맥락이 중요합니다

    숫자만 보고 “취약점 27개”라고 하면 다들 놀랍니다. 근데 실제로는 실행 경로와 무관한 패키지일 수도 있고, 이미 수정판이 준비된 경우도 있습니다. 반대로 개수가 적어도 원격 코드 실행(RCE, Remote Code Execution) 관련이면 우선순위가 훨씬 높죠. 그래서 대시보드에는 단순 개수보다 Critical 존재 여부, 고정 가능한지 여부, 운영 노출도를 같이 봤습니다.

    컨테이너 보안 사례의 Trivy 취약점 결과와 이미지 스캔 대시보드 이미지

    Critical, High, 수정 가능 여부, 베이스 이미지 기인 여부 등을 기준으로 결과를 정리한 보안 리포트 대시보드 이미지입니다.

    6. 검증과 결과: 무엇이 달라졌나

    몇 달 운영해보니 체감되는 변화가 꽤 분명했습니다. 가장 큰 건 “배포 후 발견”이 줄어든 겁니다. 이전에는 이미지가 이미 레지스트리에 올라가고 나서 보안 이슈를 알게 되는 경우가 있었는데, 지금은 빌드 단계에서 대부분 걸러집니다. 이 차이가 큽니다.

    • 개발자가 PR 단계에서 위험한 이미지 변경을 바로 인지
    • 운영자는 승인된 기준으로만 배포 가능
    • 레지스트리에 누적되는 불량 이미지 감소
    • Docker 보안 점검이 개별 담당자 감에 덜 의존

    또 하나 좋았던 건 커뮤니케이션입니다. 예전엔 보안 이슈가 나오면 “누가 확인했나요?”부터 시작했는데, 이제는 “이 이미지는 Trivy 기준을 통과했는가”로 대화가 정리됩니다. 기준이 생기니까 감정 소모가 줄더라고요.

    검증 체크리스트

    1. 빌드 직후 Trivy 스캔이 자동 실행되는가
    2. High, Critical 기준에서 파이프라인 실패가 동작하는가
    3. JSON 결과가 아티팩트로 저장되는가
    4. 운영 배포에서 다이제스트 기반 이미지를 사용하는가
    5. Kubernetes 보안 설정이 최소 기준을 만족하는가
    trivy image --severity HIGH,CRITICAL --exit-code 1 registry.example.com/sample-app:release
    kubectl get deploy sample-app -o yaml
    

    위 두 줄만 봐도 1차 확인은 꽤 됩니다. 간단하지만 실전에서는 이 기본기가 중요합니다.

    7. 운영하면서 정리한 추천 정책

    혹시 이제 막 도입하시는 분이라면, 처음부터 너무 거창하게 가지 않는 걸 추천드립니다. 저도 처음엔 모든 걸 다 통제하려다 오히려 반발만 샀거든요.

    단계 추천 정책 이유
    1단계 메인 브랜치만 차단 개발 흐름 충격 최소화
    2단계 리포트 아티팩트 보관 추적성과 감사 대응 확보
    3단계 예외 관리 프로세스 도입 무분별한 ignore 방지
    4단계 Kubernetes 배포 정책 연계 CI와 런타임 보안 연결

    이 흐름으로 가면 도입 난이도도 적당하고, 팀이 적응할 시간도 벌 수 있습니다. 특히 컨테이너 보안 사례는 도구 선정보다 운영 규칙 설계가 더 중요하다는 점, 꼭 기억하시면 좋겠습니다.

    8. 마무리: Trivy는 시작점이고, 운영 기준이 완성도를 만듭니다

    정리해보면 Trivy 자체는 비교적 가볍게 시작할 수 있는 좋은 도구입니다. 다만 진짜 차이는 이미지 스캔 결과를 어떻게 의사결정에 반영하느냐에서 나옵니다. 제가 직접 해보니, 단순히 “스캔했다”보다 “배포를 막을 수 있다”, “예외를 기록할 수 있다”, “Kubernetes 보안까지 연결된다”가 훨씬 중요했습니다.

    그리고 이건 꼭 말씀드리고 싶네요. 처음부터 완벽하게 하려고 하면 오래 못 갑니다. 먼저 CI/CD 보안 흐름에 Trivy를 넣고, 그 다음 Docker 보안 기준을 정리하고, 마지막으로 Kubernetes 보안 정책과 연결하는 순서가 현실적이었습니다. 드디어 됐다 싶었던 순간도 있었고, 베이스 이미지 하나 때문에 몇 시간 날린 적도 있었는데요, 그런 삽질이 결국 운영 기준을 더 단단하게 만들어주더라고요.

    컨테이너 보안 사례 도입 전후와 CI/CD 보안 체크포인트 요약 이미지

    도입 전후 차이, 운영 체크리스트, 다음 단계 액션 아이템을 한눈에 보여주는 요약 인포그래픽용 이미지입니다.

    다음 단계로 해볼 만한 것

    • 레지스트리 단계 스캔 자동화 추가
    • SBOM(Software Bill of Materials, 소프트웨어 구성 명세) 관리 연계
    • Admission Controller(어드미션 컨트롤러, 배포 승인 제어) 기반 배포 정책 강화
    • 이전 글의 Dockerfile 경량화 내용과 연결해 베이스 이미지 개선

    다음 글에서는 컨테이너 보안 사례를 이어서, Kubernetes 배포 정책과 Admission Controller를 어떻게 엮으면 좋은지 좀 더 깊게 다뤄보려고 합니다. 그 부분도 운영에서 체감이 크거든요.

    정리 FAQ

    Q1. Trivy는 어느 시점에 넣는 게 가장 효과적인가요?

    가장 먼저는 이미지 빌드 직후입니다. 그 다음이 레지스트리 푸시 전, 마지막이 배포 전 검증입니다.

    Q2. 모든 취약점을 차단해야 하나요?

    아닙니다. 운영에서는 보통 High, Critical부터 시작하는 편이 현실적입니다. 대신 예외 기준은 명확해야 합니다.

    Q3. 이미지 스캔만 하면 Kubernetes 보안도 충분한가요?

    아쉽지만 아닙니다. 비루트 실행, 읽기 전용 파일시스템, 권한 최소화 같은 런타임 보안 설정이 같이 필요합니다.