티스토리 뷰


1. 실시간 채팅 기능을 위한 웹소켓에서 발생하는 에러

 

우리 서비스의 주요 기능이기도 한 실시간 대화방은 소켓 통신을 활용해서 구현했다. 

 

클라이언트와 서버는 TCP의 3 way handshake 방식을 사용해서 네트워크를 형성하고,

클라이언트가 메시지를 보내면 해당 메시지를 데이터베이스에 저장하고 Broadcast 하여

대화방에 다른 사용자들에게도 실시간으로 메시지를 던져주는 방식이다.

 

로컬에서는 분명 잘 작동하던 소켓 통신... 하지만 쿠버네티스에 적용하려니 정말 끝도 없는 오류를 쏟아냈다.

우선 가장 난감했던 것은 ws 프로토콜로 이뤄진 웹소켓 연결 request url인데,

http를 고칠 때와 다르게 어떤 url을 써줘야 하는지 여러 번의 실험을 시도해야 했다. 

 

또한 웹소켓 연결 에러가 거의 3초에 한 번씩 발생했는데 polling 방식이라 어쩔 수 없는 것 같다.

해당 에러는 웹소켓 통신을 해결한 후에도 지속됐다. 

 

초반에는 아예 채팅 기능이 먹통이었는데 현재는 웹소켓을 해결한 상황이다. 이를 해결한 방법을 공유해 보겠다.

[1] 웹소켓 통신 코드 수정

백엔드의 소켓 연결부: socket.js

const db = require("./db");
const SocketIO = require("socket.io");
const http = require("./app");
const io = SocketIO(http, {
  path: '/socket.io'
});

io.on('connection', function (socket) {
  console.log('Connect from Client Chat: ' + socket.id);
  socket.on('send', function (data) {
    saveMessage(data);
    socket.broadcast.emit('send', data.message, data.sender, data.s_name);
  });
  socket.on('sendDm', function (data) {
    saveDm(data);
    socket.broadcast.emit('sendDm', data.message, data.sender, data.s_name, data.receiver);
  });
})

요청 경로가 '/socket.io'인 request에 대해 연결을 받아주도록 코드를 구성했다. 

 

프론트엔드의 소켓 연결부: CheckLogin.vue

// socket 서버에 연결
function socketConnect() {
  const s_client = io("http://sc-chatting.ddns.net", {
    path: "/socket.io" // 서버 path와 일치시켜준다
  });

  // 연결 성공 시 이벤트 핸들러
  s_client.on("connect", () => {
    console.log("웹소켓 서버에 연결되었습니다.");
    // 연결된 소켓을 userData.socket에 저장
    userData.socket = s_client;
  });

  // 연결 실패 시 이벤트 핸들러
  s_client.on("connect_error", (error) => {
    console.error("웹소켓 연결 실패:", error);
  });

  // 연결이 끊긴 경우 이벤트 핸들러
  s_client.on("disconnect", (reason) => {
    console.warn("웹소켓 연결이 끊어졌습니다. 이유:", reason);
  });

  // 오류 발생 시 이벤트 핸들러
  s_client.on("error", (error) => {
    console.error("웹소켓 오류:", error);
  });
}

서버와 소켓 io 요청 경로를 일치시켜 준다.

 

[2] 인그레스에서 /socket.io 경로로 들어온 request는 서버인 backend-service로 포워딩하도록 수정

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: sc-ingress
  annotations:
    nginx.ingress.kubernetes.io/websocket-services: sc-backend-service:5000   
spec:
  ingressClassName: nginx                       # ingress-nginx-controaller 명시
  rules:
    - host: sc-chatting.ddns.net                # SC통신의 도메인 네임
      http:
        paths:
          - path: "/api"                        # api request를 백엔드 서비스로 매핑하도록
            pathType: Prefix
            backend:
              service:
                name: sc-backend-service        # 백엔드 서비스의 도메인 네임
                port:
                  number: 5000                  # 백엔드 서비스의 포트 번호 5000
          - path: "/socket.io"                  # socket.io 백엔드 서비스로 매핑
            pathType: Prefix
            backend:
              service:
                name: sc-backend-service
                port:
                  number: 5000                  # 백엔드 서비스의 포트 번호 5000
          - path: "/"
            pathType: Prefix
            backend:
              service:
                name: sc-frontend-service       # 프론트엔드 서비스의 도메인 네임
                port:
                  number: 80                    # 프론트엔드 서비스의 포트 번호

 

🗝️결과

실시간 채팅 성공
콘솔에도 성공 메시지가 출력된다.

하지만 파드가 하나일 때는 잘 작동하지만, 파드를 두 개 이상으로 늘리면 바로 에러가 발생한다. 이는 멀티 노드로 구성했을 때 소켓 통신에서 흔히 일어나는 문제라고 한다. 공식 사이트에서 해결 방법을 제시하지만 나는 여전히 해결하지 못했다.. 해결 방식은 Sticky Session인데 한번 연결된 서버에 대해선 진득하게 세션을 유지하도록 해서 멀티 노트 상황에서 연결이 자꾸 끊기는 것을 방지는 방법이다. 추후에 시도해서 해결해 보도록 하겠다.

 

Using multiple nodes | Socket.IO

When deploying multiple Socket.IO servers, there are two things to take care of:

socket.io


이렇게 해서 프로젝트의 모든 기능 구현을 완료했다! 

나는 쿠버네티스 프로젝트를 통해 정말 많이 성장할 수 있었다. 

초면엔 낯설고 어렵기만 했던 쿠버네티스의 오브젝트를 직접 적용해 보고, 너무 해보고 싶었던 생애 첫 배포도 해보았다.

소중한 해당 프로젝트에 대해 마지막 정리 겸 회고록을 조만간 들고 오도록 하겠다.

 

끝! 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함