얼떨결에 JMeter로 부하테스트를 진행하고 있었는데, 연결 시간 초과나 소켓 닫힘 같은 기본적인 네트워크 오류가 특정 시점부터 많이 발생하길래 (1) 스위치나 보안장비에서 동일 IP의 동시 요청 건수를 제한하거나 또는 그런 설정이 있는지 확인하였으나 본인측 요청에 의해 보안장비는 필터링 제외를 시켰고, 스위치는 기본 셋팅이라 제한이 업다고 한다. (2) 당시 Apache의 mpm 설정은 worker로 되어있었고, MaxRequestWorkers는 4,096으로 설정되어 있었다. 모니터링 했을 때 Busy Worker가 폭등하다가 폭락하는 그래프를 보였고 폭락 지점이 네트워크 오류가 다수 발생하는 시점이었다.

 

여기서 부하발생기가 2,000 스레드로 요청을 발생하고 있었는데, 모든 요청을 온전하게 받아들이지 못하고 있었다고 판단을 하였다. 부하발생기의 자원사용률이나 TCP 소켓의 TIME_WAIT 상태는 크게 많지 않았음에도 불구하고 왜 이런 그래프가 반복되는가 고민하는 찰나에 작년 일이 생각났다. Apache를 운영하는 서버의 커널 파라미터를 수정했었다. 당시 사용하던 서버는 Destroyed 되었기 때문에 설정값은 기억나지 않지만(기록이란 참 중요하다) TCP 소켓 reuse와 recycle에 관련했던거 같다.

 

하지만 이게 왠걸 tcp_tw_reusetcp_tw_recycl\은 기본적으로 Outbound 연결에 대해서 효과가 있고 Web 서버 같이 Inbound 연결이 많은 환경에서는 아무 의미가 없다고 한다. 심지어 tcp_tw_recycle은 최신 커널에서는 폐기된 파라미터다. 도대체 작년에는 왜 그런일을 하면서 해골물을 마셨었는지 알 수가 없다.

 

각설하고 이번에는 net.core.somaxconn 파라미터를 수정했다. 연결에 대한 대기열(Queue)인데 기본값인 512로 설정되어 있었다. 부하발생기의 스레드는 반복해서 요청을 보내고 있는데 일부 응답이 늦어지며 연결을 유지하고 있었던 것들에 의해 대기열에 들어갔어야 했다. 하지만 대기열의 크기가 512에 불과하니 연결에 실패하는 등의 오류가 발생했던 것으로 판단되었다. net.core.somaxconn의 값은 Apache의 MaxRequestWorkers의 값과 동일하거나 그 이상으로 설정 해주면 된다.

sudo vim /etc/sysctl.conf

### 파일 내용에서 값을 찾아 수정하거나, 없으면 추가
net.core.somaxconn = 8192

# 바로 적용
sudo sysctl -p

 

그리고 Apache 설정도 바꿔줘야한다. 나중에 헷갈리지 않게 mpm.conf 쪽에다 추가했다. 해당 값은 net.core.somaxconn보다 작거나 같고, MaxRequestWorkers보다 큰 값으로 설정하면 된다. 나는 net.core.somaxconn과 같은 값으로 설정했다.

ListenBacklog 8192

 

Apache도 재기동하고 다시 요청을 보내니 관련 오류가 현저하게 줄어들어 Busy Worker 그래프가 폭락하지는 않았고, 코끼리를 삼킨 보아뱀처럼 그래프가 그려졌다. 아무튼 이를 통해 일부 문제를 해결할 수 있었다. JMeter 다루기도 무엇보다 쉽지가 않네.

아파치를 Docker로 돌리고 싶지만 사정상 서버에 직접 설치해서 운영 중이다. 이번에 다른 목적으로 WEB 서버 6대가 추가되었는데, 6개의 콘솔을 열어서 설정파일을 수정하고 재기동하는건 말도 안 되게 귀찮기 때문에 Bash 스크립트를 작성했다.

ssh 키 쌍 생성하여 패스워드 입력 없이 서버 연결 설정

# 1.키 쌍 생성
$ ssh-keygen -t rsa
# Enter file in which to save the key : 엔터 (기본 경로 ~/.ssh 사용)
# Enter passphrase : 엔터 (패스워드 없이 사용)
# Enter same passphrase again : 엔터

# 2. 생성된 공개키를 대상 서버에 복사
$ ssh-copy -id -p 22 username@remote.server.ip

# 3. 연결 확인
$ ssh -p 22 username@remote.server.ip

ssh 연결하여 명령 실행

# 한 개의 명령 실행
$ ssh -p 22 username@remote.server.ip "touch test"

# 여러 개의 명령 실행(세미콜론으로 구분)
$ ssh -p 22 username@remote.server.ip "touch test; mv test tset"

스크립트 작성

  1. 설정 파일의 수정이 있을 때 파일명.YYYYMMDD 형태로 파일을 백업한다.
  2. conf.d의 ssl.conf, vhost.conf, workers.properties 위주로 수정한다.

스크립트 작성의 전제 조건이다.

#!/bin/bash

APACHE_CONFD_PATH=/etc/httpd/conf.d
DATE=`date +%Y%m%d`
# 오늘 날짜로 수정된 파일명
MODIFIED_CONF_LIST=`ls -1 ${APACHE_CONFD_PATH} | grep ${DATE} | sed 's/\.[^.]*$//`
# 대상 서버 목록
SERVERS="192.168.0.2 192.168.0.3 192.168.0.4 192.168.0.5 192.168.0.6"

# 수정된 파일별로 반복
for CONF in ${MODIEFIED_CONF_LIST}
do
    # 대상 서버별로 파일 전송
    for SERVER in ${SERVERS}
    do
        scp -P 22 ${APACHE_CONFD_PATH}/${CONF} apache@${SERVER}:${APACHE_CONFD_PATH}/${CONF}
        scp -P 22 ${APACHE_CONFD_PATH}/${CONF}.${DATE} apache@${SERVER}:${APACHE_CONFD_PATH}/${CONF}.${DATE}
    done
done

# 아파치 재기동
for SERVER in ${SERVERS}
do
    ssh -n -p 22 apache@${SERVER} "sudo /home/apache/apache_restart.sh"
    sleep 5
done

sudo /home/apache/apache_restart.sh

 

기존에는 apache_stop.shapache_start.sh를 따로 실행해서 재기동을 했으나, 한 번의 명령으로 재기동 하기 위해 각 서버에 apache_restart.sh를 추가 작성했다.

#!/bin/bash

# 아파치 종료
sh /home/apache/apache_stop.sh

while true
do
    # 프로세스에 httpd가 없으면(내가 실행한 grep 하나만 나오면) 탈출
    if [ `ps -ef | grep -c httpd` -eq 1 ]
    then
        break
    fi

    sleep 1
done

# 아파치 시작
sh /home/apache/apache_start.sh

서비스 운영 중 일부 js 파일 등 static content의 노출로 인한 취약점 해결을 위해서 찾아보았다. 사용자의 직접 접근 요청을 차단하고, 설정한 referer의 경우에만 Directory에 접근하여 기능이 정상적으로 동작하도록 한다.

SetEnvIf Referer ^http(s)?:\/\/ allow_access
...
<Directory "/foo/bar">
    Require all denied
    Require env allow_access
</Directory>

 

Apache의 mod_setenvif 를 참고하면 SetEnvIf 지시어로 환경변수를 설정할 수 있다. 첫번째 인자는 attribute로 HTTP 요청 헤더나 Remote_Host, Remote_Addr 등을 지정하고, 두번째 인자로 정규표현식을 지정하는데 regex가 attribute에 대응하면 된다. 세번째 인자는 변수명이다.
 
따라서, 설정한 값은 HTTP 요청 헤더 중 Referer 헤더가 http:// 또는 https://로 시작하면 allow_access가 참이 된다. 그리고 VirtualHost에서 Directory 지시자로 지정한 경로(서버 기준의 절대경로)에 allow_access 대응했을 경우에만 접근이 가능하다. 목적은 직접 접근 요청 차단이어서 해결 완료(실제 서버에는 Referer 정규표현식을 더 상세하게 작성함).

+ Recent posts