Red Hat Blog를 보다가 관심 가는 글이 보여서 AI번역+약간 교정해 보았습니다.
출처: https://developers.redhat.com/blog/2025/05/07/leveraging-ansible-event-driven-automation-automatic-cpu-scaling-openshift
소개
끊임없이 진화하는 현대 IT 환경에서 애플리케이션은 트래픽 및 리소스 수요가 크게 변동하는 동적 워크로드에 직면합니다. 최적의 성능과 응답성을 유지하려면 효과적인 확장 전략을 구현하는 것이 중요합니다. 수평적 자동 확장(VM 인스턴스 추가)이 일반적인 방식이지만, 수직적 자동 확장(기존 VM의 CPU 또는 메모리와 같은 리소스 조정) 또한 특히 갑작스러운 트래픽이나 최대 수요를 처리하는 데 중요한 역할을 할 수 있습니다.
이 문서에서는 OpenShift Virtualization에서 실행되는 가상 머신(VM)의 수직 확장을 자동화하는 강력한 기술을 살펴봅니다. OpenShift의 AlertManager 기능과 Ansible Event-Driven Automation (EDA) 기능을 결합하면 사전 정의된 알림에 따라 VM의 CPU 리소스를 자동으로 조정하는 시스템을 구축할 수 있습니다.
주요 구성 요소
솔루션에 들어가기 전에 관련된 핵심 구성 요소를 소개해 드리겠습니다.
- OpenShift Virtualization: Red Hat OpenShift에 추가된 기능으로, 동일한 플랫폼에서 컨테이너화된 워크로드와 함께 VM을 실행하고 관리할 수 있도록 지원합니다. KubeVirt 오픈소스 프로젝트를 기반으로 구축되었습니다.
- AlertManager: Prometheus 모니터링 툴킷의 구성 요소로, 모니터링 규칙에 따라 발생하는 알림을 처리합니다. 이 예제에서는 CPU 사용량을 모니터링하고 미리 정의된 임계값을 초과하면 알림을 보냅니다.
- Ansible Automation Platform(AAP): 기업 전반의 IT 운영을 자동화하는 통합 플랫폼입니다. 이벤트 기반 자동화(EDA) 기능을 통해 다양한 소스에서 발생하는 이벤트에 대응하여 자동화를 구현할 수 있습니다.
- Ansible Event-Driven Automation (EDA): 이벤트에 의해 트리거되는 자동화 규칙을 정의할 수 있는 AAP 기능입니다. 이 기능을 사용하여 AlertManager 알림을 수신하고 CPU 확장 프로세스를 시작합니다.
사용 사례: 자동 CPU 확장
저희의 목표는 OpenShift Virtualization에서 실행되는 VM에 CPU 사용량 알림이 발생할 때 할당되는 CPU 수(이 예시에서는 소켓 수)를 늘리는 프로세스를 자동화하는 것입니다. 이를 통해 VM은 CPU 수요 증가에 동적으로 적응하여 애플리케이션 성능을 보장하고 리소스 병목 현상을 방지할 수 있습니다.
해결책
이 솔루션은 다음 단계로 구성됩니다.
- 모니터링 및 알림: OpenShift의 AlertManager는 대상 VM의 CPU 사용량을 모니터링하도록 구성됩니다. 사용량이 미리 정의된 임계값을 초과하면 AlertManager가 알림을 전송합니다.
- Event-Driven Automation: Ansible EDA는 AlertManager로부터 알림을 수신하도록 구성됩니다. EDA 규칙서는 이러한 알림을 처리하고 적절한 작업을 트리거하는 로직을 정의합니다.
- Ansible 플레이북 실행: CPU 사용량 알림을 수신하면 EDA는 AAP에서 Ansible 작업 템플릿을 실행합니다. 이 작업 템플릿은 OpenShift Virtualization과 상호 작용하여 VM의 CPU 소켓 수를 늘리는 플레이북을 실행합니다.
- CPU 확장: Ansible 플레이북은 Kubernetes 컬렉션을 사용하여 VM 구성을 수정하고 CPU 소켓 수를 늘립니다. 이 변경 사항은 실행 중인 VM에 적용되어 처리 능력을 향상시킵니다.
구현 세부 정보
이 솔루션에 포함된 주요 구성 파일은 다음과 같습니다.
eda-vm-rulebook.yaml: 이 파일은 AlertManager 이벤트를 수신하고 Ansible Job Template을 트리거하는 EDA 규칙책을 정의합니다.
---
- name: Process Alertmanager events to scale KubeVirt VM sockets
hosts: all
sources:
- ansible.eda.alertmanager:
host: 0.0.0.0
port: 5001
rules:
- name: Trigger AAP Job Template Launch on Alert
condition: event.payload is defined and event.payload.status == "firing"
actions:
- run_job_template:
name: "KubeVirt Increase Socket for a running Virtual Machine"
organization: Default
job_args:
extra_vars:
vm_name: "{{ event.payload.alerts[0].labels.name }}"
vm_namespace: "{{ event.payload.alerts[0].labels.namespace }}"이전 코드 조각에서 볼 수 있듯이, 이 규칙서는 AlertManager에서 “firing” 상태의 알림을 수신하면 “Default” 조직에서 “실행 중인 가상 머신에 대한 KubeVirt Increase Socket” 작업 템플릿을 시작하는 규칙을 정의합니다. 또한 VM의 이름과 네임스페이스를 추가 변수로 작업 템플릿에 전달합니다.
vm-increase-sockets.yaml: 이 파일에는 VM의 CPU 소켓을 늘리는 Ansible 플레이북이 포함되어 있습니다. 이 플레이북은 AAP에서 작업 템플릿으로 구성됩니다.
---
- name: Increment KubeVirt VM CPU Sockets
hosts: localhost # Run on the machine where Ansible is executed
connection: local # Interact with the k8s API locally, not via SSH
gather_facts: false # No need to gather facts about the localhost
# Define the target VM details here
vars:
vm_name: "my-kubevirt-vm" # <--- We need to override THIS to our VM name
vm_namespace: "default" # <--- We need to override THIS to our VM namespace
tasks:
- name: 1. Get current KubeVirt VM object
kubernetes.core.k8s_info:
api_version: kubevirt.io/v1
kind: VirtualMachine
name: "{{ vm_name }}"
namespace: "{{ vm_namespace }}"
register: vm_info
# Handle potential connection errors or if the VM doesn't exist
failed_when: "vm_info.resources | length == 0"
vars:
# Custom error message if VM not found
ansible_failed_result:
msg: "Failed to find KubeVirt VirtualMachine '{{ vm_name }}' in namespace '{{ vm_namespace }}'."
- name: 2. Extract current socket count and calculate new value
ansible.builtin.set_fact:
# Extract the current value. Use default(1) if 'sockets' is not defined in the spec.
# Ensure it's treated as an integer.
current_sockets: "{{ (vm_info.resources[0].spec.domain.cpu.sockets | default(1)) | int }}"
# Calculate the new value
new_sockets: "{{ ((vm_info.resources[0].spec.domain.cpu.sockets | default(1)) | int) + 1 }}"
- name: Display socket update plan
ansible.builtin.debug:
msg: "VM '{{ vm_name }}' in ns '{{ vm_namespace }}': Planning to update sockets from {{ current_sockets }} to {{ new_sockets }}"
- name: 3. Patch VM using kubectl patch command
ansible.builtin.command:
cmd: >
kubectl patch virtualmachine {{ vm_name | quote }} -n {{ vm_namespace | quote }}
--server={{ lookup('env', 'K8S_AUTH_HOST') | quote }}
--token={{ lookup('env', 'K8S_AUTH_API_KEY') | quote }}
--insecure-skip-tls-verify=true
--type='json'
-p='[{"op": "replace", "path": "/spec/template/spec/domain/cpu/sockets", "value": {{ new_sockets }} }]'
register: kubectl_patch_result
# Check the output of kubectl to see if a change was made
changed_when: "'patched' in kubectl_patch_result.stdout"
- name: Show Kubectl Patch Result (if changed)
ansible.builtin.debug:
var: kubectl_patch_result.stdout # Show kubectl output if changed
when: kubectl_patch_result is defined and kubectl_patch_result.changed이전 코드 조각에서 볼 수 있듯이 이 플레이북은 다음과 같은 작업을 수행합니다.
- VM 정보 가져오기: CPU 소켓 수를 포함하여 VM의 현재 구성을 검색합니다.
- 새 소켓 수 계산: 현재 소켓 수를 하나 증가시킵니다.
- VM 구성 패치: kubectl patch 명령을 사용하여 VM 사양을 새로운 소켓 수로 업데이트합니다.
kubectl 패치 사용에 대한 참고 사항:
이 플레이북의 원래 구현에서는 kubernetes.core.k8s_json_patch Ansible 모듈을 사용하여 VM의 CPU 소켓을 업데이트했습니다. 그러나 Ansible을 통해 Kubernetes API와 상호 작용하고 JSON/YAML 타이핑을 처리할 때 흔히 발생하는 함정에 직면했습니다. 초기 작업은 다음과 같았습니다.
- name: 3. Patch VM with the incremented socket count using JSON Patch
kubernetes.core.k8s_json_patch:
api_version: kubevirt.io/v1 # <--- Added api_version
kind: VirtualMachine
namespace: "{{ vm_namespace }}"
name: "{{ vm_name }}"
patch:
- op: replace
path: /spec/template/spec/domain/cpu/sockets
value: "{{ new_sockets | int }}"
register: patch_result
when: new_sockets != current_socketsJinja2에서 | int 필터를 사용하여 new_sockets 변수가 정수인지 확인했음에도 불구하고 Kubernetes API는 오류를 반환했습니다.
Failed to patch object:
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "admission webhook 'mutatevirtualmachines.kubemacpool.io' denied the request: json: cannot unmarshal string into Go struct field VirtualMachineInstanceTemplateSpec.spec.template.spec of type uint32",
"code": 400
}이 오류는 API가 소켓 개수를 JSON 숫자(예: 2) 대신 JSON 문자열(예: “2”)로 수신했음을 나타냅니다.
Ansible의 int 필터는 값을 정수로 변환해야 하지만, kubernetes.core.k8s_json_patch 모듈은 Ansible의 YAML/Jinja2 처리와 결합하여 최종 JSON 페이로드에서 값이 문자열로 직렬화되어 API에 전송됩니다.
KubeVirt API는 소켓 수에 정수(uint32)를 기대하므로 요청이 실패합니다. 이 타이핑 문제를 해결하고 올바른 JSON 페이로드가 Kubernetes API로 전송되도록 하기 위해, kubectl patch 명령을 직접 사용하도록 플레이북을 수정했습니다.
이 접근 방식은 적용되는 JSON 패치에 대한 보다 명확한 제어를 제공하고 Ansible 모듈 내에서 발생할 수 있는 암묵적인 유형 변환을 방지합니다.
전체 Ansible 플레이북은 다음 GitHub 저장소에서 찾을 수 있습니다: https://github.com/alezzandro/ansible-eda-kubevirt-verticalautoscaler
환경 설정 및 워크플로
실제 환경에서 솔루션을 시연하기 위해 다음과 같은 구성이 이루어졌습니다.
AAP 자동화 컨트롤러 구성
- Ansible 플레이북이 포함된 GitHub 저장소와 연관된 프로젝트가 Automation Controller에서 생성되었습니다.
- OpenShift 자격 증명이 구성되어 Automation Controller가 OpenShift 클러스터에 인증할 수 있도록 했습니다.
- vm-increase-sockets.yaml 플레이북을 참조하는 플레이북 템플릿이 정의되었습니다. 다음 스크린샷에서 볼 수 있듯이 이 템플릿을 사용하면 EDA에서 플레이북을 실행할 수 있습니다.

AAP 자동화 결정 컨트롤러 구성
- Automation Decision Controller에서 프로젝트가 생성되었으며, 동일한 GitHub 저장소와 연관되어 있습니다.
- AlertManager 웹후크에서 이벤트를 수신하도록 이벤트 스트림이 구성되었습니다.
- AAP Automation Controller에서 작업 템플릿을 연결하고 트리거할 수 있도록 Decision Controller에 자격 증명이 구성되었습니다.
- 마지막으로 이벤트 스트림, 자격 증명, 그리고 eda-vm-rulebook.yaml 파일을 연결하는 Rulebook Activation이 생성되었습니다. 아래 스크린샷에서 볼 수 있듯이, 이를 통해 이벤트 기반 자동화 로직이 활성화됩니다.

OpenShift Virtualization 플랫폼
- OpenShift Virtualization은 OpenShift 베어메탈 클러스터에 설치 및 구성되었습니다.
- OpenShift Virtualization 내에 가상 머신이 생성되었습니다. 아래 스크린샷에서 볼 수 있듯이 이 VM은 자동 CPU 스케일링의 대상이 됩니다.
- 가상 머신의 CPU 사용량을 모니터링하기 위해 사용자 지정 Prometheus 규칙이 배포되었습니다. 다음 스니펫에서 볼 수 있듯이, 이 규칙은 VM의 CPU 사용량이 1분 동안 100%를 초과하면 cpuVmUsageCritical이라는 알림을 트리거합니다. 이 알림에는 레이블(심각도: ‘중요’, vertautoscale: ‘참’)과 추가 정보를 제공하는 주석이 포함됩니다. 규칙은 다음과 같이 정의됩니다.
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: vm-cpu-load-alert
namespace: openshift-monitoring
spec:
groups:
- name: vmCpuLoad
rules:
- alert: cpuVmUsageCritical
expr: >
rate(kubevirt_vmi_cpu_usage_seconds_total[1m]) > 1
for: 1m
labels:
severity: 'critical'
vertautoscale: 'true'
annotations:
description: 'Total CPU usage of the VirtualMachine {{ $labels.name }} is more than 100% for the last 1 minute'
summary: 'Total CPU usage of the VirtualMachine {{ $labels.name }} is more than 100% for the last 1 minute'- AlertManager는 AAP EDA 이벤트 스트림으로 알림을 전송하도록 구성되었습니다. 다음 스니펫에서 볼 수 있듯이 AnsibleEDA 수신기는 vertautoscale = true 레이블이 지정된 알림을 AAP EDA 이벤트 스트림으로 전송하도록 구성되었습니다. 구성은 다음과 같습니다.
global:
http_config:
proxy_from_environment: true
inhibit_rules:
- equal:
- namespace
- alertname
source_matchers:
- severity = critical
target_matchers:
- severity =~ warning|info
- equal:
- namespace
- alertname
source_matchers:
- severity = warning
target_matchers:
- severity = info
receivers:
- name: Critical
- name: Default
- name: Watchdog
- name: AnsibleEDA
webhook_configs:
- url: >-
https://aapinternal-aap.apps.ocpcluster.mydomain.local/eda-event-streams/api/eda/v1/external_event_stream/d68a64ed-d547-4021-b384-340df7443929/post/
http_config:
basic_auth:
username: openshift
password: P4ssw0rd1
tls_config:
insecure_skip_verify: true
route:
group_by:
- namespace
group_interval: 5m
group_wait: 30s
receiver: Default
repeat_interval: 12h
routes:
- matchers:
- vertautoscale = true
receiver: AnsibleEDA
- matchers:
- alertname = Watchdog
receiver: Watchdog
- receiver: Critical
matchers:
- severity = critical- 높은 CPU 부하 시나리오를 시뮬레이션하고 자동화를 트리거하기 위해 가상 머신 내에서 다음 명령을 실행했습니다. 이 명령은 stress-ng 도구를 사용하여 7분 동안 하나의 CPU에 100% CPU 부하를 발생시킵니다.
$ stress-ng -c 1 -l 100 -t 7m &
결론
OpenShift의 AlertManager를 Ansible 이벤트 기반 자동화와 통합하면 실시간 지표에 따라 VM 리소스를 확장하는 강력하고 자동화된 솔루션을 구축할 수 있습니다. 이러한 접근 방식은 동적 리소스 할당을 지원하고, 애플리케이션 성능을 최적화하며, 전반적인 시스템 효율성을 향상시킵니다. 수직 자동 확장은 수평 자동 확장과 함께 전략적으로 사용해야 하지만, OpenShift Virtualization에서 까다롭고 변동이 심한 워크로드를 관리하는 데 유용한 도구를 제공합니다.