[프로젝트 종료] EyesTalk

Nam_JU·2022년 12월 17일
1

WebRTC-Project

목록 보기
18/18
post-thumbnail

2022.11.01 ~ 2022.12.13 약 한달간 진행했던 프로젝트가 끝났다.
우리가 만든 EyesTalk서비스를 정리해보자



1. WebRTC

WebRTC 는 P2P ( peer to peer Network) 로 단말간 데이터를 주고받는 표준기술이다. 웹/앱에서 카메라, 마이크를 이용하여 실시간 커뮤니케이션을 제공한다.

WebRTC를 사용한 이유

  • P2P 방식으로 미디어 스트림을 송수신 할 수 있는 유일한 표준이기 때문이다
  • P2P 방식이 아닌 다른 방식으로도 미디어 스트림을 송수신 할 수 있는 방식이 존재하기는 하지만, 오픈소스가 아니기 때문에 라이센스가 필요 없다
  • WebRTC 의 가장 큰 장점 - 실시간에 가까운 낮은 지연시간 때문이다

Mesh 연결방식을 사용한 이유

MCU는 실시간성 저해라는 치명적인 단점 때문에 고려하지 않았고, Mesh와 SFU 방식중에 고민했다

  • SFU의 가장 큰 단점 - 서버 비용에 대한 부담
    SFU는 더 많은 클라이언트끼리의 통신을 할 수 있다라는 장점이 있지만, 그만큼 서버의 트래픽이 늘어난다. 즉, 중앙 미디어 서버에 대한 비용이 가장 큰 부담으로 생각되었다.
  • Mesh 형태의 장점 - 낮은 서버 비용과 실시간에 가까운 낮은 지연시간
    Mesh의 가장 큰 장점 2가지를 뽑자면 낮은 서버 비용과 실시간에 가까운 낮은 지연시간이다. 이러한 두가지 장점이 현재 우리상황에서 가장 알맞는 장점이라는 생각이 들었다.

2. Project Schedule


일정 관리를 보면 2주이상 webRTC이론 이해가 할애된 것을 볼 수 있다.
WebRTC의 연결방식과, 어떤 기술스택을 사용하는지 시행착오를 겪고 난 뒤 본격적으로 서비스를 제작하게되었다.

시행착오를 겪으면서 작성한 벨로그
https://velog.io/@jupiter-j/Project-webrtc-ec2-실행
https://velog.io/@jupiter-j/프로젝트-수행-webRTC-ec2업로드2
https://velog.io/@jupiter-j/프로젝트-수행-webRTC-ec2업로드3
11/11 처음으로 조원 모두가 카메라에 나왔다


3. Service Architecture


EyesTalk에 사용된 기술 스택이다

  • 프론트서버 : react, WebRTC, SocketIO
    Client 의 화면 및 WebRTC 의 Connection 을 맺기 위한 로직 처리
  • 시그널링 서버 : python, Flask, SocketIO
    Peer Connection 을 맺기 위한 socketId 전송
    socket 이벤트를 처리 - Chatting, Connect, disconnect 등
  • 백엔드 서버 : Springboot
    유저 검증 및 방 생성 검증 로직, 데이터베이스에 저장하는 로직을 처리
  • 데이터베이스 : MariaDB, Redis
    방 입장 검증 로직, 목록 검색에 필요한 데이터와 PeerConnection 을 맺기위한 Connection
    여러개의 서버로 분산되어있는경우에는 Chatting 정보를 송수신할 공통된 전송채널이 필요
  • Turn 서버 : Google-Coturn
    peer 끼리의 Connection 을 맺기 위한 서버
    public 한 인터넷 공간에서는 sturn 서버만으로 충분하지만 ,일부 사설 네트워크나 보안이 심한 네트워크 망에서 접속을 하는 경우 직접적인 peer connection 은 불가능하여 connection 을 중개해줄 서버가 필요한데, 이러한 역할을 하는것이 turn 서버이다

서비스 흐름

검증 요청을 하기 위해 SpringBoot를 사용했다. 검증요청이 성공하면 클라이언트의 브라우저에서 socket-join event가 발생한다
이때 시그널링 서버는 소켓 ID와 기타 정보들을 Spring API에 전송해 유저에 대한 데이터들을 데이터 베이스에 전송한다
이후 소켓 ID와 stun 서버를 사용하여 서로의 ip, port등 Peer to Peer 커넥션에 필요한 정보를 얻고 커넥션이 맺어진다.
NAT의 보안레벨에 따라 성공하지 못할 때가 있는데, 이때는 구글의 Coturn을 사용한 우리 서버를 사용하여 통신이 가능하도록 만들어준다

Turn Server의 역할

일반적인 sturn 서버만 있어도 Peer 끼리 Connection 을 맺을 수 있지만, 일부 특수한 상황에 의해 sturn 서버만으로 연결을 할 수 없는 경우가 있다. 이러한 경우 connection 을 중개해줄 서버가 필요한데, 이러한 역할을 하는것이 Turn 서버다. Turn 서버는 구글에서 제공하는 오픈소스인 coturn 서버를 사용하여 사설 turn 서버를 구축했다

Redis가 사용된 이유

EyesTalk 서비스는 화상통화와 채팅서비스가 있다. 여기서 채팅서버는 클라이언트가 채팅방 안에 속해있는 다른 참여자들에게 메시지를 전달하기 위한 공통 통신 채널이 필요하다. 이를 구축하기위해 Redis가 사용되었다.


3-1. Signaling Server

Signarling Server 는 python, Flask, Socket.Io, Redis를 사용했다

  • 초기 WebRTC 커넥션 하기 위한 정보인 socketId 를 반환한다
  • 같은방에 있는 유저들끼리 채팅을 하기 위한 socket event 로직을 처리한다
  • 채팅에 대한 메타데이터를 ElasticSearch 에 저장하기위한 로직을 처리한다

Socket-Event 명세서

event이름설명
create-room유저 방 생성유저가 방을 생성하면 client로 부터 이벤트를 수신, 방 정보를Spring API 서버로 전송, 방 생성 메타데이터 elasticsearch 로 저장
join-room유저 방 입장유저가 방을 입장할때 유저에 대한 정보를 Client로부터 수신, 이후 유저정보를 SpringAPI 서버로 전송 및 response로 받은 user_list에 대한 로직 처리 및 client로 전송, 유저 입장 메타데이터를 Elasticsearch에 저장
disconnect유저 퇴장유저가 방을 나갈때 SpringAPI 에 유저 정보 삭제 요청, 유저 퇴장 메타데이터를 Elasticsearch 로 저장
dataconnection 데이터 송수신WebRTC connection 을 맺기위한 peer 데이터 송수신
chatting채팅 송수신Client로 부터 받은 채팅데이터를 ElasticSearch에 저장

3-2. Front Server

Front Server는 react, WebRTC, SocketIO를 사용했다

  • https://eyestalk.site로 접근 시 MainPage 렌더링한다
    socket.connect() 메서드를 통해 connect가 시작된다
  • 사용자 정보를 담고 세션에 저장한다. 새로운 멤버가 접속 할 때마다 rooms_sid[자신의 소켓 아이디]의 값을 room_id, names_sid[자신의 소켓 아이디]의 값을 유저 이름으로 할당한다.
  • user-list에는 방의 모든 인원에 대한 소켓 아이디가 담아져있다.
  • 소켓 연결이 된 이후 start_webrtc()를 시작한다. 연결된 적이 없다면 인자로 받아온 peer_id를 이용하여_peer_list[peer_id]에 RTCPeerConnection(PC_CONFIG)를 할당한다. 이 때 PC_CONFIG는 google ice 서버의 주소를 사용한다

컴포넌트 구조


3-3. Backend Server

Backend Server는 SpringBoot를 사용했다

  • Signarling Server가 커넥션을 맺기 전에 유저 검증 및 방 생성 검증, 데이터베이스에 저장을 하기 위해 사용된다
  • Swagger을 통하여 프론트와 실시간 API 확인가능하다

API 명세서

기능MethodURIInput VlaueOutput Value
방 생성GET/roomuserNickname, roomName, roomPassword,roomCapacityuserNickname, roomId, roomName, roomPassword,roomCapacity, roomEnterUser, roomCreateAt
방 검색POST/room/{roomName}roomId, roomName, roomPassword, roomCapacity,roomEnterUser, roomCreateAt
방 입장 + 유저 생성PATCH/room/enteruserNickname, roomName, roomPassword,roomCapacity, socketIdsocketId:userNickname
사용자 나가기 + 없으면 방 종료POST/room/exit?socketId={socketId}
사용자 방생성 검증POST/room/valid/createroomNameTrue/False
사용자 입장 검증POST/room/valid/enteruserNickname, roomId,roomPasswordpassword_error, capacity_error, nickname_error
전체 방 조회GET/roomroomId, roomName, roomPassword, roomCapacity,roomEnterUser, roomCreateAt

기능 명세서

방 생성사용자는 방 제목(roomName), 수용인원(roomCapactiy)을 입력하여 회의실를 생성할 수 있다. 이때 사용자의 데이터도 작성해야 한다 닉네임(userNickName)
room 테이블에 저장되는 데이터 {roomId, roomName(100), roomCapacity, roomPassword(100), roomCreateAt, roomEnterUser}user 테이블에 저장되는 데이터 {userId, userNickName, userCreateAt, roomEntity}
1. 동일한 방 이름이 있는지 체크한다. 중복이 있을경우, DUPLICATE_RESOURCE 에러코드 반환
2. 동일한 닉네임으로 생성된 룸이 있는지 체크한다, 중복이 있을 경우 INVALID_NICKNAME 에러코드 반환
3. 위의 모든 조건이 충족되면 유저정보를 저장한다이때 방 정보에서 현재 방에 입장한 인원(roomEnterUser)값을 1 증가시킨다변경된 방 정보를 저장후 저장된 데이터들을 반환한다
방 검색사용자는 방 이름(roomName)으로 방을 검색할 수 있다.
1. 방 이름이 없는 경우 ROOM_IS_EMPTY에러코드 반환
2. 방 이름을 찾으면 데이터 값을 반환한다
방 입장사용자는 닉네임(userNickName), 비밀번호(userPassword)를 입력하여 방에 입장할 수 있다. 이때 PatchVariable로 roomId값이 필요하다
1. 방 아이디가 있는지 체크한다. 없을경우, ROOM_IS_EMPTY 에러코드 반환
2. 방 비밀번호가 맞는지 체크한다. 맞지 않을경우, INVALID_PASSWORD 에러코드 반환
3. 동일한 닉네임으로 생성된 룸이 있는지 체크한다, 중복이 있을 경우 INVALID_NICKNAME 에러코드 반환
4. 수용인원과 현재 입장한 인원수를 비교한다. 수용인원이 찼을경우 FULL_CAPACITY 에러코드 반환
5. 위의 모든 조건이 만족되면 유저를 생성하고 저장한 유저의 값과 방의 데이터를 반환한다
사용자 나가기 & 방 삭제현재 방에 입장한 인원(roomEnterUser)이 1일때 사용자 나가기 요청이 오면 방은 사용자와 함께 자동으로 삭제된다 입장한 인원이 2명이상 남아 있을 때 사용자만 나가게된다
1. 방 아이디(roomId)가 없을경우 ROOM_IS_EMPTY 에러코드 반환
2. 유저 아이디 (userId)가 없을경우 MEMBER_NOT_FOUND 에러코드 반환
3. 현재 방에 입장한 인원(roomEnterUser)이 1보다 클때 방의 입장한 인원을 1명 줄이고 update된 방 정보를 저장 후 유저의 값을 삭제한다 입장인원이 1보다 작을경우, 유저와 방을 모두 삭제한다
방 생성 검증사용자가 확인버튼을 누르면 방을 생성할 수 있는지 검증한다.
1. 방이름이 중복되는 경우에는 false 를 반환
2. 방이름이 중복되지 않는다면 True 를 반환
방 입장 검증사용자가 확인버튼을 누르면 방을 입장할 수 있는지 검증한다.
1. 같은방에 이미 같은 닉네임의 사용자가 있다면 nickname_error 를 반환한다.
2. 방의 정원이 이미 가득차있는 상태라면 capacity_error 를 반환한다.
3. 방의 비밀번호를 틀렸다면 password_error 를 반환한다.
4. 1~3 의 조건을 통과했다면 200 반환
전체 방 조회전체방 리스트를 조회한다.

ERD

image


Swagger

image

자세한 내용 확인


4. AWS Elastic Kubernetes Service-EKS 환경에서 서비스 업로드


WebRTC는 HTTPS환경에서 카메라가 작동되기 때문에 Route53과 ACM을 사용하여 도메인을 인증받았다

자세한 내용 확인


4-1. 네트워크 환경 및 클러스터 구축

VPC네트워크는 Terraform을, 클러스터 구축은 Cloudformation을 사용하여 구축했다.

Terraform

서울 리전 내에 가용영역을 ap-northeast-2a, ap-northeast-2b, ap-northeast-2c, ap-northeast-2d로 구성했다. VPC CIDR 대역을 10.2.0.0/16으로 구성하였으며 subnet은 public subnet4개(10.2.1.0/24, 10.2.2.0/24, 10.2.3.0/24, 10.2.4.0/24), private subnet4개(10.2.16.0/22, 10.2.20.0/22, 10.2.24.0/22, 10.2.28.0/22)로 구성되어있다

CloudFormation

1.23버전의 eks 사용, m5.large 타입의 인스턴스를 사용하여 워커노드를 4개 사용했다. (최소사이즈:4, 최대 사이즈:10, 각각의 volume size: 50)
개발자의 개발 접속 환경을 만들어 주기 위하여 northeast-2a-public-subnet에 baston host를 생성했다. 나머지 조원은 Cloud9을 사용해 접속하여 작업했다.

인스턴스 타입

  • m5.large는 ElasticSearch가 올라가면서 파드 용량 부족으로 사용하게 되었다
    (노드당 평균 18개를 사용함)
  • c6g.medium은 Signarling Server의 네트워크 대역폭 부족으로 사용하게 되었다

네임스페이스(NameSpace)를 활용하여 Pod관리

  • ArgoCD, Prometheus 등 특정 파드를 묶어 관리하기 위해 사용했다
  • ArgoCd Namespace

자세한 내용 확인


4-2. 모니터링 구축

EKS 성능 모니터링(CloudWatch)과 애플리케이션의 로그 모니터링(Kibana)을 나눠서 구축했다

성능 모니터링

  • EKS성능을 확인 할 수 있는 모니터링을 CloudWatch에 구축했다.
  • helm으로 Promethus, Dashboard를 설치하였으나 CloudWatch가 이를 대신하고 있기 때문에 발표에서는 제외시키기로함.
  • Distro를 설치하여 CloudWatch에서 Container Insight로 배포된 애플리케이션을 모니터링이 가능하다
  • 비용 절감을 위해서 CPU사용 일정 수준이 넘어가면 Amazon Simple Notification Service(SNS)를 사용하여 메일이 오도록 했다

Container Insight

Cloud Watch 대시보드

CloudWatch 경보 알람 메일 확인


로그 모니터링

  • 원하는 로그가 발생되고 있는 python 코드에 수집하고 싶은 로그를 저장한 후 인덱스를 만들어서 elasticSearch에 저장되도록 했다
  • 수집한 로그를 시각화하기 위해 elasticSearch와 kibana를 연동해 로그 대시보드를 커스텀하여 모니터링했다
  • ElasticSearch replicas:1, Fluentd replicas:1, Kibana: Daemonset 을 사용하여 구축
  • Kibana 대시보드를 만들어 24 시간 내 생성된 방 사용량, 방 생성 수, 서비스 접속인원, room별 채팅 개수, room 별 채팅 내역을 볼 수 있도록 만들었다

수집한 로그

로그설명
des수집한 로그에 대한 설명 (create room, New member joined, user-disconnect,chatting)
user_nickname사용자가 방에 입장할 때 설정한 닉네임
chatting message사용자가 보낸 채팅 메시지 (RSA 암호화 처리됨)
room_id방 생성 시 설정된 방 이름
@timestamp로그가 발생한 시간

Kibana 대시보드 구성

대시보드설명
24H 방 생성 사용량24시간동안 어느 시간대에 방이 많이 생성되는 지를 확인할 수 있다.
Today 방 생성 수하루동안 방이 얼마나 생성되는 지를 확인할 수 있다.
room 별 채팅 개수방에서 채팅의 수가 어느정도 되는지 확인할 수 있다.
room 별 채팅 내역RSA 암호화 됨
Today 서비스 접속 인원하루동안 해당 서비스에 몇명의 사람이 접속하는지 확인할 수 있다.

자세한 내용 확인


4-3. CI/CD 파이프라인

Eyestalk 프로젝트는 서버 환경이 복잡한 만큼 에러를 해결하기 위하여 실시간으로 개발 코드가 수정이 되었다. EKS환경에서 빠른 테스트를 하기 위해 CI/CD 파이프라인을 구축했다
GithubAction, Kustomize, ArgoCD, ECR을 사용했다

  • ECR에는 Front, Backend, Signaling 개발 코드의 이미지 레파지토리가 있다
  • 깃허브에서 Push를 할때마다 자동으로 ECR에 업로드 되며 해당 이미지 태그를 Kustomize가 연동하여 ArgoCD에 반영시킨다
  • ArgoCD는 SYNC 버튼을 누르지 않아도 코드가 자동으로 반영된다
    2022-12-08 16 00 19

자세한 내용 확인


4-4. 서비스 연결


EyesTalk에는 기존의 Application 구조에서 볼 수 없었던 Redis 와 별도의 ALB 가 추가되었다
Sig Deployment 와 Front Deployment 가 별도의 ALB 를 가지고 있다. 별도의 ALB 를 둔 이유는 Sig Deployment 에서 주로 사용중인 socketIO 를 위한 Sticky Session 옵션을 사용하기 위해서다

Sticky Session을 사용한 이유

우리가 사용한 socketIO 라이브러리는 socket 끼리 제대로 커넥션이 맺혀져 있는지 10초에 한번씩 소켓서버에 pooling 을 보내게된다. 여기서 문제는 실제 커넥션이 맺혀져 있는 서버가 아닌 다른 서버로 Connection 확인작업을 하게되면서 400에러가 떴다

서로 다른 서버가 pooling 요청을 받기때문에 해당 요청이 비정상적인 요청(HTTP 400), 즉 커넥션이 끊겨있다고 판단하고 새로운 커넥션을 맺기를 시도한다. 이러한 과정이 지속적으로 반복되기 때문에 socket 의 connection 이 제대로 맺혀있는지 판단할수가 없다. 따라서 socketIO 의 공식문서에서는 분산서버에서 이러한 문제점을 해결하기 위한 방법으로 sticky session과 기존 pooling 요청을 wss로 변경하라는 옵션이 있다
코드의 수정을 최소한으로 하고 해당 문제를 해결하기 위해서 StickySession을 적용시키는 방식으로 선택했다

alb.ingress.kubernetes.io/target-group-atrtributes: stickiness.enabled=true,stickiness.lb_cookie.duration_seconds=3600
alb.ingress.kubernetes.io/target-type: ip # using IP routing policy of ALB

5. EyesTalk 서비스

EyesTalk의 변천사...


방 만들기

  • 방 생성자는 방이름, 수용인원, 닉네임, 비밀번호를 입력하고 방 생성이 가능하다
  • 동일한 방의 이름으로 생성 될 수 없다
  • 수용인원은 4명 이하까지 설정 가능하다
  • 비디오와 마이크를 ON/OFF 설정하여 접속 가능하다

메인기능

  • 카메라를 ON/OFF 할 수 있다
  • 사운드를 ON/OFF 할 수 있다
  • 화면공유가 가능하다
  • 화면을 선택하면 해당 유저의 화면을 크게 볼 수 있다

채팅

  • 방에 접속한 유저가 모두 볼 수 있도록 채팅을 보낼 수 있다
  • 전체 채팅은 파란색 채팅으로 보인다

DM (Direct Message) 보내기

  • 방에 접속한 유저를 대상으로 특정 유저를 지정하여 DM을 보낼 수 있다
  • DM의 색깔은 회색이다
  • 시스템 알림의 경우 붉은색이다

방 입장하기

  • 방 제목을 선택하여 닉네임과 비밀번호를 입력하고 방 접속이 가능하다
  • 방에 동일한 닉네임이 있을경우 다른 닉네임으로 접속이 가능하다

방 나가기

  • 머무르기 버튼을 눌러 대화를 지속할 수 있다
  • 퇴장하기 버튼을 눌러 나갈 수 있다

방 검색

  • 생성된 방 목록중 방 제목을 입력하여 검색이 가능하다
  • 전체보기를 누르면 최신 생성 순으로 정렬된다




6. 프로젝트 회고

일정대로라면 EyesTalk 서비스는 k8s 환경에서부터 EKS환경까지 서비스를 구축하는 것이었다. 하지만 커넥션 문제로 인해 k8s환경은 구축하였으나 서비스 연결부분에 문제가 생겨 EKS환경으로 바로 넘어간게 아쉽다.
또한 WebRTC에 대한 문제를 빨리 파악했다면 일정관리를 좀더 여유롭게 잡을 수 있었을 텐데 WebRTC연결방식과 이론적인 이해로 시간을 상당부분 할애하게 되었다.
연쇄적인 문제로 뒤로갈수록 시간이 부족했기 때문에 전체적인 EKS성능을 테스트하고 고도화 시키지 못한게 아쉽다.
그럼에도 불구하고 팀원 모두가 처음인 기술을 맡아도 열심히 노력했고, 팀워크가 좋았기 때문에 이렇게 완성도 있는 화상통화 서비스를 만들 수 있었다고 생각한다


EyesTalk 우리팀 모두 한달간 너무 고생했다 별다섯개 꾹꾹 ⭐⭐⭐⭐⭐






EyesTalk 프로젝트 주소 https://github.com/muji-StudyRoom

profile
개발기록

0개의 댓글