한 국가를 위한 .ovpn 파일 마흔 개가 공급자로부터 도착하고, 계획은 뻔해 보인다: 무작위로 하나를 제자리에 복사한 다음 터널을 시작한다. 이것을 ExecStart 안의 파이프라인으로 쓰면, 그 계획은 부팅 시 아무것도 설명하지 못하는 상태 코드와 함께 죽는다. 유닛 파일은 스크립트가 아니며, 수리는 셸을 명시적으로 끌어들이거나 파일 선택 자체를 완전히 그만두는 것이다.

증상: 시작하자마자 죽는 유닛

상태는 서비스가 찰나에 실패했음을 보여 주고, 저널 줄은 어떤 셸도 받았을 리 없는 명령을 지목한다. 터널에 관해서는 아무것도 나타나지 않는다, 터널이 애초에 시작되지 않았기 때문이다.

저널에서 정확한 명령줄을 읽는 것이 이를 확인하는 가장 빠른 방법이다. systemd는 실행한 내용을 그대로 되풀이하므로, 문자열에는 파이프라인 문자가 연산자가 아니라 문자 그대로의 인수로 나타난다.

ExecStart가 파이프라인을 실행할 수 없는 이유

systemd는 명령줄을 스스로 나누고, 자체 인용과 변수 치환을 적용한 뒤 결과를 직접 실행한다. 파이프, 리다이렉션, 셸에 의한 글롭 확장, 명령 치환은 애초에 그 문법의 일부가 아니다. 이것은 누락이 아니라 설계 선택이다: 유닛은 무엇을 실행할지 선언적으로 말하고, 셸 로직은 유닛이 호출하는 스크립트에 속한다. 두 가지 수리 모두 여기서 나오며, 어느 쪽도 유닛에 더 많은 권한을 줄 필요가 없다.

이것은 오해를 부르는 종료 코드도 설명한다. 첫 번째 인수가 바이너리로 취급되므로, 잘못된 줄은 당신의 로직이 조금이라도 실행되기 전에 실패한다.

선택은 ExecStartPre에서 하라

직접적인 수리는 셸을 명시적으로 호출하는 준비 단계이며, 선택과 시작을 같은 유닛에 두어 한쪽의 실패가 다른 쪽을 혼자 돌게 두지 않는다.

[Service]
Type=notify
ExecStartPre=/bin/bash -c "ls /etc/openvpn/profiles/au* | shuf -n 1 | xargs -r -I{} cp -f {} /etc/openvpn/%i.conf"
ExecStart=/usr/sbin/openvpn --config /etc/openvpn/%i.conf

세 가지 세부 사항이 이것이 실제 기계와의 접촉을 견디는지 결정한다. 이름 붙이기가 첫 번째 함정이다: systemd는 유닛을 해석하는 동안, bash가 실행되기 훨씬 전에 %i를 치환하므로, 지정자는 작은따옴표 밖에 있어야 한다. xargs -r는 글롭이 아무것과도 일치하지 않을 때 cp가 인수 없이 실행되는 것을 막는다. 그리고 실패한 준비 단계는 유닛을 멈추게 하며, 그것이 올바른 결과다: 어제의 구성으로 시작하는 것이 시작하지 않는 것보다 나쁘다. 늘 같은 파일을 고르는 무작위 선택은 대개 운이 나쁜 것이 아니다. 복사한 파일이 패키지 업데이트로 덮어써지는지, 아니면 글롭이 낡은 항목 하나에만 일치하는지 확인하라.

아니면 OpenVPN이 엔드포인트를 고르게 하라

OpenVPN은 이미 하나의 프로필에 여러 엔드포인트를 담고 그중에서 무작위로 고를 수 있으며, 이는 셸과 복사와 쓰기 권한을 부팅 경로에서 완전히 없앤다.

client
remote-random
<connection>
remote au1.example.net 1194 udp
</connection>
<connection>
remote au2.example.net 1194 udp
</connection>

이 형태는 서버 선택을 공급자가 작성한 파일 안에 두므로, 파일 이름을 바꾸는 로테이션이 유닛을 깨뜨리지 않는다. 또한 후보 목록을 감사 가능하게 만드는데, 디렉터리에 대한 글롭은 결코 그렇지 않다. 프로필이 엔드포인트보다 더 많은 점에서 다를 때 섞기는 여전히 제 몫을 한다: 다른 인증서, 다른 라우팅 지시문, 다른 암호 정책. 그 경우 선택은 구성 전체 사이의 선택이 되고, 셸 단계가 그것을 정직하게 표현하는 방법이다.

사용된 인스턴스와 파일을 다시 읽어 보라

그 내용을 디버그하기 전에 어느 유닛이 실제로 도는지 확인하라. 패키지된 메타 서비스와 템플릿은 인프라이며, 당신의 파일에 대응하는 유닛은 그 이름을 딴 인스턴스들이다.

systemctl list-units 'openvpn*'
systemctl cat [email protected]
journalctl -u openvpn@au -n 30 --no-pager

누구도 활성화한 기억이 없는 인스턴스는 대개 패키지 디렉터리에 떠도는 구성 파일로 이어진다. 그렇지 않으면 다음 패키지 업그레이드가 그것을 되살리므로, 템플릿이 아니라 그 이름의 인스턴스를 끄라. 변경 후 유닛을 시작하고 저널이 보고하는 명령줄을 읽은 다음, 실제로 열린 구성 경로를 확인하라. 그 한 번의 확인이 작동하는 무작위 선택과 지난달의 파일을 조용히 재사용하는 서비스를 가른다.

관련 글