컨테이너를 다루다 보면 Docker는 익숙한데, 어느 순간부터 containerd라는 이름이 계속 등장합니다.
특히 Kubernetes 환경에서는 “Docker 대신 containerd 쓰세요” 같은 말을 자주 듣게 되죠.

오늘은 containerd가 정확히 뭘 하는지, Docker와 관계가 뭔지, Kubernetes에서 왜 중요한지를 포스팅용으로 깔끔하게 정리합니다.


✅ 한 줄 정의

🧩 containerd = 컨테이너를 “다운로드하고 실행하고 관리하는” 경량 런타임

즉, 컨테이너를 직접 실행하는 엔진 역할만 집중해서 수행하는 구성 요소입니다.


📌 containerd가 하는 일 (역할 정리)

containerd는 “컨테이너 운영에 필요한 핵심 기능”을 담당합니다.

 

기능 설명
이미지 관리 컨테이너 이미지 pull/push, 로컬 저장소 관리
컨테이너 실행 컨테이너 생성/시작/중지/삭제
스냅샷 관리 overlayfs 등으로 이미지 레이어(계층) 관리
네임스페이스 관리 프로젝트/서비스별 격리 단위 구성
이벤트 처리 컨테이너 상태 변경 이벤트 제공
OCI 준수 OCI(Open Container Initiative) 표준 기반

👉 핵심 포인트는 이거예요:

“컨테이너 실행과 운영” 에만 집중한 Low-level 엔진


🧱 containerd는 어디서 쓰일까?

실제로 containerd는 다양한 곳에서 기본 런타임으로 쓰입니다.

플랫폼/도구 containerd 사용 여부
Docker (20.10+) ✅ 내부적으로 containerd 사용
Kubernetes ✅ CRI 통해 containerd 런타임 사용 가능
k3s / microk8s ✅ 기본 런타임으로 containerd
nerdctl ✅ containerd 전용 Docker 대체 CLI

🔄 Docker와 containerd 관계 (제일 많이 헷갈리는 부분)

많은 사람이 “Docker = 컨테이너 런타임”이라고 생각하지만, 실제로는 구조가 이렇게 됩니다.

✅ Docker는 “고수준 도구(High-level)”

  • 이미지 빌드 (docker build)
  • 네트워크/볼륨
  • Dockerfile 기반 개발 경험 제공
  • 사용하기 편한 UX 제공

✅ containerd는 “저수준 엔진(Low-level)”

  • 실제로 컨테이너를 실행하고 관리하는 역할

🧩 구조로 보면 이렇게 이해하면 끝

Docker CLI
↓ 
Docker Engine
↓ 
containerd
↓ 
runc (실제로 리눅스 컨테이너 프로세스를 실행)

✅ 결론:

Docker는 사용자 편의 도구(껍데기)
containerd는 컨테이너 실행 엔진(본체)


⚙️ containerd 직접 설치 (Ubuntu)

Docker 없이도 containerd만 설치해서 컨테이너를 운영할 수 있습니다.

sudo apt update sudo apt install -y containerd
 

기본 설정 파일 생성:

sudo mkdir -p /etc/containerd 
containerd config default | sudo tee /etc/containerd/config.toml 
sudo systemctl restart containerd

🧪 containerd 실사용 예시 (nerdctl)

containerd를 직접 쓰려면 nerdctl이 거의 필수입니다.
(느낌이 “도커 클론 CLI”라고 보면 됩니다)

예시: nginx 띄우기

nerdctl run -d -p 80:80 nginx

상태 확인

nerdctl ps

이미지 목록 확인

nerdctl images

✅ 핵심:

Docker 없이도 containerd + nerdctl로 컨테이너 운영이 가능하다


✅ containerd 요약 (포스팅용 결론 표)

항목 설명
소속 CNCF 프로젝트
주요 기능 컨테이너 실행/이미지 관리
Docker와 관계 Docker 내부 엔진으로 사용됨
Kubernetes에서 containerd를 직접 런타임으로 사용 가능
장점 경량/빠름/표준(OCI) 준수/안정적

💡 왜 containerd가 중요해졌을까?

  • Docker는 개발 경험(빌드/UX)에 강함
  • Kubernetes는 런타임 표준(CRI) 기반으로 더 가볍고 범용적인 엔진을 선호
  • 그래서 “K8s = containerd” 흐름이 자연스러워졌습니다.

✅ 특히 운영 관점에서는:

Docker의 많은 기능이 필요 없는 서버 환경에서는
containerd가 더 “딱 필요한 만큼만” 제공해서 효율적입니다.

 

Git LFS (Large File Storage)
“Git은 포인터만 관리하고, 실제 대용량 파일은 따로 보관하자”라는 전략입니다.

핵심 아이디어

  • Git 레포에는 가짜 파일(포인터) 만 저장
  • 실제 대용량 파일은 LFS 스토리지에 저장
  • checkout 시에만 필요한 파일을 내려받음

즉,

Git의 장점은 유지하고
단점만 우회하는 방식 입니다.


Git LFS는 내부적으로 이렇게 동작한다

[Git Commit]
   |
   |-- model.pt (❌ 실제 파일 아님)
   |-- model.pt.lfs (✅ 포인터)
           |
           v
      [LFS Storage]
      실제 model.pt (수백 MB~수 GB)​
 

Git이 보는 것은 단순한 텍스트 포인터입니다.

version https://git-lfs.github.com/spec/v1
oid sha256:abc123...
size 1048576000
👉 Git 입장에서는 아주 가벼운 텍스트 파일입니다.

Git LFS 설치와 기본 설정

1️⃣ Git LFS 설치

# Ubuntu / Debian
sudo apt install git-lfs

# macOS
brew install git-lfs

# 초기화
git lfs install

이건 시스템당 한 번만 하면 됩니다.


2️⃣ LFS로 관리할 파일 타입 지정

예를 들어 AI 프로젝트라면 보통 이런 파일들입니다.

git lfs track "*.pt"
git lfs track "*.ckpt"
git lfs track "*.onnx"
git lfs track "*.parquet"
git lfs track "*.zip"
그러면 .gitattributes 파일이 생깁니다.
*.pt filter=lfs diff=lfs merge=lfs -text
👉 이 파일도 반드시 Git에 커밋해야 합니다.

3️⃣ 평소와 똑같이 Git 사용

이게 가장 중요한 포인트입니다.

git add model.pt
git commit -m "Add trained model"
git push
 

사용법은 완전히 동일합니다.
차이는 내부 처리 방식뿐입니다.


AI 프로젝트에서의 현실적인 운영 패턴

권장 구조 예시

project/
├── src/                 # 코드 (Git)
├── configs/             # 설정 (Git)
├── scripts/             # 학습/배포 스크립트 (Git)
├── models/              # 모델 파일 (Git LFS)
│   ├── resnet50.pt
│   └── llama.ckpt
├── data/
│   └── samples/         # 소형 샘플만 Git
└── README.md
 

자주 쓰는 전략

  • 코드, 설정, 문서 → Git
  • 모델, 대용량 데이터 → Git LFS
  • 초대형 원본 데이터 → S3 / GCS / NAS + 링크

Git LFS는 “모든 걸 다 넣자”가 아니라
“필요한 것만 버전 관리” 하는 도구입니다.


Git LFS 사용 시 반드시 알아야 할 주의점

1️⃣ 용량 제한

대부분의 Git 호스팅 서비스는

  • LFS 저장 용량
  • 월별 트래픽

에 제한이 있습니다.

👉 대형 모델을 자주 push/pull 하면
과금 또는 차단이 발생할 수 있습니다.


2️⃣ 이미 커밋된 대용량 파일은 자동으로 LFS로 안 바뀐다

이건 정말 많이 하는 실수입니다.

❌ 이미 Git에 들어간 대용량 파일
❌ git lfs track 해도 과거 커밋은 그대로
이 경우에는
  • git filter-repo
  • git lfs migrate

같은 히스토리 재작성이 필요합니다.
(팀 프로젝트에서는 특히 신중)


3️⃣ LFS 없는 환경에서는 파일이 깨져 보인다

Git LFS가 설치되지 않은 환경에서 clone 하면

  • 실제 파일 ❌
  • 포인터 텍스트 ✅

만 보입니다.

👉 CI 서버, 배포 서버에도 Git LFS 설치 필수

1. Git Subtree란?

Git Subtree는 외부 저장소의 소스 코드를 통째로 복사해서 내 프로젝트의 히스토리(History)에 합쳐버리는 방식입니다.

Submodule이 "저쪽에 있는 코드를 참조해(Link)"라고 가리키는 '바로 가기 아이콘'이라면, Subtree는 '파일을 실제로 복사해서 내 폴더에 붙여넣기'하는 것과 같습니다. 하지만 단순 복사/붙여넣기와 다른 점은, 원본 저장소와의 연결 고리를 유지하고 있어서 원본이 업데이트되면 내 프로젝트에도 쉽게 반영할 수 있다는 점입니다.

🖼️ 비유로 이해하기: 밀키트 배송

  • Submodule: 요리법에 "A 마트에 가서 파스타 소스를 사오세요"라고 적혀 있습니다. 요리할 때마다(Clone 할 때마다) 마트에 가서 사 와야 합니다.
  • Subtree: 밀키트입니다. 이미 내 냉장고 안에 파스타 소스가 들어와 있습니다. 그냥 뜯어서 요리하면 됩니다. 마트에 갈 필요가 없죠!

2. 왜 Subtree가 더 편한가요? (장점)

가장 큰 장점은 "협업하는 동료들이 행복해진다"는 것입니다.

  1. Clone이 쉽다: 동료들은 우리 프로젝트를 git clone만 하면 됩니다. --recursive 옵션이나 submodule update 같은 추가 명령어를 몰라도, 모든 코드가 이미 들어와 있습니다.
  2. 관리가 편하다: .gitmodules 같은 별도의 설정 파일을 관리할 필요가 없습니다.
  3. 히스토리 관리: 외부 라이브러리의 수정 내역도 우리 프로젝트의 커밋 로그(Git Log)에 함께 남습니다. (단점이 될 수도 있지만, 추적하기엔 좋습니다.)

3. 실전 사용법 (명령어 3개만 기억하세요)

사용법은 Submodule보다 살짝 길지만, 논리는 더 단순합니다.

① 외부 저장소 추가하기 (add)

먼저 remote를 등록하고, Subtree로 가져옵니다.

 
# 1. 원격 저장소 별명 붙이기 (예: 'lib-remote')
git remote add lib-remote https://github.com/username/awesome-lib.git

# 2. 내 프로젝트의 'libs/awesome' 폴더에 코드 가져오기
# --prefix: 코드가 들어갈 폴더 경로
# --squash: 지저분한 커밋 내역 합치기 (추천!)
git subtree add --prefix=libs/awesome lib-remote main --squash

이제 libs/awesome 폴더에 코드가 생겼고, 커밋도 하나 생성됩니다.

② 외부 업데이트 반영하기 (pull)

원본 라이브러리가 업데이트되었다면? 당겨오면 됩니다.

 
git subtree pull --prefix=libs/awesome lib-remote main --squash

③ 내가 수정한 것 원본에 기여하기 (push) - 고급

내가 libs/awesome 안의 코드를 수정했고, 이걸 원본 저장소(lib-remote)에도 반영해주고 싶다면?

 
git subtree push --prefix=libs/awesome lib-remote main

(참고: Subtree Push는 연산 비용이 비싸서 시간이 좀 걸릴 수 있습니다.)


4. Submodule vs Subtree 한판 승부 🥊

비교 항목 Git Submodule (서브모듈) Git Subtree (서브트리)
개념 참조(Link)만 함 코드를 복사(Copy)해서 통합
Git Clone 시 --recursive 필요 (귀찮음) 그냥 clone 하면 끝 (편함)
Git 폴더 관리 .gitmodules 파일 필요 별도 파일 필요 없음
소스 코드 내 프로젝트 용량 차지 안 함 내 프로젝트 용량에 포함됨
사용 난이도 상 (팀원 교육 필요) 하 (팀원은 몰라도 됨)
업스트림 기여 비교적 쉬움 push 명령어가 느리고 복잡함

5. 결론: 언제 무엇을 쓸까요?

✅ Git Subtree를 강력 추천하는 경우:

  • "팀원들이 Git을 잘 몰라요": 팀원들에게 복잡한 Submodule 사용법을 가르치고 싶지 않을 때.
  • "일단 편한 게 최고": 외부 코드를 내 프로젝트의 일부처럼 편하게 수정하고 관리하고 싶을 때.
  • 배포(Deployment) 편의성: Netlify나 Vercel 같은 곳에 배포할 때, Submodule 권한 설정 때문에 골치 아픈 적이 있다면 Subtree가 답입니다.

✅ Git Submodule이 나은 경우:

  • 가져와야 할 외부 프로젝트가 수 기가바이트(GB) 단위로 엄청 클 때 (Subtree로 가져오면 내 저장소도 같이 무거워짐).
  • 외부 라이브러리를 절대 수정할 일이 없고, 버전 관리만 엄격하게 하고 싶을 때.

1. Git Submodule이란?

간단히 말해 "하나의 Git 저장소(부모) 안에 또 다른 Git 저장소(자식)를 폴더 형태로 포함시키는 기능"입니다.

보통 우리가 외부 라이브러리를 쓸 때는 npm install이나 pip install 같은 패키지 매니저를 쓰죠? 하지만 내가 직접 수시로 수정해야 하는 공통 코드나, 비공개 라이브러리라면 패키지로 만들어 배포하는 과정이 번거로울 수 있습니다. 이때 Submodule을 사용하면 소스 코드 자체를 내 프로젝트의 하위 폴더로 가져와서 관리할 수 있습니다.

🖼️ 그림으로 이해하기: 마트료시카 인형

 

가장 쉬운 비유는 '러시아 인형(마트료시카)'입니다. 큰 인형(메인 프로젝트)을 열었더니 그 안에 작은 인형(서브모듈 프로젝트)이 들어있는 것과 같습니다.

하지만 중요한 특징이 하나 있습니다.

메인 프로젝트는 서브모듈의 "특정 시점(Commit ID)"만을 가리키고 있습니다.

즉, 서브모듈 폴더는 자동으로 최신 상태가 되는 게 아니라, "내가 콕 집어둔 그 버전"으로 고정되어 있다는 점이 핵심입니다.


2. 언제 사용하나요? (Use Cases)

  1. 공통 라이브러리 공유: 회사 내 여러 프로젝트에서 공통으로 쓰는 '인증 모듈'이나 '디자인 시스템' 코드를 공유할 때.
  2. 민감한 설정 파일 분리: 프로젝트 코드는 공개(Public)하고 싶지만, 비밀번호나 API 키가 담긴 설정 파일은 비공개(Private) 저장소로 따로 관리하고 싶을 때.
  3. 대규모 프로젝트 분할: 서비스가 너무 커져서, 특정 기능(예: 결제 모듈)만 뚝 떼어내서 별도로 관리하고 싶을 때.

3. 핵심 명령어 3가지 (찍먹 해보기)

① 서브모듈 추가하기 (git submodule add)

가져오고 싶은 저장소의 URL을 입력하면 내 프로젝트에 폴더가 생깁니다.

Bash
 
git submodule add https://github.com/username/awesome-library.git my-libs/awesome

이렇게 하면 .gitmodules라는 설정 파일이 생기며 연결 정보가 저장됩니다.

② 서브모듈이 포함된 프로젝트 받아오기 (clone)

가장 많이 하는 실수! 그냥 git clone만 하면 서브모듈 폴더가 텅 빈 채로 받아집니다. 꼭 --recursive 옵션을 붙여야 합니다.

Bash
 
# 처음 받을 때 한방에 받기
git clone --recursive https://github.com/my-project/main-app.git

# 이미 clone 했는데 서브모듈이 비어있다면?
git submodule update --init --recursive

③ 서브모듈 업데이트하기

서브모듈 저장소에 새로운 업데이트가 생겼다면, 이를 당겨와야 합니다.

Bash
 
git submodule update --remote

4. 치명적인 단점 (주의사항 ⚠️)

Submodule은 강력하지만, 개발자들 사이에서 "악마의 기능"이라고 불리기도 합니다. 그 이유는 관리가 까다롭기 때문입니다.

  • Detached HEAD의 늪: 서브모듈 폴더로 들어가서 작업하다 보면 자주 Detached HEAD 상태(특정 커밋만 보고 있는 상태)가 됩니다. 여기서 무심코 코드를 수정하고 커밋하면 나중에 작업 내역을 잃어버리기 쉽습니다.
  • "부모님, 저 업데이트 안 됐어요": 서브모듈 코드를 수정해서 Push 했습니다. 그런데 메인 프로젝트에서 서브모듈의 버전을 갱신해주지 않으면(메인 프로젝트 쪽에서 Commit/Push 안 함), 팀원들은 옛날 서브모듈 코드를 계속 보게 됩니다.
  • 귀찮은 Clone: 신규 입사자가 들어와서 프로젝트를 받았는데, --recursive를 빼먹어서 "어? 코드가 없는데요?"라고 묻는 상황이 빈번합니다.

5. 결론: 쓸까 말까?

이런 분들에게 추천 👍 비추천 (다른 방법 찾으세요) 👎
버전 관리가 매우 중요함: "무조건 이 특정 버전의 라이브러리만 써야 해!" 단순 편의성: "그냥 최신 코드 계속 가져다 쓰고 싶은데..."
비공개 코드 공유: Private Repo를 안전하게 공유해야 할 때. 빠른 협업: 여러 사람이 동시에 서브모듈 코드를 마구 수정해야 할 때.

💡 대안은 없나요?

  • 단순히 코드를 합치고 싶다면 👉 Git Subtree (사용법이 조금 더 쉽습니다)
  • 라이브러리 관리가 목적이라면 👉 npm, pip 같은 패키지 매니저 (가장 권장됨)
  • 프로젝트 간 의존성이 너무 높다면 👉 Monorepo(모노 레포) 도구 도입 고려

개발 프로젝트를 진행하다 보면 "코드를 어디에 어떻게 담을 것인가?"에 대한 고민을 하게 됩니다. 단순히 폴더를 나누는 것을 넘어, 프로젝트의 운명을 결정짓는 구조적인 선택이죠. 오늘은 이 두 가지 방식의 차이를 '집'에 비유하여 아주 쉽게 설명해 드릴게요.

1. 레포지토리(Repository)가 뭔가요?

먼저 레포(Repo)는 'Repository(저장소)'의 줄임말입니다. 깃허브(GitHub)에 올리는 프로젝트 폴더 하나하나를 생각하시면 됩니다. 즉, "우리 프로젝트의 코드가 담긴 상자"입니다.


2. 멀티 레포 (Multi-Repo): "각자 따로 사는 단독 주택 단지" 🏘️

멀티 레포는 가장 전통적이고 일반적인 방식입니다. 프로젝트(애플리케이션)마다 별도의 저장소를 만드는 것이죠.

  • 상황: 쇼핑몰을 만든다고 가정해 봅시다.
    • 유저용 웹사이트 레포지토리 1개
    • 관리자용 페이지 레포지토리 1개
    • 서버(API) 레포지토리 1개

이렇게 각각 독립된 3개의 저장소를 만들어 관리하는 방식입니다.

장점 👍

  • 자유로움: 각 프로젝트가 서로 영향을 주지 않습니다. 유저 웹사이트가 터져도 관리자 페이지 코드는 안전하죠.
  • 가벼움: 내가 맡은 프로젝트만 다운로드(Clone) 받으면 되니 속도가 빠릅니다.
  • 권한 관리: A팀에게는 서버 코드만 보여주고, B팀에게는 웹 코드만 보여주는 식의 관리가 쉽습니다.

단점 👎

  • 코드 중복 (복붙의 유혹): 유저 웹과 관리자 웹에서 똑같은 '버튼 디자인'을 쓰고 싶다면? 코드를 복사해서 붙여넣어야 합니다. (수정할 때도 두 번 수정해야 하죠!)
  • 버전 관리의 어려움: 각 프로젝트가 사용하는 라이브러리 버전이 제각각일 수 있어 관리가 복잡해집니다.

3. 모노 레포 (Mono-Repo): "모두가 함께 사는 거대 아파트" 🏢

모노 레포는 하나의 거대한 저장소 안에 여러 프로젝트를 몽땅 넣어서 관리하는 방식입니다. Google, Facebook 같은 빅테크 기업들이 주로 사용하며 유행하기 시작했습니다.

  • 상황: 아까 그 쇼핑몰 프로젝트를 다시 볼까요?
    • 쇼핑몰 통합 레포지토리 1개
      • ㄴ 📂 유저용 웹사이트 폴더
      • ㄴ 📂 관리자용 페이지 폴더
      • ㄴ 📂 서버(API) 폴더
      • ㄴ 📂 공통 UI 라이브러리 (모두가 같이 쓰는 코드)

장점 👍

  • 코드 공유가 쉽다: 공통으로 쓰는 코드(예: 버튼, 로그인 함수)를 한 곳에 두고 가져다 쓰면 됩니다. 한 번만 수정하면 모든 곳에 적용되죠!
  • 통일성: 모든 프로젝트의 코딩 스타일(Lint)이나 설정을 한 번에 맞출 수 있습니다.
  • 한눈에 파악: 전체 프로젝트의 흐름을 한곳에서 볼 수 있어 이슈 추적이 쉽습니다.

단점 👎

  • 무거움: 프로젝트가 커지면 저장소 용량이 어마어마해집니다. (git clone 받는 데 한 세월...)
  • 설정의 복잡함: 서로 다른 프로젝트를 한 집에서 잘 살게 하려다 보니, 빌드 도구(Webpack, Turborepo 등) 설정이 매우 까다롭습니다.
  • 나비 효과: 공통 코드를 잘못 수정하면, 연결된 모든 프로젝트가 동시에 에러를 뿜을 수 있습니다.

⚡ 한눈에 비교하기

구분 멀티 레포 (Multi-Repo) 모노 레포 (Mono-Repo)
비유 여러 채의 단독 주택 🏘️ 하나의 거대한 아파트 🏢
저장소 수 프로젝트 개수만큼 딱 1개
코드 공유 어렵다 (복사 붙여넣기) 쉽다 (참조하여 사용)
설정 난이도 쉬움 (각자 알아서) 어려움 (도구 필요)
추천 대상 서로 관련 없는 프로젝트들 서로 긴밀하게 연관된 프로젝트들

🤔 초보자인 저는 무엇을 선택해야 할까요?

처음 시작하신다면 멀티 레포(Multi-Repo)가 마음 편합니다.

  1. 멀티 레포로 시작하세요: 일단 프로젝트별로 저장소를 따로 만들어 개발에 집중하세요. 설정하느라 시간을 뺏기지 않습니다.
  2. 필요할 때 넘어가세요: 프로젝트가 늘어나고, "아... 이 코드 저쪽 프로젝트에도 또 써야 하는데?" 라는 귀찮음이 폭발할 때, 그때 모노 레포 도입을 고려해도 늦지 않습니다.

모노 레포를 돕는 도구들(Yarn Workspaces, Nx, Turborepo)은 나중에 천천히 배워보세요!


요약하자면:

  • 각자 독립적으로 빠르게 달리고 싶다? 👉 멀티 레포
  • 우리는 한 팀! 코드를 공유하며 효율을 높이고 싶다? 👉 모노 레포

이 글이 여러분의 프로젝트 구조를 잡는 데 도움이 되었기를 바랍니다! 😊

+ Recent posts