개발 학습 실습실

이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.

기초개념 › 구조와 운영

배포

코드를 고치면 실행 중인 서버는 언제 바뀌는가

이 실습의 서버(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절).

이렇게 바꿔 보세요

처음 설정으로 실행하면

  1. 1번 줄: 서버를 켭니다. 컴퓨터가 서버 프로그램을 실행해 프로세스(실행 중인 프로그램)를 하나 만듭니다. 이 프로세스는 켜질 때 디스크의 소스 파일 server.js를 읽어 들입니다. 파일에 적힌 코드는 요청이 오면 "버전 1"을 답하라는 것이고, 프로세스는 읽어 들인 이 코드를 자기 안에 가지고 요청을 기다립니다.
  2. 2번 줄: 사람이 소스 파일 server.js를 열어 "버전 1"을 "버전 2"로 고쳐 저장합니다. 이제 디스크의 파일에는 "버전 2"가 적혀 있습니다. 하지만 실행 중인 서버는 켤 때 이미 파일을 읽어 들였고, 그 뒤에 파일이 바뀐 것을 가져오지 않습니다. 고치고 0.5초를 기다린 뒤 실행 중인 서버에 직접 물어보아도 서버가 가진 코드는 여전히 "버전 1"이었습니다. 파일과 서버 안의 코드는 서로 다른 곳에 있습니다.
  3. 3번 줄: 서버를 끕니다. 서버 프로세스가 끝나고, 그 프로세스가 읽어 들여 가지고 있던 코드도 함께 사라집니다.
  4. 3번 줄: 이어서 서버를 다시 켭니다. 서버 프로그램을 다시 실행해 새 프로세스를 만들고, 새 프로세스는 켜질 때 소스 파일을 처음부터 읽어 들입니다. 지금 파일에는 "버전 2"가 적혀 있으므로 새 프로세스는 "버전 2"를 답하는 코드를 가집니다. 고친 코드가 이제 서버 안으로 들어왔습니다.
  5. 4번 줄: 클라이언트가 서버에 GET /hello 요청을 보냅니다. 브라우저 주소창에 localhost:8080/hello 주소를 입력하면 브라우저가 보내는 요청과 같은 것입니다.
  6. 서버 프로세스가 가진 코드대로 "버전 2"를 답하고, 클라이언트는 "버전 2"를 받습니다. 디스크의 파일에 적힌 글도 "버전 2" 그대로여서 파일과 서버 안의 코드가 같습니다. 고친 새 버전이 요청에 답했습니다.

실행 결과

코드

# 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 커밋과 브랜치

이 실습에 나오는 개념

이 실습에서 보는 것

설정

코드


      
    

그림

실행 비교

이렇게 바꿔 보세요