L19 — 서버 한 대 안을 열어보기: Apache · gunicorn · Django · PostgreSQL (수정중)
L18 이 "요청이 우리 서버까지 오는 길"이었다면, 이 강의는 그 요청이 서버 문 안으로 들어온 다음 어떤 부품들을 거치는지를 연다. 여전히 비개발자·주니어가 따라올 수 있게 쓰되, 한 단계 더 깊이 들어간다.
1. 서버 한 대 = 여러 일꾼이 사는 집
우리가 "서버"라고 부르는 prod 박스(52.79.132.206) 한 대 안에는 역할이 다른 프로그램 여럿이 동시에 살면서 협업한다. 한 명이 다 하는 게 아니다.
| 부품 | 한 줄 역할 | 비유 |
|---|---|---|
| Apache | 바깥 요청을 맨 앞에서 받는 웹서버 | 건물 문지기·안내데스크 |
| gunicorn | Django 코드를 실제로 굴리는 실행기 | 주방의 요리사들 |
| Django | 우리가 짠 앱 로직(URL·화면·규칙) | 레시피(우리 코드) |
| PostgreSQL | 데이터를 영구 보관하는 데이터베이스 | 창고·장부 |
이 강의는 이 넷이 어떻게 한 줄로 연결돼서 페이지 하나를 만들어 내는지를 본다.
2. 한 장의 그림 — 요청이 박스 안에서 도는 길
meetings.popupstudio.ai(popax)로 들어온 요청을 따라가 보자. (실제 우리 설정 그대로다.)
그림: 바깥 요청(:443) → Apache(문지기: TLS 종료·/static 디스크 직접·그 외 proxy) → gunicorn(127.0.0.1:8024, --workers 3, systemd) → Django → PostgreSQL(popax_prod). 정적 파일은 Apache 가 popax_static_cdn 에서 직접 응답.
아래에서 부품별로 본다.
3. Apache — 문지기이자 교통경찰
Apache 는 바깥에서 오는 모든 요청을 맨 앞에서 받는다. 실제 우리 vhost(popax-prod-le-ssl.conf)를 보면 하는 일이 또렷하다.
<VirtualHost *:443>
ServerName meetings.popupstudio.ai
SSLCertificateFile /etc/letsencrypt/live/meetings.popupstudio.ai/fullchain.pem # ① TLS 자물쇠
Alias /static/ /home/ubuntu/popax_static_cdn/static/ # ② 정적은 직접
ProxyPass /static/ ! # (프록시 안 함)
ProxyPass / http://127.0.0.1:8024/ # ③ 나머지는 뒤로
ProxyPassReverse / http://127.0.0.1:8024/
RequestHeader set X-Forwarded-Proto "https"
</VirtualHost>
- ① TLS 종료 —
https자물쇠를 여기서 푼다. 안쪽(gunicorn 까지)은 같은 박스라 평문으로 줘도 안전하다. - ② 정적 파일은 직접 —
/static/·/media/요청은 gunicorn 까지 안 보내고 디스크 파일을 Apache 가 바로 집어 응답한다 (ProxyPass /static/ !의!가 "이건 넘기지 마"라는 뜻). → 7절에서 왜인지 본다. - ③ 나머지는 reverse proxy — 그 외 모든 요청은 뒤에 있는 gunicorn(
127.0.0.1:8024)에게 넘긴다. Apache 는 직접 페이지를 만들지 않는다 — 받아서 알맞은 곳으로 보내는 교통경찰이다.
127.0.0.1:8024의127.0.0.1은 "이 박스 자기 자신"이라는 뜻이다. 즉 gunicorn 은 바깥 인터넷에 직접 열려 있지 않고, 오직 Apache(같은 박스)만 말을 걸 수 있다. 바깥은 항상 Apache 라는 문을 거친다 — 보안상 중요하다.
4. gunicorn — Django 를 실제로 굴리는 일꾼
Apache 가 넘긴 요청을 받아 우리 Django 코드를 진짜로 실행하는 게 gunicorn 이다. 실제 unit(popax-prod.service):
ExecStart=/home/ubuntu/popax/bin/gunicorn popax.wsgi:application \
--bind 127.0.0.1:8024 --workers 3 --timeout 120
Environment="POPAX_PRODUCTION=True"
WorkingDirectory=/home/ubuntu/popax/src
--workers 3— 요리사가 3명이라 요청 3개를 동시에 처리한다. (사람이 몰리면 워커 수를 늘린다.)--bind 127.0.0.1:8024— 위에서 본 그 안쪽 주소. Apache 만 접근.popax.wsgi:application— gunicorn 과 Django 를 잇는 표준 연결 규격이 WSGI 다. gunicorn 은 "웹 요청"을 받아 Django 가 이해하는 "파이썬 함수 호출"로 바꿔준다.POPAX_PRODUCTION=True— 이 스위치가 Django 를 prod 설정(prod DB·prod 도메인)으로 켠다. (M1 L01 settings 분리와 같은 이야기.)
이 gunicorn 을 systemd 가 지킨다 — 죽으면 자동 재시작(Restart=on-failure), 박스 재부팅 시 자동 기동. 그래서 sudo systemctl restart popax-prod.service 가 곧 "새 코드로 일꾼을 다시 띄워라"가 된다. (→ L20 에서 이게 배포의 핵심이 된다.)
5. Django — 우리가 짠 코드가 일하는 곳
gunicorn 이 부르면 그제서야 Django(우리 코드) 가 일한다. 한 요청의 흐름은:
요청 URL → urls.py (어느 view 로?) → view 함수 (로직 실행) → 필요하면 DB 질의 → HTML/JSON 응답
여기가 우리가 실제로 작성·수정하는 부분이다. "기능을 개발한다"는 건 대개 이 Django 코드(view·model·template)를 짜는 일이고, 배포는 이 코드를 prod 의 gunicorn 이 실행하게 만드는 것이다.
6. PostgreSQL — 데이터의 영구 창고 (코드와 분리돼 있다)
Django 가 "이 사용자의 회의록 목록 줘" 같은 걸 하려면 데이터베이스에 묻는다. 우리는 PostgreSQL 을 쓰고, 프로젝트마다 별도 DB 를 둔다 — popax 는 popax_prod(운영) / popax(dev) 로 분리.
가장 중요한 개념 하나:
코드와 데이터는 분리돼 있다. gunicorn 이 실행하는 코드는 배포할 때마다 통째로 갈아끼우지만, PostgreSQL 안의 데이터(사용자·회의록 등) 는 그 자리에 그대로 남는다.
그래서 새 버전을 배포해도 사용자 데이터가 날아가지 않는다. 코드는 "갈아끼우는 부품", 데이터는 "쌓여 있는 창고" — 이 구분이 L20("배포가 뭘 바꾸나")의 핵심 전제가 된다.
⚠️ 단, 코드(모델)가 바뀌면서 DB 의 구조(표·컬럼) 자체를 바꿔야 할 때가 있다 — 그게 마이그레이션이고, 데이터가 든 실서버라 조심스럽다. 자세한 건 L20 에서.
7. 정적 파일은 왜 Apache 가 직접 주나
3절에서 /static/ 은 gunicorn 까지 안 가고 Apache 가 직접 준다고 했다. 이유:
- CSS·JS·이미지는 매번 만들 필요가 없는, 이미 완성된 파일이다. 굳이 gunicorn(요리사)을 불러 Django 를 거칠 이유가 없다 — Apache 가 디스크에서 집어 주면 훨씬 빠르고 가볍다.
- 그 파일들은 어디서 오나? 배포 때
collectstatic명령이 프로젝트 곳곳의 정적 파일을 한 폴더(/home/ubuntu/popax_static_cdn/static/)로 모아 둔다. ApacheAlias가 그 폴더를 가리킨다. - 그래서 교육 사이트(
education.popupstudio.ai)는 아예 gunicorn 이 없다 — 전부 미리 만든 정적 HTML 이라, Apache 만으로 충분하다 (L18 의 "정적이면 Apache 가 바로" 가 이 경우).
오늘 정리 + 다음
- 정리: prod 박스 한 대 안에서 Apache(문지기: TLS 풀고·정적 직접·나머지는 프록시) → gunicorn(일꾼:
--workers 3로 Django 실행,127.0.0.1:8024안쪽 전용, systemd 가 관리) → Django(우리 코드: URL→view→로직) → PostgreSQL(popax_prod, 데이터 영구 보관) 가 한 줄로 협업한다. 정적 파일은collectstatic으로 모아 Apache 가 직접 준다. 코드는 갈아끼우는 부품, 데이터는 남는 창고. - 흔한 함정: ① "gunicorn 이 왜 바깥에서 안 열려?" — 일부러
127.0.0.1(박스 안)에만 묶었다. 바깥은 Apache 문을 거쳐야 한다(보안). ② 정적 파일이 안 바뀐다 —collectstatic을 안 했거나 브라우저 캐시. Apache 가 주는 건 모아 둔 폴더의 파일이지 코드 폴더가 아니다. ③ 워커가 적어 느리다 — 동시 요청이 워커 수(3)를 넘으면 줄을 선다. - 다음 시간: 이 부품 구조를 알았으니, L20 에서 "배포(release)"가 이 부품들(코드·gunicorn·DB·정적파일) 중 무엇을 어떻게 바꾸는지 본다. 그 뒤 L10 이 그걸 안전하게·추적되게 하는 절차다.
- 자습 권장: prod 박스에서
systemctl cat <프로젝트>-prod.service로 실제 gunicorn 실행줄(바인드 주소·워커 수)을 직접 보고,/etc/apache2/sites-available/에서 vhost 의ProxyPass와Alias를 찾아 "이 도메인은 어떤 포트의 gunicorn 으로 가는지" 짚어 보기.