OpenVPN 시작 실패는 흔히 자원에 대한 불평과 한도를 올리라는 선의의 제안이 함께 오는데, 그 제안은 대략 절반의 경우에만 통합니다. 그 실패 뒤에는 서로 무관한 두 개의 카운터가 있습니다. 하나는 실제 사용자 ID가 소유한 작업 수에 대한 커널 한도이며, 프로세스가 실행될 때 상속됩니다. 다른 하나는 서비스 관리자가 유닛과 그것이 포크하는 모든 것에 적용하는 cgroup 예산입니다. 실제로 실행되는 유닛에 대해 각각의 실효 값을 읽는 편이 추측보다 비용이 적게 듭니다.

두 카운터, 두 질문

LimitNPROC는 systemd 이름 아래의 RLIMIT_NPROC이며, 그 계산 단위는 실제 사용자 ID입니다. 그 사용자가 소유한 모든 프로세스가 같은 총계에 포함되며, 다른 유닛이나 로그인 세션, 스케줄러 작업이 시작한 일도 포함됩니다. 따라서 이 한도는 서비스 유닛 안에 설정되어 있더라도 서비스별 상한이 아니며, 클라이언트마다 헬퍼를 포크하는 서비스는 그 계정으로 실행되는 다른 모든 것과 경쟁합니다. 같은 사용자 아래의 바쁜 cron 작업이 VPN 유닛을 혼자서는 결코 넘지 않았던 선 너머로 밀어낼 수 있습니다.

TasksMax에는 다른 주인이 있습니다. 유닛의 cgroup에 있는 pids 컨트롤러이며, 프로세스가 아니라 작업을 셉니다. 스레드도 작업이므로, 단일 스레드처럼 보이지만 작업자 풀을 가진 데몬은 얼핏 프로세스 수보다 훨씬 큰 예산을 소비할 수 있습니다. EffectiveTasksMax는 상위 슬라이스가 할 말을 한 뒤 cgroup 계층이 실제로 허용하는 것을 보고합니다.

root 예외가 답을 바꾸는 이유

커널은 CAP_SYS_RESOURCE나 CAP_SYS_ADMIN를 가진 프로세스에 RLIMIT_NPROC를 적용하지 않으므로, 권한을 내려놓지 않고 root로 실행되는 유닛은 그 설정으로 사실상 무제한입니다. 이 단 하나의 예외가 사람들이 주고받는 상반된 조언을 설명합니다: LimitNPROC를 올리면 권한을 내려놓은 배포는 고쳐지고, 권한을 유지한 배포에는 아무 효과도 없습니다. 그 설정에 공을 돌리거나 책임을 물기 전에 User=와 데몬이 시작 후 신원을 바꾸는지 확인하세요. 권한을 가진 유닛이 나중에 권한 없는 계정으로 전환하면 전환되는 순간 다시 한도 아래로 내려가므로, 실패의 시간선도 읽을 가치가 있습니다.

실패 단계는 범위를 더 좁힙니다. 실행 준비 중의 거부, 데몬 스스로 보고하는 포크 오류, cgroup 안에서 거부된 작업 생성은 각각 다른 journal 줄을 만들고, 그중 하나만 더 큰 예산으로 해결됩니다.

셸의 값이 아니라 실효 값을 읽으세요

대화형 셸의 ulimit -u는 그 셸을 설명합니다. 서비스 관리자가 데몬에 무엇을 넘겼는지는 설명하지 않고, cgroup 계산에 대해서도 아무 말도 하지 않습니다. 대신 유닛에 질의하세요. 안내서의 예가 아니라 호스트에 실제로 있는 인스턴스 이름을 사용합니다.

systemctl show [email protected] -p User -p LimitNPROC -p TasksCurrent -p TasksMax -p EffectiveTasksMax
cat /proc/$(systemctl show -p MainPID --value [email protected])/limits | grep -i processes

컨테이너 안에서는 infinity라는 내부 값이 게스트의 시야만 설명하기 때문에 두 번의 읽기가 더 필요합니다. 호스트 소유자에게 컨테이너 자체의 pids 상한을 묻고, 내부 서비스 관리자가 그 컨트롤러를 위임할 수 있는지 확인하세요. 중첩된 유닛은 상위 cgroup이 준 적 없는 예산을 가질 수 없습니다.

수정 규모를 산정하고 부하 아래에서 증명하세요

숫자는 오류 문구가 아니라 워크로드에서 고르세요. 데몬이 연결된 클라이언트마다 또는 헬퍼 호출마다 시작하는 프로세스를 세고, 배포가 견뎌야 할 동시성으로 곱하고, 관리 세션을 위한 여유를 더한 다음, 그 수를 두 실효 값과 비교하세요. 로컬 drop-in 재정의는 변경을 검토 가능하게 유지하고 패키지 업그레이드에서도 살아남습니다. 반면 함께 배포된 유닛 파일을 편집하면 다음 업데이트가 그것을 교체할 때까지 변경이 숨는다. 재정의 옆에 계산을 적어 두세요. 다음에 그 값을 올리거나 내릴 사람에게 필요한 것은 최종 숫자가 아니라 가정이기 때문입니다.

예산 변경은 실행 시 적용되므로 다시 불러오기만 해서는 이미 실행 중인 프로세스의 크기가 바뀌지 않습니다. 정상 수명 주기로 유닛을 다시 시작하고 세션 중단을 받아들인 뒤 새 인스턴스에서 카운터를 다시 읽으세요.

sudo systemctl daemon-reload
sudo systemctl restart [email protected]
systemctl show [email protected] -p TasksCurrent -p EffectiveTasksMax
sudo journalctl -u [email protected] -b --no-pager | tail -n 20

수정을 증명하는 것은 의도한 부하를 지면서 작업 수가 두 상한 아래에 머무는 재시작된 유닛과, 같은 단계에서 더 이상 작업이나 포크 고갈을 보고하지 않는 journal입니다. 변경과 함께 전후 숫자를 남기세요. 다음 조사가 같은 조언이 아니라 측정에서 시작됩니다. 그래도 수가 상한에 닿는다면 한도가 우연히 작았던 것이 아니라 계산이 틀린 것이며, 그 차이는 다음 재시작 전에 알아 둘 가치가 있습니다.

관련 글