본문 바로가기

Linux/systemd

systemd로 시스템 자원 제한하기

앞으로 티스토리 대신 blog.stackframe.dev에서 블로깅을 합니다. 이 블로그는 남겨 둘 예정입니다.

다수의 사람들이 공동으로 사용하는 시스템이나 여러 프로세스들이 돌아가는 서버라면 특정 사용자나 프로세스가 자원을 독점하여 다른 작업에 지장이 생기는 문제에 대해 상당히 민감할겁니다. 하지만 현재 인터넷에 올라와있는 해결방법 중 하나인 cpulimit은 자동 실행 설정하기도 불편하고 단지 특정 프로세스에 대해 CPU 만 제한할 수 있다는 겁니다.


하지만 우리의 멋진 PID 1인 systemd는 다릅니다. CPU와 memory 뿐만 아니라 프로세스 수, IO 마저 제한이 가능합니다. 거기다 systemd는 계층적으로 서비스들을 관리하고 있으므로 특정 사용자나 컨테이너, 서비스 하위의 모든 프로세스에 대해 제한이 가능합니다.


다만 이 기능을 사용하기 위해서는 두가지 필요 요건이 있습니다.

먼저 systemd v240 이후의 버전이어야 합니다. 이전 버전까지는 버그가 있어서 사용자 유닛에는 제한이 제대로 작동하지 않았습니다만 v240에서 고쳐졌습니다. v240은 약 2018년 12월 후반 즈음에 나왔고, 아치리눅스 레포지토리에는 2019년 1월 8일에 업데이트되었으므로 아치리눅스가 아닌 다른 배포판에서는 대부분 옛날버전이 사용되고 있을겁니다.

그리고 CGroup v2를 사용하도록 설정해야합니다. 설정을 하기 위해서는 부팅 때 커널 옵션으로 systemd.unified_cgroup_hierarchy=1 을 넣어줘야 합니다. systemd가 기본적으로 사용하는 CGroup 버전이 v1이지만 자원 제한을 제대로 설정하기 위해서는 v2를 사용해야 합니다. systemd의 v241 milestone에 CGroup v2를 기본으로 설정하는 것이 올라왔으니 v241 부터는 커널 옵션이 필요없을 것으로 보입니다.


systemd에서 리소스를 제한하기 위한 옵션으로 자주 사용하는 것은 아래와 같습니다.


CPUQuota: CPU 할당비율 (%)

MemoryHigh: 메모리 제한 크기

MemoryMax: 메모리 최대 사용 크기

TasksMax: 최대 프로세스 수


더 많은 옵션을 보려면 systemd.resource-control(5) man 페이지를 확인하시기 바랍니다.


먼저 CPUQuota는 CPU의 얼마만큼을 할당할 지에 관한 % 값을 받는 옵션입니다. 100%가 CPU의 한 코어를 전부 사용할 수 있다는 뜻이므로 CPU 코어를 최대 2개까지만 제한하고 싶다면 200%로 값을 주면 됩니다. 

MemoryHigh는 메모리가 제한 될 크기입니다. 여기에 설정된 메모리까지 사용하게되면 시스템은 공격적으로 메모리를 회수하려하고 프로세스는 느려집니다. 다만 어쩔 수 없을 경우에는 이 값을 넘어서 메모리를 할당하기도 합니다.

MemoryMax는 메모리 최대 사용 크기입니다. 이 값을 넘게된다면 OOM killer가 작동하여 가장 쓸모없어 보이는 프로세스를 죽여버립니다.

TasksMax는 최대 프로세스 수입니다. 여기에 설정된 프로세스 수를 넘어서 프로세스가 생성되지 않습니다.


설정하기에 앞서서 systemd-cgls 란 명령을 소개하고자 합니다.

이 명령을 실행하면 위와같이 현재 실행되고 있는 서비스들과 각 계층을 확인할 수 있습니다. -.slice로부터 모든 프로세스가 시작하여 slice나 scope, service로 가지가 뻗어나가는 것을 볼 수 있습니다. 여기서 .slice, .scope, .service 에 리소스 제한을 걸 수 있습니다.


일시적으로 리소스 제한을 걸기 위해서는 systemctl 에 --runtime 옵션을 함께 주는 것으로 할 수 있습니다. 이렇게 설정되면 리부팅 시 초기 설정으로 돌아옵니다.


저는 현재 작동중인 모든 컨테이너의 상위 계층인 machine.slice 에 memoryMax와 memoryHigh 옵션을 설정해 보겠습니다.

systemctl status로 보면 아직 메모리 제한이 걸려있지 않습니다.

메모리를 제한하기 위해서는 systemctl 의 set-property 명령을 사용합니다.

# systemctl --runtime set-property machine.slice MemoryHigh=3G MemoryMax=4G

이 명령을 실행하고 systemctl status로 보면 메모리 제한이 설정된 것을 볼 수 있습니다.

모두 원래 설정으로 되돌리고 싶다면 systemctl의 revert 명령을 사용하면 됩니다.

# systemctl revert machine.slice


영구적으로 자원을 제한하고 싶다면 --runtime 옵션 없이 설정하면 됩니다.


만약 특정 사용자의 자원 사용을 제한하고 싶다면 위의 systemd-cgls 에서 보이듯이 user-UID.slice 에 자원 제한을 걸면 됩니다. 다만 user-.slice는 특수한 slice이므로 set-property를 사용하지 못합니다. 그러므로 edit라는 명령을 사용하여 설정을 해야합니다.

# systemctl --runtime --force edit user-.slice

이 명령을 실행하면 빈 텍스트 에디터가 나올겁니다. 기본적으로는 nano가 실행되었던걸로 알고있습니다. nano가 없다면 vim을 실행하더군요. 여기서 아래와 같이 입력하고 저장합니다.

[Slice]

MemoryMax=4G

이제 각 사용자의 slice를 확인하면 4G로 메모리 제한이 걸린 것을 확인할 수 있습니다.

물론 이것도 systemctl revert user-.slice 로 원상 복구 가능합니다.


저는 간단히 메모리 제한만 보여드렸지만 CPU나 IO도 같은 방법으로 설정이 가능합니다. systemd.resource-control(5) man 페이지를 참고하여 옵션에 필요한 값이 어떤 형태로 주어져야 하는지 알아내서 위의 방법대로 설정하시면 됩니다.


만약 서버에 계정을 만들어서 다른 사용자에게 준다던가 컨테이너를 통해 서비스나 가상서버를 제공한다면 이 방법으로 간단히 자원을 관리할 수 있을겁니다.