Skip to content

SIGTERM SIGINT

다른 컨테이너들은 무중단 배포가 이상 없이 성공하는데 tradurs-back 컨테이너는 자동 배포 시 구동 후 일정 시간이 지나면 중지돼버리는 문제가 발생했습니다.

docker logs 명령으로 해당 컨테이너 로그를 확인했습니다.

shell
$ sudo docker logs 컨테이너
console
fatal: false,
errno: 45028,
sqlState: 'HY000',
code: 'ER_GET_CONNECTION_TIMEOUT' 
Error: retrieve connection from pool timeout

mariadbpool이 일정 시간마다 timeout이 발생하고 있었습니다.

  • mariadb 미들웨어acquireTimeout 설정값을 늘려줘도 같은 시간마다 timeout 발생했습니다. 단순히 pool 연결 시간문제가 아니라 MariaDB 서버가 연결된 pool을 강제로 끊어버리고 있었습니다.

  • ✔️ Nginx에서 tradurs-back 컨테이너의 설정 부분을 주석 처리하고 컨테이너를 restart 명령으로 재시작 했습니다. 컨테이너가 당연하게(?) 중지되지 않고 정상적으로 동작합니다.

    • 내부적인 연결에는 문제가 없고, Nginx를 통해 외부와 연결될 때만 문제가 발생하고 있습니다.
    • 해당 상태(외부와 연결이 끊긴 상태)에서 Nginx를 tradurs-back 컨테이너와 연결하면 다시 timeout 문제가 발생합니다.

MariaDB 서버에 접속해 show processlist; 명령으로 connection pool 목록을 확인해 보기로 했습니다.

shell
$ sudo mariadb
shell
MariaDB > show processlist;
console
+----+--------+--------------+---------+---------+-------+-------+------+----------+
| Id | User   | Host         | db      | Command | Time  | State | Info | Progress |
+----+--------+--------------+---------+---------+-------+-------+------+----------+
|  0 | tester | 0.0.0.0:1000 | tr***** | Sleep   |     2 |       | NULL |    0.000 |
|  1 | tester | 0.0.0.0:1001 | tr***** | Sleep   |     1 |       | NULL |    0.000 |
|  2 | tester | 0.0.0.0:1002 | tr***** | Sleep   |     2 |       | NULL |    0.000 |
|  . |    .   |      .       |    .    |    .    |     . |       |  .   |        . |
|  . |    .   |      .       |    .    |    .    |     . |       |  .   |        . |
|  . |    .   |      .       |    .    |    .    |     . |       |  .   |        . |

정상적으로 연결된 pool 리스트가 보입니다.
다시 Nginx를 통해 외부와 연결되면, 알 수 없는 pool들이 급격하게 증가하고, 기본 설정된 timeout 시간(10초)이 경과하면 정상적으로 연결되었던 pool들까지 모두 강제로 끊어버립니다.

기존 tradurs-back 컨테이너에 연결 또는 대기 중이던 요청들이 호스트 네트워크에 남아있다가 Nginx가 연결되면 요청을 재개하면서 발생하는 문제라고 판단, 컨테이너를 중지하거나 새 컨테이너가 시작될 때 http server, http connection, socket connection, pool connection을 정리하는 로직을 추가하기로 결정했습니다.

Graceful Shutdown

소프트웨어의 기능을 통해 운영 체제가 프로세스를 안전하게 종료하고 연결을 닫는 작업을 수행하는 것을 뜻합니다.


Node.js의 index.js에 아래와 같은 SIGTERMSIGINT 신호 구문을 추가했습니다.

SIGTERM, SIGINT

  • SIGTERM : 프로세스에 전달되는 종료 신호의 하나로 kill 명령을 내릴 때 전송됩니다.
  • SIGINT : 프로세스에 전달되는 종료 신호의 하나로 Ctrl + C를 누를 전송됩니다.
js
// connections
const connections = new Set()

httpServer.on('connection', connection => {
  connections.add(connection)
  connection.on('close', (r) => {
    connections.delete(connection)
  })
})

// gracefully shutdown logic
const disconnect = async () => {
  console.log('All socket instances disconnect')
  try {
    io.disconnectSockets(true)
  }
  catch (e) {
    console.log(`Failed to disconnect socket instances : ${e}`)
  }

  console.log('Close all connection pools')
  await endPools()
}

const shutdownHandler = async () => {
  console.info('SIGTERM or SIGINT signal received')

  console.log('Closing http server.')
  httpServer.close(async (err) => {
    if (err) {
      console.log(`Failed to close http server : ${err}`)
    }

    await disconnect()
    console.log('Complete gracefully shutdown.')
    process.exit(0)
  })

  setTimeout(async () => {
    await disconnect()
    console.error('Could not close connections in time, forcefully shutting down')
    process.exit(1)
  }, 10000)

  connections.forEach(c => c.end())
  setTimeout(() => connections.forEach(c => c.destroy()), 5000)
}

// process kill signal
process.on('SIGTERM', shutdownHandler)
process.on('SIGINT', shutdownHandler)
  1. http 서버에 새로운 요청 연결 막기
  2. 일정 시간(5000ms) 이후 연결된 connection을 destroy
  3. 일정 시간(10000ms) 이후에도 http close가 완료되지 않을 경우 기존 http 연결 강제 닫기
  4. socket 연결 끊기
  5. mariadb pool 해제

프로세스에 SIGTERMSIGINT 신호를 받았을 동작하도록 핸들러를 연결해 주었습니다.

docker stop

docker stop 명령으로 SIGTERMSIGINT 신호를 보내더라도 Graceful Shutdown이 완료되기 전에 컨테이너가 종료되어 버리는 경우

shell
$ docker stop --time 60

stop 명령 옵션의 --time을 사용하여 대기 시간을 지정해 주면 최소 설정된 시간(초) 동안 컨테이너 종료 프로세스가 실행되도록 기다려줍니다.

Made with VitePress.