DevOps/Docker & Kubernetes

[Docker] Docker Image

나죽못고나강뿐 2023. 7. 6. 00:47
📕 목차
1. 도커 허브에 공유된 이미지 사용 
2. Dockerfile
3. Container Image build
4. Docker Image Layer
5. Image Layer cache : Dockerfile Optimizing
6. 연습 문제

1. 도커 허브에 공유된 이미지 사용

 

📌 What is Docker Hub?
  • Registry : 이미지를 제공하는 저장소
  • Docker Hub : Docker에서 공식 운영하는 무료 제공 공개 Registry

 

1️⃣ web-ping 애플리케이션 이미지 내려받기

docker image pull diamol/ch03-web-ping

(web-ping 애플리케이션은 지정된 URL에 HTTP 요청을 3초마다 보내는 동작을 컨테이너 종료까지 반복한다.)

  • docker container run 명령이 가능한 이유
    • 필요한 이미지 중 로컬 환경에 없으면 이미지를 자동으로 내려 받는다.
    • SW 배포 기능이 Docker 플랫폼에 완전히 내장되었기 때문
    • Dokcer에 이미지를 내려받는 과정을 전적으로 맡긴 방법
  • docker image pull을 쓰면 Docker CLI를 통해 내려받을 이미지를 명시적으로 지정할 수 있다.

  • 내려받을 이미지 이름은 diamol/ch03-web-ping
  • 하나의 이미지가 계층적으로 쌓인 형태로 저장되어 있다.
    • docker image는 논리적으로는 하나의 대상이나, 물리적으로는 여러 개의 작은 파일로 구성된다.
    • 각각의 파일을 Image Layer라고 부른다.
    • Docker가 파일을 조립하여 컨테이너 내부 파일 시스템을 만든다.

 

2️⃣ 이미지로 컨테이너 실행

docker container run -d --name web-ping diamol/ch03-web-ping
  • -d : --detach 축양형, 백그라운드에서 동작한다
  • --name : 컨테이너 이름을 붙이고, container id 대신 사용할 수 있다.

  • 사용자 인터페이스 없이 배치 잡(batch job)처럼 동작한다.
  • 네트워크를 통해 요청을 받지도 않으므로, 포트를 외부로 공개할 필요도 없다.

 

3️⃣ 도커를 통해 수집된 애플리케이션 로그 확인

docker container logs web-ping

  • Http request에 대한 log는 RTT를 파악할 수 있어 유용하다.
  • 서버 동작 여부를 확인할 수도 있다. (ping이랑 동일)

 

4️⃣ 실행 중인 컨테이너 삭제 및 환경 변수 TARGET을 수정한 새로운 컨테이너 실행

docker rm -f web-ping
docker container run --env TARGET=google.com diamol/ch03-web-ping

  • 환경 변수(environment variable)는 OS에서 제공하는 key-value 쌍
  • 도커 컨테이너도 별도의 환경 변수를 가질 수 있다.
    • Host 환경 변수를 가져오는 것이 아니라 Docker가 부여한다.
    • Docker가 기본값을 컨테이너에 적용하고, 이 값을 애플리케이션에 적용한다.
    • 컨테이너 별로 별도의 환경 변수를 부여하는 것 또한 가능하다.
  • 컨테이너에 적용할 환경 변수 기본 값은 이미지를 생성할 때 지정한다.

 


2. Dockerfile

 

  • Dockerfile은 애플리케이션 packaging을 위한 script이다.
  • 일련의 instruction으로 구성되어 있고, 이를 실행한 결과로 docker image가 만들어진다.
  • 어떠한 애플리케이션이라도 패키징할 수 있으며, 여타 스크립트 언어들과 유사하다.

 

📌 web-ping를 구성하는 파일들

Jenkins는 왜 있냐..

const https = require("https");

const options = {
  hostname: process.env.TARGET,
  method: process.env.METHOD
};

console.log(
  "** web-ping ** Pinging: %s; method: %s; %dms intervals",
  options.hostname,
  options.method,
  process.env.INTERVAL
);

process.on("SIGINT", function() {
  process.exit();
});

let i = 1;
let start = new Date().getTime();
setInterval(() => {
  start = new Date().getTime();
  console.log("Making request number: %d; at %d", i++, start);
  var req = https.request(options, res => {
    var end = new Date().getTime();
    var duration = end - start;
    console.log(
      "Got response status: %s at %d; duration: %dms",
      res.statusCode,
      end,
      duration
    );
  });
  req.on("error", e => {
    console.error(e);
  });
  req.end();
}, process.env.INTERVAL);

애플리케이션인 js 파일은 단순히 HTTPS 요청을 보내고, RTT 값을 터미널에 뿌리는 정도의 간단한 기능을 구현하고 있다.

여기서 options가 hostname 정보를 환경 변수로 부터 가져오고 있는데, 이는 dockerfile을 확인해보면 된다.

 

FROM diamol/node

ENV TARGET="blog.sixeyed.com"
ENV METHOD="HEAD"
ENV INTERVAL="3000"

WORKDIR /web-ping
COPY app.js .

CMD ["node", "/web-ping/app.js"]
  • From
    • 모든 이미지는 다른 이미지로부터 출발한다.
    • web-ping 애플리케이션 실행을 위한 런타임인 Node.js를 시작점으로 지정한다.
  • ENV
    • 환경 변수 값 지정을 위한 인스트럭션(지침)
    • [key] = "[value]" 포맷을 따른다.
  • WORKDIR
    • 컨테이너 이미지 파일 시스템에 디렉터리를 만들고, 해당 디렉터리를 작업 디렉터리로 지정하는 인스트럭션
  • COPY
    • 로컬 파일 시스템의 파일 혹은 디렉터리를 컨테이너 이미지로 복사하는 인스트럭션
    • COPY [원본 경로] [복사 경로]
  • CMD
    • Docker가 Image로부터 Container 실행 시, 수행할 명령을 지정하는 인스트럭션
    • Node.js 런타임이 애플리케이션을 시작하도록 app.js 지정

 


3. Container Image build

 

📌 Image build를 위해 필요한 것
  • 이미지 이름 (--tag로 지정)
  • 패키징에 필요한 파일 경로 (Context)

 

1️⃣ Dockerfile script로 이미지 빌드

docker image build --tag web-ping .

  • build 에러 발생 시
    • 도커 엔진 정상 동작 여부 확인
    • 현재 작업 디렉토리 확인
    • build 명령 오타 확인
  • 파일 권한과 관련 경고 메시지가 뜬다면 윈도 환경에서 리눅스 컨테이너를 빌드하려고 한 것
    • Window docker desktop에 Linux container mode가 있어 Linux container를 빌드할 수는 있다.
    • 다만, Window의 파일 권한 설정 방법이 Linux와 달라서 이미지 파일이 모두 읽고 쓰기가 전면 허용되는 상태가 된다.

 

2️⃣ 'w'로 시작하는 태그명을 가진 이미지 목록 확인

docker image ls 'w*'

  • 정상적으로 빌드된 이미지는 도커 허브에서 내려받은 이미지와 똑같이 사용 가능하다.

 

3️⃣ 새로 빌드한 이미지로 컨테이너 실행 (전송 주기 5초로 수정)

docker container run -e TARGET=docker.com -e INTERVAL=5000 web-ping

 


4. Docker Image Layer

 

📌 Docker Image에 포함된 정보
  • 이미지는 컨테이너의 메타데이터와 파일시스템을 모아둔 것
  • 해당 Layer들이 모여서 하나의 Overlayfs를 이루고 config에 각종 메타데이터들이 들어가는 것
  • 논리적으로는 하나의 대상이지만, 물리적으로는 여러 개의 작은 파일(Layer)들을 조립해 컨테이너 내부 파일 시스템을 생성한다.
  • 자신에 대한 여러 메타데이터 정보 
    1. Image Index
      • 여러 manifest들을 모아놓은 것
      • "하나의 이미지 이름 + 태그"로 여러 architecture에 사용가능한 이미지를 만들 수 있게 하낟.
    2. Manifest
      • docker manifest inspect <image name> 으로 확인 가능
      • 이미지의 config와 layer 정보, 이미지 자체에 대한 메타 데이터 소유
      • Image와 Layer의 개념을 나누기 위해 생김
      • manifest의 digest는 모두 내용을 기반으로 해싱한 content-addressable 값
      • 따라서 내용이 같다면 서로 다른 시점에 빌드된 Layer라도 사용할 수 있다. (중복 제거)
    3. Config
      • docker inspect <image name> 으로 확인 가능
      • 이미지의 각종 메타 데이터 소유
      • json 포맷
    4. Layer
      • Image를 기반으로 Container를 만들 때, 해당 컨테이너의 File System을 구성하는 하나의 단위
      • 각각의 layer는 파일 시스템의 변경 내역을 담고 있다. (파일 또는 디렉토리의 CRUD)
      • Dockerfile의 각 커맨드가 하나의 layer이다. (단, docker 1.10 이후부터 ENV, LAVEL, ARG같이 메타데이터를 만지는 커맨드들은 layer로 쌓이지 않는다.)
      • layer는 read-only이므로 container 안에서 파일을 생성/수정해도 기존 layer에 영향이 없다. (overlayfs로 동작하기 때문)
        • config에 정의된 모든 layer들은 overlayfs의 lowerdir로 들어가고, 새로운 컨테이너는 overlayfs의 merged dir 위에서 동작
        • 따라서 컨테이너에서 파일을 수정하면 변경 내역이 담긴 upper dir을 바탕으로 새로운 layer가 생성될 뿐이다.
      • packging에 포함시킨 모든 파일 → 컨테이너 파일 시스템 형성
✒️ Overlay FS in Linux


Linux의 OverlayFS 는 lowerdir, upperdir, merged로 구성된다.
• lowerdir : OverlayFS의 ReadOnly layer
• upperdir : OverlayFS의 Read-Write layer 
• merged : lowerdir와 upperdir가 merged된 최종 layer

OverlayFS는 layer와 layer를 겹겹이 쌓은 파일시스템이다.
위에서 내려다 봤을 때, 가장 윗층에 변경한 파일의 내용이 보이지만 기존 데이터가 사라진 것은 아니다.
Docker 또한 OverlayFS를 사용하며, 명령어를 실행하거나 library를 설치할 때마다 한 개의 OverlayFS layer가 쌓이다.
Dockerfile에 의해 완성된 OverlayFS가 바로 Docker Image가 된다.

Docker Container들은 Docker Image를 lowerdir(readonly) 레이어로 두고, 각자의 upperidr(read-write)레이어를 그 위에 얹는다.

 

 

1️⃣ web-ping 이미지 히스토리 확인

docker image history web-ping

  • CREATED BY : 해당 Layer를 구성한 Dockerfile script의 instruction
  • Instruction과 Image Layer는 1:1 관계
✒️ Instruction이 Layer와 일대일 관계여야 하는 이유

• Docker Image는 Image Layer의 집합, 논리적 대상
• Layer는 Docker Engine의 Cache에 물리적으로 저장된 파일
Image Layer는 여러 Image와 Container에서 공유된다.
  ∘ 중복되는 Layer에 대해서는 공유함으로써 전체 용량을 줄일 수 있다.
  ∘ Layer를 공유하기 위해서는 Image를 Layer로 구분할 필요가 있다.

 

2️⃣ 이미지 목록 확인

docker image ls

여기선 diamol/node가 없다. 해당 SIZE는 75.3MB이다.

  • 이미지 목록의 SIZE 항목은 이미지의 논리적 용량이다. (실제 차지하는 디스크 용량이 아님)
  • 다른 이미지와 레이어를 공유한다면 훨씬 적은 용량을 차지한다.
  • 실제 사용 용량은 system df 명령로 확인할 수 있다.

 

3️⃣ 실제 사용된 디스크 용량 확인

docker system df

  • 각각의 이미지가 75.3MB 씩 차지했다면, 용량은 적어도 225.9MB 보다 커야하나 그렇지 않다.
  • 논리적 용량 합에서 물리적 용량 합의 차 == 공유한 레이어 용량 크기
  • 절약되는 디스크 공간은 기반 레이어를 공유하는 애플리케이션 숫자가 많을 수록 늘어난다.

 

🤔 Layer를 공유하면 위험하지 않을까?
  • Image Layer를 여러 Image가 공유한다면, 공유되는 Layer는 수정할 수 없어야 한다.
  • 만약 수정할 수 있다면, 레이어를 공유하는 다른 이미지에도 영향을 주게 된다.
💡 결론적으로 Layer는 재사용될 수 있으나, 수정할 수는 없다.

 


5. Image Layer cache : Dockerfile Optimizing

 

1️⃣ 일부만 수정하고 Image 빌드해보기

docker image build -t web-ping:v2 .

기존 app.js에서 마지막에 개행 한 줄 추가해주고 다시 build를 하면 아래처럼 로그가 뜬다.

  • 이전에는 전부 새로운 Layer를 생성했으나, 이번에는 중복된 부분에 대해 CACHED Layer를 재사용한다.
    • Cache에 일치하는 레이어 판단을 위해 Hash 값을 사용
    • Hash 값은 Dockerfile 스크립트 인스트럭션과 인스트럭션에 의해 복사되는 파일 내용으로 계산
    • 기존 Image Layer에 Hash값이 일치하는 것이 없다면 Cache miss → 해당 Instruction 실행
    • 이후 Instruction은 수정된 것이 없더라도 모두 실행
💡 Dockerfile 스크립트 Instruction은 잘 수정하지 않는 Instruction이 앞으로 배치하는 것이 좋다.

 

2️⃣ Dockerfile 스크립트 최적화

docker image build -t web-ping:v3 .
FROM diamol/node

CMD ["node", "/web-ping/app.js"]

ENV TARGET="blog.sixeyed.com" \
    METHOD="HEAD" \
    INTERVAL="3000"

WORKDIR /web-ping
COPY app.js .
  • CMD Instruction은 FROM Instruction 뒤라면 어디에 배치해도 무방하며, 수정할 일이 잘 없으므로 앞으로 배치
  • ENV Instruction 하나로 여러 개의 환경 변수 정의 가능
  • 최적화 스크립트를 build하면, 마지막 단계를 제외하고는 모든 레이어를 Cache에서 재사용한다.

 


6. 연습 문제

 

Docker Script 없이 Docker Image를 만들어라.

Dockerfile의 목적은 애플리케이션 배포 자동화이지만, 때로는 수동 작업 혹은 스크립트로 작성할 수 없는 절차가 필요하다.

  1. docker hub의 'diamol/ch03-lab' Image가 있다.
  2. 해당 Image 내의 '/diamol/ch03.txt' 파일 뒤에 자신의 이름을 추가하라
  3. 수정된 파일을 포함하는 새로운 이미지 빌드를 하되, Dockerfile script는 사용할 수 없다.

Hint

  • -it 플래그를 사용하면 컨테이너를 대화식으로 실행할 수 있다.
  • 파일 시스템의 컨테이너는 컨테이너가 종료된 후에도 남아있다.
  • 아직 사용해보지 않은 도커 명령이 많은데 'docker container --help' 명령에서 유용한 명령 두 가지를 찾아보라.

 

이번 거 진짜 좀 많이 어려웠는데.

  1. docker container run -it --name [container id(name)] [image url]
    1. echo [name] >> ch03.txt
    2. exit
  2. docker container commit [container id] [new version image:tag]
    • 실행 중인 도커 컨테이너를 종료 → 종료된 도커 컨테이너 ID를 확인 → commit 명령을 입력하여 종료된 도커 컨테이너 상태 그대로의 이미지를 생성 → 새로 생성된 이미지로부터 도커 컨테이너를 실행
    • 일반적으로 도커 컨테이너 상에서 작업한 내용들은 종료되면 사라진다.
    • Commit을 하여 해당 이미지로 컨테이너를 실행하면 작업 내용을 다시 사용할 수 있다.
  3. docker container run [new version image] cat ch03.txt
    • docker run [Option] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...]