ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Go 개발 생산성 향상을 위한 Air Live Reload 도입 가이드
    실제 경험과 인사이트를 AI와 함께 정리한 글 2025. 12. 6. 11:59

    작성일: 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을 레포지토리에 커밋하여 모든 개발자가 동일한 설정을 공유하는 것을 권장합니다.


    참고 자료

    공식 문서

    관련 문서

Designed by Tistory.