Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ PRD.md

### Data ###
*.csv
store_ratings.sql

### Uploads ###
uploads/
Expand Down
13 changes: 10 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,22 @@ RUN apt-get update \
&& apt-get install -y --no-install-recommends wget \
&& rm -rf /var/lib/apt/lists/*

# OpenTelemetry Java Agent โ€” ์ฝ”๋“œ ์ˆ˜์ • ์—†์ด ํŠธ๋ ˆ์ด์Šค ์ž๋™ ์ˆ˜์ง‘
# Spring MVC, JDBC, HikariCP, HTTP Client ๋“ฑ ์ž๋™ ๊ณ„์ธก
ARG OTEL_AGENT_VERSION=2.10.0
RUN wget -qO /opentelemetry-javaagent.jar \
https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v${OTEL_AGENT_VERSION}/opentelemetry-javaagent.jar \
&& chmod 644 /opentelemetry-javaagent.jar

# ๋น„๋ฃจํŠธ ์œ ์ €๋กœ ์‹คํ–‰
RUN useradd -r -u 1001 spring
USER spring

COPY --from=builder /app/build/libs/*.jar app.jar

# t3.micro(1GB RAM) ํ™˜๊ฒฝ ๊ณ ๋ คํ•œ JVM ๋ฉ”๋ชจ๋ฆฌ ํŠœ๋‹
# ์ปจํ…Œ์ด๋„ˆ์— ํ• ๋‹น๋œ ๋ฉ”๋ชจ๋ฆฌ์˜ 70%๋ฅผ ํž™์œผ๋กœ ์‚ฌ์šฉ (๋‚˜๋จธ์ง€ 30%๋Š” metaspace, ์Šคํƒ, ๋„ค์ดํ‹ฐ๋ธŒ)
ENV JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=70.0 -XX:InitialRAMPercentage=50.0"
# t3.small ํ™˜๊ฒฝ ๊ณ ๋ คํ•œ JVM ๋ฉ”๋ชจ๋ฆฌ ํŠœ๋‹ + OTel Agent ์ž๋™ ํ™œ์„ฑํ™”
# OTEL_EXPORTER_OTLP_ENDPOINT๊ฐ€ ์„ค์ • ์•ˆ ๋˜๋ฉด ํŠธ๋ ˆ์ด์Šค ์ „์†ก๋งŒ ์‹คํŒจํ•  ๋ฟ ์•ฑ์€ ์ •์ƒ ๋™์ž‘
ENV JAVA_TOOL_OPTIONS="-javaagent:/opentelemetry-javaagent.jar -XX:MaxRAMPercentage=70.0 -XX:InitialRAMPercentage=50.0 -XX:+UseG1GC"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While -XX:+UseG1GC is the default in Java 21, explicitly setting it for a container limited to 600MB (as seen in docker-compose.prod.yml) might not be optimal. G1GC is generally more effective for larger heaps. For a heap size around 400MB (70% of 600MB), you might consider testing SerialGC or ParallelGC to see if they provide better performance with lower overhead in this resource-constrained environment.


EXPOSE 8080

Expand Down
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ dependencies {
implementation 'org.springframework.ai:spring-ai-starter-model-google-genai'
implementation 'org.jetbrains.kotlin:kotlin-reflect'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
runtimeOnly 'io.micrometer:micrometer-registry-prometheus'
implementation 'io.github.cdimascio:dotenv-java:3.0.0'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
Expand Down
198 changes: 195 additions & 3 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,27 @@ services:
env_file: .env
expose:
- "8080"
# t3.micro(1GB) ๋ฉ”๋ชจ๋ฆฌ ํ•œ๊ณ„ ๊ณ ๋ ค: app์€ ์ตœ๋Œ€ 700MB๋กœ ์ œํ•œ
# t3.small(2GB) ํ™˜๊ฒฝ. ๋ชจ๋‹ˆํ„ฐ๋ง ์Šคํƒ๊ณผ ๊ณต์กดํ•˜๊ธฐ ์œ„ํ•ด app์€ 600M๋กœ ์ œํ•œ.
# OTel Agent๊ฐ€ ์•ฝ 80MB ์ถ”๊ฐ€ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ 500M โ†’ 600M๋กœ ์ƒํ–ฅ.
environment:
# JVM ์˜ต์…˜์€ Dockerfile์˜ ENV์— ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์ •์˜๋จ (javaagent + ๋ฉ”๋ชจ๋ฆฌ ํŠœ๋‹).
# ์—ฌ๊ธฐ์„  OpenTelemetry Agent์˜ ์†ก์‹  ๋Œ€์ƒ๋งŒ ์ง€์ •.
OTEL_SERVICE_NAME: catchtable-backend
OTEL_EXPORTER_OTLP_ENDPOINT: http://jaeger:4317
OTEL_EXPORTER_OTLP_PROTOCOL: grpc
OTEL_TRACES_EXPORTER: otlp
OTEL_METRICS_EXPORTER: none # ๋ฉ”ํŠธ๋ฆญ์€ Micrometer/Prometheus๋กœ ์ฒ˜๋ฆฌ
OTEL_LOGS_EXPORTER: none # ๋กœ๊ทธ๋Š” Promtail/Loki๋กœ ์ฒ˜๋ฆฌ
OTEL_RESOURCE_ATTRIBUTES: deployment.environment=production
# ์ƒ˜ํ”Œ๋ง: ์ „์ฒด ์š”์ฒญ ๋Œ€๋น„ ํŠธ๋ ˆ์ด์Šค ์ˆ˜์ง‘ ๋น„์œจ (1.0=100%, ๋ถ€ํ•˜ ์‹œ 0.1๋กœ ๋‚ฎ์ถ”๊ธฐ ๊ถŒ์žฅ)
OTEL_TRACES_SAMPLER: parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG: "1.0"
deploy:
resources:
limits:
memory: 700M
memory: 600M
reservations:
memory: 500M
memory: 400M
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8080/actuator/health"]
interval: 30s
Expand All @@ -23,6 +37,11 @@ services:
restart: unless-stopped
networks:
- catchtable-net
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"

nginx:
image: nginx:1.26.3-alpine
Expand Down Expand Up @@ -50,6 +69,179 @@ services:
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew --quiet; sleep 12h & wait $${!}; done;'"
restart: unless-stopped

# ============================================================
# ๋ชจ๋‹ˆํ„ฐ๋ง ์Šคํƒ (Prometheus + Loki + Promtail + Jaeger + Grafana)
# ============================================================

prometheus:
image: prom/prometheus:v2.55.0
container_name: catchtable-prometheus
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.retention.time=7d'
- '--storage.tsdb.retention.size=1GB'
- '--web.enable-lifecycle'
expose:
- "9090"
deploy:
resources:
limits:
memory: 350M
restart: unless-stopped
networks:
- catchtable-net

loki:
image: grafana/loki:3.2.0
container_name: catchtable-loki
volumes:
- ./monitoring/loki-config.yml:/etc/loki/local-config.yaml:ro
- loki-data:/loki
command: -config.file=/etc/loki/local-config.yaml
expose:
- "3100"
deploy:
resources:
limits:
memory: 250M
restart: unless-stopped
networks:
- catchtable-net

promtail:
image: grafana/promtail:3.2.0
container_name: catchtable-promtail
volumes:
- ./monitoring/promtail-config.yml:/etc/promtail/config.yml:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- promtail-positions:/tmp
command: -config.file=/etc/promtail/config.yml
depends_on:
- loki
deploy:
resources:
limits:
memory: 100M
restart: unless-stopped
networks:
- catchtable-net

jaeger:
image: jaegertracing/all-in-one:1.62.0
container_name: catchtable-jaeger
user: "0" # named volume mkdir ํผ๋ฏธ์…˜ ํšŒํ”ผ (๋‚ด๋ถ€ ๋„๊ตฌ๋ผ ๋ณด์•ˆ ์˜ํ–ฅ ์—†์Œ)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Running the Jaeger container as root (user: "0") is a security risk, even for internal tools. It is better to ensure the host directory has the correct permissions for the container's default user or to specify a non-root UID that matches the host's directory owner.

environment:
COLLECTOR_OTLP_ENABLED: "true"
SPAN_STORAGE_TYPE: badger
BADGER_EPHEMERAL: "false"
BADGER_DIRECTORY_VALUE: /badger/data
BADGER_DIRECTORY_KEY: /badger/key
BADGER_SPAN_STORE_TTL: 168h0m0s
volumes:
- jaeger-data:/badger
ports:
- "16686:16686" # Jaeger UI (์™ธ๋ถ€ ์ ‘๊ทผ)
expose:
- "4317" # OTLP gRPC (์•ฑ์—์„œ ์ „์†ก)
- "4318" # OTLP HTTP
deploy:
resources:
limits:
memory: 350M
restart: unless-stopped
networks:
- catchtable-net

grafana:
image: grafana/grafana:11.3.0
container_name: catchtable-grafana
environment:
GF_SECURITY_ADMIN_USER: ${GRAFANA_USER:-admin}
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD:-admin}
GF_USERS_ALLOW_SIGN_UP: "false"
volumes:
- ./grafana/provisioning:/etc/grafana/provisioning:ro
- grafana-data:/var/lib/grafana
ports:
- "3001:3000" # Grafana UI (์™ธ๋ถ€ ์ ‘๊ทผ, 3000์€ ํ”„๋ก ํŠธ dev์™€ ์ถฉ๋Œ ํšŒํ”ผ)
depends_on:
- prometheus
- loki
- jaeger
deploy:
resources:
limits:
memory: 200M
restart: unless-stopped
networks:
- catchtable-net

# ============================================================
# ๊ณ ๋„ํ™” ์ž‘์—… ์‹œ์ž‘ ์‹œ ์ฃผ์„ ํ•ด์ œ (5๋Œ€ ๊ณ ๋„ํ™” ํ•ญ๋ชฉ 1, 3๋ฒˆ ์ง„ํ–‰ ์‹œ)
# ============================================================

# redis:
# image: redis:7-alpine
# container_name: catchtable-redis
# command:
# - redis-server
# - --maxmemory
# - 80mb
# - --maxmemory-policy
# - allkeys-lru
# - --appendonly
# - "yes"
# expose:
# - "6379"
# volumes:
# - redis-data:/data
# deploy:
# resources:
# limits:
# memory: 100M
# restart: unless-stopped
# networks:
# - catchtable-net

# kafka:
# image: bitnami/kafka:3.8
# container_name: catchtable-kafka
# environment:
# # KRaft ๋ชจ๋“œ (Zookeeper ๋ถˆํ•„์š”, ๋ฉ”๋ชจ๋ฆฌ ์ ˆ์•ฝ)
# KAFKA_CFG_NODE_ID: 0
# KAFKA_CFG_PROCESS_ROLES: controller,broker
# KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 0@kafka:9093
# KAFKA_CFG_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
# KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
# KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER
# KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
# # ๋ฉ”๋ชจ๋ฆฌ ๋‹ค์ด์–ดํŠธ
# KAFKA_HEAP_OPTS: "-Xms256m -Xmx256m"
# expose:
# - "9092"
# volumes:
# - kafka-data:/bitnami/kafka
# deploy:
# resources:
# limits:
# memory: 400M
# restart: unless-stopped
# networks:
# - catchtable-net

networks:
catchtable-net:
driver: bridge

volumes:
prometheus-data:
loki-data:
jaeger-data:
grafana-data:
promtail-positions:
# redis-data:
# kafka-data:
4 changes: 4 additions & 0 deletions grafana/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Grafana ๋Ÿฐํƒ€์ž„ ๋ฐ์ดํ„ฐ (์ปค๋ฐ‹ ๊ธˆ์ง€)
data/
plugins/
*.db
13 changes: 13 additions & 0 deletions grafana/provisioning/dashboards/dashboards.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: 1

providers:
- name: 'default'
orgId: 1
folder: ''
type: file
disableDeletion: false
updateIntervalSeconds: 30
allowUiUpdates: true
options:
path: /etc/grafana/provisioning/dashboards
foldersFromFilesStructure: true
31 changes: 31 additions & 0 deletions grafana/provisioning/datasources/datasources.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
apiVersion: 1

datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: true
jsonData:
timeInterval: "15s"

- name: Loki
type: loki
access: proxy
url: http://loki:3100
editable: true
jsonData:
derivedFields:
# ๋กœ๊ทธ ์•ˆ์˜ traceId(OTel) ๋ฅผ ํด๋ฆญํ•˜๋ฉด Jaeger ํŠธ๋ ˆ์ด์Šค๋กœ ์ ํ”„
- datasourceUid: jaeger
matcherRegex: "traceId=(\\w+)"
name: TraceID
url: "$${__value.raw}"

- name: Jaeger
type: jaeger
access: proxy
url: http://jaeger:16686
uid: jaeger
editable: true
48 changes: 48 additions & 0 deletions monitoring/loki-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
auth_enabled: false

server:
http_listen_port: 3100
grpc_listen_port: 9096

common:
instance_addr: 127.0.0.1
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory

schema_config:
configs:
- from: 2024-01-01
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h

limits_config:
retention_period: 168h # 7์ผ
reject_old_samples: true
reject_old_samples_max_age: 168h
allow_structured_metadata: true
volume_enabled: true

compactor:
working_directory: /loki/compactor
compaction_interval: 10m
retention_enabled: true
retention_delete_delay: 2h
retention_delete_worker_count: 150
delete_request_store: filesystem

ruler:
alertmanager_url: http://localhost:9093

analytics:
reporting_enabled: false
20 changes: 20 additions & 0 deletions monitoring/prometheus.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
global:
scrape_interval: 15s
evaluation_interval: 15s
external_labels:
cluster: catchtable
env: prod

scrape_configs:
# Spring Boot Actuator (Micrometer)
- job_name: 'catchtable-app'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['app:8080']
labels:
service: catchtable-backend

# Prometheus ์ž์ฒด ๋ฉ”ํŠธ๋ฆญ
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
Loading
Loading