Go Docs

이 문서는 Hospital-at-Home 예제에서 Go가 맡는 runtime operations와 Kubernetes operator 경계를 설명합니다. Go는 clinical risk를 계산하는 backend가 아니라, RiskMonitor desired state와 observed runtime state를 비교해 drift와 status를 표현하는 계층입니다.

Go 문서는 pkgsite/godoc을 기준으로 관리합니다.

pkgsite
go doc ./...

package comment와 exported symbol comment를 API reference의 source of truth로 둡니다. 서비스 운영 문서는 app README.md와 CD repo overlay에 둡니다.

1. Hospital-at-Home 구현 기준

이 시나리오에서 Go는 일반 backend API server보다 Kubernetes operator와 platform automation을 담당합니다. 1단계에서는 production-grade operator가 아니라, RiskMonitor desired state와 runtime state를 비교하는 dependency-free reconcile core와 operator-style app을 구현합니다.

1-1. 책임

Go가 담당합니다.

  • RiskMonitor CRD spec/status 모델링
  • desired model version과 current deployed version 비교
  • threshold policy, rollout channel, site config reconcile
  • deployment/config drift 감지
  • runtime health status 계산
  • CD repo와 연결되는 platform contract

Go가 담당하지 않습니다.

  • 생체신호 preprocessing
  • clinical risk assessment
  • firmware packet 생성
  • clinician UI rendering
  • MQTT message parsing

1-2. Operator boundary

초기 단계에서는 Kubernetes API server 없이 reconcile decision을 먼저 고정합니다.

RiskMonitor.spec
  + CurrentRuntimeState
  -> RiskMonitorStatus
  -> RiskMonitor.status

1단계 operator가 담당하는 범위는 아래로 제한합니다.

  • RiskMonitor spec/status domain type
  • sample RiskMonitor manifest
  • stateless reconcile usecase
  • status condition 계산
  • unit/controller test

Sample manifest는 Kubernetes resource envelope를 따릅니다.

{
  "apiVersion": "samd.tirosh.ai/v1alpha1",
  "kind": "RiskMonitor",
  "metadata": {
    "name": "hospital-at-home-risk-monitor",
    "generation": 7
  },
  "spec": {
    "siteId": "demo-site",
    "desiredModelVersion": "demo-hospital-at-home-risk-24h",
    "desiredImageTag": "0.1.0-dev.42",
    "desiredPolicyVersion": "demo-policy-0.1.0",
    "desiredPreprocessingVersion": "rust-signal-preprocessor-0.1.0",
    "rolloutChannel": "dev",
    "highRiskThreshold": 0.85
  },
  "runtimeState": {
    "observedModelVersion": "demo-hospital-at-home-risk-24h",
    "observedImageTag": "0.1.0-dev.41",
    "observedPolicyVersion": "demo-policy-0.1.0",
    "observedPreprocessingVersion": "rust-signal-preprocessor-0.0.9",
    "healthy": true
  }
}

runtimeState는 production CRD spec이 아니라 local operator smoke를 위한 observed state fixture입니다. App layer에서는 이 값을 RuntimeStateProvider adapter가 제공하는 observed state처럼 다룹니다. 실제 cluster adapter에서는 이 값이 Kubernetes workload, ConfigMap, API readiness, image tag 조회 adapter에서 만들어지고, controller는 status.observedGeneration과 condition을 status subresource에 갱신합니다.

현재 1차 구현은 아래 condition을 계산하고, ReconcileRiskMonitorResource adapter가 resource status에 반영합니다. KubernetesRiskMonitorReconciler는 controller-runtime client.Client와 fake client test를 사용해 실제 operator adapter가 resource를 읽고 status subresource를 갱신하는 흐름까지 보여줍니다. 추가로 KUBEBUILDER_ASSETS가 준비된 환경에서는 envtest API server backed integration test가 실제 CRD status subresource update를 한 번 더 검증합니다.

  • Ready
  • ModelVersionApplied
  • ImageTagApplied
  • PolicyVersionApplied
  • PreprocessingVersionApplied
  • RuntimeHealthy

이 condition shape를 먼저 고정하는 이유는 Kubernetes adapter가 붙었을 때도 domain rule과 status 의미가 흔들리지 않게 하기 위해서입니다. Controller는 API watch/update를 담당하고, drift 판단은 patient-risk-go-core가 담당합니다. App layer는 metadata.generationstatus.observedGeneration으로 전달해 Kubernetes status 관례도 같이 보여줍니다.

RuntimeStateProvider는 실제 controller에서 informer, Deployment, ConfigMap, Python API readiness, image tag 조회 adapter로 바뀔 경계입니다. 1차 테스트는 fake provider를 사용해 embedded fixture drift와 provider-backed ready 상태를 검증하고, controller-runtime fake client로 status update 경계도 검증합니다.

현재 app에는 두 provider가 있습니다.

Provider 용도
EmbeddedRuntimeStateProvider local CLI smoke에서 manifest의 demo-only runtimeState를 observed state로 사용
KubernetesDeploymentRuntimeStateProvider controller-runtime client로 Deployment, optional ConfigMap, optional readiness endpoint를 읽어 observed state로 변환

KubernetesDeploymentRuntimeStateProviderRiskMonitor.metadata.annotations["samd.tirosh.ai/runtime-workload-name"]가 있으면 해당 Deployment를 읽고, 없으면 metadata.name과 같은 이름의 Deployment를 읽습니다. Deployment annotation은 model/policy/preprocessing version을 제공하고, 첫 번째 container image tag는 observed image tag가 됩니다. Deployment.status.conditions[type=Available,status=True] 또는 availableReplicas > 0이면 workload가 schedule 관점에서 available하다고 봅니다.

RiskMonitor.metadata.annotations["samd.tirosh.ai/runtime-configmap-name"]가 있으면 provider는 같은 namespace의 ConfigMap을 읽고 modelVersion, policyVersion, preprocessingVersion 값을 observed runtime metadata로 사용합니다. ConfigMap은 runtime이 실제로 로드한 model/policy/preprocessing package provenance를 드러내는 adapter입니다. 값이 비어 있으면 Deployment annotation 값을 대체값으로 사용합니다.

Deployment annotation에 samd.tirosh.ai/readiness-url이 있으면 provider는 해당 URL을 HTTP GET으로 호출합니다. 예제에서는 Python API의 /readyz처럼 storage dependency까지 확인하는 endpoint를 여기에 연결합니다. 이때 runtime health는 Deployment Available과 readiness endpoint 2xx 응답을 모두 만족해야 true가 됩니다. Version drift가 없더라도 readiness가 실패하면 Ready condition은 RuntimeUnhealthy, RuntimeHealthy condition은 HealthCheckFailed가 됩니다.

RuntimeStateProvider가 Deployment, ConfigMap, readiness endpoint를 읽지 못하는 경우도 status contract에 남깁니다. Controller는 provider error를 그대로 삼키거나 빈 status를 쓰지 않고, ReadyRuntimeHealthy condition을 Unknown/RuntimeStateUnavailable으로 기록한 뒤 requeue를 요청합니다. Provider error는 reconcile error로도 반환되어 controller-runtime retry와 Kubernetes status 관측성을 함께 보여줍니다.

Runtime failure는 아래처럼 구분합니다.

Failure Status meaning Reconcile behavior
Deployment 없음 또는 읽기 실패 runtime state 자체를 관찰하지 못함 Ready=Unknown, RuntimeHealthy=Unknown, RuntimeStateUnavailable, requeue, reconcile error
Runtime metadata ConfigMap 없음 또는 읽기 실패 model/policy/preprocessing provenance를 관찰하지 못함 Ready=Unknown, RuntimeHealthy=Unknown, RuntimeStateUnavailable, requeue, reconcile error
Deployment는 있지만 Available 아님 runtime은 관찰됐지만 unhealthy Ready=False, RuntimeHealthy=False, RuntimeUnhealthy, requeue
Readiness endpoint가 2xx 아님 app dependency readiness 실패 Ready=False, RuntimeHealthy=False, HealthCheckFailed, requeue
Desired/observed version 차이 runtime은 관찰됐지만 desired state와 drift field-level drifts, RuntimeDrift, rollout action

이 taxonomy를 두는 이유는 operator가 모든 문제를 drift로 취급하지 않게 하기 위해서입니다. Deployment/ConfigMap을 읽지 못한 상태에서 image tag나 model version drift를 계산하면 거짓 자동화가 발생할 수 있습니다. 반대로 readiness 실패는 observed version이 맞더라도 runtime health 문제로 남겨야 합니다.

이 adapter는 production operator가 바로 써도 되는 완성형 controller가 아니라, Kubernetes workload, image tag, runtime metadata, readiness endpoint를 domain usecase에 필요한 observed state로 변환하는 핵심 경계를 코드로 보여줍니다.

conditions는 Kubernetes status summary에 가깝고, drifts는 자동화가 바로 읽을 수 있는 field-level detail입니다. 각 condition에는 observedGeneration을 포함해 어떤 spec generation을 기준으로 계산된 상태인지 드러냅니다. 예를 들어 image tag와 preprocessing version이 동시에 어긋난 경우 status는 전체 action을 rollout-runtime으로 제안하면서도, drifts에는 desiredImageTag, desiredPreprocessingVersion 각각의 desired/observed/action을 남깁니다. 이 구분이 있어야 사람이 보는 상태와 자동화가 처리할 차이가 섞이지 않습니다.

Ready condition의 reason은 원인을 구분합니다. desired/observed config가 다른 경우는 RuntimeDrift, config는 맞지만 readiness probe나 runtime health가 실패한 경우는 RuntimeUnhealthy로 둡니다. 이 차이가 있어야 controller가 rollout 문제와 runtime 장애를 같은 drift로 취급하지 않습니다.

RiskMonitor 입력 manifest는 risk-monitor.v1.schema.json, reconcile 결과 status는 risk-monitor-status.v1.schema.json으로 검증합니다.

아래 항목은 범위 밖입니다.

  • actual model rollout automation
  • Argo CD/Application mutation
  • admission webhook
  • conversion webhook
  • multi-cluster reconcile
  • production HA controller

1-3. Runtime policy contract

Go operator에서 다룰 runtime policy는 아래 항목을 포함합니다.

  • model image/version
  • preprocessing package version
  • threshold policy version
  • rollout channel
  • site id
  • desired replica/runtime status
  • last applied source revision
  • drift status

이 정보는 Python risk API가 응답하는 modelVersion/policyVersion과 연결되어야 합니다. CD repo는 environment별 desired state를 RiskMonitor manifest로 표현합니다.

1-4. Test strategy

Test Scope
Unit spec/state -> reconcile decision
App smoke sample manifest -> status JSON
Controller-style app resource + runtime state provider -> status update
Kubernetes controller controller-runtime fake client -> status subresource update
Envtest controller real API server + CRD status subresource update
Contract CD repo RiskMonitor manifest와 Go type 정합성

Provider failure test는 RuntimeStateUnavailable condition과 requeue status를 고정합니다. 이 테스트는 operator가 dependency 조회 실패를 단순 로그로만 남기지 않고, status subresource에서도 운영자가 읽을 수 있게 만든다는 점을 보여줍니다. Kubernetes provider test는 missing Deployment, missing runtime metadata ConfigMap, unhealthy Deployment, failed readiness endpoint를 분리해 provider error와 runtime unhealthy가 서로 다른 condition reason으로 남는지 확인합니다.

Envtest는 실제 Kubernetes control plane binary가 필요합니다. Local에서 KUBEBUILDER_ASSETS가 비어 있으면 envtest case는 skip됩니다. CI의 source-repo-go workflow는 setup-envtest로 assets를 준비한 뒤 app test를 실행하므로, pull request에서는 envtest가 항상 gate로 동작합니다.

CI에서는 envtest binary를 checkout workspace가 아니라 $RUNNER_TEMP/envtest 아래에 둡니다. self-hosted runner는 workspace를 재사용하므로, control plane binary를 repository tree 아래에 받으면 checkout cleanup이 실패할 수 있습니다.

Local에서 같은 조건으로 확인하려면 아래처럼 assets를 준비합니다.

go install sigs.k8s.io/controller-runtime/tools/setup-envtest@release-0.20
setup_envtest="$(go env GOPATH)/bin/setup-envtest"
export KUBEBUILDER_ASSETS="$("$setup_envtest" use -p path --bin-dir "$PWD/.envtest" '1.32.x!')"

그다음 envtest case를 실행합니다.

go test ./apps/risk-monitor-operator/... -run Envtest

1-5. Clean Architecture 기준

Go에서 Clean Architecture의 중심은 controller-runtime을 domain으로 끌고 들어오지 않는 것입니다. patient-risk-go-core는 desired state와 observed state를 받아 reconcile decision과 status condition을 계산합니다. Kubernetes client, Deployment 조회, ConfigMap 조회, readiness HTTP call은 app/adapter 책임입니다.

RiskMonitorSpec + RuntimeState
  -> ReconcileRiskMonitor usecase
  -> RiskMonitorStatus

controller-runtime client
  -> RuntimeStateProvider adapter
  -> status subresource update adapter

단단하게 고정할 것은 RiskMonitor spec/status, condition reason, drift detail입니다. 느슨하게 둘 것은 Kubernetes 조회 방식, readiness endpoint, rollout automation, CD tool integration입니다.

1-6. DDD 기준

Go bounded context는 runtime operations입니다. 임상 risk 자체를 계산하지 않고, 배포된 runtime이 원하는 model/policy/preprocessing/image 상태를 따르고 있는지 판단합니다.

Term Meaning
desired CD repo나 operator input이 요구하는 runtime state
observed cluster/API/readiness에서 실제로 관찰한 runtime state
drift desired와 observed가 다른 field-level 차이
condition 사람이 읽는 Kubernetes status summary
action 자동화나 운영자가 취할 수 있는 다음 조치

이 언어를 유지하면 Go operator가 Python risk model이나 frontend alarm rule을 침범하지 않고, product runtime governance만 담당할 수 있습니다.

1-7. TDD 기준

Go operator TDD는 Kubernetes API server를 마지막에 붙입니다.

  1. pure unit test로 spec/state -> status decision을 고정합니다.
  2. fake RuntimeStateProvider로 app adapter가 status를 갱신하는지 확인합니다.
  3. controller-runtime fake client로 status subresource update boundary를 검증합니다.
  4. envtest로 실제 API server 기반 CRD/status update를 smoke test합니다.

이 순서를 지키면 reconcile rule 실패와 Kubernetes client wiring 실패를 분리해서 디버깅할 수 있습니다.

2. Package Registry

Go는 project-local registry config 파일보다 canonical module path, semver Git tag, GOPROXY, GONOSUMDB 조합을 주로 사용합니다. Nexus Go group repository를 download proxy로 둡니다.

export GOPROXY=https://nexus.internal.tirosh.ai/repository/go/
export GONOSUMDB=github.com/tirosh-chain/*

Local 개발에서는 scripts/configure-nexus.shNEXUS_GO_PROXY_URLGO_PRIVATE_MODULE_PATTERNS를 읽어 project-local .nexus-go.env를 생성합니다. 필요할 때 source .nexus-go.env로 현재 shell에만 적용합니다. CI에서는 workflow가 GOPROXY/GONOSUMDB env와 .netrc를 job 안에서 직접 구성합니다.

Go module은 raw tarball을 Nexus에 직접 올리지 않습니다. packages/patient-risk-go-core/go.mod는 외부에서 받을 수 있는 canonical module path를 선언하고, release는 module path에 맞춘 semver Git tag로 publish합니다. 이 샘플처럼 module이 repository 하위 디렉터리에 있으면 tag도 source-repo/packages/patient-risk-go-core/v0.1.0처럼 module directory prefix를 포함합니다. Nexus Go proxy는 GOPROXY를 통해 해당 module을 다운로드하고 cache합니다.

git tag source-repo/packages/patient-risk-go-core/v0.1.0
git push origin source-repo/packages/patient-risk-go-core/v0.1.0

GOPROXY=https://nexus.internal.tirosh.ai/repository/go/ \
go list -m github.com/tirosh-chain/tirosh-dev-guide/source-repo/packages/patient-risk-go-core@v0.1.0

3. Package Tests

go test ./packages/patient-risk-go-core/... -run '^TestUnit'
go test ./packages/patient-risk-go-core/... -run '^TestIntegration'
go run ./apps/risk-monitor-operator manifests/risk-monitor.demo.json
go install sigs.k8s.io/controller-runtime/tools/setup-envtest@release-0.20
setup_envtest="$(go env GOPATH)/bin/setup-envtest"
export KUBEBUILDER_ASSETS="$("$setup_envtest" use -p path --bin-dir "$PWD/.envtest" '1.32.x!')"
go test ./apps/risk-monitor-operator/... -run Envtest