- Published on
Docker exec가 멈출 때 - cgroup v2·TTY 원인
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
운영 중 컨테이너에 들어가서 로그를 보거나, 프로세스를 확인하려고 docker exec를 치는 순간 터미널이 그대로 멈춰버리는 경우가 있습니다. CPU도 안 쓰고, 에러도 안 나오고, 마치 입력이 먹통이 된 것처럼 보이죠. 이 문제는 단순히 Docker가 "느린" 게 아니라, 커널의 cgroup v2 동작 방식, TTY/STDIN 전달, 컨테이너 런타임(runc/containerd)과 보안 정책이 맞물릴 때 발생하는 전형적인 교착/대기 패턴인 경우가 많습니다.
이 글에서는 docker exec가 멈출 때 특히 많이 만나는 두 축인 cgroup v2와 TTY를 중심으로, 재현 포인트와 진단 커맨드, 그리고 우회/근본 해결책을 단계적으로 정리합니다.
- 관련해서 Docker 쪽의 캐시/빌드 이슈도 자주 같이 튀어나오니, 빌드가 이상할 때는 Docker BuildKit 캐시 무효화 원인·해결 8가지도 함께 참고하면 좋습니다.
- 호스트가 systemd 기반이고 서비스 재시작 루프가 얽혀 있으면 systemd 서비스가 반복 재시작될 때 원인 추적처럼 "호스트 레벨" 관점에서 같이 보는 게 빠릅니다.
증상 패턴: "멈춤"이 실제로는 무엇을 기다리나
docker exec의 대표적인 멈춤 패턴은 대개 아래 중 하나입니다.
TTY 할당/입력 스트림 대기
docker exec -it ...에서 특히 자주 발생- PTY 생성, STDIN 전달, 터미널 크기 ioctl 처리 등에서 멈춤
cgroup v2 관련 작업 대기
- 컨테이너에 exec 프로세스를 붙이는 과정에서 cgroup 이동/설정/검증이 걸림
- 특정 커널/런타임 조합에서 교착 또는 매우 긴 대기
보안 정책(SELinux/AppArmor/seccomp)에서의 차단이 "에러가 아니라 대기"로 보이는 경우
- 로그에는 남지만 클라이언트는 멈춘 것처럼 보일 수 있음
컨테이너 내부 PID 1/TTY 상태가 꼬여 exec 프로세스가 I/O를 못 받는 경우
이제부터는 "원인 후보를 빠르게 줄이는" 순서로 접근합니다.
1단계: -t와 -i를 분리해 TTY 문제인지 먼저 확인
가장 먼저 할 일은 TTY를 끄고 실행해보는 것입니다. -it는 편하지만, 멈춤의 상당수가 여기서 발생합니다.
# TTY 없이 실행 (STDIN도 필요 없으면 -i도 빼기)
docker exec <container> sh -lc 'echo ok; id; ps -o pid,tty,stat,cmd'
# STDIN만 유지 (-i), TTY는 끄기 (-t 제거)
docker exec -i <container> sh -lc 'cat /proc/1/status | head'
# TTY만 유지 (-t), STDIN은 끄기 (-i 제거)
docker exec -t <container> sh -lc 'echo hello'
판별 기준:
docker exec -i는 잘 되는데docker exec -it만 멈춘다- 거의 확실하게 PTY/TTY 계층 문제입니다.
docker exec자체가 어떤 옵션에서도 멈춘다- cgroup/런타임/보안 정책 등 호스트 레벨을 우선 의심합니다.
TTY 멈춤의 흔한 원인
- 클라이언트 터미널과 Docker 데몬 사이의 PTY 프록시 문제
- 컨테이너 내부에서
/dev/pts가 비정상 상태 - 컨테이너가
--privileged가 아니고 특정 디바이스 접근이 제한된 상태에서 PTY 관련 syscall이 꼬이는 경우 - 오래된 runc/containerd 버그
컨테이너 내부에서 /dev/pts 상태 확인
docker exec <container> sh -lc 'ls -al /dev/pts; mount | grep pts || true'
정상이라면 /dev/pts가 마운트되어 있고, ptmx가 보입니다.
임시 우회: script로 TTY 에뮬레이션
TTY가 깨진 환경에서는 script가 통하는 경우가 있습니다.
docker exec -i <container> sh -lc 'script -q -c "sh" /dev/null'
또는 애초에 TTY 없이 진단을 진행하는 것도 현실적인 선택입니다.
2단계: 호스트가 cgroup v2인지 확인하고, Docker가 어떤 드라이버를 쓰는지 본다
cgroup v2 전환 이후(특히 systemd 기반 배포판에서) docker exec/docker run 경로에서 미묘한 문제가 늘었습니다. 먼저 환경을 확인합니다.
# 호스트에서 cgroup 버전 확인
stat -fc %T /sys/fs/cgroup
# cgroup v2면 보통 cgroup2fs가 나옵니다.
# Docker 정보에서 cgroup 드라이버 및 버전 확인
docker info | sed -n '1,120p'
여기서 확인할 포인트:
Cgroup Version: 2인지Cgroup Driver: systemd인지cgroupfs인지- Docker/Containerd/Runc 버전
cgroup v2에서 exec가 멈출 수 있는 메커니즘(요지)
docker exec는 컨테이너 내부에 "새 프로세스"를 만들고, 그 프로세스를 컨테이너의 cgroup에 붙입니다. cgroup v2는 v1과 달리 계층 구조/컨트롤러 활성화 방식이 엄격하고, systemd가 소유한 트리와 Docker가 만드는 트리가 섞이면 다음 문제가 생깁니다.
- 컨트롤러가 상위에서 활성화되지 않아 하위에서 설정이 막힘
- 프로세스 이동(
cgroup.procs쓰기)이 예상치 못한 조건에서 대기/실패 - 특정 커널 버그/런타임 조합에서 교착에 가까운 상태
이때 클라이언트는 "exec가 멈췄다"고 느끼지만, 실제로는 데몬/런타임이 cgroup 작업을 끝내지 못해 응답을 못 주는 경우가 있습니다.
3단계: 멈춘 docker exec를 호스트에서 추적한다 (strace/log)
멈춤을 "감"으로 해결하면 재발합니다. 짧게라도 증거를 남겨야 합니다.
Docker 데몬 로그 확인
systemd 환경이라면:
journalctl -u docker --since "10 min ago" -n 200 --no-pager
containerd를 별도 서비스로 쓰는 환경이면:
journalctl -u containerd --since "10 min ago" -n 200 --no-pager
멈춘 순간 데몬이 무엇을 하고 있는지 strace
# dockerd PID 찾기
pidof dockerd
# 시스템콜 레벨에서 대기 지점 확인
strace -p <dockerd_pid> -f -s 128 -tt
futex, epoll_wait, read에서 멈춰 있는지, 아니면 /sys/fs/cgroup/.../cgroup.procs 같은 경로를 만지다 막히는지 힌트를 얻을 수 있습니다.
주의: 운영 환경에서는 strace가 부담이 될 수 있으니, 재현 가능한 시간대/스테이징에서 먼저 권장합니다.
4단계: cgroup v2 트리와 컨트롤러 활성 상태를 점검
cgroup v2의 핵심은 "상위에서 컨트롤러를 켜야 하위에서 쓸 수 있다"는 점입니다.
# v2에서 사용 가능한 컨트롤러
cat /sys/fs/cgroup/cgroup.controllers
# 현재 cgroup에서 하위에 위임된(활성화된) 컨트롤러
cat /sys/fs/cgroup/cgroup.subtree_control
Docker가 systemd 드라이버를 쓰면 보통 systemd가 cgroup 트리를 관리합니다. 이때 Docker 쪽 설정과 systemd 위임 정책이 충돌하면 exec 경로에서 문제가 발생할 수 있습니다.
Docker의 cgroup 드라이버를 systemd로 맞추기
배포판/환경에 따라 다르지만, cgroup v2에서는 systemd 드라이버가 더 안정적인 경우가 많습니다(특히 systemd가 PID 1인 호스트).
/etc/docker/daemon.json 예시:
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
적용 후:
systemctl restart docker
이미 systemd 드라이버인데도 문제라면, 오히려 버그/커널 조합 이슈일 수 있어 Docker/containerd/runc 업그레이드가 더 효과적입니다.
5단계: TTY/PTY 계층을 호스트-컨테이너 양쪽에서 확인
TTY 문제는 "컨테이너 내부"만 봐서는 안 풀릴 때가 많습니다. 호스트 측에서도 PTY 리소스가 고갈되거나 권한/마운트가 꼬일 수 있습니다.
호스트에서 PTY 리소스 확인
# 사용 중인 pts 수 대략 확인
ls /dev/pts | wc -l
# 커널 파라미터(환경에 따라 의미가 다를 수 있음)
sysctl kernel.pty.max 2>/dev/null || true
컨테이너 내부 프로세스의 TTY 상태
docker exec <container> sh -lc 'ps -eo pid,ppid,tty,stat,cmd | head -n 30'
TTY가 ?로만 나오고, 컨테이너의 메인 프로세스가 데몬화하면서 표준 입출력을 닫아버린 경우 -it exec가 특정 상황에서 더 취약해질 수 있습니다.
6단계: 보안 정책(SELinux/AppArmor/seccomp) 로그를 확인
차단이 에러로 바로 떨어지지 않고 대기로 체감되는 케이스도 있습니다.
SELinux
getenforce 2>/dev/null || true
journalctl --since "10 min ago" | grep -i avc | tail -n 50
AppArmor
aa-status 2>/dev/null || true
journalctl --since "10 min ago" | grep -i apparmor | tail -n 50
seccomp는 Docker 기본 프로파일에서 드물게 문제가 되지만, 커스텀 프로파일을 쓰거나 오래된 커널에서 특정 syscall이 애매하게 막히면 exec 경로가 이상해질 수 있습니다.
7단계: 재현 가능한 최소 케이스로 좁히기
원인을 분리하려면 "문제 컨테이너"가 아니라 "최소 재현"이 필요합니다.
1) 동일 호스트에서 새 컨테이너로 exec 테스트
docker run --rm -d --name exec-test alpine:3.19 sh -lc 'sleep 100000'
docker exec -it exec-test sh
- 새 컨테이너에서도 멈춘다: 호스트/데몬/cgroup/TTY 계층 문제
- 새 컨테이너는 정상인데 특정 컨테이너만 멈춘다: 해당 컨테이너의 런타임 설정, PID 1, 마운트, 디바이스, 보안 정책 문제
2) TTY 없이 동일 작업
docker exec exec-test sh -lc 'echo ok'
이게 정상인데 -it만 멈추면 거의 TTY 이슈로 수렴합니다.
8단계: 실전 해결책 체크리스트
A. 빠른 우회(운영 즉시 대응)
docker exec -i또는 TTY 없는 exec로 일단 진단/조치- 컨테이너 재시작이 가능하면 재시작으로 PTY/상태 꼬임 해소
- 호스트에서 Docker 데몬 재시작(영향 범위 큼)
# 영향이 큰 조치이므로 유지보수 윈도우에서 권장
systemctl restart docker
B. 근본 해결(재발 방지)
Docker/containerd/runc 업그레이드
- cgroup v2/TTY 관련 버그는 "특정 버전 조합"에서 터지는 경우가 많습니다.
cgroup 드라이버 정합성 확보
- systemd 호스트면
native.cgroupdriver=systemd를 우선 검토
- systemd 호스트면
커널 업데이트
- cgroup v2는 커널 릴리스에 따른 안정성 차이가 큽니다.
보안 정책 로그 기반으로 예외/프로파일 조정
- 무작정
--privileged는 지양하고, 필요한 capability/device만 최소로
- 무작정
TTY 의존 운영 절차 줄이기
- exec 기반 운영을 줄이고, 관측은 로그/메트릭/트레이싱으로 전환
- 장애 분석 흐름은 브라우저/클라이언트 성능 분석과 마찬가지로 "증거 기반"이 중요합니다. 프론트 쪽이지만 접근법 자체는 비슷하니 Chrome INP 급락? Long Task 10분 추적·해결처럼 "측정-수집-원인 분리" 루틴을 참고할 만합니다.
자주 묻는 질문
Q. cgroup v2를 끄면 해결되나
일부 환경에서는 커널 부팅 옵션으로 v1로 되돌리면 현상이 사라지기도 합니다. 하지만 이는 임시방편에 가깝고, 장기적으로는 v2 대응이 필요합니다. 또한 배포판 정책에 따라 v1이 점점 비권장/제거되는 추세입니다.
Q. docker exec가 멈춘 상태에서 컨테이너는 정상 동작한다. 왜 그런가
exec는 "새 프로세스 생성 + cgroup 부착 + stdio 연결"이라는 별도 경로입니다. 컨테이너의 기존 프로세스는 정상이라도, exec 경로에서만 교착/대기가 생길 수 있습니다.
마무리: 결론은 "TTY 먼저, 그 다음 cgroup v2" 순서로 자르기
docker exec 멈춤은 원인이 다양하지만, 실전에서는 다음 순서가 가장 빠릅니다.
-it를 분리해서 TTY 문제인지 먼저 판별- 호스트가 cgroup v2인지 확인하고, Docker의 cgroup 드라이버/버전 조합을 점검
- 데몬 로그와
strace로 "진짜로 무엇을 기다리는지" 증거를 확보 - 업그레이드와 설정 정합성(systemd 드라이버 등)으로 재발을 줄이기
이 루틴대로 보면, "아무 에러 없이 멈추는" 상황도 대부분은 재현/설명 가능한 형태로 바뀌고, 팀 내에서 해결책을 표준화할 수 있습니다.