해당 글에서는 프로메테우스에 대한 내용을 학습합니다.
목표: EKS 환경에서 Promtail + Loki + Grafana 로깅을 구성하고, 백엔드 S3에 로그를 적재하도록 구성합니다.
프로메테우스는 오픈 소스 모니터링 및 경보 시스템으로, 주로 시계열 데이터를 수집, 저장, 질의하는 데 특화되어 있습니다.
주요 장점은 아래와 같습니다.
prometheus를 daemon service로 정의후 재동작시킵니다. (node에 접근 가능한 서버에서 동작)
# prometheus를 systemd 통해 데몬서비스로 구성
tee /etc/systemd/system/prometheus.service > /dev/null <<EOF
[Unit]
Description=Prometheus
Wants=network-online.target
After=network-online.target
[Service]
User=prometheus
Group=prometheus
Type=simple
ExecStart=/usr/local/bin/prometheus \
--config.file=/etc/prometheus/prometheus.yml \
--storage.tsdb.path=/var/lib/prometheus \
--web.listen-address=0.0.0.0:9090
[Install]
WantedBy=multi-user.target
EOF
# 서비스 재동작
systemctl daemon-reload
systemctl enable --now prometheus
systemctl status prometheus
ss -tnlp
확인 결과, 정상적으로 데몬서비스로 동작하고 있습니다.

node_exporter도 설치 후, 데몬서비스로 등록합니다
tee /etc/systemd/system/node_exporter.service > /dev/null <<EOF
[Unit]
Description=Node Exporter
Documentation=https://prometheus.io/docs/guides/node-exporter/
Wants=network-online.target
After=network-online.target
[Service]
User=node_exporter
Group=node_exporter
Type=simple
Restart=on-failure
ExecStart=/usr/local/bin/node_exporter \
--web.listen-address=:9200
[Install]
WantedBy=multi-user.target
EOF
prometheus 설정에 수집 대상인 target "node_exporter"를 추가합니다.
# prometheus.yml 수정
cat << EOF >> /etc/prometheus/prometheus.yml
- job_name: 'node_exporter'
static_configs:
- targets: ["127.0.0.1:9200"]
labels:
alias: 'myec2'
EOF
# prometheus 데몬 재기동
systemctl restart prometheus.service
systemctl status prometheus
prometheus를 웹에서 접근합니다.
echo -e "http://$(curl -s ipinfo.io/ip):9090"
# http://43.202.60.18:9090
node와 관련된 메트릭을 쿼리해봅니다.
# query
rate(node_cpu_seconds_total{mode="system"}[1m])
node_filesystem_avail_bytes
rate(node_network_receive_bytes_total[1m])

kube-prometheus-stack은 Prometheus Operator를 사용하여 Prometheus를 통한 간편한 종단 간 Kubernetes 클러스터 모니터링을 제공하기 위해 Kubernetes 매니페스트, Grafana 대시보드, Prometheus 규칙을 문서와 스크립트와 결합하여 수집합니다.
해당 stack은 helm chart를 통해 구성하도로 하겠습니다.
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
# 파라미터 파일 생성
cat <<EOT > monitor-values.yaml
prometheus:
prometheusSpec:
scrapeInterval: "15s"
evaluationInterval: "15s"
podMonitorSelectorNilUsesHelmValues: false
serviceMonitorSelectorNilUsesHelmValues: false
retention: 5d
retentionSize: "10GiB"
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: gp3
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 30Gi
ingress:
enabled: true
ingressClassName: alb
hosts:
- prometheus.$MyDomain
paths:
- /*
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
alb.ingress.kubernetes.io/success-codes: 200-399
alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
alb.ingress.kubernetes.io/group.name: study
alb.ingress.kubernetes.io/ssl-redirect: '443'
grafana:
defaultDashboardsTimezone: Asia/Seoul
adminPassword: prom-operator
ingress:
enabled: true
ingressClassName: alb
hosts:
- grafana.$MyDomain
paths:
- /*
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
alb.ingress.kubernetes.io/success-codes: 200-399
alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
alb.ingress.kubernetes.io/group.name: study
alb.ingress.kubernetes.io/ssl-redirect: '443'
persistence:
enabled: true
type: sts
storageClassName: "gp3"
accessModes:
- ReadWriteOnce
size: 20Gi
alertmanager:
enabled: false
defaultRules:
create: false
kubeControllerManager:
enabled: false
kubeEtcd:
enabled: false
kubeScheduler:
enabled: false
prometheus-windows-exporter:
prometheus:
monitor:
enabled: false
EOT
cat monitor-values.yaml
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack --version 69.3.1 \
-f monitor-values.yaml --create-namespace --namespace monitoring
helm list 확인 시, 정상적으로 chart 구성 완료 되었습니다.

그라파나도 접근해 보겠습니다. (ID/PW는 yaml에 설정한 파라메타 참고)
echo -e "https://grafana.$MyDomain"
OOTB로 생성되어 있는 CoreDNS Dashboard로 접근 시, chart를 설치한 시점부터, 데이터가 집계되고 있음을 확인 가능합니다.

❓잠깐! 프로메테우스, 그라파나는 어떻게 접근이 가능하게 되는 것일까요?
이유는 위에서 yaml로 정의한 value 파일의 ingress 설정 때문입니다.
⁉️ 아마존 EKS의 컨트롤 플레인(Etcd, kube-scheduler, kube-controller-manager)은 AWS에서 관리하기 때문에, 일반적으로는 해당 구성 요소의 메트릭 엔드포인트가 사용자에게 직접 노출되지 않는다는 사실을 참고하시기 바랍니다.
Grafana Helm Chart 등록 및 업데이트
helm repo add grafana <https://grafana.github.io/helm-charts>
helm repo update
Grafana Helm values.yaml 작성
persistence:
enabled: true
size: 10Gi
storageClassName: gp2
type: pvc
rbac:
namespaced: true
pspEnabled: false
replicas: 1
adminPassword: admin
adminUser: admin
Grafana 설치
helm install grafana -n monitoring grafana/grafana -f grafana.yaml
IRSA를 통해 Loki가 S3 버킷의 권한을 얻도록 합니다.
IAM Policy를 생성합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "LokiStorage",
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::{bucket_name}",
"arn:aws:s3:::{bucket_name}/*"
]
}
]
}
IAM Role 생성 후, 이전에 생성한 Policy를 Role에 등록합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::{account_id}:oidc-provider/oidc.eks.{region}.amazonaws.com/id/{oidc}"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.{region}.amazonaws.com/id/{oidc}:sub": "system:serviceaccount:{namespace}:loki",
"oidc.eks.{region}.amazonaws.com/id/{oidc}:aud": "sts.amazonaws.com"
}
}
},
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::{account}:oidc-provider/oidc.eks.{region}.amazonaws.com/id/{oidc}"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.{region}.amazonaws.com/id/{oidc}:sub": "system:serviceaccount:{namespace}:loki-compactor",
"oidc.eks.{region}.amazonaws.com/id/{oidc}:aud": "sts.amazonaws.com"
}
}
}
]
}
Loki Helm values.yaml을 작성합니다.
Loki는 Promtail에서 수집된 로그를 보관하는 도구입니다.
loki:
server:
http_listen_port: 3100
grpc_server_max_recv_msg_size: 8938360
grpc_server_max_send_msg_size: 8938360
structuredConfig:
auth_enabled: false
compactor:
apply_retention_interval: 1h
compaction_interval: 10m
retention_delete_delay: 2h
retention_delete_worker_count: 150
retention_enabled: true
shared_store: s3
working_directory: /var/loki/compactor
limits_config:
max_global_streams_per_user: 100000
max_streams_per_user: 100000
reject_old_samples: false
retention_period: 30d
per_stream_rate_limit: 3MB
per_stream_rate_limit_burst: 10MB
max_query_parallelism: 90
ingestion_rate_mb: 512
ingestion_burst_size_mb: 1024
ingester:
max_transfer_retries: 0
chunk_idle_period: 10m ## chunk 파일의 업로드 주기
chunk_target_size: 1572864
max_chunk_age: 2h
chunk_encoding: snappy
lifecycler:
ring:
kvstore:
store: memberlist
replication_factor: 3
heartbeat_timeout: 10m
wal:
dir: /var/loki/wal
replay_memory_ceiling: 800mb
storage_config:
aws:
region: {region}
bucketnames: {bucket_name}
s3forcepathstyle: false
insecure: false
tsdb_shipper:
shared_store: s3
active_index_directory: /var/loki/tsdb-index
cache_location: /var/loki/tsdb-cache
index_queries_cache_config:
memcached:
batch_size: 100
parallelism: 100
schema_config:
configs:
- from: 2023-10-31
store: tsdb
object_store: s3
schema: v12
index:
prefix: loki_index_
period: 24h
chunk_store_config:
max_look_back_period: 48h
chunk_cache_config:
memcached:
batch_size: 100
parallelism: 100
write_dedupe_cache_config:
memcached:
batch_size: 100
parallelism: 100
querier:
max_concurrent: 16
query_scheduler:
max_outstanding_requests_per_tenant: 32768
serviceAccount:
create: true
name: "loki"
annotations:
"eks.amazonaws.com/role-arn": "{IAM Role ARN}"
automountServiceAccountToken: true
ingester:
replicas: 3
maxUnavailable: 1
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 1Gi
persistence:
enabled: true
claims:
- name: data
size: 10Gi
storageClass: gp2
distributor:
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 256Mi
querier:
resources:
requests:
cpu: 100m
memory: 512Mi
limits:
memory: 512Mi
queryFrontend:
resources:
requests:
cpu: 100m
memory: 512Mi
limits:
memory: 512Mi
gateway:
resources:
requests:
cpu: 100m
memory: 512Mi
limits:
memory: 512Mi
compactor:
enabled: true
serviceAccount:
create: true
name: "loki-compactor"
annotations:
"eks.amazonaws.com/role-arn": "{IAM Role ARN}"
automountServiceAccountToken: true
indexGateway:
enabled: true
memcachedChunks:
enabled: true
extraArgs:
- -I 32m
memcachedFrontend:
enabled: true
extraArgs:
- -I 32m
memcachedIndexQueries:
enabled: true
extraArgs:
- -I 32m
Loki를 설치합니다.
helm install loki -n monitoring grafana/loki-distributed -f loki.yaml
Promtail Helm values.yaml 파일을 작성합니다.
Promtail은 노드에 저장된 Pod의 로그를 수집하여 Grafana Loki로 전송하는 도구입니다.
URL 항목에는 Loki 구성 시, Gateway가 구성되며 해당 사항의 K8S DNS 주소를 기재하면 됩니다.
config:
clients:
- url: <http://loki-loki-distributed-gateway/loki/api/v1/push>
Connection URL에 DNS 주소 등록 후, Save & test 클릭합니다.

정상적으로 등록된 모습입니다.

원하는 쿼리를 통해 로그를 확인할 수 있습니다.

fake 디렉토리가 생성되며, 해당 디렉토리 안에 chunk 로그 파일이 설정된 10분 주기로 적재되고 있습니다.


Mimir는 Prometheus, Opentelemetry등의 도구로 데이터를 전달 받고, 받은 데이터를 샤딩 및 복제하여 보관하는 도구입니다.
아래 파일 내용 중, ServiceAccount, Role ARN, bucket_name, endpoint, region만 변경하면 되며, 경우에 따라 global.dnsService의 DNS 명을 수정해야할 수 도 있습니다.
serviceAccount:
create: true
name: "mimir-sa"
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::{account_id}:role/minjun-mimir-role
mimir:
structuredConfig:
usage_stats:
enabled: false
installation_mode: helm
blocks_storage:
backend: s3
bucket_store:
sync_dir: /data/tsdb-sync
s3:
bucket_name: minjun-mimir ##
endpoint: s3.ap-northeast-1.amazonaws.com ##
region: ap-northeast-1 ##
insecure: false
tsdb:
dir: /data/tsdb
compactor:
data_dir: /data
multitenancy_enabled: false
frontend:
align_queries_with_step: true
log_queries_longer_than: 10s
ingester:
instance_limits:
max_ingestion_rate: 0
ring:
final_sleep: 0s
num_tokens: 512
ingester_client:
grpc_client_config:
max_recv_msg_size: 104857600
max_send_msg_size: 104857600
server:
log_level: debug
grpc_server_max_concurrent_streams: 1000
grpc_server_max_recv_msg_size: 104857600
grpc_server_max_send_msg_size: 104857600
limits:
ingestion_rate: 80000
max_global_series_per_metric: 0
max_global_series_per_user: 0
max_label_names_per_series: 100
memberlist:
abort_if_cluster_join_fails: false
compression_enabled: false
runtime_config:
file: /var/{{ include "mimir.name" . }}/runtime.yaml
minio:
enabled: false
querier:
replicas: 2
alertmanager:
enabled: false
ruler:
enabled: false
ingester:
zoneAwareReplication:
enabled: false
persistentVolume:
enabled: false
global:
dnsService: kube-dns
dnsNamespace: kube-system
clusterDomain: cluster.local.
store_gateway:
zoneAwareReplication:
enabled: false
persistentVolume:
enabled: false
compactor:
persistentVolume:
enabled: false
nginx:
enabled: true
replicas: 1
nginxConfig:
# @default -- See values.yaml
file: |
worker_processes 5; ## Default: 1
error_log /dev/stderr {{ .Values.nginx.nginxConfig.errorLogLevel }};
pid /tmp/nginx.pid;
worker_rlimit_nofile 8192;
events {
worker_connections 4096; ## Default: 1024
}
http {
client_body_temp_path /tmp/client_temp;
proxy_temp_path /tmp/proxy_temp_path;
fastcgi_temp_path /tmp/fastcgi_temp;
uwsgi_temp_path /tmp/uwsgi_temp;
scgi_temp_path /tmp/scgi_temp;
...
Connection URL에 DNS 주소 등록 후, Save & test 클릭합니다.

원하는 쿼리를 통해 메트릭을 확인할 수 있습니다.

anonymous 디렉토리가 생성되며, 약 2시간 후에 해당 디렉토리 안에 data 파일이 적재됩니다.

