한 대의 서버에 8개 서비스를 PM2랑 Apache 리버스 프록시로 돌리고 있다
서버 한 대에서 서비스 여러 개를 PM2로 띄우고 Apache 리버스 프록시로 도메인마다 포트를 나눴다. VirtualHost 설정, 웹소켓 프록시, 와일드카드 인증서까지 실제 설정으로 정리했다.
#인프라 #Apache #PM2 #리버스프록시
pm2 list 한 번 치면 블로그, 코인, 커플, 게임, 모니터링, 포트폴리오, 타로, 여행 플래너까지 여덟 줄이 뜬다. 도메인은 다 다른데, 실제로는 전부 컴퓨터 한 대에서 도는 거다. 처음엔 이게 되는 건가 싶었는데, 막상 해보니 구조만 잡으면 별거 아니었다. 이 글에선 서비스 하나하나를 어떻게 띄우고 어떻게 도메인으로 묶었는지 정리해본다. 각 서비스는 자기 포트에서 돈다 일단 Node 서버마다 포트를 하나씩 나눠줬다. 블로그 5000, 게임 5005, 코인 5006, 커플 5010, 여행 5011, 포트폴리오 5015, 모니터링 5020, 타로 5025, 이런 식으로 안 겹치게. 규칙이 있는 건 아니고 5000번대에서 만든 순서대로 붙였다. 근데 이걸 그냥 node app.js로 띄우면 터미널 닫는 순간 죽어버린다. 그래서 PM2 로 관리한다. 백그라운드로 돌려주고, 죽으면 자동 재시작하고, 서버 재부팅돼도 알아서 다시 살린다. # 서비스 등록 pm2 start app.js --name blog-server # 전체 상태 한눈에 보기 pm2 list # 코드 바꾸고 재시작 pm2 restart blog-server # 재부팅해도 자동으로 살아나게 pm2 save pm2 startup pm2 list를 치면 서비스가 각각 online인지, 메모리는 얼마나 먹는지, 몇 번 재시작됐는지가 표로 나온다. 이 글을 쓰고 한참 지난 지금 찍은 화면인데, 이거 하나로 서버 전체 상태가 파악돼서 편하다. 표에서 재시작 횟수 열을 보면 game-server가 113회, travel-server가 99회다. 이 숫자를 한동안 안 보고 지냈다가 나중에 꽤 데였는데, 그 얘기는 PM2 크래시 루프 글에 따로 적어뒀다. 그럼 도메인 연결은 누가 하나 진짜 문제는 여기서부터다. 사용자는 coin.jaeyonging.com으로 들어오지 5006번 포트로 들어오지 않는다. 80과 443으로 들어온 요청을 알맞은 내부 포트로 넘겨줄 누군가가 필요한데, 그 역할을 Apache 리버스 프록시 가 한다. 도메인마다 VirtualHost를 하나씩 만들어서, 그 도메인으로 온 요청을 해당 포트로 넘기면 된다. 코인 사이트 설정은 대략 이렇게 생겼다. VirtualHost *:443 ServerName coin.jaeyonging.com SSLEngine on SSLCertificateFile /.../wildcard.jaeyonging.com.crt.pem SSLCertificateKeyFile /.../wildcard.jaeyonging.com.key.pem ProxyPreserveHost On ProxyPass / http://localhost:5006/ ProxyPassReverse / http://localhost:5006/ /VirtualHost 핵심은 ProxyPass / http://localhost:5006/ 이 한 줄이다. coin 도메인으로 온 요청을 전부 내부 5006으로 넘긴다. 도메인이 8개면 이런 VirtualHost를 8개 만드는 거다. ProxyPreserveHost On은 원래 요청의 Host 헤더를 그대로 넘겨주는 옵션인데, 이걸 켜둬야 Node 쪽에서 어느 도메인으로 들어온 요청인지 알 수 있다. 80 포트용 VirtualHost는 따로 두고, 거기서는 https로 301 리다이렉트만 한다. 그래서 도메인 하나에 설정 파일이 두 개씩 생긴다. 웹소켓은 따로 신경 써야 한다 코인 사이트는 실시간 채팅이랑 시세 때문에 Socket.IO를 쓰는데, 웹소켓은 일반 HTTP랑 연결 방식이 달라서 프록시 설정을 따로 해줘야 했다. 이거 안 하면 채팅이 자꾸 끊긴다. RewriteEngine On RewriteCond %{HTTP:Upgrade} websocket [NC] RewriteCond %{HTTP:Connection} upgrade [NC] RewriteRule ^/?(.*) ws://localhost:5006/$1 [P,L] ProxyPass /socket.io/ http://localhost:5006/socket.io/ ProxyPassReverse /socket.io/ http://localhost:5006/socket.io/ Upgrade 헤더가 websocket이면 ws:// 프로토콜로 따로 넘기는 건데, 이걸 추가하고 나서야 실시간 통신이 안정적으로 붙었다. 웹소켓 쓰는 서비스를 프록시 뒤에 둘 거면 이건 거의 필수다. 나중에 모니터링 서버에 SSE를 붙일 때도 비슷하게 프록시 쪽에서 한 번 더 걸렸는데, 그건 flushpackets 옵션 이야기라 SSE 글에 따로 썼다. SSL은 와일드카드 인증서 하나로 도메인이 여러 개라고 인증서를 여러 개 받을 필요는 없었다. *.jaeyonging.com 와일드카드 인증서 하나로 모든 서브도메인을 커버한다. VirtualHost마다 같은 인증서 경로를 가리키게만 하면 된다. 덤으로 HSTS 헤더도 걸어서 브라우저가 무조건 HTTPS로만 붙게 강제했다. 지금 어떤 서비스가 어느 상태인지는 따로 만든 모니터링 페이지에서 본다. 도메인마다 응답 시간과 90일치 가동 기록이 남는다. 지금은 8개가 아니다 이 글을 쓸 때 8개였는데 지금 sites-enabled를 세어보니 설정 파일이 24개다. 서비스마다 80 포트용과 443 포트용을 따로 두니까 도메인 12개에 파일 24개가 됐다. PM2 쪽도 10개가 돌고 있는데, 그중 하나는 이름이 그냥 node라 뭔지 한 번 더 들여다봐야 한다. 늘어나니 헷갈리기 시작했다. 파일 이름만 봐서는 어느 게 어느 포트로 가는지 모르겠어서, 요즘은 뭘 고치기 전에 일단 grep부터 한다. grep -rn "ProxyPass " /etc/apache2/sites-enabled/*-ssl.conf | grep -v Reverse 실제로 돌리면 이렇게 나온다. 이 한 줄이 서버 전체의 지도 역할을 한다. 보면 default-ssl.conf, 그러니까 루트 도메인이 포트폴리오(5015)가 아니라 블로그(5000)를 가리키고 있다. 애드센스 심사 때문에 잠깐 돌려둔 건데, 이걸 잊고 있다가 자동 배포가 멈춘 걸 한참 뒤에 알았다. 그 얘기는 배포 글 끝에 적어뒀다. 그리고 catchall이라는 게 하나 더 있다. 어디에도 안 걸린 도메인 요청을 받으려고 만들었는데, 정작 만들어놓고 뭘 하려고 했는지 지금은 기억이 안 난다. 설정 파일이 스무 개를 넘어가면 이런 게 생긴다. 서비스가 서너 개를 넘어가는 시점에 어느 도메인이 어느 포트로 가는지 한 줄씩 적어둔 표를 만들어두길 추천한다.