13년차의 서버실

서버, 인프라, 홈랩 기술 블로그

[Linux] cgroup v2 vs v1: 리눅스 컨테이너 리소스 제어 차이 분석

cgroup v2 vs v1: 리눅스 컨테이너 리소스 제어 차이 분석

컨테이너 운영에서 cgroup v2 vs v1은 단순한 버전 비교가 아닙니다. 실제 운영에선 “리소스 제한을 걸었다”와 “커널이 의도한 방식으로 그 제한을 집행한다” 사이의 간극이 자주 보이거든요. CPU는 남는데 응답 시간이 흔들리거나, 메모리 제한은 넣었는데 기대한 시점이 아니라 갑자기 OOM으로 터지거나, 블록 I/O가 한쪽에 쏠리면서 옆 워크로드 지연이 커지는 식입니다. 이런 문제를 따라가 보면 결국 cgroup 계층 구조, 컨트롤러 위임 방식, 런타임이 커널과 만나는 지점에서 갈리더라고요.

현장에서는 아직도 v1 흔적이 남아 있습니다. 다만 신규 배포판, systemd 중심 서버, 최근 컨테이너 런타임 조합이라면 cgroup v2를 기준으로 보는 편이 훨씬 덜 헷갈립니다. 운영 문서나 장애 대응 런북을 정리할 때도 먼저 묻는 건 하나예요. “이 서버는 v1 습관으로 읽어야 하나, 아니면 v2 규칙으로 읽어야 하나?” 이걸 초반에 잘못 잡으면 뒤에 보는 메트릭과 파일 경로 해석이 전부 어긋납니다.

cgroup v1과 v2 계층 구조 비교 다이어그램

cgroup v1은 컨트롤러별로 계층이 갈라지고, cgroup v2는 단일 트리에서 CPU·메모리·I/O 정책을 함께 다루는 모습을 보여주는 개요 이미지입니다.

1. 왜 아직도 cgroup v1과 v2를 따로 봐야 하나요

커널 기능만 놓고 보면 v1도 충분히 강력했습니다. 문제는 운영 복잡도였죠. v1은 <code>cpu, memory, blkio 같은 컨트롤러가 각자 별도 계층을 가질 수 있어서, 같은 프로세스라도 CPU는 A 경로, 메모리는 B 경로, I/O는 C 경로에 속하는 일이 자연스러웠습니다. 설계 자유도는 높았지만 장애가 나면 운영자가 세 개의 지도를 동시에 펼쳐야 했습니다. 이 구조는 대규모 운영보다는 기능이 먼저 확장된 커널 인터페이스에 더 가깝다고 보는 편이 맞습니다.

v2는 반대로 갔습니다. 유연성을 조금 덜어내는 대신 정책 일관성, 계층 예측 가능성, systemd와의 결합 안정성을 얻었습니다. 실무 체감도 꽤 분명합니다. v1에서는 “어디를 봐야 하지?”가 먼저였고, v2에서는 “이 값이 왜 이렇게 집행됐지?”가 먼저예요. 전자는 길을 잃기 쉽고, 후자는 원인 분석이 비교적 선형적입니다.

제가 기준으로 삼는 차이는 세 가지입니다.

  • 관찰 경로 수: v1은 컨트롤러별로 흩어지고, v2는 한 트리에서 따라가기 쉽습니다.
  • 정책 위임 방식: v2는 부모가 자식에게 무엇을 넘겼는지 cgroup.subtree_control로 드러납니다.
  • 운영 도구 친화성: systemd, 최신 런타임, 배포판 기본값과 맞물릴 때 v2가 덜 삐걱거립니다.

2. cgroup v2 vs v1 핵심 개념: 파일명 차이보다 더 큰 변화

cgroup v2 vs v1을 파일명 변경 정도로만 기억하면 절반만 이해한 셈입니다. 많이들 memory.limit_in_bytes가 memory.max로 바뀌었다는 식으로 외우는데, 진짜 변화는 “리소스 제한을 어떤 계층 규칙 위에 올릴 것인가”에 있습니다. 이 차이를 이해해야 마이그레이션할 때 덜 흔들립니다.

v1이 남긴 운영적 특징

  • 컨트롤러마다 별도 마운트와 별도 경로가 가능해서 추적 포인트가 많습니다.
  • 오래된 자동화 스크립트와 호환성이 좋지만, 경로 가정이 강하게 박혀 있는 경우가 많습니다.
  • 성능 이슈가 났을 때 CPU, 메모리, I/O를 같은 구조로 읽기 어렵습니다.

v2가 바꾼 운영적 특징

  • /sys/fs/cgroup 단일 트리에서 정책과 통계를 같이 읽습니다.
  • cgroup.controllers와 cgroup.subtree_control로 하위 위임 여부가 명시됩니다.
  • cpu.max, memory.max, io.max처럼 이름 규칙이 정돈돼서 자동화 작성이 수월합니다.
  • memory.high, memory.low, memory.min처럼 “강한 상한”과 “보호·완충 구간”을 나눠 설계하기 좋습니다.
항목 cgroup v1 cgroup v2 실무 해석
계층 구조 컨트롤러별 분리 가능 통합 계층(unified hierarchy) v2가 장애 분석 경로를 줄입니다.
CPU 제한 cpu.cfs_quota_us, cpu.cfs_period_us cpu.max v2는 쿼터와 주기를 한 파일에서 읽습니다.
CPU 가중치 cpu.shares cpu.weight 절대 제한보다 경쟁 상황에서의 우선순위 설계에 중요합니다.
메모리 상한 memory.limit_in_bytes memory.max 상한만 보면 부족하고, v2에선 memory.high도 같이 봐야 합니다.
I/O 제어 blkio.* io.* 장치 단위 제한과 관찰 포인트가 v2에서 더 일관적입니다.
위임 모델 복잡하고 일관성 낮음 명시적 위임 하위 cgroup이 왜 제어 파일을 못 쓰는지 원인 파악이 쉽습니다.
운영 난이도 도구와 경로 의존성이 큼 상대적으로 단순 신규 표준화는 v2 쪽이 유리합니다.

실무에서 특히 큰 차이는 v2의 메모리 압박 제어 모델입니다. v1 시절에는 상한(limit) 위주로 사고하는 팀이 많았는데, 상한만으로 운영하면 평소엔 멀쩡하다가 피크 순간에 갑자기 세게 잘리는 일이 생깁니다. v2는 memory.high 같은 완충 구간을 둘 수 있어서, “죽이기 전에 먼저 늦추는” 설계를 하기가 좋습니다. 이거 트래픽 피크가 있는 서비스에선 체감 차이가 꽤 큽니다.

3. cgroup v2 vs v1 확인: 내 서버가 어떤 계층인지 빠르게 판별하기

이 단계는 생략하면 안 됩니다. 장애 대응에서도 가장 먼저 보는 지점이에요. 블로그 글이나 사내 문서는 v2 기준인데, 실제 호스트는 hybrid 또는 v1 잔재가 섞여 있으면 설명이 전부 어긋나기 때문입니다.

  1. 파일시스템 타입을 확인합니다.
  2. 마운트 구조를 확인합니다.
  3. 현재 프로세스가 속한 cgroup 경로를 확인합니다.
  4. 대표 제어 파일 이름을 확인합니다.
# 1) cgroup 파일시스템 타입 확인
stat -fc %T /sys/fs/cgroup

# 2) 마운트 구조 확인
mount | grep cgroup

# 3) 현재 셸의 cgroup 소속 확인
cat /proc/self/cgroup

# 4) v2 대표 파일 확인
ls /sys/fs/cgroup | grep -E 'cgroup.controllers|cgroup.subtree_control|cpu.max|memory.max|io.max'

판별 기준은 이렇습니다.

  • stat -fc %T /sys/fs/cgroup 결과가 cgroup2fs면 v2입니다.
  • mount 결과에서 cgroup on /sys/fs/cgroup/cpu처럼 컨트롤러별 마운트가 여럿 보이면 v1일 가능성이 큽니다.
  • /proc/self/cgroup이 단일 계층 형태면 v2, 여러 컨트롤러 라인이 나뉘면 v1 또는 hybrid일 가능성이 큽니다.

systemd 환경에서는 system.slice, user.slice, kubepods.slice 같은 경로가 보일 수 있습니다. 이건 이상 징후가 아니라 커널 계층을 서비스 관리 단위와 맞춘 흔적입니다. 오히려 경로가 이렇게 구조적으로 보이면 추적이 쉬워집니다.

다만 여기서 자주 놓치는 함정이 있습니다. 호스트는 v2인데, 오래된 문서나 툴링이 v1 파일명을 기준으로 체크를 돌리는 경우입니다. 그러면 모니터링은 “파일 없음”만 찍고 끝납니다. 그래서 단순 버전 확인에서 멈추지 말고, 실제 자동화가 찾는 파일명까지 같이 검증하는 편이 안전합니다.

/sys/fs/cgroup 아래 파일 구조와 컨트롤러 예시

실제 서버에서 확인하는 cgroup 파일 구조 예시와 cgroup.controllers, cpu.max, memory.max 같은 핵심 파일 위치를 보여주는 이미지입니다.

4. 실전 구현: cgroup v2에서 CPU와 메모리 제한 걸기

리눅스에서 cgroup을 만지는 방법은 크게 둘입니다. systemd에 맡기거나, 파일시스템을 직접 다루거나입니다. 운영 서버에서는 전자를 먼저 권합니다. 이유는 단순해요. 프로세스 배치, 권한, 정리(clean-up), 서비스 수명주기를 systemd가 같이 책임져 주기 때문입니다. 반대로 원리 이해나 실험에는 후자가 빠릅니다.

방법 A. systemd-run으로 transient unit 실행

# 메모리 상한 512M, 메모리 압박 임계값 384M, CPU 쿼터 50%
sudo systemd-run --unit=cg-demo --scope \
  -p MemoryHigh=384M \
  -p MemoryMax=512M \
  -p CPUQuota=50% \
  -p CPUQuotaPeriodSec=100ms \
  bash -c 'stress-ng --vm 1 --vm-bytes 700M --cpu 2 --timeout 60s'

# systemd 속성 및 매핑 결과 확인
systemctl status cg-demo.scope
systemctl show cg-demo.scope \
  -p ControlGroup \
  -p MemoryCurrent \
  -p MemoryHigh \
  -p MemoryMax \
  -p CPUQuotaPerSecUSec \
  -p CPUQuotaPeriodUSec

여기서 중요한 포인트는 MemoryMax만 보지 않는 겁니다. 서비스가 순간 메모리 피크가 있는 구조라면 먼저 MemoryHigh를 잡고 관찰하는 편이 낫습니다. 상한을 너무 낮게 바로 조이면 OOM으로 가는 길이 짧아지거든요. 반대로 MemoryHigh는 압박 신호를 먼저 주기 때문에, 애플리케이션이 캐시를 줄이거나 GC가 반응할 시간을 벌 수 있습니다.

CPU도 비슷합니다. CPUQuota=50%는 직관적이지만, 워크로드가 짧은 버스트를 자주 내는 유형이면 평균 사용률은 괜찮아 보여도 nr_throttled가 빠르게 올라갈 수 있습니다. 이럴 때는 쿼터를 높일지, 아예 CPUWeight 중심으로 바꿀지 판단해야 합니다. 절대 제한은 비용 통제엔 좋지만, 지연 민감한 서비스에는 부작용이 더 크게 느껴질 수 있습니다.

방법 B. cgroup v2 파일을 직접 다뤄보기

직접 실험할 때는 위임 규칙을 먼저 확인해야 합니다. v2는 파일이 보인다고 다 쓸 수 있는 구조가 아닙니다. 부모 cgroup이 자식에게 컨트롤러를 위임하지 않으면 자식 쪽에 기대한 제어 파일이 나타나지 않거나, 설정이 먹지 않습니다.

# 루트에서 사용 가능한 컨트롤러 확인
cat /sys/fs/cgroup/cgroup.controllers

# 하위 cgroup에 cpu, memory 컨트롤러 위임
echo '+cpu +memory' | sudo tee /sys/fs/cgroup/cgroup.subtree_control

# 테스트용 cgroup 생성
sudo mkdir /sys/fs/cgroup/lab-demo

# 메모리 압박 임계값과 절대 상한 설정
echo 402653184 | sudo tee /sys/fs/cgroup/lab-demo/memory.high
echo 536870912 | sudo tee /sys/fs/cgroup/lab-demo/memory.max

# CPU 최대치 설정: 100ms 주기 중 50ms 사용
echo '50000 100000' | sudo tee /sys/fs/cgroup/lab-demo/cpu.max

# CPU 상대 우선순위 설정(기본 100, 범위 1~10000)
echo 200 | sudo tee /sys/fs/cgroup/lab-demo/cpu.weight

# 프로세스를 해당 cgroup에서 실행
sudo bash -c 'echo $$ > /sys/fs/cgroup/lab-demo/cgroup.procs; exec stress-ng --vm 1 --vm-bytes 700M --cpu 2 --timeout 60s'

이 방식은 이해에는 좋지만 운영에는 조심해야 합니다. 특히 systemd가 관리하는 호스트에서는 임의 디렉터리를 직접 다루는 방식보다 unit이나 scope로 묶는 편이 덜 위험합니다. 마지막 줄처럼 현재 셸을 직접 옮기는 패턴은 학습용으로는 괜찮아도 실서버에서는 실수 여지가 있습니다. 실제 서버에선 별도 서비스, 별도 scope, 별도 사용자 세션 단위로 분리하는 편이 재현성과 정리 측면에서 훨씬 편하더라고요.

5. 컨테이너 리소스 제어에서 무엇을 읽어야 하나

설정보다 더 중요한 건 관찰입니다. 제한을 넣는 건 10초면 끝나지만, 그 제한이 실제로 어떤 방식으로 시스템을 압박하는지 읽는 건 완전히 다른 일입니다. 기본적으로 볼 파일은 memory.current, memory.events, cpu.stat, io.stat이고, 필요하면 memory.pressure와 cpu.pressure까지 같이 봅니다.

# 메모리 사용량과 이벤트
cat /sys/fs/cgroup/lab-demo/memory.current
cat /sys/fs/cgroup/lab-demo/memory.events

# CPU 스로틀링 통계
cat /sys/fs/cgroup/lab-demo/cpu.stat

# I/O 통계
cat /sys/fs/cgroup/lab-demo/io.stat

# PSI(Pressure Stall Information) 확인
cat /sys/fs/cgroup/lab-demo/memory.pressure
cat /sys/fs/cgroup/lab-demo/cpu.pressure

읽는 기준은 숫자 자체보다 상관관계입니다.

  • memory.events의 high가 늘면 메모리 압박이 반복되고 있다는 뜻입니다. 바로 장애는 아니어도 응답 시간 악화의 전조일 수 있습니다.
  • memory.events의 max가 늘면 상한에 계속 부딪히고 있다는 뜻입니다. 이 상태가 길어지면 결국 OOM 또는 강한 reclaim으로 이어질 수 있습니다.
  • oom, oom_kill 증가와 애플리케이션 비정상 종료 시점이 맞물리면 원인 범위가 꽤 좁혀집니다.
  • cpu.stat의 nr_throttled, throttled_usec가 빠르게 증가하면 CPU 제한이 실제 처리 지연으로 번질 가능성이 큽니다.
  • memory.pressure가 높고 memory.current는 상한 아래라면, 단순 상한 초과보다 reclaim 비용이 문제일 수 있습니다.
  • io.stat만 보고 끝내면 안 됩니다. I/O는 애플리케이션 레이턴시, 파일시스템 flush, 스토리지 백엔드 상태와 같이 봐야 의미가 생깁니다.

여기서 많이 생기는 오해가 하나 있습니다. CPU throttling이 보인다고 무조건 나쁜 건 아닙니다. 배치 잡, 백그라운드 인덱싱, 테스트 워커처럼 원래 느려져도 되는 작업이라면 쿼터가 잘 먹고 있다는 뜻일 수 있습니다. 반대로 API 서버나 짧은 요청을 많이 받는 프론트 계층은 같은 throttling이 체감 지연으로 바로 번집니다. 결국 같은 지표라도 워크로드 성격에 따라 해석이 달라집니다.

systemd-run으로 생성한 cgroup과 메트릭 확인 흐름

systemd transient unit으로 프로세스를 띄우고 memory.events, cpu.stat, PSI를 확인하는 검증 흐름을 보여주는 구성도입니다.

6. v1에서 v2로 넘어갈 때 자주 만나는 함정과 근본 원인

이 부분이 진짜 운영 포인트입니다. 표면적으로는 “파일명이 바뀌었네” 정도로 보이지만, 실제 사고 원인은 더 아래층에 있습니다.

1) 예전 파일명을 계속 찾는 문제

현상은 간단합니다. memory.limit_in_bytes가 없고, 스크립트는 실패합니다. 근본 원인은 더 분명합니다. 자동화가 커널 인터페이스를 추상화하지 않고 파일명을 직접 하드코딩했기 때문입니다. 가능하면 내부 도구에 “v1/v2 판별 후 매핑” 레이어를 두는 편이 좋습니다. 운영 자동화는 언젠가 커널 세부 구현과 어긋나거든요.

2) cgroup.subtree_control를 빼먹는 문제

현상은 “파일은 있는데 자식에서 못 쓴다”, “생성한 cgroup에 기대한 제어 파일이 없다”입니다. 원인은 v2가 부모의 명시적 위임을 요구하기 때문입니다. v1 습관대로 디렉터리만 만들면 될 거라고 보면 거의 여기서 막힙니다.

3) no internal process 규칙을 무시하는 문제

v2는 자원 분배를 담당하는 부모 cgroup과 실제 워크로드가 섞이는 걸 경계합니다. 현상은 구조가 이상하게 꼬이거나, 하위 설계가 불편해지는 것이죠. 원인은 계층을 “정책 노드”와 “실행 노드”로 분리하지 않고 한군데에 몰아넣었기 때문입니다. systemd가 슬라이스와 스코프로 나눠 주는 이유도 여기와 맞닿아 있습니다.

4) 런타임 드라이버 불일치

Docker, containerd, kubelet, systemd 조합에서 꽤 자주 만나는 문제입니다. 제한은 건 것 같은데 예상한 경로가 아니라 다른 계층에 정책이 생기거나, 관찰 위치가 어긋납니다. 근본 원인은 cgroup driver와 서비스 관리자 계층이 서로 다른 모델을 가정하기 때문입니다. 이럴 때는 설정 파일 하나만 보지 말고, 실제 서비스 프로세스의 /proc/<pid>/cgroup과 systemd의 ControlGroup를 같이 봐야 합니다.

5) 메모리 상한만 믿고 압박 제어를 안 하는 문제

현상은 피크 순간 응답 시간이 급격히 나빠지거나, 갑작스러운 OOM으로 이어지는 것입니다. 원인은 메모리를 “최대치 하나”로만 관리했기 때문입니다. 서비스 성격에 따라 memory.high를 먼저 두고, 정말 넘기면 안 되는 한계만 memory.max로 닫는 구성이 더 안정적으로 먹히는 경우가 많습니다. 비용 통제와 안정성 사이에 완충 구간을 두는 셈이죠.

7. 재현 가능한 트러블슈팅 시나리오 하나

홈랩이나 테스트 서버에서 자주 재현하는 건 “제한은 정상인데, 체감 성능이 왜 이렇게 나쁘지?” 유형입니다. 이런 문제는 상한만 보는 습관으로는 잘 안 잡힙니다. 압박 이벤트와 스로틀링을 같이 봐야 감이 옵니다.

  1. MemoryHigh와 MemoryMax를 분리해서 가진 transient unit을 만듭니다.
  2. 그 안에서 메모리를 빠르게 쓰는 프로세스를 실행합니다.
  3. memory.current, memory.events, memory.pressure를 같이 봅니다.
  4. 동시에 journalctl과 애플리케이션 로그 시점을 맞춰 봅니다.
sudo systemd-run --unit=mem-lab --scope \
  -p MemoryHigh=192M \
  -p MemoryMax=256M \
  bash -c 'stress-ng --vm 1 --vm-bytes 400M --timeout 30s'

CG=$(systemctl show mem-lab.scope -p ControlGroup --value)

cat /sys/fs/cgroup${CG}/memory.current
cat /sys/fs/cgroup${CG}/memory.events
cat /sys/fs/cgroup${CG}/memory.pressure
journalctl -u mem-lab.scope --no-pager

이 시나리오에서 볼 건 세 가지입니다. 첫째, high 이벤트가 먼저 늘어나는지. 둘째, 그다음에 max나 oom이 붙는지. 셋째, 같은 시점에 애플리케이션 레이턴시나 종료 로그가 어떻게 움직이는지입니다. 여기서 얻는 교훈은 꽤 분명합니다. 메모리 문제는 사용량 숫자 하나로 판단하면 늦습니다. 압박 이벤트가 먼저 오고, 그다음 증상이 사용자에게 보이는 경우가 많습니다.

비슷한 방식으로 CPU도 재현할 수 있습니다. 쿼터를 낮춘 뒤 cpu.stat에서 nr_throttled가 늘어나는 속도와 애플리케이션 p95 지연을 같이 보면, “제한이 먹는다”와 “서비스가 느려진다”가 언제 연결되는지 보입니다. 실제 튜닝은 그 시점을 찾는 데서 출발하더라고요.

8. cgroup v2 vs v1 선택 기준: 언제 v2로 가고, 언제 점진 전환할까

실무 기준으로 말하면 신규 구축은 v2 우선입니다. 다만 “무조건 최신이 좋다”가 아니라, 어떤 운영 비용을 줄일 수 있느냐로 봐야 합니다. v2는 특히 systemd 중심 서버, 표준화된 컨테이너 운영, 내부 자동화 정비를 같이 할 수 있는 팀에 잘 맞습니다. 반대로 오래된 스크립트와 모니터링이 v1 경로에 깊게 묶여 있다면, 이행 비용을 먼저 계산해야 합니다.

상황 추천 이유 판단 포인트
신규 리눅스 서버 구축 cgroup v2 우선 통합 계층, systemd 친화성, 운영 단순화 자동화와 모니터링을 처음부터 v2 기준으로 맞출 수 있는지
systemd 중심 운영 v2 강력 추천 unit 속성과 cgroup 파일 매핑이 자연스럽습니다. systemctl show와 커널 파일을 함께 읽는 절차를 표준화할 수 있는지
레거시 스크립트가 v1 파일명에 깊게 의존 점진 전환 한 번에 바꾸면 장애 탐지 공백이 생길 수 있습니다. 파일명 매핑 레이어와 모니터링 수정이 끝났는지
CPU 버스트가 중요한 지연 민감 서비스 v2 + 쿼터 신중 적용 절대 제한이 지연을 키울 수 있습니다. CPUQuota보다 CPUWeight가 더 맞는지
메모리 피크가 잦은 서비스 v2 + memory.high 활용 갑작스러운 OOM보다 완충형 압박 제어에 유리합니다. 상한 하나가 아니라 압박 임계값과 함께 설계했는지
장애 분석 중 파일이 안 보여 혼란스러운 상태 먼저 버전 확인 v1/v2 혼동이 원인인 경우가 많습니다. 실제 PID 경로와 서비스 매니저 경로가 일치하는지

정리하면 선택 기준은 비교적 단순합니다.

  • 이럴 땐 A: 신규 서버, systemd 기본, 컨테이너 런타임 표준화가 가능하면 cgroup v2로 가는 편이 맞습니다.
  • 이럴 땐 B: 레거시 감시 스크립트와 운영 도구가 v1 경로에 박혀 있으면, 즉시 전환보다 매핑 정리 후 점진 이행이 안전합니다.
  • 이럴 땐 C: 비용 통제 목적의 배치성 워크로드는 CPUQuota/MemoryMax를 비교적 적극적으로 써도 됩니다.
  • 이럴 땐 D: 지연 민감 서비스는 CPUWeight + 완만한 메모리 압박 제어를 먼저 검토하고, 절대 상한은 마지막에 닫는 편이 낫습니다.
cgroup v1과 v2 선택 기준 요약 인포그래픽

신규 구축, 레거시 유지, systemd 운영, 지연 민감 서비스 등 상황별로 cgroup v1과 v2 선택 기준을 정리한 요약 이미지입니다.

9. 저는 이렇게 권합니다

cgroup v2 vs v1을 공부하다 보면 파일명과 옵션 암기에 빠지기 쉽습니다. 그런데 운영 관점에서는 그보다 어떤 워크로드에 어떤 제어 방식을 쓰느냐가 더 중요합니다. 새로 시작하는 환경이라면 cgroup v2를 기본값으로 잡는 편이 좋습니다. 그리고 메모리는 상한 하나만 보지 말고 memory.high와 이벤트 카운터까지 같이 보고, CPU는 무조건 쿼터부터 닫기보다 서비스 성격에 따라 CPUWeight와 병행해서 판단해 보세요.

반대로 v1 기반 자동화가 이미 깊게 들어가 있다면, 무리하게 한 번에 바꾸는 건 추천하지 않습니다. 이 경우엔 순서가 있습니다. 먼저 버전 판별 로직을 넣고, 그다음 파일명 매핑을 정리하고, 마지막으로 모니터링과 런북을 v2 기준으로 갈아타는 편이 안전합니다. 마이그레이션 실패는 커널 기능 부족보다 운영 습관이 바뀌지 않은 상태에서 인터페이스만 바꾼 경우에 더 자주 나오더라고요.

한 줄로 정리하면 이렇습니다. 레거시 제약이 없다면 v2, 레거시가 강하면 점진 전환입니다. 그리고 어떤 경우든 설정값 하나만 보지 말고 memory.events, cpu.stat, PSI, 서비스 로그를 같이 읽어야 실제 원인이 보입니다. 관련 글이 있다면 systemd resource-control 가이드나 컨테이너 메모리 튜닝 체크리스트도 이어서 읽어 두세요. 같이 보면 운영 감각이 훨씬 빨리 붙습니다.