AWES 4주차 - EKS Observability - Prometheus + Loki + Grafana (PLG Stack) / Mimir (2)

박민준·2025년 2월 28일

해당 글에서는 프로메테우스에 대한 내용을 학습합니다.
목표: EKS 환경에서 Promtail + Loki + Grafana 로깅을 구성하고, 백엔드 S3에 로그를 적재하도록 구성합니다.

Prometheus

프로메테우스는 오픈 소스 모니터링 및 경보 시스템으로, 주로 시계열 데이터를 수집, 저장, 질의하는 데 특화되어 있습니다.

주요 장점은 아래와 같습니다.

  • 시계열 데이터 모델
    메트릭을 시간의 변화에 따라 저장하는 것을 시계열 데이터라고 하며, label을 기반으로한 데이터 분류가 가능합니다.
  • PromQL
    질의 언어인 PromQL을 제공하여, 원하는 모니터링 쿼리를 쉽게 작성이 가능합니다.
  • 자동 서비스 디스커버리
    다양한 환경에서 자동으로 모니터링 타겟을 발견해 데이터를 수집하여, 동적인 환경에 적합합니다.
  • 경보 기능
    Alertmanager와 연계하여 경보를 관리하고, 다양한 알림 채널과 통합(Integration)이 가능합니다.

Prometheus 구성

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 설치

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])

Prometheus stack

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에서 관리하기 때문에, 일반적으로는 해당 구성 요소의 메트릭 엔드포인트가 사용자에게 직접 노출되지 않는다는 사실을 참고하시기 바랍니다.

PLG Stack

Grafana 구성

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

Loki 구성 전 IRSA 설정

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 구성

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> 

Loki를 Grafana Data Source로 추가

Connection URL에 DNS 주소 등록 후, Save & test 클릭합니다.

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

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

로그 S3 적재 확인

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

번외) Mimir 구성

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;
...

Mimir를 Grafana Data Source로 추가

Connection URL에 DNS 주소 등록 후, Save & test 클릭합니다.

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

데이터 S3 적재 확인

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

profile
바교망

0개의 댓글