티스토리 뷰

작성일: 2025년 12월 6일
카테고리: Go, Development Tools, Productivity
키워드: Go, Golang, Air, Live Reload, Hot Reload, 개발 환경

요약

Go 언어는 컴파일 언어 특성상 코드 변경 시 수동으로 빌드하고 재시작해야 하는 불편함이 있습니다. Next.js의 next dev, Vite의 HMR, NestJS의 --watch, FastAPI의 --reload 옵션처럼 프레임워크에서 자동 리로드 기능을 기본 제공하지 않아 개발 속도가 저하됩니다. Air는 이 문제를 해결하는 Go용 Live Reload 도구로, 파일 변경을 감지하여 자동으로 빌드하고 애플리케이션을 재시작합니다.


문제 상황

증상

Go로 웹 서버나 API를 개발할 때, 코드를 수정할 때마다 다음 과정을 반복해야 합니다:

# 1. 실행 중인 서버 종료 (Ctrl+C)
^C

# 2. 다시 빌드
$ go build -o ./bin/app ./cmd/main.go

# 3. 다시 실행
$ ./bin/app

단순한 로그 한 줄 추가에도 이 과정을 반복해야 합니다.

환경 구성

  • 언어: Go 1.21+
  • 프레임워크: Gin, Fiber, Echo 등 웹 프레임워크
  • 개발 환경: 로컬 또는 Docker 컨테이너

다른 프레임워크와의 비교

프레임워크 Watch 명령어 비고
Next.js next dev 기본 내장, HMR 지원
Vite vite dev 기본 내장, HMR 지원
NestJS nest start --watch 기본 내장
FastAPI uvicorn --reload uvicorn 옵션
Django python manage.py runserver 기본 내장
Go (Gin, Fiber 등) 없음 Air 별도 설치 필요

대부분의 현대 웹 프레임워크는 개발 서버에 자동 리로드 기능을 기본 내장하고 있습니다. 그러나 Go 웹 프레임워크(Gin, Fiber, Echo 등)는 이 기능을 제공하지 않습니다. Go는 컴파일 언어라는 특성상 인터프리터 언어처럼 런타임에서 코드를 다시 로드하는 것이 불가능하기 때문입니다. 따라서 파일 변경 감지 → 재빌드 → 프로세스 재시작 방식으로 구현해야 하며, 이를 Air가 담당합니다.


Air 소개

Air란?

Air는 Go 애플리케이션을 위한 Live Reload CLI 도구입니다. 프로젝트 루트에서 air를 실행하면, 소스 코드 변경을 감지하여 자동으로 빌드하고 애플리케이션을 재시작합니다.

주요 기능

  • 파일 변경 감지: 지정된 확장자와 디렉토리의 변경 사항 모니터링
  • 자동 빌드 및 재시작: 변경 감지 시 빌드 명령 실행 후 바이너리 재시작
  • 컬러 로그 출력: 빌드 성공/실패를 시각적으로 구분
  • 유연한 설정: .air.toml 파일을 통한 상세 설정 가능
  • 제외 디렉토리 지원: vendor, node_modules 등 불필요한 디렉토리 제외
  • 신규 디렉토리 자동 감지: Air 실행 후 생성된 디렉토리도 자동으로 감시 대상에 포함

Live Reload vs Hot Reload

구분 Live Reload Hot Reload
동작 방식 전체 애플리케이션 재시작 변경된 부분만 교체
상태 유지 상태 초기화됨 상태 유지됨
Go 지원 Air, Fresh 등 제한적 (플러그인 등 특수 케이스)
적용 범위 모든 Go 애플리케이션 특정 프레임워크/환경

Go의 특성상 진정한 Hot Reload(상태 유지 코드 교체)는 일반적으로 불가능합니다. Air는 Live Reload 방식으로, 파일 변경 시 전체 애플리케이션을 재빌드하고 재시작합니다.


설치 방법

방법 1: go install (권장)

Go 1.18 이상에서 권장하는 방식입니다:

go install github.com/air-verse/air@latest

설치 후 $GOPATH/bin이 PATH에 포함되어 있는지 확인합니다:

# PATH 확인
echo $PATH | grep -o "$(go env GOPATH)/bin"

# 설치 확인
air -v

방법 2: go get -tool (Go 1.24+)

Go 1.24부터 도입된 tool 디렉티브를 사용하는 방식입니다:

go get -tool github.com/air-verse/air@latest

프로젝트별로 버전을 관리할 수 있어 팀 협업에 유리합니다:

# 실행
go tool air -v

방법 3: 설치 스크립트

curl을 이용한 설치:

curl -sSfL https://raw.githubusercontent.com/air-verse/air/master/install.sh | sh -s -- -b $(go env GOPATH)/bin

방법 4: mise 패키지 매니저

mise use -g air

PATH 문제 해결

air: command not found 오류 발생 시:

# ~/.bashrc 또는 ~/.zshrc에 추가
export PATH=$PATH:$(go env GOPATH)/bin

# 또는 alias 설정
alias air='$(go env GOPATH)/bin/air'

# 적용
source ~/.bashrc

기본 사용법

설정 파일 초기화

프로젝트 루트에서 다음 명령으로 기본 설정 파일을 생성합니다:

air init

.air.toml 파일이 생성됩니다.

Air 실행

# 기본 실행 (현재 디렉토리의 .air.toml 사용)
air

# 특정 설정 파일 지정
air -c .air.toml

# 디버그 모드
air -d

설정 파일 없이 실행

간단한 프로젝트에서는 CLI 옵션만으로 실행할 수 있습니다:

air --build.cmd "go build -o ./tmp/main ./cmd/main.go" --build.bin "./tmp/main"

애플리케이션에 인자 전달

# 방법 1: 직접 전달
air server --port 8080

# 방법 2: -- 구분자 사용
air -- -h
air -- --config ./config.yaml

설정 파일 상세

기본 .air.toml 구조

root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"

[build]
  args_bin = []
  bin = "./tmp/main"
  cmd = "go build -o ./tmp/main ."
  delay = 1000
  exclude_dir = ["assets", "tmp", "vendor", "testdata"]
  exclude_file = []
  exclude_regex = ["_test.go"]
  exclude_unchanged = false
  follow_symlink = false
  full_bin = ""
  include_dir = []
  include_ext = ["go", "tpl", "tmpl", "html"]
  include_file = []
  kill_delay = "0s"
  log = "build-errors.log"
  poll = false
  poll_interval = 0
  post_cmd = []
  pre_cmd = []
  rerun = false
  rerun_delay = 500
  send_interrupt = false
  stop_on_error = false

[color]
  app = ""
  build = "yellow"
  main = "magenta"
  runner = "green"
  watcher = "cyan"

[log]
  main_only = false
  time = false

[misc]
  clean_on_exit = false

[screen]
  clear_on_rebuild = false
  keep_scroll = true

주요 설정 항목 설명

[build] 섹션

항목 설명 기본값
cmd 빌드 명령어 go build -o ./tmp/main .
bin 실행할 바이너리 경로 ./tmp/main
include_ext 감시할 파일 확장자 ["go", "tpl", "tmpl", "html"]
include_dir 추가로 감시할 디렉토리 []
exclude_dir 제외할 디렉토리 ["assets", "tmp", "vendor", "testdata"]
exclude_regex 제외할 파일 정규식 ["_test.go"]
delay 변경 감지 후 빌드까지 대기 시간(ms) 1000
poll 폴링 방식 사용 (Windows/Docker용) false
poll_interval 폴링 간격(ms) 0
pre_cmd 빌드 전 실행할 명령어 []
post_cmd 빌드 후 실행할 명령어 []

[screen] 섹션

항목 설명 기본값
clear_on_rebuild 리빌드 시 화면 클리어 false
keep_scroll 스크롤 위치 유지 true

실전 설정 예시

Gin 프레임워크 프로젝트

root = "."
tmp_dir = "tmp"

[build]
  bin = "./tmp/server"
  cmd = "go build -o ./tmp/server ./cmd/server/main.go"
  delay = 1000
  exclude_dir = ["assets", "tmp", "vendor", "docs", "scripts"]
  exclude_regex = ["_test.go", ".*_mock.go"]
  include_ext = ["go", "yaml", "yml", "json"]
  include_dir = ["cmd", "internal", "pkg", "configs"]
  kill_delay = "500ms"
  send_interrupt = true

[log]
  time = true

[misc]
  clean_on_exit = true

[screen]
  clear_on_rebuild = true

gRPC 서버 프로젝트

root = "."
tmp_dir = "tmp"

[build]
  bin = "./tmp/grpc-server"
  cmd = "go build -o ./tmp/grpc-server ./cmd/grpc/main.go"
  delay = 2000
  exclude_dir = ["tmp", "vendor", "proto", "docs"]
  exclude_regex = ["_test.go", ".*\\.pb\\.go"]
  include_ext = ["go"]
  include_dir = ["cmd", "internal", "pkg"]
  pre_cmd = ["echo 'Building gRPC server...'"]
  post_cmd = ["echo 'Server restarted'"]
  kill_delay = "1s"
  send_interrupt = true

[log]
  time = true

멀티 모듈 프로젝트

root = "."
tmp_dir = "tmp"

[build]
  bin = "./tmp/app"
  cmd = "go build -o ./tmp/app ./cmd/app"
  delay = 1500
  exclude_dir = ["tmp", "vendor", "node_modules", ".git", "scripts"]
  exclude_file = ["go.sum"]
  include_ext = ["go", "yaml", "yml", "toml"]
  include_dir = []
  follow_symlink = true

[misc]
  clean_on_exit = true

Docker 환경 설정

Dockerfile

FROM golang:1.21-alpine

WORKDIR /app

# Air 설치
RUN go install github.com/air-verse/air@latest

# 의존성 설치 (캐시 활용)
COPY go.mod go.sum ./
RUN go mod download

# 소스 코드 복사
COPY . .

# Air 실행
CMD ["air", "-c", ".air.toml"]

docker-compose.yml

version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    volumes:
      - .:/app
      - /app/tmp
    ports:
      - "8080:8080"
    environment:
      - GO_ENV=development

Windows/Docker 환경 주의사항

Windows에서 Docker를 사용할 경우, fsnotify가 정상 동작하지 않을 수 있습니다. 이 경우 폴링 방식을 활성화해야 합니다:

[build]
  poll = true
  poll_interval = 500

폴링 방식은 파일 시스템 이벤트 대신 주기적으로 파일 변경을 확인하는 방식입니다. 리소스를 더 사용하지만 호환성이 높습니다.


.gitignore 설정

Air 사용 시 추가해야 할 항목:

# Air
tmp/
*.log
build-errors.log

문제 해결

빌드 오류 로그 확인

빌드 실패 시 build-errors.log 파일에 상세 오류가 기록됩니다:

cat build-errors.log

프로세스 종료 문제

간혹 이전 프로세스가 종료되지 않는 경우:

[build]
  send_interrupt = true
  kill_delay = "500ms"

send_interrupt를 활성화하면 SIGINT 시그널을 먼저 보내고, kill_delay 후에 강제 종료합니다.

파일 감지 안 됨

감시 대상에 포함되지 않은 확장자나 디렉토리일 수 있습니다:

[build]
  include_ext = ["go", "yaml", "yml", "json", "toml", "html", "tmpl"]
  include_dir = ["configs", "templates"]

CPU 사용량 높음

대규모 프로젝트에서 불필요한 디렉토리를 감시하면 리소스 낭비가 발생합니다:

[build]
  exclude_dir = ["vendor", "node_modules", ".git", "docs", "tmp", "assets"]
  exclude_unchanged = true

대안 도구 비교

도구 특징 장점 단점
Air 가장 널리 사용됨 활발한 유지보수, 풍부한 설정 -
Fresh 경량화 단순함 기능 제한적, 유지보수 둔화
Realize 다기능 태스크 러너 기능 복잡함, 유지보수 중단
reflex 범용 파일 감시 Go 외 다른 용도로도 사용 가능 Go 전용 기능 없음

Air가 현재 가장 활발하게 유지보수되고 있으며, 커뮤니티 지원도 좋습니다. 특별한 이유가 없다면 Air 사용을 권장합니다.


교훈

1. 개발 환경 투자의 중요성

작은 불편함도 누적되면 큰 생산성 저하로 이어집니다. 코드 변경마다 수동으로 빌드하고 재시작하는 것은 10초 정도의 작업이지만, 하루에 수백 번 반복되면 상당한 시간 손실입니다. Air 같은 도구에 초기 10분을 투자하면 이후 모든 개발 시간에서 효율을 얻습니다.

2. 컴파일 언어의 특성 이해

Go가 TypeScript나 Python처럼 즉시 리로드되지 않는 것은 언어의 한계가 아니라 컴파일 언어의 특성입니다. 이 특성을 이해하고 적절한 도구(Air)를 사용하면 인터프리터 언어와 유사한 개발 경험을 얻을 수 있습니다.

3. 프로젝트 초기 설정의 가치

새 프로젝트를 시작할 때 Air 설정을 포함시키면, 팀 전체가 일관된 개발 환경을 사용할 수 있습니다. .air.toml을 레포지토리에 커밋하여 모든 개발자가 동일한 설정을 공유하는 것을 권장합니다.


참고 자료

공식 문서

관련 문서

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/02   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
글 보관함