infra
Platform

모듈 맵

[SW Eng] 컴파일·인터프리터·빌드·런타임 — '빌드한다'는 게 뭔가

0 / 19 완료

펼치기

Sw-engineering · 02 / 19

[SW Eng] 컴파일·인터프리터·빌드·런타임 — '빌드한다'는 게 뭔가

개발자가 '빌드 깨졌다', 'JVM 위에서 돈다', '인터프리터 언어라 느리다'고 할 때의 의미를 PM·인프라 관점에서 정리합니다

🚨INCIDENT ALERT
HIGH

배포 직전, 개발자가 슬랙에 "빌드 깨졌어요"라고 올립니다. 잠시 뒤 "로컬에선 되는데 CI에서만 안 돼요"가 따라옵니다. PM인 당신은 일정이 밀릴지 가늠해야 하고, 인프라 담당인 당신은 "서버에 뭘 더 깔아야 하나"를 판단해야 합니다. '빌드'가 정확히 무엇을 하는 단계인지, '런타임'과 어떻게 다른지 모르면 이 한 줄짜리 알림 앞에서 아무 판단도 할 수 없습니다.

이번 챕터에서 배울 것
  • 1컴파일 언어와 인터프리터 언어의 차이를 "언제 번역하는가"로 설명할 수 있다
  • 2"빌드"가 소스 코드를 배포 가능한 산출물로 바꾸는 과정임을 설명할 수 있다
  • 3빌드 산출물(JAR/WAR/번들/바이너리)과 런타임 버전을 짝으로 확인할 수 있다
  • 4"로컬은 되는데 CI는 깨진다"의 원인을 환경 차이 관점에서 진단할 수 있다

컴파일 vs 인터프리터 — 언제 번역하는가

💡개념

번역 시점의 차이: 미리 vs 실행하면서

"인터프리터 언어라 느리다", "컴파일 에러가 났다" — 이 말을 이해하려면 코드가 언제 기계가 읽는 형태로 번역되는가를 봐야 합니다.

  • 컴파일 언어(Go, C++, Rust, Java*): 실행 전에 전체 코드를 기계어나 바이트코드로 번역(컴파일)합니다. 번역 중 문법·타입 오류를 미리 잡습니다 → "컴파일 에러". 실행은 빠르지만 빌드 단계가 필요합니다.
  • 인터프리터 언어(Python, Ruby, JavaScript): 실행 하면서 한 줄씩 해석합니다. 빌드 없이 바로 돌릴 수 있지만, 일부 오류는 그 줄에 도달해야 드러납니다.

*Java/Kotlin은 '바이트코드로 컴파일 → JVM이 실행 시 해석/JIT'하는 중간형입니다. 그래서 "컴파일도 하고 JVM 런타임도 필요"합니다.

PM 관점: 컴파일 언어는 빌드 단계가 게이트가 되어 배포 전에 일정 부류의 버그를 차단합니다. 인터프리터 언어는 반복이 빠른 대신 런타임 에러 비중이 높아 테스트가 더 중요해집니다.

빌드 — 소스를 '배포 가능한 것'으로

💡개념

빌드: 사람이 쓴 코드를 기계가 실행할 산출물로 변환

"빌드 깨졌다"의 빌드는 소스 코드 → 실행/배포 가능한 산출물(artifact) 변환 과정입니다. 보통 이런 단계를 포함합니다.

TEXT
소스 코드 ─▶ 의존성 설치 ─▶ 컴파일/트랜스파일 ─▶ 번들/패키징 ─▶ 산출물
(.java/.ts)   (npm, maven)    (javac, tsc)        (jar, webpack)   (JAR/WAR/번들/바이너리)

산출물의 형태는 스택마다 다릅니다:

스택빌드 산출물배포 시 필요한 런타임
Java/Springapp.jar 또는 app.warJVM(JRE/JDK) 동일 버전
프론트엔드(React 등)정적 번들(dist/의 JS/CSS/HTML)정적 웹서버(Nginx) 또는 CDN
Go단일 실행 바이너리(런타임 내장 — 별도 설치 불필요)
Python(대개 빌드 없이 소스 배포)Python 인터프리터 + 패키지

인프라 관점의 핵심: "무엇을 빌드해서(JAR?) 무엇 위에서 실행하는가(JVM 17?)"를 한 쌍으로 확인하면 배포 준비물이 정확해집니다.

빌드 산출물과 런타임 짝 확인 — 직접 보기

1빌드 산출물과 요구 런타임 버전 확인하기

배포 대상 서버(또는 컨테이너)의 런타임 버전과, 빌드 산출물이 요구하는 버전이 맞는지 확인합니다. 불일치는 배포 실패의 단골 원인입니다.

로컬 터미널
# 서버에 설치된 자바 런타임 버전
java -version

# 빌드 산출물(JAR)이 어떤 자바 버전으로 컴파일됐는지(메이저 버전)
# 55=Java11, 61=Java17, 65=Java21
javap -verbose -cp app.jar com.example.Main | grep "major version"

# Node 프로젝트라면: 요구 런타임이 package.json engines에 명시되곤 함
node -v && cat package.json | grep -A2 '"engines"'
OUTPUT
openjdk version "11.0.22"          ← 서버는 Java 11
  major version: 61                ← 그런데 산출물은 Java 17로 빌드됨 → 불일치!
java -version
🔍실행 후 확인할 것
  • java -version의 버전과 산출물 major version을 비교한다 — 산출물 버전이 서버보다 높으면 UnsupportedClassVersionError로 실행 실패. 같거나 서버가 높아야 안전
  • major version 매핑: 55=Java11, 61=Java17, 65=Java21. 산출물 61인데 서버 11(major 55)이면 즉시 런타임 업그레이드 또는 재빌드 필요
  • Node는 package.json의 engines.node 범위를 본다 — 서버 node -v가 그 범위 밖이면 설치 단계나 실행에서 경고/실패
  • 버전이 맞는데도 안 되면 빌드 산출물 자체가 최신인지(오래된 JAR 배포) 확인 — 빌드 시각/커밋 해시를 대조한다

상황: 새 버전을 배포했더니 애플리케이션이 시작조차 못 하고 위 에러로 죽습니다. 개발팀은 "로컬·CI에선 됐다"고 합니다.

원인: 빌드는 Java 17로 했는데 배포 서버의 런타임이 Java 11입니다. JVM은 자신보다 높은 버전으로 컴파일된 바이트코드를 실행할 수 없습니다. 로컬·CI엔 17이 깔려 있어 통과했던 것입니다.

진단:

로컬 터미널
java -version                          # 서버 런타임
javap -verbose -cp app.jar Main | grep "major version"   # 산출물 요구 버전

서버 major(55=11)보다 산출물 major(61=17)가 높으면 확정입니다.

해결: 둘 중 하나로 버전을 맞춥니다. (1) 서버/베이스 이미지의 런타임을 17로 올린다, 또는 (2) 빌드 타깃을 11로 낮춰 재빌드한다. 컨테이너라면 베이스 이미지 태그를 eclipse-temurin:17처럼 명시해 빌드·런타임을 한곳에서 고정하는 것이 재발 방지책입니다.

💼
실무 맥락
현업 패턴

인프라 엔지니어로서 컨테이너 베이스 이미지를 고를 때, 이 모듈의 지식이 곧바로 쓰입니다. 개발팀이 "Spring Boot, Java 17"이라고 하면 베이스 이미지를 eclipse-temurin:17-jre로 잡고, 산출물 app.jar를 복사해 java -jar app.jar로 실행하도록 Dockerfile을 구성합니다. 프론트엔드라면 "빌드 산출물은 정적 파일"이므로 Node 런타임이 아니라 Nginx로 서빙합니다 — 이 구분을 못 하면 정적 사이트에 불필요한 Node 서버를 띄우는 낭비가 생깁니다.

다음 모듈에서는 이렇게 빌드·실행되는 코드가 프론트엔드와 백엔드로 어떻게 나뉘고, 화면이 어디서 그려지는지(렌더링)를 다룹니다.

지식 확인

퀴즈 — 4문제

Q1

컴파일 언어와 인터프리터 언어의 핵심 차이를 가장 잘 설명한 것은?

Q2

'빌드(build)'가 의미하는 것으로 가장 적절한 것은?

Q3

개발자가 '이건 JVM 위에서 돈다'고 했다. 인프라 담당이 준비해야 할 것은?

Q4

'로컬에선 되는데 빌드 서버(CI)에서만 깨진다'의 가장 흔한 원인은?

0 / 4 답변

이것도 배워보세요