1 Docker의 구성 요소
1) Docker Engine
전체 Docker의 핵심 플랫폼이다.
Docker CLI
터미널에 Docker 관련 명령어를 입력하는 도구를 말한다.
명령어들을 입력하면 Docker Daemon에게 요청을 보내는데, 여기서 클라이언트 역할을 한다.
Docker Daemon
백그라운드에서 항상 실행 중인 프로세스이다.
dockerd
이다.Docker CLI의 명령을 받아서 실행한다.
컨테이너를 생성하고, 이미지를 빌드하고, 네트워크 설정을 하는 등 Docker 관련 모든 동작을 수행하는 핵심 부분이다.
Docker Daemon이 컨테이너 마다 있는 관리 프로세스인가? 아니면 Docker 전체를 관리하는 프로세스인가?
Docker Daemon은 단 하나의 프로세스로 Docker 시스템을 제어하는 중앙 관리자 프로세스이다. 수백 개의 컨테이너가 있더라도 모두 1개의 Daemon에서 관리한다.
근데 컨테이너 마다도 관리하는 프로세스가 있어야 하는거 아닌가?
각 컨테이너에는 Docker Daemon 같은 별도의 관리 프로세스는 존재하지 않는다. 컨테이너 자체가 하나의 일반 프로세스로 실행된다.
하나의 컨테이너에서 실행되는 여러 프로세스 들이 있을 수 있고, 컨테이너가 여러 개면 파생되는 프로세스도 엄청나게 많을 텐데 그거를 다 Daemon에서 관리하나?
Daemon은 각 컨테이너 프로세스(PID 1)만 관리한다.
그리고 거기서 포크되는 다른 자식 프로세스들은 리눅스 커널과 그 PID 1번 프로세스가 관리한다.
Docker Engine 정리
사용자가 Docker CLI를 통해 명령어를 입력하면 Docker Daemon에게 요청이 보내진다. 그리고 Docker Daemon에서 도커의 핵심적인 동작을 수행한다.
2) Dockerfile
컨테이너를 어떻게 만들지 정의하는 레시피(설계도)이다.
3) Docker Image
Dockerfile을 기반으로 만들어진 정적인 실행 환경(템플릿)을 말한다.
Dockerfile을 빌드해서 생성한 결과물이다.
빌드 명령어
docker build -t likelion-backend:latest .
위에서
-t
는 이미지 이름을 지정할 때 사용한다.:
뒤는 태그이다. (태그는 일반적으로 버전을 명시한다.)마지막의
.
은 Dockerfile이 위치한 경로를 의미한다.
4) Docker Container
Docker Image를 실제로 실행한 인스턴스이다.
컨테이너는 메모리에서 실행 중인 프로세스고, 고유한 IP와 파일시스템을 갖고 있다.
실행 명령어
docker run --rm -d -p 8082:8082 --name likelion-backend likelion-backend:latest
--rm
→ 컨테이너가 종료되면 자동으로 삭제되는 옵션-d
→ Detached 모드로 실행하는 옵션 (백그라운드로 실행됨)-p 8082:8082
→호스트:컨테이너
포트 매핑하는 옵션. 외부의 8082 요청을 컨테이너의 8082로 전달한다. → 외부에서 접근하게 하기 위해서 필수적인 옵션이다. (컨테이너끼리만 통신할 때는 생략해도 됨)--name
→ 컨테이너에 붙일 이름을 설정하는 옵션likelion-backend:latest
→ 실행할 Docker 이미지 이름과 태그
5) Docker Hub
Docker Hub는 Docker의 이미지들을 공유하고 받아올 수 있는 중앙 저장소이다.
이미지를 올릴 수 있고, 다른 사람이 올린 이미지를 받을 수도 있다.
2 .dockerignore
Dockerfile에서 COPY
나 ADD
로 복사할 파일이나 디렉터리를 지정할 수 있다.
이때 .dockerignore
에 있는 파일은 복사하지 않는다.
파일을 복사한다는게 어떤 의미지? 진짜 말 그대로 복사하는 건가?
말 그대로 복사가 맞다.
물리적으로 복사한다.
보통 어떤 파일을
.dockerignore
로 설정하지?Docker Image에 넣을 필요가 없는 것들을 설정한다.
민감한 정보를 포함시키지 않기 위해서나, 빌드 속도를 높이고 최종 이미지의 크기를 줄이기 위해 필요 없는 파일은 이미지에 포함되지 않게 한다.
3 Dockerfile의 필수 요소와 선택 요소
필수 설정 요소
FROM
/COPY
/ENTRYPOINT
또는CMD
ENTRYPOINT
와CMD
를 동시에 쓰면 어떻게 되나?둘 다 있으면 둘 다 실행된다.
CMD
가ENTRYPOINT
의 인자를 설정하는 역할이 된다.근데 둘 다 명령을 실행하는 건데 하나만 있어도 다 가능한거 아닌가? 왜 같은게 두 개나 있지?
둘은 차이가 있다.
ENTRYPOINT
는 반드시 실행되는 명령어이다.반면
CMD
는 기본값으로 사용항 인자/명령어이고, 런타임에 인자가 입력되면CMD
의 인자는 덮어씌워져서 사용되지 않는다.둘을 같이 사용하는 경우가 있나?
“실행 명령은 고정하고, 인자는 유연하게 바꾸고 싶을 때”
ENTRYPOINT
로 실행 명령은 고정한다. 그리고CMD
로 디폴트 인자를 설정한다. 이 인자는 런타임에 입력한 인자로 대체가 되기 때문에 실행할 때에 맞게 인자를 설정할 수 있다.정리
ENTRYPOINT
→ 무조건 항상 실행됨CMD
→ Docker 실행 시에 인자를 주면 덮어씌워짐
선택 설정 요소
RUN
,WORKDIR
,ENV
,EXPOSE
,ARG
,LABEL
USER
,HEALTHCHECK
,VOLUME
,ONBUILD
예시
# [필수] 어떤 베이스 이미지 위에서 작업할지 설정 FROM eclipse-temurin:21-jdk # [선택] 이후 모든 명령어는 /app 디렉토리 기준으로 실행됨 WORKDIR /app # [선택] 환경 변수 설정 (애플리케이션에서 사용 가능) ENV SPRING_PROFILES_ACTIVE=prod # [선택] 빌드 시점에 사용할 인자 정의 (옵션) ARG JAR_FILE=build/libs/myapp.jar # [필수] 소스 코드나 JAR 파일을 컨테이너 안으로 복사 # 현재 호스트의 디렉토리(첫 번째 인자)의 내용을 컨테이너 내부(두 번째 인자)로 복사함 # 기준이 되는 위치는 WORKDIR에서 설정한 위치가 기준임 # 이때 .dockerignore에 설정되어 있는 것은 제외됨 COPY . . # [선택] 라벨을 붙여 이미지 정보 관리 가능 LABEL maintainer="you@example.com" LABEL version="1.0.0" # [선택] 헬스 체크 설정 (컨테이너 정상 여부 확인) HEALTHCHECK CMD curl --fail http://localhost:8080/actuator/health || exit 1 # [선택] 사용할 포트 지정 (문서화 목적, 외부 접근 가능하게 하려면 docker run -p 필요) EXPOSE 8080 # [필수] 컨테이너 실행 시 실행할 명령어 ENTRYPOINT ["java", "-jar", "build/libs/myapp.jar"]
근데 왜
ENTRYPOINT
는 리스트 형태로 값을 넘기는거지?명령어를 넘기는 방법에는 두 가지가 있다.
명령어를 문자열로 넘기기(Shell 형식)
문자열로 넘길 경우 Docker는 내부적으로
/bin/sh -c
와 함께 명령어를 실행한다. 그렇게 되면 명령어가 쉘을 통해 해석되고 쉘 문법에 따라 예기치 못한 실행 결과가 나올 수 있다. (쉘에서는 특수문자가 연산자로 쓰이고, 환경에 따라 쉘 문법이 다를 수 있다.)/bin/sh -c
가 뭐길래 그게 쉘로 실행되는 거지?/bin/sh -c "명령어"
는/bin/sh
프로그램을 실행하면서 뒤에 것을 인자로 넘기는 것이다.-c
옵션은 인자로 넘긴 문자열을 명령어로 해석해서 실행하라는 뜻이다.여기서
/bin/sh
프로그램은 가장 기본적인 POSIX 쉘이고, 그렇기 때문에 해당 명령은 쉘에서 실행된다.그리고 당연히 리눅스에서 프로그램을 실행할 때는
fork
되어서 자식 프로세스에서 실행된다.
리스트로 넘기기(Exec 형식)
Exec 형식으로 넘길 경우 Docker는 쉘 없이 명령어를 직접 실행한다. 특수 문자나 공백 같은 것들이 쉘 문법에 따라 해석되는 것이 아니라 있는 그대로 실행된다.
결론
쉘 문법에 영향을 받지 않고 인자가 정확히 나뉘어서 전달되는 Exec 형식을 사용하는 것이 권장된다.
그러면 Shell 형식은 쓸 일이 없나?
복잡한 쉘 스크립트나 파이프라인을 쓸 때 적합하다.
추가적인 차이
ENTRYPOINT
나CMD
에서 Exec 형식으로 실행되는 명령어는 해당 컨테이너 가상화 환경에서 반드시 PID 1로 실행된다. 반면 Shell 형식으로 실행 시에는 앞서 설명했듯이 PID 1인 프로세스가 아니라 그 자식 프로세스에서 실행된다.리눅스에서 PID 1번은 프로세스 트리의 루트이기 때문에 아주 특별하게 취급된다. 따라서 안정적인 실행을 위해 서버 프로세스 같은 메인 앱은 반드시 Exec 형식으로 실행되어야 한다.
4 Docker와 Docker Compose의 차이
Docker는 “단일 컨테이너 실행”에 초점이 있다.
Docker Compose는 “여러 컨테이너를 한 번에 정의하고 실행”하는 도구이다.
실행 방법
Docker는
docker run
으로 실행하고, Docker Compose는docker compose up
으로 실행한다.브릿지 네트워크
Docker로 개별
docker run
을 실행해도 기본적으로 브릿지 네트워크로 연결이 된다.하지만, Docker Compose처럼 컨테이너 이름으로 DNS처럼 다른 컨테이너에 접근할 수는 없다.
5 Docker Compose의 파일 구조
필수 설정 요소
version
/services
/image
또는build
image
와build
를 동시에 쓰면 어떻게 되나?build
를 이용해서 Dockerfile 기반으로 이미지를 새로 빌드하고,image
에 설정된 이름으로 빌드된 이미지에 이름을 붙인다.image
만 쓴 경우 → 이미 존재하는 이미지를 사용함이미지 리스트에 해당하는 이미지가 없으면 어떻게 되나?
로컬에 지정한 이미지가 없을 경우 자동으로 Docker Hub에서 이미지를 자동으로 pull하려고 시도한다.
→ Docker Hub에도 없다면 에러가 발생한다.
build
만 쓴 경우 → 이름 없는 임시 이미지로 컨테이너를 만듦
선택 설정 요소
ports
,volumes
,environment
,depends_on
,networks
,restart
,command
예시
# [필수] Compose 버전 명시 version: "3.8" # [필수] 서비스 정의 services: backend: # [필수] image와 build 둘 중 하나는 반드시 써야한다. # 이미지로 실행할지, 빌드로 생성할지 설정 build: . image: likelion-backend:latest # [선택] 포트 매핑 ports: - "8082:8082" # [선택] 환경 변수 environment: - SPRING_PROFILES_ACTIVE=prod - DB_HOST=db # [선택] 볼륨 마운트 volumes: - ./logs:/app/logs # [선택] 네트워크 설정 (명시해도 되고 안 해도 됨) networks: - likelion-net # [선택] db가 먼저 실행되도록 설정 depends_on: - db db: image: mysql:8.0 restart: always environment: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: likelion volumes: - db-data:/var/lib/mysql networks: - likelion-net # [선택] 네트워크 정의 networks: likelion-net: # [선택] 볼륨 정의 volumes: db-data:
version
은 입력 안 한다는 말이 있던데?과거 Compose (v1)은 여러 버전 스펙이 존재했다. 하지만 Compose v2는 단일 스펙으로 통일되어서 Docker CLI가 알아서 최적 버전으로 해석한다.
volume
은 복사하는건가? 아니면 심볼릭 링크 같은게 생성되는건가?복사가 아니다. 심볼릭 링크도 아니다. 컨테이너 환경에서 특정 경로에 마운트되는 것이다. 해당 디스크 볼륨을 동시에 마운트하는 것이다.
networks
는 왜 명시 안 해도 되는거지? 만약 서비스의networks
를 다르게 하면 어떻게 되는거지?Docker Compose는
networks:
가 없으면 자동으로 기본 네트워크를 생성한다.여러 서비스 그룹을 다른 네트워크로 분리하고 싶거나 네트워크 이름을 명시적으로 관리하고 싶을 때 명시한다.
서비스의
networks
를 다르게 하면 각각의 네트워크끼리는 DNS 이름으로 통신할 수 없다. (서비스 이름으로 통신 불가능)그럼 어떻게 통신가능하지? 외부 호스트를 통해야 하나?
외부 포트와 연결을 설정하고 호스트를 통해 접근해야 한다.
서비스의 하위에 있는
volumes
와 아예 밑에 있는volumes
는 무슨차이지?서비스의
volumes
는 해당 컨테이너에 마운트하기 위한 설정이다.서비스 바깥에 있는
volumes
는 사전에 볼륨 이름을 정의하는 것이다. 이를 설정하지 않으면 Docker가 자동으로 규칙에 따라서 볼륨 이름을 설정한다.→ 의도한 이름과 다를 수 있고 명확하게 추적이 어려울 수 있음.
'스터디 > 백엔드' 카테고리의 다른 글
Docker에 대한 의문점 1편 (0) | 2025.04.02 |
---|---|
[백엔드] 쿠키(Cookie)란? (0) | 2024.12.13 |