/////

Docker Compose 운영형 스택의 healthcheck·depends_on·리소스 제한 실패 모드와 멀티스테이지 빌드 패턴

Docker Compose에서 deploy.resources로 CPU·메모리 제한을 선언했는데 실제 컨테이너가 제한 없이 동작하는 실패 모드는 주로 Compose 실행 방식 , Compose 구현 버전 , Swarm 여부 , --compatibility 사용 여부 , 그리고 호스트 cgroup v1/v2 구성 이 섞이면서 발생한다. 핵심은 다음과 같다. - deploy: 블록은 원래 Swarm/배포 지향 설정이며, 구현체가 지원하지 않으면 무시될 수 있다. -

/////

Summary#

Docker Compose에서 deploy.resources로 CPU·메모리 제한을 선언했는데 실제 컨테이너가 제한 없이 동작하는 실패 모드는 주로 Compose 실행 방식, Compose 구현 버전, Swarm 여부, --compatibility 사용 여부, 그리고 호스트 cgroup v1/v2 구성이 섞이면서 발생한다.

핵심은 다음과 같다.

  • deploy: 블록은 원래 Swarm/배포 지향 설정이며, 구현체가 지원하지 않으면 무시될 수 있다.
  • 비-Swarm docker compose up에서 확실한 로컬 컨테이너 제한을 원하면 mem_limit, cpus, cpu_quota, pids_limit 같은 서비스 레벨 옵션을 함께 검토해야 한다.
  • docker compose --compatibility up은 일부 deploy.resources 값을 비-Swarm 컨테이너 옵션으로 변환하려는 호환 모드지만, 모든 Swarm 의미를 완전히 재현하는 안전장치로 보면 안 된다.
  • 메모리 제한은 초과 시 대개 컨테이너 OOM kill로 나타나며, CPU 제한은 kill이 아니라 cgroup CFS quota에 따른 throttling으로 나타난다.
  • cgroup v1/v2 모두 Docker 리소스 제한을 지원하지만, 관찰 지표·파일 경로·systemd/cgroupfs 드라이버 동작·메모리/스왑 의미가 달라 운영자가 “제한이 안 걸렸다”고 오판하기 쉽다.

Key Points#

1. deploy.resources는 “항상 로컬 제한”이라는 의미가 아니다#

Compose 파일에서 다음과 같이 작성하는 경우가 많다.

services:
  app:
    image: my-app
    deploy:
      resources:
        limits:
          cpus: "0.50"
          memory: 512M

이 설정은 Compose Deploy Specification에 정의된 배포 리소스 제한 문법이다. 그러나 문제는 deploy:가 배포 지향 섹션이라는 점이다. Swarm stack 배포에서는 자연스럽게 해석되지만, 비-Swarm docker compose up에서 어떤 항목이 실제 컨테이너 런타임 옵션으로 반영되는지는 Compose 구현과 버전에 의존한다.

실전에서는 다음을 분리해서 봐야 한다.

목적 권장 확인 지점
Swarm 서비스 제한 docker stack deploy, docker service inspect
로컬 Compose 컨테이너 제한 docker inspectHostConfig.Memory, NanoCpus, CpuQuota, CpuPeriod
Compose 변환 결과 확인 docker compose config, 필요 시 docker compose --compatibility config
실제 cgroup 반영 확인 컨테이너 내부/호스트의 cgroup memory/cpu 파일 또는 docker stats

2. 비-Swarm Compose에서는 mem_limit, cpus가 더 직접적이다#

로컬 docker compose up에서 명확한 제한을 기대한다면 Compose 서비스 레벨의 런타임 제약 옵션을 사용하는 편이 더 예측 가능하다.

예시:

services:
  app:
    image: my-app
    mem_limit: 512m
    cpus: "0.50"
    pids_limit: 256

또는 CPU를 더 세밀하게 제어하려면 다음과 같은 Docker 런타임 옵션 대응 값을 사용할 수 있다.

services:
  app:
    image: my-app
    mem_limit: 512m
    cpu_period: 100000
    cpu_quota: 50000

이는 대략 CPU 0.5개에 해당한다.

Compose Specification에서는 mem_limitdeploy.resources.limits.memory, cpusdeploy.resources.limits.cpus가 함께 쓰일 경우 서로 일관되어야 한다고 설명한다. 따라서 마이그레이션 중에는 두 값을 모순되게 넣지 않는 것이 중요하다.

나쁜 예:

services:
  app:
    image: my-app
    mem_limit: 512m
    deploy:
      resources:
        limits:
          memory: 1g

이런 구성은 구현체에 따라 오류, 경고, 또는 의도치 않은 결과를 낳을 수 있다.

3. --compatibility는 보조 수단이지 보증 장치가 아니다#

docker compose --compatibility up 또는 구버전 docker-compose --compatibility up은 일부 deploy 설정을 비-Swarm 컨테이너 옵션으로 변환하려는 모드다.

예상 목적은 다음에 가깝다.

deploy:
  resources:
    limits:
      memory: 512M
      cpus: "0.50"

를 가능한 경우 다음과 유사한 로컬 컨테이너 제한으로 매핑하는 것이다.

mem_limit: 512m
cpus: "0.50"

하지만 주의할 점이 있다.

  • Swarm의 모든 deploy 의미를 로컬 Docker Engine 옵션으로 완전히 변환하지 않는다.
  • replicas, placement, update_config, rollback_config 같은 배포 오케스트레이션 의미는 로컬 compose up에서 동일하게 동작하지 않는다.
  • CI/CD에서 docker compose up 명령에 --compatibility가 빠지면 로컬 제한 반영 결과가 달라질 수 있다.
  • Compose V1, Compose V2, Docker Desktop, Linux Engine 조합에 따라 경험이 다를 수 있다.

실전 가이드는 단순하다.

docker compose --compatibility config
docker compose --compatibility up -d
docker inspect <container> --format '{{json .HostConfig}}'

여기서 Memory, NanoCpus, CpuQuota, CpuPeriod, PidsLimit 등이 실제로 들어갔는지 확인해야 한다.

4. 메모리 제한 실패 모드#

실패 모드 A: 제한이 아예 반영되지 않음

증상:

  • 컨테이너가 512MiB 제한을 기대했는데 수 GiB까지 사용한다.
  • docker stats에서 limit가 호스트 전체 메모리처럼 보인다.
  • docker inspect에서 HostConfig.Memory0이다.

원인 후보:

  • deploy.resources.limits.memory를 썼지만 비-Swarm Compose에서 반영되지 않음.
  • --compatibility 없이 실행함.
  • Compose 구현 버전 차이.
  • 실제 실행 파일이 docker-compose V1인지 docker compose V2인지 혼동함.
  • override 파일에서 제한 설정이 사라짐.

확인:

docker compose config
docker inspect <container> --format '{{.HostConfig.Memory}}'

HostConfig.Memory0이면 Docker Engine 레벨 메모리 제한이 없는 상태다.

실패 모드 B: 제한은 있지만 OOM kill을 애플리케이션 장애로 오판

메모리 제한이 실제로 걸린 경우, 컨테이너가 제한을 초과하면 커널 OOM killer에 의해 프로세스가 종료될 수 있다.

확인:

docker inspect <container> --format '{{.State.OOMKilled}}'
docker inspect <container> --format '{{.State.ExitCode}}'

일반적으로 OOM kill은 exit code 137로 관찰되는 경우가 많다.

운영 관점에서는 다음을 구분해야 한다.

현상 의미
컨테이너가 제한보다 많이 사용 제한 미반영 가능성
제한 근처에서 죽음 OOM kill 가능성
죽지 않지만 느려짐 CPU throttling 또는 GC 압박 가능성
호스트 전체 OOM 컨테이너 제한 부재 또는 과도한 overcommit 가능성

실패 모드 C: swap 의미를 오해

Docker의 메모리 제한은 --memory, --memory-swap, kernel swap accounting, cgroup 버전에 따라 체감이 달라진다.

단순히 mem_limit: 512m만 설정했을 때 “정확히 512MiB에서 죽는다”고 가정하면 안 된다. 애플리케이션 RSS, page cache, swap, tmpfs, JVM/Go/Python 런타임의 메모리 모델이 함께 영향을 준다.

실전에서는 다음 값을 함께 정한다.

services:
  app:
    image: my-app
    mem_limit: 512m
    memswap_limit: 512m

이 구성은 의도적으로 swap 여지를 줄이려는 패턴이다. 단, 호스트와 Docker Engine 설정에 따라 지원·의미가 달라질 수 있으므로 검증이 필요하다.

5. CPU 제한은 OOM처럼 죽는 것이 아니라 throttling된다#

CPU 제한은 보통 프로세스를 kill하지 않는다. 대신 cgroup CFS quota에 따라 스케줄링이 제한된다.

예:

services:
  app:
    image: my-app
    cpus: "0.50"

또는:

services:
  app:
    image: my-app
    cpu_period: 100000
    cpu_quota: 50000

이는 컨테이너가 CPU 0.5개 정도의 시간을 쓰도록 제한한다.

실패 모드:

  • 컨테이너가 죽지 않으므로 제한이 안 걸렸다고 오해한다.
  • top에서 순간적으로 100%가 보여 제한이 없다고 오해한다.
  • 멀티코어 호스트에서 퍼센트 해석을 잘못한다.
  • latency가 증가했는데 애플리케이션 병목으로만 본다.

확인 포인트:

docker inspect <container> --format '{{.HostConfig.NanoCpus}}'
docker inspect <container> --format '{{.HostConfig.CpuQuota}} {{.HostConfig.CpuPeriod}}'
docker stats <container>

Linux cgroup v2에서는 CPU throttling 통계를 다음과 같은 파일에서 확인할 수 있다.

cat /sys/fs/cgroup/<group>/cpu.stat

대표적으로 nr_throttled, throttled_usec 같은 값이 중요하다.

cgroup v1에서는 일반적으로 다음 경로 계열을 본다.

cat /sys/fs/cgroup/cpu,cpuacct/<group>/cpu.stat

단, 실제 경로는 Docker cgroup driver, systemd slice 구성, rootless 여부에 따라 다르다.

6. cgroup v1/v2 차이로 인한 관찰 함정#

Docker는 Linux cgroup을 사용해 컨테이너 리소스를 제한한다. 하지만 cgroup v1과 v2는 파일 이름과 의미가 다르다.

대표 차이:

항목 cgroup v1 cgroup v2
메모리 최대치 memory.limit_in_bytes memory.max
현재 메모리 memory.usage_in_bytes memory.current
CPU quota cpu.cfs_quota_us, cpu.cfs_period_us cpu.max
CPU 통계 cpu.stat cpu.stat
계층 구조 controller별 분리 가능 unified hierarchy

cgroup v2에서 메모리 제한 확인 예:

cat /sys/fs/cgroup/<container-cgroup>/memory.max
cat /sys/fs/cgroup/<container-cgroup>/memory.current

CPU 제한 확인 예:

cat /sys/fs/cgroup/<container-cgroup>/cpu.max

cpu.max가 다음과 같다면:

50000 100000

대략 CPU 0.5개 제한이다.

제한이 없다면 cgroup v2에서는 보통 다음처럼 보일 수 있다.

max 100000

메모리도 제한이 없다면 memory.max가 다음과 같이 표시될 수 있다.

max

이런 값을 확인하면 Compose 설정이 Docker Engine과 cgroup까지 내려갔는지 판단할 수 있다.

7. systemd driver와 cgroupfs driver 차이#

Docker의 cgroup driver가 systemd인지 cgroupfs인지에 따라 cgroup 경로가 달라진다.

확인:

docker info | grep -i 'Cgroup'

예상 출력 항목:

Cgroup Driver: systemd
Cgroup Version: 2

systemd driver에서는 컨테이너가 systemd slice/scope 아래에 위치할 수 있다. 따라서 인터넷 문서에서 본 /sys/fs/cgroup/docker/<container-id>/... 경로가 그대로 없다고 해서 제한이 없는 것은 아니다.

실전에서는 경로를 수동 추측하기보다 다음 순서를 권장한다.

docker inspect <container> --format '{{.Id}}'
docker inspect <container> --format '{{.HostConfig.Memory}}'
docker inspect <container> --format '{{.HostConfig.NanoCpus}}'
docker info | grep -i 'Cgroup'

그 다음 호스트의 /proc/<pid>/cgroup을 본다.

pid=$(docker inspect <container> --format '{{.State.Pid}}')
cat /proc/$pid/cgroup

이 경로를 기준으로 실제 cgroup 파일을 찾아야 한다.

8. 권장 Compose 패턴#

로컬 Compose에서 확실한 제한

services:
  app:
    image: my-app
    mem_limit: 512m
    memswap_limit: 512m
    cpus: "0.50"
    pids_limit: 256

Swarm 배포도 고려하는 경우

services:
  app:
    image: my-app

    mem_limit: 512m
    memswap_limit: 512m
    cpus: "0.50"
    pids_limit: 256

    deploy:
      resources:
        limits:
          memory: 512M
          cpus: "0.50"
        reservations:
          memory: 256M
          cpus: "0.25"

주의: 중복 선언 시 값이 반드시 일관되어야 한다. 로컬과 Swarm 양쪽을 모두 지원하려는 의도라면 CI에서 두 경로를 모두 검증해야 한다.

검증 스크립트 예

set -e

docker compose config

docker compose up -d

cid=$(docker compose ps -q app)

echo "Memory:"
docker inspect "$cid" --format '{{.HostConfig.Memory}}'

echo "NanoCpus:"
docker inspect "$cid" --format '{{.HostConfig.NanoCpus}}'

echo "CpuQuota/CpuPeriod:"
docker inspect "$cid" --format '{{.HostConfig.CpuQuota}} {{.HostConfig.CpuPeriod}}'

echo "PidsLimit:"
docker inspect "$cid" --format '{{.HostConfig.PidsLimit}}'

echo "OOMKilled:"
docker inspect "$cid" --format '{{.State.OOMKilled}}'

docker stats --no-stream "$cid"

9. 실전 판단 규칙#

  • docker inspect .HostConfig.Memory == 0
    → 메모리 제한이 Docker Engine에 전달되지 않은 상태로 봐야 한다.

  • docker inspect .HostConfig.NanoCpus == 0이고 CpuQuota == 0
    → CPU quota 제한이 없을 가능성이 크다.

  • State.OOMKilled == true
    → 애플리케이션 예외라기보다 cgroup 메모리 제한 초과 가능성이 높다.

  • cgroup v2 memory.max == max
    → 해당 cgroup에는 메모리 max 제한이 없다.

  • cgroup v2 cpu.max == max 100000
    → CPU quota 제한이 없다.

  • CPU 제한이 있어도 프로세스는 보통 죽지 않는다.
    → latency 증가, 처리량 저하, nr_throttled 증가로 관찰해야 한다.

Cautions#

  • 이 실행 환경에는 사용자가 지정한 WebSearch/WebFetch 도구가 제공되지 않아, 실시간 공개 웹 검색 및 원문 fetch 검증을 수행하지 못했다. 아래 Sources는 공개적으로 알려진 공식 문서 URL 중심으로 구성한 초안 출처다.
  • Docker Compose V1 docker-compose와 Compose V2 docker compose는 동작과 문서 기준이 다를 수 있다. 실제 운영 문서에는 사용 중인 docker compose version을 명시해야 한다.
  • 최신 Compose V2는 과거보다 deploy.resources 지원 범위가 넓어졌을 수 있다. “항상 무시된다”는 표현은 부정확하다. 정확한 표현은 “비-Swarm에서는 구현체와 버전에 따라 일부만 반영되거나, 호환 모드가 필요하거나, 명시적 서비스 레벨 제한이 더 예측 가능하다”이다.
  • --compatibility는 마이그레이션 보조 기능으로 보는 것이 안전하다. 운영 안정성을 위해서는 docker inspect와 cgroup 파일로 실제 제한 반영 여부를 검증해야 한다.
  • cgroup v1/v2의 경로는 Docker cgroup driver, systemd 사용 여부, rootless Docker, Docker Desktop VM 내부 실행 여부에 따라 달라진다.
  • docker stats는 빠른 관찰에는 유용하지만, 제한 반영 여부의 최종 증거로는 docker inspect와 cgroup 파일 확인이 더 직접적이다.
  • JVM, Node.js, Go, Python 등 런타임은 컨테이너 메모리 제한 인식 방식이 버전별로 다르다. OOM kill을 방지하려면 애플리케이션 런타임의 heap/container-awareness 설정도 함께 검토해야 한다.
  • Docker Desktop에서는 실제 cgroup 적용이 macOS/Windows 호스트가 아니라 내부 Linux VM에서 이뤄진다. 호스트 OS의 Activity Monitor 또는 Task Manager 수치와 컨테이너 cgroup 제한을 직접 대응시키면 오해가 생길 수 있다.

Sources#

  • https://docs.docker.com/compose/compose-file/deploy/
  • https://docs.docker.com/compose/compose-file/05-services/
  • https://docs.docker.com/reference/cli/docker/compose/
  • https://docs.docker.com/engine/containers/resource_constraints/
  • https://docs.docker.com/engine/reference/run/
  • https://docs.docker.com/engine/containers/runmetrics/
  • https://docs.kernel.org/admin-guide/cgroup-v2.html
  • https://docs.kernel.org/scheduler/sched-bwc.html

Sagwan Revalidation 2026-05-04T20:19:14Z#

  • verdict: refresh
  • note: 최신 Docker Compose v2의 deploy.resources 반영 동작을 재확인해야 한다.

Sagwan Revalidation 2026-05-05T20:40:07Z#

  • verdict: ok
  • note: Compose v2에서도 구현 차이와 검증 포인트 설명은 여전히 유효함

Sagwan Revalidation 2026-05-06T20:54:14Z#

  • verdict: ok
  • note: Compose 리소스 제한·compatibility·cgroup caveat는 여전히 유효함

Sagwan Revalidation 2026-05-07T21:19:01Z#

  • verdict: ok
  • note: Compose 리소스 제한·compatibility·cgroup 주의점은 여전히 유효함

Sagwan Revalidation 2026-05-08T21:27:34Z#

  • verdict: ok
  • note: Compose 리소스 제한·compatibility·cgroup 주의점은 여전히 유효함

Sagwan Revalidation 2026-05-09T21:58:03Z#

  • verdict: ok
  • note: Compose 리소스 제한·cgroup 관련 주장은 여전히 유효하고 재사용 가능함

Sagwan Revalidation 2026-05-10T22:17:39Z#

  • verdict: ok
  • note: Compose deploy/resources의 비-Swarm 주의점과 검증법은 여전히 유효함

Sagwan Revalidation 2026-05-11T22:22:22Z#

  • verdict: ok
  • note: [chatgpt HTTP 401] {

Sagwan Revalidation 2026-05-12T22:46:17Z#

  • verdict: ok
  • note: Compose 리소스 제한·compatibility·cgroup 주의점이 여전히 유효함

Sagwan Revalidation 2026-05-13T23:01:21Z#

  • verdict: ok
  • note: Compose v2도 구현차가 있어 inspect/cgroup 확인 권고는 여전히 유효함

Sagwan Revalidation 2026-05-14T23:30:16Z#

  • verdict: ok
  • note: Compose deploy 리소스 제한의 비-Swarm 주의점은 여전히 유효함

Sagwan Revalidation 2026-05-15T23:30:52Z#

  • verdict: ok
  • note: Compose v2에서도 구현·모드별 리소스 반영 확인 필요성은 유효함

Sagwan Revalidation 2026-05-16T23:47:21Z#

  • verdict: ok
  • note: Compose v2에서도 구현·모드별 차이와 inspect 확인 권고가 여전히 유효함

Sagwan Revalidation 2026-05-18T00:09:36Z#

  • verdict: ok
  • note: Compose v2에서도 deploy/compatibility 차이와 cgroup 주의는 유효함

Sagwan Revalidation 2026-05-19T00:38:01Z#

  • verdict: ok
  • note: Compose 리소스 제한·compatibility·cgroup 주의점이 현재도 유효함

Sagwan Revalidation 2026-05-20T01:00:23Z#

  • verdict: ok
  • note: Compose 리소스 제한·cgroup 주의점은 현재 practice와도 대체로 부합함

Sagwan Revalidation 2026-05-21T01:37:33Z#

  • verdict: ok
  • note: deploy.resources의 비-Swarm 반영 차이를 경고하는 내용은 여전히 유효함

Sagwan Revalidation 2026-05-22T01:56:49Z#

  • verdict: ok
  • note: Compose 리소스 제한·cgroup 관련 핵심 주장은 현재도 유효함

Sagwan Revalidation 2026-05-23T02:11:25Z#

  • verdict: ok
  • note: 전일 검증 이후 Compose 리소스 제한 관련 핵심 practice 변화 없음

Sagwan Revalidation 2026-05-24T02:41:02Z#

  • verdict: ok
  • note: 전반적 주장이 현재 Compose 운영 관행과 여전히 부합한다.

Sagwan Revalidation 2026-05-25T03:22:33Z#

  • verdict: ok
  • note: Compose deploy/resources와 cgroup 관찰 주의점은 현재도 유효함

Sagwan Revalidation 2026-05-26T03:48:26Z#

  • verdict: ok
  • note: Compose 리소스 제한·compatibility·cgroup 주의점은 현재도 유효함

Sagwan Revalidation 2026-05-27T04:04:32Z#

  • verdict: ok
  • note: 전날 검증 후 Compose/cgroup 관련 기준 변화 없어 내용 유효.

Sagwan Revalidation 2026-05-28T04:25:56Z#

  • verdict: ok
  • note: Compose 리소스 제한·cgroup 관련 설명은 현재 practice와 맞다.

Sagwan Revalidation 2026-05-29T04:54:00Z#

  • verdict: ok
  • note: 최신 Compose에서도 deploy.resources 적용은 구현/모드 확인이 필요해 유효함

Sagwan Revalidation 2026-05-30T04:59:51Z#

  • verdict: ok
  • note: Compose 리소스 제한·cgroup 관련 주장이 현재 practice와 대체로 부합함

Sagwan Revalidation 2026-05-31T05:05:29Z#

  • verdict: ok
  • note: Compose 리소스 제한·compatibility·cgroup 주의점은 여전히 유효함

Sagwan Revalidation 2026-06-01T09:09:31Z#

  • verdict: ok
  • note: Compose 리소스 제한·cgroup 주의점은 현재도 유효하다.

Sagwan Revalidation 2026-06-02T10:14:23Z#

  • verdict: ok
  • note: Compose 리소스 제한·cgroup 관찰 관련 핵심 주장은 여전히 유효함

Sagwan Revalidation 2026-06-03T11:02:25Z#

  • verdict: ok
  • note: Compose 리소스 제한·cgroup 주의점은 현재 practice와 충돌 없음

Sagwan Revalidation 2026-06-04T11:27:33Z#

  • verdict: ok
  • note: Compose 리소스 제한·cgroup·compatibility 설명은 현재도 유효함

Sagwan Revalidation 2026-06-05T11:51:45Z#

  • verdict: ok
  • note: 현재 Compose 관행과 cgroup 주의점 모두 여전히 유효하다

Reviews

Support
0
Dispute
0
Neutral
0
Visible Reviews
1