본문으로 건너뛰기

운영·확장·보안

📍 현재 위치: Part VI · 규모에 맞춘 운영 — 스택은 동작합니다. 이제 그것을 살아 있게 유지하고, 단단히 잠그고, 새벽 3시에 무언가 망가졌을 때 복구 가능하게 만들 차례입니다.

쉽게 말하면

주방을 짓는 것은 즐거운 일입니다. 위생 점검을 10년 동안 매일같이 통과하는 식당을 운영하는 것은 어려운 일입니다. 이 장은 둘째 날(day two)에 관한 이야기입니다. 디스크가 가득 차는 밤, 새 CVE가 떨어지는 아침, "지난 화요일 시점으로 복구할 수 있다는 걸 보여 달라"고 묻는 감사. 오픈소스는 당신에게 아름다운 주방을 지어 줍니다. 하지만 화재 진압 설비, 출입문 자물쇠, 점검관이 읽는 점검 일지 — 그런 것들은 당신이 직접 설치하고 유지해야 하고, 그중 몇 가지는 결국 돈을 주고 사게 됩니다.

이 장에서 다루는 내용

우리는 스물네 개 장에 걸쳐 플랫폼이 어떤 일을 하게 만들어 왔습니다. 이 장은 그 플랫폼이 어떤 일을 견디게 만듭니다. 오픈소스가 정직하게 시험받는 네 가지 둘째 날의 현실을 차례로 살펴봅니다.

  • 고가용성(high availability, HA): "그냥 클러스터링하면 되잖아"가 들리는 것보다 왜 더 어려운지, 그리고 무료 MQTT 브로커 클러스터링(여러 대의 메시지 중계 서버를 하나의 내결함성 그룹으로 돌리는 것)이라는 이야기가 2025년에 어떻게 쪼그라들었는지.
  • 백업과 시점 복구(point-in-time recovery, PITR): 규제 당국이 반드시 시연을 요구할 단 하나의 것, 그리고 그것을 PostgreSQL에서 일상으로 만들어 주는 도구.
  • 네트워크 분리(network segmentation), TLS, 인증서, 시크릿: 플랜트 현장과 데이터 스택을 별개의 방어된 구역으로 유지하기.
  • CVE 대응과 자가 관측성(self-observability): 패치 주기, 어느 가벼운 브로커에 관한 교훈적 사례, 그리고 VictoriaMetrics(스택이 자기 자신을 모니터링하는 데 쓰는 오픈소스 메트릭 데이터베이스)로 감시자를 감시하기.

이 모든 과정에서 cGMP(current Good Manufacturing Practice — 의약품 제조를 구속하는 품질 규제)와 EU GMP 부속서 11(Annex 11)이 기준선을 정하며, 순수 오픈소스가 그 기준선에 못 미치는 지점들을 우리는 정직하게 밝힙니다.

둘째 날이 진짜 시험이다

이 장 이전의 모든 것은 행복한 경로(happy path)를 가정했습니다. 서비스가 시작되고, 데이터가 흐르고, 대시보드가 그려집니다. 둘째 날 운영(day-two operations)은 불행한 경로를 가정하는, 화려하지 않은 규율입니다. 배치 도중 노드 하나가 죽습니다. 일요일에 인증서가 조용히 만료됩니다. 들어 본 적도 없는 의존성(dependency)에 치명적 권고가 뜹니다. 규제 당국은 데모로 당신을 평가하지 않습니다. 그들은 복구로 당신을 평가합니다.

이것은 대부분의 분야보다 우리 세계에서 더 중요합니다. EU GMP 부속서 11은 가용성, 백업, 사업 연속성을 명시적인 의무로 만듭니다. 7절(§7)은 데이터의 정기적이고 검증된 백업과 복구된 데이터가 정확한지에 대한 점검을 요구하고, 16절(§16)은 중요 시스템 장애가 의약품을 만들거나 출하하는 일을 멈추게 하지 않도록 문서화된 사업 연속성 계획을 요구합니다 [1]. 당신은 회복력(resilience)을 있으면 좋은 것 정도로 취급할 수 없습니다. 그것은 점검관이 당신에게 그대로 되읽어 주는 항목입니다.

고가용성, 그리고 쪼그라드는 무료 클러스터링 이야기

HA란 이런 뜻입니다. 구성 요소 하나가 고장 나도 시스템이 계속 서비스를 제공합니다. 데이터베이스에서는 인계받을 준비가 된 복제본(replica)이고, 메시지 브로커에서는 부하와 상태를 나눠 갖는 브로커 클러스터입니다 — 여기서 상태란 어떤 클라이언트가 연결되어 있고 무엇을 구독했는지에 대한 실시간 기록으로, 그래야 클러스터 안의 어느 브로커든 어느 클라이언트든 서비스할 수 있습니다.

여기 정직한 부분이 있습니다. 우리가 코어 스택에 싣는 오픈소스 브로커인 Eclipse Mosquitto — MQTT 브로커, 즉 플랜트의 가벼운 발행/구독(publish/subscribe) 센서 메시지를 받아 장치들과 우리 데이터 스택 사이에서 중계하는 서버 — 는 의도적인 단일 브로커(single-broker) 설계입니다. 작고, 빠르고, 바위처럼 단단하며, 우리 compose 파일(compose.yaml, 스택이 돌리는 모든 컨테이너를 선언하는 단 하나의 YAML 파일)에서 안정 버전인 eclipse-mosquitto:2.0.22 라인에 고정(pin, 정확히 한 버전에 잠가 두어 업데이트가 조용히 그것을 바꿀 수 없게 하는 것)되어 있습니다. 그것이 아닌 것은 클러스터링입니다. 여러 브로커를 가로지르는 문제에 대한 Mosquitto의 답은 브리지(bridge) 기능인데, 독립된 브로커들을 연결하고 선택한 토픽(topic)을 그들 사이에서 전달합니다. 여러 사이트를 연합하는 데는 유용하지만, 공유 세션 상태와 자동 장애 조치(failover)를 갖춘 네이티브 클러스터링은 아닙니다 [2]. 그 하나뿐인 Mosquitto 프로세스가 죽으면, 클라이언트들은 그것이 돌아올 때까지 재접속할 곳이 없습니다.

명백한 업그레이드는 한때 EMQX였습니다. 무료 다중 노드 클러스터링을 제공했기 때문입니다. 2025년에 그 문이 좁아졌습니다. EMQX 5.9부터 프로젝트는 Apache 2.0(관대한 라이선스 — 규모 제한 없이 자유롭게 사용·수정하고 운영에서 돌릴 수 있음)에서 Business Source License(BSL 1.1)로 옮겨 갔고, 이제 운영용 다중 노드 클러스터를 돌리려면 상용 라이선스가 필요합니다 [3]. 이것은 2026년에 반복되는 라이선스 함정의 축소판입니다. HA를 위해 당신이 가장 원하는 바로 그 기능이 더 이상 무료가 아닌 그 기능입니다. 그래서 이 장은 단도직입적입니다. 규제받는 단일 사이트 mAb(monoclonal antibody, 단일클론 항체 — 배양된 세포에서 키우는 단백질 의약품으로, 이 책 전체의 실행 예제) 라인에서는, 잘 모니터링되는 단일 Mosquitto에 빠른 재시작과 문서화된 장애 조치 SOP를 더한 것이 방어 가능하고 정직한 자세입니다. 규모에 맞는 진정한 브로커 HA는 EMQX에 돈을 내거나, HiveMQ를 돌리거나, 하이브리드 상용 구성 요소를 받아들이는 지점입니다. Mosquitto 브리지를 클러스터인 척하지 마세요.

데이터베이스 쪽은 더 우호적입니다. 우리 postgres 서비스는 timescale/timescaledb:2.17.2-pg17입니다. TimescaleDB 확장(extension)을 얹은 PostgreSQL이라서, 히스토리안 하이퍼테이블(hypertable, 고속 센서 데이터를 위한 시계열 저장소 — 히스토리안 장 참조)과 ISA-88/95 배치 모델(배치·장비·레시피의 표준 관계형 모델 — 배치 및 장비 모델 장 참조)이 조인 가능한(joinable) 하나의 데이터베이스 안에 살고, 이는 examples/platform/compose/compose.yaml에서 단 한 번 정의됩니다.

# examples/platform/compose/compose.yaml
services:
# --- core --------------------------------------------------------------
postgres:
# timescale/timescaledb IS PostgreSQL + TimescaleDB, so the historian
# hypertable and the ISA-88/95 batch model live in one joinable database.
image: timescale/timescaledb:2.17.2-pg17
profiles: ["core"]
<<: *restart
environment:
POSTGRES_USER: ${POSTGRES_USER:-bioproc}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-bioproc}
POSTGRES_DB: ${POSTGRES_DB:-bioproc}
ports: ["5432:5432"]
volumes:
- pgdata:/var/lib/postgresql/data
- ../db:/docker-entrypoint-initdb.d:ro # 00-60 schema files run on first init
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-bioproc} -d ${POSTGRES_DB:-bioproc}"]
interval: 5s
timeout: 5s
retries: 20

그 블록에 새겨진 세 가지 운영 습관에 주목하세요. 이미지는 태그로 고정되어 있어서, 조용한 마이너 버전 상승이 인덱스를 손상시키는 Debian/glibc 점프를 몰래 들여올 수 없습니다. 그리고 일치하는 매니페스트 다이제스트(manifest digest)는 examples/platform/versions.lock에 기록되어 있습니다(오늘 커밋되어 있고 make lock으로 재생성됩니다). 공급자 등록부에 대한 본격적인 다룸은 검증 장에서 이어집니다. 데이터는 컨테이너의 임시(ephemeral) 계층이 아니라 이름 있는 볼륨(named volume)(pgdata)에 삽니다. 그래서 컨테이너는 버려도 되지만 데이터는 그렇지 않습니다. 그리고 실제 healthcheck가 있어서, 오케스트레이터 — 그리고 make up 폴러(poller) — 가 "프로세스가 시작됐다"와 "실제로 서비스할 준비가 됐다"의 차이를 압니다.

스트리밍 복제: 무료이고 운영 등급인 데이터베이스 HA

PostgreSQL은 또한 진짜 스트리밍 복제(streaming replication)를 줍니다. 프라이머리(primary)가 자신의 미리 쓰기 로그(write-ahead log — WAL, 모든 변경에 대한 PostgreSQL의 순서 있는 기록으로, 아래 PITR 절에서 자세히 다룹니다)를 하나 이상의 핫 스탠바이(hot standby — 연속적으로 최신 상태로 유지되며 인계받을 준비가 된 복제 데이터베이스)로 스트리밍하고, 이들은 몇 초 안에 승격(promote — 복제본에서 프라이머리로 전환)될 수 있습니다. 그것은 진짜이고, 무료이며, 운영 등급의 데이터베이스 HA로, 우리 스택 전체에서 가장 강력한 HA 이야기입니다. 그 메커니즘은 PITR를 떠받치는 바로 그 WAL 스트림입니다 — 스탠바이는 단지 아카이브에서가 아니라 프라이머리의 WAL을 연속적으로 재생할 뿐이므로, 하나의 규율(WAL을 보내고 재생한다)이 핫 장애 조치 대상 시점 되감기를 동시에 사 줍니다. TimescaleDB가 PostgreSQL이기 때문에 히스토리안 하이퍼테이블은 나머지 모든 것과 함께 복제되고, 고속 센서 데이터를 위해 따로 세울 클러스터가 없습니다. 비대칭이 바로 교훈입니다. 관계형 코어는 오픈에서 잘 클러스터링되지만, 브로커는 그렇지 않습니다.

Mosquitto 브리지는 클러스터가 아니다

이 실수가 흔하기 때문에 분명히 말해 둘 가치가 있습니다. Mosquitto의 브리지는 HA가 아닙니다. 브리지는 두 개의 독립된 브로커를 연결하고 선택한 토픽을 그들 사이에서 전달합니다. 각 브로커는 자신의 세션, 자신의 보존 메시지(retained message), 자신의 구독을 따로 갖습니다 [2]. 브로커 A가 죽으면, A에 연결되어 있던 클라이언트들은 조용히 B로 장애 조치되지 않습니다 — 그들은 B에 세션이 없고, B는 그들의 CONNECT(클라이언트가 브로커와 세션을 여는 핸드셰이크 패킷)를 받은 적이 없으며, 그들의 대기 중인 QoS 1/2 메시지 — 적어도-한-번(1) 및 정확히-한-번(2) 전달을 보장하는 MQTT 서비스 품질(quality-of-service) 레벨로(레벨 0은 발사 후 망각(fire-and-forget)이라 그런 보장이 없습니다), QoS 1과 2에서는 브로커가 확인 응답을 받을 때까지 메시지를 붙들고 있어야 합니다 — 는 이제 죽은 A의 영속(persistence) 파일 안에 살아 있습니다. 브리지는 각각 독립적으로 가용한 사이트들을 연합할 뿐, 둘로 하나의 논리적 브로커를 만들지 않습니다. 네이티브 클러스터링 — 공유 세션 상태, 어느 노드에 재접속해도 자신의 구독이 온전히 있는 클라이언트, 자동 장애 조치 — 은 바로 Mosquitto가 싣지 않고 EMQX가 2025년에 상용 라이선스 뒤로 옮긴 그 기능입니다. 그래서 정직한 단일 사이트 자세는 그대로입니다. 잘 모니터링되는 단일 Mosquitto 하나, 빠른 재시작, 문서화된 장애 조치 SOP, 그리고 그 한계를 클러스터처럼 보이지만 아닌 브리지 다이어그램으로 덮어 버리는 대신 글로 적어 두는 것입니다.

백업과 시점 복구

이 책에서 운영상의 의무 하나만 기억한다면, 바로 이것으로 하세요. 한 번도 복구해 본 적 없는 백업은 소문일 뿐입니다. PITR은 "우리가 데이터를 잃었다"를 "우리는 어제 14시 32분, 잘못된 마이그레이션(데이터베이스 스키마 변경) 직전으로 되감았다"로 바꿔 줍니다.

PostgreSQL의 PITR은 두 가지를 결합해서 작동합니다. 베이스 백업(base backup) (데이터 디렉터리의 전체 물리 복사본)과 연속적으로 보관된 WAL — 그 베이스 이후 모든 변경을 기록하는 미리 쓰기 로그 세그먼트의 흐름입니다. 복구하려면 베이스를 복원한 다음 당신이 고른 임의의 순간까지 WAL을 앞으로 재생(replay)합니다 [4]. 그 마지막 구절이 마법입니다. 당신은 마지막 야간 스냅샷에 묶이지 않습니다. 특정 트랜잭션이나 타임스탬프에 착지할 수 있습니다.

WAL 배관을 손으로 다루는 것은 번거롭기 때문에, 선택받는 오픈소스 도구는 pgBackRest(PostgreSQL License — 관대하고, 무료)입니다. 이것은 전체(full), 차등(differential), 증분(incremental) 백업을 관리하고, 호스트 외부(off-host)이며 암호화된 저장소를 지원해서 당신의 백업이 보호하려는 대상 바로 옆에 놓이지 않게 하며, 시간 또는 트랜잭션을 표적으로 한 PITR 복구를 수행합니다 [5]. 아래는 이 장이 권장하는 형태의 대표적인 ops/pgbackrest.conf입니다. 예시용 구성이며, 아직 저장소에서 실행 가능한 서비스는 아닙니다. 운영자가 무엇을 배선하는지 정확히 볼 수 있도록 여기 보입니다.

# ops/pgbackrest.conf (illustrative — the operator's day-two artifact)
[global]
repo1-path=/var/lib/pgbackrest
repo1-retention-full=4 # keep 4 weekly full backups
repo1-cipher-type=aes-256-cbc # encrypt the repository at rest
repo1-cipher-pass=<from-secrets-manager>
start-fast=y
process-max=2

[bioproc]
pg1-path=/var/lib/postgresql/data

주간 전체 백업에 일간 차등 백업을 더하고, 그 사이에 연속 WAL 보관을 두는 것이 합리적인 출발 주기입니다. 감사자가 신경 쓰는 지점은 구성이 아닙니다. 바로 복구 런북(restore runbook)입니다. 데이터베이스를 선택한 시점으로 재구축할 수 있고 복구된 데이터가 검증된다는 것을 입증하는, 글로 쓰이고 시험된 절차 말입니다. 부속서 11 §7은 단지 백업을 원하는 것이 아닙니다. 점검된 백업을 원합니다. 그리고 §16은 그것을 사용하는 연속성 계획을 원합니다 [1]. 분기마다 한 번씩 복구 훈련을 하고 그것을 기록으로 남기는 것이, "우리는 백업이 있다"를 증거로 바꾸는 방법입니다.

히스토리안도 똑같은 보살핌이 필요합니다. TimescaleDB가 PostgreSQL이기 때문에, 우리 ts 스키마의 하이퍼테이블은 바로 그 동일한 물리 백업과 WAL 스트림으로 보호됩니다 — 하나의 PITR 전략이 배치 모델과 고속 센서 데이터를 모두 지킵니다. 히스토리안은 하이퍼테이블과 drop_chunks(Apache-2.0)에 더해, 연속 집계(continuous aggregate)와 add_retention_policy 자동화에 기댑니다. 이들은 무료 TSL 커뮤니티(Community) 기능으로 — 소스 공개이고 돌리는 것은 무료이지만 OSI 오픈은 아닙니다(Open Source Initiative의 승인 라이선스 목록에 없다는 뜻으로, 이것이 이 책이 진정한 오픈소스에 적용하는 공식 기준입니다) — 그리고 우리는 TSL 컬럼스토어/압축 계층은 의도적으로 피하므로, 복구를 복잡하게 만들 독점 데이터 계층화(data-tiering) 레이어가 없습니다.

데이터베이스 변경 하나의 해부: 복구(또는 되돌리기)가 실제로 재생하는 것

PITR은 잘못된 변경 너머로 되감습니다. 그러나 잘못된 변경에 대한 더 값싼 첫째 방어선은, 애초에 모든 변경을 작고, 되돌릴 수 있으며, 스스로 검증하는 단위로 만드는 것입니다 — 그러면 "스키마를 망가뜨렸다"는 사건 대부분이 백업에 손을 뻗는 지점에까지 결코 이르지 않습니다. 저장소는 바로 그 산출물을 싣고 있습니다. examples/platform/db/migrations 아래의 Sqitch 마이그레이션입니다. PITR 이야기가 기대는 pgBackRest 매니페스트는 이 저장소에서 아직 예시 수준이므로, 해부할 정직한 산출물은 실제로 체크인되어 있고 실행 가능한 것 — 진짜 변경이고, 진짜 검증 게이트를 가지며, 이 장의 "패치는 변경이고, 변경은 재검증되어야 한다"는 규칙이 그것으로부터 지어진 — 그것입니다. 플랜 파일은 변경 recipe_param_no_overlap를 나열하고, 디스크 위에서 그것은 세 개의 SQL 스크립트 — deploy/, verify/, revert/ — 와 sqitch.plan 항목, 그리고 sqitch.conf 엔진 구성으로 되어 있습니다. 필드별로 읽으면, 마이그레이션은 히스토리안 측정값(reading)과 같은 정신의 값-더하기-출처(value-plus-provenance) 기록입니다. 단지 무엇이 변했는지가 아니라, 언제 계획되었고, 어떻게 스스로를 증명하며, 어떻게 되돌려지는지까지 담고 있습니다.

Sqitch 마이그레이션 하나를 해부한 신분증 카드: 변경 이름 recipe_param_no_overlap, planned-at UTC 타임스탬프, pg 엔진과 db:pg 타깃, 사람이 읽는 노트, divide-by-zero 자가 검증과 verify = true 자동 되돌리기 게이트를 보여 주는 강조된 녹색 verify 블록, 제약을 드롭하는 한 줄짜리 revert, 그리고 s88.recipe_parameter에 대한 deploy GiST 배제 제약을 보여 주는 보라색 패널. Sqitch 마이그레이션 하나를 필드별로 — 스키마 변경은 동작했기를 바라는 일방향 SQL 문이 아니라, 되돌릴 수 있고 스스로 검증하는 기록 단위입니다. 저자가 AI의 도움을 받아 직접 제작한 그림입니다.

세 개의 필드가 둘째 날의 무게를 짊어집니다. verify는 게이트입니다. sqitch.conf[deploy] 아래에 verify = true를 설정하므로, 엔진은 변경의 verify 스크립트를 deploy의 일부로서 실행하고, verify가 실패하면 같은 실행 안에서 그 변경을 되돌립니다. recipe_param_no_overlap의 verify 스크립트는 일부러 단도직입적입니다. pg_constraint에 대해 SELECT 1 / CASE WHEN count(*) = 1 THEN 1 ELSE 0 END를 수행하므로, 제약이 실제로 존재하지 않는 한 0으로 나누기(오류를 일으키며 deploy를 실패시킴)가 됩니다. revert는 한 줄로 된 가역성 규칙입니다. ALTER TABLE s88.recipe_parameter DROP CONSTRAINT recipe_parameter_no_overlap. 그리고 deploy는 앞으로 가는 SQL입니다 — 쉽게 말하면, 같은 레시피 파라미터의 두 버전이 동시에 "유효"하지 못하게 금지하는 규칙으로, 바로 이것이 연속된 버전 이력을 신뢰할 수 있게 만듭니다. PostgreSQL 용어로 그 규칙은 GiST 배제 제약(exclusion constraint, 새 행이 기존 행과 겹친다면 그것을 거부하는 제약으로, PostgreSQL의 GiST 인덱스 타입을 써서 겹침을 빠르게 검사함)으로, 같은 레시피 파라미터(recipe_id, name)의 두 버전이 시간상 겹치지 못하게 금지하고(tstzrange(valid_from, valid_to, '[)') WITH &&, 여기서 &&는 PostgreSQL의 범위 겹침 연산자로 — 같은 파라미터에 대해 유효 기간이 기존 행과 겹치는 새 행을 제약이 거부합니다), btree_gist가 텍스트 동등성 술어를 GiST 인덱스 안에서 가능하게 합니다. '[)' 반열린(half-open) 경계가 짐을 짊어집니다. 범위가 valid_from은 포함하되 valid_to는 배제하게 만들므로, 연이은 버전(앞 버전의 valid_to가 다음 버전의 valid_from과 같은 경우)이 겹치지 않고 맞닿고, 배제 제약이 연속된 버전 이력을 그대로 서 있게 둡니다. deploy에는 명시적 BEGIN/COMMIT이 없습니다. Sqitch가 PostgreSQL에서 각 변경을 자신의 트랜잭션으로 감싸기 때문이고, 바로 그 점이 자동 되돌리기를 깔끔하게 만듭니다.

마이그레이션 게이트: deploy, verify, 아니면 자동 되돌리기

그 셋이 하나의 게이트로 합쳐지고, 그 게이트가 반쯤 적용된 스키마 변경이 이 스택에서 조용히 살아남을 수 없는 이유입니다.

마이그레이션 게이트의 흐름: sqitch deploy가 앞으로 가는 SQL을 자신의 트랜잭션 안에서 실행한 다음 verify 스크립트를 실행한다. 녹색 verify-통과 분기에서는 변경이 커밋되어 sqitch 레지스트리에 기록되고, 분홍색 verify-실패 분기에서는 변경이 같은 실행 안에서 자동 되돌려지며, 두 분기 모두 알려진, 결코 반쯤 적용되지 않은 상태로 수렴한다.

sqitch deploy를 실행합니다. deploy 스크립트는 자신의 트랜잭션 안에서 돌아갑니다. verify 스크립트가 곧바로 그 뒤에 돌아가고, verify = true이므로 그 결과가 사람의 개입 없이 결과를 결정합니다. 통과하면 변경이 커밋되고 Sqitch 레지스트리에 기록됩니다. 실패하면 — 0으로 나누기가 발화하면 — 엔진은 같은 호출 안에서 revert/를 실행하고 데이터베이스는 시작했던 바로 그 자리로 되돌아갑니다. 어느 분기든 알려진 상태로 끝나며, 테이블이 반쯤 바뀌고 아무도 확신하지 못하는 위험한 중간은 결코 아닙니다. 이것은 복구 런북의 마이그레이션 시점 거울상입니다. 런북은 사후에 알려진 지점으로 되돌아갈 수 있음을 입증하고, verify 게이트는 애초에 그 지점을 떠난 적이 없음을 입증합니다. 모든 스키마 변경이 통제된 변경인 규제 시스템에서, "변경이 동작했음을 증명하지 못하면 스스로 되돌린다"는 "우리는 SQL을 돌렸고 오류가 안 났다"보다 훨씬 강한 자세입니다.

같은 보장, 한 계층 위로: 제약을 그래프 셰이프로

GiST 배제 제약은 하나의 PostgreSQL 테이블 안의 연속된 레시피 파라미터 이력을 지킵니다. 그러나 지식 그래프 장은 바로 그 플랜트 데이터를 RDF 트리플로 들어 올리는데, 그 안에서는 "같은 파라미터의 두 버전이 시간상 겹치지 않는다"는 동일한 규칙이 데이터베이스 제약이 아니라 SHACL 셰이프(shape)로 표현됩니다 — 그래프의 데이터가 반드시 따라야 하는 패턴을 선언하는 언어로, SQL이 행 단위로 강제하는 CHECK/배제 제약의 그래프 세계 대응물입니다. 둘은 중복이 아니라 보완 관계입니다. GiST 제약은 잘못된 행이 애초에 쓰이지 못하게 막고, SHACL 셰이프는 검증기(그리고 감사자)가 사후에 들어 올려진 그래프가 시스템을 가로질러 그 규칙을 여전히 지키는지 확인하게 해 줍니다 — 바로 Book 4의 출하 게이트가 짓는 닫힌 세계(closed-world)의 "필수 사실이 빠졌거나 중복되었는가?" 검사입니다. 이것이 마이그레이션을 단순한 diff가 아니라 출처(provenance) 기록으로 읽을 가치가 있는 이유이기도 합니다. 그것의 planned_at 타임스탬프, 계획자 신원, verify 결과는 W3C PROV-O 어휘가 변경을 귀속 가능하고 시각이 찍힌 것으로 만드는 데 쓰는 바로 그 prov:wasGeneratedBy / prov:generatedAtTime 사실들입니다 — ALCOA+ 아래에서 공정 기록을 신뢰할 수 있게 만드는 감사 추적의 데이터베이스 변경 판입니다. 온톨로지가 답하도록 지어지는 종류의 역량 질문(competency question)"어제 14시 32분에 서 있던 그대로 스키마와 데이터를 재구성하고, 어떤 변경이 언제 효력을 가졌는지 보여라" — 으로 진술하면, PITR 되감기와 Sqitch 레지스트리는 같은 감사된 답의 두 절반으로, derivedFrom과 계보 척추가 로트에 대해 수행하는 시간적 걷기를 스키마 버전에 대해 수행하는 것입니다. 이 페이지의 둘째 날 산출물은 시맨틱 계층 바깥에 있지 않습니다. 그것은 그 계층의 무결성 보장이 물리적으로 강제되는 자리입니다.

OT/IT 선 긋기: 분리, TLS, 그리고 시크릿

mAb 라인은 함께 꿰매진 두 세계입니다. 운영 기술(operational technology, OT) 쪽에는 PLC(programmable logic controller, 프로그래머블 로직 컨트롤러), DCS(distributed control system, 분산 제어 시스템), 바이오리액터 스키드(skid — 프레임 위에 올린 자족형 장비 모듈로, 자체 펌프·밸브·센서·로컬 제어를 함께 패키징한 용기)와 그 OPC UA 서버(OPC Unified Architecture 표준 인터페이스로 스키드의 태그를 노출함 — 연결성 장 참조)가 있습니다 — 수명이 길고, 취약하며, 캠페인 도중에는 좀처럼 패치되지 않습니다. 정보 기술(information technology, IT) 쪽에는 우리의 브로커, 데이터베이스, 대시보드가 있습니다. 플랜트 현장 보안의 가장 중요한 규칙은 이 두 세계가 평평한(flat) 네트워크를 공유하지 않는다는 것입니다.

구역, 도관, 그리고 보안 수준

여기서의 프레임워크는 IEC 62443, 구체적으로는 Part 3-3입니다. 이것은 구역과 도관(zones and conduits)을 형식화합니다. 플랜트를 보안 구역(security zone)으로 나누고, 그 사이의 도관(conduit, 유일하게 승인된 경로)을 정의하며, 각각에 목표 보안 수준(Security Level)을 부여합니다 [6]. 구역은 보안 요구를 공유하는 자산의 묶음(OT 스키드, IT 데이터 스택)이고, 도관은 구역 사이의 통제된 통신 경로입니다 — 규칙은 트래픽이 평평한 선 위에서 구역 대 구역으로가 아니라 오직 도관을 통해서만 건넌다는 것입니다. 각 구역은 그것이 견뎌야 하는 공격자의 강도를 반영하는 목표 보안 수준(Security Level, SL 1부터 SL 4까지)을 받고, 이것이 다시 그 도관에 대한 구체적 통제를 끌어냅니다. 우리에게 이것은 OPC UA 서버와 엣지 게이트웨이(edge gateway)가 OT 구역에 있고, 히스토리안과 브로커가 IT/DMZ(demilitarized zone, 비무장지대 — 격리된 완충 네트워크) 구역에 있으며, 둘 사이의 유일한 도관이 컬렉터(collector — OT 쪽에서 태그를 읽어 IT 쪽으로 재발행하는, 그림에 보이는 OPC-UA-에서-MQTT 브리지)로서 방화벽이 쳐지고 인증되며 암호화된 링크를 통한다는 뜻입니다. 비즈니스 네트워크상의 어떤 것도 PLC에 직접 닿지 않습니다. 그 보답은 아래 CVE 절과 함께 복리로 불어납니다. 구성 요소가 제대로 그어진 구역 안에 자리 잡으면 그것의 노출(exposure)이 떨어지고, 노출이야말로 CVSS(취약점 심각도 점수, 아래에서 정의됨) 기준점수 — 결함이 실제로 어디 자리 잡고 있는지를 따지기 전, 그 결함의 본질적 기술 심각도에 대한 점수 — 와 당신의 실제 리스크를 가르는 차이입니다.

왼쪽에서 오른쪽으로 이어지는 세 개의 라벨 붙은 보안 구역: PLC 슬래시 DCS가 OPC UA 서버 BR101에 연결된 OT 구역, OPC UA에서 MQTT로 가는 컬렉터를 담은 방화벽-및-TLS 도관, 그리고 Mosquitto·TimescaleDB 플러스 Postgres·Grafana로 이루어진 IT 구역. 읽기 위주 화살표가 도관으로 건너가고 MQTT 슬래시 TLS 화살표가 데이터 스택으로 건너간다.

저 읽기 위주(read-mostly) 화살표는 장식이 아닙니다. 그것은 1장에서 우리가 기반으로 삼은 NAMUR 개방형 아키텍처(NAMUR Open Architecture — 검증된 제어 시스템에 결코 되돌려 쓰지 않는, 읽기 전용의 두 번째 모니터링·분석 채널을 더하는 공정 산업의 패턴)의 본능입니다. 모니터링과 분석은 두 번째 채널을 두드릴 뿐, 검증된 제어로 되돌아가 쓰지 않습니다.

그 도관을 무엇이 건너는지 구체적으로 짚어 둘 가치가 있습니다. 분리는 OT 쪽을 추상적인 "스키드"가 아니라 진짜 mAb 라인으로 그려 보아야 비로소 의미가 통하기 때문입니다. 흘러나오는 OPC UA 태그는 이름 있는 단위공정의 공정 중 신호들입니다. 생산 바이오리액터의 pH·용존 산소·feed rate, 항체가 결합하고 용출되는 동안의 Protein A 캡처 컬럼 UV 트레이스와 전도도, 저(低) pH 바이러스 불활성화 홀드의 pH와 타이머, 폴리싱바이러스 여과 단계의 압력, 그리고 원료의약품(drug substance)이 농축되고 완충액 교환되는 동안의 최종 UF/DF 스키드의 막간차압(transmembrane pressure). 그 하나하나가 OT 쪽의 검증된 제어입니다 — Protein A 용출 그래디언트나 바이러스 홀드 pH를 IT 네트워크에서 조정하면 당신은 의약품이 정제되는 방식을 바꾼 것이고, 이것이 바로 읽기 위주 도관이 금지하는 쓰기(write)입니다. 히스토리안과 분석은 그 측정값을 하나도 빠짐없이 받지만, 그중 어느 것도 되돌려 쓰지는 못합니다.

이제 이 책 전체를 관통하는 불편한 고백입니다. 우리가 실은 개발용 브로커 구성은 의도적으로 활짝 열려 있고, examples/platform/mosquitto/mosquitto.conf에서 그렇다고 말해 줍니다.

# examples/platform/mosquitto/mosquitto.conf
# Mosquitto broker config for the local dev stack (Chapter 7).
# Dev-only: anonymous access on the plain 1883 listener. Chapter 28 (operating &
# securing) replaces this with TLS + per-client ACLs; never ship anonymous in
# a real plant.
listener 1883
allow_anonymous true

# enable the $SYS topic tree so the healthcheck can confirm the broker is alive
sys_interval 10

persistence true
persistence_location /mosquitto/data/
log_dest stdout

평문 1883 리스너에서의 allow_anonymous true는 노트북에는 완벽하고 플랜트에는 재앙입니다. 이 장이 바로 그 약속이 만기가 되는 지점입니다. 강화된 버전은 8883의 TLS로 옮기고, 클라이언트 인증서를 요구하며, 클라이언트별 ACL을 강제해서 탈취된 센서 계정이 모든 것을 구독하지 못하게 합니다.

# ops/mosquitto.tls.conf (illustrative hardening for production)
listener 8883
allow_anonymous false
cafile /mosquitto/certs/ca.crt
certfile /mosquitto/certs/server.crt
keyfile /mosquitto/certs/server.key
require_certificate true
acl_file /mosquitto/config/acl

그 인증서들은 그 자체로 운영의 부담입니다. 그것들은 만료되고, 만료된 브로커나 OPC UA 인증서는 늘 휴일에 떨어지는 듯한 자초한 장애입니다. 모든 인증서의 만료일을 추적하는 인벤토리, 가능한 곳에서의 자동 갱신(rotation), 그리고 그것들을 발급할 OpenSSL이나 작은 내부 CA가 필요합니다. 그리고 개인 키, repo1-cipher-pass, 데이터베이스 비밀번호 — 그 어느 것도 compose 파일이나 Git 저장소에 속하지 않습니다. 그것들은 런타임에 주입되는, 시크릿 매니저(secrets manager)에 속합니다. 위에서 본 ${POSTGRES_PASSWORD:-bioproc} 기본값은 개발 편의입니다. 운영에서 그 변수는 볼트(vault)에서 가져오는 것이지, 결코 타이핑되는 것이 아닙니다.

둘째 날 운영의 네 기둥 다이어그램: HA, 백업과 PITR, TLS와 시크릿을 갖춘 OT/IT 분리, 그리고 CVE 감시와 자가 관측성. 이 모두가 부속서 11과 IEC 62443으로 감싸여 있다.

OSS 바이오공정 스택을 위한 둘째 날 운영의 네 기둥 — 가용성, 복구 가능성, 방어된 구역, 그리고 감시되고 패치되는 스택 — 각각이 그것을 선택 불가능으로 만드는 규제나 표준에 묶여 있다. 저자가 AI의 도움을 받아 직접 제작한 그림입니다.

CVE 대응: NanoMQ의 교훈적 사례

가벼운 오픈소스 구성 요소를 고른다고 해서 그것을 유지보수할 의무가 면제되지는 않습니다. 오히려 판돈이 올라갑니다. 이제 그 유지보수가 당신의 일이기 때문입니다. 당신이 고르지 않은 브로커를 생각해 보세요. NanoMQ는 매력적으로 작은 MQTT 브로커이고, 바로 작기 때문에 패치를 받아야만 했습니다. 2026년에 권고가 떨어졌습니다. MQTT v5 가변 바이트 정수(Variable Byte Integer) 파서인 get_var_integer()에서의 범위 밖 읽기(out-of-bounds read)로, 0.24.6 버전 이하에 조작된 패킷으로 원격에서 유발될 수 있었습니다 [7]. 국가 취약점 데이터베이스(National Vulnerability Database)는 이를 CVE-2026-21888로 목록화했고, CWE-125(범위 밖 읽기)로 분류했으며, CVSS 3.1 기준점수 7.5로 High 등급을 매겼습니다 [8].

그 점수는 감(感)이 아닙니다. 그것은 정의된 모델에서 나옵니다. 공통 취약점 점수 체계(Common Vulnerability Scoring System)는 취약점의 특성을 벡터 문자열과 0–10 숫자로 바꿉니다. 이 특정 CVE는 CVSS 3.1로 점수가 매겨졌지만, 같은 원칙이 나중의 CVSS v4.0 명세에서 명시적으로 드러납니다 — 기준점수(base score)는 기술적 심각도를 측정하는 것이지 당신의 리스크를 측정하는 것이 아닙니다 [9]. 인터넷에 노출된 브로커에서의 7.5는 비상 훈련이지만, 인바운드 노출이 없는 IEC 62443 OT 구역 안에 잠긴 브로커에서의 똑같은 7.5는 다음 유지보수 창에 일정으로 잡아 두는 무언가입니다. 심각도는 입력이고, 당신의 분리와 노출이 그것을 분류(triage) 결정으로 바꿉니다. 이것이 앞 절이 여기서 보답하는 이유입니다 — 좋은 구역화는 말 그대로 주어진 CVE의 실제 리스크를 낮춥니다.

그래서 둘째 날 루프는 이렇습니다. 인벤토리 → 감시 → 분류 → 패치 → 재검증. 인벤토리는 소프트웨어 자재 명세서(software bill of materials, SBOM)입니다. 운영자는 make sbom(고정된 모든 compose 이미지에 대해 Syft → CycloneDX, 이미 이 저장소에 있습니다)으로 그것을 생성하고, make lock으로 모든 구성 요소를 다이제스트로 고정합니다. Grype/Trivy CVE 스캔 단계와 공급자 등록부는 검증 장에서 다룹니다. 스캔 타깃은 아직 이 저장소의 Makefile에 배선되어 있지 않으므로, 그 단계는 오늘 실행할 수 있는 make 타깃이라기보다 권장되는 형태로 받아들이세요. 그다음 CVE 감시 런북이 고정된 모든 이미지에 대한 권고를 구독합니다. 새로운 권고는 그 구성 요소가 실제로 어디 자리 잡고 있는지에 비추어 CVSS에 기반한 분류를 받습니다. 패치란 고정된 태그 와 다이제스트를 올리고, 다시 빌드하고, 테스트 스위트를 다시 돌리는 것을 뜻합니다 — 검증된 환경에서 패치는 변경(change)이고, 변경은 그저 적용되는 것이 아니라 재검증되어야 하기 때문입니다. 그리고 스캐너 자체도 위협 표면(threat surface) 위가 아니라 그 안에 속합니다. 스캐너 바이너리와 그 피드(feed)도 공급자로 취급하고, 검토된 허용 목록(allowlist)에 대해 실행하며, 스캐너의 출처(provenance)를 맹목적 신뢰가 아니라 평가의 일부로 삼으세요 — 널리 쓰이는 개발자 도구의 공급망 침해는 가설이 아니라 업계에서 반복되는 패턴입니다.

현장 증거: 왜 심각도가 아니라 분리가 당신의 리스크를 결정하는가

"기준점수가 아니라 노출이 당신을 무는 것"이라는 주장은 수사적 장식이 아닙니다. 인터넷 전역 측정이 계속해서 찾아내는 바로 그것입니다. 다섯 개의 산업 제어 프로토콜에 걸친 공개 IPv4 공간에 대한 기념비적인 ZMap급 조사는 6만 개가 넘는 공개적으로 도달 가능한 ICS 시스템을 찾아냈고, 그중에는 502 포트에서 응답하는 약 2만 3천 개의 진짜 Modbus 장치와 75개국에 걸친 약 2천 8백 개의 지멘스 S7 컨트롤러가 포함됩니다 — 프로토콜 설계상 어떤 인증도 하지 않고, 패킷을 라우팅할 수 있는 누구의 명령이든 실행하는 장치들입니다 [11]. 같은 패턴이 메시지 계층에서도 되풀이됩니다. Shodan 기반 스캔은 4만 9천 개가 넘는 공개적으로 도달 가능한 MQTT 브로커를 찾아냈고, 그중 3만 2천 개 이상이 아무런 비밀번호 보호도 없었습니다 [12]. 그 두 숫자를 함께 읽으면 우리 스택에 주는 교훈이 정확해집니다. NanoMQ CVE-2026-21888 범위 밖 읽기는 어디서나 7.5이지만, 실제로 악용당하는 모집단은 앞에 구역이 없는 라우팅 가능한 주소 위에 앉아 있는 쪽입니다. 당신의 실제 리스크를 움직이는 방어는 더 낮은 CVSS 점수가 아니라 — 그런 것은 존재하지 않습니다 — 도관입니다. 공격자가 닿을 수 없는 브로커나 데이터베이스는, 그 다음 치명적 권고를 새벽 3시의 진화 작업 대신 유지보수 창에 일정으로 잡아 둘 수 있는 브로커나 데이터베이스입니다. 이것이 PITR 런북이 제 몫을 하는 이유이기도 합니다. 현장 장애 양태가 "노출된 인스턴스가 침해되거나 손상되었다"일 때, 알려진-양호 지점으로의 시험된 복구는 분리가 예방의 절반을 제공하는 바로 그 자세의 복구 절반입니다.

학습하는 모델이 왜 같은 배관 위에 올라타는가

뒤이은 분석 장들은 이 플랫폼을 소비하기만 하는 것이 아닙니다. 그것들은 이 플랫폼의 둘째 날 규율을 통째로 물려받습니다. 배포된 모델은 쇠퇴하고, 감시되어야 하며, 복구 가능해야 하는 또 하나의 변경 통제(change-controlled) 구성 요소이기 때문입니다. 이 장의 습관 중 셋이 MLOps(운영에 들어간 모델의 운영 라이프사이클)에 거의 일대일로 대응됩니다.

  • PITR과 데이터셋 해시는 모델 계보(lineage)다. 모델은 그것을 적합(fit)시킨 데이터만큼만 재현 가능합니다. 데이터베이스를 어제 14시 32분으로 재구축하게 해 주는 그 WAL-및-베이스-백업 규율이, 출하된 모델 뒤의 정확한 학습 스냅샷을 고정하게 해 주는 바로 그것입니다 — 그래서 재학습(retrain)은 모호한 "우리는 최근 데이터로 다시 적합시켰다"가 아니라 데이터셋 해시와 버전 상승을 기록합니다. 바로 Book 5가 짓는 잠긴 모델, 거버넌스된 재학습 루프입니다. 학습 데이터를 되감을 수 없는 모델은 재검증할 수 없는 모델입니다.
  • CVE 재검증은 곧 모델 재검증이다. "패치는 변경이고, 변경은 재검증되어야 한다"는 규칙은 GMP 아래 학습하는 모델을 규율하는 동일한 규칙입니다. 고정된 태그를 조용히 올릴 수 없듯, 가중치를 조용히 바꿔치기할 수도 없습니다. 둘 다 같은 통제된-변경 게이트를 거치며, 그래서 학습하는 것을 검증하기는 새 자세를 발명하는 대신 이 장의 자세를 재사용합니다.
  • 분리가 노출을 낮추듯, 적용 범위가 다른 종류의 노출을 낮춘다. CVSS 기준점수가 구성 요소가 어디 자리 잡고 있는지를 따져야 비로소 실제 리스크가 되듯, 모델의 표제 정확도도 그 입력이 어디 자리 잡고 있는지 — 그것이 보정된(calibrated) 영역 안인지 바깥인지 — 를 따져야 비로소 실제 리스크가 됩니다. 적용 범위(applicability domain, 학습 데이터가 실제로 덮은 입력 영역) 바깥의 입력에 대해 예측하라고 요청받은 모델은 외삽(extrapolation) 중이며, 이는 분리되지 않은, 인터넷에 노출된 브로커의 데이터 주도(data-driven) 등가물입니다 — 기술적으로는 같은 모델, 운영상으로는 비상 훈련. MLOps 장의 드리프트 탐지기는 CVE 감시 런북의 모델 세계 대응물입니다 — 둘 다 한때 안전했던 구성 요소가 안전하지 않게 되는 순간을 감시하며, 어느 쪽도 미뤄 둔 사건을 새벽 3시의 사건으로 바꾸지 않고서는 건너뛸 수 없습니다.

교훈은 이 장 전체가 주장하는 것과 같습니다. 인프라는 분석과 별개가 아닙니다. 거버넌스되고, 복구 가능하며, 스스로를 감시하는 스택은 그것이 담은 데이터로 학습된 어떤 모델이든 신뢰하기 위한 선결 조건입니다.

VictoriaMetrics로 감시자를 감시하기

볼 수 없는 것은 운영할 수 없습니다. 플랫폼은 자기 자신을 모니터링해야 합니다. 브로커 연결 수, 데이터베이스 복제 지연(replication lag), 디스크 여유 공간, 백업 성공, 인증서 만료, 서비스 상태. 이를 위해 스택은 VictoriaMetrics를 싣습니다. victoriametrics/victoria-metrics:v1.108.1에 고정되어 있고, 같은 compose 파일에서 ops/analytics 프로파일 뒤에 게이팅되어 있습니다.

# examples/platform/compose/compose.yaml
# --- analytics ---------------------------------------------------------
victoriametrics:
image: victoriametrics/victoria-metrics:v1.108.1
profiles: ["analytics", "ops"]
<<: *restart
ports: ["8428:8428"]

VictoriaMetrics는 우리가 InfluxDB 대신 싣는 Apache-2 메트릭 저장소입니다 — InfluxDB v3 라이선스 전환을 의도적으로 피한 것으로(EMQX와 같은 부류의 움직임입니다. 더 새로운 InfluxDB 코어가 완전히 관대한 오픈소스 라이선스에서 물러섰기에, 우리는 그 위에 쌓아 올리는 것을 피합니다), BSL 시대의 EMQX보다 Mosquitto에 머무르게 한 것과 같은 본능입니다. 운영상으로 그것은 후합니다. 단일 노드가 초당 백만 샘플 미만의 수집(ingestion)을 여유롭게 처리합니다. 그 계층에서의 HA란 복제된 remote-write로 먹여지는 두 개의 동일한 단일 노드 인스턴스를 돌리는 것을 뜻합니다 — 메트릭 스트림이 두 인스턴스에 동시에 쓰이므로, 한쪽이 죽어도 다른 쪽이 서비스할 수 있습니다 — 그리고 더 큰 규모에 이르러서야 클러스터 버전에 손을 뻗게 되는데, 그쪽 HA는 대신 복제 계수(replication factor — 각 샘플이 둘 이상의 클러스터 노드에 저장되는 것)에서 나옵니다 [10]. mAb 라인 하나에는 단일 노드면 충분하고, 그 단순함 자체가 하나의 기능입니다 — 백업하고, 패치하고, 검증할 움직이는 부품이 더 적습니다. 그것이 긁어 오는 메트릭은 Grafana의 알림을 먹여서, 디스크가 가득 찬 가 아니라 그 전에 당신을 호출(page)합니다.

플랜트 데이터 플랫폼 준비성 리뷰

이 장의 모든 것 — HA, PITR, 분리, 시크릿, CVE 대응, 자가 관측 — 은 저마다 별개의 규율이며, 조용한 실패 양상은 그것들을 돌리는 목록이 아니라 읽는 목록으로 다루는 것입니다. 그래서 이 플랫폼이 GMP 관련 데이터를 담도록 허용되기 전에, 그것은 단일한 준비성 리뷰(readiness review)를 통과해야 합니다: 플랫폼 자체를 겨냥한 go/no-go 게이트로, 모든 줄이 검사관이 볼 수 있는 산출물이나 운영자가 돌릴 수 있는 명령으로 귀결됩니다. make test는 스택이 작동함을 증명하고, 이 리뷰는 그것이 운영에 적합함을 증명합니다.

준비성 영역점검무엇으로 증명하는가Go 조건
기능 수용스택이 빌드되고, 올라오고, 그 테스트가 통과함make up 다음 make test(커밋된 결정성 + db + 분석 스위트)깨끗한 체크아웃에서 녹색
데이터 무결성 및 감사 체인추가 전용(append-only) 기록의 해시 체인이 끊기지 않고 변조가 드러남감사 테이블에 대한 verify_chain()(ALCOA+ 장)깨끗하게 검증됨; 심어진 편집이 탐지됨
맥락화원시 태그가 실제로 레시피·장비·배치에 결합됨 — 고아 측정값 없음맥락화 조인을 알려진 배치에 대해 표본 점검모든 측정값이 그 batch_id와 자산으로 귀결됨
백업 및 복구 훈련데이터베이스를 선택한 순간으로 재구축할 수 있고 복구가 검증됨pgBackRest PITR을 통한, 기록된 분기별 복구 훈련(부속서 11 §7)복구된 데이터가 알려진 시점에 대해 확인됨
가용성 및 연속성장애 조치가 문서화·연습되고, 브로커의 정직한 단일 노드 한계가 덮이지 않고 적혀 있음PostgreSQL 대기본 승격에 더해 Mosquitto 빠른-재시작 SOP(부속서 11 §16)연습된 장애 조치; 파일에 있는 연속성 계획
분리 및 시크릿OT/IT 존이 강제되고, TLS가 있어야 할 곳에서 종단되며, 어떤 시크릿도 compose 파일에 놓이지 않음네트워크 다이어그램에 더해 docker compose 구성 리뷰도관 설치됨; 시크릿 외부화·순환됨
공급망 및 CVE 태세모든 이미지가 다이제스트로 고정되고, SBOM이 존재하며, 다음 권고를 위한 런북이 있음make lock(→ versions.lock) + make sbom + CVE 감시 런북다이제스트 고정; SBOM 최신; 런북 소유됨
관측성스택이 자기 자신을 감시하여, 차오르는 디스크나 멈춘 수집을 검사관이 찾기 전에 잡음Grafana 알림을 먹이는 VictoriaMetrics 메트릭모의 결함에 알림이 발화함

두 가지 정직함이 이 리뷰를 닫으며, 그것들이 이 책 전체의 논지를 한자리에 담습니다. 첫째, 녹색 리뷰는 플랫폼이 운영 가능하고 방어 가능함을 증명할 뿐, 검증됨(validated)을 증명하지 않습니다 — GAMP 5 / CSA 생애주기, 공급자 등록부, 그리고 자격 있는 서명이 통과된 체크리스트를 검증된 시스템으로 바꾸는 래퍼이며, 오픈소스는 엔진을 건네줄 뿐 그 래퍼는 아닙니다. 둘째, 정직한 단일 사이트 한계는 숨기는 대신 적힌 채로 남습니다: Mosquitto는 클러스터링되지 않고, pgBackRest 구성은 이 저장소에서 아직 예시적이며, CVE 스캔 단계는 아직 make 타깃이 아닙니다 — 그것들을 감춘 준비성 리뷰야말로 이 장이 경고하는 바로 그 눈속임일 것입니다. 게이트의 일은 승리를 선언하는 것이 아니라, 플랫폼의 실제 태세를 서명해야 하는 사람에게 한 줄 한 줄 읽히게 만드는 것입니다.

왜 중요한가

플랫폼은 한 번 동작한다고 끝난 것이 아닙니다. 그것이 장애와 공격과 시간을 통과해 계속 동작하고, 그렇게 했음을 입증할 수 있을 때 끝난 것입니다. 부속서 11은 가용성, 백업, 연속성을 감사 가능한 의무로 바꿉니다 [1]. IEC 62443은 "OT와 IT를 떼어 놓아라"를 방어 가능한 아키텍처로 바꿉니다 [6]. 둘째 날 운영을 건너뛰는 것은 지름길이 아닙니다. 그것은 미뤄 둔 장애이자 미뤄 둔 감사 지적이고, 둘 다 이자가 붙습니다.

실제 현장에서는

실제 바이오 제조사들은 이 하이브리드를 정직하게 운영합니다. PostgreSQL/PITR 이야기는 오픈에서 진정으로 운영 등급입니다 — pgBackRest는 세계에서 가장 규모가 큰 규제 데이터베이스 일부를 백업합니다 [4][5]. 브로커 HA 이야기는 돈이 오가는 지점입니다. 진정한 클러스터 MQTT가 필요한 곳은 2025년 BSL 전환 이후 EMQX를 라이선스하거나 [3], HiveMQ를 사거나 — 단일 GMP 사이트에서 가장 흔하게는 — 빠른 장애 조치 SOP와 함께 면밀히 감시되는 단일 브로커를 돌리며 Mosquitto의 정직한 단일 노드 한계를 받아들입니다 [2]. 점검관과의 접촉에서 살아남는 패턴은 결코 "우리는 오픈소스를 썼다"가 아닙니다. 그것은 "우리는 오픈소스를 검증된 라이프사이클 안에서 썼고, 시험된 복구, 분리된 네트워크, 갱신된 인증서, CVE 런북, 그리고 자기 자신을 감시하는 스택과 함께 썼다"입니다. 순수 OSS는 당신을 대부분의 길까지 데려다줍니다. 마지막 한 마일 — 클러스터 브로커 HA, 벤더 책임성, 턴키(turnkey) Part 11 래퍼(21 CFR Part 11, 전자 기록과 전자 서명을 규율하는 FDA 규정) — 은 하이브리드이고, 그렇게 말하는 것이 이 책의 핵심 그 자체입니다.

핵심 용어

  • MQTT / 브로커: MQTT는 플랜트가 센서 측정값을 옮기는 데 쓰는 가벼운 발행/구독 메시징 프로토콜이고, 브로커는 발행된 메시지를 받아 구독자에게 중계하는 서버이다.
  • 고가용성(high availability, HA): 구성 요소 하나의 고장이 서비스를 멈추게 하지 않도록 설계하는 것. 데이터베이스에는 핫 스탠바이, 브로커에는 클러스터.
  • PITR(point-in-time recovery, 시점 복구): 보관된 WAL을 베이스 백업 위에 재생함으로써 데이터베이스를 선택한 임의의 순간으로 복원하는 것.
  • WAL(write-ahead log, 미리 쓰기 로그): 모든 변경에 대한 PostgreSQL의 순서 있는 기록. 복제와 PITR의 원재료.
  • pgBackRest: 암호화·호스트 외부·표적 복구에 쓰이는 관대한 오픈소스 PostgreSQL 백업/복구 도구.
  • 오픈소스 대 소스 공개(open source vs source-available): OSI 오픈은 라이선스가 Open Source Initiative의 승인 목록에 있다는 뜻으로 — 어떤 규모로든 자유롭게 읽고, 수정하고, 돌릴 수 있다(예: Apache-2.0, PostgreSQL License. 그중 관대한(permissive) 종류는 조건을 거의 붙이지 않는다). 소스 공개(source-available)는 소스는 읽을 수 있지만 운영 사용이 제한된다는 뜻이다(예: BSL, TimescaleDB TSL 커뮤니티). 이 책은 OSI 오픈만을 진정으로 무료인 것으로 취급한다.
  • BSL(Business Source License): 특정 기능의 운영 사용을 제한하는(예: EMQX 5.9부터의 클러스터링) 소스 공개(source-available) 라이선스 — 읽기는 무료, 규모에 맞춰 돌리는 것은 무료가 아니다.
  • IEC 62443: OT 보안 표준 계열. Part 3-3은 구역, 도관, 보안 수준을 정의한다.
  • 구역과 도관(zones and conduits): 플랜트를 신뢰 구역으로 분할하고 그 사이에 통제되고 모니터링되는 경로를 두는 것.
  • CVE / CVSS: 목록화된 취약점 식별자(예: CVE-2026-21888)와 그 기술적 심각도를 점수화하는 0–10 모델.
  • SBOM(software bill of materials, 소프트웨어 자재 명세서): 스택 안 모든 구성 요소와 버전의 기계 판독 가능한 인벤토리. CVE 감시의 토대.
  • 자가 관측성(self-observability): 플랫폼이 자기 자신의 상태와 메트릭을 모니터링하는 것. 여기서는 VictoriaMetrics를 통해.
  • 준비성 리뷰(readiness review): 이 장의 별개 규율들 — 기능 수용(make test), 감사 체인 검증(verify_chain()), 맥락화, 기록된 복구 훈련, 장애 조치, 분리/시크릿, 공급망/CVE 태세, 관측성 — 을 하나의 실행 가능한 체크리스트로 모아 플랫폼이 운영에 적합함을 증명하는 go/no-go 게이트. 다만 운영 가능함이 검증됨과 같지 않다는 점을 분명히 밝힙니다.
  • Sqitch 마이그레이션: 버전 관리되는 데이터베이스 변경을 이름 있고, 되돌릴 수 있으며, 스스로 검증하는 단위로 만든 것 — 변경마다 deploy, verify, revert 스크립트가 있고, verify = true가 동작했음을 증명하지 못하는 변경을 자동으로 되돌린다.
  • 보안 수준(Security Level, SL): IEC 62443에서 구역에 부여되는 목표 회복력(SL 1–4). 그것이 견뎌야 하는 공격자의 강도를 반영하며 그 도관에 대한 통제를 끌어낸다.
  • SHACL / PROV-O: 데이터베이스 안전장치의 그래프 세계 대응물 — SHACL은 RDF 데이터가 요구된 패턴을 따르는지 검증하는 셰이프 언어(CHECK 제약의 닫힌 세계 대응물)이고, PROV-O는 누가 기록을 언제 생성했는지 적는 W3C 어휘로 감사 추적의 출처(provenance) 척추이다.
  • 역량 질문(competency question): 데이터 모델이나 온톨로지가 반드시 답할 수 있어야 하는 평이한 질문 — 여기서는 "선택한 순간에 서 있던 그대로 스키마와 데이터를 재구축하라" — 으로, 통과/실패 설계 시험으로 쓰인다.
  • MLOps / 모델 계보 / 적용 범위: MLOps는 배포된 모델의 운영 라이프사이클(드리프트 탐지, 거버넌스된 재학습, 롤백)이고, 모델 계보(lineage)는 출하된 모델 뒤의 정확한 데이터와 버전을 고정한 기록이며, 적용 범위(applicability domain)는 모델이 실제로 학습한 입력 영역으로, 그 바깥에서는 예측이 외삽이 된다.

다음 이야기

이제 플랫폼은 서 있고, 방어되고, 복구 가능하며, 감시됩니다. 잘 거버넌스된 데이터의 신뢰할 만한 흐름이 그것을 통과해 흐르는 가운데, 우리는 마침내 가장 보람 있는 질문을 던질 수 있습니다. 우리는 그 데이터에서 무엇을 배울 수 있을까요? 다음 장 공정 분석: SPC, MVDA와 소프트 센서는 히스토리안과 실험실 테이블을 통계적 공정 관리(statistical process control) 차트, 다변량 배치 모델(multivariate batch model), 그리고 라만(Raman)-역가 소프트 센서(soft sensor)로 바꿉니다 — 플랫폼 전체가 가능케 하려고 지어진 그 분석의 보상입니다.