컨테이너 인사이트에서 로그 검색 경고 만들기

컨테이너 인사이트는 관리되거나 스스로 관리하는 Kubernetes 클러스터에 배포된 컨테이너 워크로드의 성능을 모니터링합니다. 중요한 문제가 생겼을 경우 경고하기 위해, 이 문서에서는 AKS(Azure Kubernetes Service) 클러스터를 사용하여 다음과 같은 상황에 대한 로그 기반 경고를 만드는 방법을 설명합니다.

  • 클러스터 노드의 CPU 또는 메모리 사용률이 임계값을 초과하는 경우
  • 컨트롤러 내 컨테이너의 CPU 또는 메모리 사용률이 해당 리소스에 설정된 제한과 비교하여 임계값을 초과하는 경우
  • NotReady 상태 노드 수
  • Failed, Pending, Unknown, Running 또는 Succeeded Pod 단계 수
  • 클러스터 노드의 사용 가능한 디스크 공간이 임계값을 초과하는 경우

높은 CPU나 메모리 사용률, 또는 클러스터 노드에서 사용 가능한 디스크 공간 부족에 대해 경고하려면 제공된 쿼리를 사용하여 메트릭 경고 또는 메트릭 측정 경고를 만듭니다. 메트릭 경고는 로그 검색 경고보다 대기 시간이 짧지만 로그 검색 경고는 고급 쿼리와 더 높은 정교함을 제공합니다. 로그 검색 경고 쿼리는 now 연산자를 사용하고 1시간 이전으로 돌아가는 방법으로 현재와 날짜/시간을 비교합니다. (컨테이너 인사이트는 모든 날짜를 UTC[협정 세계시] 형식으로 저장합니다.)

Important

이 문서의 쿼리는 컨테이너 인사이트로 수집되고 Log Analytics 작업 영역에 저장된 데이터에 따라 달라집니다. 기본 데이터 수집 설정을 수정한 경우 쿼리가 예상한 결과를 반환하지 않을 수 있습니다. 특히 클러스터에 대해 Prometheus 메트릭을 사용하도록 설정한 이후 성능 데이터 컬렉션을 사용하지 않도록 설정한 경우 Perf 테이블을 사용하는 쿼리는 결과를 반환하지 않습니다.

성능 데이터 수집 사용 중지를 포함한 사전 설정 구성은 데이터 수집 규칙을 사용하여 컨테이너 인사이트에서 데이터 수집 구성을 참조하세요. 추가 데이터 수집 옵션은 ConfigMap을 사용하여 컨테이너 인사이트에서 데이터 수집 구성을 참조하세요.

Azure Monitor 경고에 익숙하지 않은 경우 시작하기 전에 Microsoft Azure의 경고 개요를 참조하세요. 로그 검색를 사용하는 경고에 대해 자세히 알아보려면 Azure Monitor의 로그 검색 경고를 참조하세요. 메트릭 경고에 대한 자세한 내용은 Azure Monitor의 메트릭 경고를 참조하세요.

로그 쿼리 측정값

로그 검색 경고는 다양한 시나리오에서 가상 머신을 모니터링하는 데 사용할 수 있는 두 가지 항목을 측정할 수 있습니다.

  • 결과 수: 쿼리에서 반환된 행 수를 계산하고 Windows 이벤트 로그, Syslog, 애플리케이션 예외와 같은 이벤트를 처리하는 데 사용할 수 있습니다.
  • 값 계산: 숫자 열을 기반으로 계산을 수행하며 원하는 수의 리소스를 포함하는 데 사용할 수 있습니다. 예를 들어, CPU 백분율이 있습니다.

리소스 및 차원 대상 지정

하나의 규칙을 사용하여 차원을 사용하여 여러 인스턴스의 값을 모니터링할 수 있습니다. 예를 들어, 웹 사이트 또는 앱을 실행하는 여러 인스턴스에서 CPU 사용량을 모니터링하고 80% 이상의 CPU 사용량에 대한 경고를 만들려는 경우 차원을 사용합니다.

구독 또는 리소스 그룹에 대해 대규모 리소스 중심 경고를 만들려면 차원으로 분할할 수 있습니다. 여러 Azure 리소스에서 동일한 조건을 모니터링하려는 경우 차원을 기준으로 분할하면 숫자 또는 문자열 열을 사용하여 고유한 조합을 그룹화하여 경고를 별도의 경고로 분할합니다. Azure 리소스 ID 열을 분할하면 지정된 리소스가 경고 대상이 됩니다.

해당 범위의 여러 리소스에 대한 조건을 원하는 경우 분할하지 않을 수도 있습니다. 예를 들어, 리소스 그룹 범위에 있는 5개 이상의 컴퓨터에서 CPU 사용량이 80%를 초과하는 경우 경고를 만들려고 할 수 있습니다.

차원별로 분할된 새로운 로그 검색 경고 규칙을 보여 주는 스크린샷.

영향을 받는 컴퓨터의 경고 목록을 보고 싶을 수 있습니다. 사용자 지정 리소스 그래프를 사용하여 이 보기를 제공하는 사용자 지정 통합 문서를 사용할 수 있습니다. 다음 쿼리를 사용하여 경고를 표시하고 통합 문서의 데이터 원본 Azure Resource Graph를 사용합니다.

로그 검색 경고 규칙 만들기

포털을 사용하여 로그 검색 경고 규칙을 만들려면 전체 안내를 제공하는 로그 검색 경고의 예를 참조하세요. 이러한 동일한 프로세스를 사용하여 이 문서의 쿼리와 유사한 쿼리를 사용하여 AKS 클러스터에 대한 경고 규칙을 만들 수 있습니다.

ARM(Azure Resource Manager) 템플릿을 사용하여 쿼리 경고 규칙을 만들려면 Azure Monitor의 로그 검색 경고 규칙에 대한 Resource Manager 템플릿 샘플을 참조하세요. 이러한 동일한 프로세스를 사용하여 이 문서의 로그 쿼리에 대한 ARM 템플릿을 만들 수 있습니다.

리소스 사용률

분당 멤버 노드 CPU 활용률의 평균으로 계산한 평균 CPU 활용률(메트릭 측정값):

let endDateTime = now();
let startDateTime = ago(1h);
let trendBinSize = 1m;
let capacityCounterName = 'cpuCapacityNanoCores';
let usageCounterName = 'cpuUsageNanoCores';
KubeNodeInventory
| where TimeGenerated < endDateTime
| where TimeGenerated >= startDateTime
// cluster filter would go here if multiple clusters are reporting to the same Log Analytics workspace
| distinct ClusterName, Computer
| join hint.strategy=shuffle (
  Perf
  | where TimeGenerated < endDateTime
  | where TimeGenerated >= startDateTime
  | where ObjectName == 'K8SNode'
  | where CounterName == capacityCounterName
  | summarize LimitValue = max(CounterValue) by Computer, CounterName, bin(TimeGenerated, trendBinSize)
  | project Computer, CapacityStartTime = TimeGenerated, CapacityEndTime = TimeGenerated + trendBinSize, LimitValue
) on Computer
| join kind=inner hint.strategy=shuffle (
  Perf
  | where TimeGenerated < endDateTime + trendBinSize
  | where TimeGenerated >= startDateTime - trendBinSize
  | where ObjectName == 'K8SNode'
  | where CounterName == usageCounterName
  | project Computer, UsageValue = CounterValue, TimeGenerated
) on Computer
| where TimeGenerated >= CapacityStartTime and TimeGenerated < CapacityEndTime
| project ClusterName, Computer, TimeGenerated, UsagePercent = UsageValue * 100.0 / LimitValue
| summarize AggValue = avg(UsagePercent) by bin(TimeGenerated, trendBinSize), ClusterName

분당 멤버 노드 메모리 활용률의 평균으로 계산한 평균 메모리 활용률(메트릭 측정값):

let endDateTime = now();
let startDateTime = ago(1h);
let trendBinSize = 1m;
let capacityCounterName = 'memoryCapacityBytes';
let usageCounterName = 'memoryRssBytes';
KubeNodeInventory
| where TimeGenerated < endDateTime
| where TimeGenerated >= startDateTime
// cluster filter would go here if multiple clusters are reporting to the same Log Analytics workspace
| distinct ClusterName, Computer
| join hint.strategy=shuffle (
  Perf
  | where TimeGenerated < endDateTime
  | where TimeGenerated >= startDateTime
  | where ObjectName == 'K8SNode'
  | where CounterName == capacityCounterName
  | summarize LimitValue = max(CounterValue) by Computer, CounterName, bin(TimeGenerated, trendBinSize)
  | project Computer, CapacityStartTime = TimeGenerated, CapacityEndTime = TimeGenerated + trendBinSize, LimitValue
) on Computer
| join kind=inner hint.strategy=shuffle (
  Perf
  | where TimeGenerated < endDateTime + trendBinSize
  | where TimeGenerated >= startDateTime - trendBinSize
  | where ObjectName == 'K8SNode'
  | where CounterName == usageCounterName
  | project Computer, UsageValue = CounterValue, TimeGenerated
) on Computer
| where TimeGenerated >= CapacityStartTime and TimeGenerated < CapacityEndTime
| project ClusterName, Computer, TimeGenerated, UsagePercent = UsageValue * 100.0 / LimitValue
| summarize AggValue = avg(UsagePercent) by bin(TimeGenerated, trendBinSize), ClusterName

Important

다음 쿼리는 자리 표시자 값 <your-cluster-name> 및 <your-controller-name>을 사용하여 클러스터와 컨트롤러를 나타냅니다. 경고 설정 시 해당 값을 사용자 환경에 맞는 값으로 바꿉니다.

1분마다 컨트롤러에 있는 모든 컨테이너 인스턴스의 평균 CPU 사용률로 계산한 컨트롤러에 있는 모든 컨테이너의 평균 CPU 사용률(메트릭 측정값):

let endDateTime = now();
let startDateTime = ago(1h);
let trendBinSize = 1m;
let capacityCounterName = 'cpuLimitNanoCores';
let usageCounterName = 'cpuUsageNanoCores';
let clusterName = '<your-cluster-name>';
let controllerName = '<your-controller-name>';
KubePodInventory
| where TimeGenerated < endDateTime
| where TimeGenerated >= startDateTime
| where ClusterName == clusterName
| where ControllerName == controllerName
| extend InstanceName = strcat(ClusterId, '/', ContainerName),
         ContainerName = strcat(controllerName, '/', tostring(split(ContainerName, '/')[1]))
| distinct Computer, InstanceName, ContainerName
| join hint.strategy=shuffle (
    Perf
    | where TimeGenerated < endDateTime
    | where TimeGenerated >= startDateTime
    | where ObjectName == 'K8SContainer'
    | where CounterName == capacityCounterName
    | summarize LimitValue = max(CounterValue) by Computer, InstanceName, bin(TimeGenerated, trendBinSize)
    | project Computer, InstanceName, LimitStartTime = TimeGenerated, LimitEndTime = TimeGenerated + trendBinSize, LimitValue
) on Computer, InstanceName
| join kind=inner hint.strategy=shuffle (
    Perf
    | where TimeGenerated < endDateTime + trendBinSize
    | where TimeGenerated >= startDateTime - trendBinSize
    | where ObjectName == 'K8SContainer'
    | where CounterName == usageCounterName
    | project Computer, InstanceName, UsageValue = CounterValue, TimeGenerated
) on Computer, InstanceName
| where TimeGenerated >= LimitStartTime and TimeGenerated < LimitEndTime
| project Computer, ContainerName, TimeGenerated, UsagePercent = UsageValue * 100.0 / LimitValue
| summarize AggValue = avg(UsagePercent) by bin(TimeGenerated, trendBinSize) , ContainerName

1분마다 컨트롤러에 있는 모든 컨테이너 인스턴스의 평균 메모리 사용률로 계산한 컨트롤러에 있는 모든 컨테이너의 평균 메모리 사용률(메트릭 측정값):

let endDateTime = now();
let startDateTime = ago(1h);
let trendBinSize = 1m;
let capacityCounterName = 'memoryLimitBytes';
let usageCounterName = 'memoryRssBytes';
let clusterName = '<your-cluster-name>';
let controllerName = '<your-controller-name>';
KubePodInventory
| where TimeGenerated < endDateTime
| where TimeGenerated >= startDateTime
| where ClusterName == clusterName
| where ControllerName == controllerName
| extend InstanceName = strcat(ClusterId, '/', ContainerName),
         ContainerName = strcat(controllerName, '/', tostring(split(ContainerName, '/')[1]))
| distinct Computer, InstanceName, ContainerName
| join hint.strategy=shuffle (
    Perf
    | where TimeGenerated < endDateTime
    | where TimeGenerated >= startDateTime
    | where ObjectName == 'K8SContainer'
    | where CounterName == capacityCounterName
    | summarize LimitValue = max(CounterValue) by Computer, InstanceName, bin(TimeGenerated, trendBinSize)
    | project Computer, InstanceName, LimitStartTime = TimeGenerated, LimitEndTime = TimeGenerated + trendBinSize, LimitValue
) on Computer, InstanceName
| join kind=inner hint.strategy=shuffle (
    Perf
    | where TimeGenerated < endDateTime + trendBinSize
    | where TimeGenerated >= startDateTime - trendBinSize
    | where ObjectName == 'K8SContainer'
    | where CounterName == usageCounterName
    | project Computer, InstanceName, UsageValue = CounterValue, TimeGenerated
) on Computer, InstanceName
| where TimeGenerated >= LimitStartTime and TimeGenerated < LimitEndTime
| project Computer, ContainerName, TimeGenerated, UsagePercent = UsageValue * 100.0 / LimitValue
| summarize AggValue = avg(UsagePercent) by bin(TimeGenerated, trendBinSize) , ContainerName

사용 가능한 리소스

상태가 Ready 및 NotReady인 노드 및 개수(메트릭 측정값):

let endDateTime = now();
let startDateTime = ago(1h);
let trendBinSize = 1m;
let clusterName = '<your-cluster-name>';
KubeNodeInventory
| where TimeGenerated < endDateTime
| where TimeGenerated >= startDateTime
| distinct ClusterName, Computer, TimeGenerated
| summarize ClusterSnapshotCount = count() by bin(TimeGenerated, trendBinSize), ClusterName, Computer
| join hint.strategy=broadcast kind=inner (
    KubeNodeInventory
    | where TimeGenerated < endDateTime
    | where TimeGenerated >= startDateTime
    | summarize TotalCount = count(), ReadyCount = sumif(1, Status contains ('Ready'))
                by ClusterName, Computer,  bin(TimeGenerated, trendBinSize)
    | extend NotReadyCount = TotalCount - ReadyCount
) on ClusterName, Computer, TimeGenerated
| project   TimeGenerated,
            ClusterName,
            Computer,
            ReadyCount = todouble(ReadyCount) / ClusterSnapshotCount,
            NotReadyCount = todouble(NotReadyCount) / ClusterSnapshotCount
| order by ClusterName asc, Computer asc, TimeGenerated desc

다음 쿼리는 모든 단계(Failed, Pending, Unknown, Running 또는 Succeeded)를 기반으로 Pod 단계 개수를 반환합니다.

let endDateTime = now(); 
let startDateTime = ago(1h);
let trendBinSize = 1m;
let clusterName = '<your-cluster-name>';
KubePodInventory
    | where TimeGenerated < endDateTime
    | where TimeGenerated >= startDateTime
    | where ClusterName == clusterName
    | distinct ClusterName, TimeGenerated
    | summarize ClusterSnapshotCount = count() by bin(TimeGenerated, trendBinSize), ClusterName
    | join hint.strategy=broadcast (
        KubePodInventory
        | where TimeGenerated < endDateTime
        | where TimeGenerated >= startDateTime
        | summarize PodStatus=any(PodStatus) by TimeGenerated, PodUid, ClusterName
        | summarize TotalCount = count(),
                    PendingCount = sumif(1, PodStatus =~ 'Pending'),
                    RunningCount = sumif(1, PodStatus =~ 'Running'),
                    SucceededCount = sumif(1, PodStatus =~ 'Succeeded'),
                    FailedCount = sumif(1, PodStatus =~ 'Failed')
                by ClusterName, bin(TimeGenerated, trendBinSize)
    ) on ClusterName, TimeGenerated
    | extend UnknownCount = TotalCount - PendingCount - RunningCount - SucceededCount - FailedCount
    | project TimeGenerated,
              TotalCount = todouble(TotalCount) / ClusterSnapshotCount,
              PendingCount = todouble(PendingCount) / ClusterSnapshotCount,
              RunningCount = todouble(RunningCount) / ClusterSnapshotCount,
              SucceededCount = todouble(SucceededCount) / ClusterSnapshotCount,
              FailedCount = todouble(FailedCount) / ClusterSnapshotCount,
              UnknownCount = todouble(UnknownCount) / ClusterSnapshotCount
| summarize AggValue = avg(PendingCount) by bin(TimeGenerated, trendBinSize)

참고 항목

Pending, Failed 또는 Unknown과 같은 특정 Pod 단계에 대해 경고하려면 쿼리의 마지막 줄을 수정합니다. 예를 들어, FailedCount에 대해 경고하려면 | summarize AggValue = avg(FailedCount) by bin(TimeGenerated, trendBinSize)를 사용합니다.

다음 쿼리는 사용된 사용 가능한 공간의 90%를 초과하는 클러스터 노드 디스크를 반환합니다. 클러스터 ID를 가져오려면 먼저 다음 쿼리를 실행하고 ClusterId 속성의 값을 복사합니다.

InsightsMetrics
| extend Tags = todynamic(Tags)            
| project ClusterId = Tags['container.azm.ms/clusterId']   
| distinct tostring(ClusterId)   
let clusterId = '<cluster-id>';
let endDateTime = now();
let startDateTime = ago(1h);
let trendBinSize = 1m;
InsightsMetrics
| where TimeGenerated < endDateTime
| where TimeGenerated >= startDateTime
| where Origin == 'container.azm.ms/telegraf'            
| where Namespace == 'container.azm.ms/disk'            
| extend Tags = todynamic(Tags)            
| project TimeGenerated, ClusterId = Tags['container.azm.ms/clusterId'], Computer = tostring(Tags.hostName), Device = tostring(Tags.device), Path = tostring(Tags.path), DiskMetricName = Name, DiskMetricValue = Val   
| where ClusterId =~ clusterId       
| where DiskMetricName == 'used_percent'
| summarize AggValue = max(DiskMetricValue) by bin(TimeGenerated, trendBinSize)
| where AggValue >= 90

개별 시스템 컨테이너 다시 시작 수가 지난 10분 동안 임계값을 초과하면 개별 컨테이너 다시 시작(결과 수) 경고가 발생합니다.

let _threshold = 10m; 
let _alertThreshold = 2;
let Timenow = (datetime(now) - _threshold); 
let starttime = ago(5m); 
KubePodInventory
| where TimeGenerated >= starttime
| where Namespace in ('default', 'kube-system') // the namespace filter goes here
| where ContainerRestartCount > _alertThreshold
| extend Tags = todynamic(ContainerLastStatus)
| extend startedAt = todynamic(Tags.startedAt)
| where startedAt >= Timenow
| summarize arg_max(TimeGenerated, *) by Name

다음 단계