Infra/Docker(도커)

Docker 이미지 & 컨테이너 이해하고 사용하기

민돌v 2023. 11. 16. 11:28
[Infra/Docker(도커)] - Docker - 도커란

이전 글에서 Docker의 개념에대해 살짝 정리해보았습니다.
이번 글에서는 Docker 실질적으로 사용하기위한 개념인 컨테이너와, 이미지에 대해 학습 정리를 하고자 합니다.

 

[목차]

  1. Docker Images, Container 차이점 
  2. Contanier 이미지 사용하기 (이미지 만들기)
    1. 외부 빌드된 이미지 - Docker Hub 이미지 가져오기
    2. 커스텀화된 이미지 - DockerFile 로 이미지 빌드하기
  3. 도커 이미지 레이어 이해하기
  4. 도커 이미지 지우기
  5. Docker hub 에 커스텀 이미지 올리기
  6. Docker 로 EC2에 배포하기

 


1. Images vs Containers

Docker 환경에서의 이미지와 컨테이너의 차이점

> 도커는 컨테이너 격리 프로그램입니다
> 따라서 컨테이너라는 개념을 적용하고, 이를 구성하는 이미지가 존재합니다.

 

✔️ Docker Containers

  • 컨테이너는 코드, 애플리케이션을 실행하는 전체 환경 등, 무엇이든 포함하는 작은 패키지라고 생각할 수 있습니다.
  • 컨테이너에는 소프트웨어 실행 유닛이 존재하고, 컨테이너에서는 이 유닛을 실행시킵니다.
  • 컨테이너는 실제로 실행되는 프로세스 입니다.

 

✔️ Docker Images

  • 도커 이미지란, 컨테이너의 템플릿 혹은 블루포인트(시작점) 입니다.
  • 이미지는 실제로 코드와 코드를 실행하는데 필요한 도구를 포함하고 → 그런 다음 컨테이너가 실행되어 코드가 실행됩니다.
  • 이렇게 이미지와, 컨테이너의 개념을 분리함으로써 이미지를 통해 여러대의 컨테이너를 손쉽게 만들 수 있게 됩니다.

 

📌 즉, 이미지는 모든 설정 명령과 모든 코드가 포함된 공유 가능한 패키지 이며 컨테이너는 그러한 이미지의 구체적인 실행 인스턴스(어플리케이션) 입니다
📌 Dokcer 를 사용한다는 것은 이미지를 기반으로하는 컨테이너를 실행하는 것이고, 이것이 도커의 핵심 기본 개념이며 모든 것 입니다.

 

Docker 이미지와 컨테이너의 관계

 

 


2. Docker Image 사용하기 (Docker Build Image)

 실제로 컨테이너를 실행할 수 있도록 이미지를 생성하거나 가져오는 2가지 방법이 존재합니다.


1) 외부 이미지 사용 - "Docker Hub"

  • 이미 존재하는 이미지를 사용하는 것
  • 동료, 혹은 미리 구축된 공식 이미지나 커뮤니티에서 공유한 이미지를 사용하는 방법입니다.
  • Docker Hub 에서 공식 이미지들을 찾을 수 있습니다.

 

Docke hub에서 이미지 내려받기

  •  로컬환경 shell script(명령 프롬프트) 환경에서 Docker HUB에 올라가있는 공식 이미지의 이름을 사용합니다,
  • `docker pull - `를 사용해서 이미지를 받기만해도 되고, `docker run - `을 사용하여 컨테이너를 바로 실행시킬 수 도 있습니다.
    • 맥(mac)환경이라면 Docker desktop 을 실행시켜 Docker 를 활성화 시켜주어야 합니다.
  • `docker run ` 시 로컬환경에 해당 이미지가 없다면 자동으로 Docker hub에서 이미지를 pull 받습니다.
  • `docker run `으로 실행시켰기 때문에 contatner name 도 자동으로 설정되어 실행되어집니다.
//기본적인 Docker run 포맷
docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...]

 

✔️ docker run 시 status 가 exit 인 이유

  • 하지만 위의 사진처럼 단순하게 docker run [image name] 로 실행한다면 컨테이너는 EXIT 상태가 되어집니다.
  • 그 이유는, 컨테이너는 격리되어 실행되기 때문에 노드에 의해 노출된 인터렉티브 쉘이 우리(Host)에게는 노출되지 않기 때문입니다.

 

✔️ docker run 시 컨테이너 내부 노출시키기

  • Docker run options 으로 어떻게 Host 에게 노출시킬것인지 정할 수 있습니다.
    • -it 컨테이너 실행시 내부에서 호스팅으로 대화형 세션(명령 프롬프트)를 노출시킵니다.
    • -p : 호스트에 연결된 컨테이너의 특정 포트를 외부에 노출시킵니다. 해당 포트를 이용하여 로컬에서 특정 포트를 바인딩해 컨테이너 내부로 연결시킬 수 있습니다.
// -it 옵션
$ docker run -it ubuntu:20.04

// -p 옵션
$ docker run -p 127.0.0.1:80:8080 ubuntu:20.04 bash

 

하자만 중요한 것은 나의 어플리케이션을 Docker 컨테이너로 실행시키는 것!
이렇게 컨테이너를 띄운 후, 노출된 컨테이너 내부에서 깃허브와 같은 외부 저장소에서 코드를 끌어다 쓸 수도 있지만

 

이미지의 "쉬운 공유" 장점을 살리기 위해선 우리의 어플리케이션까지 같이 빌드된 커스텀화된 이미지가 있으면 좋겠죠

 


2) Dockerfile 을 사용하여 자체 이미지 빌드하기 

> DockerFile을 이용하여 커스텀화된 이미지를 빌드할 수 있습니다.

1. Dockrfile  만들기

  • DockerFile이란 도커에의해 식별되는 특별한 이름을 가진 파일입니다.
  • 확장자 없이 "DockerFile"이란 이름의 파일을 생성하면 도커에서 이미지를 빌드하기위한 기준파일이구나~ 인식하게됩니다.
  • IDE를 사용하시다면 각 IDE에서 지원하는 "도커 플러그인"을 사용하시다면 조금 더 편하게 작성할 수 있습니다.

2. Dockerfile 문법 (Dockerfile 구조)

Dockerfile 작성법에 대해 기본적인 개념만 정리해보고자 합니다.

//node 서버를 실행하는 Dockerfile

FROM node                  //(1)
WORKDIR /app               //(3)
COPY . /app                //(2)
RUN npm install            //(4)
EXPOSE 80                  //(5)
CMD ["node", "server.js"]  //(6)
  1. FROM
    • 베이스 이미지를 불러옵니다. 베이스 이미지는 외부 빌드된 이미지 - 즉, Docker Hub 에서 가져옵니다.
    • 이론적으로 처음부터 도커 이미지 레이어를 구축 할 수도 있지만, 그건 너무 손이 많이가는일이니 미리 구축되어있는 공식 운영체제 레이어를 사용하는 걸 추천한다고 합니다.
      ( → Docker Hub 에 등록된 공식 이미지들도 보통 Linux의 이미지인 'alpine' 이나 'debian'을 사용합니다. docker node repo)
    • Docker 이미지는 base 이미지부터 시작해 기존 이미지위에 새로운 이미지를 중첩해서 여러 단계의 이미지 층( layer)를 쌓아가며 만들어집니다.
  2.  COPY
    • 이미지를 만들 때 "어떤 파일"을 복사해서 "어디에" 저장할 것인지를 의미 합니다.
    • COPY [복사할 로컬 파일경로] [붙여넣을 컨테이너 내부 경로]
      1.  이때 첫번째 경로에 Dockerfile이 존재한다면 복사 시 제거됩니다.
      2. 두번째 경로로 루트 폴더 즉, 도커 컨테이너의 루트 엔트리(/.)를 사용하지 않고 전적으로 사용자가 선택한 "/app 경로를 사용한다면 컨테이너 내부의 app 폴더 에 저장되고, 폴더가 없으면 생성됩니다.
  3.   WORKDIR 
    • 상대경로를 적었다면 이 폴더 내부에서 명령어를 실행하라고 알리는 것 입니다.
    • 그렇지않다면 이미지와 컨테이너로 인해 실행된 인스턴스는 루트에서 아래 정의된 모든 명령어를 실행할 것 입니다.
  4. Run 
    1. RUN 명령어는 도커파일로부터 도커 이미지를 빌드하는 순간에 실행이 되는 명령어입니다.
    2. RUN 명령어를 이용하여 어플리케이션을 실행시킬 수도 있지만, 이는 권장되는 방식입니다.
    3. 도커 이미지는 실행환경을 세팅하는 템플릿이고, 어플리케이션은 컨테이너가 실행될때 같이 실행되는게 권장되는 방식이기 때문입니다.
    4. 따라서 RUN 명령어는 라이브러리를 설치하는 용도로 주로 사용됩니다.
  5. EXPOSE 80
    1. EXPOSE 명령어는 해당 Dockerfile로 빌드되는 이미지가 어떤 포트를 노출할 것인지 명시적으로 알리기위한 용도라고 합니다.
    2.  따라서 Document 에 가까워서 사실 안해줘도 되지만, 공식문서에서는 Dockerfiledp 'EXPOSE'를 추가하여 이 동작을 문서화하는 것을 모범적인 사용법이라고 명시하고 있습니다.
    3. EXPOSE 명령어는 문서화이기 때문에, docker run [이미지] 실행시 -p 옵션(publish) 을 주어 실질적으로 EXPOSE 에 명시된 포트를 노출시켜주어야 합니다.
  6. CMD
    1. 이미지가 생성될 때 실행되지 않고 이미지를 기반으로 컨테이너가 시작할때 해당 명령어가 실행 됩니다.
    2. 문법이 조금 다른데 배열을 이용해 전달합니다.
      ex) CMD ["node", "server.js"]
      →  명령을 두 개의 문자열로 분열
      → 도커에게 이미지를 기반으로 컨테이너가 생성될 때마다 그 컨테이너 내부에 있는 node 명령어를 사용하여 server.js 파일을 실행하도록 지시 

 

JDK Dockerfile 예시

❗️이미지를 실행 명령어들 (RUN, ENTRYPOINT, CMD 차이점)
→ 해당 사진은, 과거 저의 포스팅에서 가져온건데, ENTRYPOINT를 사용했군요

  • RUN : 위에서 말했다시피, 이미지가 빌드될 떄 실행합니다. 따라서 어플리케이션 실행으로는 권장되지 않는 명령어입니다.
  • CMD, ENTRYPOINT : 2개 명령어 모두 컨테이너가 실행시 동작할 명령어를 정의 합니다.
    • 차이점
      • ENTRYPOINT 명령어는 오버라이딩이 어렵고 CMD 명령어는 오버라이딩이 쉽다.
      • docker run 시에 다른 실행 명령어가 있으면 CMD 명령어에 써준 내용은 무시된다. 반면에 ENTRYPOINT 명령어는 무시되지 않고 docker run에 붙여준 실행 명령어를 인자로 받아서 컨테이너를 실행한다.
      • CMD 명령어는 docker run 명령 내에 명시된 매개변수가 있는 경우 Daemon에서 무시된다.
      • ENTRYPOINT 명령어는 무시되지 않고 대신 명령의 인수로 취급하여 매개변수로 추가된다.

 

3. 이미지 빌드하기

  • 자 다왔습니다! 이제 Dockerfile이 있는 디렉토리 경로에서 빌드만 하면됩니다!
  • 태그 : 태그는 도커 레포지토리(이미지)의 버전을 관리하기 위함입니다. default는 lateast입니다.
//docker build -t {이미지명}:{태그} {Dockerfile 파일 위치}
//-t : 이름을 나타냄

docker build -t 이미지이름 .

 

4. 도커 이미지 생성 확인

> 아래의 명령어로 이미지 생성 목록을 확인할 수 있습니다.

docker images

도커 이미지 목록

 

5. 도커 이미지를 컨테이너화 시키기

이미지를 만들어주었으니 Docker Hub 이미지를 사용할 때와 마찬가지로 실행시키면 됩니다.

  • -p : 로컬포트에서 접속시 어떤 도커포트랑 연결할 것인지를 의미하는 명령문입니다.
  • -d : 백그라운드 실행을 의미합니다. (detach : 종료하지 않고 분리시킴)
docker run --name maru-spring -p 8080:8080 -d maru-spring

docker run --name '이미지 이름' -p '로컬포트:도커포트' -d 이미지이름 (-d는 백그라운드 실행을 의미)

 

도커가 실행이 완료되면 요상한 문자가 나타납니다. 어떤 도커 이미지가 실행중인지 확인해 봅니다.

docker ps

사진의 PORTS를 보면 로컬의 어떤 포트가, 도커의 포트랑 연결되어있는지 볼 수 있습니다.

 


3. Docker Image 레이어 이해하기

👏🏻 Docker image 는 레이어 기반으로 이루어져있고, 그렇기에 Docker는 레이어 기반 아키텍처를 가진다라고도 이야기합니다.

  • 실제로 이미지를 빌드해보면 각각의 명령어별로 무언가 수행됨을 볼 수 있습니다.
    (EXPOSE는 문서화, CMD 는 컨테이너가 실행될 때 수행)

 

✔️ Docker Image build 동작과정

  1. 이미지가 빌드될 떄 명령어가 레이어에 쌓여가는 과정은 아래 사진과 같이 명령어 별로 Read Only(읽기 전용) 쌓아가는 것 입니다.
  2. 이미지의 모든 명령어는 도커에서 관리하는 "읽기 전용" 레이어가 되고, 해당 이미지를 컨테이로 실행시킬 때 "쓰기 가능한" 최종 레이어가 생성됩니다.

이러한 특성 때문에 도커의 이미지는 "불변"한다고 이야기 합니다.

https://www.44bits.io/ko/post/building-docker-image-basic-commit-diff-and-dockerfile

만약 아래와 같이 3가지 명령어를 가지는 Dockerfile 을 && 명령어를 사용해 2가지 명령어로 수정한다면 레이어의 개수 또한 줄어듭니다. 

FROM ubuntu:18.04
RUN apt update
RUN apt install -y git

↓

FROM ubuntu:18.04
RUN apt update && apt install -y git

https://www.44bits.io/ko/post/building-docker-image-basic-commit-diff-and-dockerfile

"docker inspect [이미지]" 명령어를 사용하면 아래와같이 어떤 레이어로 구성되어있는지 확인할 수 있는데, 실제 Dockerfile 의 명령어 개수보다 많은 레이어로 구성되는 이유는 "FROM node" 라는 베이스 이미지에서 구축된 레이어를 포함하기 때문입니다.


✔️ Docker Cache를 통해 build 최적화 하기

→ "읽기 전용 레이어" 라는 뜻은 이미지를 다시 빌드할 때 변경된 부분의 명령과 그 이후의 모든 명령이 재평가된다는 의미입니다.
→ 아무런 변경도 없이 Dockerfile 을 이용해 새롭게 이미지를 빌드하면 이전과는 다르게 매우 빠르게 빌드가 되는 것을 확인할 수 있었습니다. (3.5s → 1.5s)

📌 그 이유는 Docker Cache 를 사용하기 때문입니다.

  1. 동일한 작업 디렉토리에
  2. 복사한 코드(COPY로 불러오는 어플리케이션 코드) 는 전혀 변경되지 않았고
  3. 새 파일도 없고 변경된 파일도 없다면

도커는 실제로 그 명령을 다시 거칠 필요가 없다고 추론합니다. →  도커는 이미지를 빌드할 때 마다 모든 명령 결과를 캐시하고 다시 빌드할때 명령을 다시 실행할 필요가 없다고 판단되면 캐시된 결과를 사용합니다.

 

📌 이렇기때문에 도커는 레이어 기반 아키텍처라고 합니다.

  • 모든 명령은 Dockerfile의 레이어를 나타냅니다. 
  • 즉, 이미지의 모든 명령은 캐시 가능한 레이어를 생성하고 이러한 캐시는 이미지의 재구축 및 공유를 돕습니다.

 

📌 하지만 여기서 하위 레이어가 변경이 일어난다면, 도커는 변경점이 일어났다고 판단하기 때문에 그 위에 쌓여진 모든 레이어를 다시 빌드합니다. 

  • 따라서 Dockerfile을 아래처럼 변경하여 최적화 할 수 있습니다.
  • COPY . /app 으로 불러오는 어플리케이션 코드가 변경되더라도, 다운받아야하는 종속성이 변경되지 않았다면 추가적인 작업을 하지 않도록 하위레이어로 쌓는 것 입니다.
//node 서버를 실행하는 Dockerfile
FROM node                  //(1)
WORKDIR /app               //(3)
COPY . /app                //(2)
RUN npm install            //(4)
EXPOSE 80                  //(5)
CMD ["node", "server.js"]  //(6)

             ↓

FROM node                  //(1)
WORKDIR /app               //(3)
COPY package.json /app     // 종속성이 추가되거나 변경됨을 감지하기위한 COPY 명령어
RUN npm install            // 종속성파일이 변경되지않으면 npm install을 또 할 필요가 없음
COPY . /app                // 어플리케이션이 변경되면 그 위의 레이어만 영향이 가도록
EXPOSE 80                  //(5)
CMD ["node", "server.js"]  //(6)

 


4. Docker 이미지 지우기

이미지를 빌드해서 만들고, 컨테이너를 생성해보았으니 이제 지워볼까 합니다.

✔️ 컨테이너 지우기 or  종료

  • 컨테이너는 Stop 명령어 중지 후에
  •  rm 명령어로 간단하게 지울 수 있습니다.
  • ps -a : 실행중인 프로세스와 종료중인 프로세스 모두 보여줍니다.

 

✔️ 이미지 지우기

  • docker rmi
    • 이미지는 rmi 명령어를 이용하여 개별 이미지를 선택해 삭제할 수 있습니다.
    • 단, 해당 이미지를 사용중인 컨테이너가 존재한다면(시작 or 중지) 그 컨테이너가 속한 이미지를 지울 수 없습니다.
    • ex) docker rmi [이미지 id] [이미지 id] [이미지 id] (복수 삭제 가능)
  • docker image prune
    • 현재 실행중인 컨테이너에서 사용중이지 않은 모든 이미지를 지우는 명령어 입니다.
    • docker image prune : 사용되지 않는 이미지 모두 삭제
  • docker run "--rm"
    • 중지된 컨테이너 자동으로 제거하는 docker run 옵션입니다.
    • 보통 중지된 컨테이너를 새로 시작할때는 → 코드에 변경이 일어나 이미지를 재빌드 해야할 때가 많습니다.
    • 그렇기 때문에 컨테이너를 중지시킬때 아예 지워버리는게 경험상 유용하다고 합니다
    • 하지만... 음... 해당 옵션을 활성화하기 위해서는 에러 로그나, 덤프파일 같이 비정상적인 다운 시 모니터링을 위한 파일은 따로 빼둘 수있는 작업이 선행되어지는게 좋아보입니다.

 


5. Docker hub에 이미지 업로드 (push)

1. 먼저 도커허브에 레포지토리를 만들어 둡니다.(https://hub.docker.com/)

2. 터미널에서 도커에 로그인을 해야합니다.

docker login

3. 그 다음 업로드할 이미지를 태깅해 주어야합니다.

docker tag maru-spring thalals/maru-spring

docker push {리파지토리 이미지명}:{태그}

태깅 결과

4. 태깅한 이미를 도커허브의 레포지토리에 push해 줍니다.

docker push thalals/maru-spring

docker push {리파지토리 이미지명}:{태그}

푸시 완료
도커 허브 레포지토리에 이미지 업로드 완료

 


6. Docker 이용해서 EC2에 배포하기

  • 사실 별건 아닙니다. Docker hub 에 이미지를 올렸기 때문에 pull 받아서 손쉽게 배포가 가능함을 보이기 위함입니다.
  • 먼저 EC2를 하나 생성하고, 터미널에서  EC2로 접속합니다.
  • EC2 프리티어 생성 및 접속 : https://thalals.tistory.com/120

 

1) EC2에 도커 설치하기

vi install.sh

install.sh

sudo apt-get update

sudo apt-get -y install \
apt-transport-https \
ca-certificates \
curl \
gnupg2 \
software-properties-common

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"

sudo apt-get update

sudo apt-get install -y docker-ce

sudo usermod -aG docker $USER

결과

install 파일에 실행권한을 준다음(chmod 777) 파일을 실행시킵니다.

chmod 777 install.sh
./install.sh

쉘스크립트 파일 설치
도커 잘 깔렸는지 확인

 

2) Docker image pull 

  • ec2에서 도커허브에 올라가있는 이미지를 pull 받아서 외부 빌드된 이미지랑 똑같이 사용하면 됩니다.
docker pull {도커 허브 이미지명}:{태그}
docker run --name {컨테이너 이름} -p 5000:5000 -d {이미지 이름}:{태그}

docker pull

 

그러면 인자 localhost:8080으로 접속할 수 있는데 → 그 전에 AWS에서 8080포트를 열어주어야합니다.

EC2인스턴스 선택 > 보안 > 보안그룹 > 인바운드 규칙 편집 > 규칙 저장

 

 

 

 


참고