brunch

You can make anything
by writing

C.S.Lewis

by 강진우 Sep 18. 2016

nss-softokn과 slab 메모리 누수

Linux Internals

오늘은 실제 서버 운영 중 있었던 slab 메모리 누수에 대해 이야기해 보려고 합니다. 전부터 써야지 써야지 했던 내용인데, 시간이 없어서 못쓰고 있었네요 ^^;; 그럼 시작합니다.


메모리 누수의 발견


어느 날 서버에서 메모리 사용률이 90%가량 된다는 알럿 메시지를 받았습니다. 딱히 메모리를 많이 사용할 이슈가 없는 서버인데, 뭔가 이상하다는 생각이 들었습니다. 그리고 아래는 해당 서버의 메모리 그래프입니다. 우측에 메모리 사용률이 내려간 건 조치를 한 후였고요, 그래프를 전체적으로 살펴보면 매우 빠른 시간 동안에 메모리 사용률이 선형적으로 증가했다는 것을 알 수 있습니다.

메모리 그래프

무슨 조치를 했을까요?


조치 내역


서버에 접속하자마자 free 명령으로 메모리의 사용률을 확인했습니다. (free 명령에 대한 캡처 내용은 없네요..ㅠㅠ) 실제로도 메모리의 사용률이 90%를 육박하는 상태였습니다. 오탐이 아님을 확인한 후, /proc/meminfo를 살펴봤습니다. 그런데, 이상한 수치를 확인할 수 있었습니다. slab 영역이 비정상적으로 상당한 크기를 차지하고 있었습니다. 역시 캡처한 내용은 없지만 7GB 정도의 크기를 차지하고 있었습니다. 그중에서도 dentry cache가 6GB 정도 차지하고 있었습니다. 아무리 I/O 가 많다고 해도 사실 저 정도의 크기는 비정상적인 크기입니다. 게다가 이 서버는 그렇게 I/O가 많은 서버가 아니었습니다. 우선 서버의 메모리를 확보해야 했기 때문에 아래 명령을 통해서 slab 메모리 영역을 비웠습니다.

echo 2 > /proc/sys/vm/drop_caches

하지만 시간이 지나면 다시 차오르는 현상이 반복되었습니다. 뭔가 문제가 남아 있는 상태였습니다.


strace, 추적의 시작


해당 서버는 jenkins가 돌아가는 서버였습니다. 다른 프로세스는 없었고 jenkins를 통해서 CI 등의 작업을 하는 서버였습니다. 그래서 jenkins 가 어떤 작업을 하는지 어디서 문제가 발생할 수 있을지를 찾아야 했습니다. 이를 하기 위해서는 여러 가지 방법이 있겠지만 strace 만한 게 없었습니다. strace로 시스템 콜 덤프를 확인하던 중 인상적인 부분을 발견했습니다.

29911 10:24:00 execve("/usr/bin/curl", ["curl", "-X", "GET", "--header", "Accept: application/json", "https://XXXXX"], [/* 70 vars */] <unfinished ...>

그리고 그 이후로 아래와 같은 시스템 콜이 잡혔습니다.

29911 10:24:00 access("/home/XXX/.pki/nssdb/.205485873_dOeSnotExist_.db", F_OK) = -1 ENOENT (No such file or directory)
29911 10:24:00 access("/home/XXX/.pki/nssdb/.205485874_dOeSnotExist_.db", F_OK) = -1 ENOENT (No such file or directory)
29911 10:24:00 access("/home/XXX/.pki/nssdb/.205485875_dOeSnotExist_.db", F_OK) = -1 ENOENT (No such file or directory)
29911 10:24:00 access("/home/XXX/.pki/nssdb/.205485876_dOeSnotExist_.db", F_OK) = -1 ENOENT (No such file or directory)
29911 10:24:00 access("/home/XXX/.pki/nssdb/.205485877_dOeSnotExist_.db", F_OK) = -1 ENOENT (No such file or directory)
29911 10:24:00 access("/home/XXX/.pki/nssdb/.205485878_dOeSnotExist_.db", F_OK) = -1 ENOENT (No such file or directory)

slab 메모리 중 dentry cache의 경우는 access() 시스템 콜을 통해 생성되기 때문에 위와 같이 동작하는 부분이 dentry cache를 증가시키는 것임을 확인할 수 있었습니다. 엄청난 양의 access() 시스템 콜이 불려졌고 이 순간 dentry cache 역시 증가 했습니다.

그래서 구글에서 slab 메모리 릭을 검색해 보면 결국 아래와 같은 버그 질라 페이지를 만날 수 있습니다. (https://bugzilla.redhat.com/show_bug.cgi?id=1044666) 그리고 해당 이슈가 이번 상황과 정확히 일치합니다.


NSS_SDB_USE_CACHE 환경 변수 설정


그래서 2014년 경에 nss-softokn 라이브러리는 패치가 되었습니다. (https://rhn.redhat.com/errata/RHBA-2014-1378.html) sdb_init() 함수 내부에서 NSS_SDB_USE_CACHE 환경 변수가 설정되어 있으면 sdb_measureAccess() 함수를 호출하지 않도록 되었습니다.

lib/softoken/sdb.c

하지만 주의해야 할 점은, NSS_SDB_USE_CACHE 환경 변수가 없으면 여전히 sdb_measureAccess() 함수를 호출하기 때문에 반드시 해당 환경 변수를 설정해야 한다는 것입니다. 그래서 nss-softokn 라이브러리 업데이트 만으로는 효과가 없습니다. /etc/profile 등과 같이 서버 전역으로 설정될 수 있는 환경 변수로 설정해 주어야 합니다. 게다가 jenkins의 경우는 execve()를 이용하기 때문에 jenkins 내부의 환경 변수로도 설정해 주어야 합니다. execve() 시스템 콜은 세번째 인자를 통해서 환경 변수를 받게 되는데 이 때 전달 받은 환경 변수가 시스템 전역 설정을 덮어 쓰기 때문에 시스템 전역 변수로 설정해 주었다고 해도 효과를 받지 못합니다.

int execve(const char *filename, char *const argv[], char *const envp[]);

그래서 jenkins와 같이 execve()를 사용하는 경우는 내부 환경 변수로도 지정해 주어야 합니다. (이 방법은 애플리케이션 별로 다르기 때문에 잘 확인해 봐야 합니다.)


정리하며


이번 글을 통해서 slab 메모리 누수에 관해 살펴봤습니다. 정리하자면 아래와 같습니다.


1. libcurl + nss_softokn 라이브러리의 조합에서 발생하는 건이며 curl을 통해서 https 주소를 호출할 때 발생하는 이슈입니다.

2. 운영 중인 서버에서 slab 메모리가 선형적으로 증가하는 패턴을 보인다면 반드시 nss-softokn 라이브러리 이슈를 확인해야 하며 NSS_SDB_USE_CACHE 환경 변수를 설정 함으로써 메모리 누수를 피할 수 있습니다.


또한 strace를 통해서 추적하는 과정도 함께 포함시켰는데요, strace는 엔지니어의 관점에서 애플리케이션의 동작 원리를 파악할 수 있게 해주는 툴이기 때문에 반드시 익혀두어야 할 툴 중에 하나입니다.


긴 글 읽어 주셔서 감사합니다. (__)

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari