멀티 모듈 프로젝트
여러 Go 모듈로 구성된 프로젝트에서 whatap-go-inst를 사용하는 방법을 설명합니다.
용어 정리
| 용어 | 설명 | 예시 |
|---|---|---|
| 모듈 | go.mod가 있는 단위 | module mycompany/user-api |
| 패키지 | 디렉토리 레벨 코드 그룹 | mycompany/user-api/pkg/auth |
| 외부 모듈 | go get으로 받은 의존성 | github.com/gin-gonic/gin |
| 로컬 모듈 | 같은 프로젝트 내 별도 모듈 | replace ../shared-lib |
패키지 유형별 처리
whatap-go-inst는 패키지 위치에 따라 계측 여부를 결정합니다.
toolexec 스킵 규칙
// 1. Go 표준 라이브러리 → 스킵
if strings.HasPrefix(path, os.Getenv("GOROOT")) {
return true // 변환 안 함
}
// 2. 외부 패키지 (go get) → 스킵
if strings.HasPrefix(path, os.Getenv("GOMODCACHE")) {
return true // 변환 안 함
}
// 3. 그 외 (내 코드) → 변환
return false
요약
| 패키지 유형 | 경로 | 계측 여부 |
|---|---|---|
| Go 표준 라이브러리 | $GOROOT/src/... | 스킵 |
| 외부 모듈 (go get) | $GOMODCACHE/... | 스킵 |
| 내 프로젝트 코드 | 로컬 경로 | 계측 |
| replace 로컬 모듈 | 로컬 경로 (../) | 계측 (fast 모드만) |
멀티 모듈 시나리오
시나리오: 3개의 분리된 모듈
C:/projects/
├── db-lib/ # 모듈 A: DB 로직 라이브러리
│ ├── go.mod # module mycompany/db-lib
│ ├── connection.go # sql.Open() 사용
│ └── query.go
│
├── web-lib/ # 모듈 B: 웹 서버 라이브러리
│ ├── go.mod # module mycompany/web-lib
│ ├── server.go # gin.Default() 사용
│ └── handler.go
│
└── main-app/ # 모듈 C: 메인 앱
├── go.mod # module mycompany/main-app
└── main.go # A, B를 import
모듈 C의 go.mod
module mycompany/main-app
go 1.21
require (
mycompany/db-lib v1.0.0
mycompany/web-lib v1.0.0
)
권장 접근 방식
방식 1: 모듈별 별도 inject (배포용)
운영 환경 배포에 권장합니다.
Step 1: 각 모듈 inject
# db-lib 계측
cd db-lib
whatap-go-inst inject -s . -o ../db-lib-instrumented
# web-lib 계측
cd web-lib
whatap-go-inst inject -s . -o ../web-lib-instrumented
# main-app 계측
cd main-app
whatap-go-inst inject -s . -o ../main-app-instrumented
Step 2: 계측된 버전으로 교체
// main-app-instrumented/go.mod
replace mycompany/db-lib => ../db-lib-instrumented
replace mycompany/web-lib => ../web-lib-instrumented
Step 3: 빌드
cd main-app-instrumented
go build ./...
방식 2: Monorepo 구조 (신규 프로젝트)
신규 프로젝트는 단일 모듈 구조가 가장 간단합니다.
myproject/
├── go.mod # 단일 모듈 mycompany/myproject
├── cmd/
│ └── main/
│ └── main.go # main 패키지
├── internal/
│ ├── db/
│ │ └── connection.go
│ └── web/
│ └── server.go
└── pkg/
└── shared/
└── utils.go
# 빌드
whatap-go-inst go build ./cmd/main
모드별 동작 비교
--wrap 모드
| 항목 | --wrap 모드 |
|---|---|
| main 모듈 | 계측됨 |
| replace 모듈 | 계측 안 됨 |
| 외부 모듈 (GOMODCACHE) | 스킵 |
| 사전 조건 | 없음 |
replace 처리
// --wrap 모드는 replace 경로만 조정
replace mycompany/db-lib => ../db-lib
// 임시 디렉토리 복사 후:
replace mycompany/db-lib => /original/path/to/db-lib // 원본 참조!
결과: replace 대상 모듈은 복사되지 않고 원본(비계측) 코드를 참조
replace 모듈도 계측하려면: 각 모듈을 별도로 inject 해야 합니다.
# 각 모듈 별도 inject
whatap-go-inst inject -s ../db-lib -o ../db-lib-instrumented
whatap-go-inst inject -s . -o ./instrumented
# instrumented/go.mod에서 replace 경로 수정
# replace mycompany/db-lib => ../db-lib-instrumented
주의사항
1. main이 없는 라이브러리 inject
main 함수가 없는 라이브러리도 inject할 수 있습니다.
| 항목 | 처리 |
|---|---|
trace.Init() | 추가 안 됨 (정상) |
sql.Open() → whatapsql.Open() | 변환됨 |
gin.Default() + 미들웨어 | 추가됨 |
# 라이브러리 모듈 inject
cd db-lib
whatap-go-inst inject -s . -o ./instrumented
# 결과: whatapsql.Open()으로 변환됨
# trace.Init() 추가 안 됨 (main에서만 호출해야 함)
2. 의존성 전파
계측된 라이브러리를 사용하는 모듈도 whatap 의존성이 필요합니다.
// db-lib-instrumented/connection.go
import "github.com/whatap/go-api/instrumentation/.../whatapsql"
// main-app에서 빌드하려면 이 의존성이 필요
cd main-app
go get github.com/whatap/go-api@latest
FAQ
Q1: replace 모듈도 계측되나요?
A: 아니요, wrap 모드는 replace 모듈을 계측하지 않습니다. replace 경로만 조정하고 원본을 참조합니다. replace 모듈도 계측하려면 각 모듈을 별도로 inject 해야 합니다.
각 모듈 별도 inject
whatap-go-inst inject -s ../db-lib -o ../db-lib-inst
whatap-go-inst inject -s ../web-lib -o ../web-lib-inst
Q2: 외부 라이브러리(gin, gorm 등)를 계측할 수 있나요?
A: 아니요. 외부 라이브러리는 $GOMODCACHE에 있어서 스킵됩니다. 이는 의도된 동작입니다.
- 외부 라이브러리 수정 시 다른 프로젝트에 영향
- Go 모듈 시스템의 불변성 원칙
대신, 사용자 코드에서 외부 라이브러리 호출이 변환됩니다:
sql.Open()→whatapsql.Open()gin.Default()→ 미들웨어 자동 추가
Q3: Monorepo vs 멀티 모듈, 어떤 게 좋은가요?
| 구조 | 장점 | 단점 |
|---|---|---|
| Monorepo | 계측 간단, 의존성 관리 쉬움 | 모듈 재사용 어려움 |
| 멀티 모듈 | 독립 모듈 배포 가능 | 계측 설정 복잡 |
권장:
- 신규 프로젝트 → Monorepo
- 기존 멀티 모듈 → 모듈별 별도 inject
Q4: CI/CD에서 멀티 모듈을 어떻게 빌드하나요?
아래 CI/CD 예제 섹션을 참조하세요.
CI/CD 예제
GitHub Actions
# .github/workflows/build.yml
name: Build with WhaTap Instrumentation
jobs:
build:
runs-on: ubuntu-latest
steps:
# 1. 모든 모듈 체크아웃
- uses: actions/checkout@v3
with:
path: main-app
- uses: actions/checkout@v3
with:
repository: mycompany/db-lib
path: db-lib
- uses: actions/checkout@v3
with:
repository: mycompany/web-lib
path: web-lib
# 2. Go 설치
- uses: actions/setup-go@v4
with:
go-version: '1.21'
# 3. whatap-go-inst 설치
- run: go install github.com/whatap/go-api-inst/cmd/whatap-go-inst@latest
# 4. 빌드
- run: |
cd main-app
whatap-go-inst go build -o myapp ./...
# 5. 아티팩트 업로드
- uses: actions/upload-artifact@v3
with:
name: myapp
path: main-app/myapp
GitLab CI
# .gitlab-ci.yml
stages:
- build
build:
stage: build
image: golang:1.21
before_script:
# whatap-go-inst 설치
- go install github.com/whatap/go-api-inst/cmd/whatap-go-inst@latest
# 다른 모듈 클론
- git clone https://gitlab.com/mycompany/db-lib.git ../db-lib
- git clone https://gitlab.com/mycompany/web-lib.git ../web-lib
script:
- whatap-go-inst go build -o myapp ./...
artifacts:
paths:
- myapp
Docker 멀티 스테이지 빌드
# Dockerfile
FROM golang:1.21 AS builder
WORKDIR /workspace
# whatap-go-inst 설치
RUN go install github.com/whatap/go-api-inst/cmd/whatap-go-inst@latest
# 소스 복사 (모든 모듈)
COPY db-lib/ ./db-lib/
COPY web-lib/ ./web-lib/
COPY main-app/ ./main-app/
# 초기화 및 의존성 추가
WORKDIR /workspace/main-app
# 빌드
RUN whatap-go-inst go build -o /app/myapp ./...
# 런타임 이미지
FROM alpine:latest
COPY /app/myapp /app/myapp
CMD ["/app/myapp"]