[프로젝트 수행] webRTC 로컬 테스트 & ELK (3)

Nam_JU·2022년 11월 16일
1

WebRTC-Project

목록 보기
5/18
post-thumbnail


SFU 방식의 mediasoup을 사용하여 WebRTC 플젝을 로컬 및 AWS에서 띄우는데까지 성공했다.

당장 일주일만에 K8S환경에서 실행시키고 그 다음주에는 EKS로 이관해야함... 기존에 있던 공부방 서비스도 없어졌다 ㅎㅎ
화상통화와 화상채팅 구현만 하는데도 시간이 없음.
일주일만에 개발 끝내는게 말이됩니까


비교적 이해하기 쉬운 코드인 Python-Flask기반의 Signaling WebRTC 소스를 가지고 변형시키는 방향으로 결정되었다

프로젝트를 하면서 논의되었던 점

  • WebRTC의 주요 기능은 서버를 거치지 않고 시그널링을 하여 서버 부담을 줄이고 다대다 통신이 가능하도록 하는것이 모토인데 반대로 SFU방식을 선택하는 이유에 대해 묻고싶다 라는 멘토님의 질문을 받음
  • Signaling 기반의 WebRTC는 서버 부하가 적다. 나중에 프로젝트 발표시에 기껏 만들어놓은 K8s 환경에서 서버 부하에 대한 테스트나 퍼포먼스적으로 보여줄 부분이 없었고, 당시 AWS, 로컬까지 성공한 프로젝트가 mediasoup이었기 때문에 선택했었다 (그러나 node 기반의 플젝이라 팀원 모두가 해당 코드를 커스텀하는데 어려움을 겪고 있었음)
  • 비교적 쉬운 언어로 만들어진 프로젝트를 기반으로 커스텀하는게 낮지 않겠느냐는 조언을 얻고 해당플젝을 시작함


Python-Flask 로컬 테스트

사용한 소스: https://github.com/YashMakan/flask_webrtc_youtube

시행착오1

  1. 깃 클론후 요구사항에 있던 requirements를 모두 다운받았으나 에러

  2. python3 [server.py](http://server.py) 작동되지 않음

ImportError: cannot import name 'run_with_reloader' from 'werkzeug.serving'


from ____ import run_with_reloader에 뭘 넣느냐에 따라 에러문이 달라짐
유튜브 검색했을때 인도아저씨는 python 종속성 문제라고 하는데 구글 검색을 해도 별다를 해결방안이 딱히 보이지 않음. 버전문제 (전부 적용해봤으나 차이없음)인지 flask 의 socket.io 문제인지는 파악 더 필요

raceback (most recent call last):
  File "server.py", line 2, in <module>
    from flask_socketio import SocketIO, emit, join_room
  File "/Users/gimnamju/Library/Python/3.8/lib/python/site-packages/flask_socketio/__init__.py", line 24, in <module>
    **from ._reloader import run_with_reloader**
ModuleNotFoundError: No module named 'flask_socketio._reloader'

----

Traceback (most recent call last):
  File "server.py", line 2, in <module>
    from flask_socketio import SocketIO, emit, join_room
  File "/Users/gimnamju/Library/Python/3.8/lib/python/site-packages/flask_socketio/__init__.py", line 24, in <module>
    **from werkzeug.serving import run_with_reloader**
ImportError: cannot import name 'run_with_reloader' from 'werkzeug.serving' (/Users/gimnamju/Library/Python/3.8/lib/python/site-packages/werkzeug/serving.py)


해결 : 파이썬 install version 문제

pip install --upgrade Flask-SocketIO==5.1.2
pip install --upgrade python-engineio==4.3.2
pip install --upgrade python-socketio==5.6.0

  • 맥유저는 server.py 코드 변경
    if any(platform.mac_ver()): socketio.run(app, debug=True)

토요일 줌으로 회의하면서 시행착오를 겪음... (팀원들 첵오)

  • server.py
from flask import Flask, render_template, request, session
from flask_socketio import SocketIO, emit, join_room
import platform

app = Flask(__name__)
app.config['SECRET_KEY'] = "wubba lubba dub dub"

socketio = SocketIO(app)

#사용자가 저장될 변수 
users_in_room = {}

#방이 저장될 변수
rooms_sid = {}

#사람의 이름 저장될 변수 
names_sid = {}


@app.route("/join", methods=["GET"])
def join():
    display_name = request.args.get('display_name')  # 영상통화를 사용하는 사용자
    mute_audio = request.args.get('mute_audio') # 1 or 0  오디오 음소거할지 안할지
    mute_video = request.args.get('mute_video') # 1 or 0  영상 음소거 할지 안할지
    room_id = request.args.get('room_id')
    session[room_id] = {"name": display_name,  # 세션은 룸 아이디를 키 값으로 가짐
                        "mute_audio": mute_audio, "mute_video": mute_video}
    return render_template("join.html", room_id=room_id, display_name=session[room_id]["name"], mute_audio=session[room_id]["mute_audio"], mute_video=session[room_id]["mute_video"])
# join.html 렌더링

@socketio.on("connect")
def on_connect():
    sid = request.sid
    print("New socket connected ", sid)


@socketio.on("join-room")
def on_join_room(data):
    sid = request.sid
    room_id = data["room_id"]
    display_name = session[room_id]["name"]

    # register sid to the room
    join_room(room_id)
    rooms_sid[sid] = room_id
    names_sid[sid] = display_name

    # broadcast to others in the room
    print("[{}] New member joined: {}<{}>".format(room_id, display_name, sid))
    emit("user-connect", {"sid": sid, "name": display_name},
         broadcast=True, include_self=False, room=room_id)

    # add to user list maintained on server
    if room_id not in users_in_room:
        users_in_room[room_id] = [sid]
        emit("user-list", {"my_id": sid})  # send own id only
    else:
        usrlist = {u_id: names_sid[u_id]
                   for u_id in users_in_room[room_id]}
        # send list of existing users to the new member
        emit("user-list", {"list": usrlist, "my_id": sid})
        # add new member to user list maintained on server
        users_in_room[room_id].append(sid)

    print("\nusers: ", users_in_room, "\n")


@socketio.on("disconnect")
def on_disconnect():
    sid = request.sid
    room_id = rooms_sid[sid]
    display_name = names_sid[sid]

    print("[{}] Member left: {}<{}>".format(room_id, display_name, sid))
    emit("user-disconnect", {"sid": sid},
         broadcast=True, include_self=False, room=room_id)

    users_in_room[room_id].remove(sid)
    if len(users_in_room[room_id]) == 0:
        users_in_room.pop(room_id)

    rooms_sid.pop(sid)
    names_sid.pop(sid)

    print("\nusers: ", users_in_room, "\n")


@socketio.on("data")
def on_data(data):
    sender_sid = data['sender_id']
    target_sid = data['target_id']
    if sender_sid != request.sid:
        print("[Not supposed to happen!] request.sid and sender_id don't match!!!")

    if data["type"] != "new-ice-candidate":
        print('{} message from {} to {}'.format(
            data["type"], sender_sid, target_sid))
    socketio.emit('data', data, room=target_sid)


if any(platform.win32_ver()):
    socketio.run(app, debug=True)


11/15 회의

화면공유, 카메라 On/Off, 사운드 On/Off 기능 구현 완료 후
1. 채팅 기능 개발
2. 리엑트로 쪼개기
3. 화면공유 버그 수정
4. 서버에 띄워서 로컬 인터넷에서 접근되는지 체크
해결하기로 함

https 적용시키기

  • ssl 인증서 발급
    openssl req -x509 -newkey rsa:4096 -nodes -out cert.pem -keyout key.pem -days 365

  • 코드 추가 server.py

if __name__ == '__main__':
    socketio.run(app,
host="0.0.0.0",
port=5000,
debug=True,
ssl_context=("cert.pem", "key.pem"))

해결해야하는 문제

  • 서버 컴퓨터에서 프로젝트를 띄웠을때 다른 컴퓨터에서 접속을 시도할 수 있도록 만들었음
  • 서버 컴퓨터에서는 멀티접속이 가능하지만 다른 pc가 접속을 했을경우 3명부터는 카메라가 제대로 뜨지 않는다
  • 와이파이를 완전히 동일하게 해줬더니 해결됨
  • 화면공유 기능을 실행할때 화면을 다시 로딩해야 제대로 동작함
    -> 현재 플젝이 바닐라 JS로 되어있기 때문에 리엑트로 바꾸는 작업을 해야한다는 결론이 나옴. 즉 위의 2번이 제대로 되어야 3번이 될것이라는 논의가 나왔다
  • 리엑트로 플젝 이전 / 채팅기능 구현 / ELK 적용시키기 3파트를 나눠서 분담하였다

ELK 적용 시키기

  • 참고한 자료 : https://pbj0812.tistory.com/381
  • 테스트한 환경 : VM으로 우분투 OS에서 테스트를 시도
    Docker + ElasticSearch + Kibana 연동
  • elastic search docker 이미지
    docker pull docker.elastic.co/elasticsearch/elasticsearch:7.10.1
  • elastic search 컨테이너 실행
    docker run -d -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:7.10.1
  • kibana docker 이미지
    docker pull docker.elastic.co/kibana/kibana:7.10.1
  • kibana 컨테이너 실행
    docker run -d --link [elasticsearch 컨테이너 id]:elasticsearch -p 5601:5601 docker.elastic.co/kibana/kibana:7.10.1

코드 수정

kibana에 띄울 자료가 필요하기 때문에 인덱스를 만들어야 한다

  • 최종 수정 코드
from flask import Flask, render_template, request, session
from flask_socketio import SocketIO, emit, join_room
import platform
import ssl
from elasticsearch import Elasticsearch
from elasticsearch import helpers
import datetime

app = Flask(__name__)
app.config['SECRET_KEY'] = "wubba lubba dub dub"

socketio = SocketIO(app)

users_in_room = {}
rooms_sid = {}
names_sid = {}

# elk 
es = Elasticsearch('http://192.168.56.103:9200')
es.info()

def utc_time():  # @timestamp timezone을 utc로 설정하여 kibana로 index 생성시 참조
    return datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'

def make_index(es, index_name):
    if es.indices.exists(index=index_name):
        es.indices.delete(index=index_name)
        es.indices.create(index=index_name)

index_name= 'webrtc_room'

@app.route("/join", methods=["GET"])
def join():
    display_name = request.args.get('display_name')
    mute_audio = request.args.get('mute_audio') # 1 or 0
    mute_video = request.args.get('mute_video') # 1 or 0
    room_id = request.args.get('room_id')
    session[room_id] = {"name": display_name,
                        "mute_audio": mute_audio, "mute_video": mute_video}
    return render_template("join.html", room_id=room_id, display_name=session[room_id]["name"], mute_audio=session[room_id]["mute_audio"], mute_video=session[room_id]["mute_video"])


@socketio.on("connect")
def on_connect():
    sid = request.sid
    print("New socket connected ", sid)


@socketio.on("join-room")
def on_join_room(data):
    sid = request.sid
    room_id = data["room_id"]
    display_name = session[room_id]["name"]

    # register sid to the room
    join_room(room_id)
    rooms_sid[sid] = room_id
    names_sid[sid] = display_name

    # broadcast to others in the room
    print("[{}] New member joined: {}<{}>".format(room_id, display_name, sid))

    # elk
    date = datetime.datetime.now()
    now = date.strftime('%m/%d/%y %H:%M:%S')
    doc_join1= {"des":"New member joined", "room_id":room_id, "sid": sid, "@timestamp": utc_time()}
    es.index(index=index_name, doc_type="log", body=doc_join1)
    
    emit("user-connect", {"sid": sid, "name": display_name},
         broadcast=True, include_self=False, room=room_id)

    # add to user list maintained on server
    if room_id not in users_in_room:
        users_in_room[room_id] = [sid]
        emit("user-list", {"my_id": sid})  # send own id only
    else:
        usrlist = {u_id: names_sid[u_id]
                   for u_id in users_in_room[room_id]}
        # send list of existing users to the new member
        emit("user-list", {"list": usrlist, "my_id": sid})
        # add new member to user list maintained on server
        users_in_room[room_id].append(sid)

    print("\nusers: ", users_in_room, "\n")


@socketio.on("disconnect")
def on_disconnect():
    sid = request.sid
    room_id = rooms_sid[sid]
    display_name = names_sid[sid]

    now = datetime.datetime.now()
    now = now.strftime('%m/%d/%y %H:%M:%S')
    doc_disconnect= {"des":"user-disconnect", "room_id":room_id, "sid": sid, "@timestamp": utc_time()}
    es.index(index=index_name, doc_type="log", body=doc_disconnect)

    print("[{}] Member left: {}<{}>".format(room_id, display_name, sid))
    emit("user-disconnect", {"sid": sid},
         broadcast=True, include_self=False, room=room_id)

    users_in_room[room_id].remove(sid)
    if len(users_in_room[room_id]) == 0:
        users_in_room.pop(room_id)

    rooms_sid.pop(sid)
    names_sid.pop(sid)

    print("\nusers: ", users_in_room, "\n")


@socketio.on("data")
def on_data(data):
    sender_sid = data['sender_id']
    target_sid = data['target_id']
    if sender_sid != request.sid:
        print("[Not supposed to happen!] request.sid and sender_id don't match!!!")

    if data["type"] != "new-ice-candidate":
        print('{} message from {} to {}'.format(
            data["type"], sender_sid, target_sid))
    socketio.emit('data', data, room=target_sid)


if __name__ == '__main__':
    socketio.run(app,
host="0.0.0.0",
port=5000,
debug=True,
ssl_context=("cert.pem", "key.pem"))
make_index(es, index_name)

  • 시간에 따른 화상화면 입장자 수를 넣어야 하는데 도표가 나타나지 않았다

  • Timestamp 설정

def utc_time():  # @timestamp timezone을 utc로 설정하여 kibana로 index 생성시 참조
    return datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'
  • 인덱스 생성
def make_index(es, index_name):
    if es.indices.exists(index=index_name):
        es.indices.delete(index=index_name)
        es.indices.create(index=index_name)
  • 인덱스 생성후 print 로 찍혀있는 값들을 인덱스에 넣어 줘야함
    타입을 로그로 지정하여 로그 생성 및 삽입을 해주었다
doc_join1= {"des":"New member joined", "room_id":room_id, "sid": sid, "@timestamp": utc_time()}
    es.index(index=index_name, doc_type="log", body=doc_join1)

  • 시간에 따라 생성된 방과 그 방에 입장한 입장자 수 & 총 입장자 인원

추가하면 좋을 ELK 데이터

  1. 시간에 따른 방 생성 개수
  2. 방 생성 유지 시간
  3. 하루 전체 입장자수 → 24h
  4. 주, 달 평균 입장자수 → 데이터베이스

각자의 노트북에서 성공, 서버 컴퓨터에서도 성공함

profile
개발기록

0개의 댓글