CLOUDWAVE

Dokcer Practice : Docker Volume, Docker Network, Docker Advance ( 2025.01.02 )

갬짱 2025. 1. 14. 21:12

Doker 볼륨

격리된 환경에서 각 컨테이너들은 각자의 컨테이너 레이어(R/W 레이어)에 데이터를 저장한다. 컨테이너간 데이터를 교류할 방안, 장치가 필요해진다. 또한 컨테이너 레이어에 작성된 데이터는 휘발가능성이 높기에 추가적으로 저장보존해야할 필요가 있다.

 

볼륨(Volume) : 컨테이너의 라이프사이클에 종속되지 않는 외부 저장장치 ⇒ 컨테이너의 생존여부 관계없이 관리됨

  • 컨테이너에 mount 하여 자료를 옮기거나 보존이 가능(일종의 USB)
  • 호스트의 변경상황을 내부로 전파가능(일종의 port)

특정 디렉토리에 볼륨을 mount한 이후에는 로컬의 스토리지를 쓰는 것이라고 인식하지만 실질적으로 공유 스토리지(볼륨)에 쓰게 된다.

 

DB 클러스터 작업과 유사한 형태

container1(MasterDB) : 쓰기작업 → volume

container2(SlaveDB) : volume → 읽기작업

 

볼륨 생성

docker volume [option] create

—name 옵션 : 볼륨의 옵션을 지정, 수행하지 않으면 해시값으로 자동지정 ⇒ 사용이 용이하도록 반드시 지정

 

볼륨 목록보기

docker volume ls

# 사용하지 않는 볼륨목록 확인
docker volume ls -f "dangling=true" # or "dangling=1"

DRIVER 항목 : 해당 볼륨이 데이터를 저장하는 방식 및 위치를 결정

 

볼륨 정보보기

inspect : 세부사항확인 명령어

 

볼륨 삭제

docker volume rm VOLUME1 ...

이름/아이디를 이용하여 삭제

 

docker volumen prune

사용하지 않는 모든 anonymous 볼륨( 마운트한 컨테이너가 하나도 없을경우 )을 삭제

 

볼륨 이용(마운트)

docker run -v 볼륨명:컨테이너내부위치

컨테이너를 run할때 -v옵션으로 즉시 mount가능 -v 사용할볼륨 : 마운트할 위치 ( -p 외부의 접근포트 : 내부의 포트 와 유사한 형태 )

 

[ 연습문제 ] Volume에 DB 데이터 저장하기

postgres:16.1-bullseye 이미지를 사용합니다. 컨테이너의 이름은 psql_db 로 설정합니다. 컨테이너 생성시 다음 환경변수가 설정되어야 합니다. ( POSTGRES_PASSWORD ) 생성한 Volume은 컨테이너의 /var/lib/postgresql/data 에 마운트합니다.

  • db_data 볼륨 생성 → postgres:16.1-bullseye 이미지를 컨테이너화와 동시에 볼륨 마운트 psql의 데이터가 존재하는 /var/lib/postgresql/data에 마운트 -v db_data:/var/lib/postgresql/data
  • main process는 DB를 구동중 + exec으로 하위 프로세스를 생성하여 터미널에 접근 & psql접속하여 질의하기
  • DB컨테이너에서 테이블을 생성하기 ( 데이터는 컨테이너의 휘발성 레이어가 아닌 마운트된 볼륨에 저장 )

 

DB컨테이너 종료후 다시구동 → 기존 컨테이너가 종료되었지만 그 데이터가 여전히 남아있음

 

 

[ 연습문제 ] bind mount 를 사용하여 소스코드 변경하기

Host의 ./app 는 컨테이너의 /code/app 와 바인드 되어야 합니다.
이미지 이름은 was 로 설정합니다.

[ 폴더구조 ]

├──CLOUDWAVE
│	 ├── app
│	 │ ├── __init__.py
│	 │ └── main.py
│	 ├── Dockerfile
│	 └── requirements.txt

이와 같은 bind mount는 컨테이너 내부의 log를 host로 빼낼 때 주로 사용한다.

또 다른 용도로는 실시간으로 업데이트하고 로컬에서 테스트할 수 있다. ( 파이썬, 노드와 같은 인터프리터 언어 → 컴파일을 즉시 실행 )

 

< main.py >

from typing import Union
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
	return {"Hello": "World"}

 

< Dockerfile >

FROM python:3.9
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--reload"]

파이썬 베이스 이미지로사용 → 컨테이너 내부 /code에서부터 작업

RUN : pip install 명령어를 실행하여 requirements.txt에 정의된 Python 패키지를 설치

CMD : uvicorn = Python의 ASGI 서버로, FastAPI 애플리케이션을 실행하는 데 사용

 

docker build -t was:fast.1 -f ./CLOUDWAVE/Dockerfile CLOUDWAVE

Dockerfile이 위치한 폴더(./CLOUDWAVE)에서 다음 명령어를 실행하여 이미지를 빌드

 

docker run --name bind -p 80:80 -v .\\CLOUDWAVE\\app:/code/app was:fast.1

-v 윈도우(local)의 저장소위치 : 컨테이너 내부 마운트할 위치

-p 윈도우(local)의 외부포트 : 컨테이너 내부포트

이미지를 컨테이너화와 동시에 볼륨을 마운트

내부 코드가 작성되는 공간에 마운트하여 지속적으로 코드를 업데이트

 

 

 

API 코드 추가

@app.get("/hostname")
def get_hostname():
    return {"name": socket.gethostname()}

 

 

[ docker volume 정리 ]

volume : container layer가 아닌 외부 저장공간

docker run -v src:mount_PATH (-v 여러개 또 가능) IMAGE <command>

mount_PATH는 주로 절대경로, src는 경로 또는 이름을 적음

 

source(src)의 종류

(1) docker volume

(2) host file system

*주의사항 : src에 .을 적어서 구분하지 않는다면 볼륨으로 인식, file system의 것을 연결하고 싶다면 상대경로(.)혹은 절대경로(/)로 시작해서 명시해야함

 

볼륨은 하나의 파일시스템임 볼륨중 일부만 마운트하는 것은 불가, 볼륨 전체를 마운트하고 움직여야함

  • docker Volume A ⇒ /data, /tmp
  • Container ⇒ /data, /tmp … /mnt

Volume A 전체를 마운트해야함~!!

 

dev container : 소스코드를 Docker 컨테이너의 볼륨으로 연결(mount) 하여 사용하는 방식 ⇒ 즉시 실행하고 테스트해는 용도로 만듬, 코드를 실행시킬 수 있는 환경만 만들어서 배포

실질적으로 소스코드를 주입시켜준것이 없음, 즉각적으로 반영해서 테스트해보는 것이다!

보안에 취약하지만 내부용, 개발용으로 사용하기에 적합하다!

Host의 ./app 는 컨테이너의 /code/app 와 바인드 되어야 합니다.
이미지 이름은 was 로 설정합니다.

[ 폴더구조 ]

├──CLOUDWAVE
│	 ├── app
│	 │ ├── __init__.py
│	 │ └── main.py
│	 ├── Dockerfile
│	 └── requirements.txt

이와 같은 bind mount는 컨테이너 내부의 log를 host로 빼낼 때 주로 사용한다.

또 다른 용도로는 실시간으로 업데이트하고 로컬에서 테스트할 수 있다. ( 파이썬, 노드와 같은 인터프리터 언어 → 컴파일을 즉시 실행 )

main.py

from typing import Union
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
	return {"Hello": "World"}

Dockerfile

FROM python:3.9
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--reload"]

파이썬 베이스 이미지로사용 → 컨테이너 내부 /code에서부터 작업

RUN : pip install 명령어를 실행하여 requirements.txt에 정의된 Python 패키지를 설치 CMD : uvicorn = Python의 ASGI 서버로, FastAPI 애플리케이션을 실행하는 데 사용

docker build -t was:fast.1 -f ./CLOUDWAVE/Dockerfile CLOUDWAVE

Dockerfile이 위치한 폴더(./CLOUDWAVE)에서 다음 명령어를 실행하여 이미지를 빌드

docker run --name bind -p 80:80 -v .\\CLOUDWAVE\\app:/code/app was:fast.1

-v 윈도우(local)의 저장소위치 : 컨테이너 내부 마운트할 위치

-p 윈도우(local)의 외부포트 : 컨테이너 내부포트

이미지를 컨테이너화와 동시에 볼륨을 마운트

내부 코드가 작성되는 공간에 마운트하여 지속적으로 코드를 업데이트

API 코드 추가

@app.get("/hostname")
def get_hostname():
    return {"name": socket.gethostname()}

[ docker volume 정리 ]

volume : container layer가 아닌 외부 저장공간

docker run -v src:mount_PATH (-v 여러개 또 가능) IMAGE <command>

mount_PATH는 주로 절대경로, src는 경로 또는 이름을 적음

source(src)의 종류

(1) docker volume

(2) host file system

*주의사항 : src에 .을 적어서 구분하지 않는다면 볼륨으로 인식, file system의 것을 연결하고 싶다면 상대경로(.)혹은 절대경로(/)로 시작해서 명시해야함

볼륨은 하나의 파일시스템임 볼륨중 일부만 마운트하는 것은 불가, 볼륨 전체를 마운트하고 움직여야함

  • docker Volume A ⇒ /data, /tmp
  • Container ⇒ /data, /tmp … /mnt

Volume A 전체를 마운트해야함~!!

dev container : 소스코드를 Docker 컨테이너의 볼륨으로 연결(mount) 하여 사용하는 방식 ⇒ 즉시 실행하고 테스트해는 용도로 만듬, 코드를 실행시킬 수 있는 환경만 만들어서 배포

실질적으로 소스코드를 주입시켜준것이 없음, 즉각적으로 반영해서 테스트해보는 것이다!

보안에 취약하지만 내부용, 개발용으로 사용하기에 적합하다!

 


 

Docker 네트워크

Docker 네트워크 드라이버 ( 3가지 ) : Docker 컨테이너 간의 네트워크 통신 방식을 설정하고 관리하는 도구

  • bridge : 단일 호스트 내에서 컨테이너간 통신을 위해 사용 호스트 내부에 개별적인 네트워크(bridge)를 갖춘 형태로 가장 많이 사용함 ( ex. front와 DB는 독자적인 네트워크를 구성하고 back을 브릿지로 구성하면 공유영역으로 사용가능 )
  • host : 컨테이너가 호스트와 동일한 네트워크를 사용 별도의 독자적인 네트워크를 구축하지 않음, 프로세스를 하나 띄운다는 개념 개발 시에 포트포워딩 작업이 번거로울때만 사용
    ** 호스트 네트워크는 인스턴스 별로 한개만 생성, 기본적으로 만들어질때 생성
  • none( isolate ) : 호스트와 완벽하게 격리된 네트워크 컨테이너의 네트워크에 ip할당을 하지 않음 나가는 것도 안됨, 들어오는 것도 안됨 → 완벽한 보안

하나의 컨테이너에 여러 네트워크가 할당될 수 있음

 

네트워크 생성

docker network create -d 드라이버지정 네트워크이름

 -d : 드라이버를 선언, 기본적으로 -d = bridge ( 고정적 )

 

 

[예시] bridge 네트워크 생성하기

(1) 호스트 네트워크는 한개만 생성가능, 이미 생성된 형태

(2) private용 브릿지 네트워크 생성

docker network create -d bridge private

Docker를 설치하고 바로 사용할 수 있는 네트워크 드라이버는 bridge, host, none의 3가지 + 새로 생성한 네트워크

 

네트워크 연결

connect : 특정 컨테이너에 네트워크를 부여하는 작업 ( ↔ disconnect : 네트워크 연결 제거 )

doker network connect 네트워크이름 컨테이너이름/아이디
  • —ip : IPv4 주소를 지정 → 거의 없음
  • —alias : 접근용도로 명칭을 붙임

 

네트워크 삭제

  • docker network rm 네트워크명
  • docker network prune : 사용하지 않는 네트워크 삭제

 

[ 연습문제 ] bridge 네트워크를 이용하여 컨테이너 연결하기

(1) 사용할 네트워크를 생성

docker network -d bridge create private

 

(2) 컨테이너 생성
컨테이너 PostgreSQL(db) : DB 서비스 → 네트워크 지정X, 고유한 네트워크를 자동적으로 별도생성하여 사용

  • 컨테이너 PgAdmin(pgadmin) : 웹상으로 DB를 제어하는 서비스
    • 기존 네트워크를 지정 ( —network옵션 )
    • 외부접근 포트를 설정( 1080 ) ⇒ 내부 포트 80과 매핑

기본적으로 두 컨테이너는 통신이 불가한 상태

 

컨테이너 db의 네트워크 확인( docker inspect db )

 

컨테이너pgadmin(localhost:1080)에서 컨테이너 db(172.17.0.3)에 접속하여 아이디, 비밀번호로 로그인하여 서버를 connect하려고 하였지만 실패하였다!

 

docker network connect private db
docker inspect db

기본적으로 할당된 네트워크 이외에 private이라는 bridge네트워크가 설정되었다. ( 기본설정값 bridge )

이후 브릿지 네트워크 private에 db와 pgadmin 컨테이너가 모두 할당된 상태이므로 통신이 가능해진다.

 

[ 연습문제2 ] alias 를 이용하여 ip 없이 컨테이너 통신하기

(1) ubuntu 컨테이너를 생성하고 필요한 Package 를 생성합니다.

 

(2) nginx:latest이미지를 가지고 컨테이너를 3개 생성 ( nginx1, nginx2, nginx3 )

—net-alias : 특정 네트워크 내에서 해당 컨테이너를 가리키는 별칭을 설정 ⇒ 같은 네트워크에 속하는 컨테이너들 간에 서로를 구분할 수 있는 이름을 지정, 중복가능

네트워크 내에서 컨테이너를 참조할 수 있는 별칭을 설정하는 목적이지만 그 시점에 따라

  • -net-alias: docker run 명령어로 컨테이너를 실행할 때
  • -alias: docker network connect 명령어로 실행 중인 컨테이너를 네트워크에 연결할 때

모두 private네트워크(bridge)에 연결 ⇒ web_app으로 공통명칭지정 & nginx3에는 추가 명칭 ready를 부여

 

(3) main 컨테이너에서 web_app 에 대해 dig 을 사용하면, 다음과 같이 DNS 질의에 대한 응답이 없음

dig(Domain Information Groper) : DNS(Domain Name System) 관련 정보를 조회할 수 있는 명령어, DNS 서버에 질의를 보내고 그 응답(도메인의 IP 주소, DNS 레코드, TTL(Time to Live) 등의 정보)을 텍스트 형식으로 반환

 

(4) main 컨테이너에 private 네트워크 연결 & 별칭 main을 부여

docker network connect --alias main private main

main 네트워크를 조회(docker inspect main)

private 네트워크의 IP 주소 범위는 172.18.0.0/16이고, 그 안에서 해당 컨테이너(네트워크 별칭 main)에 할당된 IP 주소는 172.18.0.7임을 확인

 

(5) 다시 main 컨테이너에서 web_app 에 대해 dig 을 사용하면, 다음과 같이 DNS에 대한 응답이 3개가 존재함

⇒ main 컨테이너가 private 네트워크 영역에 포함되었기에 네트워크 내부 별칭 web_app에 접근가능

 

(6) 실제로 main컨테이너에서 web_app에 ping하였을때마다 다른 서버(ip)가 응답을 함 ⇒ 로드밸런싱 효과

 

alias를 통한 LB를 쓰는 이유

(1) IP없이 통신가능 ⇒ alias를 이용( web app에 ping )

web_app이 일종의 도메인이라고 가정 ⇒ 이에 접근한다는 것은 nginx서버의 응답을 원함

(2) 기초적인 LB기능 : 서비스를 구성하는 수많은 서버의 개수가 정해지지 않음, LB에만 접근하면 알아서 가능한 서버에 분산시켜 줌

=> 서버에 접근한다는 개념이 아닌 서비스에 접근한다는 개념( 외부에서는 내부 설계를 고려하지 않고 접속가능 )

 

[ 실습 ] DB 를 alias 를 이용하여 연결하기

연습 문제( bridge 네트워크를 이용하여 컨테이너 연결하기)에서 IP 대신 DNS 를 이용하세요 PostgreSQL 과 PgAdmin 컨테이너를 생성합니다 private 네트워크를 DB 에 연결하면서 Alias 를 설정합니다. PgAdmin 에서 IP 대신 Alias 를 이용하여 DB 에 연결합니다
docker network disconnect private db
docker network connect --alias db private db

 

 

[ 외부에서 DB연결하기 ]

이미 만들어진 컨테이너에 대해 새로 포트를 열거나 변경하려면 기본적으로 컨테이너를 재생성해야 합니다. Docker는 컨테이너 실행 중에는 포트 매핑(포워딩)을 동적으로 수정할 수 없기 때문입니다.

  • 사용자 정의 네트워크, SSH우회방법
C:\\Users\\KHP>docker run -d -p 2222:80 --net private --net-alias db --name db -e POSTGRES_PASSWORD=1234 postgres:16.1-bullseye
076049ba28091b16e5bb9152f40841b5fffc30b77a59659a8a1a891bcf34cc9e

외부에서 연결가능한 포트 2222 뚫어놓기

 


 

Docker Advance

Docker commit

: 현재 운영중인 컨테이너의 상태를 저장 ( cotainer layer를 통째로 저장 ⇒ 레이어의 일부분으로 새로운 이미지를 만들어낼 수 있음 )

doker commit 컨테이너명
  • -a : 저자 명시
  • -m : 메시지 명시
  • -p : 커밋동안 컨테이너를 일시중지( default : pause = true )
  • —change : 추가적으로 적용할 Dockerfile 명령어를 설정 CMD, ENTRYPOINT, ENV, EXPOSE, LABEL 등… ⇒ RUN은 포함되지 않음( 빌드시 실행 )
  • 볼륨은 컨테이너의 라이프사이클을 따르지 않음 → 포함하지 않음

** Production 에서 사용할 이미지라면 commit 대신 Dockerfile 을 기반으로 제작하는 것이 권장된다.

 

Docker buildx

: 빌드를 하기 위해 추가적인 명령을 넣어놓은 build 명령어 ( 멀티플랫폼의 이미지를 동시에 만들어서 하나의 이름을 가지게 할 수 있음 )

  • builder( 격리된 다른 외부공간 )를 만들어서 작업을 수행하도록 함 ⇒ 빌드를 동시적으로 수행가능
  • builder의 지원 플랫폼 = 생성가능한 이미지의 플랫폼
  • 빌드 드라이버의 3종류 : ( docker-container , kubernetes , remote ) ⇒ 각각의 공간에서 플랫폼별 이미지를 빌드실행
    • 기본적으로 사용하는 docker 빌더 : 이미지 자동 로드 but 멀티아키텍처 빌드 불가( 단일 platform 이미지만 빌드 )
    • 이외의 빌더 : 자동로드X, 멀티 아키텍처 빌드를 지원

(1) builder 생성하기( docker buildx create )

드라이버 설정(--driver), --use 옵션으로 생성후 바로 빌더 지정, --platform으로 빌드할 플랫폼(들) 지정

ex) Multi-platform 용 docker-container 드라이버 builder 생성하기

docker buildx create --driver docker-container --name multi-builder --platform
linux/amd64,linux/arm64

 

(2) builder 선택하기 : docker buildx use 빌더이름

  • docker buildx inspect --bootstrap : 현재 빌더에서 지원하는 platform확인
  • docker buildx inspect : 빌더의 정보조회

(3) 빌더를 이용해 빌드 : docker buildx build

  • 기존 빌드 옵션과 유사하게 --build-arg, --no-cache, --platform, --tag 등 옵션 사용가능
  • —load 옵션 : 만든 이미지를 로컬로 가져옴 ( 내부적으로 export 를 이용하여 이미지를 추출하고 import )
  • —push옵션 : registry에 멀티플랫폼형 이미지를 docker hub에 업로드 ⇒ 멀티플래폼 이미지를 제작하는 경우(2개이상의 플랫폼에서 빌드) 로컬에 로드불가, 곧바로 레지스트리에 푸시할 것

 

[ 연습문제 1 ] 변경사항( curl 설치 )을 저장

기존의 이미지 → curl이 설치되어 있지 않음

docker exec base /bin/bash -c "apt-get update;apt-get upgrade;apt-get install -y curl”

기존의 이미지에서 curl 설치후 commit 실행

 

설치된 것이 존재함

 

[ 연습문제 2 ] 변경사항( 포트추가 )을 저장 : —change로 도커 명령 추가하기

docker run -itd --name base ubuntu:22.04
docker commit --change="EXPOSE 80" --pause=false base commit:v1
docker run -itd commit:v1

 

[연습] buildx 를 이용하여 ARM 용 cloudwave:base.v1 이미지 제작하기

docker buildx build --platform linux/arm/v7 -t cloudwave:arm.v1 .

도커파일 위치 주의!!

 

[ 실습 ] 멀티 이미지 도커허브에 업로드하기

docker buildx build --platform linux/amd64,linux/arm/v6 -t rudalsss/cloudwave:multi . --push

push할때에는 반드시 이름을 지켜줘야함 : domain/<namespace>/repo → 이때 doker hub를 쓴다면 domain은 생략가능