무색
기술블로그
에세이
연구
소개

무색

소프트웨어로 비즈니스의 가능성을 만듭니다. 웹·앱 개발, 음성 AI, 자동화 콘텐츠 제작까지 — 기술이 필요한 곳에 무색이 있습니다.

연락처

contact@museck.com

사업자 정보

상호: 무색

대표: 배성재

사업자등록번호: 577-58-00836

인천광역시 연수구 인천타워대로 323, 에이동 8층 801-802호 AB-132 (송도동, 송도 센트로드)

© 2026 무색. All rights reserved.
개인정보처리방침·이용약관·연락처
INCHEON, KR
DinD 사이드카 — 컨테이너 안의 컨테이너
홈랩 삽질기
2025. 12. 31.

containerd 기반 K8s에서 Docker 빌드하기: DinD 사이드카와 ArgoCD 크래시 수정기

kubernetesdocker-in-dockerargocdgitea-actionshomelab

containerd 클러스터에서 Docker 빌드가 안 된다고?

홈랩 K8s 클러스터에 Gitea Actions CI/CD 파이프라인을 올리는 작업을 하고 있었다. GitHub Actions처럼 push하면 자동으로 Docker 이미지 빌드해서 레지스트리에 올리고, ArgoCD가 배포하는 흐름이다.

그런데 막상 act_runner를 K8s Pod으로 띄우고 워크플로우를 돌려보니 docker build가 실패한다. 에러 메시지를 보니 Docker 소켓을 찾을 수 없다는 거다.

아, 맞다. 이 클러스터는 kubeadm + Cilium CNI 기반인데 컨테이너 런타임이 containerd다. Docker가 아니라 containerd니까 /var/run/docker.sock이 존재하지 않는다. K8s 1.24부터 dockershim이 제거되면서 containerd나 CRI-O를 직접 쓰는 게 표준이 됐는데, 기존에 Docker 소켓에 의존하던 CI/CD 패턴이 전부 깨지는 셈이다.

DinD 사이드카 패턴으로 해결하기

Docker 소켓이 없으면 직접 만들면 된다. Docker-in-Docker(DinD) 사이드카 패턴을 적용하기로 했다.

이 패턴의 핵심은 간단하다. Pod 안에 두 개의 컨테이너를 띄운다.

  • runner 컨테이너 — Gitea Actions 워크플로우를 실행하는 act_runner
  • DinD 사이드카 — docker:dind 이미지로 Docker 데몬을 실행하는 컨테이너

같은 Pod 안이니까 localhost로 통신할 수 있고, DinD 사이드카가 TCP 2375 포트로 Docker API를 노출한다. runner에서 DOCKER_HOST=tcp://localhost:2375를 설정하면 Docker 명령어가 사이드카의 데몬을 사용하게 된다.

마트료시카 인형처럼 컨테이너 안에 또 컨테이너가 있는 구조라서 Docker-in-Docker라 부른다.

Pod 설정 살펴보기

실제 YAML 설정을 보자. 핵심 부분만 추렸다.

containers:
  - name: runner
    image: gitea/act_runner:latest
    env:
      - name: DOCKER_HOST
        value: tcp://localhost:2375
    command: ["/bin/sh", "-c"]
    args:
      - |
        # Docker CLI 설치 (API v1.44+ 호환)
        apt-get update && apt-get install -y docker-ce-cli
        act_runner daemon

  - name: dind
    image: docker:dind
    securityContext:
      privileged: true
    env:
      - name: DOCKER_TLS_CERTDIR
        value: ""  # TLS 비활성화 (같은 Pod 내부 통신)

몇 가지 포인트를 짚어보겠다.

왜 TCP인가

처음에는 Unix 소켓 공유 방식(/var/run/docker.sock을 emptyDir로 마운트)을 시도했다. 결론부터 말하면 안 된다. containerd 기반 K8s에서는 호스트에 Docker 소켓 자체가 없고, DinD 사이드카가 생성하는 소켓 경로도 컨테이너 내부 파일시스템에 있어서 emptyDir 공유가 타이밍 이슈를 일으킨다.

TCP 방식이 훨씬 깔끔하다. 같은 Pod 안에서 localhost 통신이니까 네트워크 오버헤드도 거의 없다.

TLS 비활성화

DOCKER_TLS_CERTDIR=""로 TLS를 끄는 게 처음엔 좀 찝찝했는데, 생각해보면 같은 Pod 내부 localhost 통신이다. Pod 밖으로 나가는 트래픽이 아니니까 보안 위험은 없다. 오히려 인증서 관리 복잡도만 늘어나니 끄는 게 맞다.

docker-ce-cli 설치

act_runner 이미지에는 Docker CLI가 없다. 워크플로우에서 docker build나 docker push를 쓰려면 CLI를 따로 설치해야 한다. 여기서 주의할 점이 하나 있다. DinD 사이드카가 최신 Docker 데몬을 돌리고 있으니 CLI도 API v1.44 이상을 지원하는 버전이어야 한다. docker-ce-cli 패키지를 설치하면 된다.

privileged 컨테이너

DinD 사이드카에 privileged: true를 줘야 한다. Docker 데몬이 cgroup과 네임스페이스를 조작해야 하니까 어쩔 수 없다. 좋은 점은 privileged 권한이 DinD 컨테이너에만 국한된다는 거다. runner 컨테이너는 일반 권한으로 실행되니 공격 범위가 제한적이다.

ArgoCD repo-server copyutil 크래시

DinD 사이드카를 붙이고 CI 파이프라인이 잘 돌아가나 싶었는데, 이번엔 CD 쪽에서 문제가 터졌다. ArgoCD의 repo-server Pod이 계속 CrashLoopBackOff에 빠진다.

로그를 까보니 copyutil init 컨테이너에서 실패하고 있었다. 이 init 컨테이너는 argocd 바이너리를 공유 볼륨에 복사하는 역할인데, Helm 차트 업그레이드 과정에서 이미지 버전이 꼬인 거다.

업스트림 Helm 차트가 고쳐질 때까지 기다릴 수도 있지만, 프로덕션에서 ArgoCD가 죽어있으면 배포가 멈추니까 빨리 해결해야 했다.

Helm values로 init 컨테이너 override

Helm 차트의 init 컨테이너를 values에서 직접 재정의하는 방법으로 해결했다.

repoServer:
  initContainers:
    - name: copyutil
      image: quay.io/argoproj/argocd:v2.14.4
      command: [cp, /usr/local/bin/argocd, /var/run/argocd/argocd-cmp-server]
      volumeMounts:
        - mountPath: /var/run/argocd
          name: var-files

이미지 태그를 명시적으로 v2.14.4로 고정했다. 차트가 기본으로 넣는 이미지 대신 내가 검증한 버전을 쓰는 거다. 이렇게 하면 Helm 차트 버전이 올라가도 init 컨테이너는 안정적으로 동작한다.

이 패턴은 ArgoCD뿐 아니라 다른 Helm 차트에서도 쓸 수 있다. init 컨테이너나 사이드카가 호환성 문제를 일으킬 때 values에서 직접 override하면 업스트림 수정을 기다리지 않고 바로 고칠 수 있으니까.

삽질에서 배운 것

이번 작업에서 가장 오래 걸린 건 Unix 소켓 공유가 안 되는 걸 알아내는 과정이었다. Docker 시절 습관대로 docker.sock을 마운트하면 되겠지 하고 접근했다가 반나절을 날렸다.

K8s 컨테이너 런타임이 Docker에서 containerd로 전환되면서 이런 식으로 기존 워크플로우가 깨지는 경우가 꽤 있다. 검색하면 아직도 docker.sock 마운트를 안내하는 글이 많은데, containerd 환경에서는 통하지 않는다.

정리하면 이렇다.

  • containerd 기반 K8s에서 Docker 빌드가 필요하면 DinD 사이드카 + TCP 통신이 가장 확실한 방법이다
  • 같은 Pod 안 localhost 통신이면 TLS를 끄는 게 오히려 합리적이다
  • Helm 차트의 init 컨테이너가 크래시하면 values override로 빠르게 우회할 수 있다
  • containerd 전환 후 Docker 소켓에 의존하는 기존 가이드는 의심하고 봐야 한다

홈랩이니까 이런 삽질도 배움의 과정이라 생각하고 넘어가지만, 프로덕션에서 이런 일을 겪으면 꽤 고생할 수 있다. 클러스터 런타임이 뭔지 먼저 확인하는 습관을 들이자.

자주 묻는 질문

containerd 기반 K8s에서 docker build를 하려면 어떻게 해야 하나요?
Docker-in-Docker(DinD) 사이드카 패턴을 사용합니다. 같은 Pod 안에 docker:dind 컨테이너를 띄우고, runner 컨테이너에서 DOCKER_HOST=tcp://localhost:2375로 연결하면 Docker 소켓 없이도 빌드가 가능합니다.
DinD 사이드카에서 TLS를 비활성화해도 보안에 문제가 없나요?
같은 Pod 내부 localhost 통신이므로 네트워크 밖으로 트래픽이 나가지 않습니다. 오히려 TLS 인증서 관리 복잡도만 늘어나니 Pod 내부 통신에서는 끄는 것이 합리적입니다.
ArgoCD Helm 차트에서 init 컨테이너가 크래시할 때 해결 방법은?
Helm values에서 initContainers를 직접 override하고 검증된 이미지 태그를 명시적으로 고정하면 됩니다. 업스트림 수정을 기다리지 않고 바로 우회할 수 있는 패턴입니다.
홈랩 삽질기(3/19)
Prev

홈랩에 GitOps 파이프라인 구축하기: Gitea + ArgoCD App of Apps 패턴

Next

Cloudflare Tunnel로 홈랩을 인터넷에 공개한 이야기