Connection reset by peer 에러 발생 및 관련 처리_1

2024. 6. 18. 16:10개발

반응형

 

현재 회사에서 사용하는 KT Cloud의 경우 로드밸런서 타입이 TCP 형태로 동작하고 있는데 이 경우 사용자가 요청하는 IP(Client IP) 통칭 CIP가 정상적으로 들어오지 않고 앞단에 있는 웹서버의 IP가 찍히는 이슈가 있었다.

 

그에따라  kt cloud 에서 연결하던 로드밸랜서의 설정을 바꿔야 하는 일이 생겼다.  

SSL 인증서를 웹서버에서 처리하는 방식이 아닌 로드밸런서에서 인증서가 동작하는 방식으로 처리해야지만 CIP가 정상적으로 처리된다는 공식 가이드문서의 설명이 있었기 때문도 있었다.

그 외에도 다음과 같은 차이점이 있다.

  1. 로드밸런서에 인증서 세팅이 가능한데 현재는 인증서를 각 서버의 아파치 웹서버에 다 세팅해두고 있음.
    • 인증서 교체 시기가 다가오면서 관리 포인트가 증가한다.
    • 로드밸런서에 세팅하는 경우 로드밸런서쪽에서 하나만 관리하면 되는데 웹서버의 경우 웹서버가 늘어날 수록 +n개 만큼 관리해야한다.
  2. 인증서를 등록해두면 클라우드 쪽에서 보내주는 알람도 세팅 할 수가 있음.(만료관련)
    • 기존 인증서를 발급한 사이트에서도 만료알림을 보내주긴 하지만 그와 별개로 클라우드 서버에서도 보내 주는 것으로 체크가 가능하다.

 

그리고 로드밸런서 타입을 변경 한 다음 일정시간이 지난 뒤 였다. 요즘 API 통신을 할 때 Connection reset by peer 와 같은 오류가 발생한다고 전달을 받았다.

Caused by: java.io.IOException: Connection reset by peer

Connection prematurely closed BEFORE response 와 같은 오류도 발생 했다고 한다.

Connection prematurely closed BEFORE response

 

여러가지 사람들이 정리해 둔 글과 개인적인 생각을 정리해보았을때 기존에는 IP Hash 방식으로 동작하고 있던 로드밸런서를 인증서 등록하면서 Round Robin 방식으로 변경해야 했던 이슈가 있었는데… 그 부분 때문에 문제가 생기는 것으로 생각했다.

아래 글과 유사하게 로드밸런서나 웹서버쪽의 설정된 idle timeout 시간의 영향이 있는게 아닐까 싶었다.

Connection Reset by Peer 문제 해결

 

Connection Reset by Peer 문제 해결

Client 가 요청을 보냈는데 서버쪽에서 연결이 닫혔다고 다시 연결하라는 RST (Reset) 패킷을 보내는 경우에 이 에러가 발생한다.Connection prematurely closed BEFORE response 이렇게 쓰기도 한다. Client-Server 연

velog.io

Connection prematurely closed BEFORE response 에러 대응기

 

Connection prematurely closed BEFORE response 에러 대응기

오늘 다룰 내용은 Reactor Netty HTTP Client에서 간헐적으로 발생했던 Connection prematurely closed BEFORE response 에러와 관련된 내용 입니다. 왜 이 에러가 발생하는지 원인을 추적하는 과정과 어떻게 대응 했

alden-kang.tistory.com

해당 글에서는 로드밸런서에서 연결해주는 서버에 따라서 동일 오류가 발생 할 수 있는 상황에 대해서 설명해주고 있다.

 

문제가 발생한 결론으로는 다음과 같이 보였다.

서버끼리 3 Way HandShake를 통해서 커넥션을 맺고 그에 따라 1회성으로 통신이 끝나게 된다.

그러나 그 경우에는 단건 요청 시 마다 발생함으로써 서로를 확인 하는 절차에 대한 리소스를 너무 많이먹게되고, KeepAlive 라는 옵션값이 생겨나게 되었다.

최초에 3 Way HandShake를 연결 한 다음 일정시간동안 동일 요청이 들어오는 경우 연결을 유지시켜줌으로써 확인하는데 사용되는 리소스를 줄이는 형태로 동작하게 되는데.. 무제한으로 열어 둘 수는 없기 때문에 시간제한을 두게된다.

그 이후에 연결이 유지되어 있는것으로 생각하고 요청이 들어오는 경우 서버에서 모르쇠로 일관하는 것이 이 해당 문제가 발생하는 이유로 보인다.

대부분의 글에서는 WebClinet를 사용 하는 경우 maxIdleTimeout 을 설정해주라고 한다.

조금 최신에 가까운 버전이나 사용하는 reactor core의 버전이 높은 경우에는 다음과 같이 세팅을 해주면 된다.

 

WebClient를 사용하여 통신하는 부분에서 고정된 크기의 커넥션풀을 생성하여 그것으로 자원활용을 하며 내부적으로 maxIdleTime을 지원하는 형태였다.

그리고 이 provider 부분이 추가되야한다.

		ConnectionProvider provider = ConnectionProvider.builder("fixed_pool")
				.maxConnections(500)
				.maxIdleTime(Duration.ofSeconds(20))
				.maxLifeTime(Duration.ofSeconds(60))
				.pendingAcquireTimeout(Duration.ofSeconds(60))
				.build();
		
		reactor.netty.http.client.HttpClient httpClient = reactor.netty.http.client.HttpClient.create(provider)
				.tcpConfiguration(tcpClient -> tcpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, CONNECT_TIMEOUT))
				.followRedirect(true)
				.secure(t -> {
					try{
						t.sslContext(SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build());
					}catch(SSLException e){
						e.printStackTrace();
					}
				});
		return WebClient.builder()
				.clientConnector(new ReactorClientHttpConnector(httpClient))
				.build();

 

그러나 언제나 문제가 발생한다..

기존에 사용하는 reactor-core의 경우에는 위와 같은 provider의 builder 메소드가 추가가 되지 않은 옛날 버전이였다.

그리고 버전을 올리는 경우 알 수 없는 에러가 발생 할 가능성이 높아 지고, 그에 따른 모니터링 관련 부담이 늘어날 수 있어서… 최대한 가까운 버전에서 maxIdieTime을 지원하는 버전으로 세팅이 필요했다.

또한 버전을 조금만 올리면 기존에 적용되어있는 tcpConfiguration 의 경우 deprecated가 되어버리는 형태로 변하기도 했다.

현재 스프링스타터의 버전의 경우 2.1.0 해당 스타터에서 호환성과 관련해서 세팅해준 버전의 경우 0.8.2버전 이였다.

그 중에서 가장 가까운 버전 중에서 해당 옵션값을 지원하는 버전의 경우 공식 사이트에서 확인 해 본 결과 0.90.0 버전으로 확인되었다.

공식홈페이지에서 0.9.0에서부터 추가된 fixed메서드에 maxIdleTime 값을 지정 할 수 있는 기능이 추가되었다.

Reactor Netty 0.9.0.M3

 

Reactor Netty 0.9.0.M3

 

projectreactor.io

POM.XML에 추가 적용한 다음.

		<dependency>
			<groupId>io.projectreactor.netty</groupId>
			<artifactId>reactor-netty</artifactId>
			<version>0.9.0.RELEASE</version>
		</dependency>

다음과 같이 변경 적용 하였다.

ConnectionProvider provider = ConnectionProvider.fixed("fixed_connection_pool", MAX_CONN_TOTAL, CONNECT_TIMEOUT, Duration.ofMillis(CONNECT_TIMEOUT));
		reactor.netty.http.client.HttpClient httpClient = reactor.netty.http.client.HttpClient.create(provider)
				.tcpConfiguration(tcpClient -> tcpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, CONNECT_TIMEOUT))
				.followRedirect(true)
				.secure(t -> {
					try{
						t.sslContext(SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build());
					}catch(SSLException e){
						e.printStackTrace();
					}
				});
		return WebClient.builder()
				.clientConnector(new ReactorClientHttpConnector(httpClient))
				.build();

실질적인 0.90.0 버전의 구현부를 보면 다음과 같이 해당 옵션값을 사용중이다.

static ConnectionProvider fixed(String name, int maxConnections, long acquireTimeout, @Nullable Duration maxIdleTime) {
        if (maxConnections == -1) {
            return elastic(name);
        } else if (maxConnections <= 0) {
            throw new IllegalArgumentException("Max Connections value must be strictly positive");
        } else if (acquireTimeout < 0L) {
            throw new IllegalArgumentException("Acquire Timeout value must be positive");
        } else {
            return new PooledConnectionProvider(name, (allocator, destroyHandler, evictionPredicate) -> {
                return PoolBuilder.from(allocator).sizeBetween(0, maxConnections).maxPendingAcquireUnbounded().destroyHandler(destroyHandler).evictionPredicate(evictionPredicate.or((poolable, meta) -> {
                    return maxIdleTime != null && meta.idleTime() >= maxIdleTime.toMillis();
                })).fifo();
            }, acquireTimeout, maxConnections);
        }
    }

위와같이 적용한 다음 한동안 Connection By Peer 에러가 발생하지 않기에... 정상적으로 잘 처리가 되는것으로 오해를 하고 있었으나 추후 다른 문제가 야기되었다....!!

반응형