메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

지속 가능한 머신러닝을 위한 MLOps & CI/CD 구축 전략 (1) 의존성 지옥 탈출하기

 

잘되는 머신러닝 팀의 비밀 3부작

2부 지속 가능한 머신러닝을 위한 MLOps & CI/CD 구축 전략

1화.  안전하고 반복 가능한 환경 만들기

 

 

이번에는  모든 ML 종사자가 경력 중 여러 번 겪게 될 도전 과제인 ‘의존성 지옥’에 대해 이야기하려고 합니다. 의존성 지옥은 악명 높은 ‘내 컴퓨터에서는 잘 되는데’ 문제의 일반적인 원인 중 하나입니다. 의존성 지옥을 자주 다루는 ML 종사자라면 다음과 같은 질문에 대한 답을 찾고 있을 것입니다.

 

“팀원들과 내가 로컬 컴퓨터, 클라우드의 노트북, 분산 클러스터 등 어디서든지 최소한의 수고와 문제 해결로 의존성을 쉽게 재현할 수 있도록 설치할 수 있는 방법은 무엇인가?”

 

“프로젝트의 의존성이 점점 커질 때 의존성을 설치하는 것이 거대한 인터넷 자체를 다운로드하는 것처럼 느껴지지 않도록 최적화할 수 있는 방법은 무엇인가?”

 

“프로젝트가 의존성과 그 의존성의 의존성에 있는 보안 취약점으로 손상되지 않도록 보장할 수 있는 방법은 무엇인가?”

 

이번 이야기가 끝날 때쯤 여러분은 이러한 질문에 대한 답을 알게 될 것입니다. 그리고 여러분은 다음과 같은 효과적인 의존성 관리 방법을 자신의 프로젝트에 적용할 수 있게 될 것입니다.

 

  • 불완전한 의존성 관리 접근 방식을 인식하는 방법
  • ML 프로젝트에서 의존성을 효과적으로 관리하기 위한 원칙과 도구
  • 컨테이너를 언제, 왜, 어떻게 사용하는지
  • 명령행 도구인 batect를 사용하여 도커를 간소화하는 방법

 

우선 효과적인 의존성 관리를 위한 원칙과 실천 방법으로 들어가 봅시다.

 

 

1. 의존성 지옥 탈출하기 — 안전하고 반복 가능한 환경 만들기

 

체크아웃하고 바로 시작하기

 

여러분이나 새로운 팀원이 합류했을 때, 개발 환경을 설정하고 첫 커밋을 하기까지 얼마나 걸렸나요? 며칠, 혹은 몇 주가 걸렸던 경험이 있지 않나요?

 

이런 상상을 해보세요. 새로운 팀원을 온보딩할때  “리포지터리를 체크아웃하고 ./go.sh만 실행하면 개발이 시작됩니다.”라고 안내하는 겁니다. 이 말이 과장처럼 들릴 수 있지만, 실제로 많은 성숙한 소프트웨어 팀은 이를 실현하고 있습니다. 이들은 구전 지식이나 복잡한 설정 문서 없이도, 몇 개의 명령어만으로 로컬 개발 환경을 구성하고 테스트를 실행하며, 바로 코드 작업을 시작할 수 있게 합니다.


이러한 접근은 ‘체크아웃하고 바로 시작하기’라고 합니다. 팀의 생산성을 극적으로 높이고, 개발 환경 구성에 불필요한 시간을 쓰지 않고, 본질적인 문제 해결에 집중할 수 있게 해주죠. 이 개념을 실제로 구현하려면, 강력한 의존성 관리 전략이 뒷받침되어야 합니다.

 

 

효과적인 의존성 관리의 원칙

 

의존성 관리는 간단한 개념입니다. 이 맥락에서의 의존성은 프로젝트가 의도한 대로 작동하고, 런타임 오류를 피하기 위한 필요한 모든 소프트웨어 요소를 의미합니다. 의존성은 운영체제 수준과 애플리케이션 수준으로 나눌 수 있습니다.

 

출처: 『잘되는 머신러닝 팀엔 이유가 있다』 p.130

 

현대 ML 애플리케이션은 다양한 운영체제에서 실행되므로, 환경 간 일관성을 유지하는 것이 핵심입니다. 이때 운영체제 수준의 의존성은 도커 같은 컨테이너 기술을 통해 관리할 수 있습니다. 하나의 환경에서 설치된 패키지가 다른 환경에서도 동일하게 작동해야 하죠. 두 수준에서의 의존성을 적절히 관리함으로써 우리는 코드가 일관되고, 운영 환경과 유사한 환경을 만들 수 있으며, 여기서 지금 작동하는 것이 어디서든 항상 작동할 수 있도록 보장할 수 있습니다.

 

이런 일관된 환경을 어떻게 구현할 수 있을까요? 

답은 효과적인 의존성 관리 원칙에 있습니다. 특정 기술이나 도구에 구애 없이 지켜야 하는 네 가지 원칙을 살펴보겠습니다.

 

1) 코드로서의 의존성

모든 의존성과 환경 설정을 코드로 명시하면 모든 코드 기여자가 자동으로 일관된 개발 환경을 만들 수 있습니다. 또한 운영체제 수준과 애플리케이션 수준의 의존성 스택에 대한 모든 변경 사항을 버전으로 관리할 수 있으며, 자동화된 재현이 가능해집니다.

 

2) 운영 환경과 유사한 개발 환경

개발 중에도 운영 환경과 최대한 유사한 설정을 갖춰야, 릴리스 시 발생할 수 있는 잠재적인 문제를 빠르게 포착할 수 있습니다. 

 

3) 애플리케이션 수준의 환경 격리

각 프로젝트는 별도의 가상 환경을 가져야 합니다. 이렇게 하면 서로 다른 애플리케이션 간의 불필요한 결합을 피할 수 있습니다. 또한 어떤 도구를 선택하든 팀은 하나의 도구로 통일하여 복잡성을 줄여야 합니다.

 

4) 운영체제 수준의 관리도 소홀히 하지 않기

팀 전체가 하나의 운영체제를 사용하더라도 운영체제 수준의 의존성(예: 특정 버전의 파이썬 3.x 설치)을 배포 중에 정기적으로 재현할 수 있어야 합니다. 많은 ML 팀이 애플리케이션 수준의 의존성은 잘 관리하지만, 운영체제 수준의 의존성 관리는 소홀히 하는 경우가 많습니다. 이로 인해 수많은 에러와 시간 낭비가 발생하곤 합니다.

 

이러한 원칙을 실천하려면, 적절한 도구 선택도 중요합니다. 예컨대 도커는 운영체제 수준의 환경을 캡슐화하여 일관성과 재현성을 높여줍니다. 이를 실제로 어떻게 구현하는지, 그리고 도커의 복잡함을 줄여주는 도구 batect에 대해서도 살펴보겠습니다.

 

 

도커로 환경 일관성 확보하기

 

도커는 컨테이너 안에 코드와 모든 의존성을 함께 포장함으로써, 여러 환경에서도 일관된 실행을 보장해 줍니다. 로컬에서 실행한 코드가 클라우드 훈련 환경, 프로덕션 API 서버, CI 파이프라인에서도 동일하게 작동하는 것이죠.

 

팀 전체가 동일한 환경을 사용할 수 있다는 것은 예측 가능성과 생산성을 모두 높여줍니다. 물론, 도커를 처음 접하는 ML 실무자라면 "복잡하다"거나 "꼭 필요할까?"라고 생각할 수도 있습니다. 하지만 도커는 오히려 환경 구성을 단순화하고, 문제 해결 시간을 줄여주는 강력한 도구입니다.

 

그리고, 파이썬에서 흔히 마주치는 "버전 불일치 문제"나 "누락된 시스템 패키지"도 도커를 통해 예방할 수 있습니다. 특정 CPU 아키텍처에 맞는 파이썬 휠이 존재하지 않는 상황에서도, gcc를 미리 설치해둔다면 빌드 실패 없이 안정적으로 환경을 구성할 수 있습니다.

 

이처럼 도커를 잘 활용하면, ML 프로젝트에서 대부분의 환경에서 일관되게 작동하는 안정적인 개발 환경을 구현할 수 있습니다.

 

✔️도커 이미지 빌드 최적화, 다양한 플랫폼 지원 등 더 복잡한 환경 설정 전략은 『잘되는 머신러닝 팀엔 이유가 있다』3장에서 더 자세히 다루고 있습니다.

 

 

batect로 도커의 복잡성 줄이기

 

도커의 유연성은 강력하지만, 프로젝트가 커질수록 명령어가 길고 복잡해지는 문제가 생깁니다. 예를 들어, docker run 명령에 포함된 환경 변수, 포트 설정, 볼륨 마운트 등을 README, 셸 스크립트, CI 설정 등 여러 곳에서 따로 관리해야 하죠.

 

batect는 이 복잡성을 단일 구성 파일에 정리해 주는 도구입니다. 예를 들어 ./batect train-model 한 줄이면, 모든 필요한 도커 설정과 명령을 일관되게 실행할 수 있습니다.

 

또한, 테스트 간 의존 관계를 정의하여 “모델 훈련 → API 테스트”처럼 순차적인 작업 흐름을 설정할 수도 있고, 로컬 환경과 CI 파이프라인에서 동일한 명령어를 사용할 수 있다는 점도 큰 장점입니다.

 

✔️ batect의 캐시 설정, CI 통합, YAML 구성 방식 등은 『잘되는 머신러닝 팀엔 이유가 있다』3장에서 더 자세히 다루고 있습니다.

 

 

프로그램에서 batect 사용 방법

 

batect는 설치가 간단하며 깃허브 액션과 CircleCI같은 일반적인 CI 플랫폼과 잘 통합됩니다. batect는 컨테이너와 작업이라는 두 가지 간단한 개념을 제공하며, 도커 클라이언트를 통해 할 수 있는 모든 작업을 지원합니다.  batect에 대해 더 알아보고 싶다면 관련된 문서가 잘 정리되어 있습니다. batect 시작 가이드는 물론  batect.yml 구성 참조, CI 시스템과 함께 사용하는 방법에 대한 가이드를 포함하고 있습니다.

 

 

 

2. 실무에서의 효과적인 의존성 관리

 

지금까지 ‘의존성 지옥’에서 탈출하기 위해 꼭 지켜야 할 네 가지 원칙을 살펴보았습니다. 이제 실전으로 들어가 보겠습니다. 여기서는 이 원칙들이 실제 머신러닝 프로젝트에 어떻게 적용되는지를 코드 예제와 함께 구체적으로 설명합니다. 

 

목표는 간단합니다. 어디서든, 누구나, 예측 가능한 방식으로 개발 환경을 재현하고 실행하는 것입니다.

 

이제부터는 의존성 관리를 보다 깊이 이해하기 위해 ‘대출 채무 불이행 예측 모델’을 훈련시키고, 테스트하는 전 과정을 따라가 보겠습니다. 새로운 팀원이 합류해 프로젝트를 클론한 뒤 ./go.sh 한 줄만 실행하면, 도커와 batect로 관리되는 일관된 환경에서 모델을 훈련하고, API로 서빙하며, 클라우드에 배포하는 전 과정을 경험하게 될 것입니다.

 

실제 작업에 들어가기 전, 우리가 ML 워크플로에서 어떤 부분을 컨테이너로 만들고 있는지에 대해 쉽게 이해할 수 있도록 설명하겠습니다.

 

 

컨테이너화의 출발점: “무엇을 컨테이너화할 것인가?”

 

프로젝트를 도커로 만드는 가장 첫걸음이자 중요한 점은 우리가 무엇을 컨테이너화할지 명확히 정하는 것입니다.

 

컨테이너를 도입한다고 해서 모든 작업을 하나의 이미지에 담을 필요는 없습니다. 오히려 그것은 최악의 선택일 수 있습니다. 많은 팀들이 범하는 실수 중 하나는, 모델 훈련 환경과 API 운영 환경, 개발용 노트북까지 전부 같은 컨테이너로 처리하려는 시도입니다. 하지만 이는 불필요하게 무거운 이미지를 만들고, 보안 취약점을 확대하며, 유지보수 난이도를 증가시킵니다.

 

프로젝트의 작업 목적에 따라 컨테이너 이미지를 분리하면, 불필요한 의존성을 줄이고 관리 비용을 절감할 수 있습니다. 문서에서는 ‘공유도’와 ‘독특성’을 기준으로 네 가지 이미지로 구성해 실습을 진행합니다.

 

출처: 『잘되는 머신러닝 팀엔 이유가 있다』 p.158

 

이 표에 나타나는 각 작업을 시각화 하면 아래와 같습니다. 물론 여러분이 알다시피 이것은 컨테이너화된 프로세스와 그에 해당하는 각각의 이미지에 불과합니다.

 

출처: 『잘되는 머신러닝 팀엔 이유가 있다』 p.159

 

 

컨테이너 도입 타이밍: “언제 도커를 써야 할까?”

 

많은 실무자들이 갖는 고민입니다. "실험인데 굳이 도커까지 써야 하나요?" 이에 대한 답은 프로젝트가 배포될 가능성이 있거나, 운영 환경과 다른 운영체제에서 실행될 가능성이 있다면 도커를 고려해야 한다는 것입니다.

 

같은 운영체제를 사용하는 소규모 팀이나 단기 실험 프로젝트라면 파이썬의 의존성 관리 도구인 poetry로도 충분할 수 있습니다. 하지만 한번이라도 CI 파이프라인에 넣거나, 클라우드에 올릴 가능성이 있다면 컨테이너 기반 접근이 장기적으로 훨씬 효율적입니다.

 

경험상, 초기에는 “당장 도커는 너무 무거워 보여서” 피하고 싶지만, 결국 운영 환경에서 발생하는 크고 작은 문제들을 수습하는 데 드는 비용이 컨테이너 도입보다 훨씬 크다는 걸 알게 됩니다.

 

 

✅ 실습: ‘체크아웃하고 바로 시작하기’ 구현하기

 

이제 본격적으로 실습을 통해 개발 환경을 구성하고, 모델 훈련과 API 배포까지 전체 워크플로를 구성해봅니다. 

💡 실습에 대한 상세한 내용은『잘되는 머신러닝 팀엔 이유가 있다』4장에서 더 자세히 다루고 있습니다.

 

1. 운영체제 의존성 설치

go 스크립트로 OS별 필수 도구를 자동 설치합니다. macOS, Ubuntu, Windows용으로 각각 준비되어 있어 환경별 차이를 최소화합니다. 도커 데스크톱 역시 자동 설치되며, 복잡한 시스템 설정을 최소화합니다.

 

2. 개발용 컨테이너 이미지 빌드

./batect setup 명령 한 줄이면 poetry가 자동으로 애플리케이션 의존성을 설치하고 가상 환경을 구성합니다. 모든 설정은 batect.yml에 선언되어 있어, 명령어 사용자는 내부 동작을 몰라도 일관된 결과를 얻을 수 있습니다.

 

출처: 『잘되는 머신러닝 팀엔 이유가 있다』 p.164

 

3. 개발 컨테이너 시작 및 테스트

이제 배시 셸로 컨테이너에 접속해 다양한 개발 작업을 수행할 수 있습니다. smoke 테스트로 모델 훈련을 확인하거나, Jupyter 노트북을 열 수도 있습니다.

 

# 컨테이너 시작

$ ./batect start-dev-container



# smoke 테스트 실행

$ scripts/tests/smoke-test-model-training.sh

 

4. 로컬 API 서버 구동 및 테스트

모델을 내장한 FastAPI 서버를 로컬에서 실행하고, curl 스크립트로 예측 요청을 테스트합니다.

 

# 로컬 API 서버 실행

$ ./batect start-api-locally



# 예측 요청 테스트

$ scripts/request-local-api.sh

 

5. 통합 개발 환경(IDE) 설정: 가상 환경 연동

poetry로 구성된 가상 환경을 IDE에 연동하면, 코드 자동완성, 문서 보기, 타입 힌트 등을 바로 활용할 수 있습니다. VS Code, 파이참 기준으로 자세한 설정법도 제공됩니다.

 

6. 클라우드 훈련: GitHub Actions 연동

코드 변경 후 git push만 하면, 깃허브 CI 파이프라인이 트리거되어 도커 컨테이너 안에서 모델 훈련이 자동으로 실행됩니다.

 

# .github/workflows/ci.yaml 예시

train-model:

  runs-on: ubuntu-20.04

  steps:

    - uses: actions/checkout@v3

    - name: Train model

      run: ./batect train-model

 

7. API 이미지 배포

훈련된 모델을 내장한 API 이미지를 빌드하고, 도커 레지스트리에 푸시한 뒤 클라우드에 배포합니다. 이 작업도 batect를 통해 동일한 명령어로 로컬과 CI에서 실행 가능합니다.

 

# API 배포

$ ./batect deploy-api

 

이 실습 예제는 단지 하나의 데모가 아닙니다. 실무에서 이 방식을 프로젝트 템플릿으로 재사용하면, 팀 전체가 코드 기준으로 동일한 개발 환경을 공유하게 됩니다. 개발 생산성이 올라가고, 협업 장벽은 낮아지며, CI/CD와 운영 자동화에 자연스럽게 연결됩니다. 다음 파트에서는 이렇게 구성한 환경을 얼마나 안전하게 유지할 수 있는지, 즉 보안 취약점을 사전에 탐지하고 종속성을 자동으로 최신 상태로 유지하는 전략을 살펴보겠습니다.

 

 

안전한 종속성 관리

 

앞서 우리는 개발 환경을 빠르게 재현하고, 도커와 batect를 활용해 일관된 ML 워크플로를 구축하는 방법을 살펴봤습니다. 하지만 환경이 일관되게 작동한다고 해서, 반드시 안전한 것은 아닙니다.

 

이번 글의 마지막 순서로  ML 프로젝트의 보안을 실질적으로 위협할 수 있는 종속성 관리 문제를 다룹니다. 그 중에서도 가장 큰 위협은 바로, 사용하지 않는 종속성과 오래된 라이브러리에서 비롯되는 보안 취약점입니다.

 

 

1억 4천만 명 정보 유출의 출발점, 오래된 종속성

 

2017년, 미국의 신용 모니터링 회사 에퀴팩스(Equifax)는 해커들의 공격으로 1억 4,300만 명의 개인정보를 유출당했습니다. 문제의 원인은 놀랍게도 아주 단순했습니다. 이미 보안 패치가 완료된 라이브러리(Apache Struts)의 구버전을 계속 사용하고 있었던 것이죠. 

 

이처럼 오래된 종속성을 방치하면, 시스템 전체가 치명적인 보안 리스크에 노출됩니다. 파이썬도 예외는 아닙니다. 클라우드 자격 증명 유출이나, 임의 코드 실행이 가능한 종속성들이 실제로 발견되었으며, 지금도 일부 프로젝트는 이들에 노출된 채 운영되고 있을 수 있습니다.

 

그렇다면 보안 위험을 줄이는 데 도움이 되는 방법은 무엇일까요?

 

 

방법 1: 필요하지 않은 종속성 제거하기

 

의존성이 많을수록 공격받을 수 있는 범위가 넓어지고, 이미지 빌드 시간과 배포 시간이 늘어나며, 유지보수는 복잡해지고, 하위 종속성과의 충돌 가능성도 높아집니다. 특히 ML 프로젝트에서는 초기 실험용으로 설치한 패키지들을 그대로 운영 환경까지 끌고 가는 경우가 많은데, 이는 매우 위험한 접근이죠. 다음은 실무에서 적용할 수 있는 세 가지 간단한 규칙입니다.

체크리스트

설명

✅ 가능한 작은 베이스 이미지 사용예: python:3.10 → python:3.10-slim-bookworm
✅ pyproject.toml에서 불필요한 패키지 제거의존성 목록을 최소화
✅ 운영 이미지에 개발용 종속성 포함 금지테스트, 노트북 등은 배제

특히 마지막 항목은 도커 다단계 빌드를 활용하면 쉽게 구현할 수 있습니다.

 

 

방법 2: 종속성 검사 및 업데이트 자동화

 

애플리케이션 보안을 강화하는 두 번째 방법은 종속성을 주기적으로 스캔하고, 자동으로 업데이트하는 체계를 구축하는 것입니다. 이때 다음과 같은 도구를 사용할 수 있습니다.

 

도구

기능

DependabotGitHub에 내장된 자동 PR 생성 도구
Trivy, Snyk, Safety오픈소스 SCA(Software Composition Analysis) 도구
nvd.nist.gov국가 취약점 데이터베이스 (의존성 스캐너들이 참조하는 DB)

이들 도구는 대부분 다음과 같은 세 가지 보안 레벨을 자동으로 검사합니다.

 

• 도커 이미지 수준 — 운영체제 패키지 취약점

• 애플리케이션 수준 — Python 패키지 취약점

• 자동 업데이트 PR 생성 및 병합

 

이처럼 정기적이고 자동화된 점검과 업데이트 프로세스를 도입하면, 취약점을 조기에 발견하고 빠르게 대응할 수 있는 기반이 마련됩니다.

 

단 보안 업데이트가 자동화되더라도, 때로는 제로데이 취약점처럼 아직 패치되지 않은 보안 위협이 존재할 수 있습니다. 또는 자동 업데이트된 종속성이 모델의 예측 성능이나 API 동작에 영향을 줄 수도 있습니다.

 

다음 글에서는 이 안전한 환경 위에 신뢰할 수 있는 머신러닝 시스템을 구축하기 위해 필요한 자동 테스트 전략과 CI/CD 흐름을 구체적으로 살펴보겠습니다.


위 콘텐츠는『잘되는 머신러닝 팀엔 이유가 있다』내용을 재구성하여 작성하였습니다.

댓글

댓글 입력