Docker 이미지 생성 기능 구현: 컨테이너를 토대로 이미지 저장 방법

본문은 Docker 클라이언트 구현 시퀀스의 일환으로, 컨테이너를 이미지로 변환하는 기능을 구현하는 방법을 다룹니다. 이 기능은 실제 Docker CLI의 commit 명령어와 유사한 동작을 수행합니다.

전체 코드는 다음 링크에서 확인 가능합니다: https://github.com/lixd/mydocker 별점 부탁드립니다

Docker 기초 원리에 대한 이해를 돕기 위한 참고 자료:

  • 내부 메커니즘: 네임스페이스, 콘그루스, 루트 파일시스템 등 Docker 핵심 구성 요소
  • 네임스페이스 기반 격리: Linux 네임스페이스 활용한 컨테이너 격리 원리
  • 리소스 제한: 콘그루스 기반 리소스 제어 및 관리
  • 파일시스템: OverlayFS를 통한 유니온 파일시스템 구조
  • 네트워크: veth pair 및 브릿지 기반 네트워크 구현 방식

개발 환경 정보:

root@mydocker:~# lsb_release -a
Distributor ID:	Ubuntu
Description:	Ubuntu 20.04.2 LTS
Release:	20.04
Codename:	focal
root@mydocker:~# uname -r
5.4.0-74-generic

주의: root 권한이 필요합니다

  1. 개요

이전 단계에서 pivotRoot와 OverlayFS 기술을 사용하여 /root/merged 디렉토리를 컨테이너 루트 파일시스템으로 설정했습니다. 이로 인해 컨테이너 내에서 발생한 모든 변경 사항은 해당 디렉토리에 반영됩니다.

따라서 mydocker commit 명령어는 해당 디렉토리를 간단히 타르 압축하여 저장하는 방식으로 구현할 수 있습니다

  1. 구현

실행 흐름

전체 패키징 프로세스는 다음과 같은 순서로 진행됩니다:

커밋 명령 처리

main_command.go 파일에서 commit 명령을 추가하고, 사용자 입력에서 이미지 이름을 추출합니다.

var commitCommand = cli.Command{
	Name:  "commit",
	Usage: "컨테이너를 이미지로 변환",
	Action: func(context *cli.Context) error {
		if len(context.Args()) < 1 {
			return fmt.Errorf("이미지 이름 누락")
		}
		imageName := context.Args().Get(0)
		saveContainerImage(imageName)
		return nil
	},
}

메인 함수에 커밋 명령을 등록합니다:

func main() {
	app := cli.NewApp()
	app.Name = "mydocker"
	app.Usage = usage

	app.Commands = []cli.Command{
		initCommand,
		runCommand,
		commitCommand,
	}
	// 기타 설정 생략
}

컨테이너 이미지 저장

commit.go 파일에 saveContainerImage 함수를 추가하여 컨테이너 파일시스템을 압축합니다.

func saveContainerImage(imageName string) {
	rootfsPath := "/root/merged"
	imageArchivePath := "/root/" + imageName + ".tar"
	fmt.Printf("이미지 저장 경로: %s\n", imageArchivePath)
	
	cmd := exec.Command("tar", "-czf", imageArchivePath, "-C", rootfsPath, ".")
	if err := cmd.Run(); err != nil {
		log.Errorf("디렉토리 압축 실패: %v", err)
	}
}
  1. 테스트

테스트 절차는 다음과 같습니다:

  1. 컨테이너 실행
  2. 파일 생성
  3. 새로운 터미널에서 컨테이너를 이미지로 변환
  4. 압축 파일 해제 후 생성된 파일 존재 여부 확인

먼저 컨테이너를 실행합니다:

root@mydocker:~/feat-commit/mydocker# ./mydocker run -it /bin/sh
{"level":"info","msg":"resConf:\u0026{ 0  }","time":"2024-01-19T16:18:24+08:00"}
{"level":"info","msg":"busybox:/root/busybox busybox.tar:/root/busybox.tar","time":"2024-01-19T16:18:24+08:00"}
{"level":"info","msg":"mount overlayfs: [/usr/bin/mount -t overlay overlay -o lowerdir=/root/busybox,upperdir=/root/upper,workdir=/root/work /root/merged]","time":"2024-01-19T16:18:24+08:00"}
{"level":"info","msg":"command all is /bin/sh","time":"2024-01-19T16:18:24+08:00"}
{"level":"info","msg":"init come on","time":"2024-01-19T16:18:24+08:00"}
{"level":"info","msg":"Current location is /root/merged","time":"2024-01-19T16:18:24+08:00"}
{"level":"info","msg":"Find path /bin/sh","time":"2024-01-19T16:18:24+08:00"}

파일을 생성합니다:

/ # echo KubeExplorer > tmp/hello.txt
/ # cat /tmp/hello.txt
KubeExplorer

다른 터미널에서 커밋 명령을 실행합니다:

root@mydocker:~/feat-commit/mydocker# ./mydocker commit myimage
이미지 저장 경로: /root/myimage.tar

/root 디렉토리 상태 확인:

root@mydocker:~# ls
busybox  busybox.tar merged  myimage.tar  upper  volume  work

압축 파일 내용 확인:

root@mydocker:~# tar -tf myimage.tar |grep hello.txt
./tmp/hello.txt
  1. 요약

본 기능은 타르 명령을 이용한 간단한 루트 파일시스템 압축만 수행하며, 복잡한 로직은 포함하지 않습니다.

이번 단계에서는 다음과 같은 핵심 내용을 정리할 수 있습니다:

  • 기본 이미지로 BusyBox를 사용해 컨테이너 환경을 구축
  • OverlayFS를 활용한 계층적 파일시스템 구조 설명
  • 볼륨 마운팅을 통한 외부 파일시스템 접근 방법
  • 컨테이너 이미지 생성 기능 구현

이번 챕터에서는 이미지 저장 및 파일시스템 원리를 기초적으로 소개했으며, 이후 다양한 확장 기능 구현에 기반이 됩니다.

[0에서 시작하는 Docker 시리즈] 지속 업데이트 중입니다. 검색창에서 【탐구 클라우드 네이티브】를 검색하여 더 많은 글을 읽을 수 있습니다.

전체 코드: https://github.com/lixd/mydocker 별점 부탁드립니다

관련 코드는 feat-volume 분기에서 확인 가능합니다. 테스트 스크립트:

# 코드 클론
git clone -b feat-commit https://github.com/lixd/mydocker.git
cd mydocker
# 의존성 설치 및 컴파일
go mod tidy
go build .
# 테스트 실행
./mydocker run -it  /bin/ls
./mydocker commit

태그: docker cli OverlayFS Tar ContainerImage

5월 23일 10:21에 게시됨