linux-kernel

8 개의 포스트

meta4분 읽기큐레이션 요약

오픈 소스 커널 스케줄러를 활용한 Meta 광고 서비스 현대화

Meta는 광고 서버의 워크로드 특성을 반영한 `sched_ext` 기반 커스텀 스케줄러를 도입해 광고 검색 경로의 p99 지연을 28% 줄이고, 전력 3.28MW를 절감했으며, 순위 매긴 광고 수를 1.1% 늘렸다. 일반 목적 스케줄러 대신 요청의 중요도와 실행 경로를 이해하는 스케줄링 정책을 적용한 결과다. 이후 사용자 공간 BPF 정책만 수정해 p99 지연을 추가로 60% 줄이고 타임아웃 오류도 18% 감소시켰다. ## 광고 서비스에서 지연 시간이 중요한 이유 - Meta의 광고 플랫폼은 초당 평균 500만 건 이상의 요청을 처리하며, 하루 기준 4,000억 건이 넘는다. - p99 지연 시간이 몇 밀리초만 늘어나도 광고의 관련성과 광고주 ROI가 악화될 수 있다. - 기존 Linux 스케줄러인 CFS와 EEVDF는 범용 목적이라 각 스레드의 업무 중요도나 광고 요청 처리 경로를 알지 못한다. - 광고 서비스에서는 어떤 스레드가 사용자 요청의 핵심 경로에 있는지 알고 있으므로, 이를 스케줄러에 직접 반영할 여지가 있었다. ## EEVDF 전환으로 발생한 문제 - Linux 6.6부터 도입된 EEVDF가 최신 커널 6.9에서 광고 서버의 지연 시간을 악화시켰다. - 그 결과 응답에서 검색·순위 지정되는 광고 수가 줄어들었다. - 일부 광고 서버는 성능 회귀를 피하기 위해 Linux 6.4와 CFS에 계속 남아야 했고, 커널 버전이 혼재하는 운영 부담과 기술 부채가 발생했다. - 이미 Meta 내 여러 서비스에서 성능 개선 효과를 보인 `sched_ext`가 이 문제의 해결책으로 선택됐다. ## BPF 기반 `sched_ext`의 구조 - `sched_ext`는 BPF 프로그램으로 스케줄링 정책을 구현할 수 있는 Linux의 확장 가능한 프레임워크다. - Linux 6.12에 공식적으로 포함됐으며, Google의 ghOSt 설계 경험을 바탕으로 업스트림 통합을 고려해 개발됐다. - 커널은 다음과 같은 이벤트가 발생할 때 BPF 스케줄러를 호출한다. - 스레드가 실행 가능 상태가 될 때 CPU 선택 - 실행 큐에 스레드를 넣을 때 - CPU가 유휴 상태가 되어 다음 스레드를 선택할 때 - CPU가 유휴 상태에 진입하거나 빠져나올 때 - 광고 워크로드가 실행되는 호스트에만 광고 최적화 정책을 적용할 수 있다. ## 광고 요청에 맞춘 CPU 분할과 지역성 개선 - CPU를 두 개의 논리적 풀로 나눈다. - 지연 시간에 민감한 광고 요청 처리 스레드용 - 상대적으로 덜 중요한 백그라운드·비핵심 작업용 - 어떤 스레드를 어느 풀에 배치할지는 광고 도메인 지식에 따라 결정한다. - 부하 기반 휴리스틱으로 각 CPU 풀의 크기를 동적으로 조절한다. - 관련 작업을 장기간 같은 CPU에 배치해 L3 캐시 지역성을 높이고 DRAM 접근 비용을 줄인다. - 정책은 사용자 공간 바이너리가 BPF 프로그램을 로드하는 형태로 배포된다. - 정책을 변경할 때 커널을 다시 빌드하거나 설치할 필요 없이 스케줄러 프로세스를 재시작하면 되므로 실험과 배포가 빠르다. ## 성능과 운영 효과 Linux 6.4의 CFS에서 Linux 6.9와 `sched_ext`로 전환한 초기 도입 결과는 다음과 같다. - 광고 검색 경로의 서비스 p99 지연 28% 감소 - 전체 서버 플릿에서 전력 3.28MW 절감 - 가중 광고 순위 지표 1.1% 증가 - 사용자 공간 정책을 두 차례 추가 개선한 뒤: - 서비스 p99 지연이 추가로 60% 감소 - 핵심 경로의 타임아웃 오류 18% 감소 커널 변경이 필요하지 않았기 때문에 후속 개선은 수개월이 아닌 며칠 단위로 배포할 수 있었다. ## 장기적인 전략 자산으로의 확장 - 업스트림 Linux 스케줄러의 변화와 별개로, Meta가 자체 워크로드에 맞는 정책을 병렬적으로 개선할 수 있다. - 커널 패치와 장기간의 검증이 필요했던 기능도 사용자 공간 BPF 업데이트로 빠르게 실험할 수 있다. - 예시로 로컬 캐시 인식 배치, ROI 기반 실행기 라우팅, NUMA 인식 CPU 선택 등을 적용할 수 있다. - `sched_ext`가 Linux에 업스트림되면서 클라우드 사업자, 대규모 서비스 운영자, 임베디드 시스템 등도 커널을 포크하지 않고 특화된 스케줄링 정책을 구현할 수 있게 됐다. ## 향후 방향 - 광고 서비스가 요청의 상대적 중요도 같은 애플리케이션 수준의 정보를 스케줄러에 전달할 수 있다. - 스케줄러는 중요 요청을 처리하는 스레드에 더 긴 실행 시간을 주거나, 실행 큐의 최상단에 유지하는 방식으로 대응할 수 있다. - 이를 통해 단순한 CPU 부하 균형을 넘어, 비즈니스 가치와 요청 우선순위를 반영한 스케줄링이 가능해진다. 워크로드별 우선순위와 실행 특성을 명확히 알고 있는 대규모 서비스라면, 범용 스케줄러만 고집하기보다 `sched_ext` 같은 확장 프레임워크로 지연 시간·전력·처리량을 함께 최적화하는 방안을 검토할 만하다.

원문 읽기(새 탭에서 열림)
cloudflare4분 읽기큐레이션 요약

‘idle’이 유휴 상태가 아닐 때: 리눅스 커널 최적화가 QUIC 버그가 된 과정

CUBIC의 혼잡 윈도우(cwnd)가 심각한 손실 이후 최솟값에 고정되어 회복하지 못하는 QUIC 버그가 발견됐다. 손실이 2초 후 완전히 사라졌는데도 quiche의 CUBIC은 회복 상태와 혼잡 회피 상태를 RTT 주기로 반복하며 전송량을 늘리지 못했다. 원인은 Linux TCP CUBIC의 유휴 상태 최적화를 QUIC에 적용하는 과정에서 `bytes_in_flight == 0` 상황이 잘못 해석된 데서 비롯됐으며, 결국 매우 작은 수정으로 문제가 해결됐다. ## CUBIC과 혼잡 윈도우의 역할 - 혼잡 제어 알고리즘(CCA)은 송신자가 네트워크에 동시에 전송할 수 있는 데이터의 상한인 `cwnd`를 조절한다. - `cwnd`가 크면 한 번에 더 많은 데이터를 전송하고, 작으면 전송 속도가 제한된다. - 손실 기반 알고리즘인 CUBIC은 다음과 같은 전제를 따른다. - 패킷 손실이 없으면 네트워크 여유가 있다고 보고 전송률을 증가시킨다. - 패킷 손실이 발생하면 네트워크 용량을 초과했다고 판단해 전송률을 줄인다. - CUBIC은 Linux의 기본 TCP 혼잡 제어기이며, Cloudflare의 QUIC 구현인 quiche에서도 기본값으로 사용된다. ## 재현 조건과 이상 증상 - 문제는 초반에 심각한 패킷 손실이 발생한 뒤 회복하는 통합 테스트에서 발견됐다. - 테스트 조건: - localhost에서 실행되는 HTTP/3 클라이언트와 서버 - RTT 10ms - 10MB 파일 다운로드 - CUBIC 사용 - 연결 시작 후 2초 동안 무작위 30% 패킷 손실 - 이후 패킷 손실 제거 - 10초 타임아웃 - 정상이라면 손실 구간에서 `cwnd`가 감소한 뒤, 손실이 사라지면 다시 증가해 4~5초 안에 다운로드를 완료해야 한다. - 그러나 100회 반복 시 약 60~61%가 10초 안에 완료되지 못했다. ## 손실이 없는데도 회복하지 못한 CUBIC - 2초 이후 패킷 손실은 완전히 사라졌지만 `bytes_in_flight`와 `cwnd`는 계속 최솟값에 머물렀다. - CUBIC은 약 6.7초 동안 회복 상태와 혼잡 회피 상태를 999회 반복했다. - 상태 전환 주기는 약 14ms로, 연결의 RTT 10ms와 비슷했다. - `cwnd`는 2700바이트, 즉 최대 세그먼트 크기의 패킷 2개 수준에 고정됐다. - 다운로드 서버 입장에서는 클라이언트의 ACK가 도착할 때마다 전송 중인 바이트가 0이 되고, 다시 두 패킷을 보내는 과정이 반복됐다. - 이 ACK 기반의 전송 리듬이 CUBIC의 상태 전환을 매 RTT마다 잘못 촉발한 것으로 분석됐다. ## Reno 비교를 통한 CUBIC 특화 문제 확인 - 동일한 테스트를 Reno로 실행한 결과 100% 성공했다. - Reno는 손실이 끝난 뒤 정상적으로 `cwnd`를 증가시키고 다운로드를 완료했다. - 따라서 네트워크 시뮬레이션이나 QUIC 전반의 문제가 아니라 CUBIC 구현의 특정 로직에 문제가 있음을 확인했다. ## Linux TCP의 유휴 상태 최적화 - 문제의 출발점은 2017년 Linux 커널에 적용된 TCP CUBIC 최적화였다. - 기존 구현에서는 CUBIC의 시간 기준점인 `epoch`가 연결 시작 시점과 손실 발생 시점에만 갱신됐다. - 애플리케이션이 오랫동안 유휴 상태에 있다가 다시 전송하면 `현재 시각 - epoch_start` 값이 지나치게 커질 수 있었다. - 그 결과 CUBIC의 목표 전송률과 증가 기울기가 비정상적으로 커지고, `ca->cnt`가 지나치게 작아질 수 있었다. - Linux는 이를 완화하기 위해 `ca->cnt`에 최솟값 2를 적용했으며, 특히 `slow_start_after_idle`이 비활성화된 경우 유휴 후 위험한 cwnd 증가가 발생할 수 있었다. - 이 최적화는 TCP에서 애플리케이션이 전송을 제한한 구간을 혼잡으로 오인하지 않도록 하기 위한 것이었지만, QUIC 구현에 이식되는 과정에서 `bytes_in_flight == 0`인 상황이 예상과 다르게 작동했다. ## 문제의 본질 - CUBIC은 실제 패킷 손실이 없더라도 전송 중인 바이트가 0이 되는 순간을 유휴 또는 특수한 상태로 처리한다. - 하지만 이 테스트에서는 연결이 유휴한 것이 아니라, `cwnd`가 두 패킷으로 제한되어 ACK를 받은 뒤 잠시 `bytes_in_flight`가 0이 되는 상황이 반복됐다. - 결과적으로 CUBIC의 유휴 상태 처리와 회복 상태 전환이 ACK 클록과 결합되어 매 RTT마다 잘못된 상태 변화를 일으켰다. - 그 결과 혼잡 윈도우가 최소값에서 탈출하지 못하고, 손실이 사라진 뒤에도 전송 속도가 회복되지 않았다. ## 실용적인 시사점 - 혼잡 제어 테스트는 정상적인 성장 구간뿐 아니라, `cwnd`가 최솟값으로 떨어진 뒤 회복하는 시나리오도 반드시 포함해야 한다. - `bytes_in_flight == 0`은 실제 애플리케이션 유휴 상태와 ACK 직후의 일시적인 상태를 구분하지 않으면 오판의 원인이 될 수 있다. - TCP 최적화 코드를 QUIC에 이식할 때는 전송 모델과 ACK 처리 방식의 차이를 검증해야 한다. - 이 사례는 상태 전환 횟수와 RTT 상관관계를 관찰하는 qlog 기반 계측이 혼잡 제어 버그를 찾는 데 효과적임을 보여준다.

원문 읽기(새 탭에서 열림)
cloudflare4분 읽기큐레이션 요약

Cloudflare는 “Copy Fail” 리눅스 취약점에 어떻게 대응했나

2026년 4월 29일 공개된 Linux 커널 권한 상승 취약점 “Copy Fail”(CVE-2026-31431)에 대해 Cloudflare는 공개 직후 영향 범위와 탐지 가능성을 분석했다. Cloudflare의 자동화된 커널 업데이트와 기존 행위 기반 탐지 체계 덕분에 취약점 악용을 수분 내 식별할 수 있었으며, 실제 환경·고객 데이터·서비스에는 아무런 영향이 없었다. 핵심 대응은 최신 LTS 커널 패치 적용, 인프라별 노출도 분석, 공격 행위 검증이었다. ## Cloudflare의 Linux 커널 운영 체계 - Cloudflare는 330개 도시에 걸친 대규모 Linux 인프라를 운영한다. - 커뮤니티 Linux LTS 버전을 기반으로 자체 커널을 빌드하며, 여러 LTS 계열을 동시에 사용할 수 있다. - 보안·안정성 업데이트가 반영되면 자동화된 작업이 약 1주일 주기로 내부 커널 빌드를 생성한다. - 새 빌드는 스테이징 데이터센터에서 검증한 뒤, Edge Reboot Release(ERR) 파이프라인을 통해 약 4주 주기로 전 세계 엣지 인프라에 배포·재부팅한다. - CVE가 공개될 때는 수정 사항이 이미 안정화된 LTS 버전에 수 주 전 반영되어 있는 경우가 많아, Cloudflare는 공개 시점에 이미 패치를 배포한 상태인 경우가 많다. - 취약점 공개 당시 대부분의 시스템은 6.12 LTS를 사용했고, 일부는 6.18 LTS로 전환 중이었다. ## AF_ALG와 커널 암호화 API - Linux 커널 암호화 API는 kTLS와 IPsec 같은 기능을 제공한다. - 비권한 사용자 프로세스도 `AF_ALG` 소켓을 통해 암호화·복호화 기능을 요청할 수 있다. - `algif_aead` 모듈은 AEAD 암호 알고리즘을 처리한다. - 일반적인 처리 흐름은 다음과 같다. - `AF_ALG` 소켓을 열고 AEAD 템플릿에 바인딩 - 키를 설정하고 요청 소켓을 생성 - `sendmsg()` 또는 `splice()`로 입력 전달 - `recvmsg()`로 암호화 연산 실행 - 특히 `splice()`는 실제 데이터를 복사하기보다 페이지 캐시의 페이지 참조를 전달하므로, 이번 취약점 악용에 중요한 역할을 했다. ## 페이지 캐시와 인플레이스 암호화의 문제 - Linux 페이지 캐시는 파일 내용을 공유하는 시스템 캐시다. - 페이지 캐시에 있는 파일 페이지가 수정되면, 해당 페이지가 캐시에서 제거되기 전까지 모든 사용자가 수정된 내용을 볼 수 있다. - `algif_aead`는 2017년 성능 개선 과정에서 입력·출력 페이지를 연결해 인플레이스 연산을 수행하도록 최적화됐다. - 그러나 이 구조에는 암호화 알고리즘이 의도된 출력 영역을 넘어 쓰지 못하도록 충분히 제한하는 검증이 없었다. - 그 결과 공격자는 파일의 페이지 캐시를 암호화 scatterlist에 연결해, 파일의 특정 위치에 데이터를 덮어쓸 수 있었다. ## “Copy Fail” 취약점의 작동 원리 - `recvmsg()` 처리 중 `authencesn` 래퍼가 정상적인 출력 영역을 넘어 4바이트를 기록한다. - 공격자는 `splice()`를 이용해 대상 파일의 페이지 캐시 페이지를 scatterlist에 포함시킬 수 있다. - 이때 다음 요소를 공격자가 조절할 수 있다. - 대상 파일: 읽을 수 있는 파일 - 쓰기 위치: `assoclen`과 `splice()` 파라미터로 조정 - 기록 값: `sendmsg()`의 특정 AAD 바이트로 제어 - 기본 공격 대상은 거의 모든 Linux 배포판에 존재하는 setuid-root 바이너리 `/usr/bin/su`다. - 공격자는 해당 파일을 페이지 캐시에 올린 뒤 암호화 API 요청을 구성하고, `recvmsg()` 실행 중 발생하는 범위를 벗어난 쓰기로 바이너리의 코드 영역을 변조한다. - `recvmsg()`가 `-EBADMSG` 오류를 반환하더라도 페이지 캐시에 대한 4바이트 쓰기는 이미 수행될 수 있다. - 이후 변조된 `/usr/bin/su`가 실행되면 setuid 권한으로 인해 삽입된 코드가 root 권한으로 실행될 수 있다. - Linux 업스트림 수정 커밋 `a664bf3d603d`는 문제가 된 2017년 인플레이스 최적화를 되돌려 이 공격 경로를 제거했다. ## Cloudflare의 대응 - 취약점 공개 직후 보안팀과 커널 엔지니어링팀이 병렬로 대응을 시작했다. - 먼저 어떤 커널 버전이 취약한지 확인하고, 각 인프라 구성에서 실제 노출 가능성을 분석했다. - 기존의 자동 커널 빌드·검증·배포 절차를 통해 수정된 LTS 커널을 신속하게 적용할 수 있는 기반을 확보하고 있었다. - 보안팀은 공개된 익스플로잇 기법을 검토해 Cloudflare 환경에서 공격이 가능한지 검증했다. - 기존 행위 기반 탐지 시스템이 `AF_ALG`, `splice()`, 비정상적인 페이지 캐시 조작 등 익스플로잇의 행동 패턴을 수분 내 식별할 수 있음을 확인했다. - 결과적으로 실제 침해, 고객 데이터 노출, 서비스 중단은 발생하지 않았다. ## 실용적인 교훈 - 커널 취약점 대응에서는 단순히 패치를 기다리기보다 자동화된 빌드·테스트·점진적 배포 체계를 평소에 갖추는 것이 중요하다. - LTS 커널을 사용하더라도 여러 버전이 공존할 수 있으므로 자산별 커널 버전과 패치 상태를 지속적으로 관리해야 한다. - 시그니처 기반 탐지만으로는 부족하며, 시스템 호출 조합과 비정상적인 파일·페이지 캐시 접근을 관찰하는 행위 기반 탐지가 효과적이다. - 취약점 공개 직후에는 패치 적용뿐 아니라 익스플로잇 재현, 환경별 노출도 분석, 탐지 검증을 동시에 수행해야 한다.

원문 읽기(새 탭에서 열림)
github4분 읽기큐레이션 요약

GitHub가 eBPF를 사용하여 배포 안전성을 향상시키는 방법

GitHub는 장애 상황에서도 배포를 계속할 수 있도록 배포 코드가 GitHub 자체나 다른 내부 서비스에 의존하는 순환 의존성을 차단하려 한다. 이를 위해 배포 프로세스만 별도 Linux cGroup에 배치하고, eBPF의 `BPF_PROG_TYPE_CGROUP_SKB`를 이용해 해당 프로세스의 네트워크 송신을 선택적으로 감시·차단하는 방식을 검토했다. 이 접근법은 호스트 전체의 네트워크를 막지 않으면서 배포 과정의 외부·내부 의존성을 검증할 수 있다는 점이 핵심이다. ## GitHub 배포의 순환 의존성 - GitHub는 자체 소스 코드를 `github.com`에 보관하므로, GitHub 장애 시 소스 코드 접근과 복구 배포가 동시에 어려워질 수 있다. - 이를 완화하기 위해 다음을 유지한다. - 장애 시 수정 배포를 위한 코드 미러 - 롤백에 사용할 사전 빌드된 배포 자산 - 하지만 배포 스크립트가 새롭게 순환 의존성을 만들 가능성은 여전히 남아 있다. - 내부 서비스 호출 - GitHub에서 바이너리 다운로드 - 실행 중인 도구의 자동 업데이트 확인 등 ## 순환 의존성의 세 가지 유형 ### 직접 의존성 - 배포 스크립트가 GitHub에서 오픈 소스 도구의 최신 릴리스를 직접 다운로드한다. - GitHub 장애로 릴리스 데이터를 제공할 수 없으면 배포 스크립트도 완료되지 않는다. ### 숨은 의존성 - 필요한 도구가 이미 호스트 디스크에 존재하더라도, 실행 시 업데이트 가능 여부를 확인할 수 있다. - 이때 도구가 GitHub에 접속하지 못하면: - 오류를 반환하고 실패하거나 - 네트워크 타임아웃으로 멈출 수 있다. - 코드만 검토해서는 이런 런타임 의존성을 발견하기 어렵다. ### 일시적·간접 의존성 - 배포 스크립트가 내부 마이그레이션 서비스 같은 다른 서비스를 API로 호출한다. - 해당 서비스가 다시 GitHub에서 최신 바이너리를 받으려 하면, 의존성이 여러 단계 뒤에서 발생한다. - 최종적으로 GitHub 장애가 내부 서비스와 배포 스크립트까지 연쇄적으로 실패시킨다. ## 기존 검증 방식의 한계 - 기존에는 각 상태 저장 호스트를 담당하는 팀이 배포 스크립트를 검토해 순환 의존성을 찾아야 했다. - 실제로는 많은 의존성이 장애가 발생한 뒤에야 드러난다. - 가장 단순한 검증 방법은 호스트에서 `github.com` 접근을 전부 차단하는 것이다. - 그러나 해당 호스트는 롤링 배포, 드레인, 재시작 중에도 고객 트래픽을 처리하므로 호스트 전체의 네트워크를 차단하면 운영 기능까지 손상된다. ## cGroup과 eBPF를 이용한 선택적 네트워크 차단 - eBPF는 Linux 커널에 사용자 정의 프로그램을 로드하고 네트워크 같은 핵심 시스템 동작에 연결할 수 있다. - GitHub가 주목한 프로그램 유형은 `BPF_PROG_TYPE_CGROUP_SKB`다. - 특정 cGroup의 네트워크 ingress/egress에 연결 가능 - 특히 프로세스 그룹의 외부 송신 트래픽을 제어할 수 있음 - cGroup은 프로세스 집합에 리소스 제한과 격리를 적용하는 Linux 기능이다. - Docker 전용 기능이 아니며 직접 생성·구성할 수 있다. - 따라서 다음 구조가 가능하다. - 배포 스크립트만 별도 cGroup에 배치 - 해당 cGroup의 egress 트래픽만 eBPF로 감시 - GitHub나 특정 내부 서비스로 향하는 연결만 차단 - 같은 호스트에서 실행 중인 고객 트래픽 처리 프로세스는 계속 네트워크 사용 ## Go와 `cilium/ebpf`를 이용한 구현 - GitHub는 Go 기반 proof of concept을 만들고 `cilium/ebpf` 라이브러리를 사용했다. - 이 라이브러리는 다음 작업을 단순화한다. - eBPF 프로그램과 맵을 읽고 수정 - 프로그램을 컴파일·로드 - 커널의 다양한 hook에 연결 - Go 코드에서는 다음 흐름으로 cGroup에 eBPF 프로그램을 연결한다. - 사전 컴파일된 eBPF 오브젝트와 맵을 커널에 로드 - `/sys/fs/cgroup/system.slice` 같은 cGroup 경로 지정 - `ebpf.AttachCGroupInetEgress`를 사용해 송신 트래픽 hook에 연결 - eBPF 맵에서 패킷 수 등 관측 데이터를 주기적으로 조회 - 예제 eBPF 프로그램은 `BPF_MAP_TYPE_ARRAY` 맵에 값을 저장하고, `cgroup_skb/egress` hook이 호출될 때마다 송신 패킷 수를 증가시키는 구조다. - 이처럼 먼저 트래픽을 관찰한 뒤, 특정 목적지에 대한 연결을 허용하거나 차단하는 정책으로 확장할 수 있다. ## 실용적인 의미 - 배포 시스템은 “호스트 전체를 격리”하는 대신 “배포 프로세스만 제한”할 수 있다. - 장애 대응 전에 GitHub, 내부 API, 자동 업데이트 서버 등 필수 경로에 대한 의존성을 실제 실행 환경에서 검증할 수 있다. - eBPF 기반 필터링은 숨은 의존성과 간접 의존성을 찾아내는 방어 계층으로 활용할 수 있다. - 다만 실제 운영에서는 차단 정책을 적용하기 전에 관찰 모드로 트래픽을 수집하고, 정상적인 고객 트래픽과 배포 트래픽이 정확히 분리되는지 검증하는 것이 바람직하다.

원문 읽기(새 탭에서 열림)
netflix원문

넷플릭스의 마운트 메 (새 탭에서 열림)

넷플릭스는 컨테이너 런타임을 현대화하는 과정에서 수백 개의 컨테이너가 동시에 부팅될 때 시스템이 멈추거나 헬스 체크가 실패하는 심각한 병목 현상에 직면했습니다. 조사 결과, 이는 컨테이너 보안을 위해 도입된 사용자 네임스페이스(User Namespace)의 `idmap` 마운트 작업이 리눅스 커널의 VFS(가상 파일 시스템) 전역 잠금 장치에서 경합을 일으키기 때문으로 밝혀졌습니다. 특히 이러한 현상은 구형 다중 소켓(NUMA) 하드웨어 아키텍처에서 더욱 두드러지게 나타났으며, 최신 단일 소켓 인스턴스로 전환함으로써 스케일링 성능을 크게 개선할 수 있었습니다. **컨테이너 보안 강화와 마운트 폭증의 관계** - 넷플릭스는 보안 강화를 위해 각 컨테이너에 고유한 사용자 범위를 할당하는 새로운 런타임(Kubelet + Containerd)으로 전환했습니다. - 파일 소유권을 실제로 변경하는 비용을 줄이기 위해 커널의 `idmap` 마운트 기능을 사용하는데, 이는 각 레이어마다 `open_tree`, `mount_setattr`, `move_mount` 등의 호출을 발생시킵니다. - 50개의 레이어를 가진 컨테이너 100개를 동시에 실행할 경우, 이론적으로 약 20,000번 이상의 마운트 관련 작업이 수행되며 이는 커널의 마운트 테이블 전역 락(Global Lock)에 엄청난 부하를 줍니다. **커널 및 하드웨어 수준의 병목 현상 진단** - 시스템 분석 결과, CPU는 커널의 `path_init()` 함수 내 시퀀스 락(Sequence Lock)을 기다리는 스핀 루프(Spin Loop)에서 대부분의 시간을 소비하며 'Pause' 명령어를 반복 실행했습니다. - TMA(Topdown Microarchitecture Analysis) 분석에 따르면 파이프라인 슬롯의 95.5%가 경합된 액세스로 인해 중단되었으며, 57%는 가짜 공유(False Sharing)로 인해 발생했습니다. - 여러 코어가 동일한 캐시 라인에 접근하려고 시도하면서 캐시 라인 바운싱(Cache Line Bouncing) 현상이 발생하여 시스템 성능이 급격히 저하되었습니다. **인스턴스 아키텍처에 따른 성능 차이** - 테스트 결과, 5세대 인텔 듀얼 소켓 인스턴스인 `r5.metal`은 100개 이상의 컨테이너가 동시에 실행될 때 성능이 급격히 저하되며 실패하는 모습을 보였습니다. - 반면, 단일 소켓 및 단일 NUMA 도메인을 사용하는 7세대 인스턴스(`m7i.metal-24xl`, `m7a.24xlarge`)는 높은 동시성 환경에서도 훨씬 낮은 지연 시간과 높은 성공률을 유지했습니다. - 이는 NUMA 아키텍처의 프로세서 간 상호 연결(Interconnect) 대기 시간이 전역 락 경합 상황에서 병목 현상을 수배로 증폭시키기 때문입니다. 대규모 컨테이너 환경을 운영한다면 컨테이너 이미지의 레이어 수를 최소화하여 마운트 발생 횟수를 줄여야 합니다. 또한, 컨테이너 생성 및 삭제가 빈번한 워크로드의 경우 다중 소켓 기반의 구형 인스턴스보다는 메모리 접근 대기 시간이 짧고 락 경합에 유리한 최신 단일 소켓 혹은 단일 NUMA 노드 아키텍처를 선택하는 것이 성능 안정성에 유리합니다.

datadog원문

런타임 보안을 위한 eBPF 강화: Datadog Workload Protection의 교훈 (새 탭에서 열림)

Datadog은 지난 5년간 수천 개의 환경에서 eBPF 기반의 런타임 보안 제품인 'Workload Protection'을 운영하며 얻은 실전 경험과 교훈을 공유합니다. eBPF는 기존 커널 모듈이나 감사(Audit) 프레임워크보다 안전하고 효율적이지만, 대규모 운영 환경에서는 커널 호환성이나 성능 오버헤드 같은 복잡한 문제들이 발생합니다. 결론적으로 eBPF는 강력한 도구이나, 실제 운영 환경에서 신뢰성을 확보하기 위해서는 단순한 구현을 넘어 정교한 모니터링과 배포 전략이 필수적입니다. **기존 커널 모니터링 기술의 한계와 평가** * **커널 모듈(LKM):** 시스템의 거의 모든 부분을 제어할 수 있는 강력한 권한을 가지지만, 코드 오류가 커널 전체의 크래시로 이어질 수 있어 안정성 측면에서 위험부담이 큽니다. * **전통적인 트레이싱 인터페이스:** inotify, fanotify, kprobes 등은 시스템 내부를 들여다볼 수 있게 해주지만, 전체적인 시스템 활동을 파악하려면 여러 도구를 복잡하게 조합해야 하는 파편화 문제가 있습니다. * **ptrace 및 seccomp-bpf:** 사용자 공간의 프로세스를 추적하는 데 유용하지만, 모든 프로세스 액세스를 감시하기에는 성능 오버헤드가 발생하며 커널 수준의 가시성이 부족합니다. * **Linux Audit 프레임워크:** 가장 널리 사용되는 보안 솔루션이지만, 대량의 이벤트가 발생할 때 시스템 성능에 상당한 영향을 미치는 단점이 있습니다. **보안 제품에 eBPF를 선택한 핵심 이유** * **검증된 안전성:** eBPF 프로그램은 로드되기 전 커널 검증기(Verifier)를 통해 무한 루프나 잘못된 메모리 접근 여부를 정적으로 분석하므로 커널 모듈보다 훨씬 안전합니다. * **통합 가시성:** 프로세스 실행, 파일 시스템 접근, 네트워크 활동 등을 단일 메커니즘으로 모두 추적할 수 있어 시스템 전반에 대한 통합적인 가시성을 제공합니다. * **컨테이너 최적화:** 네임스페이스(Namespace)와 cgroup에 대한 이해도가 높아 컨테이너 환경에서 일관된 모니터링이 가능하며, 특히 CO-RE(Compile Once – Run Everywhere) 도입으로 배포가 쉬워졌습니다. * **강력한 제어 권한:** BPF LSM 기능을 통해 단순한 모니터링을 넘어 시스템 호출을 차단하는 등의 강제 접근 제어(Mandatory Access Control)를 수행할 수 있습니다. **대규모 생산 환경에서의 운영 교훈** * **커널 호환성 유지:** 특정 커널 버전에서는 작동하지만 다른 버전에서는 실패하는 경우를 방지하기 위해 프로그램 로드 및 부착(Attach) 과정을 정교하게 관리해야 합니다. * **성능 비용 관리:** eBPF가 효율적이긴 하지만, 수많은 훅(Hook)이 동시에 실행될 때 발생하는 성능 비용을 지속적으로 측정하고 제어하는 메커니즘이 필요합니다. * **풍부한 데이터 처리:** 캡처된 원시 데이터를 단순히 전달하는 것이 아니라, 보안 분석에 유용하도록 문맥(Context)을 보강하고 정확하게 강화하는 로직이 중요합니다. * **안전한 변경 배포:** 수천 대의 호스트에 영향을 줄 수 있으므로, eBPF 프로그램의 변경 사항을 안전하게 롤아웃하고 문제 발생 시 즉시 감지할 수 있는 시스템을 갖춰야 합니다. **실용적인 제언** eBPF를 도입할 때 "안전하고 성능 저하가 없다"는 마케팅적 수사에만 의존해서는 안 됩니다. 모니터링하려는 워크로드의 특성에 따라 성능 임팩트가 달라질 수 있으므로, 자체적인 성능 모니터링 지표를 구축하고 커널 버전별로 철저한 회귀 테스트를 거치는 것을 추천합니다.

datadog원문

단순한 또 하나의 네트워크 지연 문제가 아니었다: 숨겨진 병목 현상의 연쇄를 파헤친 과정 (새 탭에서 열림)

사용량 추정 서비스의 배포 시마다 반복되는 높은 시작 지연 시간(Startup Latency) 문제를 해결하기 위해, 시스템 전반의 네트워크 경로와 인프라 계층을 다각도로 조사했습니다. 단순히 애플리케이션 코드를 수정하는 수준을 넘어, 사이드카 프록시 설정, 리눅스 커널 버그, 클라우드 인스턴스의 네트워크 대역폭 한계 등 복합적인 병목 현상을 단계별로 추적해 해결했습니다. 최종적으로 인프라 최적화와 우아한 종료(Graceful Shutdown) 메커니즘을 결합하여 서비스 안정성을 확보하고 팀의 경보 피로도를 대폭 낮추는 결론에 도달했습니다. **Envoy 사이드카의 CPU 병목 해소** * 배포 단계에서 원격 캐시 데이터를 대량으로 불러올 때, 사이드카 프록시인 Envoy가 모든 쿼리를 배치 처리하며 과도한 CPU를 사용함을 확인했습니다. * Envoy가 할당된 CPU 자원(2코어)을 모두 소진하여 쓰로틀링(Throttling)이 발생했고, 이로 인해 패킷 처리 지연과 TCP 재전송(Retransmit)이 급증했습니다. * Envoy에 할당되는 CPU 자원을 늘려 1차적인 지연 시간 수치를 개선했으나, 여전히 배포 중 지연 시간이 300ms에서 1s 사이를 진동하는 문제가 남았습니다. **리눅스 커널 버그 패치 및 트래픽 분산** * 조사 과정에서 AWS의 Elastic Network Adapter(ENA)를 사용할 때 발생하는 리눅스 커널 버그를 발견했습니다. * 해당 버그는 네트워크 트래픽을 8개의 전송 큐(Transmit Queue)에 분산하지 않고 첫 번째 큐에만 몰아넣어 병목을 유발하고 있었습니다. * 트래픽을 모든 큐에 골고루 분산시키는 핫픽스를 적용하여, 배포 기간 외에 간헐적으로 발생하던 지연 시간 스파이크 문제를 해결했습니다. **AWS 인스턴스 네트워크 대역폭 최적화** * 커널 수정 후에도 배포 중 지연이 지속되자 AWS 전용 메트릭인 `bw_in_allowance_exceeded`와 `bw_out_allowance_exceeded`를 분석했습니다. * 분석 결과, 배포 시 발생하는 급격한 트래픽이 인스턴스 유형별로 할당된 최대 네트워크 대역폭을 초과하여 하이퍼바이저 수준에서 패킷 드롭이 발생하고 있었습니다. * 이를 해결하기 위해 더 높은 대역폭을 제공하는 네트워크 최적화(Network-optimized) EC2 인스턴스로 마이그레이션하여 대역폭 제한 문제를 해결했습니다. **종료되는 파드로의 요청 라우팅 방지** * 모든 인프라 개선 후에도 남아있던 1초 가량의 지연 스파이크가 원격 캐시 파드의 종료(Terminating) 시점과 일치함을 포착했습니다. * 기존의 우아한 종료 로직이 Envoy 클라이언트의 처리 중인 요청(In-flight requests)을 충분히 기다리지 못해, 종료 중인 파드에 요청이 전달되어 타임아웃과 재시도가 발생하고 있었습니다. * 파드에 `preStop` 훅을 구현하여 종료 전 유지 관리 모드 상태임을 클라이언트에 알리고, 모든 요청이 완료될 때까지 대기하도록 설정하여 지연 시간을 최종적으로 안정화했습니다. 성능 최적화 과정에서 단일 원인을 찾기보다 네트워크 스택의 각 계층(프록시, OS 커널, 클라우드 인프라, 애플리케이션 생명주기)을 체계적으로 검증하는 접근 방식이 중요합니다. 특히 대규모 트래픽이 발생하는 배포 시점에는 시스템의 숨겨진 한계치가 드러나기 쉬우므로, 클라우드 제공업체의 전용 메트릭과 네트워크 큐 상태를 면밀히 모니터링할 것을 권장합니다.

datadog원문

Dirty Pipe 취약점을 이용해 컨테이너 탈출하기 | Datadog Security Labs (새 탭에서 열림)

리눅스 커널의 Dirty Pipe(CVE-2022-0847) 취약점은 권한이 없는 프로세스가 읽기 전용 파일에 데이터를 쓸 수 있게 하여, 컨테이너 환경에서 호스트의 권한을 탈취하는 '컨테이너 탈출'을 가능하게 한다. 이 글은 Kubernetes 환경에서 runC 바이너리를 덮어쓰는 방식을 통해, 공격자가 격리된 컨테이너를 벗어나 호스트 수준의 관리자 권한을 획득하는 과정을 상세히 설명한다. 이는 과거 runC 취약점 패치가 성능 최적화를 위해 커널 페이지 캐시를 공유한다는 점을 역이용한 결과로, 현대적 컨테이너 런타임 구조 내의 보안 허점을 시사한다. ### 컨테이너 런타임과 OCI 명세의 이해 * Kubernetes는 컨테이너 실행을 위해 containerd나 CRI-O 같은 고수준 런타임을 사용하며, 이들은 내부적으로 runC와 같은 저수준 OCI(Open Container Interface) 런타임을 호출한다. * runC는 리눅스의 네임스페이스와 제어 그룹(cgroups)을 설정하여 프로세스를 논리적으로 격리하며, 최종적으로 `execve` 시스템 콜을 통해 사용자가 지정한 엔트리포인트를 실행한다. * 컨테이너 프로세스가 생성되는 시점에 `/proc/self/exe` 파일 기술자(File Descriptor)를 통해 호스트의 runC 바이너리에 접근할 수 있는 경로가 일시적으로 열리게 된다. ### runC 취약점의 역사적 맥락 * 과거 CVE-2019-5736 취약점은 컨테이너 내부에서 호스트의 runC 바이너리를 직접 수정하여 루트 권한을 획득하는 방식을 사용했다. * 이를 방어하기 위해 runC 개발팀은 바이너리를 복제(clone)하여 실행하거나, 호스트의 runC 바이너리를 읽기 전용으로 마운트하여 컨테이너 내부에 제공하는 패치를 적용했다. * 하지만 Dirty Pipe 취약점은 커널 페이지 캐시를 조작하여 읽기 전용 파일조차 수정할 수 있게 하므로, 성능 향상을 위해 도입된 '읽기 전용 공유 방식'이 오히려 새로운 공격 경로가 되었다. ### Dirty Pipe를 이용한 컨테이너 탈출 메커니즘 * 공격자는 권한이 없는 컨테이너 내부에서 스크립트를 실행하여 호스트의 runC가 다시 실행되기를 기다린다(예: 관리자의 `kubectl exec` 호출). * runC가 실행되는 순간, 공격 프로세스는 `/proc/<runC-pid>/exe` 경로를 통해 Dirty Pipe 취약점을 가동한다. * 이 취약점은 커널 페이지 캐시 수준에서 메모리를 덮어쓰기 때문에, 호스트의 물리적 디스크에 저장된 runC 파일은 건드리지 않으면서도 현재 실행 중인 runC 프로세스를 악성 바이너리로 교체할 수 있다. ### 공격 증명(PoC) 및 실행 과정 * 공격 스크립트는 루프를 돌며 `ps` 명령어로 `/proc/self/exe`를 참조하는 runC 프로세스의 PID를 지속적으로 감시한다. * 대상 PID가 발견되면 Dirty Pipe 익스플로잇 코드를 실행하여, 해당 프로세스가 참조하는 바이너리 데이터를 호스트 권한으로 실행될 악성 ELF 파일로 덮어쓴다. * 조작된 runC는 호스트 시스템에서 루트 권한으로 실행되며, 공격자가 의도한 명령(예: 호스트의 `/tmp/hacked` 파일 생성 등)을 수행한 뒤 호스트 전체를 장악할 수 있게 한다. ### 보안 결론 및 대응 방안 * 본 취약점은 컨테이너 격리 기술 자체가 아닌 리눅스 커널의 메모리 관리 결함에서 비롯된 것이므로, 가장 확실한 해결책은 Dirty Pipe 보안 패치가 적용된 최신 커널 버전으로 노드를 업데이트하는 것이다. * 컨테이너 환경에서는 `/proc` 파일 시스템에 대한 비정상적인 접근을 모니터링하고, 불필요한 고권한(Privileged) 컨테이너 사용을 지양하는 보안 정책이 병행되어야 한다. * 시스템 재부팅이나 캐시 초기화 시 조작된 페이지 캐시가 사라져 공격 흔적이 휘발될 수 있으므로, 실시간 침입 탐지 시스템을 통한 조기 대응이 중요하다.