이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.
기초개념 › 구조와 운영
배포
코드를 고치면 실행 중인 서버는 언제 바뀌는가
이 실습의 서버(Node.js)는 소스 파일(코드를 적어 둔 파일)을 읽어 들여, 그 코드대로 요청에 답하는 프로그램입니다. 서버를 켜면 컴퓨터가 서버 프로그램을 실행해 프로세스(실행 중인 프로그램)를 하나 만들고, 이 프로세스는 켜질 때 소스 파일 server.js를 읽어 들입니다. 그 뒤에는 읽어 들인 코드로 돌 뿐이고, 이 서버는 디스크에 있는 파일을 다시 읽지 않습니다. 그래서 파일을 고쳐 저장해도 이미 켜져 있는 서버는 옛 코드 그대로 답합니다. 새 코드를 쓰게 하려면 서버를 끄고 새 코드로 다시 켜야 하고, 이렇게 새 버전을 실제로 돌아가게 하는 일을 배포라고 합니다. 코드 칸은 자바 코드가 아니라 사람이 하는 일을 순서대로 적은 목록입니다(#으로 시작하는 줄은 번호가 붙은 할 일입니다). 이 실습은 소스 파일을 고치는 2번 줄과 서버를 껐다 켜는 3번 줄을 하나씩 지우거나 순서를 바꿔 가며, 4번 요청에 어느 버전이 답하는지를 봅니다.
실행으로 대조 · Node.js 24.18.0의 서버(자식 프로세스)를 실제로 띄우고 server.js 파일을 고치고 껐다 켜며 http 클라이언트로 요청하는 여섯 가지 조합을 실행해 대조
이 실습에 나오는 개념
- 배포
- 배포는 새 버전의 코드를 실제로 돌아가는 서버에 반영해, 사용자의 요청이 새 버전에 닿게 만드는 일이다. 소스 파일을 고쳐 저장하는 것만으로는 배포가 되지 않는다. 이미 켜져 있는 서버는 켤 때 읽어 들인 코드로 돌기 때문에, 서버가 새 코드로 바뀌어야 한다. 가장 단순한 방법은 서버를 끄고 새 코드로 다시 켜는 것이고, 이 방법은 서버가 한 대일 때 꺼져 있는 동안 요청이 실패한다. Kubernetes 문서는 이 일을 옛 Pod를 새 Pod로 바꾸는 것으로 설명하고, 기존 Pod를 모두 끝낸 뒤 새로 만드는 Recreate와 점진적으로 바꾸는 RollingUpdate를 구분한다.
- 서버 재시작
- 서버 재시작은 실행 중인 서버 프로세스를 끝내고, 같은 서버 프로그램을 다시 실행해 새 프로세스를 만드는 일이다. 끝난 프로세스가 가지고 있던 코드는 함께 사라지고, 새 프로세스는 켜질 때 소스 파일을 처음부터 읽어 들인다. 그래서 재시작은 그 순간 디스크에 있는 코드를 서버 안으로 들여오는 방법이 된다. 끄고 다시 켜기 전까지는 연결을 받는 프로세스가 없으므로, 그 사이에 온 요청은 연결 거부 오류로 실패한다. 소스 파일을 고치지 않고 재시작하면 같은 코드를 다시 읽을 뿐이다.
- 시작할 때 읽은 코드
- 이 실습의 서버(Node.js)는 프로세스가 켜질 때 소스 파일을 읽어 들이고, 그 뒤에는 자기 안에 가진 코드로 돈다. Node.js 문서는 require로 불러온 모듈이 처음 읽어 들인 뒤 캐시되며, 같은 모듈을 여러 번 불러도 모듈의 코드가 여러 번 실행되지 않는다고 설명한다. 다만 이 실습의 server.js는 require로 불러온 모듈이 아니라 node 명령으로 직접 실행한 시작 파일이라서, 이 문서가 server.js의 동작까지 정하는 것은 아니다. 이 PC의 Node.js 24.18.0에서 서버를 켠 뒤 파일을 고치고 0.5초를 기다려도 살아 있는 프로세스가 가진 코드는 그대로였고, 다시 켜지 않은 서버는 옛 코드로 요청에 답했다. 디스크의 파일과 프로세스 안의 코드는 서로 다른 곳에 있다.
- 연결 거부 오류
- 연결 거부 오류는 TCP로 연결을 요청했는데 그 포트를 열고 기다리는 프로그램이 없을 때 보내는 쪽이 받는 오류이다. Node.js에서는 이 경우 connect 이벤트 대신 error 이벤트가 오고 오류 코드가 ECONNREFUSED로 나타난다(net 문서는 연결에 문제가 있으면 connect 대신 error가 온다고 적고, ECONNREFUSED라는 이름은 문서에서 확인하지 못했으며 이 PC의 실행에서 관찰했다). 연결이 맺어지지 않았으므로 글은 하나도 보내지지 않는다. TCP 표준은 연결이 없는 상태로 온 요청에 거부 신호(RST)를 보내도록 정한다(RFC 9293 3.5.2절).
이렇게 바꿔 보세요
- 처음 설정으로 — 파일을 고치고 서버를 끄고 다시 켠 뒤 요청합니다. 새 버전이 답합니다.
- 3번 줄을 지우면 — 파일만 고치고 서버는 그대로입니다. 옛 버전이 계속 답합니다.
- 서버를 끈 사이에 요청하면 — 서버가 꺼져 있는 동안 온 요청은 답을 받지 못합니다.
- 2번 줄을 지우면 — 고친 것이 없으면 다시 켜도 같은 버전이 답합니다.
- 2번 줄과 3번 줄을 둘 다 지우면 — 아무것도 바뀌지 않아 처음 버전이 답합니다.
처음 설정으로 실행하면
- 1번 줄: 서버를 켭니다. 컴퓨터가 서버 프로그램을 실행해 프로세스(실행 중인 프로그램)를 하나 만듭니다. 이 프로세스는 켜질 때 디스크의 소스 파일 server.js를 읽어 들입니다. 파일에 적힌 코드는 요청이 오면 "버전 1"을 답하라는 것이고, 프로세스는 읽어 들인 이 코드를 자기 안에 가지고 요청을 기다립니다.
- 2번 줄: 사람이 소스 파일 server.js를 열어 "버전 1"을 "버전 2"로 고쳐 저장합니다. 이제 디스크의 파일에는 "버전 2"가 적혀 있습니다. 하지만 실행 중인 서버는 켤 때 이미 파일을 읽어 들였고, 그 뒤에 파일이 바뀐 것을 가져오지 않습니다. 고치고 0.5초를 기다린 뒤 실행 중인 서버에 직접 물어보아도 서버가 가진 코드는 여전히 "버전 1"이었습니다. 파일과 서버 안의 코드는 서로 다른 곳에 있습니다.
- 3번 줄: 서버를 끕니다. 서버 프로세스가 끝나고, 그 프로세스가 읽어 들여 가지고 있던 코드도 함께 사라집니다.
- 3번 줄: 이어서 서버를 다시 켭니다. 서버 프로그램을 다시 실행해 새 프로세스를 만들고, 새 프로세스는 켜질 때 소스 파일을 처음부터 읽어 들입니다. 지금 파일에는 "버전 2"가 적혀 있으므로 새 프로세스는 "버전 2"를 답하는 코드를 가집니다. 고친 코드가 이제 서버 안으로 들어왔습니다.
- 4번 줄: 클라이언트가 서버에 GET /hello 요청을 보냅니다. 브라우저 주소창에 localhost:8080/hello 주소를 입력하면 브라우저가 보내는 요청과 같은 것입니다.
- 서버 프로세스가 가진 코드대로 "버전 2"를 답하고, 클라이언트는 "버전 2"를 받습니다. 디스크의 파일에 적힌 글도 "버전 2" 그대로여서 파일과 서버 안의 코드가 같습니다. 고친 새 버전이 요청에 답했습니다.
실행 결과
- 실행 중인 서버는 켤 때 읽어 들인 코드로 돕니다. 소스 파일을 고친 뒤 서버를 끄고 다시 켜면 새 프로세스가 고친 파일을 읽어 "버전 2"가 답합니다. 이렇게 새 버전을 실제로 돌아가게 하는 일이 배포입니다. 코드를 고치기만 하는 것과 다른 곳은 3번 줄(서버를 끄고 다시 켠다)입니다.
- 고치는 일(2번 줄)과 다시 켜는 일(3번 줄)은 서로 다른 일입니다. 둘이 모두 있어야 새 코드가 요청에 답합니다.
코드
# 1. 서버를 켠다 (코드: "버전 1"을 답한다)
# 2. 소스 파일의 "버전 1"을 "버전 2"로 고친다
# 3. 서버를 끄고 다시 켠다
# 4. 요청을 보낸다
이 모형의 전제
서버는 Node.js 24.18.0의 http 서버입니다. 조합마다 server.js 파일을 실제로 쓰고(시작할 때 const answer = '버전 1' 줄을 읽어 들이고, 요청이 오면 그 값을 답하는 짧은 프로그램), node server.js를 자식 프로세스로 실제로 띄웠습니다. 화면의 코드는 실행한 프로그램이 아니라 사람이 하는 일을 적은 목록이고, Probe는 이 목록을 줄 단위로 그대로 실행했습니다. 켠다는 자식 프로세스를 시작하는 것, 고친다는 server.js 파일을 실제로 다시 쓰는 것, 끈다는 그 프로세스를 끝내는 것, 요청은 http 모듈의 GET /hello입니다. 여섯 가지 조합(다시 켜는 줄이 없으면 요청 때를 고를 수 없으므로 여덟 가지 설정 가운데 서로 다른 것)을 모두 실행해, 요청에 온 응답(또는 연결 거부 오류), 그때 살아 있는 프로세스가 가진 값(별도의 요청으로 물음), 그때 디스크 파일에 적힌 값, 껐다 켠 뒤의 프로세스가 처음 프로세스와 다른 프로세스인지, 켠 횟수, 파일을 고치고 0.5초 기다린 뒤에도 처음 프로세스가 가진 값이 그대로인지, 서버를 끈 사이에 요청한 조합에서 다시 켠 뒤 한 번 더 보낸 요청의 답을 확인해 모형과 대조했고, 화면의 코드가 Probe가 실행한 목록과 같은 글인지도 조합마다 확인했습니다. 한 조합 안에서는 껐다 켜도 같은 포트를 썼고, 화면의 localhost:8080은 예시 주소이며 실제로는 빈 포트를 한 번 잡아 썼습니다. 이 실습의 서버는 파일을 읽는 일을 켤 때 한 번만 하는 짧은 프로그램이라, 파일이 바뀌면 스스로 다시 읽어 들이는 도구(개발용 자동 재시작 도구 등)나 요청마다 파일을 읽는 프로그램은 다루지 않았습니다. 문서로 확인한 것은 Node.js 문서 Modules의 Caching 절(모듈은 처음 읽어 들인 뒤 캐시되고, require 캐시를 바꾸지 않으면 같은 모듈을 여러 번 불러도 모듈의 코드가 여러 번 실행되지 않는다는 서술)과 Kubernetes 문서 Deployments(Deployment가 Pod의 선언적 갱신을 맡는다는 소개, Strategy 절의 Recreate는 기존 Pod를 모두 끝낸 뒤 새 Pod를 만들고 RollingUpdate는 점진적으로 바꾼다는 서술)입니다. 두 문서는 읽기만 했고, 서버가 켤 때 읽어 들인 코드로 돈다는 것은 문서가 아니라 위의 실행으로 확인했습니다. Node.js 문서의 Caching 절은 require로 불러온 모듈에 대한 서술이고 이 실습의 server.js는 node 명령으로 직접 실행한 시작 파일이어서, 문서가 server.js의 동작까지 정한다고는 쓰지 않았습니다. 다루지 않은 것: 서버를 껐다 켜는 틈을 없애는 방법(여러 대를 차례로 바꾸는 롤링, 새 환경을 통째로 만드는 블루-그린 등)은 "세부 동작 실습: 배포 전략"에서, 코드를 고친 뒤 빌드와 테스트를 거쳐 서버에 올리는 자동화는 "세부 동작 실습: CI/CD 파이프라인"에서, 컨테이너로 포장해 쿠버네티스가 서버를 바꾸는 방식은 "세부 동작 실습: 쿠버네티스 자가 치유"에서 다룹니다. 자바 같은 컴파일 언어에서는 소스 파일을 고친 뒤 다시 컴파일해야 한다는 단계가 하나 더 있는데, 이 실습은 컴파일이 없는 Node.js의 소스 파일로만 보였습니다.
앞 실습: 메시지 큐 · 다음 실습: Git 커밋과 브랜치