docker

장현웅·2023년 11월 2일
0

도커(Doker)란?


Docker는 마치 매직 상자와 같은 도구로, 여러분의 웹 애플리케이션, 데이터베이스, 그리고 다른 소프트웨어를 안전하고 격리된 상자 안에 넣어서 운영할 수 있는 플랫폼입니다. 이 상자를 "컨테이너"라고 부릅니다.

컨테이너는 마치 작은 가상 머신처럼 동작하지만, 가상 머신보다 훨씬 가벼우며 빠르게 시작하고 실행할 수 있습니다. 컨테이너는 모든 필요한 것을 한 곳에 묶어놔서 애플리케이션을 어디서든 실행할 수 있게 합니다.

간단히 말하면, Docker는 여러분의 애플리케이션을 휴대용 상자 안에 넣어서 어디서든 실행할 수 있게 만들어주는 마법 상자입니다.

가상 머신과 컨테이너 환경의 차이

가상 머신과 컨테이너는 둘 다 컴퓨터 안의 컴퓨터 같은 것들이지만, 그들의 동작 방식과 사용 목적은 약간 다릅니다.

가상 머신은 마치 주택 건물을 하나 더 올려놓는 것과 비슷합니다. 각각의 주택(가상 머신)은 독립된 집이고, 각 집 안에는 자신만의 부엌, 욕실, 침실이 있습니다. 이것은 완전히 독립된 환경을 제공하지만, 집집마다 무거운 벽과 천장(가상 머신 운영 체제)을 가지고 있기 때문에 자원 소모가 큽니다.

컨테이너는 반면에, 마치 아파트의 여러 개의 방처럼 생각할 수 있습니다. 모든 방(컨테이너)은 같은 건물(호스트 운영 체제) 안에 있지만, 각 방은 독립된 환경을 가지며 자신만의 공간을 사용합니다. 이 방식은 훨씬 가볍고 빠르며, 여러 개의 방(컨테이너)을 하나의 건물(호스트 운영 체제) 안에서 쉽게 관리할 수 있습니다.

요약하면, 가상 머신은 완전히 독립적인 가상 운영 체제를 생성하는 반면, 컨테이너는 가볍고 빠르게 실행되며, 모든 컨테이너는 동일한 호스트 운영 체제를 공유합니다. 가상 머신은 더 많은 자원을 소모하지만 완전한 격리를 제공하고, 컨테이너는 빠르게 실행되고 더 경량적입니다.

Docker를 사용하는 이유

  1. 빠르고 쉬운 배포
    Docker를 사용하면 애플리케이션과 모든 종속성을 하나의 패키지로 만들어서 어디서든 빠르게 배포할 수 있습니다. 이것은 마치 애플리케이션을 상자 안에 넣어두고 필요한 때마다 꺼내서 실행하는 것과 비슷합니다.

  2. 안정적인 운영
    Docker 컨테이너는 독립적인 환경에서 실행되므로 서로 영향을 주지 않습니다. 하나의 컨테이너에서 문제가 발생해도 다른 컨테이너에 영향을 주지 않아서 애플리케이션 운영이 안정적입니다. 예를 들어, 프론트엔드와 백엔드가 각각 Docker 컨테이너로 실행되고 있을 때, 프론트엔드 컨테이너에서 발생한 문제가 백엔드 컨테이너에 영향을 주지 않습니다. 이것은 각각의 컨테이너가 독립적인 프로세스로 실행되고 격리된 환경을 가지고 있기 때문에 가능합니다.

  3. 환경 일관성
    Docker 이미지를 사용하면 애플리케이션을 실행하는 데 필요한 모든 설정, 라이브러리, 종속성, 환경 변수 등이 이미 하나의 패키지로 묶여 있음을 의미합니다. 이는 애플리케이션을 실행하는 환경이 항상 동일하다는 의미로 애플리케이션을 로컬 환경, 개발 서버, 테스트 서버, 프로덕션 서버 등 다양한 곳에서 실행할 때도 항상 동일한 환경에서 작동하게 됩니다. 이러한 일관성은 버그를 추적하고 문제를 해결하는 데 매우 유용합니다. 만약 어떤 문제가 발생하면, Docker 이미지의 설정이나 버전을 확인하여 어떤 변화가 있는지 파악할 수 있습니다.

Docker 이미지란?

Docker 이미지는 마치 애플리케이션을 설치하는 설치 프로그램처럼 생각할 수 있습니다.

예를 들어, 가상 머신에 윈도우 운영 체제를 설치할 때, 윈도우 ISO 파일을 사용해 설치하는 것과 비슷합니다. Docker 컨테이너도 생성될 때 실행되는 실행 파일, 설정, 라이브러리, 그리고 애플리케이션의 모든 부분을 포함하는 패키지인 이미지를 기반으로 생성됩니다.

이미지는 컨테이너가 실행될 때 필요한 모든 것을 이미 내장하고 있습니다. 예를 들어, Docker PostgreSQL 이미지는 컨테이너가 실행될 때 PostgreSQL 데이터베이스를 실행하는 데 필요한 모든 패키지와 설정을 이미 가지고 있습니다. 따라서 컨테이너를 시작하면 PostgreSQL 데이터베이스가 즉시 실행됩니다.

이러한 이미지를 사용하면 애플리케이션을 쉽게 패키징하고 배포할 수 있으며, 이미지가 컨테이너를 통해 애플리케이션을 실행하는 데 필요한 모든 것을 제공합니다. 그래서 Docker 이미지는 애플리케이션을 쉽게 이동하고 실행할 수 있는 편리한 방법입니다.

docker를 사용해 컨테이너를 생성하고 활용하는 방법


  1. docker 패키지 설치
sudo apt install docker.io -y

# 만약 아래와 같은 에러가 발생한다면
# E: Package 'docker.io' has no installation candidate
# sudo apt update 명령어 실행 후 docker 패키지를 다시 설치해주세요

  1. docker 설치 확인
sudo docker --version 		# 도커 버전이 출력되는지 확인합니다.

# Docker version 20.10.12, build 20.10.12-0ubuntu2~20.04.1 - 정상
# command not found: docker 와 같은 문구가 출력될 경우 docker가 설치되었는지 확인해야 합니다.

  1. docker 컨테이너 생성하기
sudo docker run -d -p 80:80 httpd:latest

# run : 이미지를 사용해 컨테이너를 실행시킵니다.
# -d : 컨테이너를 데몬(백그라운드)으로 실행시킵니다.
# 80:80 : 80번 포트로 접속했을 때 컨테이너에 접근할 수 있도록 포트포워딩 설정을 해줍니다.
# httpd:latest : httpd의 가장 최신 이미지를 사용해 컨테이너를 생성합니다.

  1. 실행중인 컨테이너 확인하기
sudo docker ps 		# 실행중인 컨테이너 목록 확인하기

sudo docker ps -a

# -a : 중지된 컨테이너 목록까지 포함해서 모두 확인하기

# CONTAINER ID : 컨테이너가 가지고 있는 고유한 id
# IMAGE : 컨테이너가 생성될 때 사용된 이미지
# COMMAND : 컨테이너가 생성될 때 실행되는 명령어
# CREATED : 생성 후 경과 시간
# STATUS : 컨테이너 상태
# PORTS : 사용중인 포트


Docker를 사용하여 httpd 이미지를 기반으로 컨테이너를 생성하면, 해당 이미지에는 Apache HTTP Server가 내장되어 있으며 기본적으로 웹 페이지를 서빙하는 역할을 합니다. 이렇게 생성한 컨테이너는 웹 서버 역할을 하게 됩니다.

쉽게 설명하면, Docker를 사용하면 마치 레고 블록을 조립하듯이 애플리케이션을 만들 수 있습니다. "httpd 이미지"는 웹 서버를 가지고 있는 레고 블록이라고 생각하면 되고, 이 레고 블록을 사용하여 컨테이너라는 상자를 만들고, 그 상자 안에는 웹 서버가 들어가 있습니다.

그리고 이 웹 서버를 운영하려면 AWS 같은 클라우드 환경에서 컨테이너를 실행하면 됩니다. SSH로 AWS에 접속해서 컨테이너가 실행되는 서버의 주소(aws에 SSH로 접속할 때 사용되는 IP)로 웹 브라우저를 열면, 마치 마법처럼 그 컨테이너 안에 들어있는 웹 페이지를 볼 수 있습니다.

이렇게 Docker를 사용하면, 애플리케이션을 손쉽게 만들고 배포할 수 있고, 동일한 이미지를 다양한 환경에서 실행해도 웹 페이지를 제공하는 일관된 결과를 얻을 수 있습니다.

  1. 다운받은 이미지 확인하기
sudo docker images

# REPOSITORY : 이미지 저장소 이름
# TAG : 이미지 버전
# IMAGE ID : 이미지의 고유한 id
# CREATED : 이미지 생성일(마지막 업데이트 일)
# SIZE : 이미지 용량

  1. 컨테이너 내부로 들어가보기

지금까지 Docker 이미지를 사용하여 Docker 컨테이너를 생성했습니다. 이렇게 생성한 컨테이너는 이미지에서 정의한 내용을 기반으로 실행되며, 독립적인 환경에서 애플리케이션을 실행할 수 있습니다.

이제 컨테이너 안으로 진입하려고 합니다. 쉽게 말하면, 이미지를 사용하여 만든 컨테이너가 포함된 "상자"에 들어가서 그 안에서 작업을 수행하고 싶다는 것입니다.

sudo docker exec -it $container_id /bin/bash

# $containser_id : sudo docker ps를 쳤을 때 확인되는 container_id를 입력합니다.
# /bin/bash : 컨테이너에 접속할 때 사용되는 쉘을 입력합니다.
# 일부 가벼운 이미지들은 /bin/bash 대신 /bin/sh 쉘을 사용하여 컨테이너 내부로 접속해야 합니다.

컨테이너에 접속해서 리스트를 보면 내부 파일들을 볼 수 있는데, 'htdocs'로 디렉토리를 이동해서 보면 'index.html'이라는 파일이 있는데, 이것을 cat명령어로 확인해보면 내용이 아까 웹페이지에서 봤던 내용임을 확인할 수 있습니다. 만약 이 문구를 변경하고 싶다면 이 파일을 교체해주면 됩니다.

docker-compose란?


Docker Compose는 Docker 컨테이너를 더 간편하게 관리하기 위한 도구입니다. Docker Compose를 사용하면 여러 개의 Docker 컨테이너를 한 번에 실행하고 관리할 수 있습니다.

쉽게 설명하자면, Docker 컨테이너는 독립적으로 실행되는 어플리케이션 단위라고 생각할 수 있습니다. 그런데 실제 어플리케이션은 종종 여러 컨테이너로 구성되며, 이러한 컨테이너들을 함께 실행하고 관리하는 작업이 필요합니다. Docker Compose는 이 작업을 간단하게 만들어줍니다.

docker run 명령어를 사용하여 컨테이너를 시작할 때는 각 컨테이너에 대한 설정 및 연결 관계를 명령어 옵션으로 일일이 지정해야 합니다.

sudo docker run -d --name web-app -p 80:80 my-web-app-image

sudo docker run -d -p 80:80 httpd:latest

Docker Compose를 사용하면, 하나 이상의 컨테이너를 정의하고 이 모든 컨테이너들 간의 관계 및 설정, 컨테이너에서 사용 될 이미지, 옵션 등을 한 docker-compose.yml(혹은 .yaml) 파일에 작성한 후 사용하게 됩니다. 이 파일을 사용하여 여러 컨테이너를 한 번에 실행하고 관리할 수 있습니다.

docker-compose.yml 파일을 사용하면 어떤 컨테이너를 실행할 것인지, 각 컨테이너의 이미지는 무엇인지, 어떤 포트를 매핑할 것인지, 컨테이너 간의 연결 및 의존성은 무엇인지 등을 한눈에 파악할 수 있습니다.

예를 들어, 웹 어플리케이션을 개발 중이고 이 어플리케이션은 웹 서버, 데이터베이스, 백그라운드 작업을 위한 컨테이너로 구성되어 있다고 가정해보겠습니다. Docker Compose를 사용하면 이 세 컨테이너를 간단하게 실행하고 서로 통신하도록 설정할 수 있습니다.

이렇게 Docker Compose를 사용하면 여러 컨테이너를 쉽게 관리하고, 개발 환경을 설정하거나 테스트 환경을 만들 때 편리하게 사용할 수 있습니다.

docker-compose 설치하기

docker compose를 설치하는 방식은 일반적인 패키지를 설치하는 방식과는 다릅니다. docker처럼 apt install로 패키지를 설치하는 것이 아니라 Compose는 Docker와는 별개의 독립적인 도구로서, docker에 plug-in을 설치하는 개념입니다.

  • 컨테이너 나가기 : Ctrl D
sudo mkdir -p /usr/lib/docker/cli-plugins
# /usr/lib/docker 경로에 cli-plugins라는 디렉토리를 생성합니다.
# -p : 만약 상위 디렉토리가 없다면 상위 디렉토리까지 함께 생성합니다.
# '-p'가 없다면 없는 디렉토리에 'cli-plugins'라는 디렉토리를 생성하려고 해서 에러가 발생합니다.
# sudo : /usr/lib/이라는 경로는 사용자 권한으로 컨트롤 할 수 없는 경로이기 때문에 관리자 권한으로 실행해줍니다.

sudo curl -SL https://github.com/docker/compose/releases/download/v2.11.2/docker-compose-linux-x86_64 -o /usr/lib/docker/cli-plugins/docker-compose
# github에 release 된 docker-compose 파일을 /usr/lib/docker/cli-plugins/ 경로에 다운로드 받습니다.
# v2.11.2는 docker-compose의 버전이며, 최신 버전은 여기서 확인 가능합니다.

sudo chmod +x /usr/lib/docker/cli-plugins/docker-compose
# 기본적으로 Linux에서는 파일에 대한 실행권한이 없기 때문에 다운받은 docker-compose 파일에 실행 권한을 부여해 줍니다.

sudo docker compose version
# docker-compose가 정상적으로 설치되었는지 확인합니다.
# Docker Compose version v2.11.2 정상적으로 설치 된 경우 버전이 출력됩니다.

docker-compose를 사용할 때 주의해야 할 점

docker-compose는 실행할 때 사용자가 작성한 docker-compose.yml 파일의 내용에 맞게 컨테이너를 설정하고 실행하게 됩니다.

때문에 docker-compose.yml 파일이 존재하지 않는 경로에서 docker compose 명령어를 실행시킬 경우 다음과 같은 에러가 발생할 수 있습니다.

docker 명령어가 아닌 docker-compose를 활용해 컨테이너를 생성하기


docker 컨테이너 삭제하기

sudo docker ps -a 	# docker에 존재하는 컨테이너 목록을 확인합니다.
					# -a : 만약 docker 컨테이너가 실행중이 아니라면 -a를 안해줄 경우 목록에 표시되지 않습니다.
# CONTAINER ID   IMAGE          COMMAND              CREATED        STATUS        PORTS                               NAMES
# 54445308314d   httpd:latest   "httpd-foreground"   23 hours ago   Up 23 hours   0.0.0.0:80->80/tcp, :::80->80/tcp   sweet_engelbart

sudo docker rm -f $container_id # 컨테이너의 실행중이면 컨테이너를 삭제할 수 없기 때문에 -f 명령어로 컨테이너 실행 여부와 관계없이 강제로 삭제합니다.
								# rm : 파일을 삭제하는 명령어인데 docker 뒤에 오면 컨테이너를 삭제하는 명령어로 작동합니다.

docker-compose.yml 작성해보기

docker 컨테이너를 생성하기 위해서는 가장 먼저 docker-compose.yml를 작성해야합니다.

# 파이썬과 같이 들여쓰기가 중요합니다.

version: '3.8' 				# docker-compose.yml에 사용될 문법 버전을 정의합니다. 버전에 따라 밑의 양식이 달라질 수 있습니다.

services:
  example: 					# 서비스 이름을 지정합니다. 서비스 이름은 컨테이너끼리 통신할 때 사용됩니다.
    container_name: example # 컨테이너 이름을 지정합니다.
    image: 'httpd:latest' 	# 컨테이너를 생성할 때 사용될 이미지를 지정합니다.
    restart: always 		# 컨테이너가 종료됐을 때 다시 실행시켜 줍니다.

컨테이너 실행시켜보기

# docker compose 명령어는 docker-compose.yml 파일이 존재하는 자리에서 실행해야 합니다. 
# docker-compose.yml 파일에 정의한 컨테이너의 정보를 참조해서 컨테이너를 생성합니다.

sudo docker compose up -d	# docker run이 아니라 docker compose로 실행시켜줍니다.
							# up : docker-compose.yml 파일을 읽어 정의된 서비스들을 실행시킵니다.
							# -d : 컨테이너를 데몬(백그라운드)으로 실행시킵니다.

컨테이너 중지시키기

💡 docker에서 컨테이너를 중지시킬 때 stop혹은 down 옵션을 사용할 수 있습니다.

두 옵션 모두 컨테이너를 중지할 때 사용되기 때문에 비슷해 보일 수 있지만 다른 용도로 사용되며, 사용 시 주의가 필요합니다.

우선 컨테이너를 stop 명령어를 사용해 중지시켜 보겠습니다.

sudo docker compose stop

이후 docker ps 명령어를 쳐보면 컨테이너 목록에 아무것도 보여지지 않습니다.

하지만 docker ps 명령어에 -a 옵션을 추가해 주면 중지 상태의 컨테이너를 확인할 수 있습니다.

sudo docker ps -a

sudo docker compose stop 명령어를 사용해 중지시킨 컨테이너는 sudo docker compose start 명령어로 다시 재시작해줄 수 있습니다.

sudo docker compose start

이번에는 docker compose down 명령어를 사용해 컨테이너를 중지시켜 보겠습니다.

sudo docker compose down

stop과는 다르게 컨테이너가 Removed 됐다는 로그를 확인할 수 있으며, 이와 같이 삭제 된 컨테이너는 docker ps -a 명령어로도 확인되지 않습니다.

sudo docker ps -a

즉, stop 명령어는 컨테이너를 완전히 삭제시키는 것이 아닌 중지 상태로 만드는 것이며, 이는 docker compose start 명령어로 다시 실행시킬 수 있습니다.

반면 down 명령어로 컨테이너를 종료시켰을 때에는 해당 컨테이너 자체가 삭제되어 컨테이너를 다시 실행시키기 위해서는 docker compose up 명령어로 컨테이너를 다시 생성해야 합니다.

그리고 down 명령어로 삭제한 컨테이너를 다시 살리기 위해서는 sudo docker compose up -d 명령어를 다시 실행해줘야 합니다.

docker compose stop 명령어로 컨테이너를 중지시키고 start 명령어로 다시 실행시키는 경우와는 다르게 down 명령어는 컨테이너를 삭제하고 up -d 명령어로 다시 생성해주는 것이기 때문에 컨테이너의 id가 다른 새로운 컨테이너가 목록에 표시됩니다.

컨테이너가 잘 실행되는지 웹 브라우저에서 확인해보기

이전에는 컨테이너를 생성했을 떄, 웹 브라우저에 aws의 SSH 접속에 사용되는 ip를 치고 들어가면 컨테이너에서 지정한 어떤 문구를 볼 수 있었는데 지금은 접속이 안됩니다. 이유는 포트포워딩 설정을 해주지 않았기 때문입니다.

일반적으로 컨테이너가 웹 서버를 실행하는 경우, 웹 서버는 특정 포트(일반적으로 80번 포트)에서 요청을 대기합니다. 그러나 외부의 호스트 컴퓨터에서 컨테이너의 웹 서버에 접근하려면 호스트 컴퓨터와 컨테이너 간의 포트 포워딩을 설정해야 합니다. 이렇게 설정하면 호스트 컴퓨터의 특정 포트(예: 호스트 컴퓨터의 8080번 포트)를 컨테이너의 80번 포트로 연결하고, 호스트 컴퓨터의 IP 주소와 포트로 접근하면 컨테이너의 웹 서버로 연결됩니다.

예를 들어, 호스트 컴퓨터에서의 IP 주소가 127.0.0.1이고 포트 포워딩을 설정하여 호스트 컴퓨터의 8080번 포트를 컨테이너의 80번 포트로 연결했다면, 웹 브라우저에서 "http://127.0.0.1:8080"으로 접근하면 컨테이너의 웹 서버로 연결됩니다.

컨테이너 포트 포워딩 설정하기

db, web, ssh 등 다양한 서비스는 기본적으로 가지고 있는 포트 번호가 존재합니다.

  • 포트 번호 예시
    • http : 80
    • https : 443
    • postgresql : 5432
    • django : 8000
    • ssh : 22

포트 포워딩이라는 이름과 같이, 외부에서 서버의 특정 포트에 접근했을 때 지정한 서비스로 전달해 주는 것을 의미합니다.

예를 들어 특정 컨테이너의 포트포워딩 설정을 80:8000과 같이 해줬다면, 외부에서 80 포트로 접속했을 때 해당 컨테이너의 8000번 포트로 접속하겠다는 의미입니다.

※ 웹 브라우저에서 naver.com과 같은 사이트에 접근할 때 https://www.naver.com과 같은 주소로 접근하게 되는데, http 프로토콜은 기본적으로 80 포트를 사용하고 https 프로토콜은 443 포트를 사용하게 되며 이는 웹 브라우저에 주소를 입력할 때 생략됩니다.

docker-compose.yml

version: '3.8' 					# docker-compose.yml에 사용될 문법 버전을 정의합니다.

services:
  example: 						# 서비스 이름을 지정합니다. 서비스 이름은 컨테이너끼리 통신할 때 사용됩니다.
    container_name: example 	# 컨테이너 이름을 지정합니다.
    image: 'httpd:latest' 		# 컨테이너를 생성할 때 사용될 이미지를 지정합니다.
    ports: 						# 포트포워딩을 설정해줍니다.
     - 80:80 					# 외부에서 80 포트로 접속했을 때 컨테이너의 80 포트로 연결해줍니다.
    restart: always 			# 컨테이너가 종료됐을 때 다시 실행시켜 줍니다.

컨테이너 실행시켜보기

sudo docker compose up -d

이제 docker run 명령어로 실행했을 때처럼 페이지가 잘 뜹니다.

컨테이너 로그 확인하기

docker로 앱을 배포하다 보면 배포가 정상적으로 되고 있는지 정상적으로 확인해야 하는 경우가 있습니다. 이 때, docker compose logs 명령어를 사용해 컨테이너가 정상적으로 동작하는지 확인할 수 있습니다.

sudo docker compose logs

이 때, -f 옵션을 추가하면 컨테이너가 동작하며 발생하는 로그를 실시간으로 확인할 수 있습니다.

sudo docker compose logs -f

※ -f 옵션을 사용한 경우 ctrl+c를 입력해 탈출할 때까지 해당 컨테이너의 로그를 실시간으로 출력합니다.

volume을 사용해 데이터 보존하기

--
docker 컨테이너는 컨테이너가 종료될 때 변경된 데이터는 모두 초기화 된다는 특징을 가지고 있습니다. 그러나 때로는 컨테이너의 데이터를 유지하고 여러 컨테이너 간에 데이터를 공유해야 할 수도 있습니다. 이럴 때 Docker에서 volume 옵션을 사용할 수 있습니다.

volume을 사용하지 않으면?

Docker 컨테이너에서 volume을 사용하지 않으면, 컨테이너가 종료될 때 변경된 데이터는 모두 초기화됩니다. 이는 컨테이너의 파일 시스템이 일시적이며 컨테이너가 실행 중인 동안에만 유효하기 때문인데, 데이터는 컨테이너 내부에 저장되며, 컨테이너가 삭제되거나 중지되면 데이터도 함께 사라집니다.

예를 들어, 데이터베이스 컨테이너를 실행한다고 가정해보면, 컨테이너 내부에서 생성된 데이터는 컨테이너가 중지되거나 삭제될 때 사라지게 됩니다. 이로 인해 데이터의 보존과 백업이 어려워집니다.

docker container 내부로 접근해서 테스트용으로 띄운 컨테이너에 접속했을 때 보여지는 문구를 변경해 보도록 하겠습니다.

우선, 컨테이너에 접속합니다.

sudo docker exec -it example /bin/bash

※ docker container에 접속할 때 container id 대신 container name을 사용할 수도 있습니다. example이라는 이름은 docker-compose.yml에서 container_name으로 지정한 명칭입니다.

컨테이너 내부로 접속했습니다. 이제 test라는 문구를 추가해보겠습니다.

echo "test" >> /usr/local/apache2/htdocs/index.html

# echo : 파일에 텍스트를 추가하는 명령입니다.

컨테이너 안에서 해당 명령어를 실행한 후 웹 브라우저에서 다시 접속해보면 test라는 문구가 추가된 것을 확인할 수 있습니다.

이제 exit 명령어를 사용해 컨테이너에서 나간 후

sudo docker compose down

sudo docker compose up -d

위 명령어를 사용해 컨테이너를 재시작 시키면, 조금 전 추가한 test라는 문구가 사라지는 것을 확인할 수 있습니다.

이렇게 docker는 기본적으로 컨테이너 내부에서 작업한 내용들이 컨테이너를 재시작하면 사라진다는 것을 확인해봤습니다.

volume이 해주는 역할

docker volume을 사용하게 되면 특정 경로에 있는 데이터들이 컨테이너 안에 저장되는게 아니라 호스트에 저장됩니다. 그리고 컨테이너에서는 호스트에서 데이터를 받아와서 사용하게 됩니다.

volume은 컨테이너에 저장되는 데이터의 일부를 host와 공유해 주는 역할을 합니다. host에 저장 된 데이터는 사용자가 직접 삭제하지 않는 이상 계속해서 유지되며, 때문에 컨테이너가 종료된다 하더라도 데이터는 유실되지 않습니다. 이와 같은 특성 덕분에, volume은 컨테이너 내부에서 변경되는 내용들을 유지해야 할 때 주로 사용됩니다.

또한, volume을 통해 여러 컨테이너가 동일한 데이터를 공유할 수 있습니다. 이를 통해 데이터베이스, 파일 공유, 로그, 설정 파일 및 기타 중요한 데이터를 여러 컨테이너 간에 공유하고 동기화할 수 있습니다.

docker volume 종류


docker volume의 종류는 docker volume, bind mount, tmpfs mount방식이 있으며 docker volume 방식이 주로 사용됩니다.

docker volume

  • docker 엔진이 관리하는 volume을 생성하는 방식입니다.
  • docker volume 방식을 사용해 생성된 volume은 host의 /var/lib/docker/volumes/ 경로에 저장됩니다.
  • 컨테이너가 종료되거나 삭제되더라도 데이터는 유지됩니다.
  • 컨테이너 간에 데이터를 공유하고 동기화할 수 있습니다.
  • 여러 드라이버(로컬, 네트워크, 클라우드 등)를 사용하여 데이터를 저장할 수 있습니다.
  • docker에서 가장 권장하는 방식입니다.

bind mount

  • docker volume 방식과 매우 유사합니다.
  • docker container를 생성할 때 사용자가 지정한 경로에 데이터가 저장됩니다.
  • docker 엔진의 관리를 받지 않는 영역이기 때문에 사용자가 직접 파일을 추가/수정/삭제 할 수 있다는 특징이 있습니다. docker 공식 문서에서는 이러한 특징으로 인해 운영에 영향을 미칠 수 있기 때문에 유사한 기능인 docker volume 방식을 사용하는 것을 권장하고 있습니다. 예를 들어, 컨테이너에서 호스트 파일을 삭제하면 호스트 컴퓨터에서도 해당 파일이 삭제됩니다.

tmpfs mount

  • 기존의 방식들이 ssd 혹은 hdd와 같은 저장장치에 데이터를 저장한다면, tmpfs mount 방식은 휘발성 메모리인 RAM에 데이터를 저장합니다.
  • 파일로 저장하면 안 되는 민감한 정보를 다룰 때 사용됩니다.

언제 어떤 방식의 volume을 사용해야 할까?

  • 각각의 방식들은 장/단점이 있기 때문에 필요에 맞게 사용해야 합니다.
  • Docker 볼륨 (Docker Volume)
    주로 컨테이너 간에 데이터를 공유하거나 중요한 데이터를 안전하게 관리해야 할 때나 데이터의 영속성이 필요한 경우에 사용됩니다. 컨테이너를 재시작하거나 삭제해도 데이터가 보존됩니다. 또한, Docker 엔진이 볼륨을 관리하므로 데이터의 격리와 관리가 용이합니다.
  • Bind Mount
    호스트와 컨테이너 간에 파일 또는 디렉토리를 공유하고 싶을 때나 컨테이너 내부에서 호스트의 파일 시스템을 직접 접근해야 하는 경우에 사용됩니다. 주로 개발 환경 또는 설정 파일을 컨테이너와 호스트 간에 공유할 때 활용됩니다.
  • tmpfs Mount
    데이터를 휘발성 메모리에 저장하고 싶을 때, 특히 보안적으로 중요한 데이터를 메모리에 저장하고 싶을 때나 임시 데이터 저장 및 빠른 읽기/쓰기가 필요한 경우에 유용합니다. 데이터는 메모리에서만 보존됩니다.
  • 자세한 내용은 docker 공식 문서에서 확인할 수 있습니다.

docker-compose.yml에 bind mount방식 적용해보기


version: '3.8' # docker-compose.yml에 사용될 문법 버전을 정의합니다.

services:
  example: 						# 서비스 이름을 지정합니다. 서비스 이름은 컨테이너끼리 통신할 때 사용됩니다.
    container_name: example 	# 컨테이너 이름을 지정합니다.
    image: 'httpd:latest' 		# 컨테이너를 생성할 때 사용될 이미지를 지정합니다.
    ports: 						# 포트포워딩을 설정해줍니다.
      - 80:80 					# 외부에서 80 포트로 접속했을 때 컨테이너의 80 포트로 연결해줍니다.
    volumes: 					# volume을 설정해줍니다.
      - ./example_http_code/:/usr/local/apache2/htdocs/ 	# 호스트 디렉토리(경로)를 컨테이너 내부의 디렉토리(경로)에 mount하라는 의미입니다. 즉, 두 디렉토리를 volume으로 잡아주겠다는 의미입니다.
      														# host에 docker compose 파일이 있는 디렉토리 또는 경로 : 컨테이너 index.html이 있는 디렉토리 또는 경로
    restart: always 			# 컨테이너가 종료됐을 때 다시 실행시켜 줍니다.


이것은 Docker 컨테이너와 호스트 간의 파일 공유를 설정하는 방법 중 하나입니다. 호스트 디렉토리를 컨테이너 내부의 디렉토리에 mount한다는 것은 호스트(또는 로컬 머신)의 파일 시스템 디렉토리를 Docker 컨테이너 내부의 디렉토리로 연결한다는 의미입니다. 이것은 파일 또는 데이터를 호스트와 컨테이너 간에 공유하고 서로 접근할 수 있도록 하는 것입니다.

호스트 디렉토리(예: ./example_http_code/)에 있는 파일과 디렉토리를 컨테이너 내부의 /usr/local/apache2/htdocs/ 디렉토리와 연결한다는 것은 호스트에서 파일을 수정하면 해당 변경 사항이 컨테이너에서도 반영되며, 그 반대의 경우도 마찬가지로 반영된다는 의미입니다.

이것은 개발 환경에서 코드 또는 데이터를 동기화하거나, 웹 서버 컨테이너에서 정적 웹 페이지를 호스트 파일 시스템에 배포할 때 유용합니다. 따라서 파일을 수정하고 저장할 때 컨테이너와 호스트 간에 파일이 공유되므로 변경 내용이 즉시 반영됩니다.

이제 volume으로 지정한 내용을 적용시켜보겠습니다.

 sudo docker compose up -d


아까는 'It work!'라는 문장이 나왔는데 지금은 'index of /'라는 문구로 잘못 나오고 있습니다. 그 이유는 volume을 설정해줄 때, host의 디렉토리 경로를 './' 현재 경로의 'example_http_code/'로 잡아줬습니다.

'example_http_code/' 디렉토리 안을 보면 'index.html'이 없습니다. volume 설정 시 host에서 비어있는 디렉토리를 지정해줬기 때문에 컨테이너 내부에도 비어있는 디렉토리가 mount된 것입니다.

호스트 디렉토리와 컨테이너 디렉토리 모두 비어있는 경우, 컨테이너에는 아무 파일도 존재하지 않게 되므로 "index of /"와 같이 디렉토리 목록을 보여주게 됩니다. 그래서 mount를 해줄 때는 mount 대상이 될 디렉토리(host)에서 실행될 코드나 파일이 필요합니다.

호스트 디렉토리에 웹 페이지 파일(예: "index.html")을 추가하고 컨테이너를 재시작하여 변경 내용을 적용해주겠습니다.

'sudo' 명령어를 붙여준 이유는 'example_http_code'라는 디렉토리가 docker에 의해 생성된 관리자 디렉토리이기 때문에 일반 사용자의 권한으로는 이 디렉토리 안에 파일을 만들거나 수정할 수 없습니다.


컨테이너 안에 'index.html'이 없기 때문에 못 불러왔었지만, host에서 'index.html'을 생성해주니 정상적으로 실행됩니다.

그럼 이번엔 컨테이너 안에서 수정해보기 위해 컨테이너에 진입하겠습니다.

컨테이너에서 수정한 내용도 host에서 수정된 것을 볼 수 있습니다.

다시 정리를 해보자면, bind mount 방식은 host 경로에 있는 파일이나 디렉토리를 컨테이너 내부에 공유해주는 방식이라고 생각하면 됩니다. 그래서 host에서 수정한 내용이 컨테이너에 반영이 되고, 컨테이너 내부에서 수정한 내용도 host에도 반영이 됩니다.

docker-compose.yml에 docker volume방식 적용해보기

version: '3.8' 					# docker-compose.yml에 사용될 문법 버전을 정의합니다.

volumes:
  example_http_code: {} 		# docker volume을 정의합니다.
  								# {} : volume 생성할 때, 호스트의 디렉토리를 컨테이너 내의 볼륨에 연결하는 등의 설정을 넣어줍니다.

services:
  example: 						# 서비스 이름을 지정합니다. 서비스 이름은 컨테이너끼리 통신할 때 사용됩니다.
    container_name: example 	# 컨테이너 이름을 지정합니다.
    image: 'httpd:latest' 		# 컨테이너를 생성할 때 사용될 이미지를 지정합니다.
    ports: 						# 포트포워딩을 설정해줍니다.
      - 80:80 					# 외부에서 80 포트로 접속했을 때 컨테이너의 80 포트로 연결해줍니다.
    volumes: 					# volume을 성정해줍니다.
      - example_http_code:/usr/local/apache2/htdocs/ 	# 정의한 volume의 mount할 경로를 지정합니다.
    restart: always 			# 컨테이너가 종료됐을 때 다시 실행시켜 줍니다.

bind mount 방식에서는 호스트의 파일 시스템 경로를 직접 지정합니다. 따라서 Docker Compose 파일의 volumes 섹션에서 호스트의 디렉토리 경로를 왼쪽에 지정하고, 컨테이너 내의 경로를 오른쪽에 지정합니다. 이것은 호스트 디렉토리와 컨테이너 간의 직접적인 연결을 생성합니다.

volumes:
  - /host/directory:/container/directory

docker volume 방식에서는 볼륨 이름을 먼저 정의하고, 그 이름을 사용하여 컨테이너와 호스트 간에 볼륨을 공유합니다. 볼륨 이름은 Docker Compose 파일의 volumes 섹션에서 정의되며, 그런 다음 컨테이너 설정에서 해당 볼륨을 참조합니다.

volumes:
  my_volume_name: {}

services:
  my_service:
    volumes:
      - my_volume_name:/container/directory

docker volume 방식에서는 볼륨을 먼저 생성한 다음, 해당 볼륨을 컨테이너 내부의 디렉토리로 마운트(mount)하여 사용합니다.

이제 docker-compose up -d 명령어로 docker-compose.yml 파일을 기반으로 Docker Compose를 실행시켜주면, 볼륨이 생성되고 컨테이너와 연결됩니다.

그 다음 웹 페이지에서 다시 aws public ip로 접속해보면, 처음으로 돌아왔습니다.

그 이유는, 조금 전에 만든 'example_http_code' 디렉토리 안의 index.html을 참조한 것이 아니라 docker 영역의 volume을 따로 생성해서 호스트 디렉토리의 데이터가 volume으로 mount되었기 때문입니다.

docker volume 방식은 bind mount 방식과는 다르게 원래 있던 내용이 덮어써져서 사라지거나 하지는 않습니다. volume은 컨테이너에 저장되는 데이터의 일부를 host와 공유해 주는 역할을 합니다. host에 저장 된 데이터는 사용자가 직접 삭제하지 않는 이상 계속해서 유지되며, 때문에 컨테이너가 종료된다 하더라도 데이터는 유실되지 않습니다.

이 상태로 처음 해줬던 테스트를 다시 해보면,

docker 컨테이너 내부로 들어가서, 컨테이너 내부의 웹 페이지 파일 index.html에 "haha"라는 텍스트를 추가하고, 컨테이너에서 나와서 컨테이너를 중지&삭제한 후 다시 실행해도 데이터가 삭제되지 않고 남아있습니다.

volume을 사용한 경우에는 컨테이너가 삭제되어도 데이터가 유지됩니다. volume은 데이터를 컨테이너와 독립적으로 보관하며, 컨테이너가 종료 또는 삭제되어도 해당 볼륨의 데이터는 보존됩니다.

docker volume 방식을 사용해 생성된 volume은 host의 /var/lib/docker/volumes/ 경로에 저장됩니다. volume을 확인하기 위해 이 경로에 리스트를 보려고 하면 'Permission denied' 에러가 발생합니다. 이 경로는 사용자 권한으로는 들여다 볼 수 없고, sudo 명령어를 통해 관리자 권한으로 확인해야합니다.

보통은 아래 방식으로 volume을 확인합니다.

sudo docker volume ls

volume의 자세한 내용을 확인하고 싶을 때는 아래의 방식으로 확인합니다.

sudo docker volume inspect 볼륨 name

사용하지 않는 docker volume 삭제하기

docker volume방식으로 생성한 컨테이너는 docker compose down 혹은 docker rm 명령어로 컨테이너를 삭제해도, docker volume은 삭제되지 않고 남아있게 됩니다.

이러한 데이터들이 지속적으로 쌓일 경우 불필요한 리소스 낭비가 생길 수 있어 주기적으로 정리해주는 습관을 들이는게 좋습니다.

아래 명령어를 활용해 사용되지 않는 docker volume들을 일괄적으로 삭제할 수 있습니다.

sudo docker volume prune

Dockerfile을 활용해 직접 이미지 빌드하기


Dockerfile 이란?

Dockerfile은 컨테이너를 만들기 위한 레시피 같은 것입니다. 컨테이너는 프로그램과 그 프로그램이 동작할 환경을 함께 갖고 있습니다. 그래서 Dockerfile을 사용하면, 원하는 프로그램과 환경을 결합해서 컨테이너를 만들 수 있습니다.

Dockerfile은 원하는 프로그램을 만들기 위해 필요한 단계와 명령을 나열하는 파일이라고 생각하면 됩니다. 그럼 Docker는 이 레시피를 따라 컨테이너를 만듭니다.

예를 들어, 만약 웹 애플리케이션을 만들고자 한다면, Ubuntu 또는 Alpine Linux와 같은 기본 이미지를 선택하고, Dockerfile에 웹 서버 소프트웨어 설치, 애플리케이션 코드 추가, 포트 설정 등의 단계를 작성합니다. 그러면 Docker는 이 레시피를 따라 웹 애플리케이션을 컨테이너로 만들어 줍니다. 그리고 이 컨테이너는 어디에서든 실행할 수 있습니다.

Dockerfile을 사용하면 어떤 앱이든 컨테이너로 만들 수 있고, 그것을 어디에서든 배포하고 실행할 수 있습니다.

Dockerfile은 언제 사용되나

  1. 애플리케이션 배포
    Dockerfile은 컨테이너화된 애플리케이션을 만들기 위한 핵심 도구로 사용됩니다. 개발한 애플리케이션을 독립적인 환경인 컨테이너에 담아 배포할 때 Dockerfile을 사용합니다.

  2. 환경 구성
    웹 서버, 데이터베이스, 웹 애플리케이션 서버 등의 서비스를 실행하는 환경을 Dockerfile로 정의할 수 있습니다. 이렇게 하면 다른 환경에서도 동일한 설정을 사용할 수 있습니다.

  3. 패키지 설치
    Dockerfile을 사용하여 이미지에 필요한 소프트웨어나 패키지를 설치할 수 있습니다. 예를 들어, 웹 애플리케이션을 Docker 컨테이너로 배포할 때, 필요한 웹 서버 소프트웨어를 Dockerfile에서 설치할 수 있습니다. 예를 들어, docker에서 django를 배포한다고 가정 했을 때, 기본 python 이미지를 불러온 후 django 패키지를 pip install 한 후 이미지를 생성하게 됩니다.

  4. 파일 추가
    Dockerfile을 사용하여 이미지에 특정 파일을 추가하거나 복사할 수 있습니다. 이를 통해 구성 파일, 애플리케이션 코드, 정적 파일 등을 이미지에 포함시킬 수 있습니다.

  5. 컨테이너 초기화 스크립트
    Dockerfile에서는 컨테이너가 시작될 때 실행되는 초기화 스크립트를 정의할 수 있습니다. 이를 통해 컨테이너가 실행되면 자동으로 실행해야 하는 작업을 설정할 수 있습니다.

요약하면, Dockerfile은 Docker 이미지를 만들기 위한 설계 도구로, 애플리케이션 및 환경 설정, 소프트웨어 설치, 파일 추가 등 다양한 작업을 수행하는 데 사용됩니다. 이렇게 만들어진 이미지는 어디서든 동일하게 실행될 수 있습니다.

html 파일을 컨테이너 이미지에 집어넣기

1. Dockerfile 작성

Dockerfile을 작성할 때는, docker compose 파일을 작성할 때처럼 파일 이름이 다르면 인식하지 못합니다.

vi Dockerfile

# 빌드할 때 사용할 이미지를 지정해줍니다.
FROM httpd:latest

# 현재 경로에 존재하는 index.html 파일을 컨테이너 내부의 /usr/local/apache2/htdocs/index.html로 복사합니다.
# 컨테이너 내부에 index.html이 없을 경우 새로 생성되고, 있을 경우 덮어쓰기가 됩니다.
COPY ./index.html /usr/local/apache2/htdocs/index.html



Dockerfile을 생성했습니다.

Dockerfile에서 자주 사용되는 문법

# 빌드할 때 사용할 이미지를 지정합니다.
FROM httpd:latest

# 지정한 명령어를 실행합니다.
RUN mkdir /app/

# docker 이미지에서 사용할 기본 경로를 지정합니다.
WORKDIR /app/

# 지정한 파일을 컨테이너 내부로 복사합니다.
COPY ./app/ /app/

# 이미지에서 사용될 환경변수를 지정해줍니다.
ENV DEBUG 1

docker-compose.yml

version: '3.8' 					# docker-compose.yml에 사용될 문법 버전을 정의합니다.

services:
  example: 						# 서비스 이름을 지정합니다. 서비스 이름은 컨테이너끼리 통신할 때 사용됩니다.
    container_name: example 	# 컨테이너 이름을 지정합니다.
    build: . 					# 현재 경로에 있는 Dockerfile을 사용해 이미지를 생성합니다.
    ports: 						# 포트포워딩을 설정해줍니다.
      - 80:80 					# 외부에서 80 포트로 접속했을 때 컨테이너의 80 포트로 연결해줍니다.
    restart: always 			# 컨테이너가 종료됐을 때 다시 실행시켜 줍니다.


index.html을 직접 만들고 docker 실행(sudo docker compose up -d)해보기

Dockerfile을 작성할 때 ./index.html 파일을 복사하도록 지정해 줬기 때문에, 동일한 이름으로 해당 파일을 생성하고 편집합니다.

vi index.html 		# 내용 추가

sudo docker compose up --build -d


파일이 컨테이너에 잘 들어갔는지 확인해줍니다.

docker-compose.yml 파일을 기반으로 컨테이너를 시작합니다.

컨테이너를 중지/삭제했다가 다시 실행해도 build를 따로 안하는 것을 볼 수 있습니다.

※ Dockerfile로 빌드된 이미지가 없을 경우, --build 옵션을 추가하지 않더라도 이미지를 빌드하고 그 이미지를 사용해서 컨테이너를 띄워줍니다. 만약 Dockerfile로 빌드된 이미지가 있을 경우에는 새로 빌드하지 않고 기존에 빌드 된 이미지를 사용해서 컨테이너를 생성합니다. 그래서 Dockerfile을 아무리 수정해도 기존에 빌드된 이미지가 있으면 Docker compose up -d 명령어로는 이미지 빌드를 안해줍니다. Dockerfile을 수정하고 이미지를 새로 빌드해야 할 때는 --build 옵션을 추가해서 사용해야 합니다. 정리하자면, 이 --build 옵션은 이미지를 빌드해서 컨테이너를 생성하겠다는 의미입니다.

컨테이너가 생성된 것도 확인할 수 있습니다.

이제 웹페이지에서 aws public ip로 접속해줍니다.

이전까지는 volume을 사용해서 index.html 파일을 관리했지만 지금은 Dockerfile에서 이미지를 빌드하는 과정을 통해 index.html 파일을 컨테이너에 넣어줬습니다.

0개의 댓글