AI

AIOPs를 통한 업무 자동화 PoC(Holmesgpt를 곁들인..)

Hanhorang31 2025. 3. 29. 23:34
 

Why ?

  • A서비스, EKS 파드 하나가 계속 죽어요
  • B서비스, AWS 비용이 전달 대비 100%올랐어요
  • C서비스, 누가 우리 서비스 공격하는 것 같아요 로그 보여주세요
  • D서비스, 뭐지? 누가 우리 계정 RDS 날려먹었는데요?

어려움 점

  • 사용해야할 툴이 제각각이다
  • 각 기술 사용 역량이 필요하며 사례별 경험이 필요, 담당자 별로 실력차이가 있을 수 있음
  • 요청이 다수인 경우, 요청별 시간이 많이 걸림
  • 반복 업무

어려운 점을 ChatGPT처럼 사용자의 질문에 따라 답할 수 있는 툴이 있을까 찾아보니 HolmesGPT 이 있어 소개합니다.

HolmesGPT

HolmesGPT는 LLM을 사용하여 알림에 더 빠르게 수행하여 분석을 자동으로 진행합니다.

  • Fetch logs, traces, and metrics
  • Determine if issues are application or infrastructure related
  • Find upstream root-causes
  • Holmes는 KRR, robusta 를 개발한 곳에서 AI를 통합하여 개발한 툴입니다.
  • 알람 대응 뿐만 아니라 사용자 질문에 대해 대응도 가능합니다.
  • Robusta 의 하부 모듈로 Robust의 기능을 공유합니다.

 

Robusta integrates with Prometheus by webhook and adds features like:

 

환경 구성

테라폼을 통해 AWS EKS 기본 환경을 구성하였습니다.

필자의 깃 레파지토리를 참고해주세요. https://hanhorang.tistory.com/38

EKS Version 1.31 Node t3.large 2대 addon 자동 설치 : ALB Controller, EBS CSI Drvier, kube-prometheues-stack
⌨️ git clone https://github.com/HanHoRang31/blog-share.git 

⌨️ cd blog-share/aews3-observability/grafana-eks-simple 

⌨️ ./isntall.sh 
  • 약 15분정도 소요됩니다.

그라파나 접근은 다음과 같이 해주세요.

⌨️ kubectl patch svc kube-prometheus-stack-grafana -n kube-prometheus-stack -p '{"spec":{"type":"LoadBalancer"}}'

# EXternal IP 확인 
⌨️ kubectl get svc -A | grep grafana 
kube-prometheus-stack   kube-prometheus-stack-grafana                    LoadBalancer   172.20.10.99     a766b12aaac1c4d3a93b8779f945aecb-1958566172.ap-northeast-2.elb.amazonaws.com   80:30926/TCP  
  • 그라파나 접근 계정은 admin/admin1234 입니다.

 

prometheus-stack 가 없는 경우 따로 설치

더보기
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \ --namespace kube-prometheus-stack \ --create-namespace \ --version 48.1.1 \ -f kube-prometheus.yaml

 

Holmes 설치

SaaS 로 등록, Robusta UI account ↗

clusterName: hsh-eks

globalConfig:
  signing_key: -
  account_id: -
sinksConfig:
  - robusta_sink:
      name: robusta_ui_sink
      token: -
  - slack_sink:
      name: main_slack_sink
      slack_channel: -
      api_key: -
enablePrometheusStack: false
enablePlatformPlaybooks: true
runner:
  sendAdditionalTelemetry: true
enableHolmesGPT: true
enabledManagedConfiguration: true
holmes:
  additionalEnvVars:
    - name: ROBUSTA_AI
      value: "true"
  toolsets:
    aws/security:
      enabled: true
    kubernetes/kube-prometheus-stack:
      enabled: true

# 설치 
helm repo add robusta https://robusta-charts.storage.googleapis.com && helm repo update
helm install robusta robusta/robusta -f ./generated_values.yaml 

알람 설정

그라파나 > Alerting > Contact Points > New contact point

 

Integration : Webhook
URL : https://api.robusta.dev/integrations/generic/alertmanager

Optional Webhook settings 
 - HTTP Method : POST 
 - Authorization Header - Credentials : account_id + ' ' + signing_key
   ex. b95- d31-
   

Create contact point 내 “Test" button > Select "custom" and add a cluster_name  기입 후 테스트


vi generated_values.yaml
globalConfig: # this line should already exist
  # add the lines below
  grafana_url: "http://a2e2322ec96de-.ap-northeast-2.elb.amazonaws.com/"
  # Create alert silencing when using Grafana alerts
  grafana_api_key: glsa_Aj-
  alertmanager_flavor: grafana # 
  prometheus_url: "kube-prometheus-stack-prometheus.kube-prometheus-stack.svc.cluster.local:9090"
  # "http://PROMETHEUS_SERVICE_NAME.NAMESPACE.svc.cluster.local:9090" # 


# 업그레이드
helm upgrade robusta robusta/robusta --values=generated_values.yaml 

슬랙 챗봇 활성화 (holmesGPT 활성화)

 

 

실습 1. kubernetes log 알람 확인

테스트 파드를 배포하여 테스트하겠습니다.

kubectl apply -f https://gist.githubusercontent.com/robusta-lab/283609047306dc1f05cf59806ade30b6/raw


kubectl get pods -A 
NAMESPACE               NAME                                                        READY   STATUS             RESTARTS      AGE
amazon-cloudwatch       aws-cloudwatch-metrics-9ln2l                                1/1     Running            0             49m
amazon-cloudwatch       aws-cloudwatch-metrics-hz926                                1/1     Running            0             49m
default                 crashpod-77c67656c-bkt8j                                    0/1     CrashLoopBackOff   5 (31s ago)   3m24s
default                 krr-job-bc5dc57e-d226-487d-b472-523fcc5e23b1-pv2mn          0/1     Pending            0             25m
default                 robusta-forwarder-5c5fdbbf57-twzr2                          1/1     Running            0             28m
default                 robusta-holmes-699d789f8d-88x2s                             1/1     Running            0             28m
default                 robusta-runner-69b66696b7-f9bkq                             1/1     Running            0             28m
kube-prometheus-stack   kube-prometheus-stack-grafana-d8f44877-25m9k                3/3     Running            0             49m
kube-prometheus-stack   kube-prometheus-stack-kube-state-metrics-7857ff9764-jtkk7   1/1     Running            0             49m
kube-prometheus-stack   kube-prometheus-stack-operator-857c7b8dd9-mdbjm             1/1     Running            0             49m
kube-prometheus-stack   kube-prometheus-stack-prometheus-node-exporter-n8st9        1/1     Running            0             49m
kube-prometheus-stack   kube-prometheus-stack-prometheus-node-exporter-slclc        1/1     Running            0             49m
kube-prometheus-stack   prometheus-kube-prometheus-stack-prometheus-0               2/2     Running            0             49m
kube-system             aws-load-balancer-controller-8bffc8f4-gt8nx                 1/1     Running            0             49m
kube-system             aws-load-balancer-controller-8bffc8f4-xsjff                 1/1     Running            0             49m
kube-system             aws-node-ph9wt                                              2/2     Running            0             49m
kube-system             aws-node-qfklr                                              2/2     Running            0             49m
kube-system             coredns-86f5954566-7hdwc                                    1/1     Running            0             49m
kube-system             coredns-86f5954566-9xzkj                                    1/1     Running            0             49m
kube-system             ebs-csi-controller-68d4dc9d4c-4mbcz                         6/6     Running            0             49m
kube-system             ebs-csi-controller-68d4dc9d4c-72t78                         6/6     Running            0             49m
kube-system             ebs-csi-node-fsh8p                                          3/3     Running            0             49m
kube-system             ebs-csi-node-vsdkp                                          3/3     Running            0             49m
kube-system             kube-proxy-ksmsv                                            1/1     Running            0             49m
kube-system             kube-proxy-zjjtq                                            1/1     Running            0             49m

 

 

실습 2. Loki (logging) 확장 (실패)

로키 구성 : https://www.notion.so/han-horang/Grafana-Observability-1a6a6285e6c580fab623e24d979ea924

clusterName: hsh-eks

globalConfig:
  # add the lines below
  grafana_url: "http://ad4e41-.ap-northeast-2.elb.amazonaws.com/"
  grafana_api_key: glsa_fNYC-
  alertmanager_flavor: grafana # 
  prometheus_url: "kube-prometheus-stack-prometheus.kube-prometheus-stack.svc.cluster.local:9090"
  signing_key: 64-
  account_id: -
sinksConfig:
  - robusta_sink:
      name: robusta_ui_sink
      token: -
  - slack_sink:
      name: main_slack_sink
      slack_channel: aiops
      api_key: -
enablePrometheusStack: false
enablePlatformPlaybooks: true
runner:
  sendAdditionalTelemetry: true
enableHolmesGPT: true
enabledManagedConfiguration: true
holmes:
  additionalEnvVars:
    - name: ROBUSTA_AI
      value: "true"
  toolsets:
    aws/security:
      enabled: true
    kubernetes/kube-prometheus-stack:
      enabled: true
    grafana/loki:
      enabled: true
      config:
        api_key: glsa_fNYCuOYE8x0HeqhSWjjbaKtGf3OY93gm_7cf1405e
        url: http://-.ap-northeast-2.elb.amazonaws.com # Your Grafana cloud account URL
        grafana_datasource_uid: ac19ca32-68f7-48f8-a4fb-a8880cad08f0
        labels:
          k8s_cluster_name: "eks-hsh"
    kubernetes/logs:
      enabled: false # Disable HolmesGPT's default logging mechanism
   
  

grafana_datasource_uid  확인 방법 

kubectl port-forward svc/kube-prometheus-stack-grafana  -n kube-prometheus-stack 3000:80
-----------
Forwarding from 127.0.0.1:3000 -> 3000
Forwarding from [::1]:3000 -> 3000

curl -s -u admin:admin1234 http://localhost:3000/api/datasources | jq '.[] | select(.type == "loki")'
ac19ca32-68f7-48f8-a4fb-a8880cad08f0

재배포

helm upgrade robusta robusta/robusta --values=generated_values.yaml 

실패 ! 그라파나 클라우드 아니여서 팅기는 것이 아닌가 추정 중입니다. (확인 중)

 

 

실습3. AWS 확장

This builtin toolset is currently only available in HolmesGPT CLI
AWS Security는 Saas가 아닌 CLI를 통해 지원합니다.
brew tap robusta-dev/homebrew-holmesgpt
brew install holmesgpt
holmes --help

# Alpha 버전 아닌 지 확인 (버그 있음) 
holmes version 
0.10.2

# aws configure 
export AWS_ACCESS_KEY_ID="<your AWS access key ID>"
export AWS_SECRET_ACCESS_KEY="<your AWS secret access key>"
export AWS_DEFAULT_REGION="us-west-2"

# cli config 설정 
sudo vi /etc/holmes/config/custom_toolset.yaml 
toolsets:
    aws/security:
        enabled: true
        

holmes ask --model="gpt-4o" --api-key="***" "Who created the EKS cluster hsh-eks?"
 
에러 로그

https://platform.openai.com/settings/organization/billing/overview 과금 필요

카드 등록과 5.5달러 선결제로 API 호출 제한 해제 https://platform.openai.com/settings/organization/billing/payment-methods

과금 주의 설정

과금 후 😢 API 키 발급 https://platform.openai.com/settings/organization/api-keys

Tool Name
Description
aws_cloudtrail_event_lookup
AWS CloudTrail에서 특정 유형의 이벤트와, 해당 이벤트를 호출한 사용자를 조회합니다.
aws_cloudtrail_event_details
AWS CloudTrail에서 이벤트 ID를 기준으로 특정 이벤트의 전체 세부 정보를 JSON 형태로 조회합니다.
aws_user_audit_logs
AWS CloudTrail에서 특정 사용자의 최근 24시간 동안의 감사 로그를 조회합니다. 사용자 이름은 aws_event_lookup 또는 aws_event_details의 출력을 사용합니다.

질문을 잘 선별해야 한다..!

  • Show me the last 10 AWS CloudTrail event - 최근 10개 이벤트 가져오기 (실패)
  • Fetch AttachVolume using aws_cloudtrail_event_lookup. - 특정 이벤트 선정 이벤트 5개 가져오기(성공)
  • who deleted eks named hsh-eks? - 누가 내 EKS 지웠니? (성공)
  • Can you retrieve JSON details for the specific event when my EKS was deleted? - EKS 삭제 이벤트 상세 정보(성공)
  • Fetch the recent AWS login attempts and show the usernames involved. - 누가 로그인했니 (성공)
  • Did user hsh@lgcns.com make any configuration changes recently? - 어떤 유저가 최근에 어떤 걸 변경했니? (부분 실패, 토큰에러)
  • Provide recent CloudTrail logs of the user who deleted EKS - EKS 누가 삭제했는 지 알려줘 (부분 실패, 읽기 제한)
 

원리 이해(React) 

https://www.youtube.com/watch?v=Eug2clsLtFs&t=27s

ReAct(Reason + Act)는 구조화된 템플릿을 LLM에 제공하여 추론 능력과 모델 평가를 향상시키는 Prompt Engineering 기법입니다.

구체적으로는 LLM이 템플릿을 참고하여 자체적으로 추론하고 필요한 도구를 호출하면서 문제를 단계적으로 해결해나가 추론 능력과 모델 평가를 향상시킵니다.

구조화된 템플릿은 질문, 사고, 조치, 관찰로 구성됩니다.

  • 질문, QA : 사용자의 요청 작업 또는 해결해야하는 문제
  • 사고, Thoutgt : 문제를 해결하고 취해야할 조치를 식별하는 방법을 모델에게 제시
  • 조치, Act : 모델이 간접적으로 호출할 수 있는 API
  • 관찰, obs : 조치를 수행한 결과

 

 

  • 사고(Thought): "아, 대통령이 누군지 알아야겠네"
  • 조치(act): "대통령 정보 검색해보자"
  • 관찰(Observation): "조 바이든이네"
  • 사고(Thought): "그럼 나이 계산해야지"
  • Finish: "78세야"

 

HolmesGPT 원리이해

AWS Security 예제에서 사용했던 command인 ask 의 코드를 확인하면 동작 원리를 확인할 수 있습니다.

# TODO: add interactive interpreter mode
# TODO: add streaming output
@app.command()
def ask(
...
    """
    Ask any question and answer using available tools
    """
    console = init_logging(verbose)
    config = Config.load_from_file(  
        config_file,
        api_key=api_key,
        model=model,
        max_steps=max_steps,
        custom_toolsets=custom_toolsets, 
        slack_token=slack_token,
        slack_channel=slack_channel,
        system_prompt: Optional[str] = typer.Option(
        "builtin://generic_ask.jinja2", help=system_prompt_help
    )

    ai = config.create_console_toolcalling_llm( 
        allowed_toolsets=allowed_toolsets, dal=None
    )
    template_context = {
        "enabled_toolsets": ai.tool_executor.enabled_toolsets, 
    }
    system_prompt = load_and_render_prompt(system_prompt, template_context)
    if echo_request:
        console.print("[bold yellow]User:[/bold yellow] " + prompt)
    for path in include_file:
        f = path.open("r")
        prompt += f"\n\nAttached file '{path.absolute()}':\n{f.read()}"
        console.print(f"[bold yellow]Loading file {path}[/bold yellow]")

    response = ai.prompt_call(system_prompt, prompt, post_processing_prompt) 

    if json_output_file:
        write_json_file(json_output_file, response.model_dump())

    issue = Issue(
        id=str(uuid.uuid4()),
        name=prompt,
        source_type="holmes-ask",
        raw={"prompt": prompt},
        source_instance_id=socket.gethostname(),
    )
    handle_result(
        response, console, destination, config, issue, show_tool_output, False
    )

위 코드를 요약하면 GPT 모델을 두 번 호출하며, Holmes는 각 툴에 등록된 명령어를 실행하는 것을 알 수 있습니다.

  1. aws.yaml → aws_cloudtrail_event_lookup tool 등록됨
  2. GPT가 해당 질문을 받고 Tool 호출
  3. Holmes가 해당 CLI 실행 후 결과 반환
  4. GPT가 결과 요약 후 사용자에게 자연어 응답 제공

 

1. 프롬프트에 custom toolset이 등록됩니다.

툴셋에 대한 정의는 plugins/toolset 에 있습니다.

각 툴셋을 로드하여 prerequisites 실행하고 결과가 있는 tools 들은 추가합니다.

# aws.yaml
toolsets:
  aws/security:
    description: "Set of tools to audit AWS security"
    docs_url: "https://docs.robusta.dev/master/configuration/holmesgpt/toolsets/aws.html#security"
    icon_url: "https://upload.wikimedia.org/wikipedia/commons/9/93/Amazon_Web_Services_Logo.svg"
    tags:
      - cli
    prerequisites:
      - command: "aws sts get-caller-identity"

    tools:
      - name: "aws_cloudtrail_event_lookup"
        description: "Fetches events of a specified type from AWS CloudTrail along with the users that called them"
        user_description: "get {{ EVENT_NAME }} events"
        command: |
          echo "EventName,EventId,EventTime,Username,AccessKeyId,ip,userID"
          aws cloudtrail lookup-events \
            --lookup-attributes AttributeKey=EventName,AttributeValue="{{ EVENT_NAME }}" \
            --start-time $(date -v -7d -u +%Y-%m-%dT%H:%M:%SZ) \
            --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
            --query 'Events[*].{EventName:EventName,EventId:EventId,EventTime:EventTime,Username:Username,AccessKeyId:AccessKeyId,ip:CloudTrailEvent.sourceIPAddress,userID:CloudTrailEvent.userIdentity.sessionContext.sessionIssuer.userName}' \
            --output table

      - name: "aws_cloudtrail_event_details"
        description: "Fetches and returns full event details for an AWS cloudtrail event in JSON format given an event ID"
        user_description: "looking up event {{ EVENT_ID }}"
        command: |
          aws cloudtrail lookup-events \
            --lookup-attributes AttributeKey=EventId,AttributeValue="{{ EVENT_ID }}" \
            --query 'Events[0]' --output json

      - name: "aws_user_audit_logs"
        description: "Fetches audit logs for a specified user from AWS CloudTrail in past 24 hours. Provide username as was outputed by aws_event_lookup or aws_event_details"
        user_description: "get audit logs for {{ UserName }}"
        command: |
          aws cloudtrail lookup-events \
          --lookup-attributes AttributeKey=Username,AttributeValue="{{ UserName }}" \
          --start-time $(date -v -1d -u +%Y-%m-%dT%H:%M:%SZ) \
          --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
          --query 'Events[*].{EventName:EventName,EventSource:EventSource,EventId:EventId,EventTime:EventTime,Username:Username,AccessKeyId:AccessKeyId,ip:CloudTrailEvent.sourceIPAddress,userID:CloudTrailEvent.userIdentity.sessionContext.sessionIssuer.userName}' \
          --output table

  aws/rds:
    description: "Read access to Amazon RDS resources"
    docs_url: "https://docs.robusta.dev/master/configuration/holmesgpt/toolsets/aws.html#rds"
    icon_url: "https://upload.wikimedia.org/wikipedia/commons/9/93/Amazon_Web_Services_Logo.svg"
    tags:
      - core
    prerequisites:
      - command: "aws sts get-caller-identity"

    tools:
      - name: "aws_rds_describe_events"
        description: "Runs aws rds describe-events"
        user_description: "fetch rds events"
        command: "aws rds describe-events"

      - name: "aws_rds_describe_instance"
        description: "Get the configuration of a RDS instance"
        user_description: "Get the configuration of a RDS instance"
        command: "aws rds describe-db-instances --db-instance-identifier '{{ db_instance_identifier }}'"

      - name: "aws_rds_describe_instances"
        description: "Runs aws rds describe-db-instances"
        user_description: "fetch rds instances"
        command: "aws rds describe-db-instances"

      - name: "aws_rds_describe_logs"
        description: "Describe all available logs for an AWS RDS instance."
        user_description: "list available RDS logs (e.g. slow query logs)"
        command: "aws rds describe-db-log-files --db-instance-identifier '{{ db_instance_identifier }}'"

      - name: "aws_rds_fetch_log_by_name"
        description: "Fetch a specific log for an AWS RDS instance by log file name."
        user_description: "fetch a specific RDS log"
        command: "aws rds download-db-log-file-portion --db-instance-identifier '{{ db_instance_identifier }}' --log-file-name '{{ log_file_name }}' --starting-token 0"

 

2. GPT가 해당 질문을 받고 Tool 호출합니다.

사용자 질문에 먼저 답하지않고 이용가능한 tools 을 GPT을 통해 전달받는 과정입니다.

tools이란 aws_cloudtrail_event_lookup , aws_cloudtrail_event_details 을 뜻합니다.

기초 프롬프트에 대한 설정은 generic_ask.jinja2 에 있습니다.

# generic_ask.jinja2 
You are a tool-calling AI assist provided with common devops and IT tools that you can use to troubleshoot problems or answer questions.
Whenever possible you MUST first use tools to investigate then answer the question.
Do not say 'based on the tool output' or explicitly refer to tools at all.
If you output an answer and then realize you need to call more tools or there are possible next steps, you may do so by calling tools at that point in time.
If you have a good and concrete suggestion for how the user can fix something, tell them even if not asked explicitly

Use conversation history to maintain continuity when appropriate, ensuring efficiency in your responses.

{% include '_general_instructions.jinja2' %}

# Style guide

* Reply with terse output.
* Be painfully concise.
* Leave out "the" and filler words when possible.
* Be terse but not at the expense of leaving out important data like the root cause and how to fix.

## Examples

User: Why did the webserver-example app crash?
(Call tool kubectl_find_resource kind=pod keyword=webserver`)
(Call tool kubectl_previous_logs namespace=demos pod=webserver-example-1299492-d9g9d # this pod name was found from the previous tool call)

AI: `webserver-example-1299492-d9g9d` crashed due to email validation error during HTTP request for /api/create_user
Relevant logs:

```
2021-01-01T00:00:00.000Z [ERROR] Missing required field 'email' in request body
```

Validation error led to unhandled Java exception causing a crash.
  • 맨 먼저 Tool을 호출하려고 하고, Tool 결과를 기반으로 사용자가 알아차리지 못하게 자연스럽게 요약하고, root cause와 fix를 짧고 강력하게 전달해줘.

 

 

3. Holmes가 해당 CLI 실행 후 결과 반환

Holmes가 GPT에 사용 가능한 tools 을 가져와서 CLI를 실행 후 결과를 반환합니다.

만약, aws_cloudtrail_event_lookup tool을 반환했다면, 해당 이벤트에 commnad를 실행합니다.

echo "EventName,EventId,EventTime,Username,AccessKeyId,ip,userID"
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue="{{ EVENT_NAME }}" \
  --start-time $(date -v -7d -u +%Y-%m-%dT%H:%M:%SZ) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
  --query 'Events[*].{EventName:EventName,EventId:EventId,EventTime:EventTime,Username:Username,AccessKeyId:AccessKeyId,ip:CloudTrailEvent.sourceIPAddress,userID:CloudTrailEvent.userIdentity.sessionContext.sessionIssuer.userName}' \
  --output table

 

4. GPT가 결과 요약 후 사용자에게 자연어 응답 제공

The EKS cluster "hsh-eks" was created by the user with the email "hsh@l-".  

참고 자료

https://docs.robusta.dev/master/setup-robusta/installation/extend-prometheus-installation.html#install-existing-prometheus

https://www.youtube.com/watch?v=5nRZr1eX618

https://seifrajhi.github.io/blog/ai-and-kubernetes/#--holmes-gpt

https://jennifersoft.com/ko/blog/kubernetes/2024-01-10-kubernetes-16/



'AI' 카테고리의 다른 글

AWS Bedrock(LLM)을 활용한 앤서블 GPT 모델 만들기  (1) 2024.02.11