컨테이너 가상화(Docker)

하나의 호스트 위에 여러 컨테이너를 적재

서버(하이퍼바이저) 가상화

보안 수준 유리

컨테이너 가상화

자원의 오버헤드 측면, 프로비저닝이 필요없음, 자원/속도 유리

  하이퍼바이저 가상화 컨테이너 가상화
격리수준 하드웨어 수준 프로세스 수준
자원 오버헤드 높음 오버헤드 낮음
보안 컨테이너 가상화보다 높음 하이퍼바이저보다 낮음
프로비저닝 속도 상대적으로 느림
guest VM부터 구성
상대적으로 빠름
호스트 H/W와 커널까지 공유하기 때문
따로 Guest VM과  Quest 커널을 구성할 필요없음
호환성 원하는 OS 설치 가능 호스트에 종속된다.
리눅스 기반의 컨테이너만 가능

컨테이너 가상화에 필요한 리눅스 기반 기술

1. ch root

컨테이너의 최상위 디렉토리 변경

컨테이너 내부에서 접근 가능한 디렉토리(공간)를 제한

2. Cgroup(Control Group)

자원을 컨트롤, 컨테이너에 호스트의 자원을 제한

  • 하이퍼바이저 VM의 경우, 자원을 '할당'하는 개념(2core, 2GB)
  • 컨테이너의 경우, '제한'하는 개념(램을 2GB까지)
    • 호스트의 모든 자원을 하나의 컨테이너가 끌어다 쓰는 것을 제한

컨테이너 가상화의 종류

  1. Docker: 컨테이너를 관리하는데 최적화, 앞으로 K8s를 할때도 계속 사용
  2. Containerd: K8s를 구성하는 런타임으로 사용할 예정
  3. CRIO
  4. LXC(Linux Container)

실습환경 구성

ubuntu 20.04에 도커 설치 → ubuntu-tem clone

hostname: host, RAM: 4GB, IP: 211.183.3.100 /24

  • IP: 211.183.3.100 /24
root@ubun-tem:~# vi /etc/netplan/00-installer-config.yaml
root@ubun-tem:~# netplan apply
  • hostname: host
root@ubun-tem:~# hostnamectl set-hostname host
root@ubun-tem:~# su

도커 설치

  • 최상위 디렉토리 위치에 설치 스크립트 다운
root@host:~# curl -fsSL https://get.docker.com -o get-docker.sh
  • 권한 부여 후 실행
root@host:~# chmod +x get-docker.sh 
root@host:~# ./get-docker.sh
  • 설치 및 버전 확인
root@host:~# docker -v
Docker version 28.0.1, build 068a01e

# 컨테이너는 실행되는데, 컨테이너끼리나 외부로 통신이 안되면 docker0 인터페이스 IP가 잘 올라왔는지 확인하기
# IP가 없으면 systemctl restart docker 실행하기

  • IP 확인
root@host:~# ip add
  • IP 안뜰경우 실행
root@host:~# systemctl restart docker

리눅스 측면에서 바라본 컨테이너 네트워크 (172.16.0.0/16)

도커 기본 명령어

docker run

  • nginx라는 이미지로 컨테이너를 run(생성+동작)
root@host:~# docker run nginx

# 실행 가능한게 container밖에 없으므로 container 기입 생략

 

# 컨테이너가 잘 동작하려면 반드시 foreground 상태인 프로세스가 존재해야 한다.

  • 자바컨테이너 : java -jar app.jar 
    (자바가 잘 동작하기위한 명령을 실행해줘야 한다.)
  • 파이썬컨테이너 : python manager.py runserver
  • nginx컨테이너 : nginx -g "daemon off;"
  • httpd컨테이너 : httpd -D FOREGROUND

→ 기본적이고 중요한 개념

 

터미널 창을 하나 더 열어서 docker ps를 쳐보면,

  • docker ps : 컨테이너 상태

  •  Ctrl + C (Cancel)로 종료 후 실행 중인 컨테이너 확인

# 내가 포어그라운드로 점유하던 프로세스가 종료됐다. 세션이 닫힘

# 컨테이너 내부에서 포어그라운드로 동작하던 프로세스가 종료되자 컨테이너가 중지됐다. 이 차이를 이해하고 해결하는게 컨테이너 가상화에서 필수이자 가장 기본이 되는 능력이다.

  • docker ps -a : 모든 컨테이너 확인

# 중지된 컨테이너는 -a 옵션으로 볼 수 있음

  • docker rm : 컨테이너 삭제

  • -f 강제삭제: 실행중인 경우에도 삭제
  • centos:7 이미지로 test라는 이름을 갖는 컨테이너 생성+동작
root@host:~# docker run --name test centos:7

# centos:7이라는 이미지는 처음에 설계당시, '이 컨테이너를 실행하면 /bin/bash라는 명령을 수행해라'라고 만들어졌다. 따라서 우리가 docker run 명령으로 컨테이너를 실행시켰을때 /bin/bash라는 명령이 수행됐을것이다. 하지만, 컨테이너가 '정상동작'하려면 반드시 컨테이너 내부에서 포어그라운드로 프로세스가 실행되어야하기 때문에 그 조건을 충족시키지 못해 결국 컨테이너는 중지상태가 된다.

  • 삭제

  • 재생성

# 이미지를 기준으로 왼쪽은 컨테이너 run에 필요한 옵션을 적어주고 오른쪽에는 이 컨테이너가 동작시 실행된 명령어(COMMAND)를 '하나' 적어준다. 컨테이너  동작시 60초 동안 포어그라운드 상태로 만들기 위해 sleep 60이라는 명령을 수행하도록하자

# 60초가 지난후 docker ps를 쳐보면 컨테이너 내부에서 foreground를 유지시켜주던 'sleep 60'이라는 명령어 종료로 인해 foreground 프로세스가 사라지면서 컨테이너는 중지된다.

# 컨테이너가 정상동작하고 있다면 pid가 1인 프로세스가 반드시 존재한다.

# status=up의 의미

  • docker start : 실행
  • docker restart : 재실행
  • docker stop : 중지
질문) 컨테이너의 경우엔 꼭 운영체제가 아닌, nginx나 python, java같은 앱도 컨테이너로 만들 수 있다
→ 이미 호스트의 커널을 공유중이기 때문

# kvm의 이미지와 컨테이너이미지가 어떤 차이가 있는지 이해하면 좋다.

# 컨테이너이미지로 컨테이너를 띄우더라도 원래 이미지는 그대로 존재한다.

docker run 옵션

  • -d(detach): 백그라운드로 컨테이너 실행
root@host:~# docker run -d --name ntest nginx:latest

  • 컨테이너의 세부정보
root@host:~# docker inspect ntest

  • NIC 확인

# exec로 내부에 드감

  • 리눅스(host)에서 컨테이너로 통신이 잘 되는 것 확인
root@host:~# curl 172.17.0.2

  • 컨테이너의 로그 확인
root@host:~# docker logs ntnest

# docker logs: 일반적으로 컨테이너를 실행시켰을 때, 내부의 프로세스가 적절하게 동작하지 않아 컨테이너가 중지되는 경우가 있는데, 왜 중지됐는지 여부를 어느정도 확인할 수 있다. (db연동이 안되거나, command에 문제가 있는 경우, 내부에서 프로세스가 동작 안할거임)

  • 컨테이너 네트워크 목록
root@host:~# docker network ls

# 컨테이너 가상화에서 명칭이 다르지만, bridge: workstation의 NAT와 같음

 

  • 컨테이너 이미지 목록
root@host:~# docker image ls
  • pull했던 두개의 이미지(nginx:latest, centos:7은 도커허브에서 가져온 오피셜 이미지)

 

  • docker exec : 컨테이너 내부에서 명령을 수행
root@host:~# docker exec ntest ls

root@host:~# docker rm -f ntest
  • 해당 컨테이너와 터미널로 상호작용하겠다 = 해당 컨테이너 접속
root@host:~# docker run -it --name cent centos:7
  • -i : interactive, 상호작용
  • -t : terminal

# cent라는 컨테이너를 실행과 동시에 안으로 들어옴

  • Ctrl + PQ: 빠져나오고 컨테이너 동작 유지

# Ctrl + PQ로 빠져나오면 내가 열었던 터미널이 유지되어 컨테이너가 잘 동작한다.

  • 삭제

  • Ctrl + D: 프로세스 유지하지 않고 나옴

  • 포어그라운드로 동작하는 프로세스가 없기때문에, 컨테이너는 중지상태


myhttpd라는 이름을 갖는 컨테이너를 띄워보세요. 이 컨테이너를 백그라운드로 실행시키고, 컨테이너 이미지는 httpd:latest  이걸로 하세요.
root@host:~# docker run -d --name myhttpd httpd:latest

    • -d : detach, 백그라운드로 동작(호스트 입장에서)
이 컨테이너의 ip를 조회해서 curl을 해보세요.
root@host:~# docker inspect myhttpd | grep -i ipa

root@host:~# curl 172.17.0.2


  • bash라는 command로는 내부에서 httpd라는 프로세스가 정상적으로 동작하지 않음
root@host:~# docker run -it --name test httpd:latest bash

# test라는 컨테이너는 실행(run)되면서 bash라는 명령이 수행이 됐다. 근데 이 명령만으로는 내부에서 httpd라는 프로세스가 정상적으로 동작하지 않는다.

  • bash로 foreground가 동작하지 않음
root@host:~# docker run -it --name test httpd:latest bash
root@7d656a9c05bd:/usr/local/apache2#
root@host:~# curl 172.17.0.2
curl: (7) Failed to connect to 172.17.0.2 port 80: Connection refused
  • 내부에서 foreground 명령을 통해 동작시킴
root@host:~# docker run -it --name test httpd:latest bash
root@7d656a9c05bd:/usr/local/apache2# httpd-foreground

root@host:~# curl 172.17.0.2
<html><body><h1>It works!</h1></body></html>

# 컨테이너 내부에서 httpd-foreground라는 명령을 수행하면 외부에서 curl이 잘 된다.

  • 이미 동작중인 애한테 들어가겠다
root@host:~# docker exec -it test bash

 

# 이미 동작중인 컨테이너 내부로 진입해서 명령을 치겠다 = 콘솔접속하는느낌


실습)

httpd:latest 이미지로 이름이 index_http 라는 컨테이너를 만들고 이 컨테이너의 주소로 curl했을때 hello라는 기본페이지가 보이도록 해보세요.

방법1) 컨테이너 접속

root@host:~# docker run -d --name index_httpd httpd:latest

root@host:~# docker exec -it index_http bash
root@9260ca7f2409:/usr/local/apache2# cd htdocs/
root@9260ca7f2409:/usr/local/apache2/htdocs# echo hello > index.html

root@host:~# curl 172.17.0.2

방법2) 호스트에서 echo 명령 수행

root@host:~# docker exec index_httpd sh -c 'echo hello2 > /usr/local/apache2/htdocs/index.html'

# 특문(리다이렉션)이 없었으면 명령을 그대로 수행해도 되는데, 특문이 있어서 sh -c를 통해 수행할 명령어를 묶어줌.


실습2)

nginx:latest 이미지로 이름이 index_nginx 라는 컨테이너를 만들고 이 컨테이너의 주소로 curl했을때 hello라는 기본페이지가 보이도록 해보세요.
웹루트디렉토리는 검색해보세요!
  • /usr/share/nginx/html/index.html

방법1) 직접들어가서 echo

root@host:~# docker run -d --name index_nginx nginx:latest
e999d59ad03d2aba656c16106099d471c523af2be8338d566f66a618dc369108
root@host:~# docker exec -it index_nginx bash
root@e999d59ad03d:/# echo hello > /usr/share/nginx/html/index.html

방법2) 호스트에서 exec 실행

root@host:~# docker exec index_nginx sh -c 'echo hello_nginx2 > /usr/share/nginx/html/index.html'


  • docker rm -f $(docker ps -qa) : 모든 컨테이너 삭제
root@host:~# docker rm -f $(docker ps -qa)


docker volume (-v, ★★★★★)

컨테이너와 호스트를 마운트 개념

컨테이너의 경우, 어떤식으로든 컨테이너가 삭제되면 내부 데이터나 로그 등도 같이 삭제됨

볼륨으로 호스트에 파일을 업로드, 백업

ex. 데이터베이스 컨테이너를 띄웠는데, 갑자기 컨테이너가 날아가면 데이터도 다 날라감
→ 따라서 영구적으로 저장하고 싶은 데이터가 있으면 -v(볼륨)을 통해 host에 영구 저장할 수 있음

  • host
root@host:~# mkdir /host-txt
root@host:~# docker run -d --name vol_nginx -v /host-txt:/txt nginx:latest
root@host:~# echo vol-test > /host-txt/test.txt
  • 내부로 들어감
root@host:~# docker exec vol_nginx cat /txt/test.txt

# 컨테이너 내부의 /txt 경로에 test.txt가 있는걸 확인 가능

# 호스트 및 컨테이너 내부에 디렉토리가 없으면 자동으로 생성함


과제)

1. 호스트에 파일이 존재하는 경우
2. 마운트 포인트가 이미 존재하고 파일도 있는 경우
→ 두개가 어떤 차이가 있고, 어떻게 되는지 한번 생각해보세요.

실습)

-it 옵션이나 exec를 쓰지 않고 nginx:latest 이미지로 컨테이너를 생성했을때 vol_test라는 문구가 뜨도록 해보세요.
root@host:~# cd /host-txt
root@host:/host-txt# ls
root@host:/host-txt# echo vol_test > index.html
root@host:/host-txt# docker run -d --name my_nginx -v /host-txt:/usr/share/nginx/html nginx:latest
292bd0dfce16ce267caf13bc708a960b1a4af99d2516f5c27bdf0dccb60580d5
root@host:/host-txt# docker inspect my_nginx | grep -i ipa
root@host:/host-txt# curl 172.17.0.2


root@host:/host-txt#  docker run -it --name test nginx:latest bash
root@f981c26d04f1:/# cd /usr/share/nginx/html 
root@f981c26d04f1:/usr/share/nginx/html# ls

# 새로 nginx 컨테이너를 띄워서 내부로 진입하여, /usr/share/nginx/html로 가보니, 이미 index.html이 존재함

→ 기존에 파일이 존재하면, 호스트의 파일이 우선순위가 높다.

 

# 50x.html이 존재하지 않는 상황.  컨테이너 내부의 /usr/share/nginx/html이 바라보는 대상이 호스트의 /host-txt 라는 경로로 변경 됐다.

  • 전부 삭제
root@host:/# docker rm -f $(docker ps -qa)

Publish (-p, ★★★★★)

컨테이너를 외부에 노출, 배포(publish)시키는 옵션

root@host:/# docker run -d --name pub -p 8080:80 nginx:latest

# 일반적으로 웹서버는 80, python 장고는 8000, nodejs기반은 3000번 등등... 중요한건 컨테이너 내부에서 실제로 어떤포트로 서비스가 제공되는지가 제일 중요하고, 그 포트를 찾아서 publish해야 함

root@host:/host-txt# curl localhost:8080

# localhost = 211.183.3.100

# 실습, 테스트할때는 되도록이면 호스트의 웰노운포트는 쓰지 않는게 좋다. 만약에 최종적으로 웹서버를 배포하는거면 80번 포트같은 웰노운포트를 쓰는게 당연히 좋다.


실습)

호스트의  /host_vol 이라는 경로에는 간단한 댕댕이 템플릿이 존재한다. 이 무료템플릿을 호스트의 7979번 포트로 퍼블리쉬 해보세요.

풀이) httpd:latest

root@host:/# apt update -y && apt install -y unzip wget

  • 템플릿 다운
root@host:~# wget https://www.free-css.com/assets/files/free-css-templates/download/page8/dogcare.zip
root@host:~# unzip dogcare.zip
root@host:~# cd dogcare/
root@host:~/dogcare# mkdir /host_vol
root@host:~/dogcare# cp -r ./* /host_vol
root@host:~/dogcare# cd /host_vol/
root@host:/host_vol# docker run -d --name web -v /host_vol:/usr/local/apache2/htdocs -p 7979:80 httpd:latest


docker cp ()

1. 호스트의 파일을 컨테이너에 복사

# 최상위 디렉토리에 cptest.txt가 존재하는걸 확인가능

  • 컨테이너의 파일을 호스트에 복사


환경변수 지정 (-e, ★★★)

  • -it 말고, 처음에 생성할때 -d로 하는 것이 좋음
root@host:~# docker run -d --name envcon -e ENVTEST=test nginx:latest
root@host:~# docker exec -it envcon bash
root@4d58eebc87ad:/# echo $ENVTEST

# 데이터베이스 컨테이너의 경우엔 적어도 root 패스워드를 설정해야 컨테이너 잘 동작


작업디렉토리지정 (-w, ★★★★★)

root@host:~# docker run -d --name workdir nginx:latest
root@host:~# docker exec -it workdir bash

 

# 컨테이너에 진입했을때의 경로 = 현재작업디렉토리(pwd)

root@host:~# docker run -d --name workdir1 -w /usr nginx:latest

# 작업디렉토리를 /usr로 지정

root@host:~# docker exec -it workdir1 bash

# 나중에 중요

  • 도커 엔진 재시작
root@host:~# systemctl restart docker

# 동작중이던 컨테이너가 전부 stop

root@host:~# docker rm -f $(docker ps -qa)
  • --restart=always : 컨테이너 중지 시 항상 재시작
root@host:~# docker run -d --name retest --restart=always nginx:latest