안녕하세요. ManVSCloud 김수현입니다.
오늘은 최근 제 블로그가 간헐적 끊김이 있어 확인 후
Memory 부족 원인을 파악하고 해결까지에 대한 내용을 작성해보았습니다.
Symptom & Identify the cause
어제 블로그에 포스팅 후 블로그 페이지가 정상적으로 출력되지 않는 현상이 발생했습니다.
서버 접속 시 Cannot allocate memory 에러가 발생하는 중이었으며 명령어 입출력이 정상적이지않은 상태라 우선 인스턴스 재부팅을 진행하게 되었습니다.
-bash: fork: Cannot allocate memory
대시보드에서 자원 사용률을 살펴보았는데 다른 자원 사용률은 정상인데 유독 Memory만 90% 이상 사용 중!! Memory 문제가 있는 것을 알게되었습니다.

VSZ, RSS 확인 시 Apache, PHP에서만 대량 사용 중이라 웹 로그를 먼저 확인해보았습니다.
1주일 단위로 logrotate가 걸려있어 최근 로그만 파악할 수 있는 상태였는데 우선 공격성으로 대량 접근하는 IP가 존재하는지 확인하였고 의심되는 IP는 WAF에서 추가 검토하였는데 따로 공격성 접근은 아닌 것으로 파악되었습니다.
[root@ip-10-0-1-68 httpd]# cat /var/log/httpd/access_log | awk '{print $1}' | sort -n | uniq -c | grep -Ev "10.0|172.16|192.168|-" | sort -rn | head -n 10 399 88.150.-.- 319 121.125.-.- 200 125.141.-.- 186 63.141.-.- 83 66.249.-.- 75 66.249.-.- 49 117.111.-.- 46 52.79.-.- 37 66.249.-.- 36 45.146.-.-
sar를 이용하여 추가 검토를 진행하였습니다.
인스턴스 재시작 후에도 메모리 사용량이 80% 이상 계속 유지 중이었습니다.
[root@ip-10-0-1-68 ~]# sar -r Linux 4.14.193-149.317.amzn2.x86_64 (ip-10-0-1-68.ap-northeast-2.compute.internal) 05/22/2021 _x86_64_ (1 CPU) 12:00:02 AM kbmemfree kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty 12:10:01 AM 65860 941080 93.46 0 103936 4407680 437.73 785580 28120 120 12:20:01 AM 58924 948016 94.15 0 106820 4414144 438.37 789028 31556 176 12:30:01 AM 66768 940172 93.37 0 103328 4410048 437.97 784968 28244 208 12:40:01 AM 64996 941944 93.55 0 102724 4410256 437.99 787600 26904 232 12:50:01 AM 61056 945884 93.94 0 103356 4414996 438.46 792132 26448 40 01:00:01 AM 68052 938888 93.24 0 96388 4412948 438.25 787572 24756 112 01:10:01 AM 63336 943604 93.71 0 100240 4414108 438.37 789356 27116 112 01:20:02 AM 65780 941160 93.47 0 102172 4408852 437.85 785084 29272 112 01:30:01 AM 61096 945844 93.93 0 108252 4408644 437.83 . . [생략] . 08:26:04 AM LINUX RESTART 08:36:33 AM LINUX RESTART 08:40:02 AM kbmemfree kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty 08:50:01 AM 192852 814088 80.85 2088 365732 3775100 374.91 401104 280868 296 09:00:01 AM 203284 803656 79.81 2088 378252 3162244 314.04 376764 289192 328 09:10:01 AM 195364 811576 80.60 2088 378620 3171048 314.92 384684 289380 332 09:20:01 AM 194748 812192 80.66 2088 379568 3173428 315.16 383840 290344 328 09:30:01 AM 181256 825684 82.00 2088 379744 3187564 316.56 407336 280344 328 Average: 193501 813439 80.78 2088 376383 3293877 327.12 390746 286026 322
마지막으로 free 명령어로 확인해보니 buff/cache가 비워지지 않고 그대로 남아있는 상태였습니다.
[root@ip-10-0-1-68 ~]# free -m total used free shared buff/cache available Mem: 983 448 125 60 408 338 Swap: 0 0 0
우선 아래 명령어를 이용하여 캐시 메모리를 지우고 메모리 확보를 진행 하였습니다.
[root@ip-10-0-1-68 ~]# sync && echo 3 > /proc/sys/vm/drop_caches
[root@ip-10-0-1-68 ~]# free -m total used free shared buff/cache available Mem: 983 435 365 60 182 358 Swap: 0 0 0
(sync && echo 3 > /proc/sys/vm/drop_caches 명령어에 대해서는 아래에서 추가적인 설명을 하도록하겠습니다.)
이후 메모리는 정상화 되었으나 추가적인 조치를 하지 않으면 동일한 장애가 다시 발생할 수 있어 우선 swap을 생성하기로 했습니다.
Create Swap


[root@ip-10-0-1-68 ~]# fdisk -l Disk /dev/xvda: 15 GiB, 16106127360 bytes, 31457280 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: gpt Disk identifier: 66F46060-ED00-4D14-9841-F5CB6310A14A Device Start End Sectors Size Type /dev/xvda1 4096 31457246 31453151 15G Linux filesystem /dev/xvda128 2048 4095 2048 1M BIOS boot Partition table entries are not in disk order. Disk /dev/xvdb: 3 GiB, 3221225472 bytes, 6291456 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes
연결이 잘됐는지 확인 후 이제 파티션을 아래와 같이 생성해주었습니다.
[root@ip-10-0-1-68 ~]# fdisk /dev/xvdb Welcome to fdisk (util-linux 2.30.2). Changes will remain in memory only, until you decide to write them. Be careful before using the write command. Device does not contain a recognized partition table. Created a new DOS disklabel with disk identifier 0xb9e2e385. Command (m for help): n Partition type p primary (0 primary, 0 extended, 4 free) e extended (container for logical partitions) Select (default p): p Partition number (1-4, default 1): 1 First sector (2048-6291455, default 2048): Last sector, +sectors or +size{K,M,G,T,P} (2048-6291455, default 6291455): Created a new partition 1 of type 'Linux' and of size 3 GiB. Command (m for help): t Selected partition 1 Hex code (type L to list all codes): 82 Changed type of partition 'Linux' to 'Linux swap / Solaris'. Command (m for help): w The partition table has been altered. Calling ioctl() to re-read partition table. Syncing disks. [root@ip-10-0-1-68 ~]# fdisk -l | tail -n 9 Disk /dev/xvdb: 3 GiB, 3221225472 bytes, 6291456 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0xb9e2e385 Device Boot Start End Sectors Size Id Type /dev/xvdb1 2048 6291455 6289408 3G 82 Linux swap / Solaris
swap 파티션이 잘 생성되었다면 mkswap으로 swap을 만들어주고 blkid에 나온 UUID를 참고하여 /etc/fstab에 추가해줍니다.
이 부분을 잘못 추가하게될 경우 정상 부팅이 되지않아 번거로운 추가 작업이 생길 수 있으니 잘 추가해줍시다.
[root@ip-10-0-1-68 ~]# mkswap /dev/xvdb1 [root@ip-10-0-1-68 ~]# blkid /dev/xvdb1 /dev/xvdb1: UUID="dc082f63-27d8-41b1-ad74-8b019162f766" TYPE="swap" PARTUUID="b9e2e385-01" [root@ip-10-0-1-68 ~]# vi /etc/fstab UUID=dc082f63-27d8-41b1-ad74-8b019162f766 swap swap defaults 0 0 [root@ip-10-0-1-68 ~]# swapon -a [root@ip-10-0-1-68 ~]# swapon -s Filename Type Size Used Priority /dev/xvdb1 partition 3144700 0 -2
swapon 명령어에 -a 옵션을 주면 /etc/fstab에 있는 swap 파티션을 모두 enable 합니다.
-s 옵션은 사용중인 스왑 장치를 요약하여 볼 수 있습니다.
저는 볼륨을 2개 추가하여 하여 swap 볼륨 하나가 장애가 발생하더라도 나머지 하나로 작동할 수 있도록 해두었습니다.
[root@ip-10-0-1-68 ~]# swapon -s Filename Type Size Used Priority /dev/xvdb1 partition 3144700 0 -2 /dev/xvdc1 partition 3144700 0 -3 [root@ip-10-0-1-68 ~]# free -m total used free shared buff/cache available Mem: 983 461 332 60 188 328 Swap: 6141 0 6141
Swapfile
swap 볼륨을 따로 추가할 수 없을 수도 있습니다.
그때는 swapfile을 만들어서 사용할 수 있는데 간단하게 swapfile을 만드는 방법만 설명하겠습니다.
// dd 명령어를 이용해서 용량 지정 및 swap파일을 만들어줍니다. (mkdir로 디렉토리 만들어두는 것 아닙니다.) [root@manvscloud-test-pub-kr2 ~]# dd if=/dev/zero of=/swapfile bs=1MiB count=4096 4096+0 records in 4096+0 records out 4294967296 bytes (4.3 GB) copied, 12.3267 s, 348 MB/s //권한 변경 [root@manvscloud-test-pub-kr2 ~]# chmod 600 /swapfile //스왑 생성 [root@manvscloud-test-pub-kr2 ~]# mkswap /swapfile Setting up swapspace version 1, size = 4194300 KiB no label, UUID=4e0d57c0-dd8f-4527-af2d-659ce04921b7
이후 /etc/fstab에 등록해주고 swapon -a 해주는 것은 동일합니다.
/swap01 swap swap defaults 0 0
sync && echo 3 > /proc/sys/vm/drop_caches
Linux에서 아래 명령어를 이용하여 캐시를 비울 수 있습니다.
- PageCache만 비우기
sync && echo 1 > /proc/sys/vm/drop_caches
(= sync; echo 1 > /proc/sys/vm/drop_caches) - dentries와 inodes 비우기
sync && echo 2 > /proc/sys/vm/drop_caches
(= sync; echo 2 > /proc/sys/vm/drop_caches) - PageCache, dentries, inodes 모두 비우기
sync && echo 3 > /proc/sys/vm/drop_caches
(= sync; echo 3 > /proc/sys/vm/drop_caches)
sync를 통해 파일 시스템 버퍼를 플러시합니다.
(개발쪽은 잘 모르지만 JAVA나 Python에서 flush() 함수가 버퍼 비우는 용도로 사용되는걸 본 적이 있습니다)
단 echo 3을 사용한다면 모두 비우기가 가능하지만 디스크 I/O가 활발한 시스템에서 사용하게 된다면 CPU 자원 사용량이 일시적으로 상승합니다.
- pagecache : 리눅스 커널에 의해 사용되는 기본 디스크 캐시.
대부분의 경우 커널은 디스크에서 읽거나 디스크에 쓸 때 페이지 캐시를 참조합니다.
I/O 속도와 퍼포먼스를 높이기 위해 시스템이 할당한 임시 메모리 저장소로 임시 메모리에 저장된 파일을 다시 읽을 때 해당 메모리에서 바로 불러오기에 디스크의 읽기,쓰기 속도가 빨라집니다. (Windows에서는 동일한 역할로 페이지 파일이 있습니다.) - dentries : dcache, 즉 디렉토리 항목 캐시입니다. 경로/파일명을 특정 dentry로 변환하여 빠른 조회를 제공하며 디스크가 아닌 RAM에 저장됩니다. (inode를 검색하여 수행됩니다.)
- inodes : 파일과 디렉토리에 관한 정보를 담고 있는 자료 구조
(물리적 위치, 크기, 생성 일시, 권한)
[root@ip-10-0-1-68 ~]# vmstat -m | head -n 1; vmstat -m | grep proc_inode_cache Cache Num Total Size Pages proc_inode_cache 13570 13570 688 23
⬆ (위 명령어로 inode 캐시값 확인이 가능합니다.)
※ TIP-1 (Slab) ?
서비스 프로세스 할당량을 계산해보아도 전체 메모리에 비해 유휴 메모리가 없을 때가 있을 것입니다. 이는 커널에서 메모리를 사용하고 있기때문인데 커널에서 사용하고 있는 메모리는 slab 메모리 영역을 확인하면 얼마나 사용하고 있는지 알 수 있습니다.
[root@ip-10-0-1-68 ~]# cat /proc/meminfo | grep Slab Slab: 48832 kB
Slab에 대한 내용이 깊이있게 설명되어 있는 곳은 링크로 남겨두었으니 참고바랍니다.
- Slab : 메모리 영역 중 커널이 직접 사용하는 영역을 Slab영역이며 dentries와 inodes 캐시가 이 영역에 해당됩니다.
(slabtop 명령어를 이용하여 현재 시스템의 slab 자료 구조 상태를 확인할 수 있습니다.)
[root@ip-10-0-1-68 ~]# slabtop Active / Total Objects (% used) : 232162 / 241799 (96.0%) Active / Total Slabs (% used) : 7662 / 7662 (100.0%) Active / Total Caches (% used) : 74 / 101 (73.3%) Active / Total Size (% used) : 55501.21K / 58750.71K (94.5%) Minimum / Average / Maximum Object : 0.01K / 0.24K / 9.50K OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME 39580 39403 99% 0.20K 1979 20 7916K vm_area_struct 38080 38080 100% 0.06K 595 64 2380K anon_vma_chain 30072 30072 100% 0.19K 1432 21 5728K dentry 19136 19089 99% 0.09K 416 46 1664K anon_vma 15504 14550 93% 0.04K 152 102 608K Acpi-Namespace 15210 15210 100% 0.13K 507 30 2028K kernfs_node_cache 13570 13570 100% 0.67K 590 23 9440K proc_inode_cache 10868 10476 96% 0.60K 418 26 6688K inode_cache 7808 6525 83% 0.03K 61 128 244K kmalloc-32 5746 4799 83% 0.94K 169 34 5408K xfs_inode 5248 3838 73% 0.06K 82 64 328K kmalloc-64 5180 3660 70% 0.57K 185 28 2960K radix_tree_node 3822 2783 72% 0.09K 91 42 364K kmalloc-96 3264 2517 77% 0.25K 102 32 816K kmalloc-256 3248 3248 100% 0.07K 58 56 232K Acpi-Operand 3072 3072 100% 0.02K 12 256 48K kmalloc-16 3060 3060 100% 0.05K 36 85 144K ftrace_event_field 2560 2560 100% 0.01K 5 512 20K kmalloc-8 2037 1762 86% 0.19K 97 21 388K kmalloc-192 1610 1610 100% 0.09K 35 46 140K trace_event_file 1536 1258 81% 0.50K 48 32 768K kmalloc-512 1344 1046 77% 0.12K 42 32 168K kmalloc-128
※ TIP-2 (vfs_cache_pressure) ?
vfs_cache_pressure는 리눅스 커널의 vm구조와 관련된 파라미터입니다.
vfs_cache_pressure를 이용해서 커널에게 디렉토리와 inode Object에 대한 캐시로 사용된 메모리를 반환하도록하는데 이 값이 0일 경우 커널이 오브젝트에 대한 캐시를 반환하지 않아 Out Of Memory가 발생합니다.
보통 이 값이 100으로 설정되어 있으나 자주 캐시를 반환하기 위해서는 이 값을 변경해주어야하며 값이 높아질수록 메모리에서 캐시를 보관하지 않게되어 dentries와 inodes 캐시를 줄일 수 있습니다.
[root@ip-10-0-1-68 ~]# cat /proc/sys/vm/vfs_cache_pressure 100
기본값은 100으로 되어있습니다.
/proc/sys/vm/vfs_cache_pressure 에 값을 지정해주는 것은 일회성이니 sysctl.conf에 설정해줍니다.
[root@ip-10-0-1-68 ~]# vi /etc/sysctl.conf 이후 아래값을 추가 및 저장, load 해줍니다 vm.vfs_cache_pressure = 10000 [root@ip-10-0-1-68 ~]# sysctl -p
Personal Comments and Wrap-up
아직 캐시 메모리를 비우고 swap 생성만 해두었습니다.
이걸로 완벽하게 해결됐다고 보기는 어렵습니다. 이후 메모리 사용량 경보를 추가하여 알림 설정을 하고 현재 오토스케일링 추가를 생각하고 있습니다.
(우선 오토스케일링 추가 전 서버 내 자원 최적화 작업을 먼저 진행해야겠습니다.)

그리고 이번 메모리 최적화에 도움주신 리눅서님에게 감사의 인사올립니다.
(아… 난 왜 swap을 잊고있었지? 매번 감사합니다…??♂️ 덕분에 이번 기회로 메모리에 대해 많이 공부한 것같습니다.)
No Comments