์ด์ ํ๊ฒฝ๊ณผ ์ ์ฌํ ๋ก์ปฌ ์ธํ๋ผ๋ฅผ ๋น ๋ฅด๊ฒ ๊ตฌ์ฑํ๊ธฐ ์ํ Docker Compose ๋ชจ์์ ๋๋ค. ๊ฐ ๋๋ ํฐ๋ฆฌ๋ ๋ ๋ฆฝ ์คํ์ด ๊ฐ๋ฅํ๋ฉฐ, ๊ณต์ฉ Docker ์ธ๋ถ ๋คํธ์ํฌ๋ฅผ ํตํด ์๋ก ํต์ ํฉ๋๋ค.
| ๋๋ ํฐ๋ฆฌ | ๊ตฌ์ฑ | ์ฉ๋ |
|---|---|---|
postgres/ |
PostgreSQL Primary/Replica + ํ์ฅ | ๊ด๊ณํ DB, ๋ณต์ ํ ์คํธ |
redis/ |
Redis 3๋ ธ๋ ํด๋ฌ์คํฐ | ์บ์/์ธ์ , ํด๋ฌ์คํฐ ํ ์คํธ |
kafka/ |
Kafka KRaft 3๋ธ๋ก์ปค + Kafka UI | ์ด๋ฒคํธ ์คํธ๋ฆฌ๋ฐ |
otel/ |
OTel Collector + OpenSearch + Data Prepper + Prometheus + Alertmanager + Pyroscope | ๊ด์ธก์ฑ(Trace/Log/Metric/Profile) |
- Docker Engine / Docker Compose v2
- OpenSearch ์คํ์ ์ํ ์ปค๋ ์ค์
sudo sysctl -w vm.max_map_count=262144docker network create <shared_network_name>๊ธฐ๋ณธ ๋คํธ์ํฌ ์ด๋ฆ์ ๊ฐ compose ํ์ผ์์ ํ์ธ/๋ณ๊ฒฝ ๊ฐ๋ฅํฉ๋๋ค.
.env๋ git์์ ์ ์ธ๋ฉ๋๋ค. ๊ฐ ๋๋ ํฐ๋ฆฌ์์ .env.example์ ๋ณต์ฌํด .env๋ฅผ ์์ฑํ์ธ์.
cp postgres/.env.example postgres/.env
cp redis/.env.example redis/.env
cp kafka/.env.example kafka/.env
cp otel/.env.example otel/.envcd postgres && docker compose up -d
cd ../redis && docker compose up -d
cd ../kafka && docker compose up -d
cd ../otel && docker compose up -d์๋ ์์๋ ํํ๋ง ์ ๊ณตํฉ๋๋ค. ์ค์ ๊ฐ์ ํ ์ ์ฑ ์ ๋ง๊ฒ ์ ๋ ฅํ์ธ์.
POSTGRES_USER=<postgres_user>
POSTGRES_PASSWORD=<postgres_password>
POSTGRES_DB=<postgres_database>
REPLICATION_USER=<replication_user>
REPLICATION_PASSWORD=<replication_password>REDIS_PASSWORD=<redis_password>CLUSTER_ID=<kafka_kraft_cluster_id>
KAFKA_CLUSTERS_0_NAME=<kafka_ui_cluster_display_name>
KAFKA_CLIENT_USERNAME=<kafka_client_username>
KAFKA_CLIENT_PASSWORD=<kafka_client_password>OPENSEARCH_PASSWORD=<opensearch_admin_password>
OTEL_CLUSTER_NAME=<opensearch_cluster_name>
OPENSEARCH_DASHBOARDS_USERNAME=<dashboards_system_username>
OPENSEARCH_DASHBOARDS_PASSWORD=<dashboards_system_password>
POSTGRES_USER=<postgres_user>
POSTGRES_PASSWORD=<postgres_password>
POSTGRES_DB=<postgres_database>
REDIS_PASSWORD=<redis_password>PostgreSQL Primary/Replica ๊ตฌ์ฑ์ผ๋ก ์คํธ๋ฆฌ๋ฐ ๋ณต์ ๋ฅผ ํ ์คํธํ ์ ์์ต๋๋ค.
| ์ปจํ ์ด๋ | ํธ์คํธ ํฌํธ | ๋น๊ณ |
|---|---|---|
postgres-primary |
<postgres_primary_port> |
์ฝ๊ธฐ/์ฐ๊ธฐ |
postgres-replica |
<postgres_replica_port> |
์ฝ๊ธฐ ์ ์ฉ |
env_file๋ก ์๊ฒฉ์ ๋ณด ์ฃผ์replica-entrypoint.sh์์pg_basebackup๊ธฐ๋ฐ ์ด๊ธฐ ๋๊ธฐํinit/00-replication.sh๋ก replication user/slot ์์ฑinit/01-extensions.sql๋ก ํ์ฅ ์๋ ์ค์น
pg_trgmpostgis,postgis_topologypgauditpg_stat_statementsvector
- Primary:
postgresql://<user>:<password>@<host>:<port>/<database> - Replica:
postgresql://<user>:<password>@<host>:<port>/<database>
Redis 3๋ ธ๋ ํด๋ฌ์คํฐ ๊ตฌ์ฑ์ ๋๋ค.
| ์ปจํ ์ด๋ | ํธ์คํธ ํฌํธ | ๋น๊ณ |
|---|---|---|
redis-node-1 |
<redis_node1_port> |
ํด๋ฌ์คํฐ ๋ ธ๋ |
redis-node-2 |
<redis_node2_port> |
ํด๋ฌ์คํฐ ๋ ธ๋ |
redis-node-3 |
<redis_node3_port> |
ํด๋ฌ์คํฐ ๋ ธ๋ |
redis-cluster-init |
- | 1ํ์ฑ ํด๋ฌ์คํฐ ๋ถํธ์คํธ๋ฉ |
- ์ธ์ฆ์
--requirepass,--masterauth๋ก.env์์ ์ฃผ์ redis.conf๋ ์ฑ๋ฅ/์์์ฑ/ํด๋ฌ์คํฐ ์ค์ ์ค์ฌredis-cluster-init๊ฐ ์ํ ํ์ธ ํ ํ์ ์์๋ง--cluster create์คํ
- ๋จ์ผ ๋
ธ๋:
redis://:<password>@<host>:<port> - ํด๋ฌ์คํฐ ํด๋ผ์ด์ธํธ:
redis://:<password>@<host1>:<port1>,<host2>:<port2>,<host3>:<port3>
Kafka KRaft 3๋ธ๋ก์ปค ๊ตฌ์ฑ์ ๋๋ค (ZooKeeper ์์).
| ๋ฆฌ์ค๋ | ์ฉ๋ | ๋ณด์ |
|---|---|---|
PLAINTEXT |
๋ธ๋ก์ปค ๋ด๋ถ ํต์ | PLAINTEXT |
CONTROLLER |
KRaft ์ปจํธ๋กค๋ฌ ํต์ | PLAINTEXT |
EXTERNAL |
ํธ์คํธ/์ธ๋ถ ํด๋ผ์ด์ธํธ ์ ์ | SASL_PLAINTEXT + PLAIN |
| ์ปจํ ์ด๋ | ์ธ๋ถ ์ ์ ์ฃผ์ ํํ |
|---|---|
kafka-1 |
<host>:<kafka_broker1_external_port> |
kafka-2 |
<host>:<kafka_broker2_external_port> |
kafka-3 |
<host>:<kafka_broker3_external_port> |
kafka-ui |
http://<host>:<kafka_ui_port> |
bootstrap.servers=<host>:<port>,<host>:<port>,<host>:<port>
security.protocol=SASL_PLAINTEXT
sasl.mechanism=PLAIN
sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="<username>" password="<password>";CLUSTER_ID๋ KRaft ๋ด๋ถ ์๋ณ์์ด๊ณ , KAFKA_CLUSTERS_0_NAME์ Kafka UI ํ์๋ช
์
๋๋ค.
์ ํ๋ฆฌ์ผ์ด์ ์ OTLP๋ก๋ง ์ ์กํ๊ณ , Collector๊ฐ ์ ํธ๋ณ ์ ์ฅ์๋ก ๋ผ์ฐํ ํฉ๋๋ค.
Application (OTLP gRPC/HTTP)
-> OTel Collector
-> Traces -> Data Prepper -> OpenSearch
-> Logs -> OpenSearch
-> Metrics -> Prometheus
-> Profiles -> Pyroscope
| ๋ก์ปฌ ๊ตฌ์ฑ | AWS ๋์ ์๋น์ค |
|---|---|
| OpenSearch | Amazon OpenSearch Service |
| OpenSearch Dashboards | OpenSearch Dashboards |
| Data Prepper | Amazon OpenSearch Ingestion |
| Prometheus | Amazon Managed Prometheus |
| OTel Collector | ADOT |
| Pyroscope | Pyroscope (self-managed) |
| ์๋น์ค | ์ด๋ฏธ์ง | ํฌํธ |
|---|---|---|
opensearch |
opensearch-nori:<version> |
<opensearch_http_port>, <opensearch_transport_port> |
opensearch-dashboards |
opensearchproject/opensearch-dashboards:<version> |
<dashboards_port> |
otel-collector |
otel/opentelemetry-collector-contrib:<version> |
<otlp_grpc_port>, <otlp_http_port>, <collector_metrics_port> |
data-prepper |
opensearchproject/data-prepper:<version> |
<data_prepper_otel_trace_port>, <data_prepper_health_port> |
prometheus |
prom/prometheus:<version> |
<prometheus_port> |
alertmanager |
prom/alertmanager:<version> |
<alertmanager_port> |
pyroscope |
grafana/pyroscope:<version> |
<pyroscope_port> |
postgres-exporter |
prometheuscommunity/postgres-exporter:<version> |
<postgres_exporter_port> |
redis-exporter |
oliver006/redis_exporter:<version> |
<redis_exporter_port> |
| ํ์ผ | ์ค๋ช |
|---|---|
otel/docker-compose.yaml |
OTel ์คํ ์ค์ผ์คํธ๋ ์ด์ |
otel/Dockerfile |
OpenSearch ์ปค์คํ ์ด๋ฏธ์ง ๋น๋ |
otel/otel-collector.yaml |
Collector ํ์ดํ๋ผ์ธ (์์ง/์ฒ๋ฆฌ/๋ด๋ณด๋ด๊ธฐ) |
otel/data-prepper-pipelines.yaml |
Trace ํ์ดํ๋ผ์ธ ์ ์ |
otel/data-prepper-config.yaml |
Data Prepper ๋ฐํ์ ์ค์ |
otel/data-prepper-entrypoint.sh |
Data Prepper ํ ํ๋ฆฟ ๋ณ์ ์นํ |
otel/opensearch_dashboards.yml |
Dashboards ์ค์ |
otel/prometheus.yml |
Prometheus scrape/alert ์ค์ |
otel/alertmanager.yml |
Alertmanager ๋ผ์ฐํ ์ค์ |
otel/rules/alerts.yml |
์๋ฆผ ๊ท์น |
| ์๋น์ค | URI ํํ |
|---|---|
| OpenSearch Dashboards | http://<host>:<dashboards_port> |
| OpenSearch | http://<host>:<opensearch_port> |
| Prometheus | http://<host>:<prometheus_port> |
| Alertmanager | http://<host>:<alertmanager_port> |
| Pyroscope | http://<host>:<pyroscope_port> |
| OTel Collector gRPC | <host>:<otlp_grpc_port> |
| OTel Collector HTTP | http://<host>:<otlp_http_port> |
| OTel Collector Metrics | http://<host>:<collector_metrics_port> |
export OTEL_EXPORTER_OTLP_ENDPOINT=<otlp_endpoint_uri>
export OTEL_EXPORTER_OTLP_PROTOCOL=<grpc_or_http/protobuf>
export OTEL_RESOURCE_ATTRIBUTES="service.name=<service_name>,deployment.environment=<environment>"otel์คํ ๊ธฐ๋ ํ OpenSearch health ํ์ธ- ์๋น์ค๋งต ์ธ๋ฑ์ค ํ ํ๋ฆฟ ์์ฑ
- ์ ํ๋ฆฌ์ผ์ด์ ํธ๋ํฝ ๋ฐ์
- Dashboards Workspace/Data Source/Dataset ์ฐ๊ฒฐ
curl -s -u '<opensearch_admin_user>:<opensearch_admin_password>' \
-X PUT 'http://<host>:<opensearch_port>/_index_template/otel-v2-apm-service-map-template' \
-H 'Content-Type: application/json' \
-d '{
"index_patterns": ["otel-v2-apm-service-map*"],
"priority": <priority_number>,
"template": {
"mappings": {
"properties": {
"sourceNode": { "properties": { "type": { "type": "keyword" }, "keyAttributes": { "properties": { "name": { "type": "keyword" }, "environment": { "type": "keyword" } } }, "groupByAttributes": { "type": "object" } } },
"targetNode": { "properties": { "type": { "type": "keyword" }, "keyAttributes": { "properties": { "name": { "type": "keyword" }, "environment": { "type": "keyword" } } }, "groupByAttributes": { "type": "object" } } },
"sourceOperation": { "properties": { "name": { "type": "keyword" }, "attributes": { "type": "object" } } },
"targetOperation": { "properties": { "name": { "type": "keyword" }, "attributes": { "type": "object" } } },
"nodeConnectionHash": { "type": "keyword" },
"operationConnectionHash": { "type": "keyword" },
"timestamp": { "type": "date" }
}
}
}
}'๋จ์ผ ์๋น์ค ํ๊ฒฝ์์๋ ์ด ํ ํ๋ฆฟ์ด ์์ผ๋ฉด ์๋น์ค๋งต์์
targetNode๊ด๋ จ ์ค๋ฅ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
http://<host>:<dashboards_port>์ ์ ํ ๋ก๊ทธ์ธ- Workspace ์์ฑ
- Dashboards Management์์ Data Source ์์ฑ
- Workspace์ Data Source ์ฐ๊ฒฐ
- Discover์์ Logs/Traces Dataset ์์ฑ
Data Source ๋ฑ๋ก ์ ๊ฐ ํํ:
- Title:
<data_source_name> - Endpoint URL:
http://<opensearch_container_name>:<opensearch_http_port> - Authentication: Username/Password
Traces Dataset ์์ฑ ์:
- Index:
<traces_index_pattern> - Time field:
startTime(์ผ๋ถ ํ๊ฒฝ์์@timestamp์ด์ ํํผ)
Logs Dataset ์์ฑ ์:
- Index:
<logs_index_pattern> - Time field:
@timestamp๋๋observedTimestamp
# Collector ์์ /์ก์ ํ์ธ
curl -s http://<host>:<collector_metrics_port>/metrics | rg 'receiver_accepted|exporter_sent'
# OpenSearch ์ธ๋ฑ์ค ์์ฑ ํ์ธ
curl -s -u '<opensearch_admin_user>:<opensearch_admin_password>' \
'http://<host>:<opensearch_port>/_cat/indices?v'์์ ์ธ๋ฑ์ค ํํ:
ss4o_logs-*otel-v1-apm-span-*otel-v2-apm-service-map*
- ๊ท์น ํ์ผ:
otel/rules/alerts.yml - ๋ผ์ฐํ
ํ์ผ:
otel/alertmanager.yml receivers.default.webhook_configs.url์ ์ค์ webhook URI๋ก ๊ต์ฒด ํ ์ฌ์ฉ
Webhook URI ํํ ์์:
https://<webhook_host>/<path>
| ํญ๋ชฉ | ์ํฅ | ์ฐํ/๋์ |
|---|---|---|
@timestamp๊ฐ ๊ธฐ๋์ ๋ค๋ฅด๊ฒ ์ ์ฅ๋จ |
Traces ์กฐํ ์ ์๊ฐ์ถ ๋ฌธ์ ๊ฐ๋ฅ | Traces Dataset์ time field๋ฅผ startTime์ผ๋ก ์ง์ |
durationInNanos ์คํค๋ง ๋ถ์ผ์น |
์ผ๋ถ Trace ํจ๋ ์๋ฌ ๊ฐ๋ฅ | Spans/๊ธฐ๋ณธ ์กฐํ ์ค์ฌ์ผ๋ก ํ์ธ |
targetNode.keyAttributes ๋๋ฝ |
์๋น์ค๋งต ํจ๋ ์ค๋ฅ ๊ฐ๋ฅ | ์๋น์ค๋งต ์ธ๋ฑ์ค ํ ํ๋ฆฟ ์ ์์ฑ |
| Metrics UI ์ ์ฝ | Dashboards ๋ด Metrics ์ฐ๋ ์ ํ | Prometheus UI ์ง์ ์กฐํ |
| HTTP ๋นTLS | ๋ก์ปฌ ๊ฐ๋ฐ ์ธ ํ๊ฒฝ์ ๋ถ์ ํฉ | ์ด์์์๋ HTTPS/TLS ์ ์ฉ |
- OpenSearch ์ปค์คํ
๋น๋์
analysis-nori,analysis-icuํ๋ฌ๊ทธ์ธ ํฌํจ - Data Prepper๋ ํ์ดํ๋ผ์ธ ํ ํ๋ฆฟ์ ๋ํด entrypoint์์ ํ๊ฒฝ๋ณ์ ์นํ ์ํ
- Profiles๋ Collector feature gate(
service.profilesSupport) ํ์ฑ ์ ์ฌ์ฉ
network ... not found- ๊ณต์ฉ ์ธ๋ถ ๋คํธ์ํฌ๋ฅผ ๋จผ์ ์์ฑํ๋์ง ํ์ธ
- OpenSearch ๋ถํ
์คํจ
vm.max_map_count๊ฐ ํ์ธ
- Redis ํด๋ฌ์คํฐ ๋ฏธ๊ตฌ์ฑ
redis-cluster-init๋ก๊ทธ ํ์ธ (cluster_state:ok์ฌ๋ถ)
- Postgres replica ๋ฏธ๊ธฐ๋
- primary health ์ํ/๋ณต์ ๊ณ์ ์ค์ /๋ฐ์ดํฐ ๋ณผ๋ฅจ ์ด๊ธฐํ ์ฌ๋ถ ํ์ธ
- Kafka ์ธ๋ถ ์ ์ ์คํจ
EXTERNALํฌํธ,SASL_PLAINTEXT์ค์ , username/password ์ผ์น ์ฌ๋ถ ํ์ธ
- OTel ๋ฐ์ดํฐ๊ฐ UI์ ์ ๋ณด์
- Collector metrics ์ฆ๊ฐ ์ฌ๋ถ
- OpenSearch ์ธ๋ฑ์ค ์์ฑ ์ฌ๋ถ
- Dashboards Data Source/Dataset/time field ์ค์ ํ์ธ
cd <stack_directory>
docker compose downcd <stack_directory>
docker compose down -vdocker network rm <shared_network_name>