런타임 메트릭
런타임 메트릭 (Runtime metrics)
Docker stats
docker stats 명령을 사용해 컨테이너의 런타임 메트릭을 실시간으로 스트리밍할 수 있어요. 이 명령은 CPU, 메모리 사용량, 메모리 제한, 네트워크 IO 메트릭을 지원해요.
다음은 docker stats 명령의 샘플 출력이에요:
$ docker stats redis1 redis2
CONTAINER CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O
redis1 0.07% 796 KB / 64 MB 1.21% 788 B / 648 B 3.568 MB / 512 KB
redis2 0.07% 2.746 MB / 64 MB 4.29% 1.266 KB / 648 B 12.4 MB / 0 B
docker stats 레퍼런스 페이지에 docker stats 명령에 대한 자세한 내용이 있어요.
출처: 문서
본문
제어 그룹 (Control groups)
Linux 컨테이너는 제어 그룹(control groups)에 의존하는데, 이는 프로세스 그룹을 추적할 뿐만 아니라 CPU, 메모리, 블록 I/O 사용량에 대한 메트릭도 노출해요. 그 메트릭들에 접근할 수 있고 네트워크 사용량 메트릭도 얻을 수 있어요. 이것은 "순수" LXC 컨테이너와 Docker 컨테이너 모두에 해당돼요.
제어 그룹은 의사 파일시스템(pseudo-filesystem)을 통해 노출돼요. 현대 배포판에서는 이 파일시스템을 /sys/fs/cgroup 아래에서 찾을 수 있어요. 그 디렉토리 아래에는 devices, freezer, blkio 등이라고 불리는 여러 하위 디렉토리가 보여요. 각 하위 디렉토리는 실제로 서로 다른 cgroup 계층에 해당해요.
오래된 시스템에서는 제어 그룹이 구별되지 않는 계층으로 /cgroup에 마운트되어 있을 수 있어요. 이 경우 하위 디렉토리 대신 그 디렉토리에 파일 묶음과, 아마도 기존 컨테이너에 해당하는 일부 디렉토리가 보여요.
제어 그룹이 어디에 마운트되어 있는지 알아내려면 다음을 실행해요:
$ grep cgroup /proc/mounts
cgroup 열거 (Enumerate cgroups)
cgroup의 파일 배치는 v1과 v2 사이에서 크게 달라요.
시스템에 /sys/fs/cgroup/cgroup.controllers가 있으면 v2를 사용하는 것이고, 없으면 v1을 사용하는 거예요. 자신의 cgroup 버전에 해당하는 하위 섹션을 참고하세요.
cgroup v2는 다음 배포판에서 기본으로 사용돼요:
- Fedora (31부터)
- Debian GNU/Linux (11부터)
- Ubuntu (21.10부터)
Important 이 페이지 뒷부분의 상세 메트릭 예시는 cgroup v1 파일 배치를 설명해요. cgroup v2 호스트에서는 이 페이지를 사용해 컨테이너의 cgroup 디렉토리를 찾은 다음 그 디렉토리의 컨트롤러 파일(예:
memory.*,cpu.*,io.*)을 검사하세요. 컨트롤러 의미에 대해서는 cgroup v2 커널 문서를 참고하세요.
cgroup v1
/proc/cgroups를 살펴보면 시스템에 알려진 다양한 제어 그룹 하위 시스템, 그들이 속한 계층, 포함하는 그룹 수를 볼 수 있어요.
/proc/<pid>/cgroup을 살펴보면 프로세스가 속한 제어 그룹을 볼 수도 있어요. 제어 그룹은 계층 마운트포인트의 루트에 상대적인 경로로 표시돼요. /는 프로세스가 그룹에 할당되지 않았음을 뜻하고, /lxc/pumpkin은 프로세스가 pumpkin이라는 컨테이너의 구성원임을 나타내요.
cgroup v2
cgroup v2 호스트에서 /proc/cgroups의 내용은 의미가 없어요. 사용 가능한 컨트롤러는 /sys/fs/cgroup/cgroup.controllers를 참고하세요.
cgroup 버전 변경 (Changing cgroup version)
cgroup 버전을 변경하려면 전체 시스템을 재부팅해야 해요.
systemd 기반 시스템에서 커널 커맨드 라인에 systemd.unified_cgroup_hierarchy=1을 추가하면 cgroup v2를 활성화할 수 있어요. cgroup 버전을 v1로 되돌리려면 대신 systemd.unified_cgroup_hierarchy=0을 설정해야 해요.
시스템에 grubby 명령이 있으면(예: Fedora) 커맨드 라인을 다음과 같이 수정할 수 있어요:
$ sudo grubby --update-kernel=ALL --args="systemd.unified_cgroup_hierarchy=1"
grubby 명령이 없으면 /etc/default/grub의 GRUB_CMDLINE_LINUX 줄을 편집하고 sudo update-grub을 실행해요.
cgroup v2에서 Docker 실행 (Running Docker on cgroup v2)
Docker는 Docker 20.10부터 cgroup v2를 지원해요. cgroup v2에서 Docker를 실행하려면 다음 조건도 충족해야 해요:
- containerd: v1.4 이상
- runc: v1.0.0-rc91 이상
- 커널: v4.15 이상(v5.2 이상 권장)
cgroup v2 모드는 cgroup v1 모드와 약간 다르게 동작한다는 점에 유의하세요:
- 기본 cgroup 드라이버(
dockerd --exec-opt native.cgroupdriver)는 v2에서systemd, v1에서cgroupfs예요. - 기본 cgroup 네임스페이스 모드(
docker run --cgroupns)는 v2에서private, v1에서host예요. docker run플래그--oom-kill-disable은 v2에서 무시돼요.
주어진 컨테이너의 cgroup 찾기 (Find the cgroup for a given container)
각 컨테이너에 대해 각 계층에 하나의 cgroup이 생성돼요. 오래된 LXC userland 도구 버전이 있는 오래된 시스템에서는 cgroup의 이름이 컨테이너의 이름이에요. 더 최근 버전의 LXC 도구에서는 cgroup이 lxc/<container_name>.이에요.
cgroups를 사용하는 Docker 컨테이너의 경우 cgroup 이름은 컨테이너의 전체 ID 또는 긴 ID예요. 컨테이너가 docker ps에서 ae836c95b4c3로 나타나면 그 긴 ID는 ae836c95b4c3c9e9179e0e91015512da89fdec91612f63cebae57df9a5444c79 같은 것일 수 있어요. docker inspect 또는 docker ps --no-trunc으로 찾아볼 수 있어요.
Docker 컨테이너의 메모리 메트릭을 보려면 모든 것을 종합해 다음 경로를 살펴보세요:
- cgroup v1,
cgroupfs드라이버의/sys/fs/cgroup/memory/docker/<longid>/ - cgroup v1,
systemd드라이버의/sys/fs/cgroup/memory/system.slice/docker-<longid>.scope/ - cgroup v2,
cgroupfs드라이버의/sys/fs/cgroup/docker/<longid>/ - cgroup v2,
systemd드라이버의/sys/fs/cgroup/system.slice/docker-<longid>.scope/
cgroups의 메트릭: memory, CPU, block I/O (cgroup v1)
Note 이 섹션은 cgroup v1 메트릭 파일과 형식을 문서화해요. cgroup v2에서는 컨테이너 cgroup 디렉토리의 관련 컨트롤러 파일을 검사하고 커널 문서를 참고하세요.
각 하위 시스템(memory, CPU, block I/O)에 대해 하나 이상의 의사 파일이 존재하며 통계를 담고 있어요.
메모리 메트릭: memory.stat
메모리 메트릭은 memory cgroup에서 찾을 수 있어요. 메모리 제어 그룹은 호스트의 메모리 사용량을 매우 세밀하게 계산하므로 약간의 오버헤드를 추가해요. 그 때문에 많은 배포판이 이 기능을 기본으로 활성화하지 않기로 선택했어요. 일반적으로 활성화하려면 커널 커맨드 라인 매개변수 cgroup_enable=memory swapaccount=1을 추가하기만 하면 돼요.
메트릭은 의사 파일 memory.stat에 있어요. 다음과 같아요:
cache 11492564992
rss 1930993664
mapped_file 306728960
pgpgin 406632648
pgpgout 403355412
swap 0
pgfault 728281223
pgmajfault 1724
inactive_anon 46608384
active_anon 1884520448
inactive_file 7003344896
active_file 4489052160
unevictable 32768
hierarchical_memory_limit 9223372036854775807
hierarchical_memsw_limit 9223372036854775807
total_cache 11492564992
total_rss 1930993664
total_mapped_file 306728960
total_pgpgin 406632648
total_pgpgout 403355412
total_swap 0
total_pgfault 728281223
total_pgmajfault 1724
total_inactive_anon 46608384
total_active_anon 1884520448
total_inactive_file 7003344896
total_active_file 4489052160
total_unevictable 32768
첫 번째 절반(total_ 접두사 없음)은 하위 cgroup을 제외한 cgroup 내 프로세스와 관련된 통계를 담고 있어요. 두 번째 절반(total_ 접두사 있음)은 하위 cgroup도 포함해요.
일부 메트릭은 "게이지(gauges)", 즉 증가하거나 감소할 수 있는 값이에요. 예를 들어 swap은 cgroup 구성원이 사용하는 swap 공간의 양이에요. 다른 일부는 "카운터(counters)", 즉 특정 이벤트의 발생을 나타내므로 값이 올라갈 수만 있는 값이에요. 예를 들어 pgfault는 cgroup 생성 이후 페이지 폴트의 횟수를 나타내요.
cache
블록 디바이스의 블록과 정확히 연관지을 수 있는 이 제어 그룹의 프로세스가 사용하는 메모리 양. 디스크의 파일을 읽고 쓸 때 이 양은 증가해요. 이것은 "관례적인" I/O(open, read, write syscall)와 매핑된 파일(mmap)을 사용할 때 모두 해당돼요. tmpfs 마운트가 사용하는 메모리도 계산하며, 그 이유는 불명확해요.
rss
디스크의 어떤 것에도 해당하지 않는 메모리 양: 스택, 힙, 익명 메모리 맵.
mapped_file
제어 그룹의 프로세스가 매핑한 메모리 양을 나타내요. 얼마나 많은 메모리가 사용되는지에 대한 정보는 주지 않고, 어떻게 사용되는지를 알려줘요.
pgfault, pgmajfault
cgroup의 프로세스가 각각 "페이지 폴트(page fault)"와 "메이저 폴트(major fault)"를 트리거한 횟수를 나타내요. 페이지 폴트는 프로세스가 현재 물리 메모리 프레임에 매핑되지 않은 가상 메모리 페이지에 접근할 때 발생해요. 이것은 메모리 관리의 정상적인 부분이에요. 예를 들어 프로세스가 스왑아웃된 메모리 영역이나 메모리 매핑된 파일에 해당하는 영역을 읽을 때 페이지 폴트가 발생해요: 이 경우 커널이 디스크에서 페이지를 로드하고 CPU가 메모리 접근을 완료하게 해요. 프로세스가 copy-on-write 메모리 영역에 쓸 때도 발생해요: 커널이 메모리 페이지를 복제하고 프로세스 자신의 페이지 복사본에 쓰기 연산을 재개해요. "메이저" 폴트는 커널이 디스크에서 데이터를 읽어야 할 때 발생해요. 기존 페이지를 복제하거나 빈 페이지를 할당할 때는 일반(또는 "마이너") 폴트예요.
swap
이 cgroup의 프로세스가 현재 사용하는 swap 양.
active_anon, inactive_anon
커널이 각각 활성(active) 과 비활성(inactive) 으로 식별한 익명 메모리의 양. "익명(Anonymous)" 메모리는 디스크 페이지와 연결되지 않은 메모리예요. 즉, 위에서 설명한 rss 카운터와 동등해요. 실제로 rss 카운터의 정의는 active_anon + inactive_anon - tmpfs(이 제어 그룹이 마운트한 tmpfs 파일시스템이 사용한 메모리 양)예요. 그럼 "active"와 "inactive"의 차이는 무엇일까요? 페이지는 처음에 "active"이고, 정기적으로 커널이 메모리를 훑어 일부 페이지를 "inactive"로 표시해요. 페이지에 다시 접근할 때마다 즉시 다시 "active"로 표시돼요. 커널이 메모리가 거의 부족해져 디스크로 스왑아웃할 때가 되면 커널은 "inactive" 페이지를 스왑해요.
active_file, inactive_file
위의 anon 메모리와 비슷하게 active 와 inactive 가 있는 캐시 메모리. 정확한 공식은 cache = active_file + inactive_file + tmpfs예요. 커널이 active와 inactive 세트 사이에서 메모리 페이지를 옮기는 데 사용하는 정확한 규칙은 익명 메모리에 사용하는 것과 다르지만, 일반적인 원칙은 같아요. 커널이 메모리를 회수해야 할 때 이 풀에서 깨끗한(=수정되지 않은) 페이지를 회수하는 것이 더 저렴해요, 즉시 회수할 수 있기 때문이에요(익명 페이지와 더티/수정된 페이지는 먼저 디스크에 써야 해요).
unevictable
회수할 수 없는 메모리 양. 일반적으로 mlock으로 "잠긴" 메모리를 계산해요. 암호화 프레임워크가 비밀 키와 기타 민감한 자료가 결코 디스크로 스왑아웃되지 않도록 하는 데 자주 사용돼요.
memory_limit, memsw_limit
이것들은 실제 메트릭이 아니라 이 cgroup에 적용된 제한을 상기시키는 것이에요. 첫 번째는 이 제어 그룹의 프로세스가 사용할 수 있는 물리 메모리의 최대 양을 나타내고, 두 번째는 RAM+swap의 최대 양을 나타내요.
페이지 캐시의 메모리 계산은 매우 복잡해요. 서로 다른 제어 그룹의 두 프로세스가 같은 파일을 읽으면(결국 디스크의 같은 블록에 의존), 해당 메모리 청크는 제어 그룹들 사이에 분할돼요. 좋은 점이지만, 이는 cgroup이 종료될 때 다른 cgroup의 메모리 사용량이 증가할 수 있음을 의미해요. 그 메모리 페이지들에 대한 비용을 더 이상 분할하지 않기 때문이에요.
CPU 메트릭: cpuacct.stat
메모리 메트릭을 다뤘으니 나머지는 비교하면 모두 간단해요. CPU 메트릭은 cpuacct 컨트롤러에 있어요.
각 컨테이너에 대해 의사 파일 cpuacct.stat이 컨테이너의 프로세스가 축적한 CPU 사용량을 user와 system 시간으로 구분해 담고 있어요. 그 구분은:
user시간은 프로세스가 프로세스 코드를 실행하며 CPU를 직접 제어한 시간의 양.system시간은 커널이 프로세스를 대신해 시스템 호출을 실행하는 시간.
이 시간들은 1/100초 단위 틱으로 표현되며, "user jiffies"라고도 불러요. 초당 USER_HZ _"jiffies" 가 있고, x86 시스템에서 USER_HZ는 100이에요. 역사적으로 이것은 초당 스케줄러 "틱" 수와 정확히 일치했지만, 더 높은 주파수 스케줄링과 tickless kernel 덕분에 틱 수는 무의미해졌어요.
블록 I/O 메트릭 (Block I/O metrics)
블록 I/O는 blkio 컨트롤러에서 계산돼요. 서로 다른 메트릭이 여러 파일에 흩어져 있어요. 커널 문서의 blkio-controller 파일에서 심층적인 세부 내용을 찾을 수 있지만, 여기 가장 관련 있는 것들의 짧은 목록이 있어요:
blkio.sectors
cgroup 구성원 프로세스가 읽고 쓴 512바이트 섹터 수를 디바이스별로 담고 있어요. 읽기와 쓰기는 단일 카운터로 합쳐져요.
blkio.io_service_bytes
cgroup이 읽고 쓴 바이트 수를 나타내요. 디바이스마다 4개의 카운터가 있는데, 각 디바이스에 대해 동기 vs 비동기 I/O, 그리고 읽기 vs 쓰기를 구분하기 때문이에요.
blkio.io_serviced
수행된 I/O 작업 수(크기와 무관). 디바이스마다 4개의 카운터도 있어요.
blkio.io_queued
현재 이 cgroup에 대해 대기 중인 I/O 작업 수를 나타내요. 즉, cgroup이 어떤 I/O도 하지 않으면 0이에요. 그 반대는 사실이 아니에요. 즉, 대기 중인 I/O가 없어도 cgroup이(I/O 측면에서) 유휴 상태임을 의미하지 않아요. 그저 조용한 디바이스에 순수하게 동기 읽기를 하고 있어 즉시 처리할 수 있어 대기열에 들어가지 않을 수도 있어요. 또한 어떤 cgroup이 I/O 하위 시스템에 부하를 주는지 알아내는 데 도움이 되지만, 상대적 수량임을 명심하세요. 프로세스 그룹이 더 많은 I/O를 수행하지 않아도 다른 디바이스들 때문에 디바이스 부하가 증가하면 대기열 크기가 커질 수 있어요.
네트워크 메트릭 (Network metrics)
네트워크 메트릭은 제어 그룹이 직접 노출하지 않아요. 그럴 만한 이유가 있어요: 네트워크 인터페이스는 네트워크 네임스페이스 의 컨텍스트 안에 존재해요. 커널이 프로세스 그룹이 보내고 받은 패킷과 바이트에 대한 메트릭을 축적할 수는 있겠지만 그 메트릭들은 그다지 유용하지 않아요. 인터페이스별 메트릭을 원하거든요(로컬 lo 인터페이스에서 발생하는 트래픽은 실제로 중요하지 않으니까). 하지만 단일 cgroup의 프로세스가 여러 네트워크 네임스페이스에 속할 수 있으므로 그 메트릭들은 해석하기 더 어려울 거예요: 여러 네트워크 네임스페이스는 여러 lo 인터페이스, 잠재적으로 여러 eth0 인터페이스 등을 의미하거든요. 그래서 제어 그룹으로 네트워크 메트릭을 수집할 쉬운 방법이 없어요.
대신 다른 소스에서 네트워크 메트릭을 수집할 수 있어요.
iptables
iptables(또는 더 정확히 말해 iptables가 그저 인터페이스인 netfilter 프레임워크)는 상당한 어카운팅을 할 수 있어요.
예를 들어 웹 서버의 나가는 HTTP 트래픽을 계산하는 규칙을 설정할 수 있어요:
$ iptables -I OUTPUT -p tcp --sport 80
-j나 -g 플래그가 없으므로 규칙은 일치한 패킷을 세고 다음 규칙으로 진행해요.
나중에 카운터 값을 확인할 수 있어요:
$ iptables -nxvL OUTPUT
기술적으로 -n은 필수는 아니지만, iptables가 이 시나리오에서 쓸모없는 DNS 역조회를 하는 것을 방지해요.
카운터에는 패킷과 바이트가 포함돼요. 이처럼 컨테이너 트래픽에 대한 메트릭을 설정하려면 for 루프를 실행해 컨테이너 IP 주소마다 FORWARD 체인에 두 개의 iptables 규칙(각 방향에 하나)을 추가할 수 있어요. 이것은 NAT 레이어를 통과하는 트래픽만 측정해요. userland 프록시를 통과하는 트래픽도 추가해야 해요.
그런 다음 정기적으로 카운터를 확인해야 해요. collectd를 사용한다면 iptables 카운터 수집을 자동화하는 좋은 플러그인이 있어요.
인터페이스 수준 카운터 (Interface-level counters)
각 컨테이너는 가상 이더넷 인터페이스를 가지므로 그 인터페이스의 TX와 RX 카운터를 직접 확인하고 싶을 수 있어요. 각 컨테이너는 vethKk8Zqi 같은 이름의 호스트 내 가상 이더넷 인터페이스와 연관돼 있어요. 어떤 인터페이스가 어떤 컨테이너에 해당하는지 알아내는 것은 안타깝게도 어려워요.
하지만 지금은 최선의 방법이 컨테이너 안에서 메트릭을 확인하는 것이에요. 이를 위해 ip-netns magic을 사용해 컨테이너의 네트워크 네임스페이스 안에서 호스트 환경의 실행 파일을 실행할 수 있어요.
ip-netns exec 명령은 현재 프로세스에 보이는 어떤 네트워크 네임스페이스 안에서도 (호스트 시스템에 존재하는) 어떤 프로그램이든 실행할 수 있게 해줘요. 이는 호스트가 컨테이너의 네트워크 네임스페이스에 들어갈 수 있지만, 컨테이너는 호스트나 다른 피어 컨테이너에 접근할 수 없음을 의미해요. 컨테이너는 자신의 하위 컨테이너와는 상호작용할 수 있어요.
명령의 정확한 형식은:
$ ip netns exec <nsname> <command...>
예를 들어:
$ ip netns exec mycontainer netstat -i
ip netns는 네임스페이스 의사 파일을 사용해 mycontainer 컨테이너를 찾아요. 각 프로세스는 하나의 네트워크 네임스페이스, 하나의 PID 네임스페이스, 하나의 mnt 네임스페이스 등에 속하고, 이 네임스페이스들은 /proc/<pid>/ns/ 아래에 구체화돼요. 예를 들어 PID 42의 네트워크 네임스페이스는 의사 파일 /proc/42/ns/net으로 구체화돼요.
ip netns exec mycontainer ...를 실행하면 /var/run/netns/mycontainer가 그러한 의사 파일 중 하나이기를 기대해요. (심볼릭 링크는 허용돼요.)
즉, 컨테이너의 네트워크 네임스페이스 안에서 명령을 실행하려면 다음을 해야 해요:
- 조사하려는 컨테이너 내 어떤 프로세스의 PID 알아내기
/var/run/netns/<somename>에서/proc/<thepid>/ns/net으로 심볼릭 링크 생성ip netns exec <somename> ....실행
네트워크 사용량을 측정하려는 컨테이너 내 프로세스의 cgroup을 찾는 방법은 Enumerate Cgroups를 검토해요. 거기에서 cgroup(따라서 컨테이너)의 모든 PID를 담고 있는 tasks라는 의사 파일을 검사할 수 있어요. PID 중 아무거나 선택해요.
모든 것을 종합하면, 컨테이너의 "짧은 ID"가 환경 변수 $CID에 있다면 다음을 할 수 있어요:
$ TASKS=/sys/fs/cgroup/devices/docker/$CID*/tasks
$ PID=$(head -n 1 $TASKS)
$ mkdir -p /var/run/netns
$ ln -sf /proc/$PID/ns/net /var/run/netns/$CID
$ ip netns exec $CID netstat -i
고성능 메트릭 수집을 위한 팁 (Tips for high-performance metric collection)
메트릭을 업데이트할 때마다 새 프로세스를 실행하는 것은 (상대적으로) 비싸요. 높은 해상도로, 그리고/또는 많은 수의 컨테이너(단일 호스트의 1000개 컨테이너를 생각해 보세요)에 대해 메트릭을 수집하려면 매번 새 프로세스를 포크하고 싶지 않을 거예요.
단일 프로세스에서 메트릭을 수집하는 방법은 다음과 같아요. 메트릭 수집기를 C(또는 저수준 시스템 호출을 할 수 있는 어떤 언어)로 작성해야 해요. 현재 프로세스가 임의의 네임스페이스에 들어갈 수 있게 하는 특수 시스템 호출 setns()를 사용해야 해요. 다만 네임스페이스 의사 파일에 대한 열린 파일 디스크립터가 필요해요(기억하세요: 그것은 /proc/<pid>/ns/net의 의사 파일이에요).
하지만 주의할 점이 있어요: 이 파일 디스크립터를 열어 두면 안 돼요. 그렇게 하면 제어 그룹의 마지막 프로세스가 종료되어도 네임스페이스가 파괴되지 않고, 그 네트워크 리소스(컨테이너의 가상 인터페이스 같은)가 계속 남아요(또는 그 파일 디스크립터를 닫을 때까지).
올바른 접근 방식은 각 컨테이너의 첫 PID를 추적하고 네임스페이스 의사 파일을 매번 다시 여는 것이에요.
컨테이너가 종료될 때 메트릭 수집 (Collect metrics when a container exits)
때로는 실시간 메트릭 수집에 신경 쓰지 않고, 컨테이너가 종료될 때 얼마나 많은 CPU, 메모리 등을 사용했는지 알고 싶을 수 있어요.
Docker는 lxc-start에 의존하는데, 이는 스스로 깨끗하게 정리하기 때문에 이를 어렵게 만들어요. 보통 정기적인 간격으로 메트릭을 수집하는 것이 더 쉬우며, 이것이 collectd LXC 플러그인이 동작하는 방식이에요.
하지만 컨테이너가 중지될 때 통계를 수집하고 싶다면 방법은 다음과 같아요:
각 컨테이너에 대해 수집 프로세스를 시작하고, 그 PID를 cgroup의 tasks 파일에 써서 모니터링하려는 제어 그룹으로 이동해요. 수집 프로세스는 주기적으로 tasks 파일을 다시 읽어 자신이 제어 그룹의 마지막 프로세스인지 확인해야 해요. (이전 섹션에서 설명한 대로 네트워크 통계도 수집하려면 프로세스를 적절한 네트워크 네임스페이스로도 이동해야 해요.)
컨테이너가 종료되면 lxc-start가 제어 그룹을 삭제하려 시도해요. 제어 그룹이 여전히 사용 중이므로 실패하지만, 괜찮아요. 이제 프로세스는 자신이 그룹에 남은 유일한 것임을 감지해야 해요. 지금이 필요한 모든 메트릭을 수집할 적절한 때예요!
마지막으로 프로세스는 스스로 루트 제어 그룹으로 이동하고 컨테이너 제어 그룹을 제거해야 해요. 제어 그룹을 제거하려면 그 디렉토리를 rmdir하면 돼요. 파일이 여전히 포함된 디렉토리를 rmdir하는 것은 직관에 반하지만, 이것이 의사 파일시스템이라는 점을 기억하세요. 그래서 보통 규칙이 적용되지 않아요. 정리가 끝나면 수집 프로세스는 안전하게 종료될 수 있어요.