World map

 

BM: Baca dalam Bahasa Melayu

World O4: The Diary & One Order

Capstone mission 9: Watch Kedai Kancil

You will learn: to run your own control room for Kedai Kancil: Prometheus, Grafana, node-exporter, one alert and one dashboard.

Checked with Prometheus 3 on 1 Nov 2026

The problem

Mission 9: watch Kedai Kancil! You will build a small control room in a monitoring/ folder. First, put each file in its place.

Drag a card into a box. Or tap the card, then tap the box.

        Watch

        node-exporter has no ports line. How does Prometheus reach it?

        Pick one answer.

        services:
          prometheus:
            image: quay.io/prometheus/prometheus:v3.13.5
            volumes:
              - ./prometheus:/etc/prometheus:ro
            ports: ["9090:9090"]
          node-exporter:
            image: quay.io/prometheus/node-exporter:v1.12.1
          grafana:
            image: grafana/grafana:13.2.5
            volumes:
              - ./grafana/provisioning:/etc/grafana/provisioning:ro
              - ./grafana/dashboards:/var/lib/grafana/dashboards:ro
            ports: ["3000:3000"]

        Watch

        Test the “Targets up” panel’s query first. Make it one number.

        Type a PromQL query and press Run. The table shows the answer at the eval time. The graph shows it over time.

        Metrics in this data (tap to add):

        Time 0 is when the data starts. The query looks back from the eval time.

        {
          "uid": "kedai",
          "title": "Kedai Kancil",
          "panels": [
            { "type": "stat", "title": "Targets up",
              "gridPos": { "x": 0, "y": 0, "w": 6, "h": 6 },
              "targets": [{ "refId": "A", "expr": "sum(up)" }] },
            { "type": "timeseries", "title": "CPU busy (%)",
              "gridPos": { "x": 6, "y": 0, "w": 18, "h": 6 },
              "targets": [{ "refId": "A", "expr": "100 * (1 - avg(rate(node_cpu_seconds_total{mode=\"idle\"}[$__rate_interval])))" }] }
          ]
        }

        Watch

        Grafana runs in its own container. Why does the data source say http://prometheus:9090, not localhost?

        Pick one answer.

        # grafana/provisioning/datasources/prometheus.yaml
        apiVersion: 1
        datasources:
          - name: Prometheus
            type: prometheus
            uid: prometheus
            url: http://prometheus:9090
            access: proxy
            isDefault: true
        # grafana/provisioning/dashboards/kedai.yaml
        apiVersion: 1
        providers:
          - name: kedai
            folder: Kedai Kancil
            type: file
            options:
              path: /var/lib/grafana/dashboards

        Try it

        This prometheus.yml scrapes localhost:9100. Inside the Prometheus container, nothing listens there. Point the node job at node-exporter.

        Change the YAML. The box below shows how a computer reads it.

        prometheus.yml13 lines

        How a computer reads it

        • globalmap · 2 keys
          • scrape_interval15stext
          • evaluation_interval15stext
        • rule_fileslist · 1 items
          • item 1rules.ymltext
        • scrape_configslist · 2 items
          • item 1map · 2 keys
            • job_nameprometheustext
            • static_configslist · 1 items
              • item 1map · 1 keys
                • targetslist · 1 items
                  • item 1localhost:9090text
          • item 2map · 2 keys
            • job_namenodetext
            • static_configslist · 1 items
              • item 1map · 1 keys
                • targetslist · 1 items
                  • item 1localhost:9100text

        Try it

        Now the alert. Page when any target is down for a minute, and name the job and instance.

        Build an alert rule from the blocks. The timeline shows when it would wait and when it would fire.

        When (expr)
        Wait first (for)
        Labels
        Message (annotations)
        Your rule: rules.yml
        groups:
        - name: kedai
        rules:
        - alert: KedaiTargetDown
        expr: up == 0
        annotations:
        summary: Target down
        
        The value the alert watches
        00.250.50.7510s10m20m30mthreshold 0
        Graph as a table
        Graph as a table
        Timeup{instance="db-1", job="db"}up{instance="shop-1", job="shop"}up{instance="shop-2", job="shop"}
        0s111
        1m111
        2m111
        3m111
        4m111
        5m111
        6m011
        7m011
        8m011
        9m111
        10m111
        11m101
        12m101
        13m101
        14m101
        15m101
        16m101
        17m101
        18m101
        19m101
        20m101
        21m101
        22m101
        23m101
        24m101
        25m101
        26m101
        27m101
        28m101
        29m101
        30m101
        31m101

        What the alert does over time

        Grey: quiet. Amber: pending (true, but waiting). Red: firing.

        • {instance="db-1", job="db"}

          🔔 firing 6m to 8m

        • {instance="shop-1", job="shop"}

          🔔 firing 11m to 31m

        At 12m:

        • FIRING KedaiTargetDown instance="shop-1", job="shop"Target down
        Timeline as a table
        Time{instance="db-1", job="db"}{instance="shop-1", job="shop"}
        0squietquiet
        1mquietquiet
        2mquietquiet
        3mquietquiet
        4mquietquiet
        5mquietquiet
        6m🔔 firingquiet
        7m🔔 firingquiet
        8m🔔 firingquiet
        9mquietquiet
        10mquietquiet
        11mquiet🔔 firing
        12mquiet🔔 firing
        13mquiet🔔 firing
        14mquiet🔔 firing
        15mquiet🔔 firing
        16mquiet🔔 firing
        17mquiet🔔 firing
        18mquiet🔔 firing
        19mquiet🔔 firing
        20mquiet🔔 firing
        21mquiet🔔 firing
        22mquiet🔔 firing
        23mquiet🔔 firing
        24mquiet🔔 firing
        25mquiet🔔 firing
        26mquiet🔔 firing
        27mquiet🔔 firing
        28mquiet🔔 firing
        29mquiet🔔 firing
        30mquiet🔔 firing
        31mquiet🔔 firing

        Try it

        Now build it for real, step by step. Your ticks are saved.

        Real mission

        1. mkdir -p monitoring && cd monitoring && mkdir -p prometheus grafana/dashboards grafana/provisioning/datasources grafana/provisioning/dashboards
        2. docker compose config --quiet
        3. docker run --rm --entrypoint promtool -v "$PWD/prometheus:/etc/prometheus:ro" quay.io/prometheus/prometheus:v3.13.5 check config /etc/prometheus/prometheus.yml
        4. docker compose up -d
        5. http://localhost:9090/targets
        6. http://localhost:3000
        7. docker compose stop node-exporter
        8. http://localhost:9090/alerts
        9. docker compose start node-exporter
        10. git add monitoring && git commit -m "Watch the shop" && git push
        11. docker compose down

        0 of 12 steps done · Your ticks are saved.

        Check

        GitHub can see your files. Type your username and press Check now.

        Check it on GitHub

        Your username is sent only to api.github.com.

        • Your kedai-kancil repo is publicNot checked yet
        • It has monitoring/compose.yamlNot checked yet
        • It has the Prometheus configNot checked yet
        • It has the alert ruleNot checked yet
        • It has the dashboardNot checked yet

        Check

        The rest only you can see. Tick what is true.

        Self-check

        0 of 5 steps done · Your ticks are saved.

        You did it!

        You can now run a small control room: metrics, a dashboard and an alert.

        Go deeper (optional)

        Where the story breaks. A real control room watches from outside. Ours runs on the machine it watches. If that machine dies, the bell dies too. In a container, node-exporter sees the container’s own disks and network. Its README shows the host mounts for real machines. Next: add Alertmanager, so the bell reaches a phone.