kimyu0218
  • [docker] GitHub Actions에서 도커 캐싱하기(cache from/cache to)
    2024년 02월 11일 21시 05분 55초에 업로드 된 글입니다.
    작성자: @kimyu0218

    CI/CD에서는 이미지 빌드 실행 시간을 최소화하는 것이 중요하다. 따라서 도커 파일을 작성할 때 각 레이어를 작은 단위로 분리하여 효율적으로 캐싱되도록 설계하는 것이 좋다. 하지만 깃헙 액션의 특성상 새로운 환경의 러너를 할당받게 되어 이전 빌드 내용이 남아있지 않아 캐시를 활용할 수 없었다.

    도커에 대해 좀 더 공부하다보니 도커에서 캐싱할 수 있는 옵션이 있었다. 이미지 빌드 시에 `--cach-from` 및 `--cache-to`를 사용하여 캐싱을 관리할 수 있다. 지금부터 이러한 옵션을 활용하여 이미지 빌드 시간을 최적화해보자.

    캐시 유형
    • `inline` - 빌드 캐시를 동일한 이미지에 내장하어 레지스트리에 푸시될 때 캐시도 함께 푸시한다.
    • `registry` - 빌드 캐시를 별도의 이미지에 내장한다.
    • `local` - 빌드 캐시를 로컬 디렉토리에 저장하여 동일한 머신에서 재사용 할 수 있도록 한다.
    • `gha` - 빌드 캐시를 깃헙 액션 캐시에 업로드한다.
    🚨 깃헙 액션 캐시는 10GB의 크기 제한이 있다. 10GB를 초과하면 총 크기가 10GB 미만이 될 때까지 캐시를 제거한다.

     

    docker compose의 캐시 옵션

    docker CLI를 사용하는 경우, `--cache-from`, `--cache-to`를 활용하면 된다!

     

    캐시 모드

    캐시 관련 옵션을 살펴보기 전에 캐시 모드부터 알아보자. 캐시 모드는 캐시에 포함할 레이어를 정의하는 옵션으로, 두 가지 옵션이 있다.

    • `mode=min` : (기본값) 결과 이미지로 내보내는 레이어만 캐시
    • `mode=max` : 중간 단계의 레이어를 포함하여 모든 레이어를 캐시

    최소 모드는 도커 파일의 결과로 생성되는 이미지를 캐시한다. 레이어는 유지하기 때문에 변경 사항이 있는 경우, 변경된 부분 이후의 명령어만 다시 실행하고, 변경되지 않은 부분은 이전에 캐시된 결과를 재사용한다. 캐시 크기가 작아 저장 비용을 줄이고 시간을 단축할 수 있다.
     
    반면, 최대 캐시는 도커 파일의 모든 레이어를 캐시한다. 변경된 부분이 발생하면 모든 명령어를 다시 실행하여 더 많은 빌드 시간이 소요된다. 캐시 크기가 커 그만큼 저장 비용이 많이 들지만 더 많은 캐시 히트를 얻을 수 있다.
     

    캐시 가져오기

    `cache_from` 옵션은 컴포즈 파일에서 사용되는 옵션으로, 외부로 내보낸 빌드 캐시를 가져오는 데 사용된다. 이 옵션을 이용하면 이전에 내보낸 캐시를 가져와 현재 빌드에서 재사용할 수 있다.

    # alias version
    cache_from: [IMAGE_NAME[:TAG]]
    # full version
    cache_from: type=[CACHE_TYPE[, KEY=VALUE]],ref=[IMAGE_NAME[:TAG]]

     

    캐시 내보내기

    `cache_to` 옵션은 컴포즈 파일에서 사용되는 옵션으로, 빌드 캐시를 외부로 내보내는 데 사용된다. 이 옵션을 이용하면 현재 빌드에서 생성된 캐시를 다른 곳에서 재사용할 수 있다.

    일반적으로 `inline` 캐시를 사용하는 것이 좋지만 `inline` 캐시는 최소 모드만 지원한다. 따라서 최대 모드를 사용하려면 다른 캐시를 사용해야 한다.
    # alias version
    cache_to: [IMAGE_NAME[:TAG]]
    # full version
    cache_to: type=[CACHE_TYPE[, KEY=VALUE]],ref=[IMAGE_NAME[:TAG]]

     

    GitHub Actions에서 도커 캐싱하기

    컴포즈 파일에 캐시 옵션 추가하기

    이제 컴포즈 파일에 캐싱 옵션을 추가하여 캐싱을 활용해보자.

    version: "3.8"
    
    services:
      was:
        build:
          context: .
          cache_from:
            - ${DOCKER_USERNAME}/magicconch:was-latest
          cache_to:
            - ${DOCKER_USERNAME}/magicconch:was-latest
          dockerfile: Dockerfile.was
    
      signal:
        build:
          context: .
          cache_from:
            - ${DOCKER_USERNAME}/magicconch:signal-latest
          cache_to:
            - ${DOCKER_USERNAME}/magicconch:signal-latest
          dockerfile: Dockerfile.signal

    build 섹션에 `cache_from`과 `cache_to`를 추가해줬다. 도커 레지스트리에 푸시된 빌드 캐시를 활용하여 was, signal 서비스를 빌드하고 도커 레지스트리로 이번 빌드 캐시를 내보낸다.
     

    깃헙 액션 작성하기

    이번엔 컴포즈 파일을 빌드하고 레지스트리에 푸시하는 깃헙 액션 파일을 작성할 것이다.

    name: Backend Build
    
    on:
      pull_request:
        types: [opened, edited]
        paths: ["backend/**"]
    
    env:
      DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
    
    jobs:
      backend-build:
        runs-on: ubuntu-latest
    
        steps:
          - name: Checkout code
            uses: actions/checkout@v4
    
          - name: Generate .env file
            run: |
              cd backend
              echo "DOCKER_USERNAME=${{ secrets.DOCKER_USERNAME }}" >> .env
    
          - name: Login to Docker Hub
            uses: docker/login-action@v3
            with:
              username: ${{ secrets.DOCKER_USERNAME }}
              password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
    
          - name: Setup docker-compose
            uses: KengoTODA/actions-setup-docker-compose@v1.2.1
            with:
              version: "2.24.5"
    
          - name: Create a Builder Instance
            run: |
              docker buildx create --name buildx
              docker buildx use buildx
    
          - name: Build & Push Docker Cache
            run: |
              cd backend
              docker-compose -f compose.cache-export.yml build --builder buildx --build-arg BUILDKIT_INLINE_CACHE=1
              docker-compose -f compose.cache-export.yml push

    위의 코드는 액선 파일의 전문이다. 캐싱을 수행하기 위해서는 버전 업그레이드, 빌더 설치 등의 사전 작업이 필요하다.
     

    • 도커 컴포즈 버전 업그레이드
    Unsupported option: 'cache_to'

    `cache_to`와 `cache_from`을 이용하려는데 위와 같은 에러가 떴다. 해당 옵션은 버전 2부터 지원되는 옵션이기 때문에 버전을 업그레이드 해야 한다.

    - name: Setup docker-compose
      uses: KengoTODA/actions-setup-docker-compose@v1.2.1
      with:
        version: "2.24.5"

    위의 액션을 이용하여 버전을 `2.24.5`로 업그레이드 해줬다.
     

    • buildx 설치하기
    Cache export is not supported for the docker driver.
    Switch to a different driver, or turn on the containerd image store, and try again.

    컴포즈 버전을 업그레이드 하고 나면 또 다른 에러가 발생한다. 이는 기본으로 사용하는 도커 드라이버에서 캐싱 기능을 활용할 수 없음을 의미한다. 따라서 `buildx`를 사용하도록 수정해야 한다.

    - name: Create a Builder Instance
      run: |
        docker buildx create --name buildx
        docker buildx use buildx

    `buildx`라는 이름의 새로운 Docker Buildx 빌더 인스턴스를 생성하고, 방금 생성한 빌더를 사용하도록 지시한다.

    - name: Build & Push Docker Cache
      run: |
        cd backend
        docker-compose -f compose.cache-export.yml build --builder buildx --build-arg BUILDKIT_INLINE_CACHE=1
        docker-compose -f compose.cache-export.yml push

    `--builder` 옵션으로 빌더 인스턴스를 연결해주고, `--build-arg`로 인라인 캐시를 허용하는 옵션을 전달한다.

    CACHED!!

    위 사진은 `act`를 사용하여 로컬에서 깃헙 액션을 실행한 결과다. `importing cache`에 의해 도커 파일의 각 레이어가 캐시를 이용하는 것을 확인할 수 있다. 


    참고자료

    댓글