본 포스팅은 시작하세요 도커/쿠버네티스 서적 내용 기반으로 작성된 게시글입니다.
도커 이미지
도커 이미지는 다음과 같은 형태를 띄고 있다.
도커 이미지의 태그는 생략할 수 있으며, 생락한다면 latest 태그로 인식한다.
# 저장소이름/이미지 이름:태그
wath1457/ubuntu:22.04
다만, 저장소 이름은 생략 가능하며, 저장소 이름이 명시되지 않은 이미지는 Docker Hub의 공식 이미지를 의미한다.
# 도커 허브에 저장될 이미지라는 것을 명시하기 위에 이미지 이름 최상위에 docker.io를 붙이기도 한다
# docker.io 외에도 gcr.io 등 컨테이너 레지스트리마다 다를 수 있다.
docker.io/wath1457/ubuntu:22.04
도커 컨테이너
컨테이너 기본 사용자는 root이고, 호스트 이름은 무작위의 16진수 해시값이다.
docker run 명령어의 -i 옵션으로 상호 입출력을, -t 옵션으로 tty를 활성화하여 배시 셸을 사용하도록 설정했다.
docker create vs run
docker run : (이미지가 없는 경우) docker pull -> docker create -> docker start -> (-i -t 옵션인 경우) docker attach
docker create : (이미지가 없는 경우) docker pull -> docker create
* 컨테이너를 대상으로 하는 모든 명령어는 컨테이너 이름 대신 ID를 사용할 수 있으며, 중복된 컨테이너가 존재하지 않는다면 앞의 2~3자리만 입력해도 된다.
docker ps : 정지되지 않은 컨테이너 출력
docker ps -a : 정지된 컨테이너를 포함한 모든 컨테이너 출력
COMMAND
컨테이너가 시작될 때 실행될 명령어
이미지에 내장된 커멘트는 docker run이나 create 명령어의 맨 끝에 입력해서 컨테이너 생성할 때 덮어쓸 수 있다.
# COMMAND를 덮어쓰지 않는 경우 이미지에 내장된 /bin/bash 실행
docker run -i -t ubuntu:22.04 echo hello world!
실행 중인 모든 컨테이너 정리
docker ps 명령어의 -q 옵션과 -a 옵션을 이용하여 컨테이너를 삭제할 수 있다.
-q : 컨테이너의 ID만 출력
-a : 모든 컨테이너 출력
docker stop $(docker ps -a -q)
docker rm $(docker ps -a -q)
컨테이너 외부 노출
기본적으로 도커는 172.17.0.X 의 IP를 순차적으로 노출
사진에서처럼 도커의 NAT IP인 172.17.0.2를 할당받은 eth0 인터페이스와 로컬 호스트인 lo인터페이스가 존재한다.
외부에 컨테이너 애플리케이션을 노출하기 위해서는 eth0의 IP와 포트를 호스트이 IP와 포트에 바인딩해야 한다.
docker run -p : 컨테이너의 포트를 호스트의 포트와 바인딩하여 연결할 수 있음
[호스트의 포트]:[컨테이너의 포트]
# 호스트의 3306과 컨테이너의 3306 / 호스트의 7777과 컨테이너 80포트를 연결
docker run -i -t -p 3306:3306 -p 호스IP:7777:80 ubuntu:14.04
그 외 docker run 옵션
- -d : 백그라운드 실행 / 입출력이 없는 상태로 컨테이너를 실행되며 Detached 모드인 커넽이너는 반드시 컨테이너에서 프로그램이 실행되어야 하며, 포그라운드 프로그램이 실행되지 않으면 컨테이너는 종료됨
- -e : 컨테이너 내부의 환경변수 설정 (-e ROOT_PASSWORD=password)
실행중인 docker container 임시로 빠져나왔다가 다시 들어가기
docker run -it 로 인터렉션 모드로 실행 -> ctrl + P,Q (P와 Q를 순차적으로 누르기) -> exit와는 다르게 컨테이너는 실행중인 상태에서 컨테이너에서 빠져나옴 -> docker attach 명령어로 실행중인 도커 컨테이너로 다시 진입
docker exec와 docker attach
두 명령어 모두 실행 중인 컨테이너와 상호작용할 때 사용하지만, 동작방식은 다르다.
docker exec
✅ 새로운 프로세스를 컨테이너 내부에서 실행
- 컨테이너 내부에서 새로운 셸이나 특정 명령어를 실행할 때 사용.
- 기존 컨테이너의 표준 입력/출력과 별도로 동작.
- 컨테이너가 종료되어도 원래 실행 중이던 프로세스에는 영향을 안 줌.
docker exec -it my_container /bin/bash # 컨테이너 내부에서 새로운 bash 실행
docker exec -it my_container ls -l # 컨테이너 내부에서 ls 실행
docker attach
✅ 컨테이너의 메인 프로세스에 연결
- 컨테이너의 기존 실행 중인 표준 입력/출력에 붙는 것
- 컨테이너를 실행한 것처럼 보이지만, 실제로는 실행 중인 프로세스의 입출력을 그대로 가져옴.
- 종료하면 컨테이너도 같이 종료될 수 있음. (Ctrl + C 시 주의)
이러한 차이점 때문에, 보통 실무에서는 docker exec를 많이 사용하는 것 같다.
docker volume
첫번째 방법
# 컨테이너 볼륨 마운트
docker run ~ -v [호스트 공유 디렉토리]:[컨테이너 공유 디렉토리]
- 디렉토리 단위의 공유뿐만 아니라, 단일 파일 단위의 공유도 가능하며, 동시에 여러개의 -v 옵션을 사용할 수 있다.
- 이미지에 원래 존재하던 디렉토리에 호스트의 볼륨을 공유하면, 컨테이너의 디렉토리가 덮어씌워진다. (= 호스트의 디렉토리를 컨테이너의 디렉토리에 마운트한다.)
두번째 방법
볼륨 컨테이너
-v 옵션으로 볼륨을 사용하는 컨테이너를 다른 컨테이너와 공유하는 방법 (간접적으로 데이터를 공유받는 방법)
--volumes-from 옵션으로 가능
# 호스트 볼륨 공유
docker run -it --name volume_override -v /home/db:/home/test_dir ${image}
# override 컨테이너에서 볼륨을 공유받기
docker run -it --name volumes_from_container --volumes-from volume_override
세번째 방법
도커 볼륨
docker volume create --name ${volume_name} 방법으로 여러 종류의 스토리지 백엔드를 volume으로 사용 가능하며, 기본 옵션은 local이다.
# 컨테이너에 도커 볼륨 마운트
# [볼륨의 이름]:[컨테이너의 공유 디렉토리]
docker run -it --name volume_test -v myvolume:/root/ ubuntu:14.04
도커 볼륨은 호스트에 저장함으로써 데이터를 보존하며, 도커 엔진에서 관리된다.
docker inspect 명령어로 볼륨 정보를 확인할 수 있으며, inspect는 도커의 모든 구성 단위의 정보를 확인할때 사용된다.
도커 볼륨을 사용하고 있는 컨테이너를 삭제해도 볼륨은 남아있기에, 사용하지 않는 볼륨은 삭제하는 것이 좋다.
# 사용하지 않는 모든 도커 볼륨 제거
docker volume prune
도커 네트워크
도커 컨테이너가 실행중인 상태에서 호스트에서 ifconfig 명령어를 실행시켜 본다면, 실행중인 도커 컨테이너 개수만큼 veth~ 라는 네트워크 인터페이스를 볼 수 있다. 이 veth~ 인터페이스를 생성하여 컨테이너에 외부와의 네트워크를 제공할 수 있다.
또한, docker0 이라는 브리지도 존재하여, 브리지는 각 veth 인터페이스와 바인딩돼 호스트의 eth 인터페이스와 이어주는 역할을 한다.
컨테이너를 생성하면 기본적으로 docker0 브리지를 사용하지만, 설정을 통해 네트워크 드라이버를 변경할 수 있다. 서드파티 플러그인과 도커에서 기본적으로 제공하는 네트워크 드라이버(host, none...)가 존재한다.
브리지 네트워크
컨테이너들이 서로 통신할 수 있도록 가상의 네트워크 환경을 제공하는 것
브리지 네트워크는 NAT(Network Address Translation)를 사용해서 외부(인터넷)와 통신 가능
# 브리지 네트워크 생성
docker network create --driver bridge mybridge
# 브리지 네트워크 connect & disconnect
docker network disconnect mybridge test_container
docker network connect mybridge test_container
bridge network 또는 overlay network 같이 특정 IP 대역을 갖는 네트워크 모드에만 connect & disconnect 명령어를 사용할 수 있습니다.
# 네트워크의 서브넷, 게이트웨이, IP 할당 등 범위를 임의로 설정할 수 있다.
docker network create --driver=bridge \
--subnet=172.72.0.0/16 \
--ip-range=172.72.0.0/24 \
--gateway=172.72.0.1 \
my_network
호스트 네트워크
호스트의 네트워크 환경을 그대로 사용할 수 있으며, 호스트 머신에서 설정한 호스트 이름이 컨테이너의 호스트 이름으로 설정된다.
none 네트워크
아무런 네트워크를 사용하지 않으며, 로컬호스트를 나타내는 lo외의 다른 네트워크 인터페이스는 존재하지 않게 된다.
컨테이너 네트워크
다른 컨테이너의 네트워크 네임스페이스 환경을 공유할 수 있다.
공유되는 속성은 내부IP, 네트워크 인터페이스의 MAC주소 등..
따라서, 새로운 내부 IP할당 받지 않으며 호스트에 veth로 시작하는 가상 네트워크 인터페이스도 생성되지 않는다.
--net container:[다른 컨테이너의 ID]
--net-alias
브리지 타입의 네트워크와 run 명령어의 --net-alias 옵션을 함께 사용하면 특정 호스트 이름으로 컨테이너 여러개의 접근이 가능해진다.
호스트 이름으로 접근시, 라운드 로빈 방식으로 접근할 수 있는 컨테이너의 IP가 결정된다.
MacVLAN 네트워크
호스트의 네트워크 인터페이스 카드를 가상화해 물리 네트워크 환경을 컨테이너에게 동일하게 제공한다.
컨테이너는 물리 네트워크상에서 가상의 맥 주소를 가지며, 해당 네트워크에 연결된 다른 장치와의 통신이 가능해진다.
따라서, MacVLAN에 연결된 컨테이너는 기본 할당 IP대역인 172.17.X.X 대신, 네트워크 장비의 IP를 할당받게 된다.
다만 해당 네트워크를 사용하는 컨테이너는 기본적으로 호스트와 통신이 불가능하다.
도커 컨테이너 로그 확인하기
# 컨테이너 로그 확인하기
docker logs $(container_name)
# 마지막 n줄의 로그 출력
docker logs --tail -n $(container_name)
# 특정 시간의 이후 로그 확인
docker logs --since $(유닉스 시간) $(container_name)
# 로그 스트림 & 타임스탬프로 확인
docker logs -f -t $(container_name)
# 기본 컨테이너 로그 확인 (도커 컨테이너 내부)
cat /var/lib/docker/containers${CONTAINER_ID}/$CONTAINER_ID}-json.log
# 도커 컨테이너 로그 syslog로 내보내기
docker run -d --name container_name --log-driver=syslog
또한 syslog 로그 드라이버를 이용하여 rsyslog로 로그를 보낼 수 있으며, 원격의 서버로 로그를 전송할 수 있다.
⚡ 3. syslog vs rsyslog 비교
비교 항목 | syslog | rsyslog |
출시 연도 | 1980년대 | 2004년 |
프로토콜 | UDP만 지원 | TCP & UDP 지원 |
성능 | 단일 스레드 | 멀티스레드 (더 빠름) |
로그 필터링 | 단순한 규칙 | 정교한 필터링 가능 |
확장성 | 제한적 | 플러그인 지원 (Elasticsearch, MySQL 등) |
지원 포맷 | 텍스트 기반 | JSON, 데이터베이스 저장 지원 |
대표 사용 예시 | 기본적인 시스템 로그 저장 | 대량의 로그 처리 및 분석 |
컨테이너 자원 할당 제한
run, create 명령어에서 컨테이너 자원 할당 관련 옵션을 사용하지 않으면, 컨테이너는 호스트의 자원을 제한없이 사용할 수 있다.
따라서, 호스트 또는 다른 컨테이너의 동작을 보장하기 위해 컨테이너 자원 할당 제한을 할 수 있다.
컨테이너 메모리 제한
# 컨테이너 메모리 1GB 할당 / 컨테이너의 swap메모리는 메모리의 2배로 설정됨
docker run -d --memory='1g' test
컨테이너 CPU 제한
# --cpu-shares 옵션을 통해 컨테이너가 '상대적'으로 얼마나 CPU를 사용할 수 있는지 나타냄
# 아무런 설정을 하지 않은 경우 1024, CPU할당에서 '1'의 비중을 나타냄
dockeer run -d --cpu-shares 1024 ~
--cpu-preiod, --cpu-quota
컨테이너 CFS 주기는 기본 100ms로 설정되지만 --cpu-period와 --cpu-quota로 주기 변경이 가능
# --cpu-period 값은 기본적으로 100000이며, 100ms 의미
# --cpu-quota는 --cpu-period에 설정된 시간 중 CPU 스케줄링에 얼마나 할당할 것인지 설정
docker run -d --name quota_test\
--cpu-period=10000 \
--cpu-quota=25000 \
...
# 100000중에 25000만큼을 할당하여 CPU주기가 1/4로 줄었으므로 CPU 성능이 1/4로 감소
# 즉, 컨테이너는 [--cpu-quota] / [--cpu-period] 만큼 CPU 시간을 할당받음
--cpus
--cpu-period, --cpu-quota 와 동일한 기능 / 직관적으로 CPU 개수를 지정
# --cpu-period=100000, --cpu-quota=50000과 동일
docker run -d -cpus=0.5 ...
Block I/O 제한
옵션을 따로 설정하지 않으면 컨테이너 내부에서 파일을 읽고 쓰는 대역폭에 제한이 설정되지 않는다.
Direct I/O의 경우에만 블록 입출력이 제한되며, Buffered I/O는 제한되지 않는다.
--device-write-bps
--device-read-bps
--device-write-iops
--device-read-iops
✅ Buffered I/O (제한 안 받음)
- OS의 페이지 캐시를 활용해서 데이터를 읽고 씀.
- 데이터를 먼저 메모리에 저장한 뒤, 나중에 디스크로 기록하는 방식.
- 속도가 빠르고, 여러 프로세스가 같은 데이터를 효율적으로 공유할 수 있음.
- Block I/O 제한을 받지 않음 (컨테이너가 제한을 걸어도 적용되지 않음).
❌ Direct I/O (제한 적용됨)
- 페이지 캐시를 거치지 않고, 바로 디스크에 접근하는 방식.
- 주로 데이터베이스(MySQL, PostgreSQL) 같은 애플리케이션에서 사용됨.
- 디스크의 실제 성능을 그대로 반영하지만, 속도가 느릴 수 있음.
- Docker의 Block I/O 제한이 적용됨.
# 초당 쓰기 작업 최대치 1MB
# [디바이스이름]:[값] / 예시에선 /dev/xvda 디바이스 사용
docker run -it \
--device-write-bps /dev/xvda:1mb \
...
🔥 bps vs iops 차이
--device-read-bps | 초당 읽기 속도 제한 | 바이트/초 (B/s, KB/s, MB/s, GB/s) | 데이터 전송량 기반 제한 | --device-read-bps /dev/sda:10mb → 10MB/s까지만 읽기 가능 |
--device-write-iops | 초당 쓰기 I/O 요청 개수 제한 | IOPS (Input/Output Operations Per Second) | 입출력 요청 횟수 기반 제한 | --device-write-iops /dev/sda:100 → 초당 100번까지만 쓰기 가능 |
- bps(Byte per Second)
- "한 번에 얼마나 많은 데이터를 읽거나 쓸 수 있냐?"
- 물리적인 **대역폭(throughput)**을 제한하는 방식.
- 주로 대용량 파일 전송 제한에 사용됨.
- iops(Input/Output Operations Per Second)
- "초당 몇 번의 I/O 요청을 처리할 수 있냐?"
- 작은 파일을 여러 개 읽고 쓰는 성능을 제한하는 방식.
- 주로 DB와 같은 랜덤 읽기/쓰기 많은 워크로드에 적용됨.
docker commit vs docker build
1. docker commit
- 실행 중인 컨테이너의 변경 사항을 새로운 이미지로 저장하는 명령어.
- 수동적인 방식으로 컨테이너 내부에서 직접 수정 후 저장할 때 사용.
- Dockerfile 없이도 이미지 생성 가능.
docker commit <컨테이너_ID> <새로운_이미지_이름>:<태그>
docker commit my_container my_custom_image:v1
2. docker build
- Dockerfile을 기반으로 자동화된 빌드를 수행하는 명령어.
- 코드로 빌드 과정을 관리할 수 있어 재현성이 뛰어남.
- 기본적으로 Dockerfile이 필요함
docker build -t my_custom_image:v1 .
언제 어떤 걸 써야 할까?
- 빠르게 현재 컨테이너 상태를 이미지로 저장해야 한다면? → docker commit
- 일관된 빌드 과정을 만들고 싶다면? → docker build (Dockerfile 작성)
docker images
# 이미지 레이어 구조 확인
docker history ${image}:${image_tag}
이미지를 사용중인 컨테이너가 존재할 때 이미지는 강제로 삭제하게 된다면, 이미지 이름이 <none>으로 변경되며 이러한 이미지들 댕글링(dangling)이미지 라고 한다.
# 댕글링 이미지 확인
docker images -f dangling=true
삭제되는 이미지의 부모 이미지가 존재하지 않아야만 해당 이미지의 파일이 실제로 삭제됨
1. ubnuntu:14.04
2. 1이미지에 레이어가 추가된 test:first 이미지
3. 2이미지에 레이어가 추가된 test:second 이미지
해당 이미지들이 존재하는 경우, 각각의 이미지의 용량이 표기되지만, 세 개의 용량을 각각 차지하는 것이 아님
실제 이미지의 크기 : 1번 이미지크기 + 2번 레이어 크기 + 3번 레이어 크기
Dockerfile
FROM : 생성할 이미지의 베이스가 될 이미지
LABEL : 이미지에 메타데이터 추가 key:value 형테로 저장됨
RUN : 컨테이너 내부에서 명령어 실행 / 이미지를 빌드할 때 별도의 입력을 받아야 하는 RUN이 있다면 build 명령어는 오류로 간주함
RUN ["실행 가능한 파일", "명령줄 인자", "명령줄 인자", ...]
ADD : 파일을 이미지에 추가 / Dockerfile이 위치한 context에서 추가할 파일을 가져옴
WORKDIR: 명령어를 실행할 디렉토리를 나타냄
EXPOSE: 이미지에서 노출할 포트 설정 / 노출된 포트는 반드시 호스트와 바인딩되는 것은 아니며, 해당 포트를 사용할 것임을 나타냄 (docker run -p 옵션으로 포트 바인딩 진행)
CMD: 컨테이너가 시작될 때마다 실행될 명령어 / docker run 명령어에서 커맨드 명령줄 인자를 입력하면 Dockerfile CMD는 덮어씌워진다.
# WORKDIR /var/www/html 과 동일한 동작
WORKDIR /var
WORKDIR www/html
# label을 이용해 특정 label을 가진 이미지 출력
# label 정보가 purpose:practice인 이미지
docker images --filter "label=purpose=practice"
docker build context
Docker 빌드 컨텍스트(Build Context)는 docker build
명령을 실행할 때 도커 데몬(Docker Engine)으로 전송되는 파일과 디렉터리의 집합이
- Dockerfile이 위치한 build context를 읽어들임
- ADD와 COPY를 통해 build context의 파일을 이미지에 추가
- build context는 build 명령어의 맨 마지막에 지정된 위치에 있는 파일을 전부 포함하기에, Dockerfile이 위치한 곳에는 이미지 빌드에 필요한 파일만 있는 것이 바람직함 (루트 디렉토리에 있다면 하위 디렉토리를 모두 포함하게 됨)
- 위의 문제를 방지하기 위한 .dockerignore파일을 이용 (Dockerfile과 같은 경로에 위치)
# .dockerignore 파일 예시
# Dockerfile 경로 기준
test.html
*.html
*/*.html
# html확장자 파일은 제외 단, test로 시작하는 html파일은 예외
*.html
!test*.html
- 이미지 빌드시, Dockerfile의 명령어 줄 수만큼 레이어가 존재함
- 따라서, 빌드 중간 과정에서 임시 컨테이너도 같은 수 만큼 생성되고 삭제함
- 한 번 이미지 빌드를 마치고 난 뒤 같은 빌드를 진행하면, 이전 이미지 빌드에서 사용했던 캐시를 사용 (이미 존재하는 모든 이미지와 레이어를 검사 후에 겹치는 부분의 레이어 재사용)
- 이미지 빌드 중 오류가 발생했을 경우, 빌드가 중지되며 레이어 생성을 위해 임시로 생성된 마지막 켄테이너가 남아있음 또한 <none>:<none> 형태로 이미지가 생성됨
# 캐시 미사용 (예 - Dockerfile에 git clone의 경우 캐시를 이용하게되면 원격 레포지토리의 변경사항이 반영되지 않음)
docker build --no-cache -t build_test:0.1
# 캐시로 사용할 이미지 직접 지정
docker build --cache-from nginx -t my_extend_nginx:0.2
멀티 스테이징 빌드
하나의 Dockerfile 안에 여러개의 FROM 이미지를 정의함으로써, 빌드 완료 시 최종적으로 생성될 이미지의 크기를 줄임
FROM golang
ADD main.go /root
WORKDIR /root
RUN go build -o /root/mainApp /root/main.go
FROM alpint:latest
WORKDIR /root
# --from=0 : 첫 번째 FROM에서 빌드된 이미지의 최종 상태
# 첫 번째 FROM 이미지에서 빌드한 /root/mainApp 파일을 두 번째 FROM에 명시된 이미지 alpine:latest에 복사
COPY --from=0 /root/mainApp .
CMD["./mainApp"]
멀티 스테이지 빌드를 통해 필요한 실행 파일만 최종 이미지 결과물에 포함시킴으로써 이미지 크기를 효과적으로 줄일 수 있다.
# 특정 단계에 별칭 지정
FROM golang as builder
ADD main.go /root
WORKDIR /root
RUN go build -o /root/mainApp /root/main.go
FROM alpint:latest
WORKDIR /root
# --from=builder 특정 단계의 이미지 builder를 가져옴
# 첫 번째 FROM 이미지에서 빌드한 /root/mainApp 파일을 두 번째 FROM에 명시된 이미지 alpine:latest에 복사
COPY --from=0 /root/mainApp .
CMD["./mainApp"]
ENV
Dockerfile에서 사용될 환경변수 지정
docker run -e 옵션에서 같은 이름의 환경변수 지정하면, Dockerfile에서 지정한 값은 덮어씌워진다.
${env_name:-value} : env_name 환경변수 값이 설정되지 않았으면 이 환경변수 값을 value로 사용
${env_name:+value} : env_name 환경변수 값이 설정되어 있으면 value를 값으로 사용, 값이 설정되지 않았으면 빈 문자열 사용
FROM ubuntu:14.04
ENV my_env my_value
RUN echo ${my_env:-value} / ${my_env:+value} / ${my_env2:-value} / ${my_env2:+value}
docker build .
# 결과
my_value / value / value /
ARG
build 명령어 실행할 때, 추가 입력을 받아 Dockerfile 내에서 사용될 변수의 값 설정
FROM ubuntu:14.04
ARG my_arg
# 기본값 지정 가능
ARG my_arg2=value2
RUN touch ${my_arg}/mytouch
# 빌드 예시
docker build --build-arg my_arg=/home -t myarg:0.1 ./
USER
...
# 컨테이너에서 사용될 사용자 계정의 이름이나 UID 설정
# 일반적으로 RUN으로 사용자 그룹과 계정을 생성한 뒤 사용, 루트 권한이 필요하지 않다면 USER 사용 권장
RUN groupadd -r author && useradd -r -g author wath1457
USER wath1457
...
기본적으로 컨테이너 내부에서는 root 사용자로 설정됨
docker run 에서 --user 옵션으로도 가능하지만, 이미지 자체에 root가 아닌 다른 사용자를 설정해 놓는 것이 좋다.
ONBUILD
빌드된 이미지를 기반으로 하는 다른 이미지가 Dockerfile로 생성될 때 실행할 명령어를 추가한다.
# Dockerfile (onbuild_test:0.0)
FROM ubuntu:14.04
RUN echo "test!"
ONBUILD RUN echo "onbuild test" >> /onbuild_file
# 위의 Dockerfile 이미지 컨테이너에는 /onbuild_file이 존재하지 않음
docker build ./ -t onbuild_test:0.0
# Dockerfile2 (onbuild_test:0.1)
FROM onbuild_test:0.0
RUN echo "test2"
# 해당 이미지에는 /onbuild_file이 존재
docker build -f ./Dockerfile2 ./ -t onbuild_test:0.1
이처럼 ONBUILD는 RUN, ADD 등 이미지가 빌드될 때 수행해야 할 Dockerfile 명령어를 나중에 빌드될 이미지를 위해 미리 저장해 놓을 수 있다.
* ONBUILD는 부모 이미지의 자식이미지에만 적용되며, 자식 이미지는 ONBUILD 속성을 상속받지 않는다.
ONBUILD 활용 예시
# maven 프로젝트 쉽게 사용
FROM maven:3-jdk-8-alpine
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
ONBUILD ADD . /usr/src/app
ONBUILD RUN mvn install
HEALTHCHECK
컨테이너에서 동작하는 애플리케이션의 상태 체크
예) 컨테이너 내부에서 동작 중인 애플리케이션의 프로세스가 종료되지 않았으나, 애플리케이션이 동작하고 있지 않은 상태를 방지하기 위해 사용
FROM nginx
RUN apt-get update -y && apt-get install curl -y
HEALTHCHECK --interval=1m --timeout=3s --retries=3 CMD curl -f http://localhost || exit1
# --interval : 지정된 주기마다 상태 체크
# --timeout : 이 시간을 초과하면 상태 체크에 실패한 것으로 간주
# --retries : retires만큼 명령어 반복 / 이 횟수만큼 실패하면 컨테이너는 unhealthy 상태가 됨
# HEALTHCHECK가 있는 컨테이너는 docker ps 명령어 시에 STATUS에 정보가 추가됨
# HEALTHCHECK 로그는 docker inspect에 State-Health-Log 항목에서 확인 가능
SHELL
# Dockerfile에서 기본적으로 사용하는 셸은 리눅스에서 "/bin/sh-c", 윈도우에서 "cmd /S /C"
FROM node
# 사용하고자 하는 셸을 명시
SHELL ["/usr/local/bin/node"]
ADD, COPY
로컬 디렉토리에서 읽어 들인 컨텍스트로부터 이미지에 파일을 복사
ADD와 COPY의 기능은 동일하지만, COPY는 로컬의 파일만 이미지에 추가할 수 있는 반명에 ADD는 외부 URL 및 tar파일에서도 파일을 추가할 수 있다.
COPY의 기능은 ADD에 포함되어 있는 셈이다.
*다만, ADD는 권장되지 않는다. URL이나 tar파일을 추가할 경우 이미지에 정확히 어떤 파일이 추가될지 알 수 없기 때문이다.
ENTRYPOINT, CMD
entrypoint와 command는 모두 컨테이너가 시작될 때 수행할 명령을 지정한다.
다만, entrypoint는 커맨드를 인자로 받아 사용할 수 있는 스크립트의 역할이 가능하다.
entrypoint : 없음, cmd : /bin/bash
-> 컨테이너 내부에서 /bin/bash 실행
entrypoint : echo, cmd : /bin/bash
-> 컨테이너 내부에서 echo /bin/bash 실행
command와 entrypoint 둘 중 하나는 반드시 설정해야 한다.
entrypoint를 이용한 스크립트 실행
# 스크립트 파일을 entrypoint의 인자로 사용해 컨테이너가 시작될 때마다 해당 스크립트 파일을 실행하도록 설정
docker run -it --name entrypoint_test --entrypoint="/test.sh" ubuntu:14.04 /bin/bash
- 실행할 스크립트 파일은 컨테이너 내부에 존재해야 한다.
- 이미지를 빌드할 때 다음과 같은 단계를 거친다.
- 어떤 설정 및 실행이 필요한지에 대해 스크립트로 정리
- ADD 또는 COPY로 스크립트를 이미지로 복사
- ENTRYPOINT를 이 스크립트로 설정
- 이미지를 빌드하여 사용
- 스크립트에서 필요한 인자는 docker run 명령어에서 cmd로 entrypoint의 스크립트에 전달
JSON 배열 형태와 일반 형식의 entrypoint, cmd
# JSON 배열 형태로 입력하지 않으면 /bin/sh -c가 추가됨
CMD echo test
-> /bin/bsh -c echo test
ENTRYPOINT /entrypoint.sh
-> /bin/sh -c /entrypoint.sh
CMD ["echo", "test"]
-> echo test
ENTRYPOINT["/bin/bash", "/entrypoint.sh"]
-> /bin/bash /entrypoint.sh
Dockerfile 빌드 시 주의사항
# 잘못된 Dockerfile 사용
FROM ubuntu:14.04
RUN mkdir /test
RUN fallocate -l 100m /test/dummy
RUN rm /test/dummy
# 생성한 파일을 지웠음에도 해당 이미지는 베이스 이미지 + 100mb의 크기를 갖고 있다.
# Dockerfile 개선
# 불필요한 레이어를 생성하지 않음
FROM ubuntu:14.04
RUN mkdir /test && \
fallocate -l 100m /test/dummy && \
RUN rm /test/dummy
# 해당 이미지는 베이스 파일의 용량과 동일하다.
- 빌드된 이미지에 불필요한 이미지 레이어가 있다면, 해당 이미지로 컨테이너를 생성하고 docker export, import 명령어를 사용해 컨테이너를 이미지로 만들어, 이미지 크기를 줄일 수 있다.
- export된 파일을 import하여 다시 도커에 저장하면 레이어가 한 개로 줄어들게 된다.
- 다만, 이전 이미지에 저장되어 있던 이미지 설정은 사라진다.
도커 데몬
docker 명령어는 /usr/bin/docker 에서 실행되지만, 도커 엔진 프로세스는 /usr/bin/dockerd 파일로 실행되고 있다.
도커 명령어 -> 도커 클라이언트 -> /var/run/docker.sock 통해 도커 데몬 API 호출 -> 도커 데몬 동작
# 호스트의 모든 네트워크 인터페이스 카드에 할당된 IP 주소와 2375번 포트로 도커 데몬 사용 & 유닉스 소켓 활성화하여 도커 클라이언트(docker 명령어) 사용
dockerd -H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375
스토리지 드라이버
도커 데몬이 사용하는 컨테이너와 이미지가 저장되는 디렉토리를 별도 지정하지 않았다면, /var/lib/docker/{드라이버 이름} 경로에 저장된다.
이 디렉토리를 임의로 지정하기 위해 도커 데몬(dockerd) 옵션에 --data-root 옵션을 사용한다.
해당 옵션으로 빈 디렉토리가 지정된다면, 도커 엔진이 초기화된 상태로 도커 데몬이 실행된다.
단, 여러 개의 디바이스 드라이버의 디렉토리가 하나의 디렉토리에 존재할 경우 --storage-driver로 사용할 드라이버를 명시하지 않으면 도커 데몬이 시작하지 않는 문제가 발생하기에, 사용할 스토리지 드라이버 명시하자.
이미지는 읽기 전용 파일로 사용되며, 컨테이너는 이 이미지 위에 레이어를 생성함으로써 컨테이너의 고유한 공간을 생성한다.
스냅샷 : 원본 파일은 읽기 전용으로 사용하되, 해당 파일이 변경되면 새로운 공간을 할당한다.
스토리지를 스냅샷으로 만들면 스냅숏 안에 어느 파일이 어디에 저장돼 있는지가 목록으로 저장된다.
그리고 스냅샷 안의 파일에 변화가 생기면 변경된 내역을 따로 관리함으로써 스냅숏을 사용한다.
스냅샷 CoW, RoW
CoW - 기존 데이터 보호 및 공간 절약
✔ 스냅샷을 생성하면 원본 데이터 블록을 그대로 둔다. (원본 데이터 불변)
✔ 쓰기 작업이 발생하면, 기존 블록을 수정하는 대신 새로운 블록을 할당하여 변경된 내용만 저장한다.
✔ 스냅샷을 찍을 때 복사는 발생하지 않으므로 빠르게 생성할 수 있고, 저장 공간을 절약할 수 있다.
✔ 쓰기 작업이 발생하면, 변경될 블록을 읽고 새로운 블록을 할당한 후 변경된 데이터를 저장한다.
✔ 이 과정에서 ① 데이터를 읽고, ② 새로운 블록을 할당하고, ③ 변경된 데이터를 저장하는 작업이 필요하므로 오버헤드가 발생할 수 있다.
RoW - 성능 최적화
✔스냅샷을 생성하면 원본 데이터 블록을 그대로 둔다. (원본 데이터 불변)
✔쓰기 작업이 발생하면, 기존 블록을 수정하는 대신 새로운 블록을 할당하여 원본 데이터 + 변경된 데이터를 함께 저장한다.
✔즉, 기존 데이터를 직접 수정하지 않고 새로운 위치에 데이터를 저장한 후, 포인터를 새로운 블록으로 변경한다.
✔스냅샷이 기존 데이터를 계속 참조할 수 있도록 원본 블록을 유지하면서 새로운 데이터 블록을 생성한다.
✔이 과정에서 ① 기존 데이터를 읽고, ② 새로운 블록을 할당하고, ③ 기존 데이터와 변경된 내용을 함께 저장하는 작업이 필요하므로 오버헤드가 발생할 수 있다.
방식 | 기존 데이터 변경 방식 | 새로운 블록 할당 방식 | 스냅샷 시 복사 여부 | 오버헤드 발생 원인 |
CoW (Copy-on-Write) | 기존 데이터를 공유하다가, 변경 시 새 블록을 할당하여 변경된 데이터만 저장 | 변경된 블록만 새로 생성 | ❌ (변경 전까지는 복사 없음) | 쓰기 시 원본 블록 읽기 + 새로운 블록 할당 |
RoW (Redirect-on-Write) | 기존 블록을 사용하지 않고, 새로운 블록에 기존 데이터 + 변경된 데이터를 저장한 후 포인터 변경 | 전체 데이터가 새로운 위치로 이동 | ❌ (스냅샷 시 복사 없음) | 쓰기 시 새로운 블록 할당 + 기존 데이터 이동 |
이미지 레이어 = 각 스냅샷
컨테이너 = 스냅샷을 사용하는 변경점
컨테이너 레이어는 이전 이미지에서 변경된 사항이 저장되어 있으며, 컨테이너를 이미지로 만들면 변경된 사항이 스냅샷으로 생성되고 하나의 이미지 레이어로서 존재하게 된다.
도커 데몬 모니터링
# 도커 데몬 디버그 모드
# Remote API 입출력 & 로컬 도커 클라이언트의 모든 명령어를 로그로 출력
dockerd -D
# 도커를 서비스로 구동한 경우
# upstart 기반 리눅스
/var/log/upstart/docker.log
# systemd 기반 리눅스(우분투 등)
journalctl -u docker
# 도커 데몬에서 실행되는 명령어의 결과를 로그로 출력
# 모든 명령어x
# attach, commit, create, copy 등 컨테이너 관련 명령어 및 이미지, 볼륨, 네트워크 플러그인 등
docker events
# 혹은
docker system events
# 특정 타입의 이벤트만 출력
# type뿐만 아니라 라벨, 특정 이미지로 생성된 이미지 등 필터 조건이 가능
docker events --filter 'type=image'
# 실행 중인 모든 컨테이너의 자원 사용량 스트림으로 출력
# 더 자세한 정보는 Remote API의 stats를 활용하자
docker stats
# no stream
docker stats --no-stream
# 도커에서 사용중인 이미지, 컨테이너 및 볼륨의 개수 관련 정보 출력
docker system df
'Kubernetes' 카테고리의 다른 글
쿠버네티스 설치 및 기본 개념 (pod, replicaset, deployment) (0) | 2025.04.05 |
---|---|
도커 컴포즈 (설치 및 개념) (0) | 2025.03.29 |
쿠버네티스 시크릿 관리 도구 (0) | 2025.01.20 |
kube-apiserver 트러블슈팅 (feat. kubelet) (1) | 2024.11.19 |
kubeflow 1.9 마이그레이션 (0) | 2024.09.22 |