TL;DR

  • 컨테이너화된 애플리케이션은 리눅스에서 네이티브 프로세스로 실행되며, 컨테이너 이미지에 리눅스 배포판이 반드시 필요한 것은 아님.
  • servertje라는 간단한 Go HTTP 서버를 정적 리눅스 바이너리로 컴파일하고, 바이너리 하나만 포함하는 FROM scratch 이미지로 구성함.
  • 이미지 파일 시스템에는 /servertje 하나만 있으며, 실행 중인 컨테이너의 서버도 호스트에서 일반 리눅스 프로세스로 확인 가능함.
  • 컨테이너 이미지는 소프트웨어와 의존성을 패키징하는 방식이며, 프로세스 격리는 리눅스 커널 기능의 조합으로 이뤄짐.
  • 많은 이미지가 리눅스 배포판을 포함하는 이유는 동적 링커, 시간대 정보, 루트 인증 기관 인증서 등 애플리케이션 의존 파일을 제공하기 때문임.

최소 컨테이너 이미지

  • 모든 방문자에게 “Hello world”를 반환하고 8080 포트에서 HTTP 요청을 받는 작은 Go 프로그램을 작성함.
  • go mod init servertje로 모듈을 초기화한 뒤 GOOS=linux CGO_ENABLED=0 go build -o servertje . 명령으로 리눅스 바이너리 servertje를 생성함. 이름은 네덜란드어로 “작은 서버”라는 뜻임.
  • FROM scratch를 기반으로 한 Dockerfile에서 servertje만 /servertje에 실행 권한과 함께 복사하고, 이를 진입점으로 지정함.
  • docker build . -t dockerized-servertje로 이미지를 빌드하고 docker run --rm -p 8080:8080 dockerized-servertje로 HTTP 포트를 열어 실행함.
  • 실행 후 curl localhost:8080을 호출하면 “Hello world” 응답을 받으며, 브라우저에서 http://localhost:8080에 접속해 확인할 수도 있음.

이미지와 실행 중인 컨테이너 살펴보기

  • 생성한 컨테이너 이미지의 파일 시스템에는 /servertje라는 파일 하나만 포함됨. 이미지를 내보내 레이어 내용을 확인하거나 dive 같은 도구를 사용해 검증 가능함.
  • 따라서 작은 HTTP 서버와 함께 리눅스 배포판을 이미지에 넣지 않음.
  • 컨테이너 실행 중 호스트에서 ps -C servertje -o pid,user,cmd를 실행하면 /servertje와 해당 프로세스 식별자(PID)가 표시됨.
  • 단, 컨테이너가 리눅스 가상 머신 안에서 실행 중이라면 해당 명령을 호스트가 아니라 가상 머신 안에서 실행해야 함.

이것이 의미하는 바

  • 컨테이너 이미지는 소프트웨어와 그 의존성을 함께 패키징하는 방식임. 의존성이 없는 바이너리라면 바이너리만 패키징해도 됨.
  • 컨테이너 안에서 명령을 실행하면 컨테이너화된 프로세스가 실행됨. 가상 머신 전체가 작동하는 것처럼 보일 정도로 격리되지만, 실제 격리는 리눅스 커널 기능의 조합으로 이뤄짐.
  • 격리 기능은 향후 별도 글에서 다룰 예정이며, 자세한 내용은 runc의 컨테이너 명세에서 확인 가능함.

덧붙임: 컨테이너 이미지에 리눅스 배포판이 자주 포함되는 이유

  • 많은 애플리케이션이 리눅스 배포판에 흔히 포함되는 파일에 의존하므로, 배포판을 이미지에 넣는 것이 편리함.
  • 필요한 파일에는 동적 링커, 시간대 정보, 루트 인증 기관 인증서 등이 포함될 수 있음.
  • servertje 같은 간단한 Go 프로그램은 이런 파일 없이도 실행되지만, 일반적인 애플리케이션에는 FROM scratch 이미지보다 더 많은 구성 요소가 필요할 수 있음.

컨테이너와 운영체제

  • 실용적인 관점에서 컨테이너는 리눅스 기반 기술임. 리눅스를 사용하지 않는 환경에서는 선택한 컨테이너 도구, 대개 Docker Desktop이 백그라운드에서 리눅스 가상 머신을 시작하고 그 안에서 컨테이너를 실행함.
  • 윈도 컨테이너도 존재하지만, 실제 사용하는 사례는 들어본 적이 없음.