인프런워밍업클럽4기 devops - [미션5] 컨테이너 이미지 사례 실습

인프런워밍업클럽4기 devops - [미션5] 컨테이너 이미지 사례 실습

도커 (Docker)

 [쿠버네티스 어나더 클래스] 수강생 분들께서는 cicd-server에서 작업하시면 됩니다.

image 

사전 준비사항

# 도커 파일 및 App 소스 다운로드
curl -O https://raw.githubusercontent.com/k8s-1pro/install/main/ground/etc/docker/Dockerfile
curl -O https://raw.githubusercontent.com/k8s-1pro/install/main/ground/etc/docker/hello.js
[root@cicd-server ~]# ls
Dockerfile  hello.js

image

업로드 실습을 해보기 위해서는 본인의 도커 허브 Username 필요

<전체 실습 명령어>

1. docker build -t tjdtjq91/hello:1.0.0 .
2. docker image list
3. docker tag tjdtjq91/hello:1.0.0 tjdtjq91/hello:2.0.0
4-1. docker login -u 1pro
4-2. docker push tjdtjq91/hello:1.0.0
5. docker rmi tjdtjq91/hello:1.0.0
6. docker pull tjdtjq91/hello:1.0.0
7. docker save -o file.tar tjdtjq91/hello:1.0.0
8. docker load -i file.tar

<실습 시작>

​1. 빌드

image

2. 이미지 리스트 조회

image

3. 태그 변경

image

4-1. 로그인

image

4-2. 이미지 업로드

image

5. 이미지 삭제

image

6. 이미지 다운로드

image 

7. 이미지 -> 파일로 변환

image

이미지 삭제

image

8. 파일 -> 이미지로 변환

image

컨테이너디 (Containerd)

[쿠버네티스 어나더 클래스] 수강생 분들께서는 k8s-master에서 작업하시면 됩니다.

image

<전체 실습 명령어>

1. ctr ns list
2. ctr -n k8s.io image list
3. ctr images pull docker.io/tjdtjq91/hello:1.0.0
4. ctr images tag docker.io/tjdtjq91/hello:1.0.0 docker.io/tjdtjq91/hello:2.0.0
5. ctr image push docker.io/tjdtjq91/hello:2.0.0 --user 1pro
6. ctr -n default image export file.tar docker.io/tjdtjq91/hello:1.0.0
7. ctr -n k8s.io image import file.tar
8. ctr -n k8s.io image remove docker.io/tjdtjq91/hello:1.0.0​

<실습 시작>

1. 네임스페이스 조회

여기서 이미지에 대한 namespace의 개념은 중요합니다. 컨테이너 런타임으로 containerd를 쓰고 있는 쿠버네티스 입장에서 이 [k8s.io] 네임스페이스에 있는 이미지들을 사용하거든요.

(사용자가 네임스페이스 지정없이 수동으로 이미지를 다운 받았다면 default라는 namespace에 이미지가 만들어지게 되고 그럴 경우 쿠버네티스에서 사용할 수 없게 됩니다)

image

2. 특정 네임스페이스 내 이미지 조회

image

3. 다운로드 및 이미지 확인 (이미지는 default라는 네임스페이스에 다운 받아집니다.)

image

4. 태그 변경

image

5. 업로드

image

6. 이미지 (namespace : default) -> 파일로 변환

image

7. 파일 -> 이미지로 변환 (namespace : k8s.io)image 

8. 삭제 (namespace : k8s.io)

image

같은 이미지를 도커에서 받았을 때와 쿠버네티스에서 받았을 때 사이즈가 다른 이유

먼저 Docker Hub에서 이미지 사이즈를 보겠습니다.

Docker Hub (248.26 MB)

https://hub.docker.com/r/1pro/api-tester/tags

image

그리고 이걸 Docker와 Containerd에서 다운로드 받고 이미지 Size를 확인해 볼께요.

Docker (490MB)

image

Containerd (248.3 MiB)

image

 

※ MB : 10의 거듭제곱을 기준. 1MB는 정확히 1,000,000바이트(10^6 바이트)

※ MiB : 2의 거듭제곱을 기준. 1MiB는 정확히 1,048,576바이트(2^20 바이트)

 

정리하면 이렇게 됩니다.

image


1. Container Image를 만들 때 플랫폼(amd64, arm64)을 고려해야 되는데, Docker에서는 amd64를 받았고, Kuberentes에서 arm64를 받아서 이미지 크기가 달라졌을 것이다.

Docker Hub에 올라간 이미지는 amd64 [win, linux]과 arm64 [mac m series]을 지원합니다.)

image

Docker (amd64가 보임)

아키텍처 확인

#docker image inspect 1pro/api-tester:latest


[
    {
        "Id": "sha256:320d6bd226c920f6876939f87cf5d81ea00de92d4e20d226ca73562c1a1a88f6",
        "RepoTags": [
            "1pro/api-tester:latest"
        ],
        "RepoDigests": [
            "1pro/api-tester@sha256:189625384d2f2856399f77b6212b6cfc503931e8b325fc1388e23c8a69f3f221"
        ],
        "Parent": "",
        "Comment": "buildkit.dockerfile.v0",
        "Created": "2023-11-25T07:01:31.85143467Z",
        "Container": "",
        "ContainerConfig": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": null,
            "Cmd": null,
            "Image": "",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": null
        },
        "DockerVersion": "",
        "Author": "",
        "Config": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "ExposedPorts": {
                "8080/tcp": {}
            },
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/java/openjdk-17/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "JAVA_HOME=/usr/java/openjdk-17",
                "LANG=C.UTF-8",
                "JAVA_VERSION=17.0.2"
            ],
            "Cmd": null,
            "Image": "",
            "Volumes": null,
            "WorkingDir": "/usr/src/myapp",
            "Entrypoint": [
                "java",
                "-Dspring.profiles.active=${spring_profiles_active}",
                "-Dapplication.role=${application_role}",
                "-Dpostgresql.filepath=${postgresql_filepath}",
                "-jar",
                "/usr/src/myapp/app.jar"
            ],
            "OnBuild": null,
            "Labels": null
        },
        "Architecture": "arm64",
        "Os": "linux",
        "Size": 520321200,
        "VirtualSize": 520321200,
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/d57d7d8b06ef8b48164afce5802afad7ac35ba9f13c999c2a38c5bd22462758c/diff:/var/lib/docker/overlay2/660cd2a25be940fb691e195edb1946f135aaff00bd81a59b3978e44802af22e8/diff:/var/lib/docker/overlay2/1f0de966874deedcbc4a04593f0f0ad5486c762c742057c2eb1fb5f9af71a747/diff:/var/lib/docker/overlay2/962edd389f5e5ee4343d585cbcf7c41df43e9d5d77c8d91a300b4ef35f3ace43/diff",
                "MergedDir": "/var/lib/docker/overlay2/475742e53f8bd9ee4856c12baabb3d11b3d1ee109a69c8202a1a8142a7e17e70/merged",
                "UpperDir": "/var/lib/docker/overlay2/475742e53f8bd9ee4856c12baabb3d11b3d1ee109a69c8202a1a8142a7e17e70/diff",
                "WorkDir": "/var/lib/docker/overlay2/475742e53f8bd9ee4856c12baabb3d11b3d1ee109a69c8202a1a8142a7e17e70/work"
            },
            "Name": "overlay2"
        },
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:b0d4c4485e7e7fdd7cb6a595a4845c47e39fdfac598fde0fbd8f327423ceb5a0",
                "sha256:03ee828ef0e49649de4f809d41b8cb2c29e193269f809d3de2a5b6021bc44cec",
                "sha256:e017d39c755a5133aebbe26075334984cb9a3e18ff9a6790123072fd1f2d3c53",
                "sha256:34456869abeaa10fe2990423a1b40249e43ae00bc672ff90b5f62b250b271884",
                "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
            ]
        },
        "Metadata": {
            "LastTagTime": "0001-01-01T00:00:00Z"
        }
    }
]

Containerd (amd64, arm64)가 보임

image

REF                              TYPE                                    DIGEST                                                                  SIZE      PLATFORMS                               LABELS 
docker.io/1pro/api-tester:latest application/vnd.oci.image.index.v1+json sha256:189625384d2f2856399f77b6212b6cfc503931e8b325fc1388e23c8a69f3f221 247.8 MiB linux/amd64,linux/arm64,unknown/unknown -  

분석 :

해당 Image는 Mac M 시리즈나 Window 유저가 모두 다운받을 수 있도록 만들었습니다. 그래서 한 Image에 amd64, arm64 버전이 각각 있는 거고요.

현재 저는 Linux (adm64)에서 이미지를 다운로드를 실행 했고, Docker에서는 amd64 이미지를 받았고, Containerd에서는 amd64와 arm64 둘다 있는 걸로 보아 두 버전이 모두 사용가능한 이미지가 다운 받아진 것 처럼 느껴질 수 있습니다.

그럼 두 버전을 모두 받은 Containerd 이미지가 더 커야 할 텐데, 오히려 이미지가 더 작아요.

결론 :

Containerd에서 PLATFORMS는 그냥 해당 이미지가 어떤 플랫폼을 지원하는지 정보를 보여준 것이지, 실제 여러 플랫폼에서 실행 가능한 이미지를 받았다는 의미는 아닙니다.

내가 amd64 환경에서 다운로드 했다면, Containerd는 알아서 amd64의 이미지를 다운 받아와요.

그렇기 때문에 Docker나 Containerd 에서는 같은 amd64 이미지를 다운받았고, 결국 Size도 같아야 합니다.

질문에서 처럼 만약 Docker는 amd64였고, Containerd는 arm64 환경에서 설치가 됐다면, 해당 이미지의 사이즈는 달라졌을 수 있으나, 현재는 그게 정답은 아닌 것 같네요.

 


2. Container 이미지는 각각의 Layer로 구성돼 있는데, Docker에서 다운 받을 때는 전체 Layer를 받았고, Kubernetes에는 기존 이미지에 이미 존재하는 Layer가 있기 때문에 새로 받은 이미지의 Size가 작게 조회 됐을 것이다.

 

아래 테스트를 해보겠습니다.

image

Docker -> Containerd

보류

 


3. 쿠버네티스에는 다른 Runtime을 사용 했을 수 있고, 같은 이미지더라도 사용하는 Runtime에 따라서 이미지의 크기는 달라질 것이다.

아래 그림은 제가 강의 [컨테이너 한방정리] 에서 설명 드렸던 그림 입니다.

Docker 내부에 Containerd가 있는데, Docker는 정말 많은 기능들을 가지고 있는 고급 레벨의 Runtime이고 실질적으로 Containerd가 컨테이너 만들어 줘요.

image

그리고 쿠버네티스 입장에서는 Runtime으로 Docker나 Containerd 둘중 하나를 선택할 수가 있는데, 예전에는 주로 도커를 사용했지만, 현재는 Contaienrd가 기본으로 사용되고 있어요.

자세한 히스토리는 강의에서 말씀을 드려서 생략하고, 결론적으로 현재 쿠버네티스에서 어떤 Rumtime을 사용하고 있는지 아는 게 중요하고. 이미지 사이즈가 다른 이유는 Docker와 Kubernetes가 아닌 Docker와 Contaienrd의 차이입니다.

 

결론 :

Docker는 다른 많은 기능들을 지원해 주기 때문에 실제 이미지가 248.26MB라고 하더라도 다운 받은 이후 자신의 매타데이터 규격에 맞게 데이터들을 더 추가하고 이미지를 재구성 합니다. 그래서 490MB가 된거고요. Containerd에서 이미지를 가져왔을 때도 마찬가지로, 이미지를 재구성 하느라 Size가 커진 거예요.

반대로 Docker의 이미지를 Contaienrd로 가져가게 됐을 때, Docker에서 재구성을 하느라 커진 불필요한 메타데이터들이 그대로 들어가진 거고요.

이런 사실을 토대로 우리가 하지 말아야 할 일이 있겠습니다.

인터넷이 연결되지 않는 환경에서는 이미지들을 다운 받아서 파일 형태로 복사를 해야되요.

이때 쿠버네티스에서 Contaienrd를 사용한다면 Docker로 받은 이미지를 복사해 넣을 경우 불필요하게 이미지 사이즈가 커지게 됩니다.

 

 

출처

Docker와 Containerd 명령 실습 : https://cafe.naver.com/kubeops/137

Docker 이미지 사이즈 : https://cafe.naver.com/kubeops/158

댓글을 작성해보세요.

채널톡 아이콘