라즈베리파이로 산업용 디바이스 만들 때 WiFi + 부팅 시간이 의외로 큰 이슈입니다. 둘 다 사용자가 직접 보고 느끼는 부분이라 첫인상을 좌우합니다.

문제 1 — WiFi 자격증명이 사라진다

NetworkManager 프로필 소실이 증상입니다. 처음 WiFi 비번 입력하면 잘 접속됩니다. 재부팅 후 → 연결 안 됩니다. 프로필이 사라졌거나 default 로 돌아가 있습니다.

WiFi 관리 페이지 — 스캔 / 접속 / 저장

원인은 다양합니다.

  • SD 카드 복제 시 NM 프로필이 빠짐
  • cloud-init 이 부팅 시 NM 설정 wipe
  • /etc/NetworkManager/system-connections 권한 이상
  • 양산 시 uniquify 스크립트가 cloned-mac-address 같은 필드 건드리다 사고

NM 의 disk persistence 만 믿을 수 없습니다. 우리 쪽 백업이 필요합니다.

해결: wifi-saved.json 자체 저장소

// Software/System/WifiCredentialStore.cs
public sealed class WifiCredentialStore
{
    private readonly string _path;  // {ContentRoot}/wifi-saved.json

    public sealed record SavedNetwork(string Ssid, string? Password, DateTime SavedAt);

    public Store Save(string ssid, string? password)
    {
        var store = Load();
        store.Networks.RemoveAll(n => n.Ssid == ssid);
        store.Networks.Add(new SavedNetwork(ssid, password, DateTime.UtcNow));
        File.WriteAllText(_path, JsonSerializer.Serialize(store, JsonOpts));
        File.SetUnixFileMode(_path, UnixFileMode.UserRead | UnixFileMode.UserWrite);
        return store;
    }
}

WifiService 가 nmcli connect 성공 시 이 store 에도 자동 저장합니다. 0600 권한이라 평문이지만 owner 만 읽습니다 (이미 root 접근하면 어차피 끝).

부팅 시 복원:

// On service startup (Program.cs)
_ = Task.Run(async () =>
{
    await Task.Delay(3000);  // let systemd + NM settle
    var wifi = app.Services.GetRequiredService<WifiService>();
    await wifi.RestoreSavedAsync();
});

// WifiService.RestoreSavedAsync
public async Task RestoreSavedAsync(CancellationToken ct = default)
{
    var saved = _store.Load();
    var (ok, list) = await RunAsync("nmcli", "-t -f NAME connection show");
    var existing = new HashSet<string>(/* parse list */);

    foreach (var n in saved.Networks)
    {
        if (existing.Contains(n.Ssid)) continue;  // NM already has it
        _log.LogInformation("WiFi restore: '{Ssid}' missing — re-registering", n.Ssid);
        await RunAsync("nmcli", $"device wifi connect \"{n.Ssid}\" password \"{n.Password}\"");
    }
}

부팅 3초 후 백그라운드로 실행 → HTTP 서버 안 막습니다. NM 에 프로필 이미 있으면 skip 합니다 (오버헤드 0). 사라졌으면 우리 백업으로 자동 재등록합니다.

이미지에 깔린 후 처음 부팅하든, SD 카드 복제 후든, NM 캐시 깨졌든 — 사용자는 그냥 평소처럼 동작하는 것처럼 보입니다.

문제 2 — 부팅이 1분 31초

부팅 시간 1분 31초의 범인은 systemd-analyze blame 으로 바로 나왔습니다:

1min 190ms NetworkManager-wait-online.service       ← 1분
    8.735s zpi-controller.service
    6.347s fstrim.service
    4.912s NetworkManager.service
    ...

NetworkManager-wait-online.service 가 60초 timeout 까지 기다리다 실패하고 다음 부팅 절차로 진행합니다.

왜? WiFi 가 끊긴 상태로 부팅 → NM 이 autoconnect 시도 → 다 실패 → “online” 도달 못 함 → wait-online 이 60초 max 기다림 → 결국 포기합니다.

network-online.target 을 기다리는 서비스가 있으면 다 같이 60초 멈춥니다. zpi-controller.serviceAfter=network-online.target 이었으니 적어도 60초 후에야 시작됩니다.

해결 — wait-online 의존 제거 두 갈래

1. 우리 서비스가 wait-online 안 기다리게

# /etc/systemd/system/zpi-controller.service
[Unit]
Description=ZPi Controller Service
# 변경: network-online.target → network.target
After=network.target NetworkManager.service
Wants=network.target NetworkManager.service

기본 network 만 기다립니다 (네트워크 초기화 완료). 실제 인터넷 도달까지는 안 기다립니다. 우리 서비스는 백그라운드로 WifiService.RestoreSavedAsync() 실행해서 알아서 살아남습니다.

2. wait-online 서비스 자체 mask

sudo systemctl disable NetworkManager-wait-online.service
sudo systemctl mask NetworkManager-wait-online.service

/dev/null 심볼릭링크로 만들어서 systemd 가 영영 시작 못 합니다. 다른 서비스가 Wants=network-online.target 으로 우회 의존할 수 있는데, 그 경우에도 같은 효과입니다 (그냥 즉시 도달).

결과

Before: 1min 31.88s (kernel 5.7s + userspace 1min 26s)
After : 33.39s     (kernel 5.5s + userspace 27.9s)

60초 단축입니다. graphical.target 도달 27초로 정상화됐습니다.

systemd-analyze blame 도 정상:

8.293s zpi-controller.service       ← 이제 가장 느린 것
4.938s NetworkManager.service
4.068s cloud-init-main.service
2.095s dev-mmcblk0p2.device
1.757s accounts-daemon.service

문제 3 — 호스트명 변경 시 mDNS 가 새 이름 광고 안 함

avahi 호스트명 광고4편 (세 가지 함정) 에서 다룬 내용입니다. avahi-daemon 의 reload 가 호스트명 변경을 picked up 안 합니다. restart 로 변경 후 자체검증 추가했습니다.

합쳐서 — “운영자가 보는 첫인상”

운영자가 보는 첫인상은 이 세 가지로 정리됩니다:

  • 첫 부팅 33초 (예전 1분 31초)
  • WiFi 한 번 연결하면 다음에도 자동 (NM 캐시 깨져도)
  • 호스트명 바꾸면 즉시 <new>.local 로 접속 가능

체감 차이가 큽니다. 재부팅이 빠르고 / 네트워크가 항상 살아있고 / 이름 바꾸는 게 즉시 반영 되는 디바이스는 신뢰감이 다릅니다.

보너스 — 양산 시 호스트명 자동 부여

양산 호스트명 자동 부여는 같은 SD 이미지로 보드 여러 대 만들 때 호스트명 충돌을 막습니다. 첫 부팅 시 자동 명명합니다:

# scripts/zpi-uniquify.sh - 양산 첫 부팅 트리거
IP=$(hostname -I | awk '{print $1}')
LAST_OCTET=$(echo "${IP:-0}" | awk -F'.' '{print $NF}')
NEW_HOST="going-zpi-${LAST_OCTET}"
hostnamectl set-hostname "$NEW_HOST"

IP 끝자리(LAST_OCTET)가 그대로 호스트명 꼬리가 됩니다. 예를 들어 192.168.0.x 대역의 보드들은 각자 going-zpi-<끝자리>.local 이 되어 LAN 에서 자연스럽게 구분되고, mDNS 충돌도 자동 회피됩니다.

라즈베리파이 산업 디바이스 설정 정리

라즈베리파이 + .NET 산업 디바이스를 만들 때 챙길 것들입니다.

  • WiFi 자체 백업 (NM 만 믿지 말 것)
  • wait-online 의존 제거 (또는 우회)
  • 호스트명 변경 helper 가 검증까지 책임
  • 양산 시 자동 명명

다 작은 변화지만 “디바이스가 잘 동작한다” 의 느낌은 이런 디테일에서 나옵니다.

부팅이 빠르고 네트워크가 항상 살아있는 디바이스는 이런 작은 디테일에서 신뢰감이 나옵니다.

ZPi Controller 시리즈 다음 편

문의