얼떨결에 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 다루기도 무엇보다 쉽지가 않네.

기본적으로 JBoss EAP 7.4에서 운영 중에 있으나, 특정 서비스의 요구사항으로 JBoss EAP 8.0을 설치한 서버가 있다. standalone.xml의 설정 과정에서 datasource 서브시스템의 security 절을 동일하게 사용하고, security 서브시스템으로 username과 password를 관리하려 했으나 8.0에서는 security 서브시스템을 지원하지 않고, elytron 서브시스템을 사용해야한다.

<subsystem xmlns="urn:jboss:domain:datasources:7.0">
  <datasources>
    <datasource jndi-name="java:/fooDs" pool-name="fooDs" enabled="true" use-java-context="true">
      <connection-url>...</connection-url>
      <driver>oracle</driver>
      <!-- 생략 -->
      <security>
        <security-domain>fooDs</security-domain>
      </security>
    </datasource>
  </datasources>
</subsystem>

<subsystem xmlns="urn:jboss:domain:security:2.0">
  <security-domains>
    <security-domain name="fooDs" cache-type="default">
      <authentication>
        <login-module code="org.picketbox.datasource.security.SecureIdentityLoginModule" flag="required">
          <module-option name="username" value="foo"/>
          <module-option name="password" value="bar"/>
        </login-module>
      </authentication>
    </security-domain>
  </security-domains>
</subsystem>

 

7.4에서 쓰던 설정이고, 그대로 8.0에 옮겼을 때는 앞에서 말했듯이 오류가 난다.

/subsystem=elytron/credential-store=fooDs-credstore:add( \
    path=fooDs-credstore.cs, \
    relative-to=jboss.server.config.dir, \
    create=true, \
    credential-reference={clear-text="5dOaAVafCSd;12345678;100} \
)

/subsystem=elytron/credential-store=fooDs-credstore:add-alias(\
    alias=fooDs-username, \
    secret-value="foo" \
)

/subsystem=elytron/credential-store=fooDs-credstore:add-alias(\
    alias=fooDs-password, \
    secret-value="bar" \
)

 

우선 security를 elytron으로 변경하기 위해 jboss-cli에서 elytron credential store를 생성한 뒤에, store에 username과 password를 각각 저장해준다.

<security>
  <credential-reference>
    <store>fooDs-credstore</store>
    <alias>fooDs-username</alias>
    <clear-text-credential-reference>
      <store>fooDs-credstore</store>
      <alias>fooDs-password</alias>
    </clear-text-credential-reference>
  </credential-reference>
</security>

 

설정한 datasource에서 security 구문만 바꿔줬더니 오류가 난다.

<security>
  <credential-reference store="fooDs-credstore" alias="fooDs-username"/>
  <credential-reference store="fooDs-credstore" alias="fooDs-username"/>
</security>

 

store랑 alias를 credential-reference의 속성으로 넣어봐도 오류가 난다.

<subsystem xmlns="urn:jboss:domain:datasources:7.1">

 

datasources 서브시스템을 7.0에서 7.1로 올리면 store를 사용할 수 있다고 해서 변경하였으나 오류가 난다.

<security>
  <user-name>foo</user-name>
  <credential-reference store="fooDs-credstore" alias="fooDs-username"/>
</security>

 

다시 datasources 서브시스템을 7.0으로 바꾸고 username을 user-name 태그로 변경하니 성공했다.

/subsystem=datasources/data-source=fooDs:test-connection-in-pool
{
    "outcome" => "success",
    "result" => [ture]
}

 

username은 평문으로 쓰고, password만 elytron credential store에 등록해서 설정하면 되는듯하다.

서비스 운영 중 일부 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 정규표현식을 더 상세하게 작성함).

일년에 몇 번 안 쓰는 기능을 쓰다보니 오류 때문에 진행이 안 된다고 연락이 왔다.

JBWEB002004: More than the maximum number of request parameters (GET plus POST) for a single request (10000) were detected. Any parameters beyond this limit have been ignored. To change this limit, set the maxParameterCount attribute on the Connector.

 

처음에는 제대로 안 읽고 single request 뒤의 (10000)에만 집중해서 WEB 서버에 접속해 Apache의 ssl.conf 파일을 여니 LimitRequestFields가 딱 10000이었다. 문제는 스테이징 서버도 동일 설정 값이어서 운영 서버에서 오류가 나는게 이상한 상황이었지만, 일단 10000에 꽂혔으니 늘려봤다. 당연히 결과는 허탕.

<system-properties>
    ...
    <property name="org.apache.tomcat.util.http.Parameters.MAX_COUNT" value="20000"/>
</system-properties>

 

찾아보니 JBoss의 standalone.xml에 위 내용을 추가해보라고 해서 추가하니 잘 됐다. 기본값이 10000이었다.

+ Recent posts