운영체제와 커널 차원에서의 튜닝과 보안에 대해서 중요한 부분을 설명하겠다. 보안도 다루기는 하지만 일반적인 차원에서의 보안에 대한 내용은 뺐다. 일반적인 보안에 대해서는 잘 다룬 서적이나 자료들이 많기 때문에 중복된 내용을 설명하기보다는 다른 곳에서는 자세히 다루지 않았지만 중요한 부분들을 설명하는게 나을 것 같다. 먼저 파일시스템 설정과 관련된 부분에 대해서 설명을 하고 운영체제 및 커널 차원의 튜닝과 보안에 대해서 설명을 하겠다.
1. 파일 시스템 관련
/etc/fstab에는 파일 시스템 관련된 정보가 들어있다.
$ cat /etc/fstab
/dev/sda2 /home ext2 defaults,rw,nosuid,nodev,noexec 1 2
/dev/sda3 /var ext2 defaults,rw,nosuid,nodev,noexec,noatime 1 2
/dev/sda4 /tmp ext2 defaults,rw,nosuid,nodev,noexec 1 2
여기서 세 번째 줄은 해당 파티션에 대한 제어정보가 들어있는데 의미는 다음과 같다.
default : quota, read-write, suid, async
noquota : 쿼터 사용하지 않음
nosuid : SUID/SGID 설정을 할 수 없음
nodev : 문자나 블락 특수 디바이스 설정을 할 수 없음
noexec : 바이너리 파일의 실행 권한을 주지 않음
quota : 쿼터 설정
ro : 읽기 전용 파일시스템
rw : 읽기/쓰기 파일시스템
suid : SUIG/SGID 설정 허용
noatime : atime(파일 접근 시간)을 업데이트하지 않음
리눅스 및 유닉스에서는 파일 생성 시간 및 마지막 변경시간과 함께 마지막 접근시간을 계속 기록한다. 그렇지만 자주 접근하고 변경이 되는 파일에 대해서는 파일 접근시간이 필요하지 않다면 굳이 마지막 파일 접근시간을 기록하지 않아도 된다. 웹서버, 캐쉬파일, 로그파일 등 주로 읽기전용 파일이 이에 해당할 것이다. 이 설정만으로 성능 병목현상의 가장 큰 주범인 디스크 I/O를 줄이는 데에 도움이 될 수 있다. 그렇지만 파일이 변경된 경우에는 업데이트가 되니 걱정하지 말기 바란다. (chattr 프로그램을 이용하여 개별 파일에 대해서도 지정할 수 있다)
nosuid는 SUID,SGID를 하지 못하도록 막는다. SET-UID 프로그램은 보안상 문제가 생길 여지가 많으며 루트 계정으로 접근할 수 있는 지름길이 되는 경우가 많다. 불가피하게 사용해야 할 경우가 있지만 그 외에는 nosuid 설정을 통해 보안을 강화하는게 좋다. 레드햇 계열에서 SET-UID root 프로그램은 다음과 같은 것들이 있다.
# find / -type f \( -perm -04000 -o -perm -02000 \) \-exec ls -alF {} \;
-rwsr-xr-x 1 root root 14188 Mar 7 2000 /bin/su*
-rwsr-xr-x 1 root root 86690 Oct 20 2000 /bin/ping*
-rwsr-xr-x 1 root root 62192 May 4 09:45 /bin/mount*
-rwsr-xr-x 1 root root 28432 May 4 09:45 /bin/umount*
-r-sr-xr-x 1 root root 15688 Dec 1 2000 /sbin/pwdb_chkpwd*
-r-sr-xr-x 1 root root 16312 Dec 1 2000 /sbin/unix_chkpwd*
-rwsr-xr-x 1 root root 22232 Feb 22 10:21 /usr/bin/crontab*
-rws--x--x 1 root root 14056 Jan 11 2001 /usr/bin/chfn*
-rws--x--x 1 root root 13800 Jan 11 2001 /usr/bin/chsh*
-rws--x--x 2 root root 563164 Aug 9 2000 /usr/bin/suidperl*
-rws--x--x 2 root root 563164 Aug 9 2000 /usr/bin/sperl5.00503*
-rwxr-sr-x 1 root man 36368 May 23 13:25 /usr/bin/man*
-r-s--x--x 1 root root 12244 Feb 8 2000 /usr/bin/passwd*
-rwxr-sr-x 1 root mail 11620 Feb 8 2000 /usr/bin/lockfile*
-rwsr-sr-x 1 root mail 76432 Feb 8 2000 /usr/bin/procmail*
-rwxr-sr-x 1 root slocate 23024 Dec 21 2000 /usr/bin/slocate*
-rwsr-xr-x 1 root root 198312 Nov 10 2000 /usr/bin/ssh1*
-rws--x--x 1 root root 5640 Jan 11 2001 /usr/bin/newgrp*
-rwxr-sr-x 1 root tty 8332 Jan 11 2001 /usr/bin/write*
-rwxr-sr-x 1 root utmp 6096 Feb 25 2000 /usr/sbin/utempter*
-rws--x--x 1 root root 9568 Mar 29 18:30 /usr/sbin/suexec*
-rwsr-sr-x 1 root mail 369340 Jun 19 14:31 /usr/sbin/sendmail*
-rwsr-xr-x 1 root root 17704 Oct 7 2000 /usr/sbin/traceroute*
-rwsr-xr-x 1 root root 34751 Jan 16 00:50 /usr/libexec/pt_chown*
여기서 불필요한 파일은 chmod를 이용 설정을 바꾸어야 한다. chage, gpasswd, wall, chfn, chsh, newgrp, write, usernetctl, traceroute, mount, umount, ping, netreport 등이 여기에 해당한다.
그런데 위의 프로그램은 서비스를 제공하기 위해 어쩔 수 없는 경우이지만 그러한 경우가 아니라면 특정한 파티선에 nosuid를 사용해서 계정 등의 사용자가 장난을 치는 경우를 막을 수 있다. 여기에 noexec까지 주면 바이너리 파일을 실행하지 못하기 때문에 더 안전하게 시스템을 유지할 수 있다. noexec를 준 파티션은 루트도 바이너리 파일을 실행하지 못한다. quota를 이용해서 사용자가 제한되어 있는 하드 디스크 공간을 모두 차지하는 일을 막을 수 있다. 쿼터 설정은 여기서는 생략을 하겠다. async 옵션은 파일시스템에 대한 I/O를 비동기적으로 메모리에 버퍼링하는 것인데 기본값으로 되어 있다. 디스크 접근 시간을 최소화하기 위한 부분이므로 바꿀 필요가 없다. 이것을 sync로 바꾸면 메모리에서 변경된 자료가 바로 디스크 I/O에 반영이 되므로 속도가 얼마나 느려질지 장담할 수 없다.
2. 보안관련 커널 설정
커널과 관련된 옵션은 /proc에서 직접 값을 써 줄 수도 있고 /etc/sysctl.conf 파일을 이용하여 바꿀 수도 있다. 커널과 관련된 옵션은 그냥 무조건 사용하는게 아니라 자신의 환경을 생각해야하고 그 원리를 제대로 이해했을 때 사용하는 것이 좋다. 그런데 이 작업이 그렇게 만만한 작업은 아니고 운영체제와 네트웍에 대해서 많은 지식이 있어야 이해가 가능하다. 여기서는 주로 많이 사용되는 커널 옵션에 대해서 살펴보겠다. 커널과 관련된 설정은 sysctl 프로그램을 이용하여 편리하게 설정할 수 있다. sysctl -a 하면 현재 설정된 값이 나오고 -w 옵션을 이용하여 특정값을 저장할 수 있다. 재부팅후에도 계속 사용하려면 /etc/sysctl.conf 파일에 저장하여 사용하면 된다.
ㅇ ping 요청에 반응하기 막기 : ping 요청에 반응하는 것을 막을 수 있다. 외부에서 누구도 서버에 핑을 보낼 수도 없고 보내도 반응을 하지 않기에 보안상으로 도움이 될 수 있고 네트웍 보안을 향상시킬 수 있다. 그러나 원격에서 서비스를 관리하는 경우 간단하게 시스템의 상태를 확인할 수 있는 수단을 막는 결과를 초래할 수도 있다.
net.ipv4.icmp_echo_ignore_all = 1
ㅇ Broadcast 로 오는 핑을 차단하기 : 브로드캐스트 주소에 ping을 쏘는 것을 막는다. smurf 공격 방지용이다. 스머프 공격은 에코 리퀘스트 패킷을 어떤 네트웍의 브로드캐스트 주소로 보내면 해당 네트워크의 모든 호스트가 request 패킷에 응답하느라 다른 일은 하지 못하는 것을 말한다. 그렇지만 정작 문제는 해당 네트워크의 호스트가 아니다. 이 엄청난 개수의 컴퓨터들에서 일시에 어느 한 곳으로 ICMP echo replay 패킷을 보내면 패킷을 보냈던 시스템은 마비상태로 가기 싶다. 하지만 이미 출발지 주소 자체가 실제 패킷을 보낸 시스템이 아니라 공격을 할 호스트로 바뀌어있는 상태일 것이다. (IP 스푸핑)
net.ipv4.icmp_echo_ignore_broadcasts = 1
ㅇ IP 소스 라우팅 막기 : 라우팅과 라우팅 프로토콜은 몇가지 문제를 생성해낸다. RFC 1122에 따르면 목적지의 경로에 대한 세부적인 내용을 담고 있는 IP 소스 라우팅은 목적지 호스트에서도 같은 경로를 따라 반응을 해야 하다는 문제점이 있다. 크래커가 특정 네트웍에 소스 라우팅 패킷을 보낼 수 있다면 돌아오는 반응을 가로채서 상대방의 호스트와 신뢰받은 호스트처럼 속일 수 있다. 기본값이 1이므로 0으로 바꾼다.
net.ipv4.conf.all.accept_source_route = 0
ㅇ backlog 늘리기와 syncookie 기능 사용하기 : SYN Attack 은 서비스 거부 공격(DOS)의 하나로 공격할 시스템의 모든 자원을 소비하게 해서 재부팅하게 만든다. 두 호스트 사이에 TCP 연결이 이루어지는 것은 클라이언트 호스트가 TCP 헤더에 SYN 플래그를 on한 상태로 서버 호스트로 연결 요청을 하면서 시작된다. 그러면 요청을 받은 서버는 클라이언트로 SYN/ACK를 보내고 이를 받은 클라이언트는 서버에게 ACK를 보낸다. 그런데 클라이언트에서 ACK를 보내지 않고 있으면 어떻게 될까? 문제는 TCP에서 한 소켓에서 동시에 SYN 요청을 처리하기에는 한계가 있으며 이 한계가 백로그(backlog)라고 한다. 백로그는 연결 요청이 아직 완전히 처리되지 아니한 대기상태에 있는 큐의 길이이다. 이 백로그 큐가 꽉 차게 되면 이후 들어오는 SYN 요청은 무시되며 이러한 공격을 SYN 플러딩이라고 한다. 다른 유닉스 중에서는 들어오는 소켓 요청에 대하여 두 개의 큐를 만드는 경우도 있다. 이 경우 하나는 half-open 소켓(SYN을 받고 SYN/ACK를 보낸 상태)을 위한 큐이고 또하나는 완전히 연결이 되었지만 애플리케이션에서 accept() 콜을 기다리는 큐이다. (솔라리스의 경우 tcp_conn_req_max_q0, tcp_conn_req_max_q가 이에 해당한다) backlog도 무조건 늘리는 것이 좋은 것은 아니라는 보고가 있으며 1024 이상으로 설정시에는 커널 소스를 수정해야 한다. syncookie는 SYN Flood 공격에 대응하기 위해 나온 것이지만 large windows에서는 성능에 문제가 발생할 가능성이 있다고 한다. syncookie를 사용하려면 커널에서 지정이 되어 있어야 한다.
net.ipv4.tcp_max_syn_backlog=1024
net.ipv4.tcp_syncookies = 1
ㅇ ICMP redirect를 막는다 : 호스트에서 특정 목적지로 최적화되지 않은 라우팅을 한다고 할때 ICMP redirect 패킷은 호스트에게 정확한 라우팅 경로를 알려주는 역할을 한다. 그런데 크래커가 ICMP redirect 패킷을 위조할 수 있게 된다면 호스트의 라우팅 테이블을 변경할 수 있게 되고 원하지 않는 경로로 트래픽을 보내 호스트의 보안을 해칠 수 있다. 잘 디자인된 네트웍에서는 최종 목적지로의 redirect가 필요하지 않다. redirect 보내고 받는 것을 모두 없애야 한다. (**주1)
net.ipv4.conf.all.send_redirects=0
net.ipv4.conf.all.accept_redirects=0
ㅇ bad icmp 패킷 차단 : 어떤 router 들은 broadcast frame들로 거짓 응답을 보냄으로서 RFC 1122를 위반한다. 이러한 것들은 보통 커널 경고를 통해 로깅이 된다. 이것을 TRUE로 설정을 할경우 커널은 이러한 경고를 하지 않을 것이며, 로그 파일이 지저분해 지는 것을 피할 수 있다. 기본값은 FALSE 이다. 1로 설정하여 활성화를 하면 IP나 TCP 헤더가 깨진 bad icmp packet을 무시한다.
net.ipv4.icmp_ignore_bogus_error_responses = 1
ㅇ IP 스푸핑 방지하기 : DOS 공격에서 자신의 네트워크가 스푸핑된 공격지의 소스로 쓰이는 것을 차단한다. RFC182에 따른 IP spoof 방지를 위한 설정이다.
net.ipv4.conf.all.rp_filter = 1
ㅇ IP 스푸핑된 패킷 로그에 기록하기 : 스푸핑된 패킷, 소스 라우팅 패킷, redirect 패킷에 대하여 로그파일에 기록을 남긴다.
net.ipv4.conf.all.log_martians = 1
참고로 /etc/host.conf 파일에 nospoof 옵션을 주어 IP 스푸핑 방지를 위한 설정을 추가하기 바란다.
# cat /etc/host.conf
order bind, hosts
multi on
nospoof on
3. 최적화 관련 커널 및 운영체제 설정
커널과 운영체제에서의 최적화는 네트웍, 가상 메모리, 디스크 I/O, 각종 자원 제한 등으로 나눌 수 있다. TCP-IP에서 네트웍 최적화는 TCP/IP의 기본값을 줄여서 TCP/IP에서 더 많은 연결을 처리할 수 있도록 하는게 목적이다. TCP의 연결 해제 시간을 줄이고 불필요한 IP 확장기능을 사용하지 않는다.
ㅇ 연결종료시간을 줄인다 : tcp_fin_timeout은 close되었을 때 FIN-WAIT-2 상태로 얼마나 오랫동안 소켓을 유지하고 있는가를 지정한다. 상대 호스트에 문제가 생겨서 close를 못할 수도 있고 예상치 않게 죽을 수도 있다. 커널 2.4에서 기본값은 60초인데 커널 2.2에서는 180초였다. 커널 2.2와 같이 더 늘릴 수는 있지만 시스템이 웹서버의 용도라면 수많은 소켓들 때문에 메모리에 문제가 생길 수 있다. FIN-WAIT-2 소켓은 최대 1.5K의 메모리를 사용하기 때문에 FIN-WAIT-1보다는 위험이 덜하지만 더 오래 가는 경향이 있다.
net.ipv4.tcp_fin_timeout = 30
ㅇ keepalive 시간 줄이기 : tcp_keepalive_time은 keepalive가 활성화되어 있을 때 얼마나 자주 keepalive 메시지를 보내줄지 정한다. 이는 HTTP 1.1의 연결지속 옵션과 관계없이 TCP 자체에 있는 기능이다. TCP 접속은 한쪽에서 다른 쪽으로 데이터를 전송할 필요가 없는 경우에는 트래픽이 전혀 발생하지 않으므로 휴지 상태인 접속의 경우에는 폴링이나 네트워크 부하가 없다. 그런데 더 이상 사용하지 않는 네트워크 접속을 위해 서버에서는 메모리 버퍼를 유지하고 있는 것으로 자원을 낭비하는 것이다. 사용자가 모뎀을 꺼버리거나 컴퓨터의 전원을 끄는 경우도 이에 해당한다. 기본값은 2시간(7200초)인데 처리량이 많은 웹서버인 경우에는 사용자가 페이지를 읽고 얼마나 오래 머무는지를 판단하여 이 대기시간을 최대 시간 간격으로 설정하면 되며 보통 30분정도면 적당하다.
net.ipv4.tcp_keepalive_time = 1800
ㅇ 열수 있는 포트 늘리기 : ip_local_port_range 옵션은 TCP와 UDP에서 사용할 수 있는 로컬 포트의 범위를 지정한다. 두가지 숫자로 이루어져 있는데 앞의 번호는 시작포트이고 뒤의 번호는 마지막 로컬 포트 번호이다. 메모리양에 따라 기본값이 조정되는데 128M 이하일 경우에는 1024-4999, 128M 이상일 경우에는 32768-61000 이다.
net.ipv4.ip_local_port_range = 32768 61000
ㅇ 컴파일 옵션 변경
x86 프로그램에서 최대의 성능을 내기 위해서는 컴파일 옵션에 -O9 플래그를 지정할 수 있다. 대부분의 프로그램은 Makefile에 -O2를 지정하는데 -O9는 코드를 가장 최고로 최적화하는 것이다. 프로그램의 크기는 커지지만 속도가 빨라진다. 그렇지만 CPU가 686이상에서만 성능 향상이 있다. -mcpu=cpu_type -march=cpu_type을 사용하면 각 CPU에 최적화된 코드가 생성된다. 이럴 경우에는 프로그램이 해당 CPU에서만 돈다는 단점이 있다. gcc에서 -S 옵션을 이용하면 어셈블리어로 번역되어 결과가 출력되는데 최적화를 할수록 어셈블리 코드가 짧게 변하는 것을 볼 수 있다. 이외에도 gcc를 egcs로 바꾸어서 사용할 수 있다. 이외에도 최적화된 컴파일을 하기 위해 몇가지 추천하는 옵션이 있었지만 커널 컴파일 등에서 제대로 부팅이 되지 않는 경우가 있어 여기서는 일반적인 옵션에 대해서만 소개를 하였다.
ㅇ bdflush 제어 : bdflush는 가상 메모리(VM) 서브시스템의 활동과 관련이 되어있는데 bdflush 커널 대몬에 대한 제어를 한다. bdflush는 블럭 디바이스 입출력 버퍼를 관리하는 커널 대몬으로 주로 버퍼를 비우는 역할을 맡고 있다. 하드 디스크에 쓰여질 데이터는 바로 하드 디스크로 저장되는 것이 아니라 중간에 버퍼를 통한다고 앞부분에서 설명을 하였다. 주기적으로 버퍼의 내용을 하드 디스크에 기록하는 형태로 하드 디스크 접근을 최대로 줄여서 파일 시스템의 성능을 높이기 위한 것이다. bdflush는 9개의 값으로 구성이 되어있으며 기본값은 커널 2.2에서는 40 500 64 256 500 3000 500 1884 2 이었다가 커널 2.4에서는 구성이 약간 바뀌었다. 기본값만 바뀐 것이 아니라 순서도 바뀌었다. 순서가 nfract, ndirty, nrefill, dummy1, interval, age_buffer, nfract_sync, dummy2, dummy3 이며 기본값은 “30 64 64 256 500 3000 60 0 0” 이다.
커널 소스는 /usr/src/linux/fs/buffer.c이다. 여기에서 중요한 부분은 앞의 세가지이다. nfract는 전체 버퍼 캐쉬중에서 더티 버퍼의 비율을 나타낸다. 여기서 더티는 프로세스에서 변경이 되어 하드 디스크에 쓰여질 데이터를 나타낸다. (반대는 clean 버퍼이다) 수치를 높이면 디스크에 쓰는 동작은 최대한 지연이 되지만 메모리가 부족할 경우 I/O부하가 걸릴 수 있다. 또 어느 책에서는 이 수치를 100%로 지정하는 것을 추천하고 있는데 메모리와 관련하여 "do_try_to_free_pages failed for ...." 등의 문제가 발생한 경우가 생기고 있다. ndirty는 한번에 기록될 더티 블락의 최대 숫자이다. 수치를 높이면 I/O작업을 지연시킬 수 있는데 수치가 너무 낮으면 bdflush가 오랫동안 작동을 하지 않아 메모리 부족 현상이 생길 수 있다. nrefill은 refill을 호출할 때마다 가져올 클린 버퍼 개수이다. 때로는 버퍼가 메모리 페이지에 비해 다른 크기가 될 수 있기 때문에 미리 free 버퍼를 할당하는 것이 필요하다. 수치가 클수록 메모리가 낭비될 확률은 높지만 refill_freelist()를 실행할 필요는 적어진다.
vm.bdflush = 30 64 64 256 500 3000 60 0 0
ㅇ 버퍼 메모리 사용량 조절 : buffermem 은 전체 메모리에서 버퍼 메모리 사용량을 % 비율로 조절한다. 기본값은 2 10 60으로 되어 있다. 여기서 실제 사용하는 값은 제일 앞의 한 개만 사용하며 전체 메모리에서 버퍼로 사용할 수 있는 최소량을 %로 나타낸다.
vm.buffermem = 2 10 60
위에서 말을 한 대로 가상 메모리와 관련한 설정은 시스템의 성능에 큰 영향을 미치지만 잘못 바꾸었을 경우에는 아예 시스템이 멎어버리는 현상이 생길 수가 있다. 테스팅 서버에서 조금씩 값을 바꾸어가면서 자신에게 맞는 설정을 찾아야 할 듯하다. 절대 현재 서비스되고 있는 시스템에서 테스팅을 하지는 말자.
ㅇ 스와핑 조절 : 가상 메모리와 관련된 설정 중 시스템의 성능과 연관이 깊은 것으로는 freepages(vm.freepages)가 있다. freepages는 커널에서 스와핑을 언제 어떻게 할지에 대한 동작을 제어한다. 첫 번째 값은 min으로 free pages가 이 숫자 이하로 넘어갈 때만 커널에서 더 많은 메모리를 할당한다. 두 번째 값은 low로 free pages가 이 숫자 이하로 넘어갈 때 커널에서 스와핑을 시작한다. 세 번째 값은 high로 시스템에서 스와핑을 원활하게 하기 시작하기 위해서 최소한 가지고 있어야 할 free 메모리 페이지를 지정한다. 그러므로 항상 시스템에서 여기에 해당하는 페이지를 free로 가지고 있으려 한다. 아래는 기본 설정값이다.
vm.freepages = 383 766 1149
ㅇ 열 수 있는 파일 핸들 조정 : file-max는 리눅스 커널에서 할당할 수 있는 최대 파일 핸들을 지정한다. “running out of file handles” 라는 에러 메시지가 나오는 경우에는 적절히 늘려주어야 한다. 이는 파일 오픈이 많은 사이트에서 나타나는 현상이다. 웹호스팅 업체의 경우 웹로그파일, 메일서버 관련파일 등을 제대로 관리하지 않으면 이런 경우가 생긴다. 불필요한 파일을 없애고 로그파일을 통합하여 처리한 후 이후에 별도로 도메인별로 분리하는 방법을 생각해 볼 수 있다. 4M당 256개의 파일을 할당해주면 된다. 메모리에 맞추어 설정해주면 된다.
fs.file-max = 8192
/proc/sys/fs에서 file-nr을 보면 현재 할당되어 있는 파일 개수를 알 수 있다.
# cat /proc/sys/fs/file-nr
696 158 32768
첫 번째 숫자는 현재 할당된 파일핸들, 두 번째 숫자는 그중 사용된 파일핸들이고 제일 뒤의 숫자는 파일핸들의 최대 숫자이다. 위에서 지정한 file-max 값이다.
시스템에서 현재 할당되어 있는 inode 개수는 inode-state 나 inode-nr의 제일 앞의 숫자를 보면 된다.
# cat /proc/sys/fs/inode-state
3457 6 0 0 0 0 0
ㅇ 프로세스 개수와 파일 개수 조정 : ulimit -a를 이용하여 사용자별 프로세스 숫자와 열수 있는 파일 개수를 제한할 수 있다. 커널 2.2에서는 동시에 생성 가능한 프로세스 수에 대한 제한이 있었는데 커널 2.4로 넘어오면서 이에 대한 제한이 없어졌다. (task.h에서 지정했는데 커널 2.4에는 파일이 없어졌다) 이제 유일한 제약은 메모리 한계이며 많은 수의 프로세스를 지원하기 위해 스케쥴러도 향상이 되었다. max user processes는 16383이며 open files는 1024이다. 슈퍼유저인 root에게는 max user processes 제한을 ulimit -n unlimited 라는 명령을 통하여 없앤다. 이후에도 계속 적용을 하려면 .bashrc 등에 지정을 하면 된다.
# ulimit -a
core file size (blocks) 0
data seg size (kbytes) unlimited
file size (blocks) unlimited
max memory size (kbytes) unlimited
stack size (kbytes) 8192
cpu time (seconds) unlimited
max user processes 16383
pipe size (512 bytes) 8
open files 1024
virtual memory (kbytes) 2105343
그런데 자원 한도를 변경한다고 하더라도 사용자가 소프트 한도를 변경할 수 있다. 또한 위의 모든 제한은 프로세스별로 되어 있는데 실제 작업은 여러개의 프로세스로 구성이 되어 있다. 그렇지만 코어 파일 크기의 한도를 제한하는 것은 유용한 일이다. 이를 보완할 수 있는 것이 PAM(Pluggable Authentication Module)을 이용한 limit.conf 파일의 활용이다. /etc/security 디렉토리에 있다. 이 파일을 이용하여 로그인한 사용자의 시스템 자원을 제한할 수 있다.
먼저 /etc/pam./login 파일에 아래 내용이 없으면 추가한다. limit.conf를 사용할 수 있도록 설정하는 것이다.
session required /lib/security/pam_limits.so
이제 /etc/security/limit.conf 파일을 필요에 따라 수정한다. 간단한 예를 들어 설명해보겠다.
* hard core 0
* hard rss 5000
* hard nproc 20
* hard cpu 5
* hard data 10000
코어 파일은 생성하지 못하고 최대 resident set size는 5M로, 프로세스는 최대 20개로 제한을 하였고 CPU 선점 시간은 최대 5분이며 최대 데이터는 10M이다. 계정을 가진 사용자가 아무런 제한없이 접속하여 메모리를 엄청나게 잡아먹는 프로그램을 만들었다고 해보자. (**주2)
$ cat memdown.c
#include
#include
int main()
{
char *memory;
int size=1024;
int m=0;
int i=0;
while(1)
{
for(i=0;i<1024;i++)
{
memory = (char *)malloc(size);
if(memory == NULL) exit(-1);
sprintf(memory, "메모리를 사랑해~\n");
}
m++;
printf("현재 %d M 소비\n",m);
}
exit(0);
}
$ gcc -o memdown memdown.c
그러면 memdown을 실행해보자. 절대 서버에서는 하지 말기 바란다.
아무런 제한이 없는 경우에는 메모리가 부족한 시스템에서는 현재 있는 메모리를 모두 잡아먹고 나중에는 스왑까지 전부 소비할 수가 있다. 이렇게 메모리 고갈, 프로세스 고갈 등을 막는데 유용하다.
여기서 제한을 걸 수 있는 사항은 다음과 같다.
- core : 코어파일 크기(KB)
- data : 최대 데이터 크기(KB)
- fsize : 최대 파일크기(KB)
- memlock - 최대 메모리잠김(locked-in-memory) 주소 공간(KB)
- nofile : 최대 열수 있는 파일수
- rss : 최대 상주셋(resident set) 크기(KB)
- stack : 최대 스택 크기(KB)
- cpu : 최대 CPU 시간(MIN)
- nproc : 최대 프로세스 개수
- as - 주소 공간 제한
- maxlogins - 최대 로그인 숫자
- priority - 사용자 프로세스 실행 우선순위
위에서 보았던 간단한 프로그램은 “username hard data 10000” 이렇게 간단한 한줄로 충분히 막을 수 있다. 다시 위의 프로그램을 실행해보면 9M까지 메모리를 사용한후 멈춘다.
$ ./memdown
현재 1 M 소비
현재 2 M 소비
현재 3 M 소비
현재 4 M 소비
현재 5 M 소비
현재 6 M 소비
현재 7 M 소비
현재 8 M 소비
현재 9 M 소비
maxlogin은 웹호스팅 업체에서 사용자들의 접속을 제한하는데 유용하다. 계정을 주고 몇십명이서 접속을 하도록 내버려두어서는 안되기 때문이다.
ㅇ 프로세스당 열 수 있는 파일 개수 : 위에서 설정을 바꾼다고 하더라도 프로세스당 열 수 있는 파일 개수는 1024로 되어 있다. 이를 수정하려면 커널 소스를 고쳐야하는데 커널 소스 디렉토리의 include/linux 디렉토리로 이동하여 fs.h 와 limits.h를 수정하면 된다. fs.h에서는 INR_OEPN 과 NR_OEPN을 원하는 수치로 조정하고 새로 컴파일을 하면 된다. 그런데 시스템에서 동시에 열 수 있는 파일수 이상으로 이 수치를 높이는 것은 불필요한 일이다. 그리고 굳이 수정하지 않더라도 일반적인 환경에서 하나의 프로세스가 1024개 이상의 파일을 여는 일은 드물다고 생각이 된다.
1. 파일 시스템 관련
/etc/fstab에는 파일 시스템 관련된 정보가 들어있다.
$ cat /etc/fstab
/dev/sda2 /home ext2 defaults,rw,nosuid,nodev,noexec 1 2
/dev/sda3 /var ext2 defaults,rw,nosuid,nodev,noexec,noatime 1 2
/dev/sda4 /tmp ext2 defaults,rw,nosuid,nodev,noexec 1 2
여기서 세 번째 줄은 해당 파티션에 대한 제어정보가 들어있는데 의미는 다음과 같다.
default : quota, read-write, suid, async
noquota : 쿼터 사용하지 않음
nosuid : SUID/SGID 설정을 할 수 없음
nodev : 문자나 블락 특수 디바이스 설정을 할 수 없음
noexec : 바이너리 파일의 실행 권한을 주지 않음
quota : 쿼터 설정
ro : 읽기 전용 파일시스템
rw : 읽기/쓰기 파일시스템
suid : SUIG/SGID 설정 허용
noatime : atime(파일 접근 시간)을 업데이트하지 않음
리눅스 및 유닉스에서는 파일 생성 시간 및 마지막 변경시간과 함께 마지막 접근시간을 계속 기록한다. 그렇지만 자주 접근하고 변경이 되는 파일에 대해서는 파일 접근시간이 필요하지 않다면 굳이 마지막 파일 접근시간을 기록하지 않아도 된다. 웹서버, 캐쉬파일, 로그파일 등 주로 읽기전용 파일이 이에 해당할 것이다. 이 설정만으로 성능 병목현상의 가장 큰 주범인 디스크 I/O를 줄이는 데에 도움이 될 수 있다. 그렇지만 파일이 변경된 경우에는 업데이트가 되니 걱정하지 말기 바란다. (chattr 프로그램을 이용하여 개별 파일에 대해서도 지정할 수 있다)
nosuid는 SUID,SGID를 하지 못하도록 막는다. SET-UID 프로그램은 보안상 문제가 생길 여지가 많으며 루트 계정으로 접근할 수 있는 지름길이 되는 경우가 많다. 불가피하게 사용해야 할 경우가 있지만 그 외에는 nosuid 설정을 통해 보안을 강화하는게 좋다. 레드햇 계열에서 SET-UID root 프로그램은 다음과 같은 것들이 있다.
# find / -type f \( -perm -04000 -o -perm -02000 \) \-exec ls -alF {} \;
-rwsr-xr-x 1 root root 14188 Mar 7 2000 /bin/su*
-rwsr-xr-x 1 root root 86690 Oct 20 2000 /bin/ping*
-rwsr-xr-x 1 root root 62192 May 4 09:45 /bin/mount*
-rwsr-xr-x 1 root root 28432 May 4 09:45 /bin/umount*
-r-sr-xr-x 1 root root 15688 Dec 1 2000 /sbin/pwdb_chkpwd*
-r-sr-xr-x 1 root root 16312 Dec 1 2000 /sbin/unix_chkpwd*
-rwsr-xr-x 1 root root 22232 Feb 22 10:21 /usr/bin/crontab*
-rws--x--x 1 root root 14056 Jan 11 2001 /usr/bin/chfn*
-rws--x--x 1 root root 13800 Jan 11 2001 /usr/bin/chsh*
-rws--x--x 2 root root 563164 Aug 9 2000 /usr/bin/suidperl*
-rws--x--x 2 root root 563164 Aug 9 2000 /usr/bin/sperl5.00503*
-rwxr-sr-x 1 root man 36368 May 23 13:25 /usr/bin/man*
-r-s--x--x 1 root root 12244 Feb 8 2000 /usr/bin/passwd*
-rwxr-sr-x 1 root mail 11620 Feb 8 2000 /usr/bin/lockfile*
-rwsr-sr-x 1 root mail 76432 Feb 8 2000 /usr/bin/procmail*
-rwxr-sr-x 1 root slocate 23024 Dec 21 2000 /usr/bin/slocate*
-rwsr-xr-x 1 root root 198312 Nov 10 2000 /usr/bin/ssh1*
-rws--x--x 1 root root 5640 Jan 11 2001 /usr/bin/newgrp*
-rwxr-sr-x 1 root tty 8332 Jan 11 2001 /usr/bin/write*
-rwxr-sr-x 1 root utmp 6096 Feb 25 2000 /usr/sbin/utempter*
-rws--x--x 1 root root 9568 Mar 29 18:30 /usr/sbin/suexec*
-rwsr-sr-x 1 root mail 369340 Jun 19 14:31 /usr/sbin/sendmail*
-rwsr-xr-x 1 root root 17704 Oct 7 2000 /usr/sbin/traceroute*
-rwsr-xr-x 1 root root 34751 Jan 16 00:50 /usr/libexec/pt_chown*
여기서 불필요한 파일은 chmod를 이용 설정을 바꾸어야 한다. chage, gpasswd, wall, chfn, chsh, newgrp, write, usernetctl, traceroute, mount, umount, ping, netreport 등이 여기에 해당한다.
그런데 위의 프로그램은 서비스를 제공하기 위해 어쩔 수 없는 경우이지만 그러한 경우가 아니라면 특정한 파티선에 nosuid를 사용해서 계정 등의 사용자가 장난을 치는 경우를 막을 수 있다. 여기에 noexec까지 주면 바이너리 파일을 실행하지 못하기 때문에 더 안전하게 시스템을 유지할 수 있다. noexec를 준 파티션은 루트도 바이너리 파일을 실행하지 못한다. quota를 이용해서 사용자가 제한되어 있는 하드 디스크 공간을 모두 차지하는 일을 막을 수 있다. 쿼터 설정은 여기서는 생략을 하겠다. async 옵션은 파일시스템에 대한 I/O를 비동기적으로 메모리에 버퍼링하는 것인데 기본값으로 되어 있다. 디스크 접근 시간을 최소화하기 위한 부분이므로 바꿀 필요가 없다. 이것을 sync로 바꾸면 메모리에서 변경된 자료가 바로 디스크 I/O에 반영이 되므로 속도가 얼마나 느려질지 장담할 수 없다.
2. 보안관련 커널 설정
커널과 관련된 옵션은 /proc에서 직접 값을 써 줄 수도 있고 /etc/sysctl.conf 파일을 이용하여 바꿀 수도 있다. 커널과 관련된 옵션은 그냥 무조건 사용하는게 아니라 자신의 환경을 생각해야하고 그 원리를 제대로 이해했을 때 사용하는 것이 좋다. 그런데 이 작업이 그렇게 만만한 작업은 아니고 운영체제와 네트웍에 대해서 많은 지식이 있어야 이해가 가능하다. 여기서는 주로 많이 사용되는 커널 옵션에 대해서 살펴보겠다. 커널과 관련된 설정은 sysctl 프로그램을 이용하여 편리하게 설정할 수 있다. sysctl -a 하면 현재 설정된 값이 나오고 -w 옵션을 이용하여 특정값을 저장할 수 있다. 재부팅후에도 계속 사용하려면 /etc/sysctl.conf 파일에 저장하여 사용하면 된다.
ㅇ ping 요청에 반응하기 막기 : ping 요청에 반응하는 것을 막을 수 있다. 외부에서 누구도 서버에 핑을 보낼 수도 없고 보내도 반응을 하지 않기에 보안상으로 도움이 될 수 있고 네트웍 보안을 향상시킬 수 있다. 그러나 원격에서 서비스를 관리하는 경우 간단하게 시스템의 상태를 확인할 수 있는 수단을 막는 결과를 초래할 수도 있다.
net.ipv4.icmp_echo_ignore_all = 1
ㅇ Broadcast 로 오는 핑을 차단하기 : 브로드캐스트 주소에 ping을 쏘는 것을 막는다. smurf 공격 방지용이다. 스머프 공격은 에코 리퀘스트 패킷을 어떤 네트웍의 브로드캐스트 주소로 보내면 해당 네트워크의 모든 호스트가 request 패킷에 응답하느라 다른 일은 하지 못하는 것을 말한다. 그렇지만 정작 문제는 해당 네트워크의 호스트가 아니다. 이 엄청난 개수의 컴퓨터들에서 일시에 어느 한 곳으로 ICMP echo replay 패킷을 보내면 패킷을 보냈던 시스템은 마비상태로 가기 싶다. 하지만 이미 출발지 주소 자체가 실제 패킷을 보낸 시스템이 아니라 공격을 할 호스트로 바뀌어있는 상태일 것이다. (IP 스푸핑)
net.ipv4.icmp_echo_ignore_broadcasts = 1
ㅇ IP 소스 라우팅 막기 : 라우팅과 라우팅 프로토콜은 몇가지 문제를 생성해낸다. RFC 1122에 따르면 목적지의 경로에 대한 세부적인 내용을 담고 있는 IP 소스 라우팅은 목적지 호스트에서도 같은 경로를 따라 반응을 해야 하다는 문제점이 있다. 크래커가 특정 네트웍에 소스 라우팅 패킷을 보낼 수 있다면 돌아오는 반응을 가로채서 상대방의 호스트와 신뢰받은 호스트처럼 속일 수 있다. 기본값이 1이므로 0으로 바꾼다.
net.ipv4.conf.all.accept_source_route = 0
ㅇ backlog 늘리기와 syncookie 기능 사용하기 : SYN Attack 은 서비스 거부 공격(DOS)의 하나로 공격할 시스템의 모든 자원을 소비하게 해서 재부팅하게 만든다. 두 호스트 사이에 TCP 연결이 이루어지는 것은 클라이언트 호스트가 TCP 헤더에 SYN 플래그를 on한 상태로 서버 호스트로 연결 요청을 하면서 시작된다. 그러면 요청을 받은 서버는 클라이언트로 SYN/ACK를 보내고 이를 받은 클라이언트는 서버에게 ACK를 보낸다. 그런데 클라이언트에서 ACK를 보내지 않고 있으면 어떻게 될까? 문제는 TCP에서 한 소켓에서 동시에 SYN 요청을 처리하기에는 한계가 있으며 이 한계가 백로그(backlog)라고 한다. 백로그는 연결 요청이 아직 완전히 처리되지 아니한 대기상태에 있는 큐의 길이이다. 이 백로그 큐가 꽉 차게 되면 이후 들어오는 SYN 요청은 무시되며 이러한 공격을 SYN 플러딩이라고 한다. 다른 유닉스 중에서는 들어오는 소켓 요청에 대하여 두 개의 큐를 만드는 경우도 있다. 이 경우 하나는 half-open 소켓(SYN을 받고 SYN/ACK를 보낸 상태)을 위한 큐이고 또하나는 완전히 연결이 되었지만 애플리케이션에서 accept() 콜을 기다리는 큐이다. (솔라리스의 경우 tcp_conn_req_max_q0, tcp_conn_req_max_q가 이에 해당한다) backlog도 무조건 늘리는 것이 좋은 것은 아니라는 보고가 있으며 1024 이상으로 설정시에는 커널 소스를 수정해야 한다. syncookie는 SYN Flood 공격에 대응하기 위해 나온 것이지만 large windows에서는 성능에 문제가 발생할 가능성이 있다고 한다. syncookie를 사용하려면 커널에서 지정이 되어 있어야 한다.
net.ipv4.tcp_max_syn_backlog=1024
net.ipv4.tcp_syncookies = 1
ㅇ ICMP redirect를 막는다 : 호스트에서 특정 목적지로 최적화되지 않은 라우팅을 한다고 할때 ICMP redirect 패킷은 호스트에게 정확한 라우팅 경로를 알려주는 역할을 한다. 그런데 크래커가 ICMP redirect 패킷을 위조할 수 있게 된다면 호스트의 라우팅 테이블을 변경할 수 있게 되고 원하지 않는 경로로 트래픽을 보내 호스트의 보안을 해칠 수 있다. 잘 디자인된 네트웍에서는 최종 목적지로의 redirect가 필요하지 않다. redirect 보내고 받는 것을 모두 없애야 한다. (**주1)
net.ipv4.conf.all.send_redirects=0
net.ipv4.conf.all.accept_redirects=0
ㅇ bad icmp 패킷 차단 : 어떤 router 들은 broadcast frame들로 거짓 응답을 보냄으로서 RFC 1122를 위반한다. 이러한 것들은 보통 커널 경고를 통해 로깅이 된다. 이것을 TRUE로 설정을 할경우 커널은 이러한 경고를 하지 않을 것이며, 로그 파일이 지저분해 지는 것을 피할 수 있다. 기본값은 FALSE 이다. 1로 설정하여 활성화를 하면 IP나 TCP 헤더가 깨진 bad icmp packet을 무시한다.
net.ipv4.icmp_ignore_bogus_error_responses = 1
ㅇ IP 스푸핑 방지하기 : DOS 공격에서 자신의 네트워크가 스푸핑된 공격지의 소스로 쓰이는 것을 차단한다. RFC182에 따른 IP spoof 방지를 위한 설정이다.
net.ipv4.conf.all.rp_filter = 1
ㅇ IP 스푸핑된 패킷 로그에 기록하기 : 스푸핑된 패킷, 소스 라우팅 패킷, redirect 패킷에 대하여 로그파일에 기록을 남긴다.
net.ipv4.conf.all.log_martians = 1
참고로 /etc/host.conf 파일에 nospoof 옵션을 주어 IP 스푸핑 방지를 위한 설정을 추가하기 바란다.
# cat /etc/host.conf
order bind, hosts
multi on
nospoof on
3. 최적화 관련 커널 및 운영체제 설정
커널과 운영체제에서의 최적화는 네트웍, 가상 메모리, 디스크 I/O, 각종 자원 제한 등으로 나눌 수 있다. TCP-IP에서 네트웍 최적화는 TCP/IP의 기본값을 줄여서 TCP/IP에서 더 많은 연결을 처리할 수 있도록 하는게 목적이다. TCP의 연결 해제 시간을 줄이고 불필요한 IP 확장기능을 사용하지 않는다.
ㅇ 연결종료시간을 줄인다 : tcp_fin_timeout은 close되었을 때 FIN-WAIT-2 상태로 얼마나 오랫동안 소켓을 유지하고 있는가를 지정한다. 상대 호스트에 문제가 생겨서 close를 못할 수도 있고 예상치 않게 죽을 수도 있다. 커널 2.4에서 기본값은 60초인데 커널 2.2에서는 180초였다. 커널 2.2와 같이 더 늘릴 수는 있지만 시스템이 웹서버의 용도라면 수많은 소켓들 때문에 메모리에 문제가 생길 수 있다. FIN-WAIT-2 소켓은 최대 1.5K의 메모리를 사용하기 때문에 FIN-WAIT-1보다는 위험이 덜하지만 더 오래 가는 경향이 있다.
net.ipv4.tcp_fin_timeout = 30
ㅇ keepalive 시간 줄이기 : tcp_keepalive_time은 keepalive가 활성화되어 있을 때 얼마나 자주 keepalive 메시지를 보내줄지 정한다. 이는 HTTP 1.1의 연결지속 옵션과 관계없이 TCP 자체에 있는 기능이다. TCP 접속은 한쪽에서 다른 쪽으로 데이터를 전송할 필요가 없는 경우에는 트래픽이 전혀 발생하지 않으므로 휴지 상태인 접속의 경우에는 폴링이나 네트워크 부하가 없다. 그런데 더 이상 사용하지 않는 네트워크 접속을 위해 서버에서는 메모리 버퍼를 유지하고 있는 것으로 자원을 낭비하는 것이다. 사용자가 모뎀을 꺼버리거나 컴퓨터의 전원을 끄는 경우도 이에 해당한다. 기본값은 2시간(7200초)인데 처리량이 많은 웹서버인 경우에는 사용자가 페이지를 읽고 얼마나 오래 머무는지를 판단하여 이 대기시간을 최대 시간 간격으로 설정하면 되며 보통 30분정도면 적당하다.
net.ipv4.tcp_keepalive_time = 1800
ㅇ 열수 있는 포트 늘리기 : ip_local_port_range 옵션은 TCP와 UDP에서 사용할 수 있는 로컬 포트의 범위를 지정한다. 두가지 숫자로 이루어져 있는데 앞의 번호는 시작포트이고 뒤의 번호는 마지막 로컬 포트 번호이다. 메모리양에 따라 기본값이 조정되는데 128M 이하일 경우에는 1024-4999, 128M 이상일 경우에는 32768-61000 이다.
net.ipv4.ip_local_port_range = 32768 61000
ㅇ 컴파일 옵션 변경
x86 프로그램에서 최대의 성능을 내기 위해서는 컴파일 옵션에 -O9 플래그를 지정할 수 있다. 대부분의 프로그램은 Makefile에 -O2를 지정하는데 -O9는 코드를 가장 최고로 최적화하는 것이다. 프로그램의 크기는 커지지만 속도가 빨라진다. 그렇지만 CPU가 686이상에서만 성능 향상이 있다. -mcpu=cpu_type -march=cpu_type을 사용하면 각 CPU에 최적화된 코드가 생성된다. 이럴 경우에는 프로그램이 해당 CPU에서만 돈다는 단점이 있다. gcc에서 -S 옵션을 이용하면 어셈블리어로 번역되어 결과가 출력되는데 최적화를 할수록 어셈블리 코드가 짧게 변하는 것을 볼 수 있다. 이외에도 gcc를 egcs로 바꾸어서 사용할 수 있다. 이외에도 최적화된 컴파일을 하기 위해 몇가지 추천하는 옵션이 있었지만 커널 컴파일 등에서 제대로 부팅이 되지 않는 경우가 있어 여기서는 일반적인 옵션에 대해서만 소개를 하였다.
ㅇ bdflush 제어 : bdflush는 가상 메모리(VM) 서브시스템의 활동과 관련이 되어있는데 bdflush 커널 대몬에 대한 제어를 한다. bdflush는 블럭 디바이스 입출력 버퍼를 관리하는 커널 대몬으로 주로 버퍼를 비우는 역할을 맡고 있다. 하드 디스크에 쓰여질 데이터는 바로 하드 디스크로 저장되는 것이 아니라 중간에 버퍼를 통한다고 앞부분에서 설명을 하였다. 주기적으로 버퍼의 내용을 하드 디스크에 기록하는 형태로 하드 디스크 접근을 최대로 줄여서 파일 시스템의 성능을 높이기 위한 것이다. bdflush는 9개의 값으로 구성이 되어있으며 기본값은 커널 2.2에서는 40 500 64 256 500 3000 500 1884 2 이었다가 커널 2.4에서는 구성이 약간 바뀌었다. 기본값만 바뀐 것이 아니라 순서도 바뀌었다. 순서가 nfract, ndirty, nrefill, dummy1, interval, age_buffer, nfract_sync, dummy2, dummy3 이며 기본값은 “30 64 64 256 500 3000 60 0 0” 이다.
커널 소스는 /usr/src/linux/fs/buffer.c이다. 여기에서 중요한 부분은 앞의 세가지이다. nfract는 전체 버퍼 캐쉬중에서 더티 버퍼의 비율을 나타낸다. 여기서 더티는 프로세스에서 변경이 되어 하드 디스크에 쓰여질 데이터를 나타낸다. (반대는 clean 버퍼이다) 수치를 높이면 디스크에 쓰는 동작은 최대한 지연이 되지만 메모리가 부족할 경우 I/O부하가 걸릴 수 있다. 또 어느 책에서는 이 수치를 100%로 지정하는 것을 추천하고 있는데 메모리와 관련하여 "do_try_to_free_pages failed for ...." 등의 문제가 발생한 경우가 생기고 있다. ndirty는 한번에 기록될 더티 블락의 최대 숫자이다. 수치를 높이면 I/O작업을 지연시킬 수 있는데 수치가 너무 낮으면 bdflush가 오랫동안 작동을 하지 않아 메모리 부족 현상이 생길 수 있다. nrefill은 refill을 호출할 때마다 가져올 클린 버퍼 개수이다. 때로는 버퍼가 메모리 페이지에 비해 다른 크기가 될 수 있기 때문에 미리 free 버퍼를 할당하는 것이 필요하다. 수치가 클수록 메모리가 낭비될 확률은 높지만 refill_freelist()를 실행할 필요는 적어진다.
vm.bdflush = 30 64 64 256 500 3000 60 0 0
ㅇ 버퍼 메모리 사용량 조절 : buffermem 은 전체 메모리에서 버퍼 메모리 사용량을 % 비율로 조절한다. 기본값은 2 10 60으로 되어 있다. 여기서 실제 사용하는 값은 제일 앞의 한 개만 사용하며 전체 메모리에서 버퍼로 사용할 수 있는 최소량을 %로 나타낸다.
vm.buffermem = 2 10 60
위에서 말을 한 대로 가상 메모리와 관련한 설정은 시스템의 성능에 큰 영향을 미치지만 잘못 바꾸었을 경우에는 아예 시스템이 멎어버리는 현상이 생길 수가 있다. 테스팅 서버에서 조금씩 값을 바꾸어가면서 자신에게 맞는 설정을 찾아야 할 듯하다. 절대 현재 서비스되고 있는 시스템에서 테스팅을 하지는 말자.
ㅇ 스와핑 조절 : 가상 메모리와 관련된 설정 중 시스템의 성능과 연관이 깊은 것으로는 freepages(vm.freepages)가 있다. freepages는 커널에서 스와핑을 언제 어떻게 할지에 대한 동작을 제어한다. 첫 번째 값은 min으로 free pages가 이 숫자 이하로 넘어갈 때만 커널에서 더 많은 메모리를 할당한다. 두 번째 값은 low로 free pages가 이 숫자 이하로 넘어갈 때 커널에서 스와핑을 시작한다. 세 번째 값은 high로 시스템에서 스와핑을 원활하게 하기 시작하기 위해서 최소한 가지고 있어야 할 free 메모리 페이지를 지정한다. 그러므로 항상 시스템에서 여기에 해당하는 페이지를 free로 가지고 있으려 한다. 아래는 기본 설정값이다.
vm.freepages = 383 766 1149
ㅇ 열 수 있는 파일 핸들 조정 : file-max는 리눅스 커널에서 할당할 수 있는 최대 파일 핸들을 지정한다. “running out of file handles” 라는 에러 메시지가 나오는 경우에는 적절히 늘려주어야 한다. 이는 파일 오픈이 많은 사이트에서 나타나는 현상이다. 웹호스팅 업체의 경우 웹로그파일, 메일서버 관련파일 등을 제대로 관리하지 않으면 이런 경우가 생긴다. 불필요한 파일을 없애고 로그파일을 통합하여 처리한 후 이후에 별도로 도메인별로 분리하는 방법을 생각해 볼 수 있다. 4M당 256개의 파일을 할당해주면 된다. 메모리에 맞추어 설정해주면 된다.
fs.file-max = 8192
/proc/sys/fs에서 file-nr을 보면 현재 할당되어 있는 파일 개수를 알 수 있다.
# cat /proc/sys/fs/file-nr
696 158 32768
첫 번째 숫자는 현재 할당된 파일핸들, 두 번째 숫자는 그중 사용된 파일핸들이고 제일 뒤의 숫자는 파일핸들의 최대 숫자이다. 위에서 지정한 file-max 값이다.
시스템에서 현재 할당되어 있는 inode 개수는 inode-state 나 inode-nr의 제일 앞의 숫자를 보면 된다.
# cat /proc/sys/fs/inode-state
3457 6 0 0 0 0 0
ㅇ 프로세스 개수와 파일 개수 조정 : ulimit -a를 이용하여 사용자별 프로세스 숫자와 열수 있는 파일 개수를 제한할 수 있다. 커널 2.2에서는 동시에 생성 가능한 프로세스 수에 대한 제한이 있었는데 커널 2.4로 넘어오면서 이에 대한 제한이 없어졌다. (task.h에서 지정했는데 커널 2.4에는 파일이 없어졌다) 이제 유일한 제약은 메모리 한계이며 많은 수의 프로세스를 지원하기 위해 스케쥴러도 향상이 되었다. max user processes는 16383이며 open files는 1024이다. 슈퍼유저인 root에게는 max user processes 제한을 ulimit -n unlimited 라는 명령을 통하여 없앤다. 이후에도 계속 적용을 하려면 .bashrc 등에 지정을 하면 된다.
# ulimit -a
core file size (blocks) 0
data seg size (kbytes) unlimited
file size (blocks) unlimited
max memory size (kbytes) unlimited
stack size (kbytes) 8192
cpu time (seconds) unlimited
max user processes 16383
pipe size (512 bytes) 8
open files 1024
virtual memory (kbytes) 2105343
그런데 자원 한도를 변경한다고 하더라도 사용자가 소프트 한도를 변경할 수 있다. 또한 위의 모든 제한은 프로세스별로 되어 있는데 실제 작업은 여러개의 프로세스로 구성이 되어 있다. 그렇지만 코어 파일 크기의 한도를 제한하는 것은 유용한 일이다. 이를 보완할 수 있는 것이 PAM(Pluggable Authentication Module)을 이용한 limit.conf 파일의 활용이다. /etc/security 디렉토리에 있다. 이 파일을 이용하여 로그인한 사용자의 시스템 자원을 제한할 수 있다.
먼저 /etc/pam./login 파일에 아래 내용이 없으면 추가한다. limit.conf를 사용할 수 있도록 설정하는 것이다.
session required /lib/security/pam_limits.so
이제 /etc/security/limit.conf 파일을 필요에 따라 수정한다. 간단한 예를 들어 설명해보겠다.
* hard core 0
* hard rss 5000
* hard nproc 20
* hard cpu 5
* hard data 10000
코어 파일은 생성하지 못하고 최대 resident set size는 5M로, 프로세스는 최대 20개로 제한을 하였고 CPU 선점 시간은 최대 5분이며 최대 데이터는 10M이다. 계정을 가진 사용자가 아무런 제한없이 접속하여 메모리를 엄청나게 잡아먹는 프로그램을 만들었다고 해보자. (**주2)
$ cat memdown.c
#include
#include
int main()
{
char *memory;
int size=1024;
int m=0;
int i=0;
while(1)
{
for(i=0;i<1024;i++)
{
memory = (char *)malloc(size);
if(memory == NULL) exit(-1);
sprintf(memory, "메모리를 사랑해~\n");
}
m++;
printf("현재 %d M 소비\n",m);
}
exit(0);
}
$ gcc -o memdown memdown.c
그러면 memdown을 실행해보자. 절대 서버에서는 하지 말기 바란다.
아무런 제한이 없는 경우에는 메모리가 부족한 시스템에서는 현재 있는 메모리를 모두 잡아먹고 나중에는 스왑까지 전부 소비할 수가 있다. 이렇게 메모리 고갈, 프로세스 고갈 등을 막는데 유용하다.
여기서 제한을 걸 수 있는 사항은 다음과 같다.
- core : 코어파일 크기(KB)
- data : 최대 데이터 크기(KB)
- fsize : 최대 파일크기(KB)
- memlock - 최대 메모리잠김(locked-in-memory) 주소 공간(KB)
- nofile : 최대 열수 있는 파일수
- rss : 최대 상주셋(resident set) 크기(KB)
- stack : 최대 스택 크기(KB)
- cpu : 최대 CPU 시간(MIN)
- nproc : 최대 프로세스 개수
- as - 주소 공간 제한
- maxlogins - 최대 로그인 숫자
- priority - 사용자 프로세스 실행 우선순위
위에서 보았던 간단한 프로그램은 “username hard data 10000” 이렇게 간단한 한줄로 충분히 막을 수 있다. 다시 위의 프로그램을 실행해보면 9M까지 메모리를 사용한후 멈춘다.
$ ./memdown
현재 1 M 소비
현재 2 M 소비
현재 3 M 소비
현재 4 M 소비
현재 5 M 소비
현재 6 M 소비
현재 7 M 소비
현재 8 M 소비
현재 9 M 소비
maxlogin은 웹호스팅 업체에서 사용자들의 접속을 제한하는데 유용하다. 계정을 주고 몇십명이서 접속을 하도록 내버려두어서는 안되기 때문이다.
ㅇ 프로세스당 열 수 있는 파일 개수 : 위에서 설정을 바꾼다고 하더라도 프로세스당 열 수 있는 파일 개수는 1024로 되어 있다. 이를 수정하려면 커널 소스를 고쳐야하는데 커널 소스 디렉토리의 include/linux 디렉토리로 이동하여 fs.h 와 limits.h를 수정하면 된다. fs.h에서는 INR_OEPN 과 NR_OEPN을 원하는 수치로 조정하고 새로 컴파일을 하면 된다. 그런데 시스템에서 동시에 열 수 있는 파일수 이상으로 이 수치를 높이는 것은 불필요한 일이다. 그리고 굳이 수정하지 않더라도 일반적인 환경에서 하나의 프로세스가 1024개 이상의 파일을 여는 일은 드물다고 생각이 된다.