심화 스터디/데이터 엔지니어링 스터디

[4차시/15기 최경석] Docker-1

경석 2023. 4. 5. 13:59

1. Introduction

Docker Overview

왜 docker가 필요한가?

서비스를 개발할때 필요한 다양한 웹서버, db 등등에 대해서, 상호간의 호환성 문제(library, os버전, 등)를 해결하기 위해

→ 모두가 같은 OS, VM 상에서 작동하지만, 각각은 별도의 container 내에 구축된 환경에서 작동

Container은 무엇인가?

Container은 같은 os kernel를 공유하는 완전히 독립된 envorionment

→ 독립된 process, network, mounts,,,

Container개념 자체는 최근에 등장한 것은 아님

  • LXC, LXD, LXCFS 등등

Docker는 LXC Container를 활용 → Container를 구축하는건 굉장히 low level에서 이루어지기 때문에, Docker를 통해서 high level에서 다양한 기능을 적용하여 구축할 수 있음

그럼 OS(Operating System)을 알아보자

ex) Ubuntu, Fedora, Suse,,,

모든 Operating System들은 2가지로 구성되어있는데

  1. OS Kernel
  • 실제 하드웨어와 interact하는 부분 (Linux,,)
  1. Software
  • 개발자 도구, 각 OS를 구분, UI,,,

위에서 말했지만, docker의 container은 같은 OS Kernel을 공유한다고 했음

즉, Ubuntu기반의 OS는 Linux Kernel을 사용하기 때문에, Linux기반의 다른 OS (Ubuntu, Fedora, Suse,,,) 기반의 Container를 활용할 수 있

그럼 Container가 Virtual Machine과 다른 점이 뭔가요?

Container은 같은 OS를 공유하고, 그 위에 Docker, Container를 구성

But, VM은 각 VM마다 독립된 OS를 사용

VM은 별도의 OS를 구성하기 때문에 활용도는 높을 수 있지만, 그만큼 메모리를 엄청나게 많이 잡아먹음

VM을 구성해놓고 그 안에 Docker를 사용하는 식으로 구성을 할 수 있음

Container VS Image

  1. Docker Image
    • Application을 포장하고 전송할때 사용
    • Image는 application 실행에 필요한 독립적인 환경 및 런타임 환경을 위한 템플릿 (문서)
    • 읽기 전용 파일이고, 특정 시점에서 application이 가지는 가지는 속성에 대한 정보를 담고 있음 ⇒ 개발자가 안정적이고 균일한 조건에서 소프트웨어를 테스트할 수 있도록 도와줌
    • Image는 템플릿 그 자체이기 때문에 실행할 수는 없음
  • 하나의 base image에 대해서, 컨테이너를 실행하면 해당하는 Image를 수정할 수 있는 복사본이 생성되고, 그 위에서 수정을 하고 저장할 수 있음
  • 이렇게 수정되고 저장되는 Image는 무제한으로 생성할 수 있고, 초기 상태를 변경하고 저장할 때마다 추가 레이어가 쌓인 템플릿으로 저장됨
  • 따라서 image는 여러개의 layer로 구성되고, 각 layer는 이전 단계의 layer의 변형된 형
  1. Docker Container
    • Container는 실행중인 Image의 instance
    • 독립적이고, 자신만의 환경을 구축하고 있는 것
    • 한개의 image로부터 무수히 많은 container를 생성할 수 있음

기존 개발환경에서는 개발자가 개발을 하면, 그에 필요한 개발 환경이나 라이브러리 등등에 대한 가이드를 보내줘야하는데, 이러면 운영팀에서는 어려움이 많음

⇒ Docker Image의 형태로 전송을 하면 그대로 실행하면 됨!!

2. Docker Command

# Run Command => Container 실행
# 해당 Image가 없으면 docker hub에서 Pull
docker run nginx

# ps Command => 실행중인 container들의 list 및 정보
# random한 ID 부
docker ps

# 실행 중인 container + 이전에 실행했던 container까지 보려면 ps -a
docker ps -a 

# STOP => 실행중인 container 중
docker stop """ 

# Rm => container 삭제
docker rm """

# images => image들의 list (TAG, ID, Size)
docker images

# rmi => image 삭제
# image를 삭제하기 이전에, 해당 image로 생성된 container 들을 먼저 중단하고 삭제해야 
docker rmi nginx

# pull => image 저장 (실행은 안함)
docker pull nginx

Container는 특정한 목적(웹서버, db, 연산, 분석 등) 을 위해 불러오는 것이기 때문에!!!

Container를 실행한 뒤에, 유지시키기 위해서는 그 안의 process가 실행이 되어야함

즉, 단순히 불러오고 실행한다고 해서 유지되는게 아님

그래서 ubuntu image를 불러오면, ubuntu는 다른 application을 위한 operating system의 한 종류라서, 생성하면 바로 중단됨

docker run ubuntu 

# 5초동안 유지 (sleep 5)
docker run ubuntu sleep 5

# 실행중인 container를 중단하고 싶으면
docker exec '이름'

# attach 
# 기본적으로 실행을 하면, foreground에서 실행될거임
# attached to the console??? => 해당 container가 중단될때까지 콘솔에서 다른 행동을 할 수 없음
docker run kodekloud/simple-webapp

# detach (-d)
# container가 background에서 실행 => 프롬프트에서 다른 행동을 할 수 있음
docker run -d kodekloud/simple-webapp

# attach
# detach인 상태에서 다시 attach 하고 싶을 경우
docker attach '이름'

3. Docker Run

# 버전을 설정하고 싶은 경우 => ":version" 적어줌 (tag)
# default는 Latest
docker run redis
docker run redis:4.0

버전 찾는 법 ⇒ Docker Hub에 가면 다 나와있

Docker Hub Container Image Library | App Containerization

docker에서 container를 실행하면, input을 받지 않고 그냥 실행시켜버림

즉, input을 받을 terminal이 없음 ⇒ 그럼 input을 넣고 싶으면 어떻게 하는가??

# 기존
docker run kodekloud/simple-propmp-docker

# input을 넣을 수 있는 형태 (interactive)
docker run -i kodekloud/simple-propmp-docker 

# 그렇다 하더라도 아직 terminal의 형태는 없음 => terminal이 있는 형태
docker run -it kodekloud/simple-propmp-docker 

PORT Mapping

Docker에서 application을 실행하면, 어떤 서버 위에서 작동하고 있음을 알고 있음

  • docker에서 돌아가는 application이 어떤 포트번호를 사용하는지 알 수 있음
  • 그럼 어떤 IP를 사용하나?
    • 1)Docker Container의 IP ⇒ Internal IP이기 때문에 외부에서 접속할 수가 없음
      1. Docker Host의 IP ⇒ 그러기 위해서는 Container의 포트번호를 사용자가 접속하는 Docker Host의 포트번호에 Mapping 함
# 기존
docker run kodekloud/webapp

# Port Mapping
# Docker Container의 포트번호 5000 -> Docker Host의 포트번호 80
# 원하는 만큼 매핑 시킬 수 있고, 당연하게도 같은 포트번호에 매핑할 수는 없
docker run -p 80:5000 kodekloud/webapp

Volume Mapping

Docker Host안에 Container에 자체 DB가 있는데, 해당 DB의 내용을 Host상에 저장하고 싶을 경우

⇒ 저장을 안해놓으면 Container를 지울때 모든 정보도 같이 삭제 ⇒ 그러니깐, container가 아니라 host 상에서 저장하자

# mysql이라는 Container실행
docker run mysql

# mysql 중단 및 삭제 => 이 경우 모든 정보가 다 삭삭제
docker stop mysql
docker rm mysql

# /var/lib/mysql에 있는 db를 Container 바깥의 /opt/datadir에 Mapping
docker run -v /opt/datadir:/var/lib/mysql mysql

이렇게 해서 Container 를 실행시키면, 외부 경로에 있는 폴더를 불러올 것

Inspect Container

# Inspect : Container에 대한 정보를 JSON파일로 전달
docker inspect '이름'

Container Logs

# Background에서 실행시킨 container의 log 기
docker logs '이름'

4. Docker Image

Docker Image 만들기

Docker image를 왜 직접 만들지??

→ 내가 원하는 서비스가 제공이 안될 때 / 효율적일 때

  • Dockerfile 작성 ⇒ “build”
  • Local에서 Image 생성 ⇒ “push” Public Docker Hub로 공개
  • 1번부터 6번까지 Layer로 순차적으로 생성되며, 처음에 있는 Image에서 필요한 기능들이 layer마다 추가된 것

Docker File

  • Dockerfile은 docker가 이해할 수 있는 구체적인 형식(Instruction - Argument)을 가지고 있는 TextFile
    • FROM Ubuntu : base OS 가 무엇인지
    • RUN : Install 해야 할 사항
    • COPY : 해당 경로로 필요한 소스코드를 복사
    • ENTRY : 해당 image가 container로써 작동할 때에 사용될 command
  • Layer이 추가될때마다 모두 저장되기 때문에, 새로운 layer를 추가하거나 실패했을 때 처음부터 쌓을 필요가 없음 ⇒ 이전 단계에서의 Layer에서 바뀌는 부분만 추가하면 됨
# base image (ubuntu) 생성
# interaction & terminal 
docker run -it ubuntu bash

# Python
apt -get update
apt -get install -y python

# Flask
apt -get install pip
pip install flask

# image로 만들고자 하는 source code 저장
# opt/app.py 에 코드 저장
cat > /opt/app.py

#  Flask application 시작
FLASK_APP = app.py flask run --host=0.0.0.0

# 실제 서버 확인은 DOCKER HOST에서 Container IP 주소 & 포트번호 를 통해 들어가야함

# Containerize하기 위해서는 우선 전체 step을 확인해야함
# history => 지금까지 어떤 step을 거쳐왔는지 확인 
history
# 위에서 확인한 history를 containerize하려면 일단 docker file을 만들어야함
mkdir my-simple-webapp
cd my-simple-webapp

# docker file 생성
cat > Dockerfile

FROM ubuntu

RUN apt-get update
RUN apt-get install -y python python-pip
RUN pip install flask

COPY app.py /opt/app.py

ENTRYPOINT FLASK_APP = app.py flask run --host=0.0.0.0

# app.py가 local에 없으므로 만들어줌

# docker image 생성!!
# image 이름은 tag 로 생성 "my-simple-webapp"
docker build . -t my-simple-webapp

# 모든 과정을 저장하기 때문에 처음부터 다 만들필요 없음 

# public으로 공개하기 (로그인 해야함)
docker push 이름/파일이름

환경변수 : Environment Variables

매번 소스코드를 일일이 바꿀 수 없으니, environment variable 로 둬서 편하게 바꾸자!

docker run simple-webapp-color

docker run -e APP_COLOR=blue simple-webapp-color
 
# 환경 변수는 어떻게 찾나?? inspect -> Config -> Env
docker inspect """"

Command & Entrypoint

Container은 해당 process가 지속될 동안에만 유지됨

멈추거나 완료되면 container에서 exit함!!

CDM(Command) : Container 내에서 어떤 process가 진행될 것인지 정의

다른 command를 사용하고 싶다면?

  • 실행할 때에, 다른 command를 기본 command위에 덮어씀
# 기존 command를 override
# sleep 5 : command
docker run ubuntu sleep 5

# 이걸 고정하고싶으면 새로운 image 생성

# CDM ["command", "param"]의 형태로 생성
FROM Ubuntu
CMD sleep 5

CMD ["sleep", "5"]

# 생성
docker build -t ubuntu-sleeper .

# 이걸 또 다시 10초로 늘리고 싶다??
docker run ubuntu-sleeper sleep 10
# 이렇게 하면 너무 이상함

docker run ubuntu-sleeper 10
# 이렇게 만들고 싶으면 어떻게 하지?? => 이건 Entrypoint 덕분
FROM Ubuntu
ENTRYPOINT ["sleep"] sleep 10
# 라고 하면 뒤에 10만 붙이면 sleep 10 으로 설정됨
# 만약 entrypoint만 설정하고 cmd 없이 그냥 run 할 경우?
docker run ubuntu-sleeper
#라고 하면 에러가 발생함 (operand가 없으므로)
# 이걸 해결하려면 둘 다 사용하는거임!!
FROM Ubuntu
ENTRYPOINT ["sleep"]
CMD ["5"]

# entrypoint도 바꾸고 싶으면 (sleep -> sleep2.0)
docker run --entrypoint sleep2.0 ubuntu-sleeper 10