blog.stackframe.dev

네트워크 연결 완료 이후 서비스 실행하기

서버 장애 발생 시 자동으로 복구되도록 설정하는 과정에서 nginx가 제대로 실행되지 못하고 죽는 문제가 발생했다.

IPv6 bind()가 실패하여 nginx가 실행되지 못했다.

내 서버는 여러 IP가 할당되어 있고, 특정 IP로 접근 할 때만 웹 페이지가 표시되도록 설정 파일에 listen 할 IP가 포함되어 있는 상황이다. 그런데 IPv6 주소가 할당되기 전에 nginx가 실행되었기 때문에 bind() 과정에서 에러가 발생한 것이다.

리눅스에서는 할당되지 않은 주소로 bind하면 실패하는게 기본 동작이다. sysctl에서 net.ipv4.ip_nonlocal_bind, net.ipv6.ip_nonlocal_bind를 1로 설정한다면 실패하지 않지만 개인적으로 아름답지 못하다고 생각한다. 그러므로 systemd를 통해서 네트워크가 제대로 설정 완료된 이후에 서비스를 실행하도록 변경하고자 한다.

먼저 아래에 설명할 방법을 사용하기 위해서는 네트워크 인터페이스를 systemd-networkd가 관리하여야 한다. NetworkManager가 관리한다면 다른 부분이 조금 있기 때문에 여기서는 설명하지 않을 예정이다.

systemd는 network-online.target이란 target을 제공한다. 이 target이 실행된 이후는 네트워크에 연결된 것을 보장한다. 그리고 이 target의 의존성으로 사용될 systemd-networkd-wait-online.service가 있다. 이 서비스 유닛을 enable 해두면 network-online.target을 의존하는 유닛이 실행되려 하면 의존성에 의해 먼저 실행되어 systemd-networkd가 네트워크 설정을 완료할 때까지 기다리게 된다. 결국 네트워크가 연결될 때까지 서비스의 실행이 미뤄지게 된다:

# systemctl enable systemd-networkd-wait-online.service

기존 서비스의 의존성에 추가하려면 systemctl edit 명령을 사용한다. nginx.service를 예로 들면 아래와 같다:

# systemctl edit nginx.service

그리고 [Unit] 섹션에 아래와 같이 추가하여 의존성 설정과 network-online.taget 이후에 서비스가 실행되도록 한다.

[Unit]
Wants=network-online.target
After=network-online.target

Arch Linux의 기본 nginx.service 유닛 파일은 이미 After=network-online.target을 포함하고 있으므로 입력할 필요는 없었다. systemctl edit 명령을 사용하면 나오는 에디터에 기존 설정파일 내용이 표시되므로 잘 읽어보고 필요한 내용만 추가하면 된다.

network-online.target이 이미 After=에 포함되어 있다.

단순한 시스템에서는 이걸로 IP 주소를 할당받은 이후에 서비스가 실행되기 때문에 충분하다. 하지만 내 서버는 여전히 동일한 에러를 발생시켰다. 그 이유는 서버의 네트워크 구성과 systemd-networkd-wait-online.service의 기본 동작에 있었다.

먼저 systemd-networkd-wait-online.service의 기본 동작은 systemd-networkd에 의해 관리되는 모든 인터페이스들이 configured 상태가 되면서 적어도 하나의 인터페이스가 online이 될 때까지 기다리는 것이다. 그리고 내 서버의 IP 할당 방법은 다음과 같다.

  • 인터넷 인터페이스

    • 고정 IPv4
    • DHCP를 통한 IPv4
    • RA(Router Advertisement)를 통한 SLAAC IPv6
  • VPN 인터페이스

    • 고정 IPv4

문제는 SLAAC으로 할당받는 IPv6가 가장 늦게 설정되고 그 전에 DHCPv4를 통해 IPv4 주소를 할당받는다. 결국 인터페이스가 configuredonline 상태가 되어버려서 systemd-networkd-wait-online.service가 실행 완료되고 IPv6를 할당받지 못한 채 nginx.service가 실행되었기 때문이다.

인터페이스가 IPv6를 할당받을 때까지 online 상태가 되지 않도록 하려면 인터페이스 설정 유닛(.network)를 열어서 다음 내용을 추가한다:

[Link]
RequiredForOnline=routable
RequiredFamilyForOnline=both

RequiredForOnline=routable로 라우팅 가능한 주소가 설정되고 RequiredFamilyForOnline=both로 IPv4와 IPv6 모두 설정이 되어야 online 상태가 되도록 하였다.

여기까지만 설정하고 원하는대로 작동하면 좋겠지만 이번에도 systemd-networkd-wait-online.service는 IPv6가 할당되기 전에 종료되었다. 그 이유는 위에서 말했듯이 systemd-networkd-wait-online.service는 관리하는 모든 인터페이스 configured 상태가 되고 적어도 하나의 인터페이스가 online이 되면 종료하기 때문이다. 인터넷 인터페이스는 DHCPv4로 IPv4가 할당되면 configured 상태로 바뀌고 VPN 인터페이스는 고정 IPv4를 사용하기 때문에 금방 online 상태가 되어 systemd-networkd-wait-online.service는 종료된다.

systemd-networkd-wait-online.service가 기본 동작 대신 인터넷 인터페이스가 online 상태가 되는걸 기다리게 하려면 아래와 같이 수정한다:

# systemctl edit systemd-networkd-wait-online.service
[Service]
ExecStart=
ExecStart=/usr/lib/systemd/systemd-networkd-wait-online -i <인터페이스 이름>

맨 먼저 ExecStart=를 넣은 이유는 기존 명령을 없애기 위함이다. -i 옵션을 사용하여 인터페이스 이름을 전달하면 해당 인터페이스가 online 상태가 될 때까지 대기한다.

이제 리부팅이 되어도 IPv6까지 할당된 이후에 서비스가 실행된다.

댓글