Fix Docker Container Thoát với Code 137 (OOMKilled: true)

intermediate🐳 Docker2026-03-18| Docker Engine 20.x+ trên Linux (Ubuntu, Debian, CentOS), macOS (Docker Desktop), Windows (Docker Desktop với WSL2)

Error Message

Container exited with code 137 (OOMKilled: true)
#docker#oom#memory#container#resources

Mô Tả Lỗi

Container của bạn dừng đột ngột. Chạy docker ps và container đã biến mất. Khi kiểm tra thì thấy:

Container exited with code 137 (OOMKilled: true)

Exit code 137 có nghĩa là tiến trình nhận được signal 9 (SIGKILL). Cờ OOMKilled: true cho biết đây không phải crash hay bug — OOM killer của Linux kernel đã chủ động kết thúc container vì hết bộ nhớ.

Nguyên Nhân

Linux kernel theo dõi mức sử dụng bộ nhớ theo từng cgroup (control group). Khi container vượt quá giới hạn bộ nhớ — hoặc khi chính host hết bộ nhớ — OOM killer sẽ chọn một tiến trình để kết thúc. Container thường là mục tiêu đầu tiên.

Có hai tình huống kích hoạt lỗi này:

  • Container chạm giới hạn bộ nhớ cứng: Bạn đã đặt --memory và container vượt quá giới hạn đó.
  • Host hết bộ nhớ: Không có giới hạn nào được đặt. Container cứ tăng dần cho đến khi host không còn gì để cấp phát.

Dù trường hợp nào, hướng xử lý đều giống nhau: xác nhận nguyên nhân kill, kiểm tra mức sử dụng, đặt hoặc tăng giới hạn, rồi tìm xem thứ gì đang ngốn bộ nhớ.

Bước 1: Xác Nhận Đây Là OOMKill

Kiểm tra trạng thái container trực tiếp:

docker inspect <container_name_or_id> --format='{{.State.OOMKilled}}'

Trả về true? OOM killer chính là thủ phạm. Giờ xác nhận exit code:

docker inspect <container_name_or_id> --format='{{.State.ExitCode}}'

Kết quả phải là 137. Kiểm tra kernel log trên host để xem chính xác tiến trình nào bị nhắm đến:

dmesg | grep -i 'oom\|killed'
# hoặc trên hệ thống systemd:
journalctl -k | grep -i oom

Tìm dòng kiểu như: Out of memory: Kill process 1234 (node) score 900 or sacrifice child. Tên tiến trình và OOM score cho biết container nào bị kill và tại sao kernel chọn nó.

Bước 2: Kiểm Tra Mức Sử Dụng Bộ Nhớ Hiện Tại

Xem các container đang chạy đang dùng bao nhiêu bộ nhớ:

docker stats --no-stream

Lệnh này hiển thị mức sử dụng hiện tại so với giới hạn đã cấu hình của từng container. Bất kỳ container nào đang dùng trên 80% giới hạn đều có nguy cơ bị kill ngay khi có một đợt tăng traffic.

Kiểm tra giới hạn đã đặt trên container bị kill:

docker inspect <container_name_or_id> --format='{{.HostConfig.Memory}}'

Giá trị 0 có nghĩa là không có giới hạn nào được đặt — container có thể dùng hết toàn bộ bộ nhớ của host cho đến khi kernel buộc phải can thiệp.

Bước 3: Đặt Hoặc Tăng Giới Hạn Bộ Nhớ

Với container khởi động bằng docker run, thêm các cờ bộ nhớ:

docker run -d \
  --memory="512m" \
  --memory-swap="512m" \
  your-image

Đặt --memory-swap bằng với --memory để tắt swap cho container. Đặt cao hơn để cho phép dùng một phần swap, hoặc bỏ qua để mặc định gấp đôi giới hạn bộ nhớ.

Với Docker Compose (v3 dùng deploy, áp dụng trong Swarm hoặc với cờ --compatibility):

services:
  app:
    image: your-image
    deploy:
      resources:
        limits:
          memory: 512M
        reservations:
          memory: 256M

Với Docker Compose tiêu chuẩn (cú pháp v2, không dùng Swarm):

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

Sau khi cập nhật, tạo lại container:

docker compose up -d --force-recreate app

Bước 4: Tìm Nguồn Gốc Ngốn Bộ Nhớ

Tăng giới hạn chỉ giúp câu giờ. Nó không sửa được memory leak. Nếu mức sử dụng cứ tiếp tục tăng, container sẽ lại bị OOMKill ở giới hạn mới cao hơn — bạn chỉ chờ lâu hơn giữa các lần restart mà thôi.

Kiểm tra thống kê bộ nhớ bên trong container đang chạy:

docker exec -it <container_name> sh -c 'cat /proc/meminfo'
docker exec -it <container_name> top

Với ứng dụng Java — nếu không có cài đặt nhận biết container, JVM sẽ định kích thước heap dựa trên RAM của host, không phải giới hạn container. Trên host 16GB chứa container 512MB, JVM có thể nhắm tới heap tối đa 4GB. Khi ứng dụng đẩy quá 512MB, kernel sẽ kill nó. Hãy sửa bằng cách đặt kích thước heap tường minh:

docker run -d \
  -e JAVA_OPTS="-Xmx256m -Xms128m" \
  your-java-image

Trên Java 8u191+ hoặc Java 10+, dùng -XX:+UseContainerSupport thay thế — JVM sẽ đọc giới hạn cgroup trực tiếp và tự điều chỉnh kích thước theo đó:

docker run -d \
  -e JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0" \
  your-java-image

Với ứng dụng Node.js — V8 cũng không tôn trọng giới hạn bộ nhớ container. Hãy giới hạn heap tường minh:

docker run -d your-node-image node --max-old-space-size=256 app.js

Với tình trạng bộ nhớ tăng liên tục — hãy lấy heap dump và dùng công cụ profiling của runtime để tìm chỗ rò rỉ. Càng profile sớm dưới tải thực tế, bạn càng tìm ra nguyên nhân nhanh hơn.

Bước 5: Thêm Restart Policy Trong Lúc Sửa Nguyên Nhân Gốc

Cách này giúp container tự khởi động lại sau OOMKill trong khi bạn xử lý vấn đề thực sự:

docker run -d \
  --restart=on-failure:5 \
  --memory="512m" \
  your-image

Hoặc trong Compose:

services:
  app:
    image: your-image
    restart: on-failure
    mem_limit: 512m

Cách này không ngăn được OOMKill. Nó chỉ giữ cho service hoạt động trong lúc bạn tìm nguyên nhân gốc rễ.

Kiểm Tra Sau Khi Sửa

Khởi động lại container và theo dõi bộ nhớ theo thời gian thực:

docker stats <container_name>

Bộ nhớ nên ổn định dưới 70–80% giới hạn đã cấu hình. Sau 10–15 phút dưới tải bình thường, xác nhận cờ OOMKilled đã được xóa:

docker inspect <container_name> --format='{{.State.OOMKilled}}'
# Kết quả mong đợi: false

Cũng kiểm tra xem container có đang âm thầm restart ở nền không:

docker ps --format "table {{.Names}}\t{{.Status}}\t{{.RestartCount}}"
# RestartCount tăng liên tục nghĩa là container vẫn đang bị kill

Mẹo Tránh OOMKilled Trong Môi Trường Production

  • Luôn đặt giới hạn bộ nhớ. Container không giới hạn trên host dùng chung có thể kéo sập mọi thứ đang chạy trên đó. Hãy coi việc đặt giới hạn là yêu cầu bắt buộc khi deploy, không phải tùy chọn.
  • Đặt reservations kèm theo limits trong Compose để scheduler biết tài nguyên tối thiểu cần có trước khi đặt container.
  • Cảnh báo trước khi chạm giới hạn. Kết nối Docker metrics với Prometheus + cAdvisor và cảnh báo ở mức 80% bộ nhớ — để bạn xử lý vấn đề trước khi kernel làm điều đó thay bạn.
  • Kiểm thử với giới hạn production ngay trên máy local. Chạy container với cùng giá trị --memory như trên production, rồi load test. Bắt lỗi bộ nhớ trước khi đưa lên production.
  • Chú ý kích thước log buffer. Ứng dụng ghi lượng lớn log vào buffer trong bộ nhớ có thể âm thầm làm bộ nhớ tăng vọt. Kiểm tra cấu hình logging driver của bạn.
  • Cân nhắc kỹ khi dùng swap. Cho phép swap (--memory-swap > --memory) có thể ngăn OOMKill nhưng làm giảm hiệu năng nghiêm trọng khi hệ thống chịu tải. Đây chỉ là giải pháp tạm thời, không phải cách sửa thực sự.

Related Error Notes