← 전체 글
OPC UA/약 11분 읽기/— 조회

OPC UA 연결이 끊겼다 — 어느 Timeout이 죽였나?

OPC UA에서는 secure channel, session, subscription이 각자의 시계로 만료된다. 어느 쪽이 죽었는지 알면 firewall, server 부하, keepalive count 중 무엇을 봐야 하는지 정해진다.

OPC UASCADAHMI네트워킹문제 해결

HMI에는 "연결됨"이 떠 있다. 트렌드는 평평한 직선이다. Netstat에는 4840 포트로 ESTABLISHED socket이 보인다. 이 조합에서 많이들 헷갈린다. OPC UA에서 "포트가 열려 있다"와 "client가 fresh data를 받고 있다"는 완전히 다른 이야기이고, 대부분의 session 불만은 그 사이 틈에서 나온다.

OPC UA는 그 TCP socket 위에 서로 독립된 세 개의 lifetime을 쌓아 올린다. 각각 자기 시계로 만료된다.

  • Secure channel (OPC UA Part 4, §5.5). RevisedLifetime을 가지며, client는 token이 끝나기 전에 갱신해야 한다.
  • Session (Part 4, §5.6). RevisedSessionTimeout을 가진다. 그 시간 안에 server가 client의 service request를 하나도 못 받으면 죽는다.
  • Subscription (Part 4, §5.13). Lifetime을 초가 아니라 publishing interval 개수로 센다. 사람들이 가장 많이 오해하는 지점이다.

데이터가 멈추면 먼저 이 셋 중 무엇이 실제로 만료됐는지부터 가려야 한다. 원인마다 해법이 정반대 방향이기 때문이다. Secure channel이 죽었으면 시간 동기나 인증서 문제다. Session이 죽었으면 client가 말을 멈춘 것이다. Subscription이 죽었으면 publish request가 제때 못 온 것이다. "그냥 timeout 키워"는 이 셋을 구분하지 않고 덮어 버린다.

계층을 추측하지 말고 읽는다

Timeout 값을 만지기 전에 장애를 어느 계층에 놓는다. Client가 남긴 StatusCode가 보통 위치를 알려준다.

계층끊기는 지점대표 StatusCode / 증상
Network 경로Packet loss, 중복 IP, NAT, firewall idle timeout, DNS 변경TCP 재전송, 정상 close 없음
TCPConnect refused, RST, 재전송 지연BadConnectionClosed, BadNotConnected
Secure channel인증서 trust, security policy 불일치, token 갱신, 시간 차이BadSecureChannelIdInvalid, BadSecurityChecksFailed
SessionSession timeout, user token, server session limitBadSessionIdInvalid, BadSessionClosed, BadTooManySessions
SubscriptionPublishing interval, keepalive count, lifetime countBadSubscriptionIdInvalid, BadNoSubscription
Monitored itemSampling interval, quality, queue overflowBad/Uncertain quality, BadMonitoredItemIdInvalid

Firewall이 조용한 연결을 끊는 것과 subscription lifetime이 끝나는 것은 둘 다 "데이터 없음"으로 끝나지만, 조용한 구간 뒤의 BadSecureChannelIdInvalid와 부하 상황의 BadSubscriptionIdInvalid는 다른 장애다. StatusCode부터 확보한다. Packet capture보다 싸고, 대개 그걸로 충분하다.

사람들이 틀리는 subscription lifetime 계산

시운전 때 물리는 부분이 여기다. Subscription의 건강은 server가 돌려주는 세 개의 revised 값이 지배한다. 무엇을 요청하든 server가 clamp한다.

  • PublishingInterval — server가 data나 keepalive를 보낼 수 있는 주기.
  • MaxKeepAliveCount — data 변화가 없는 interval이 몇 번 지나야 server가 keepalive를 보내는지. Keepalive는 notification이 없는 빈 Publish 응답이다. 조용한 구간에 subscription이 살아 있음을 client가 아는 방법이다.
  • LifetimeCount — client의 Publish request가 하나도 없는 publishing interval이 몇 번 지나면 server가 subscription을 아예 삭제하는지.

Part 4(§5.13.2)는 명확하다. LifetimeCount는 최소 3 × MaxKeepAliveCount 여야 한다. 둘을 가깝게 — 예를 들어 lifetime 10, keepalive 8 — 잡으면 publish request 두 개가 늦게 도착하는 순간 한 번에 subscription 전체가 삭제된다. 그러면 client는 monitored item을 전부 다시 만들어야 하고, operator는 직선이 bad quality로 바뀌었다 돌아오는 걸 본다. 나는 lifetime을 keepalive의 3~4배로 잡고 server가 revise하게 둔다.

또 하나의 함정. 이 값들은 ms가 아니라 개수다. Publishing interval 1000 ms에서 keepalive count 10은 10초마다 keepalive다. Diagnostic 화면이면 괜찮다. 같은 template을 interlock이나 command feedback 화면에 붙이면, 연결된 것처럼 보이면서 최대 10초 묵은 상태를 보여주는 화면을 만든 것이다. 모든 tag에 같은 subscription template을 쓰지 않는다. 운전용 fast data, 느린 diagnostic, historian 수집은 그룹과 숫자를 나눈다.

Session keepalive는 별개 장치다

Subscription과 자주 헷갈리니 분리해 둔다. Session은 server가 client로부터 아무것도 — Read도 Publish도 — 못 들으면 RevisedSessionTimeout으로 죽는다. 대부분의 stack은 background keepalive로 이걸 막는다. 예를 들어 UA-.NET client는 기본 5000 ms의 KeepAliveInterval로 Server_ServerStatus_CurrentTime(NodeId i=2258)을 주기적으로 읽는다. 그 read가 timeout 나기 시작하면 client는 session이 실제로 만료되기 전에 keepalive event를 낸다. 그게 조기 경보이지 장애 자체는 아니다.

요청값도 중요하다. 많은 stack이 RequestedSessionTimeout을 60000 ms로 기본값을 잡고, server는 보통 하한 10초 상한 1시간 근처로 clamp한다. 항상 revised 값을 로그로 남긴다. Client가 10분을 요청했는데 server가 30초만 허가하는 건 흔하고 실제인 "랜덤" drop 원인이다. 요청값만 보면 끝없이 헛짚는다.

HMI에서는 똑같아 보이는 장애들

Firewall / VPN idle timeout. 조용한 연결이 NAT나 firewall table에서 지워진다. Client는 다음 publish가 timeout 날 때까지 session이 멀쩡하다고 믿는다 — 긴 정지 구간 뒤 reconnect가 몰린다. 단서: 끊기는 간격이 의심스럽게 딱 떨어진다. 30분, 60분, 300분처럼 정확한 주기면 그건 OPC UA가 아니라 장비 타이머다. Client를 만지기 전에 firewall idle 설정부터 확인한다.

Server session limit. 시운전 때 engineering tool, redundant SCADA, historian, test client가 다 붙는다. Server가 MaxSessionCount에 걸려 거절하거나 evict를 시작한다. Server_ServerDiagnostics를 읽는다 — CurrentSessionCount, CumulatedSessionCount, SecurityRejectedSessionCount. 흔한 범인은 failover 시험이 남긴, session timeout 전까지 정리되지 않는 버려진 session이다.

Subscription lifetime이 짧음. 위에서 다뤘다. 이게 부하 관련 원인이다. Subscription recreated event를 전부 남기고 CPU, packet loss, server diagnostic과 시간을 맞춘다. Recreate가 부하 스파이크와 겹치면 lifetime 대 keepalive 비율이 너무 빡빡한 것이다.

Secure channel token 갱신. 시간 차이, 만료 임박한 인증서, OpenSecureChannel 갱신을 제때 처리 못 할 만큼 바쁜 server에서 갱신이 실패한다. 랜덤 drop처럼 보인다. 단서: drop이 secure channel lifetime 경계 근처에 몰린다. 많은 stack이 RevisedLifetime의 약 75%에서 갱신하므로, 기본 3600000 ms channel이면 45분 부근에 갱신 트래픽이 뜬다. Drop이 거기 떨어지면 NTP와 인증서 유효기간을 본다.

재접속은 기본값이 아니라 설계다

실패한 endpoint를 최대 속도로 두드리는 client는 server 재시작 하나를 전체 plant의 connection storm으로 키운다. 출시할 만한 재접속 정책에는 이런 게 있다.

  • 짧은 network 흔들림을 위한 빠른 1차 retry, 그다음 반복 실패에는 진짜 backoff.
  • 재접속 중에는 quality를 bad나 uncertain으로 — 마지막 정상값을 살아 있는 것처럼 남겨 두지 않는다.
  • Platform이 지원하면 critical과 noncritical client의 재접속 그룹 분리.
  • Operator가 실제로 읽을 수 있는 연결 상태 — disconnected, reconnecting, connected-but-bad-subscription, connected-with-fresh-data를 구분한다. "연결됨" 하나가 우리가 시작한 그 거짓말이다.

Redundant server면 failover뿐 아니라 failback도 시험한다. Failback이 monitored item 수천 개를 한 번에 다시 만들면 장애 자체보다 더 아플 수 있다. 여기서 session transfer가 값을 한다 — backup으로 TransferSubscriptions를 할 수 있는 client는 subscription을 처음부터 다시 만들지 않고 넘겨받는다.

인수 전에 돌려 볼 단 하나의 시험

위의 전부는 한 번 직접 보기 전까지는 이론이다. 인수 전에:

  1. 정상 session, subscription, tag quality를 baseline으로 확인한다.
  2. Client network cable을 정해진 시간 뽑는다. 다시 연결하고 fresh data가 돌아오는 시간을 잰다.
  3. OPC UA server를 재시작하고 client 동작을 기록한다.
  4. 경로에 firewall이나 VPN이 있으면 타이머를 넘길 만큼 idle 구간을 만든다.
  5. Stale data와 통신 장애 alarm이 실제로 나오는지 확인한다.
  6. Historian gap이 조용히 보간되지 않고 gap으로 남는지 확인한다.

측정한 복구 시간은 인수 문서에 숫자로 남긴다. 새벽 2시에 진짜 장애가 터지면 maintenance engineer에게는 비교할 숫자가 필요하다. "지난 시험 때 7초쯤"이 재접속이 어떻게 동작해야 하는지에 대한 어떤 설명보다 낫다.

그리고 벽에 붙여 둘 규칙 하나. Timeout을 키운 게 고친 유일한 것이라면, 아직 원인을 못 찾은 것이다. Firewall reaper나 버려진 session leak, 너무 빡빡한 lifetime 대 keepalive 비율을 덮은 긴 timeout은 반드시 돌아온다. 대개 그걸 튜닝한 사람이 휴가 간 그 한 주에.