Cloud

Helm 과 lgtm Stack 맛보기(Loki)

Hanhorang31 2025. 10. 26. 01:11

쿠버네티스 패키지 도구 Helm 에 대해 정리한 글을 공유합니다.

Helm 구성과 lgtm stack 배포를 통해 구성 원리를 확인하겠습니다.

 

Helm

Helm은 쿠버네티스 패키지 관리자입니다. 쿠버네티스용으로 개발된 소프트웨어를 제공, 공유 및 관리할 수 있도록 지원합니다.

Google의 쿠버네티스용 배포 관리자와 통합되어 현재 Helm의 주요 프로젝트 저장소로 잡았고, 깃허브의 3만개의 이상의 Star, 200만 건 이상의 다운로드를 기록하고 있습니다.

CNCF에서 졸업(Graduation) 단계인 프로젝트로 쿠버네티스 패키지를 손쉽게 배포하고 관리하는 표준으로 자리매김하고 있습니다.

 

Helm Chart

기본 헬름 차트 구조는 다음과 같이 구성됩니다.

mychart/
  Chart.yaml # 차트 설명 및 메타데이터 포함 
  values.yaml # 차트 설정 값 
  charts/  # 하위 차트
  templates/ # 템플릿 
  ... 

예제 차트를 구성하겠습니다.

# chart 차트 구성 
cat << EOF > Chart.yaml
apiVersion: v2
name: pacman
description: A Helm chart for Pacman
type: application
version: 0.1.0        # 차트 버전, 차트 정의가 바뀌면 업데이트한다
appVersion: "1.0.0"   # 애플리케이션 버전
EOF

# templates 템플릿 구성 
cat << EOF > templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Chart.Name}}    # Chart.yaml 파일에 설정된 이름을 가져와 설정
  labels:
    app.kubernetes.io/name: {{ .Chart.Name}}
    {{- if .Chart.AppVersion }}     # Chart.yaml 파일에 appVersion 여부에 따라 버전을 설정
    app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}     # appVersion 값을 가져와 지정하고 따움표 처리
    {{- end }}
spec:
  replicas: {{ .Values.replicaCount }}     # replicaCount 속성을 넣을 자리 placeholder
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ .Chart.Name}}
  template:
    metadata:
      labels:
        app.kubernetes.io/name: {{ .Chart.Name}}
    spec:
      containers:
        - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion}}"   # 이미지 지정 placeholder, 이미지 태그가 있으면 넣고, 없으면 Chart.yaml에 값을 설정
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          securityContext:
            {{- toYaml .Values.securityContext | nindent 14 }} # securityContext의 값을 YAML 객체로 지정하며 14칸 들여쓰기
          name: {{ .Chart.Name}}
          ports:
            - containerPort: {{ .Values.image.containerPort }}
              name: http
              protocol: TCP
EOF

## service.yaml 파일에서 템플릿화 : service 이름, 컨테이너 포트
cat << EOF > templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: {{ .Chart.Name }}
  name: {{ .Chart.Name }}
spec:
  ports:
    - name: http
      port: {{ .Values.image.containerPort }}
      targetPort: {{ .Values.image.containerPort }}
  selector:
    app.kubernetes.io/name: {{ .Chart.Name }}
EOF

# 차트 값 Value 구성
cat << EOF > values.yaml
image:     # image 절 정의
  repository: quay.io/gitops-cookbook/pacman-kikd
  tag: "1.0.0"
  pullPolicy: Always
  containerPort: 8080

replicaCount: 1
securityContext: {}     # securityContext 속성의 값을 비운다
EOF
  • toYaml 는 Value에서 설정한 YAML 값를 그대로 가져와서 Deployment 파일의 해당 위치(nindent)에 적절히 들여쓰기하여 출력해주는 역할임

로컬 차트 랜더링( Value → Template, Chart에 구성)

helm template .
---
# Source: pacman/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: pacman
  name: pacman
spec:
  ports:
    - name: http
      port: 8080
      targetPort: 8080
  selector:
    app.kubernetes.io/name: pacman
---
# Source: pacman/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pacman            # Chart.yaml 파일에 설정된 이름을 가져와 설정
  labels:
    app.kubernetes.io/name: pacman     # Chart.yaml 파일에 appVersion 여부에 따라 버전을 설정
    app.kubernetes.io/version: "1.0.0"     # appVersion 값을 가져와 지정하고 따움표 처리
spec:
  replicas: 1     # replicaCount 속성을 넣을 자리 placeholder
  selector:
    matchLabels:
      app.kubernetes.io/name: pacman
  template:
    metadata:
      labels:
        app.kubernetes.io/name: pacman
    spec:
      containers:
        - image: "quay.io/gitops-cookbook/pacman-kikd:1.0.0"   # 이미지 지정 placeholder, 이미지 태그가 있으면 넣고, 없으면 Chart.yaml에 값을 설정
          imagePullPolicy: Always
          securityContext:
              {} # securityContext의 값을 YAML 객체로 지정하며 14칸 들여쓰기
          name: pacman
          ports:
            - containerPort: 8080
              name: http
              protocol: TCP

배포 및 관리

# 헬름 배포 
helm install pacman .

# 차트 리소스 확인
helm list -A 
NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
pacman  default         1               2025-10-24 23:16:49.558416 +0900 KST    deployed        pacman-0.1.0    1.0.0    

# 배포 리소스 확인
kubectl get all 
NAME                          READY   STATUS    RESTARTS   AGE
pod/pacman-576769bb86-xp7xh   1/1     Running   0          4m5s

NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP    6d5h
service/pacman       ClusterIP   10.96.204.156   <none>        8080/TCP   4m5s

NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/pacman   1/1     1            1           4m5s

NAME                                DESIRED   CURRENT   READY   AGE
replicaset.apps/pacman-576769bb86   1         1         1       4m5s

# 헬름 히스토리 확인 
helm history pacman 
REVISION        UPDATED                         STATUS          CHART           APP VERSION     DESCRIPTION     
1               Fri Oct 24 23:16:49 2025        deployed        pacman-0.1.0    1.0.0           Install complete

# 헬름 메타데이터 관리를 위한 Secret 리소스 관리
## Helm이 차트의 상태를 복구하거나 rollback 할 때 데이터 사용
kubectl get secret 
NAME                           TYPE                 DATA   AGE
sh.helm.release.v1.pacman.v1   helm.sh/release.v1   1      9m47s

차트 업그레이드 및 메타데이터 확인

# 헬름 차트 
helm upgrade pacman --reuse-values --set replicaCount=2 .
---
Release "pacman" has been upgraded. Happy Helming!
NAME: pacman
LAST DEPLOYED: Fri Oct 24 23:36:15 2025
NAMESPACE: default
STATUS: deployed
REVISION: 3
TEST SUITE: None

# 메타데이터 관리 확인 
kubectl get secret 
NAME                           TYPE                 DATA   AGE
sh.helm.release.v1.pacman.v1   helm.sh/release.v1   1      19m
sh.helm.release.v1.pacman.v2   helm.sh/release.v1   1      5m23s
sh.helm.release.v1.pacman.v3   helm.sh/release.v1   1      21s

# 헬름 차트 정보 확인 
helm get manifest pacman
---
...

helm get values pacman
---
USER-SUPPLIED VALUES:
replicaCount: 2

# 차트 삭제
helm uninstall pacman
kubectl get secret
release "pacman" uninstalled
No resources found in default namespace.

_helpers.tpl 를 통한 재사용 가능한 코드 블록 정의

pacman 차트에서 동일한 필드가 3곳 존재함 → 수정시 똑같이 업데이트 필요

# deployment.yaml, service.yaml 에 selector 필드가 동일
## deployment.yaml
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ .Chart.Name}}
  template:
    metadata:
      labels:
        app.kubernetes.io/name: {{ .Chart.Name}}

## service.yaml
  selector:
    app.kubernetes.io/name: {{ .Chart.Name }}

_helpers.tpl 이용

  • define 값(pacman.selectorLabels)을 통해 매핑
# _helpers.tpl 파일 작성
cat << EOF > templates/_helpers.tpl
{{- define "pacman.selectorLabels" -}}   # stetement 이름을 정의
app.kubernetes.io/name: {{ .Chart.Name}} # 해당 stetement 가 하는 일을 정의
{{- end }}
EOF

# 기존 yaml 리팩토링
## deployment.yaml 수정
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "pacman.selectorLabels" . | nindent 6 }}   # pacman.selectorLabels를 호출한 결과를 6만큼 들여쓰기하여 주입
  template:
    metadata:
      labels:
        {{- include "pacman.selectorLabels" . | nindent 8 }} # pacman.selectorLabels를 호출한 결과를 8만큼 들여쓰기하여 주입
        
## service.yaml 수정
  selector:
    {{- include "pacman.selectorLabels" . | nindent 6 }}
    
# 랜더링 확인
helm template .

자주 쓰는 명령어

헬름을 통해 패키지를 배포하였으나, 차트 파일이 없는 상태일 경우

# 차트 리스트 확인
helm list -A 
---
NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
pacman  default         1               2025-10-25 00:07:08.44483 +0900 KST     deployed        pacman-0.1.0    1.0.0      

# 차트 값, 매니페스트 재구성
helm pull pacman --version 0.1.0 --untar  # 로컬이라 안됨 -> URL을 통해 가져올 수 있음
helm get values pacman > values.yaml

# Values 값 수정 후 업데이트 
vi values.yaml 
replicaCount: 2 # 1->2 수정 

# 업그레이드
helm upgrade pacman . --reuse-values -f values.yaml 
---
Release "pacman" has been upgraded. Happy Helming!
NAME: pacman
LAST DEPLOYED: Sat Oct 25 00:20:21 2025
NAMESPACE: default
STATUS: deployed
REVISION: 2
TEST SUITE: None
  • -f 은 values 파일 설정 옵션
  • 덮어쓰기가 가능하므로 맨 뒤에 파일이 최종으로 업데이트됨




Helm 원리 이해하기

배포 단계에서 설정 값에 따라 구성 옵션을 자유롭게 변경할 수 있습니다.

이때 내부 차트는 어떻게 구성되어 있는 지, LGTM Stack 을 통해 확인해보겠습니다.

 

LGTM Stack ?

https://github.com/daviaraujocc/lgtm-stack?tab=readme-ov-file

The LGTM stack, by Grafana Labs, combines best-in-class open-source tools to provide

helm repo add grafana https://grafana.github.io/helm-charts

# 패키지 설치 
helm install my-lgtm-distributed grafana/lgtm-distributed --version 2.1.0


# 로컬 템플릿 확인
helm pull grafana/lgtm-distributed --version 2.1.0 --untar


# 패스워드 확인 
kubectl get secret my-lgtm-distributed-grafana -n lgtm -o json | jq -r '.data | to_entries[] | "\(.key): \(.value|@base64d)"' 

연동 확인

 

차트 구성

LGTM Stack 들이 각 하위 차트로 구성되어 있습니다.

tree -L 2 .
.
├── Chart.lock
├── Chart.yaml
├── README.md
├── charts
│   ├── grafana
│   ├── loki-distributed
│   ├── mimir-distributed
│   ├── oncall
│   └── tempo-distributed
├── templates
│   ├── NOTES.txt
│   └── _helpers.tpl
└── values.yaml

 

 

 

Loki 저장소 확장 구성 확인

Loki 로그 저장소를 AWS S3를 통해 구성하고 헬름 차트를 확인하겠습니다.

S3를 로그 저장소로 구성하면 데이터 보관, 확장성과 백업 구성이 용이해집니다.

https://grafana.com/docs/loki/latest/setup/install/helm/deployment-guides/aws/

 

1. IAM 역할 정의

AWS S3 접근 제어를 위해 IAM 역할 및 정책을 정의하겠습니다.

자리표시자(<, >) 값을 실제 값으로 수정해야 합니다.
vi loki-s3-policy.json 
---
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "LokiStorage",
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::< CHUNK BUCKET NAME >",
                "arn:aws:s3:::< CHUNK BUCKET NAME >/*",
                "arn:aws:s3:::< RULER BUCKET NAME >",
                "arn:aws:s3:::< RULER BUCKET NAME >/*"
            ]
        }
    ]
}

aws iam create-policy --policy-name LokiS3AccessPolicy --policy-document file://loki-s3-policy.json

2. EKS 로키 파드에서 AWS S3를 사용하기 위해 신뢰 정책을 정의합니다.

vi trust-policy.json
---
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::< ACCOUNT ID >:oidc-provider/oidc.eks.<INSERT REGION>.amazonaws.com/id/< OIDC ID >"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "oidc.eks.<INSERT REGION>.amazonaws.com/id/< OIDC ID >:sub": "system:serviceaccount:loki:loki",
                    "oidc.eks.<INSERT REGION>.amazonaws.com/id/< OIDC ID >:aud": "sts.amazonaws.com"
                }
            }
        }
    ]
}

# IAM 역할 생성 
aws iam create-role --role-name LokiServiceAccountRole --assume-role-policy-document file://trust-policy.json

# 정책 Attach 
aws iam attach-role-policy --role-name LokiServiceAccountRole --policy-arn arn:aws:iam::<AccountID>:policy/LokiS3AccessPolicy

3. 헬름 초기 버전 배포

helm repo add grafana https://grafana.github.io/helm-charts

helm repo update

kubectl create namespace lgtm

# Helm 설치
helm install my-lgtm-distributed grafana/lgtm-distributed \
  --version 2.1.0 \
  --namespace lgtm

4. Loki 패스워드 설정

# htpasswd -c .htpasswd <username>
htpasswd -c .htpasswd admin

kubectl create secret generic loki-basic-auth --from-file=.htpasswd -n lgtm

kubectl create secret generic canary-basic-auth \
  --from-literal=username=admin \
  --from-literal=password=admin \
  -n lgtm
  

5. Loki 차트 구성

Loki 차트는 LGTM 하위 차트로 구성되어 있습니다.

중첩 구조를 통해 Loki 스토리지 설정을 다음과 같이 설정합니다.

# lgtm-values.yaml
loki:
  enabled: true
  
  # loki-distributed 차트 설정은 여기에
  loki:
    schemaConfig:
      configs:
        - from: 2020-07-01
          store: boltdb-shipper
          object_store: aws
          schema: v11
          index:
            prefix: index_
            period: 24h
    
    storageConfig:
      boltdb_shipper:
        active_index_directory: /loki/boltdb-shipper-active
        cache_location: /loki/boltdb-shipper-cache
        cache_ttl: 24h         # Can be increased for faster performance over longer query periods, uses more disk space
        shared_store: s3
      aws:
        s3: s3://ap-northeast-2
        bucketnames: <bucket-name>
        s3forcepathstyle: false
    
    ingester:
      chunk_encoding: snappy
    
    limits_config:
      allow_structured_metadata: true
      volume_enabled: true
      retention_period: 672h
    
    compactor:
      retention_enabled: true
      delete_request_store: s3
      shared_store: s3
      working_directory: /var/loki/compactor
    
    ruler:
      enabled: false
    
    querier:
      max_concurrent: 4
  
  # 컴포넌트 replicas 설정
  ingester:
    replicas: 1
    persistence:
      enabled: false
  
  querier:
    replicas: 1
  
  queryFrontend:
    replicas: 1
  
  queryScheduler:
    enabled: true
    replicas: 1
  
  distributor:
    replicas: 1
  
  compactor:
    enabled: true
    replicas: 1
    persistence:
      enabled: false
  
  indexGateway:
    enabled: true
    replicas: 1
    persistence:
      enabled: false
  
  ruler:
    enabled: true
    replicas: 1
    maxUnavailable: 1
  
  gateway:
    enabled: true
    service:
      type: LoadBalancer
    basicAuth:
      enabled: true
      existingSecret: loki-basic-auth
  
  # ServiceAccount (IRSA)
  serviceAccount:
    create: true
    annotations:
      eks.amazonaws.com/role-arn: "arn:aws:iam::<Account-ID>:role/LokiServiceAccountRole"

grafana:
  enabled: true

  datasources:
    datasources.yaml:
      apiVersion: 1
      datasources:
        - name: Loki
          uid: loki
          type: loki
          url: http://my-lgtm-distributed-loki-gateway
          access: proxy
          isDefault: false
          basicAuth: true
          basicAuthUser: ${LOKI_BASIC_AUTH_USER}
          jsonData:
            httpHeaderName1: X-Scope-OrgID     
          secureJsonData:
            basicAuthPassword: ${LOKI_BASIC_AUTH_PASSWORD}
            httpHeaderValue1: "1"              

        - name: Mimir
          uid: prom
          type: prometheus
          url: http://my-lgtm-distributed-mimir-nginx/prometheus
          isDefault: true

        - name: Tempo
          uid: tempo
          type: tempo
          url: http://my-lgtm-distributed-tempo-query-frontend:3100
          isDefault: false
          jsonData:
            tracesToLogsV2:
              datasourceUid: loki
            lokiSearch:
              datasourceUid: loki
            tracesToMetrics:
              datasourceUid: prom
            serviceMap:
              datasourceUid: prom

  # 🔐 환경변수로 BasicAuth 주입(운영은 Secret 권장)
  env:
    LOKI_BASIC_AUTH_USER: "admin"
    LOKI_BASIC_AUTH_PASSWORD: "admin"

mimir:
  enabled: true
  # ... 기존 mimir 설정 유지

tempo:
  enabled: true
  # ... 기존 tempo 설정 유지

helm upgrade my-lgtm-distributed grafana/lgtm-distributed \
  --version 2.1.0 \
  -f lgtm-values.yaml \
  --namespace lgtm

 

6. 차트 구성 확인

헬름 차트에서 스토리지 연동 부분은 다음과 같습니다. Loki 2.9.6 버전 configmap을 통해 연동을 하며 옵션 확인은 차트가 아닌 Doc를 통해 확인해야 합니다.

  • 예제로 선택한 Lgtm Stack이 작년 이후 업데이트 되지 않은 상태라 Loki 버전이 낮습니다. deprecated 된 boltdb를 기준으로 설명합니다.
# lgtm-vlaues.yaml 
..
  storageConfig:
    aws:
      region: ap-northeast-2
      bucketnames: hshan92
      s3forcepathstyle: false
    tsdb_shipper:
      active_index_directory: /var/loki/index
      cache_location: /var/loki/cache
      shared_store: s3
..
 
{{- toYaml .Values.loki.storageConfig | nindent 2}}

# values.yaml
...
storage_config:
{{- if .Values.indexGateway.enabled}}
{{- $indexGatewayClient := dict "server_address" (printf "dns:///%s:9095" (include "loki.indexGatewayFullname" .)) }}
{{- $_ := set .Values.loki.storageConfig.boltdb_shipper "index_gateway_client" $indexGatewayClient }}
{{- end}}
{{- toYaml .Values.loki.storageConfig | nindent 2}} # 스토리지 입력
{{- if .Values.memcachedIndexQueries.enabled }}
  index_queries_cache_config:
    memcached_client:
      addresses: dnssrv+_memcached-client._tcp.{{ include "loki.memcachedIndexQueriesFullname" . }}.{{ .Release.Namespace }}.svc.{{ .Values.global.clusterDomain }}
      consistent_hash: true
...
          

# 랜더링 확인
helm template my-lgtm-distributed grafana/lgtm-distributed \
  -n lgtm \
  -f lgtm-values.yaml \
  | grep -A 20 "storage_config:"\

storage_config:
  aws:
    bucketnames: hshan92
    s3: s3://ap-northeast-2
    s3forcepathstyle: false
  boltdb_shipper:
    active_index_directory: /loki/boltdb-shipper-active
    cache_location: /loki/boltdb-shipper-cache
    cache_ttl: 24h
    index_gateway_client:
      server_address: dns:///my-lgtm-distributed-loki-index-gateway:9095
    shared_store: s3
  filesystem:
    directory: /var/loki/chunks
table_manager:
  retention_deletes_enabled: false
  retention_period: 0s

7. 테스트 로그 호출

ELB=a08db421ff2f34f11a25629d1e1ed624-2032163681.ap-northeast-2.elb.amazonaws.com

# (Gateway에 basicAuth 켜둔 상태라면) 사용자/비번 포함
curl -u 'admin:admin' \
  -X POST http://$ELB/loki/api/v1/push \
  -H "Content-Type: application/json" \
  -d '{
    "streams": [{
      "stream": { "app": "test", "level": "info" },
      "values": [[ "'$(date +%s)000000000'", "Test log message" ]]
    }]
  }'

kubectl port-forward svc/my-lgtm-distributed-grafana 3000:80 -n lgtm

AWS S3 연동 확인

# 업로딩 로그 확인
kubectl logs my-lgtm-distributed-loki-ingester-0 -n lgtm
---
...
level=info ts=2025-10-25T07:14:47.019507465Z caller=index_set.go:86 msg="uploading table loki_index_20386"
level=info ts=2025-10-25T07:14:47.019515227Z caller=index_set.go:107 msg="finished uploading table loki_index_20386"
level=info ts=2025-10-25T07:14:47.019521961Z caller=index_set.go:185 msg="cleaning up unwanted indexes from table loki_index_20386"
...

 

 
 
 

참고

https://grafana.com/docs/loki/latest/setup/install/helm/deployment-guides/aws/

'Cloud' 카테고리의 다른 글

ArgoCD 정리 (1)  (0) 2025.11.08
Gitops CI & CD 구성(Jenkins & ArgoCD)  (0) 2025.11.02
쿠버네티스 GItOps  (0) 2025.10.19
Amazon VPC Lattice for Amazon EKS  (0) 2025.04.27
EKS 파드로 노드 관리하기  (0) 2025.04.12