Init Container에서 OTel Operator로 — 관측성 자동화의 다음 단계
관측성(Observability)은 앱의 관심사가 아니다. 인프라가 책임져야 한다.
OpenTelemetry 커뮤니티와 Grafana Labs가 공통적으로 강조하는 원칙이다. 앱 개발자는 비즈니스 로직에 집중하고, 로그/트레이스/메트릭 수집은 플랫폼이 알아서 처리해야 한다. 하지만 현실은 그렇지 않은 경우가 많다. Dockerfile에 Agent를 넣고, Init Container를 설정하고, 환경변수를 10개 넘게 직접 입력하는 작업을 앱 개발자가 하고 있다면, 관심사 분리가 안 되고 있는 것이다.
이 글에서는 Init Container / 이미지 내장 방식에서 OTel Operator 방식으로 전환해야 하는 이유와 동작 원리를 정리한다.
일반적으로 Java 앱에 OpenTelemetry를 적용하는 방식은 크게 두 가지다.
이미지 내장 방식: Dockerfile에서 Agent JAR을 복사하고, JAVA_TOOL_OPTIONS에 javaagent 경로를 설정한다. 빌드할 때마다 Agent가 포함되므로 이미지가 무거워지고, Agent 버전 관리가 앱 팀의 몫이 된다.
Init Container 방식: deployment.yaml에 Init Container를 추가하여, Pod 시작 전에 Agent JAR을 복사한다. 앱 코드 수정은 불필요하지만, 앱마다 Init Container + 환경변수 + Volume 설정이 반복된다.
둘 다 결국 앱 개발자가 관측성 설정을 직접 관리해야 한다는 점에서 같은 문제를 안고 있다.
flowchart TB
subgraph current["앱 개발자가 관리하는 것들"]
A["Dockerfile에<br/>Agent JAR 추가"] --> B["deployment.yaml에<br/>Init Container 추가"]
B --> C["환경변수<br/>10개 이상 설정"]
C --> D["Volume Mount<br/>설정"]
end
비유하자면, 아파트 건물에 새 세입자가 들어올 때마다 세입자가 직접 수도관을 사서 설치하는 것과 같다. 어떤 집은 최신 배관, 어떤 집은 낡은 배관. 고장 나면 세입자가 알아서 고쳐야 한다. 수도관은 건물 관리인이 일괄로 관리하는 게 당연한 것처럼, 관측성도 플랫폼 팀이 중앙에서 관리해야 한다.
OTel Operator는 쿠버네티스에서 관측성 자동 주입기 역할을 한다. 앱 Pod에 어노테이션 한 줄만 붙이면, Operator가 자동으로 Agent를 주입하고 환경변수를 설정한다. 앱 코드나 Dockerfile을 건드릴 필요가 없다.
flowchart LR
A["앱 Pod 생성"] -->|"어노테이션 감지"| B["OTel Operator"]
B -->|"자동 주입"| C["Agent + 환경변수 + 설정"]
C --> D["텔레메트리 자동 수집"]
비유를 이어가면, 새 세입자는 관리사무소에 “수도 연결해주세요"라고 메모(어노테이션) 한 장만 남기면 된다. 관리인이 알아서 최신 배관을 설치하고, 고장 나면 관리인이 수리한다.
| 항목 | Before (Init Container) | After (Operator) |
|---|---|---|
| 앱 코드 변경 | Init Container, 환경변수, Volume 직접 설정 | 어노테이션 1줄 |
| Dockerfile | Agent JAR 포함 (이미지 내장 시) | 변경 없음 |
| Agent 버전 관리 | 앱마다 개별 관리, 파편화 | CRD 한 곳에서 일괄 관리 |
| 새 앱 온보딩 | 30줄 이상 YAML 복붙 | 어노테이션 1줄 추가 |
| On/Off | 재빌드 또는 YAML 수정 | 어노테이션 제거하면 끝 |
코드로 보면 차이가 더 명확하다.
Before — deployment.yaml (30줄 이상 추가)
|
|
After — values.yaml (1줄 추가)
|
|
OTel Operator는 쿠버네티스의 Mutating Webhook을 사용한다. Pod 생성 요청이 API Server에 도착하면, Operator가 중간에 끼어들어 Pod 스펙을 수정(Agent 주입)한 뒤 Pod를 생성한다.
sequenceDiagram
participant Dev as 앱 개발자
participant K8s as K8s API Server
participant Op as OTel Operator
participant Pod as Pod
Dev->>K8s: Pod 생성 요청 (어노테이션 포함)
K8s->>Op: Mutating Webhook 호출
Op->>Op: 어노테이션 확인 (inject-java: true)
Op->>K8s: Pod 스펙 수정 (Agent + 환경변수 주입)
K8s->>Pod: 수정된 Pod 생성
Note over Pod: Agent가 자동으로 트레이스/메트릭 수집
Operator가 Pod 스펙을 수정할 때 실제로 하는 일은 다음과 같다:
- Init Container 추가 (Agent JAR 복사)
JAVA_TOOL_OPTIONS등 환경변수 자동 설정- Volume Mount 자동 설정
결국 Init Container 방식과 동일한 결과물이 만들어지지만, 그 설정을 사람이 아니라 Operator가 자동으로 생성한다는 것이 핵심이다.
Operator 방식에 필요한 컴포넌트는 3가지다.
flowchart TB
subgraph operator["OTel Operator 구성"]
A["cert-manager"] --> B["OTel Operator"]
B --> C["Instrumentation CRD"]
end
subgraph apps["앱들"]
D["Java 앱"]
E["Python 앱"]
F["Node.js 앱"]
end
C --> D
C --> E
C --> F
| 컴포넌트 | 역할 |
|---|---|
| cert-manager | Webhook에 필요한 TLS 인증서 자동 발급/갱신 |
| OTel Operator | Pod 생성 감시 + 어노테이션 기반 Agent 자동 주입 |
| Instrumentation CRD | Agent 이미지 버전, OTLP 엔드포인트, 전파 방식 등 설정 정의 |
Agent 버전을 업그레이드하고 싶으면 Instrumentation CRD의 이미지 태그만 변경하면 된다. 해당 CRD를 참조하는 모든 앱이 다음 재시작 시 자동으로 새 버전을 사용한다.
OTel Operator는 Java만 지원하는 것이 아니다. 주요 언어를 모두 지원한다.
| 언어 | 지원 | 어노테이션 | 방식 |
|---|---|---|---|
| Java | O | inject-java |
javaagent JAR 주입 |
| Python | O | inject-python |
Python 패키지 주입 |
| Node.js | O | inject-nodejs |
Node.js SDK 주입 |
| .NET | O | inject-dotnet |
.NET profiler 주입 |
| Go | △ | inject-go |
eBPF 기반 (실험적) |
| GraalVM Native | X | - | AOT 컴파일이라 바이트코드 조작 불가 |
Instrumentation CRD에 언어별 Agent 이미지를 정의하면 된다.
|
|
GraalVM Native Image는 AOT 컴파일 특성상 런타임 바이트코드 조작이 불가능하므로 Operator 자동 주입이 동작하지 않는다. 이 경우 빌드 시점에 OTEL SDK를 직접 의존성으로 추가해야 한다. 다만 일반 JVM 위에서 돌리는 GraalVM(JIT 모드)이면 Java와 동일하게 동작한다.
| 순위 | 방식 | 장점 | 단점 |
|---|---|---|---|
| 1순위 | OTel Operator | 앱 코드 제로, 중앙 관리, 일괄 업데이트 | 초기 Operator 설치 필요 |
| 2순위 | Init Container | Operator 없이 동작, 앱 코드 수정 불필요 | 앱별 YAML 중복, 버전 파편화 |
| 3순위 | 이미지 내장 | 단순한 구조 | 이미지 비대화, 앱 팀 관리 부담 |
1순위와 2순위의 최종 결과물(Pod 스펙)은 사실상 동일하다. 차이는 그 결과물을 사람이 만드느냐, Operator가 자동으로 만드느냐다.
이 글은 OTel Operator의 개념과 필요성을 정리한 것이다. 실제 도입 과정에서는 다음을 추가로 고려해야 한다.
- cert-manager 설치: Operator Webhook에 필요한 TLS 인증서 관리
- Alloy Service 포트 설정: Instrumentation CRD의 OTLP 엔드포인트가 실제로 열려 있는지 확인
- 기존 Init Container 제거: Operator 도입 후 기존 수동 설정과 충돌하지 않도록 정리
- 점진적 전환: 한 앱에 먼저 적용하고, 안정성 확인 후 전체 확대
이 글은 LGTM 스택 구축기의 후속편이다. LGTM으로 백엔드를 갖췄다면, 다음은 앱에서 텔레메트리를 보내는 방법을 표준화할 차례다.