tutorial

Monitoring KubeLinter YAML Linting Pipelines with Vigilmon: CI/CD Gate Heartbeats, Helm Chart Validation, TLS Certificate Alerts & Policy Enforcement Health Checks

How to use Vigilmon to monitor KubeLinter Kubernetes YAML linting pipelines — CI/CD gate heartbeats, Helm chart validation jobs, and TLS certificate monitoring for KubeLinter-integrated policy enforcement workflows.

KubeLinter is an open-source static analysis tool from StackRox (Red Hat) that checks Kubernetes YAML files and Helm charts against a library of security and correctness best practices. KubeLinter evaluates objects for issues like containers running as root, missing read-only root file systems, absent resource limits, privileged containers, and missing network policies — producing detailed actionable findings before manifests ever reach a cluster. When KubeLinter's CI gate is silently disabled, when a Helm chart linting job stops running, or when the policy enforcement workflow breaks, non-compliant Kubernetes resources reach production undetected. Vigilmon gives you heartbeat monitoring for every KubeLinter pipeline stage that enforces your Kubernetes security baseline.

What You'll Build

  • Heartbeat monitors for KubeLinter CI/CD pipeline gates on pull requests
  • Heartbeat monitors for scheduled full-repository lint runs
  • Heartbeat monitors for Helm chart-specific KubeLinter validation
  • HTTP and SSL monitors for any API endpoints in the linting workflow
  • Alerting runbook mapping KubeLinter pipeline failures to Vigilmon signals

Prerequisites

  • KubeLinter installed (brew install kubelinter or download from StackRox releases)
  • Kubernetes YAML manifests or Helm charts in a repository
  • CI/CD pipeline (GitHub Actions, GitLab CI, or similar)
  • A free account at vigilmon.online

Step 1: Understand the KubeLinter Pipeline Health Surface

KubeLinter is a CLI tool that runs inside CI/CD pipelines and has no persistent HTTP service. All health signals come from pipeline execution:

# Lint a single Kubernetes manifest
kube-linter lint deployment.yaml
# Expected: Pass or list of lint findings with severity

# Lint all YAML files in a directory
kube-linter lint k8s/
# Expected: Combined lint output, exit code 1 on findings

# Lint a Helm chart
kube-linter lint charts/myapp/
# Expected: Chart templates evaluated against all checks

# List all available checks
kube-linter checks list
# Expected: Table of check name, description, remediation

# Run with custom policy configuration
kube-linter lint --config .kube-linter.yaml k8s/
# Expected: Only checks defined in config file

# Output as JSON for programmatic processing
kube-linter lint --format json k8s/ | python3 -c "
import sys, json
results = json.load(sys.stdin)
for item in results.get('Items', []):
    print(f\"{item['Object']['K8sObject']['Name']}: {item['Diagnostic']['Message']}\")
"

# Return codes
# 0 — no lint findings
# 1 — lint findings found
# Other — internal error (file not found, parse failure)

The key health signals to monitor:

  1. KubeLinter CI gate running on every pull request that touches YAML or Helm files
  2. Scheduled full-repository lint runs to catch drift in existing manifests
  3. Helm-specific validation pipelines
  4. Any notification or ticketing endpoints in the linting workflow

Step 2: Heartbeat Monitoring for KubeLinter CI Gates

Pull Request Manifest Gate

A GitHub Actions workflow that blocks merges when KubeLinter finds security or correctness issues:

# .github/workflows/kubelinter.yml
name: KubeLinter YAML analysis

on:
  pull_request:
    paths:
      - 'k8s/**'
      - '**.yaml'
      - '**.yml'

jobs:
  kubelinter:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install KubeLinter
        run: |
          curl -sL https://github.com/stackrox/kube-linter/releases/latest/download/kube-linter-linux.tar.gz | \
            tar xzf - -C /usr/local/bin kube-linter

      - name: Lint Kubernetes manifests
        run: kube-linter lint k8s/

      - name: Lint Helm charts
        run: |
          for CHART in charts/*/; do
            echo "Linting: ${CHART}"
            kube-linter lint "${CHART}"
          done

      - name: Ping Vigilmon heartbeat
        if: success()
        run: curl -s https://vigilmon.online/heartbeat/abc123

Set up the heartbeat monitor in Vigilmon:

  1. Log in to VigilmonAdd Monitor → Cron Heartbeat.
  2. Expected interval: 1500 minutes (expected on every PR with active development; 1-day grace period).
  3. Copy the heartbeat URL: https://vigilmon.online/heartbeat/abc123.
  4. Paste it as the final step in the workflow.
  5. Label: KubeLinter PR gate — YAML manifests.
  6. Click Save.

KubeLinter with Custom Policy Configuration

Teams often configure a .kube-linter.yaml policy file to customize which checks are enforced. Make sure the custom config is linted too:

# .kube-linter.yaml (checked into the repository)
customChecks: []
checks:
  addAllBuiltIn: true
  exclude:
    - "unset-cpu-requirements"  # tracked separately in VIG-XXXX
  include:
    - "privileged-container"
    - "run-as-non-root"
    - "read-only-root-filesystem"
    - "no-read-only-root-fs-for-init-containers"
    - "required-label-owner"
    - "env-var-secret"
    - "no-extensions-v1beta"
    - "resource-requirements"
# .github/workflows/kubelinter-policy.yml
name: KubeLinter with custom policy

on:
  pull_request:
    paths:
      - 'k8s/**'
      - 'charts/**'
      - '.kube-linter.yaml'

jobs:
  kubelinter-policy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install KubeLinter
        run: |
          curl -sL https://github.com/stackrox/kube-linter/releases/latest/download/kube-linter-linux.tar.gz | \
            tar xzf - -C /usr/local/bin kube-linter

      - name: Lint with custom policy
        run: kube-linter lint --config .kube-linter.yaml k8s/ charts/

      - name: Validate policy file itself
        run: kube-linter checks list --config .kube-linter.yaml

      - name: Ping Vigilmon heartbeat
        if: success()
        run: curl -s https://vigilmon.online/heartbeat/def456
  1. Add Monitor → Cron Heartbeat.
  2. Expected interval: 1500 minutes.
  3. Label: KubeLinter PR gate — custom policy.
  4. Click Save.

Step 3: Scheduled Full-Repository Lint Run

A nightly job that runs KubeLinter across the entire repository, including manifests not recently modified:

# .github/workflows/kubelinter-nightly.yml
name: KubeLinter nightly full scan

on:
  schedule:
    - cron: '0 2 * * *'

jobs:
  full-lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install KubeLinter
        run: |
          curl -sL https://github.com/stackrox/kube-linter/releases/latest/download/kube-linter-linux.tar.gz | \
            tar xzf - -C /usr/local/bin kube-linter

      - name: Full repository lint
        run: |
          kube-linter lint --format json k8s/ charts/ > /tmp/lint-results.json 2>&1 || true
          
          FINDING_COUNT=$(python3 -c "
import json
with open('/tmp/lint-results.json') as f:
    results = json.load(f)
print(len(results.get('Items', [])))
" 2>/dev/null || echo "0")
          
          echo "KubeLinter findings: ${FINDING_COUNT}"
          
          # Fail if findings exceed a configured threshold
          MAX_ALLOWED=0
          if [ "${FINDING_COUNT}" -gt "${MAX_ALLOWED}" ]; then
            echo "FAIL: ${FINDING_COUNT} lint findings exceed threshold of ${MAX_ALLOWED}"
            cat /tmp/lint-results.json | python3 -c "
import sys, json
for item in json.load(sys.stdin).get('Items', []):
    print(f\"  {item['Object']['K8sObject']['Name']}: {item['Diagnostic']['Message']}\")
"
            exit 1
          fi

      - name: Ping Vigilmon heartbeat
        if: success()
        run: curl -s https://vigilmon.online/heartbeat/ghi789

Set up the heartbeat monitor:

  1. Add Monitor → Cron Heartbeat.
  2. Expected interval: 1500 minutes (nightly at 02:00 UTC, 60-minute grace period).
  3. Label: KubeLinter nightly full scan.
  4. Click Save.

Step 4: Helm Chart KubeLinter Pipeline

A dedicated Helm chart linting pipeline that catches issues specific to Helm-templated output:

#!/bin/bash
# helm-kubelinter.sh — lint all Helm charts in the repository

CHARTS_DIR="${1:-charts/}"
VIGILMON_HB="https://vigilmon.online/heartbeat/jkl012"
FAILED=0

# Verify Helm and kube-linter are available
if ! command -v helm &>/dev/null; then
  echo "ERROR: helm not found on PATH"
  exit 1
fi
if ! command -v kube-linter &>/dev/null; then
  echo "ERROR: kube-linter not found on PATH"
  exit 1
fi

for CHART in "${CHARTS_DIR}"*/; do
  CHART_NAME=$(basename "${CHART}")
  echo "=== Linting Helm chart: ${CHART_NAME} ==="

  # Update chart dependencies
  helm dependency update "${CHART}" 2>/dev/null

  # Run kube-linter directly on the chart directory
  if ! kube-linter lint "${CHART}" --config .kube-linter.yaml; then
    echo "FAIL: ${CHART_NAME} has KubeLinter findings"
    FAILED=1
  else
    echo "OK: ${CHART_NAME} passed KubeLinter"
  fi

  # Also lint with non-default values (e.g., production values)
  if [ -f "${CHART}/values.production.yaml" ]; then
    echo "Linting ${CHART_NAME} with production values..."
    TMPDIR=$(mktemp -d)
    helm template "${CHART_NAME}-prod" "${CHART}" \
      -f "${CHART}/values.production.yaml" \
      --output-dir "${TMPDIR}" 2>/dev/null
    if ! kube-linter lint "${TMPDIR}"; then
      echo "FAIL: ${CHART_NAME} with production values has KubeLinter findings"
      FAILED=1
    fi
    rm -rf "${TMPDIR}"
  fi
done

if [ "${FAILED}" -eq 0 ]; then
  curl -s "${VIGILMON_HB}"
fi
# .github/workflows/helm-kubelinter.yml
name: KubeLinter Helm chart validation

on:
  push:
    paths:
      - 'charts/**'
  schedule:
    - cron: '0 4 * * *'

jobs:
  helm-lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Helm
        run: curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

      - name: Install KubeLinter
        run: |
          curl -sL https://github.com/stackrox/kube-linter/releases/latest/download/kube-linter-linux.tar.gz | \
            tar xzf - -C /usr/local/bin kube-linter

      - name: Run Helm + KubeLinter
        run: bash scripts/helm-kubelinter.sh charts/

      - name: Ping Vigilmon heartbeat
        if: success()
        run: curl -s https://vigilmon.online/heartbeat/jkl012
  1. Add Monitor → Cron Heartbeat.
  2. Expected interval: 1500 minutes (daily + on every Helm chart push).
  3. Label: KubeLinter Helm chart validation.
  4. Click Save.

Step 5: Monitor Notification and Integration Endpoints

If the KubeLinter pipeline posts findings to a Slack webhook, a security dashboard, or a Jira project, monitor those endpoints:

# Verify Slack webhook for KubeLinter alerts
curl -s -o /dev/null -w "%{http_code}" \
  -X POST https://hooks.slack.com/services/XXXXXX/YYYYYY/ZZZZZZ \
  -H "Content-Type: application/json" \
  -d '{"text": "KubeLinter webhook test"}'
# Expected: 200

# Verify security findings dashboard
curl -I https://security-dashboard.example.com/api/lint-results
# Expected: 200 or 401

# Verify OPA/Gatekeeper policy sync endpoint (if syncing KubeLinter findings)
curl -I https://policy-server.example.com/v1/policies
# Expected: 200 or 401
  1. Add Monitor → HTTP.
  2. URL: https://security-dashboard.example.com/api/lint-results.
  3. Expected status codes: 200, 401.
  4. Label: KubeLinter security dashboard API.
  5. Click Save.

Add SSL certificate monitors:

  1. Add Monitor → SSL Certificate.
  2. Domain: security-dashboard.example.com.
  3. Alert when expiry is within: 30 days.
  4. Label: KubeLinter dashboard TLS certificate.
  5. Click Save.

Step 6: Policy Drift Detection

A periodic check that compares current KubeLinter findings against a saved baseline — detecting when newly added manifests introduce violations that weren't in the previous baseline:

#!/bin/bash
# kubelinter-drift-check.sh — run every 6 hours to detect policy drift

MANIFEST_DIRS="k8s/ charts/"
BASELINE="/var/lib/kubelinter/baseline-findings.txt"
VIGILMON_HB="https://vigilmon.online/heartbeat/mno345"
FAILED=0

# Get current findings sorted for stable comparison
CURRENT_FINDINGS=$(kube-linter lint --format json ${MANIFEST_DIRS} 2>/dev/null | \
  python3 -c "
import sys, json
results = json.load(sys.stdin)
findings = []
for item in results.get('Items', []):
    findings.append(f\"{item['Object']['K8sObject']['Name']}|{item['Check']}\")
for f in sorted(findings):
    print(f)
" 2>/dev/null)

CURRENT_COUNT=$(echo "${CURRENT_FINDINGS}" | grep -c . 2>/dev/null || echo "0")

if [ -f "${BASELINE}" ]; then
  BASELINE_COUNT=$(wc -l < "${BASELINE}" 2>/dev/null || echo "0")
  NEW_FINDINGS=$(comm -23 \
    <(echo "${CURRENT_FINDINGS}" | sort) \
    <(sort "${BASELINE}"))
  
  if [ -n "${NEW_FINDINGS}" ]; then
    echo "DRIFT DETECTED: New KubeLinter findings since baseline:"
    echo "${NEW_FINDINGS}"
    FAILED=1
  else
    echo "OK: No new KubeLinter findings (${CURRENT_COUNT} total, baseline: ${BASELINE_COUNT})"
    echo "${CURRENT_FINDINGS}" > "${BASELINE}"
  fi
else
  echo "No baseline — saving current state (${CURRENT_COUNT} findings)"
  mkdir -p "$(dirname "${BASELINE}")"
  echo "${CURRENT_FINDINGS}" > "${BASELINE}"
fi

if [ "${FAILED}" -eq 0 ]; then
  curl -s "${VIGILMON_HB}"
fi

Schedule via cron:

# /etc/cron.d/kubelinter-drift
0 */6 * * * ci-user /opt/scripts/kubelinter-drift-check.sh >> /var/log/kubelinter-drift.log 2>&1
  1. Add Monitor → Cron Heartbeat.
  2. Expected interval: 420 minutes (runs every 6 hours with a 60-minute grace period).
  3. Label: KubeLinter policy drift detector.
  4. Click Save.

Step 7: Configure Alerting

In Vigilmon under Settings → Notifications, configure alert channels and response runbooks:

| Monitor | Trigger | Immediate action | |---|---|---| | PR manifest gate heartbeat | Missed ping | Check GitHub Actions for disabled workflow; confirm active PRs are touching YAML — distinguish no PRs from broken gate | | PR custom policy gate heartbeat | Missed ping | Check workflow for .kube-linter.yaml parse errors; verify policy file is valid | | Nightly full scan heartbeat | Missed ping | Check scheduled workflow; look for KubeLinter install failure or timeout on large repo | | Helm chart validation heartbeat | Missed ping | Check Helm dependency update step; look for chart template parse failures | | Drift detector heartbeat | Missed ping | Check cron job logs; verify KubeLinter binary is accessible for the cron user | | Security dashboard API | Non-200/401 | Findings are not being recorded; teams cannot review historical lint trends | | Dashboard TLS certificate | < 30 days | Rotate certificate; confirm CI pipelines can POST results after renewal |


Common KubeLinter Pipeline Failure Modes and What Vigilmon Catches

| Scenario | Vigilmon signal | |---|---| | KubeLinter CI step commented out in workflow | PR gate heartbeat goes silent; insecure manifests can merge | | KubeLinter binary not in CI runner image | Pipeline fails at install; PR gate heartbeat fires (fails before ping) | | .kube-linter.yaml policy file has syntax error | Custom policy gate heartbeat fires on every PR; no actual linting runs | | Nightly scan workflow accidentally disabled | Nightly heartbeat goes silent; existing manifest violations accumulate undetected | | Helm chart dependency update fails before linting | Helm validation heartbeat fires; chart changes ship without lint check | | New manifest added outside scanned path | PR gate passes vacuously; drift detector catches new violations in the next cycle | | Security dashboard URL rotated | Dashboard API monitor fires; CI pipelines fail to POST results | | Dashboard TLS certificate expires | SSL monitor fires; curl POST fails with TLS handshake error | | Policy drift from new deployment added without review | Drift detector heartbeat fires; new violations flagged before next release | | KubeLinter version mismatch in CI vs local | PR gate fires on new checks only available in newer version; pin version in CI |


KubeLinter enforces your Kubernetes security baseline in CI/CD — but only as long as its pipeline gates keep running and linting every change. Vigilmon's heartbeat monitors give you the external signal that confirms your KubeLinter gates are active on every pull request, your nightly full scans are running on schedule, and your Helm chart validation jobs haven't silently stopped. When a workflow file is edited to skip the linting step, or when a scheduled scan stops triggering, Vigilmon alerts you before non-compliant Kubernetes resources reach a cluster.

Start monitoring your KubeLinter pipelines in under 5 minutes — register free at vigilmon.online.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →