오늘은 구현했던 실시간 채팅기능을 프로덕션 서버에서 올리다 삽질한 걸 남겨보려 한다.
프로덕션 환경에서 로컬환경에서 세팅한 것과 유사하게
설정을 진행하고 브라우저에서 소켓 연결을 시도했다.
그런데 실서버에서 소켓 연결을 하면 자꾸 disconnect이 되었다.
클라이언트가 시도하는 소켓 주소 :
wss://www.{도메인 이름}:8001/ws/hole/<str:hole_id>
→ 여기서 hole_id는 변수
삽질을 하면서 1차로 했던 설정은 아래와 같다.
세팅 파일 : /etc/nginx/sites-enabled/{도메인 이름}
upstream your_channel_daphne {
server localhost:8001;
}
server {
{...}
access_log /var/log/nginx/live_be/access.log;
error_log /var/log/nginx/live_be/error.log;
server_name {도메인 이름} www.{도메인 이름};
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
{...}
location /ws/ {
proxy_pass http://your_channel_daphne;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
}
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [os.environ.get("REDIS_URL")]
},
},
}
REDIS_URL='redis://:{비밀번호}@{redis 있는 서버 public ip}:6379/0'
ASGI_APPLICATION = 'config.routing.application'
import os
import django
# from django.core.asgi import get_asgi_application
from channels.routing import get_default_application
# from channels.asgi import get_channel_layer
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.production')
django.setup()
application = get_default_application()
from django.conf.urls import url
from django.urls import re_path,path
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
# from channels.security.websocket import AllowedHostsOriginValidator, OriginValidator
from chat_messages.consumers import ChatConsumer
websocket_urlpatterns = [
re_path(r"^ws/hole/(?P<room_id>[\w.@+-]+)", ChatConsumer),
]
application = ProtocolTypeRouter({
'websocket': AuthMiddlewareStack(
URLRouter(
websocket_urlpatterns
)
)
})
3.36.64.xx
)위의 ip의 private ip
, 127.0.0.1로 설정daphne.service로 설정해놓음.
3가지 방법 모두 똑같이 안 됨.
혹시나 관련 패키지 버전문제인가 싶어서,
redis가 있는 인스턴스를 ubuntu 20.04로 업그레이드하고 redis 버전 5로 올리고, nginx 버전도 1.19(최신 버전)으로 올리면서 아래의 패키지들을 업데이트했다.(그래도 문제는 계속 발생했다.)
일단 원인 파악을 구체적으로 하기 위해 소켓 통신 튜토리얼(channels 튜토리얼)을 production 서버에서 다시 구현해보면서 어떤 부분이 잘못됐는지 살펴보기로 결정했다.
daphne 서버를 8000번 포트로 띄우고, websocket path를 로컬 ip로 설정 해봄.
wss://127.0.0.1:8000/ws/hole/671
wss로 보내기 때문에 daphne는 ssl 설정을 해서 띄웠다. 인증서와 프라이빗 키는 Let's encrypt로 만든 인증서를 활용.
daphne 호스트 바인딩으로 public, private ip를 시도해봤지만 가장 명확하게 되는건 로컬호스트(127.0.0.1)라고 결론.
하드코딩된 path로 클라이언트와 소켓 커넥션을 맺으면 명확하게 daphne 서버에서 handshaking 요청을 받아줬다.
→ 즉, daphne 자체는 port, path만 잘 맞으면 동작을 하고 있다는 얘기.
$ python3 manage.py shell
>>> import channels.layers
>>> channel_layer = channels.layers.get_channel_layer()
>>> from asgiref.sync import async_to_sync
>>> async_to_sync(channel_layer.send)('test_channel', {'type': 'hello'})
>>> async_to_sync(channel_layer.receive)('test_channel')
{'type': 'hello'}
데모를 통해 위의 4개 가설 중 2,3,4번은 큰 문제가 없을 것 같다는 1차 결론을 내렸다.
그 후 튜토리얼에서 했던 설정을 서비스 서버의 설정으로 커스텀해서 테스트해봤다.
→ 실패했다. daphne 서버는 응답이 없었고, 여전히 socket은 disconnect 되었다.
다만, nginx access log를 확인해보니 해당 소켓 주소로 HTTP GET 요청은 들어오고 있었는데 WSS 요청은 오지 않았다. 지난번에 올린 글 증상과 비슷해보여서 에러 로그를 좀 더 자세하게 찍어보기로 했다.
참고 :
위에서는/etc/nginx/sites-enabled/{도메인 이름}
에서 설정했는데, 아래부터는/etc/nginx/conf.d/{도메인 이름}.conf
에 설정을 똑같이 두고 변경했다.
에러 로그 설정 변경 :
error_log /var/log/nginx/live_be/error.log debug;
그리고 websocket path를 아래와 같이 변경했다.
AS-IS : wss://{도메인 이름}:8000/ws/hole/671
TO-BE : wss://{도메인 이름}/ws/hole/671
nginx 설정도 변경해보았다.
upstream your_channel_daphne {
server 127.0.0.1:8000;
}
server {
{...}
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
{ ...}
location /ws {
proxy_pass http://your_channel_daphne;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
}
설정 이유 : 내가 nginx에서 받던 server_name은 도메인 네임 뿐이었고 포트는 포함하지 않았기 때문에 도메인 이름으로 서버에 요청하면 nginx가 도메인 뒤에 붙은
/ws
를 보고http://127.0.0.1:8000/ws
로 보낼 것이라고 생각했다.
그 후 다시 소켓 요청을 해보니, 아래와 같은 로그가 찍히기 시작했다.
2021/04/02 16:47:49 [info] 27993#27993: *27 client 143.248.232.194 closed keepalive connection
2021/04/02 16:47:50 [error] 27993#27993: *34 upstream prematurely closed connection while reading response header from upstream, client: 143.248.232.194, server: ask2live.me, request: "GET /ws/hole/671/ HTTP/1.1", upstream: "http://127.0.0.1:8000/ws/hole/671/", host: "www.ask2live.me"
location /ws {
proxy_read_timeout 300s;
proxy_connect_timeout 75s;
proxy_pass https://your_channel_daphne;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
참고 :
- proxy_connect_timeout은 proxy된 서버의 커넥션 타임아웃을 설정하는 컨피그이고, 보통 75초를 넘기지 말라고 한다.
- proxy_read_timeout은 proxy된 서버의 리스폰스 읽기 타임아웃을 설정하는 컨피그이다.
위의 nginx 재설정 이후 다시 소켓 연결을 하니까 2번째 connection 요청에 드디어 연결이 되었다!!!
소켓 연결 1번째 요청은 (소켓 요청이 다른 api response가 성공해야지, connect 될 수 있는데 타이밍 상 그 response가 오지 않는다는 이유로 ) 실패했다.
이 문제 가지고 2~3일을 씨름했는데, 차근차근 트러블 슈팅을 하니까 결국 일단락 되었다.
물론 이 설정이 완벽한게 아니기 때문에 앞으로는 그 타이밍을 최적화 하는 방법을 좀 더 고민해봐야할 것 같다.
혹시나 저와 비슷한 문제로 고민하는 분들이 있다면, 아래의 방법으로 접근해보길 추천드립니다.
참고 :
이 글 보고 겨우 에러 고쳤어요ㅠㅠㅠ 검색해도 안 나와서 머리 하얗게 됐었는데.... 진짜 너무 감사드려요 아침마다 계신 쪽으로 절 할게요ㅜㅠㅠㅠ