🔧 DevOps

Dagger

Dagger

프로그래밍 가능한 CI/CD 엔진. 컨테이너 기반, 로컬 실행 가능.

상세 설명

Dagger는 CI/CD 파이프라인을 프로그래밍 언어로 작성할 수 있게 해주는 혁신적인 도구입니다. Docker의 공동 창시자 Solomon Hykes가 만든 이 도구는 YAML이나 Jenkins 스크립트 대신 Go, Python, Node.js 등 익숙한 프로그래밍 언어로 파이프라인을 정의합니다.

핵심 특징

  • Pipeline as Code: 실제 프로그래밍 언어로 CI/CD 로직 작성
  • 로컬 실행: CI 서버 없이 개발 머신에서 파이프라인 테스트
  • 컨테이너 기반: 모든 작업이 컨테이너에서 실행되어 재현성 보장
  • 캐싱: 빌드 결과를 캐싱하여 반복 실행 속도 향상
  • 이식성: 어떤 CI/CD 플랫폼에서도 동일하게 동작

Dagger의 아키텍처

  • Dagger Engine: 파이프라인 실행 엔진 (Docker 기반)
  • Dagger SDK: Go, Python, Node.js, Rust SDK 제공
  • GraphQL API: 파이프라인 구성 요소 간 통신
  • Dagger Cloud: 분산 캐싱 및 시각화 (선택적)

코드 예제

Go SDK를 사용한 CI 파이프라인

// main.go - Dagger CI 파이프라인
package main

import (
    "context"
    "fmt"
    "os"

    "dagger.io/dagger"
)

func main() {
    if err := build(context.Background()); err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
}

func build(ctx context.Context) error {
    // Dagger 클라이언트 초기화
    client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stdout))
    if err != nil {
        return err
    }
    defer client.Close()

    // 소스 코드 마운트
    src := client.Host().Directory(".", dagger.HostDirectoryOpts{
        Exclude: []string{"node_modules", ".git"},
    })

    // Node.js 컨테이너 설정
    node := client.Container().
        From("node:20-alpine").
        WithDirectory("/app", src).
        WithWorkdir("/app")

    // 의존성 설치
    node = node.WithExec([]string{"npm", "install"})

    // 테스트 실행
    node = node.WithExec([]string{"npm", "test"})

    // 빌드 실행
    node = node.WithExec([]string{"npm", "run", "build"})

    // 빌드 결과물 내보내기
    _, err = node.Directory("/app/dist").Export(ctx, "./dist")
    if err != nil {
        return err
    }

    fmt.Println("Build completed successfully!")
    return nil
}

// Docker 이미지 빌드 및 푸시
func buildAndPush(ctx context.Context, registry, tag string) error {
    client, err := dagger.Connect(ctx)
    if err != nil {
        return err
    }
    defer client.Close()

    // 레지스트리 인증
    secret := client.SetSecret("registry-password", os.Getenv("REGISTRY_PASSWORD"))

    // 이미지 빌드
    src := client.Host().Directory(".")
    image := client.Container().
        Build(src).
        WithRegistryAuth(registry, "username", secret)

    // 이미지 푸시
    addr, err := image.Publish(ctx, fmt.Sprintf("%s/myapp:%s", registry, tag))
    if err != nil {
        return err
    }

    fmt.Printf("Published: %s\n", addr)
    return nil
}

Python SDK 예제

# ci.py - Dagger Python SDK
import sys
import anyio
import dagger

async def main():
    config = dagger.Config(log_output=sys.stdout)

    async with dagger.Connection(config) as client:
        # Python 컨테이너 설정
        python = (
            client.container()
            .from_("python:3.11-slim")
            .with_directory("/app", client.host().directory("."))
            .with_workdir("/app")
        )

        # 의존성 설치
        python = python.with_exec(["pip", "install", "-r", "requirements.txt"])

        # 린트 검사
        python = python.with_exec(["flake8", "."])

        # 테스트 실행
        python = python.with_exec(["pytest", "-v"])

        # 결과 출력
        output = await python.stdout()
        print(output)

if __name__ == "__main__":
    anyio.run(main)

dagger.json 모듈 설정

{
  "name": "my-pipeline",
  "sdk": "go",
  "dependencies": [],
  "source": "dagger"
}

실무 대화 예제

DevOps 엔지니어
"Jenkins 파이프라인이 복잡해지면서 YAML 관리가 힘들어지고 있어요. 로컬에서 테스트도 어렵고..."
시니어 개발자
"Dagger를 도입해보는 건 어때요? Go나 Python으로 파이프라인을 작성할 수 있어서 IDE 지원도 받고, 로컬에서 바로 테스트할 수 있어요. 우리 팀 백엔드가 Go니까 Go SDK로 작성하면 모두가 이해하고 수정할 수 있죠."
주니어 개발자
"그런데 기존 GitHub Actions랑 같이 쓸 수 있나요?"
시니어 개발자
"네, Dagger는 어떤 CI 환경에서도 실행할 수 있어요. GitHub Actions에서 `dagger run go run ./ci` 명령 하나로 호출하면 돼요. 핵심 로직은 Dagger에, 트리거만 GitHub Actions에 두는 거죠. 나중에 GitLab CI나 CircleCI로 옮겨도 파이프라인 코드는 그대로예요."

주의사항

Docker 의존성

Dagger는 Docker(또는 Podman, containerd)가 필요합니다. Docker가 없는 환경에서는 실행할 수 없으며, CI 러너에 Docker 권한이 있어야 합니다.

학습 곡선

YAML 기반 CI에 익숙한 팀은 초기에 적응 시간이 필요합니다. SDK 사용법과 컨테이너 개념에 대한 이해가 필요합니다.

디버깅 복잡성

여러 컨테이너 계층에서 실행되므로 디버깅이 복잡할 수 있습니다. `--debug` 플래그와 로그 분석 능력이 필요합니다.

초기 빌드 시간

첫 실행 시 베이스 이미지 풀링으로 시간이 걸립니다. 캐싱이 적용되면 이후 빌드는 빨라집니다.

더 배우기