Category: Linux

[번역] 바쁜 리눅스 서버에서 TCP TIME-WAIT 상태 대처하기.

이 문서는 원작자의 동의를 얻어 다음이 포스트를 발 번역한 것 입니다.

그리고 이 글의 라이센스는 “CC BY-NC-SA 3.0” 입니다. 글 아래의 라이센스는 이 글에서는 해당되지 않음을 알려 드립니다. 다시 한번 원작자 Vincent Bernat 씨에게 감사 합니다.

net.ipv4.tcp_tw_recycle 를 활성화하지 말라!

리눅스 커널 문서는 net.ipv4.tcp_tw_recycle 이 무엇인지에 대해서 별다른 도움이 안된다.

TIME-WAIT 소켓의 빠른 재사용을 활성화. 기본값은 0이다. 기술적인 전문가의 충고나 요구없이 함부로 바꿔서는 안된다.

이와 비슷한, net.ipv4.tcp_tw_reuse 는 좀더 문서화되었지만 하는 말은 똑같다.

프로토콜 입장에서 안전한때에 새로운 접속에 대해 TIME-WAIT 소켓의 재사용을 허용하라. 기본값은 0 이다.기술적인 전문가의 충고나 요구없이 함부로 바꿔서는 안된다.

이렇게 부족한 문서로 인해서 우리는 TIME-WAIT 숫자를 줄이기위해서 양쪽다 모두 1로 세팅하라고 충고하는 수많은 튜닝 가이드를 찾게된다. 그러나 tcp(7) 메뉴얼 페이지에 의해하면, net.ipv4.tcp_tw_recycle 옵션은 같은 NAT 장치 뒤에 있는 서로 다른 컴퓨터들로부터 접속을 핸들링할 수 없는것처럼, 이는 찾기도 힘들고 그것이 당신을 집어삼킬거다, 대중적인 서버들에게 아주 많은 문제가 있다.

TIME-WAIT 소켓의 빠른 재사용의 활성하기. 이 옵션의 활성화는 NAT(Network Address Translation) 을 가지고 동작할때에 문제를 발생시킬수 있어서 권장하지 않는다.

나는 인터넷에 잘못된 정보를 주는 사람들을 위해서 좀 더 상세한 설명을 여기서 제공할 것이다.

참고로, 이름에 ipv4를 사용함에도, net.ipv4.tcp_tw_recycle 은 IPv6 에함께 적용되어 제어된다, 우리는 리눅스의 TCP 스택에서 찾고 있다는 것을 유념해야 한다. This is completely unrelated to Netfilter connection tracking which may be tweaked in other ways

TIME_WAIT 상태에 대해.

TIME-WAIT 상태를 찾아보고 기억해보자. 이것이 무엇일까? 아래의 TCP 상태 다이어그램을 보라.

TCP State Diagram
TCP State Diagram

오직 처음으로 접속을 차단하는 끝부분에서 TIME-WAIT 상태에 도달한다. 다른 끝단은 빠르게 접속을 태우는것을 허용되는 경로를 따를것이다.

여러분은 ss -tan 를통해서 현재 접속 상태를 살펴볼 수 있다.

목적 TIME-WAIT 상태에 대한 목적은 두가지가 있다.

  • 아주 잘 알고 있듯이 지연된 세그먼트(Delayed Segments)를 같은 네쌍둥이(소스 주소, 소스 포트, 목적지 주소, 목적지 포트) 에 의존하는 뒤늦은 접속을 받아들일려고 하는 접속을 차단하는 것이다. 순차적인 번호(sequence number) 는 또한 접속을 받을 수 있는 어떤 구간에 있어야 한다. 이것은 아주 작은 문제를 야기하지만 여전히 특히 아주 큰 윈도우즈를 받는 빠른 접속에서는 존재하게 된다. RFC 1337 은 TIME-WAIT 상태가 부족해지면 무슨일이 벌어지는지를 상세히 설명하고 있다. Here is an example of what could be avoided if the TIME-WAIT state wasn’t shortened
duplicate-segment
Due to a shortened TIME-WAIT state, a delayed TCP segment has been accepted in an unrelated connection.
  •  또 다른 목적은 원격 끝에 접속을 닫을 수 있도록 한다. 마지막 ACK 를 잃었을때, 원격 끝은 LAST-ACK 상태에 머문다. TIME-WAIT 이 없다면 원격 끝이 여전히 이전 접속이 유효하다고 생각하는데도 연결은 재개될 수 있다. SYN 세그먼트를 받았을때에 이것은 세그먼트와 같은 것을 기대하지 않는 것처럼 RST 를 응답하게된다. 새로운 연결은 에러와 함께 중단된다.
last-ack
If the remote end stays in LAST-ACK state because the last ACK was lost, opening a new connection with the same quadruplet will not work.

RFC 793 은 적어도 2MSL 시간에 TIME-WAIT 상태가 필요하다. 리눅스에서 이 기간은 튜닝할수 없으며 include/net/tcp.h 에 정의된 것처럼 1분이다.

이 값을 튜닝하도록 하는 제안들이 있었지만, 실제 현장에서는 TIME-WAIT이 좋은 것이기에 거절되어져 왔다.

문제점

이제, 이 상태가 많은 접속을 핸들링하는 서버에서 왜 성가신 존재일수 있는지를 보자. 세가지 측면에서 문제점이 있다.

  • 슬롯(slot)은 같은 종류의 새로운 연결을 차단하기 위해서 연결테이블에 가져다 놓는다.
  • 메모리(Memory)는 커널의 소켓 구조(socket structure) 의 의해서 소모되며,
  • 이에 더해서 CPU도 사용된다.

‘ss -tan state time-wait | wc -l’ 의 결과는 문제가 아니다.

Connection table slot

TIME-WAIT 상태에 접속은 접속 테이블에서 1분동안 유지된다. 이것은 같은 네 쌍둥이(소스 주소, 소스 포트, 목적지 주소, 목적지 포트)에 다른 연결은 존재할 수 없다는 것을 뜻한다.

웹서를 예를들면, 목적지 주소와 목적지 포트는 상수와 같다.(변하지 않는 값이라는 뜻) 만약 웹 서버가 L7 로드 밸런서 뒤에 있다면 소스 주소 또한 상수 일수 있다. 리눅스에서 클라이언트 포트는 기본적으로 30,000 개의 포트 범위에서 할당된다. (이것은 net.ipv4.ip_local_port_range 를 튜닝함으로써 바꿀 수 있다) 이것은 오직 웹 서버와 로드 밸런서 사이에 연결이 매분당 30,000 개, 약 초당 500 개 연결이 맺어질수 있다는 것을 의미한다.

만약 TIME-WAIT 소켓이 클라이언트측에 존재한다면 이러한 상황은(30,000개 포트를 모두 할당한 나머지 더 이상 접속을 받을 수 없는 상황) 쉽게 찾을 수 있다. connect() 시스템 콜을 호출하면 EADDRNOTAVAIL 을 리턴하고 애플리케이션은 그것에 대한 어떤 에러메시지를 기록할 것이다. 서버측에서 이것은 로그도 없고 계산할 수 있는 카운터도 없어서 좀 더 복잡하다. 이상하게도, 당신은 사용되어지는 네 쌍둥이의 숫자 리스트에서 판단할 수 있는 무언가 오기를 노력해야 한다.

이에 대한 해결책은 좀 더 많은 네 쌍둥이들이다. 이것은 몇가지 방법으로 이룰 수 있다. (설정하는데 어려운 순서대로)

  • net.ipv4.ip_local_port_range 를 좀 더 넓게 세팅함으로써 좀 더 많은 클라이언트 포트를 사용.
  • 리스닝 하기위한 웹서버에게 몇개의 추가적인 포트(81,82,83,…)를 할당함으로써 좀 더 많은 서버 포트를 사용.
  • 로드 밸런서에 아이피를 추가하거나 라운드 로빈 기능으로 그들을 사용하게 함으로써 좀 더 많은 클라이언트 아이피를 사용.
  • 웹 서버에 추가적인 아이피를 추가함으로써 좀 더 많은 서버 아이피 사용.

물론, 마지막 해결책은 net.ipv4.tcp_tw_reuse 나 net.ipv4.tcp_tw_recycle 를 트윅하는 거다. 아직 하지는 말자. 우리는 뒤에서 그것들의 세팅에 대해서 이야기할 할 거다.

메모리

핸들링을 위해서 많은 접속을 가지는 경우에, leaving a socket open for one additional minute may cost your server some memory. 예를들어, 만약 당신이 약 초당 10,000 개의 새로운 접속을 핸들링하길 원한다면 당신은 TIME-WAIT 상태에 약 600,000 소켓을 가지게 될 것이다. 그러면 얼마나 많은 메모리를 소모할까? 그렇게 많지 않다.

첫째로, 애플리케이션 입장에서, TIME-WAIT 소켓은 그 어떤 메모리도 소모하지 않는다. 소켓은 닫혔다. 커널에서 TIME-WAIT 소켓은 세개의 다른 목적에 대해 세개의 구조로 제공된다.

  1. 다른 상태의 연결이 포함되어 있음에도 “TCP established hash table” 로 불리우는 접속 해쉬 테이블(A hash table of connection) 은, 예를들어 새로은 세그먼트를 받아들일때에 존재하는 연결을 위치시키는 장소 사용되어진다.이 해쉬테이블의 각각의 버킷(bucket)은 TIME-WAIT 상태에서 접속 리스트와 정규적인 활성 접속 리스트가 포함 된다. 이 해쉬테이블의 크기는 시스템 메모리에 의존적이며 부팅시에 볼수 있다.

    이것은 thash_entries 파라메터를 가지는 커널 명령어 라인에 숫자 엔트리를 지정함으로써 값을 재지정할 수 있다. TIME-WAIT 상태에서 접속 리스트의 각 요소들은 ‘struct tcp_timewait_sock’ 이며, 다른 상태들을 위한 타입은 ‘struct tcp_sock’ 이다.
  2. 접속 리스트의 집합은, “death row” 로 불리우는, TIME-WAIT 상태에서 접속 만료(expire)를 위해서 사용되어진다. 그들은 만료되기전에 얼마나 시간이 남았는지에 따라서 정렬되어진다. 접속 해쉬 테이블에 엔트리처럼 같은 메모리 공간을 사용한다. 이것은 ‘struct inet_timewait_sock’ 의 ‘struct hlist_node tw_death_node’ 멤버이다.
  3. 바운드 포트 해쉬 테이블은, 로컬 바운드 포트들과 연관된 파라메터들을 들고 있는, 주어진 포드가 리슨(Listen)하기위해 안전한지 혹은 다이나믹 바운드 케이스의 경우에 자유 포트를 찾을수 있는지를 결정하는데 사용되어진다. 이 해쉬 테이블의 크기는 접속 해쉬테이블의 크기와 똑같다.

    각 요소들은 ‘struct inet_bind_socket’ 이다. 그것은 각 로컬적인 바운드 포트당 하나의 요소다. 웹 서버에 TIME-WAIT 접속은 로컬적으로 80포트에 바운드 되고 TIME-WAIT 접속들의 형제들(sibling) 처럼 같은 엔트리들을 공유한다. 다시 말해서, 원격 서비스에대한 하나의 접속은 로컬적으로 어떤 랜덤 포트에 바운드 되고 그 엔트리는 공유되지 않는다.

그래서, 우리는 오직 ‘struct tcp_timewait_sock’ 과 ‘struct inet_bind_socket’ 에 의해서 소모되는 용량만을 생각하면 된다. 하나의 ‘struct tcp_timewait_sock’ 은 TIME-WAIT 상태에서 각 접속에대한, inbound or outbound, 것이다. 하나의 전용의 ‘struct inet_bind_socket’ 은 각 outbound 접속에 대한 것이며 inboud 접속에 대한 것은 없다.

하나의 ‘struct tcp_timewait_sock’ 은 168 bytes 인 반면에 ‘struct inet_bind_socket’ 은 48 bytes 이다.

그래서, 만약 약 40,000 개의 TIME-WAIT 상태의 inboud 접속을 가지고 있다면 10MB 보다 적은 메모리를 소모한다. 만약 dir 40,000개의 TIME-WAIT 상태의 outbound 접속을 가지고 있다면 2.5MB 추가적인 메모리를 위해 계산이 필요하다. ‘slabtop’의 출력을 보고 체크해보자. 여기에 TIME-WAIT 상태에서 약 50,000 개의 접속과 약 45,000개의 outbound 접속을 가지는 서버의 결과가 있다.

여기서 바뀐 것은 아무것도 없다. TIME-WAIT 접속으로 인해 사용되어지는 메모리는 정말 작다. 만약 당신의 서버가 초당 수천개의 새로운 접속을 처리해야 해야 한다면, 클라이언트에게 데이터를 효과적으로 전송하기 위한 좀 더 많은 메모리가 필요할 뿐이다. TIME-WAIT 접속 오버헤드는 무시해도 좋다.

CPU

CPU 측면에서 free local port 를 찾는 것은 조금 비싼작업일 수 있다. 이 작업은 락(lock)을 사용하고 free port 를 찾을때까지 로컬 바운드 포트들을 반복하는 ‘inet_csk_get_port’ 함수에 의해서 이루러진다. 이 해쉬 테이블의 아주 많은 엔트리들은 TIME-WAIT 상태의 outbound 연결을 가졌다면 일반적으로 문제가 되지 않는다. 접속들은 보통 같은 프로파일을 공유하고 함수들은 그들을 순차적으로 반복함으로써 아주 빠르게 free port 를 찾는다.

다른 해결법

만약 여전히 이전 섹션을 읽었는데도 TIME-WAIT 접속들이 문제가 있다라고 생각한다면 이 문제를 해결하기 위해 세 가지 추가적인 해결법이 있다.

  • socket lingering 비활성.
  • net.ipv4.tcp_tw_reuse.
  • net.ipv4.tcp_tw_recycle

Socket lingering

close() 가 호출되면, 커널 버퍼에 남아있는 데이터는 백그라운드로 보내질 것이고 소켓(Socket)은 최종적으로 TIME-WAIT 상태로 변한다. 애플리케이션은 즉각적으로 계속 일을 할 수 있고 모든 데이터는 안전하게 분배되도록 다루어진다.(주, 소켓이 빨리 닫힘에따라 애플리케이션은 소켓관련 작업을 끝내고 다른 일을 할 수 있다는 말)

그러나 애플리케이션은 이러한 행동을 하지못하도록 선택할 수 있는데, 그것이 바로 Socket lingering 이다. 거기에는 두가지 특징이 있다.

  1. 첫째로, 어떤 남아있는 데이터는 폐기처분되고 일반적인 4번의 패킷 접속 차단 시퀀스를 갖는 차단 접속 대신에 연결은 RST 를 가지고 닫힐 것이며 즉각적으로 파괴될 것이다.
  2. 두번째로, 여전히 소켓의 보내는 버퍼에 데이터가 남아있다면 프로세스는 close() 가 호출될때에 모든 데이터 보내지고 접속자로부터 응답을 수신받거나 설정된 링거 타임머가 만료될때까지 sleep 될 것이다. 이것은 소켓이 non-blocking 세팅되었을때에 프로세스가 sleep 되지 않을 가능성이 있다. 이것은 설정된 타임아웃 동안 보내어질 수 있는 데이터가 남아있는것을 허용하지만 만약 데이터가 성공적으로 보내지면, 일반적인 차단 시퀀스는 실행되고 TIME-WAIT 상태를 얻게된다. 그리고 다른 게이스로, RST 를 가지는 접속 차단을 얻을 것이고 남은 데이터는 폐기되어진다.

양쪽의 경우에, 비활성 Socket lingering 은 one-size-fits-all 해결방법이 아니다. 이것은 상위 프로토콜 지점으로부터 사용하는 것이 안전한 HAProxy 나 Nginx 처럼 몇몇 애플리케이션에서 사용되어질 수 있다. (주, Nginx 의 경우 lingering 관련 옵션들이 존재한다.) There are good reasons to not disable it unconditionnaly

net.ipv4.tcp_tw_reuse

TIME-WAIT 은 별 상관없는 접속을 받는 지연된 세그먼트를 차단한다. 그러나, 어떤 조건에서, 새로운 접속의 세그먼트가 오래된 접속의 세그먼로 잘못 해석할 수 없도록 해준다.

RFC 1323 은 고대역폭 경로를 넘는 성능향상을 위한 TCP 확장 셋을 제공한다. 다른 것들 사이에서, 이것은 두개의 4 바이트 타임 스탬프 필드를 전달하는 새로운 TCP 옵션을 정의한다.  하나는 TCP 보내기 옵션의 현재 타임스탬프 값이며 다른 하나는 원격 호스트로부터 아주 최근에 받은 타임스탬프 이다.

net.ipv4.tcp_tw_reuse 를 활성화 하면, 리눅스는 새롭게 외부로나가는 접속을 위해 새로운 타임스탬프가 이전 접속에서 기록된 최근의 타임스탬프보다 현격하게 크다면 TIME-WAIT 상태에서 존재하는 접속을 재사용한다: TIME-WAIT 상태에서 외부로나가는 접속은 정확히 1초 후에 재사용되어질 수 있다.

어떻게 이게 안전한가? TIME-WAIT 의 첫번째 목적은 똑같은 세그먼트가  아무런 연관이 없는 접속을 받는 것을 피하는 것이다. 타임스탬프 사용 덕분에, 이러한 똑같은 세그먼트들은 시간이 벗어난 타임스탬프로부터 오게되었고 결국 폐기된다.

두번째 목적은 원격 끝점이 마지막 ACK 를 잃었을때에 발생되는 LAST-ACK 상태가 안되게 해준다. 원격 끝점은 다음의 경우가 있을때까지 FIN 세그먼트를 재전송한다.

  1. 포기하고 접속이 해제될때까지
  2. 기다리던 중에 ACK 를 수신하고 접속이 해제될때까지
  3. RST 를 수신하고 접속이 해제될때까지

만약 FIN 세그먼트가 제때에 수신이 되면 로컬 소켓은 여전히 TIME-WAIT 상태에 있을 것이고 기대한 ACK 세그먼트를 보낼 것이다.

한번 새로운 접속이 TIME-WAIT 엔트리로 바뀌면, 새로운 접속의 SYN 세그먼트는 timestamps 덕분에 무시되어지고 RST 에 의한 응답은 없지만 FIN 세그먼트의 재전송에 의한 응답만 있다. FIN 세그먼트는 LAST-ACK 상태 중 전환을 허용할 RST 를 가진 응답이 있을 것이다.( 왜냐하면 로컬 접속은 SYN-SENT 상태이다) 응답이 없기 때문에 초기 SYN 세그먼트는 결국 (일초 후) 재전송되고 연결이 약간의 지연을 제외하고, 명백한 오류없이 설정됩니다.

If the remote end stays in LAST-ACK state because the last ACK was lost, the remote connection will be reset when the local end transition to the SYN-SENT state.
If the remote end stays in LAST-ACK state because the last ACK was lost, the remote connection will be reset when the local end transition to the SYN-SENT state.

접속이 재사용될때에 TWRecycled 카운트가 증가하는거에 주목해야 한다.

net.ipv4.tcp_tw_recycle

이 메커니즘은 타임스탬프 옵션에 의존하지만 서버는 일반적으로 먼저 연결을 종료할 때 incoming 과 outgoing 접속 모두에 영향을 미친다.

TIME-WAIT  상태는 곧 만료 될 예정이다: 이것은 RTT와 그 편차로부터 산출되는 재송신 타임아웃 기간 후에 제거되어진다. 여러분은 ss 명령어를 통해서 살아있는 접속에 대한 알맞은 값들을 발견 할 수 있다.

만료 타이머를 감소시키면서, 접속이 TIME-STATE 상태로 들어갈때, TIME-WAIT 상태가 제공하는 것과 동일한 보장을 유지하려면 맨 마지막 타임스탬프는 이전에 알고있는 목적지에 대해서 다양한 매트릭을 포함하는 전용구조에 기억되어 진다. 그리고, 리눅스는 TIME-WAIT 상태가 만료되지 않는 한, 최종적으로 기록된 타임스탬프보다 훨씬 큰 타임스탬프가 아닌 리모트 호스트로부터 모든 세그먼트를 드랍(Drop) 할 것이다.

리모트 호스트는 NAT 장치에 있을때, 타임스탬프 조건은 1분 동안 연결된 NAT 장치 뒤에 하나를 제외하고 모든 호스트는 금지되기 때문에 그들은 같은 타임스탬프 클럭을 공유할 수 없다. 이상하게도, 이것은 문제를 진단하고 감지하기 힘든다면 이 옵션을 비활성화 하는것이 훨씬 좋다.

LAST-ACK 상태는 net.ipv4.tcp_tw_recycle 와 같이 정확하게 같은 방법으로 다루어진다.

요약

보편적인 해결책은 더 많은 서버 포트들을 사용하는  네 쌍둥이 숫자를 증가시키는 것이다. 이것은 TIME-WAIT 엔트리들을 가진 가능한 접속들을 고갈되지 않도록 해준다.

서버측에서, NAT 장치들이 가지고 있지 않는지 확신이 서지 않는다면 net.ipv4.tcp_tw_recycle 를 활성화하지 마라. net.ipv4.tcp_tw_reuse 활성화는 들어오는 접속에 대해서는 쓸모가 없다.

클라이언트 측에서, net.ipv4.tcp_tw_reuse 활성화는 아주 안전한 해결책이다. net.ipv4.tcp_tw_reuse 와함께 net.ipv4.tcp_tw_recycle 활성화는 대부분 쓸모가 없다.

마지막으로 Unix Network Programming 을 쓴 W.Richard Stevens 의 말을 인용한다.

TIME-WAIT 상태는 우리의 친구이고 우리에 도움을 준다. (i.e, to let old duplicate segments expire in the network). 이 상태를 피하기 노력하기 보다는 그것을 이해해야 한다.

리눅스 시스템 자원 제한.

아주 접속이 많은 웹 서버를 운영하던중에 다음과 같은 종류의 메시지를 접하는 경우가 종종 있다.

너무나 많은 파일을 열었다는 건데, 도대체 무슨 파일을 열었다는건지 서비스하는 웹 페이지의 파일 개수도 많지도 않은데 말이다.

이 문서는 이러한 의문을 가지는 사람들 위한 것이다.

모든게 파일!

리눅스 시스템은 모든것이 파일이다. 장치도 파일이다. 그 유명한 /dev/ 디렉토리에 있는 팡리들이 바로 장치들이다. 저 파일들에게 cat 명령어로 데이터를 던져주면 그것을 알아먹는 장치는 동작하게 된다.

또, 리눅스에서 프로그램의 실행은 혼자 동작하는 것이 아니다. C 프로그램을 해본사람이라면 라이브러리(Library) 개념을 알고 있을 텐데 프로그램이 동작할때에는 이러한 라이브러리 파일도 함께 로딩(Loading)된다. 예를들어 sshd 라는 명령어가 어떤 라이브러리들고 로딩되는지는 다음과 같이 알수 있다.

sshd 가 실행중인데, 현재 이 프로그램은 아주 많은 파일들을 사용하고 있다. 프로그램 실행을 위한 라이브러리 파일뿐만 아니라 sshd 설정파일들도 눈에 보인다.

그런데, 리눅스 시스템에서 각 프로그램에게 64개 이상의 파일을 사용하지 못하도록 설정되어 있다면 어떻게 될까? 그것이 바로 위에서 만나게 되는 메시지이다.

리눅스 시스템 자원 제어하기.

리눅스 시스템은 위에서 설명한 것처럼 열어야할 파일 개수뿐만아니라 다양한 자원들에게 대해서 제어를 할 수 있도록 해놨다. 이를 위해서 ulimit 라는 명령어를 제공하고 이를 통해서 각각의 자원들을 제어할 수 있다.

위에 나열한 것처럼 cpu, login, file open, file size 등 다양한 것에 대해서 제한(limit)을 둘 수 있다.

또, 각각의 제한은 리눅스 시스템 계정 사용자별, 그룹별, 프로세스별로 줄 수 있다. 그렇다면 현재 로그인한 상태에서 제한은 다음과 같이 확인이 가능하다.

만일 슈퍼 유저(혹은 root 유저) 라면 다른 사용자의 제한도 다음과 같이 확인할 수 있다.

 

Soft and Hard Limit

시스템 리소스 제한에는 Soft Limit 와 Hard Limit 가 있다. Hard Limit 는 Soft Limit 의 최대임계치로서 이 제한에 걸리면 곧바로 문제가 된다. 하지만 Soft Limit 는 최대 임계치는 아니여서 이것을 벗어나더라도 Hard Limit 까지는 문제가 없다. 대신에 Soft Limit 를 벗어났을때에 메일을 보낸다든지 경고 메시지를 내보내는등 조만간에 최대 임계치에 도달할 수 있다는 것을 미리 알려줄 수 있다.

사용자별, 자원별 제한

사용자별, 자원별로 제한을 둘 수 있다. 이는 /etc/security/limits.conf 파일에 명시하면 사용자별, 자원별로 제한이 된다. 파일을 열어보면 자원별은 item 으로 부르고 있는데 제한을 두고 싶은 시스템 자원에 대한 지시어를 찾아서 적으면 된다.

예를들어 systemv 사용자에 대해 최대 파일 열기 디스크립터 개수를 제한고 싶다면 다음과 같이 해주면 된다.

이 내용을 적은 이후부터 systemv 로 시스템에 로그인 하는 사용자는 바로 적용이 되고 이 사용자 계정으로 실해되는 데몬 프로그램은 재시작을 해주면 바로 적용된다.

각 프로세스당 자원에 대한 제한을 알고 싶다면 PID 를 알아낸뒤에 다음과 같이 해주면 된다.

 

Nginx KeepAlive

Nginx 에서 KeepAlive 관련된 설정은 다음과 같이 세가지이다.

  • keepalive_disable
  • keepalive_timeout
  • keepalive_requests

먼저, Keepalive 는 HTTP 와 TCP 상에서 구현되어 있다. HTTP 의 경우에는 1.0 버전과 1.1 버전이 존재하는데, 1.0 버전에서는 KeepAlive 는 존재하지 않으며 1.1 에서는 연결시에 기본으로 KeepAlive 가 활성화 된다.

HTTP 는 “Connetionless” 방법을 취한다. 웹 컨텐츠를 전송 받기 위해서 서버에 연결을 한 후에 데이터를 전송받는다. 그리고는 연결을 바로 끊게 된다. 하지만 웹이라는게 HTML, Javascript, CSS, Image 파일등으로 수백개로 이루어진 상태에서 수백의 컨텐츠를 전송하기위해서 연결을 수백번을 한다면 비 효율적일 것이다. 그래서 한번의 연결로 수십개의 컨텐츠를 전송하도록 해주는것이 KeepAlive 이다.

그렇다면 의문이 들 것이다. KeepAlive 를 켜주기만할 것이지 왜 timout, request 와같은 설정이 필요한 것일까?

답은 네트워크 상황이 좋지 않을때에 나타난다. 만약 KeepAlive 연결이 된상태에서 데이터 전송에 문제가 생겼을 경우 또는 Client 에서 데이터 전송을 하지 않을 경우에 이 연결된 접속은 어떻게 해야 할까? Server 접속 자원은 무한정이 아니기에 이러한 접속을 계속 유지되는 것은 Server 에 막대한 손실을 발생시키고 이는 곧 접속 장애로 이어진다.

그래서 접속이 이루어진 후에 컨텐츠를 전송받은후에 얼마간 또 다시 컨텐츠 전송 요청이 없다면 Server 가 접속을 차단시키도록 해야하는데 이것이바로 keepalive_timeout 이다. 다시말해 처음 접속이 이루어지고 컨텐츠를 한번 전송한 이후에 타이머를 실행시켜서 timeout 시간까지 Client 가 컨텐츠 요청이 없다면 Server는 접속을 차단하게 된다.

keepalive_request 는 Server 에 접속이 이루어진 이후에 컨텐츠를 요청한 갯수를 계산하고 이값을 넘으면 접속을 차단하는 것이다.

이렇게 접속이 차단되면 다음 번 컨텐츠 요청을 위해선느 새로운 접속이 이루어져야 한다.

그렇다면, Nginx 에서는 어떻게 이것을 다룰까? 이를 위해서는 먼저 Nginx 를 debug 모드가 활성화 되도록 컴파일 설치해야 한다.

그리고 다음과 같이 error_log 에 debug 를 활성화 해줍니다.

keepalive_timeout  설정을 5초로 해줍니다.

이렇게해서 nginx 를 실행하고 error.log 파일을 보면 nginx 가 처리하는 과정을 상세히 볼 수 가 있다.

이와 더불어 HTTP 연결 테스트는 Telnet 을 이용하면 된다. 테스트는 다음과 같이 진행하면 된다.

마지막에 “Connection close by foreign host.” 메시지는 5초동안 아무런 요청이 없으면 나타나는데 이는 keepalive_timeout 설정된 값과 같다. 만일 5초가 지나기 전에 또 한번에 데이터 요청을 Server 에 보낸다면 keepalive_timeout 은 reset 되고 전송이 끝난후에 다시 타이머가  흐르기 시작한다.

이는 keepalive_timout 에 대해서 데이터 전송이 끝난 후에 다시 데이터 전송 요청때까지의 갭타임을 의미하기도 한다. 그 사이에 데이터 전송 요청이 없다면 Server 가 접속을 차단하게 된다.

위 실험에서 Nginx는 과연 어떻게 처리했을까? 로그에는 아주 많은 정보가 표시되는데, 전송이 모두 끝난후에 다음과 같은 로그가 보인다.

Nginx 에서 timout 관련 설정은 거의 대부분 내부적으로 timer 로 처리 된다. Nginx 의 I/O Multiplexer 는 epoll 이기 때문에 내부적으로 epoll_wait 에 timer 가 전달되어 처리되는 구조다.

그리고 각각의 timeout 들은 내부적으로 처리하는 handler 가 존재해 처리된다. 각각의 handler 들은 timer 를 체크하고 close_connection 을 호출하는 방식으로 접속을 차단하도록 구현되어 있다.

“http keepalive handler” 는 ngx_http_request.c 에 구현되어 있다.

 

Cassandra 2.1.2 설치하기.

Cassandra 로고

 

카산드라(Cassandra)는 분산 데이터 스토리지 시스템 입니다. 아파치 재단에서 오픈소스로 만들어 배포하고 있고, 자바기반으로 제작되었습니다. peer to peer 프로토콜을 이용한 고가용성이 구현되어 있습니다.

이 문서는 카산드라(Cassandra) 2.1.2 에 싱글(Single) 노드를 위한 설치에 대한 것입니다. 설치환경은 CentOS 입니다.

jdk 7 update 75

카산드라(Cassandra) 2.1.2 는 jdk 7 update 75 버전 이상을 필요로 합니다. 만일 idk 버전 이하를 사용한다면 시작스크립트에서 오류를 내며 작동하지 않습니다. Oracle 홈페이지에서 jdk 7 update 75 를 다운받아 다음과 같이 압축을 해제합니다.

카산드라(Cassandra) 2.1.2 설치

카산드라(Cassandra) 는 리눅스의 슈퍼유저인 root 로 실행할 필요가 없습니다. 카산드라(Cassandra) 를 운영하기 위한 시스템 계정을 만들고 그 시스템 계정으로 운영하면 됩니다.

카산드라(Cassandra) 2.1.2 는 홈페이지에서 다운로드 받을 수 있습니다.

다운로드: 카산드라(Cassandra) 2.1.2

설치는 별도의 진행이 필요 없이 압축을 해제하면 됩니다.

이제 카산드라(Cassandra) 에서 사용할 디렉토리를 생성해야 합니다. 필요한 디렉토리는 다음과 같습니다.

  • data_file_directories: /home/cassandra/data
  • commitlog_directory: /home/cassandra/commitlog
  • saved_caches_directory: /home/cassandra/saved_caches

카산드라(Cassandra) 에서 위 디렉토리를 설정하기 위해서 설정파일을 편집해 줍니다.

시작 스크립트

jdk 7 update 75 에 대해 시스템 java 설정을 해주면 별도로 필요가 없지만 카산드라(Cassandra) 에서만 쓰게 하기위해서는 시작스크립트를 작성하고 jdk 7 update 75 를 인식시켜줘야 합니다.

top 사용시 command 매칭된것만 보기.

top 은 리눅스에서 아주 유용한 시스템 모니터링 툴 입니다. 매우 많은 옵션도 제공하는데, 그중에서 프로세스의 명령어와 매칭되는 것만 보여주기도 한다. -c 옵션이 그것인데, 다음과 같이 pgrep 과 조합해서 사용할 수 있다.

 

CentOS 7 싱글모드 부팅

centos logoCentOS 7 에서 많은 변화가 있지만 그 중에 하나가 싱글 모드(Single Mode) 부팅 입니다.

CentOS 6 에서 싱글모드 부팅을 위해서 부팅 커널 이미지 옵션으로 1 을 입력하면 되었습니다. 하지만 CentOS 7 에서는 그렇게하면 안됩니다.

이 문서는 CentOS 7 싱글모드 부팅 을 어떻게 하는지에 대한 글 입니다.

1. 싱글모드 부팅 (Single Mode Booting)

CentOS 7 에서는 부팅 매니저가 Grub2 로 변경 되었습니다. Grub2 부팅 매니저가 나오면 ‘e’ 를 클릭해서 부팅 커널 이미지를 선택 합니다.

CentOS 7 Grub2
CentOS 7 Grub2

그러면 선택한 커널 이미지에 대한 Grub2 의 각각지 설정값들을 볼수 있으며 커널 이미지와 이에 대한 옵션들을 볼 수 있습니다.

CentOS 7 Grub2 에서 부팅 커널 편집
CentOS 7 Grub2 에서 부팅 커널 편집

화면를 자세히 보시면 파일시스템 마운트 옵션인 ‘ro’를 보실 수 있습니다. 이부분을 다음과 같이 바꿔 줍니다.

CentOS 7 싱글모드 옵션
CentOS 7 싱글모드 옵션

‘ro’ 되어 있던 부분을 ‘rw init=/sysroot/bin/sh’ 로 변경해 줍니다. 그리고 아래부분에 나온대로 Ctrl-x 를 눌러줍니다. 그러면 부팅이 됩니다.

CentOS 7 싱글모드
CentOS 7 싱글모드

가만히 생각해보면 CentOS 7 에서의 싱글모드 부팅은 CentOS 7 의 복구부팅(Rescue Booting) 과 유사합니다.

CentOS 7 싱글모드로 부팅을 하면 /sysroot 디렉토리로 파일시스템을 rw 모드로 마운팅을 해줍니다.

2. Root 패스워드 리셋

보통 싱글모드 부팅을 하는 많은 목적중에 Root 패스워드 리셋이 있습니다. Root 패스워드를 잊어먹어서 Root 패스워드를 바꾸고 싶을때에 싱글모드로 부팅을 하면 인증없이 Root 쉘을 받을 수 있고 그래서 바로 바꿀 수 있습니다.

그런데 CentOS 7 에서 싱글모드 부팅은 복구모드처럼 부팅을 하기 때문에 먼저 파일시스템을 chroot 를 이용해서 변경해 줍니다.

CentOS 7 싱글모드에서 패스워드 변경
CentOS 7 싱글모드에서 패스워드 변경

‘chroot /sysroot’ 로 변경을 하고 패스워드를 변경해 줍니다. ‘touch /.autorelabel’ 는 SELinux 를 신규로 업데이트 해주는 겁니다.

Bash History 를 Syslog 에 남기기

centos logoBash 쉘은 명령어 히스토리 기능을 제공 합니다. history 명령어를 입력하면 지금까지 사용했던 Bash 명령어들이 모두 보여 줍니다.

이러한 기능은 사용자 홈 디렉토리에 ‘.bash_history’ 파일에 기록되어 집니다. 그러나 여러 사람이 사용하는 서버에서 각 사용자 홈 디렉토리에 히스토리를 남기기 보다는 리눅스의 syslog 에 남기게 함으로써 사용자가 못된 일을 하는지 않하는지를 감시하도록 하면 좋을 것입니다.

이 문서는 Bash History 를 Syslog 에 남기기 에 대한 것입니다.

1. logger 를 이용한 방법

logger 는 쉘 명령어를 syslog 에 적도록하는 모듈 입니다. 이를 이용하면 수동으로 syslog 에 기록을 남기게 할 수 있습니다. 이를 이용해서 다음과 같이 /etc/profile.d/cmd.sh 파일을 작성 합니다.

이제 이를 Source 해 줍니다.

2. rsyslog 설정

다음과 같이 설정을 해줍니다.

이렇게하면 ‘/var/log/bash_history’ 파일에 Bash 쉘 히스토리가 남기며 ‘192.168.0.2’ 서버에 로그를 전송합니다.

CentOS 7 에서 네트워크 인터페이스 이름 바꾸기

centos logoCentOS 7 은 많은 변화가 있었지만 그중 하나가 네트워크 인터페이스 이름 입니다.

지금까지 네트워크 인터페이스 이름은 eth0, eth1 식으로 고정되어 있었습니다. 하지만 CentOS 7 부터는 바이오스 장치 이름을 조합해서 생성됩니다. 다시 말해서 각 서버마다 네트워크 인터페이스 카드가 모두 동일하다고 할지라도 CentOS 7 에서의 네트워크 인터페이스 이름은 모두 다를 수가 있다는 겁니다.

이 문서는 CentOS 7 에서 네트워크 인터페이스 이름 바꾸기 에 대한 것입니다.

1. 상태확인

CentOS 7 에서의 네트워크 인터페이스 이름은 다음과 같습니다.

enp0s3 으로 나옵니다. 네트워크 인터페이스 설정 파일도 이름과 동일하게 생성 됩니다.

2. eth0 로 바꾸기

바꾸는 방법은 grub 에서 바이오스장치이름을 네트워크 인터페이스 이름으로 사용하지 않겠다고 선언하는 것입니다. 커널이 업그레이드되면 grub 에 파라메터값을 다시 해줘야 하는 불편함을 없애기위해서 grub 의 기본설정을 변경해 줍니다.

핵심은 ‘GRUB_CMDLINE_LINUX’ 에 ‘net.ifnames=0 biosdevname=0’ 를 추가하는 것입니다. 그리고 다음과 같이 grub2 를 다시 만들어 줍니다.

이제 네트워크 인터페이스 파일을 다음과 같이 바꿔 줍니다.

이제 재부팅을 하면 네트워크 인터페이스 이름이 eth0 로 올라온것을 볼 수 있습니다.

Nvidia Driver 설치하기

nvidia-linux

Nvidia 는 AMD 와 함께 그래픽 카드 시장을 양분하고 있는 회사 입니다. 어느쪽이 더 좋은지는 각 회사에 새로운 제품이 출시될때마다 달라질만큼 우열을 가리기 힘듭니다.

Nvidia 와 AMD 는 리눅스를 위한 드라이버도 만들어서 배포하는데 CentOS 7 에서 Nvidia Driver 를 설치하는 방법에 대한 것입니다. 사실 AMD HD2600 을 썼었는데 AMD 에서 더 이상 드라이버를 업데이트를 해주지 않아 그래픽 가속을 쓸수 없었는데, 몇일전에 Nvidia Geforce GTS 450 이 손에 들어와서 설치하는 과정을 정리한 글이기도 합니다.

1. Requirement

Nvidia Driver 를 설치하기 위해서는 컴파일을 위한 의존성 패키지가 설치되어 있어야 합니다. 의존성 패키지는 다음과 같이 설치하시면 됩니다.

또, Nvidia 를 비롯한 그래픽 카드 드라이버는 커널에 의존적이기 때문에 커널이 바뀌면 재컴파일 설치를 해줘야 합니다. 따라서 이왕설치하는거면 최신 커널에 설치를 하는게 좋을 것입니다. 다음과 같이 커널을 업그레이드 해주고 시스템을 리붓해줍니다.

2. Nouveau driver 제거

CentOS 7 에도 Nvidia Driver 가 설치되어 있습니다. 오픈소스 드라이버인데, 여기에는 Nouveau 가 활성화되는 모듈이 포함됩니다. Nvidia Driver 는 이 Nouveau 모듈과 충돌이 나서 설치하는데 지장이 생깁니다.

141115-0001

이는 다음과 같이 확인이 가능 합니다.

부팅할때에 nouveau 를 메모리에 올리못하게 하면 이 문제는 해결 됩니다. 다음과 같이 blacklist 에 적어줍니다.

위와같이 파일을 작성하고 저장한 후에 시스템을 리붓해 줍니다.

3. 설치

Nvidia Driver 를 Nvidia 홈페이지에서 다운받습니다. 그리고 CentOS 7 을 그래픽 모드가 아니 터미널 모드로 전환해주고 다운받은 파일을 실행해 줍니다.

 

4. 마치며

nouveau 비활성화는데에는 grub 을 이용할 수도 있습니다. 부팅할때에 grub 에 옵션으로 주면 비활성화를 할 수 있는데, CentOS 7 에선 grub2 를 사용하고 grub2 에서는 공통부분을 /etc/default/grub 에 “rdblacklist=nouveau” 를 추가해줍니다.

“GRUB_CMDLINE_LINUX” 에 추가해줍니다.

그리고 grub2 를 갱신해줍니다.

이렇게하면 blacklist 파일을 작성하지 않고도 nouveau 모듈을 부팅때마다 비활성화 할 수 있습니다.