Thursday, November 28, 2024

Giám sát Docker với Grafana, Prometheus, cAdvisor và Node Exporter

-

1. Tổng quan.

Trong thế giới hiện đại của ứng dụng và hệ thống phức tạp, việc theo dõi và giám sát hệ thống trở nên ngày càng quan trọng. Điều này giúp đảm bảo rằng ứng dụng của bạn hoạt động một cách ổn định, tối ưu hóa tài nguyên, và giải quyết vấn đề ngay khi chúng xuất hiện. Trong bài viết này, chúng ta sẽ tìm hiểu về cách xây dựng một hệ thống theo dõi sử dụng các công cụ phổ biến như Grafana, Prometheus, cAdvisor và Node Exporter.

Prometheus là một hệ thống giám sát và thu thập số liệu mã nguồn mở. Nó hoạt động bằng cách scrape (lấy số liệu) từ các ứng dụng và dịch vụ, sau đó lưu trữ số liệu này trong cơ sở dữ liệu dựa trên time-series. Prometheus cung cấp một ngôn ngữ truy vấn mạnh mẽ để truy cập và biểu đồ số liệu

Grafana là một nền tảng trực quan hóa và giám sát mã nguồn mở. Nó cho phép bạn tạo biểu đồ và bảng điều khiển tùy chỉnh để hiển thị số liệu từ Prometheus và các nguồn dữ liệu khác. Grafana cung cấp một giao diện người dùng dễ sử dụng để theo dõi hiệu suất ứng dụng và hệ thống.

cAdvisor (Container Advisor) là một công cụ giám sát mã nguồn mở được phát triển bởi Google. Nó theo dõi các container Docker và cung cấp thông tin về tài nguyên, hiệu suất và sự hoạt động của chúng. cAdvisor tích hợp dễ dàng với Prometheus để cung cấp số liệu chi tiết về container.

Node Exporter là một phần mềm giám sát mã nguồn mở cho hệ thống máy chủ. Nó thu thập thông tin về tài nguyên hệ thống như CPU, bộ nhớ, đĩa và mạng. Node Exporter là một phần quan trọng để hiểu hiệu suất của máy chủ và hệ thống.

Mô hình hoạt động của hệ thống theo dõi sử dụng Grafana, Prometheus, cAdvisor và Node Exporter như sau:

  • Node Exporter chạy trên mỗi máy chủ cần được giám sát. Nó thu thập số liệu từ máy chủ và cung cấp chúng cho Prometheus.
  • cAdvisor chạy trên mỗi máy chủ Docker và giám sát các container. Nó cũng cung cấp số liệu cho Prometheus.
  • Prometheus là trung tâm thu thập số liệu. Nó lấy số liệu từ Node Exporter, cAdvisor và các ứng dụng khác, sau đó lưu trữ chúng trong cơ sở dữ liệu time-series.
  • Grafana kết nối đến Prometheus để truy vấn và hiển thị số liệu. Grafana cho phép bạn tạo biểu đồ, bảng điều khiển và cảnh báo để theo dõi hiệu suất và sự hoạt động của hệ thống.

2. Thực hành.

Sơ đồ.

  • Theo dõi sơ đồ trên và bạn thấy mình có 2 máy chủ:
    • Máy chủ 1: 192.168.13.200/23 Là máy chủ chạy Docker Host và Monitoring bao gồm Prometheus, Grafana, Node Explore và cAdvisor.
    • Máy chủ 2: 192.168.13.184 là máy chủ chỉ chạy Docker, Node Explore và cAdvisor

Bây giờ hãy thiết kế thư mục và file theo cấu trúc sau.

.
├── alertmanager
│   └── config.yml
├── docker-compose.yaml
├── grafana
│   └── provisioning
│       └── dashboards
│           └── dashboard.json
└── prometheus
    ├── alert.rules
    └── prometheus.yml

Tạo các folder chứa các manifest.

mkdir -p /home/asterisk/demo/prometheus
mkdir -p /home/asterisk/demo/alertmanager
mkdir -p /home/asterisk/demo/grafana/provisioning/dashboards

Tạo file config cho Alertmanager.

cat > /home/asterisk/demo/alertmanager/config.yml << 'OEF'
route:
  receiver: 'chatwork'
  group_by: ['alertname']
  group_wait:      15s
  group_interval:  5m
  repeat_interval: 3h

receivers:
  - name: 'chatwork'
    webhook_configs:
      - url: https://cw-forwarder.sun-asterisk.vn/api/v1/webhooks/498f6b7b661c2651c
        send_resolved: false
        max_alerts: 3
OEF

Tạo file config lưu thông tin Dashboard cho Grafana.

cat > /home/asterisk/demo/grafana/provisioning/dashboards/dashboard.json << 'OEF'
{
  "annotations": {
    "list": [
      {
        "builtIn": 1,
        "datasource": {
          "type": "datasource",
          "uid": "grafana"
        },
        "enable": true,
        "hide": true,
        "iconColor": "rgba(0, 211, 255, 1)",
        "name": "Annotations & Alerts",
        "type": "dashboard"
      }
    ]
  },
  "description": "Docker Monitoring Template",
  "editable": true,
  "fiscalYearStartMonth": 0,
  "gnetId": 179,
  "graphTooltip": 1,
  "id": 1,
  "links": [],
  "liveNow": false,
  "panels": [
    {
      "collapsed": false,
      "datasource": {
        "type": "prometheus",
        "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
      },
      "gridPos": {
        "h": 1,
        "w": 24,
        "x": 0,
        "y": 0
      },
      "id": 17,
      "panels": [],
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
          },
          "refId": "A"
        }
      ],
      "title": "Host Info",
      "type": "row"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [
            {
              "options": {
                "match": "null",
                "result": {
                  "text": "N/A"
                }
              },
              "type": "special"
            }
          ],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          },
          "unit": "s"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 5,
        "w": 4,
        "x": 0,
        "y": 1
      },
      "id": 15,
      "links": [],
      "maxDataPoints": 100,
      "options": {
        "colorMode": "none",
        "graphMode": "none",
        "justifyMode": "auto",
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "mean"
          ],
          "fields": "",
          "values": false
        },
        "textMode": "auto"
      },
      "pluginVersion": "10.1.2",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
          },
          "expr": "time() - process_start_time_seconds{job=\"prometheus\"}",
          "format": "time_series",
          "intervalFactor": 1,
          "refId": "A"
        }
      ],
      "title": "Uptime",
      "type": "stat"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "fixedColor": "rgb(31, 120, 193)",
            "mode": "fixed"
          },
          "mappings": [
            {
              "options": {
                "match": "null",
                "result": {
                  "text": "N/A"
                }
              },
              "type": "special"
            }
          ],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "#d44a3a",
                "value": null
              },
              {
                "color": "rgba(237, 129, 40, 0.89)",
                "value": 0
              },
              {
                "color": "#299c46",
                "value": 1
              }
            ]
          },
          "unit": "none"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 5,
        "w": 4,
        "x": 4,
        "y": 1
      },
      "id": 31,
      "links": [],
      "maxDataPoints": 100,
      "options": {
        "colorMode": "none",
        "graphMode": "area",
        "justifyMode": "auto",
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "mean"
          ],
          "fields": "",
          "values": false
        },
        "textMode": "auto"
      },
      "pluginVersion": "10.1.2",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
          },
          "expr": "count(rate(container_last_seen{job=\"cadvisor\", name!=\"\"}[5m]))",
          "format": "time_series",
          "intervalFactor": 1,
          "refId": "A"
        }
      ],
      "title": "Running Containers",
      "type": "stat"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "decimals": 2,
          "mappings": [
            {
              "options": {
                "match": "null",
                "result": {
                  "text": "N/A"
                }
              },
              "type": "special"
            }
          ],
          "max": 100,
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "rgba(50, 172, 45, 0.97)",
                "value": null
              },
              {
                "color": "rgba(237, 129, 40, 0.89)",
                "value": 65
              },
              {
                "color": "rgba(245, 54, 54, 0.9)",
                "value": 90
              }
            ]
          },
          "unit": "percent"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 5,
        "w": 4,
        "x": 8,
        "y": 1
      },
      "id": 6,
      "links": [],
      "maxDataPoints": 100,
      "options": {
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showThresholdLabels": false,
        "showThresholdMarkers": true
      },
      "pluginVersion": "10.1.2",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
          },
          "expr": "sum(sum by (container_name)( rate(container_cpu_usage_seconds_total[1m] ) )) / count(node_cpu_seconds_total{mode=\"system\"}) * 100",
          "format": "time_series",
          "interval": "1m",
          "intervalFactor": 1,
          "legendFormat": "",
          "refId": "A",
          "step": 10
        }
      ],
      "title": "CPU usage",
      "type": "gauge"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [
            {
              "options": {
                "match": "null",
                "result": {
                  "text": "N/A"
                }
              },
              "type": "special"
            }
          ],
          "max": 100,
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "rgba(50, 172, 45, 0.97)",
                "value": null
              },
              {
                "color": "rgba(237, 129, 40, 0.89)",
                "value": 65
              },
              {
                "color": "rgba(245, 54, 54, 0.9)",
                "value": 90
              }
            ]
          },
          "unit": "percent"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 5,
        "w": 3,
        "x": 12,
        "y": 1
      },
      "id": 4,
      "links": [],
      "maxDataPoints": 100,
      "options": {
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showThresholdLabels": false,
        "showThresholdMarkers": true
      },
      "pluginVersion": "10.1.2",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
          },
          "expr": "(sum(node_memory_MemTotal_bytes) - sum(node_memory_MemFree_bytes +node_memory_Buffers_bytes + node_memory_Cached_bytes) ) / sum(node_memory_MemTotal_bytes) * 100",
          "format": "time_series",
          "interval": "10s",
          "intervalFactor": 1,
          "refId": "A",
          "step": 10
        }
      ],
      "title": "Memory usage",
      "type": "gauge"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "decimals": 2,
          "mappings": [
            {
              "options": {
                "match": "null",
                "result": {
                  "text": "N/A"
                }
              },
              "type": "special"
            }
          ],
          "max": 100,
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "rgba(50, 172, 45, 0.97)",
                "value": null
              },
              {
                "color": "rgba(237, 129, 40, 0.89)",
                "value": 65
              },
              {
                "color": "rgba(245, 54, 54, 0.9)",
                "value": 90
              }
            ]
          },
          "unit": "percent"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 5,
        "w": 3,
        "x": 15,
        "y": 1
      },
      "id": 7,
      "links": [],
      "maxDataPoints": 100,
      "options": {
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showThresholdLabels": false,
        "showThresholdMarkers": true
      },
      "pluginVersion": "10.1.2",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
          },
          "expr": "sum (container_fs_limit_bytes - container_fs_usage_bytes) / sum(container_fs_limit_bytes)",
          "interval": "10s",
          "intervalFactor": 1,
          "metric": "",
          "refId": "A",
          "step": 10
        }
      ],
      "title": "Filesystem usage",
      "type": "gauge"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [
            {
              "options": {
                "match": "null",
                "result": {
                  "text": "N/A"
                }
              },
              "type": "special"
            }
          ],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "#d44a3a",
                "value": null
              },
              {
                "color": "rgba(237, 129, 40, 0.89)",
                "value": 0
              },
              {
                "color": "#299c46",
                "value": 1
              }
            ]
          },
          "unit": "none"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 5,
        "w": 3,
        "x": 18,
        "y": 1
      },
      "id": 11,
      "links": [],
      "maxDataPoints": 100,
      "options": {
        "colorMode": "background",
        "graphMode": "none",
        "justifyMode": "auto",
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "mean"
          ],
          "fields": "",
          "values": false
        },
        "textMode": "auto"
      },
      "pluginVersion": "10.1.2",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
          },
          "expr": "sum(up)",
          "format": "time_series",
          "intervalFactor": 1,
          "refId": "A"
        }
      ],
      "title": "Targets Online",
      "type": "stat"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [
            {
              "options": {
                "0": {
                  "text": "N/A"
                }
              },
              "type": "value"
            }
          ],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "#299c46",
                "value": null
              },
              {
                "color": "rgba(237, 129, 40, 0.89)",
                "value": 0
              },
              {
                "color": "#d44a3a",
                "value": 1
              }
            ]
          },
          "unit": "none"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 5,
        "w": 3,
        "x": 21,
        "y": 1
      },
      "id": 13,
      "links": [],
      "maxDataPoints": 100,
      "options": {
        "colorMode": "background",
        "graphMode": "none",
        "justifyMode": "auto",
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "mean"
          ],
          "fields": "",
          "values": false
        },
        "textMode": "auto"
      },
      "pluginVersion": "10.1.2",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
          },
          "expr": "sum(ALERTS)",
          "format": "time_series",
          "intervalFactor": 1,
          "refId": "A"
        }
      ],
      "title": "Alerts",
      "type": "stat"
    },
    {
      "aliasColors": {
        "RECEIVE": "#ea6460",
        "SENT": "#1f78c1",
        "TRANSMIT": "#1f78c1"
      },
      "bars": false,
      "dashLength": 10,
      "dashes": false,
      "datasource": {
        "type": "prometheus",
        "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
      },
      "fill": 4,
      "fillGradient": 0,
      "gridPos": {
        "h": 8,
        "w": 8,
        "x": 0,
        "y": 6
      },
      "hiddenSeries": false,
      "id": 25,
      "legend": {
        "avg": false,
        "current": false,
        "max": false,
        "min": false,
        "show": true,
        "total": false,
        "values": false
      },
      "lines": true,
      "linewidth": 1,
      "links": [],
      "nullPointMode": "null",
      "options": {
        "alertThreshold": true
      },
      "percentage": false,
      "pluginVersion": "10.1.2",
      "pointradius": 5,
      "points": false,
      "renderer": "flot",
      "seriesOverrides": [],
      "spaceLength": 10,
      "stack": false,
      "steppedLine": false,
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
          },
          "expr": "sum(rate(container_network_receive_bytes_total{id=\"/\"}[$interval])) by (id)",
          "format": "time_series",
          "interval": "2m",
          "intervalFactor": 2,
          "legendFormat": "RECEIVE",
          "refId": "A"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
          },
          "expr": "- sum(rate(container_network_transmit_bytes_total{id=\"/\"}[$interval])) by (id)",
          "format": "time_series",
          "interval": "2m",
          "intervalFactor": 2,
          "legendFormat": "TRANSMIT",
          "refId": "B"
        }
      ],
      "thresholds": [],
      "timeRegions": [],
      "title": "Node Network Traffic",
      "tooltip": {
        "shared": true,
        "sort": 0,
        "value_type": "cumulative"
      },
      "type": "graph",
      "xaxis": {
        "mode": "time",
        "show": true,
        "values": []
      },
      "yaxes": [
        {
          "format": "Bps",
          "logBase": 1,
          "show": true
        },
        {
          "format": "s",
          "logBase": 1,
          "show": true
        }
      ],
      "yaxis": {
        "align": false
      }
    },
    {
      "aliasColors": {
        "Available Memory": "#508642",
        "Used Memory": "#bf1b00"
      },
      "bars": false,
      "dashLength": 10,
      "dashes": false,
      "datasource": {
        "type": "prometheus",
        "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
      },
      "fill": 3,
      "fillGradient": 0,
      "gridPos": {
        "h": 8,
        "w": 7,
        "x": 8,
        "y": 6
      },
      "hiddenSeries": false,
      "id": 27,
      "legend": {
        "avg": false,
        "current": false,
        "max": false,
        "min": false,
        "show": true,
        "total": false,
        "values": false
      },
      "lines": true,
      "linewidth": 1,
      "links": [],
      "nullPointMode": "null",
      "options": {
        "alertThreshold": true
      },
      "percentage": false,
      "pluginVersion": "10.1.2",
      "pointradius": 5,
      "points": false,
      "renderer": "flot",
      "seriesOverrides": [],
      "spaceLength": 10,
      "stack": true,
      "steppedLine": false,
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
          },
          "expr": "sum(node_memory_MemTotal_bytes) - sum(node_memory_MemAvailable_bytes)",
          "format": "time_series",
          "interval": "2m",
          "intervalFactor": 2,
          "legendFormat": "Used Memory",
          "refId": "B"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
          },
          "expr": "sum(node_memory_MemAvailable_bytes)",
          "format": "time_series",
          "interval": "2m",
          "intervalFactor": 2,
          "legendFormat": "Available Memory",
          "refId": "A"
        }
      ],
      "thresholds": [],
      "timeRegions": [],
      "title": "Node Mermory",
      "tooltip": {
        "shared": true,
        "sort": 0,
        "value_type": "individual"
      },
      "type": "graph",
      "xaxis": {
        "mode": "time",
        "show": true,
        "values": []
      },
      "yaxes": [
        {
          "format": "decbytes",
          "logBase": 1,
          "show": true
        },
        {
          "format": "s",
          "logBase": 1,
          "show": true
        }
      ],
      "yaxis": {
        "align": false
      }
    },
    {
      "aliasColors": {
        "Available Memory": "#508642",
        "Free Storage": "#447ebc",
        "Total Storage Available": "#508642",
        "Used Memory": "#bf1b00",
        "Used Storage": "#bf1b00"
      },
      "bars": false,
      "dashLength": 10,
      "dashes": false,
      "datasource": {
        "type": "prometheus",
        "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
      },
      "fill": 3,
      "fillGradient": 0,
      "gridPos": {
        "h": 8,
        "w": 9,
        "x": 15,
        "y": 6
      },
      "hiddenSeries": false,
      "id": 28,
      "legend": {
        "avg": false,
        "current": false,
        "max": false,
        "min": false,
        "show": true,
        "total": false,
        "values": false
      },
      "lines": true,
      "linewidth": 1,
      "links": [],
      "nullPointMode": "null",
      "options": {
        "alertThreshold": true
      },
      "percentage": false,
      "pluginVersion": "10.1.2",
      "pointradius": 5,
      "points": false,
      "renderer": "flot",
      "seriesOverrides": [],
      "spaceLength": 10,
      "stack": true,
      "steppedLine": false,
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
          },
          "expr": "sum(node_filesystem_free_bytes {job=\"node-exporter\", instance=~\".*9100\", device=~\"/dev/.*\", mountpoint!=\"/var/lib/docker/aufs\"}) ",
          "format": "time_series",
          "interval": "2m",
          "intervalFactor": 2,
          "legendFormat": "Free Storage",
          "refId": "A"
        }
      ],
      "thresholds": [],
      "timeRegions": [],
      "title": "Filesystem Available",
      "tooltip": {
        "shared": true,
        "sort": 0,
        "value_type": "individual"
      },
      "type": "graph",
      "xaxis": {
        "mode": "time",
        "show": true,
        "values": []
      },
      "yaxes": [
        {
          "format": "decbytes",
          "logBase": 1,
          "show": true
        },
        {
          "format": "s",
          "logBase": 1,
          "show": true
        }
      ],
      "yaxis": {
        "align": false
      }
    },
    {
      "collapsed": false,
      "datasource": {
        "type": "prometheus",
        "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
      },
      "gridPos": {
        "h": 1,
        "w": 24,
        "x": 0,
        "y": 14
      },
      "id": 19,
      "panels": [],
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
          },
          "refId": "A"
        }
      ],
      "title": "Container Performance",
      "type": "row"
    },
    {
      "aliasColors": {},
      "bars": false,
      "dashLength": 10,
      "dashes": false,
      "datasource": {
        "type": "prometheus",
        "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
      },
      "decimals": 3,
      "editable": true,
      "error": false,
      "fill": 0,
      "fillGradient": 0,
      "grid": {},
      "gridPos": {
        "h": 10,
        "w": 6,
        "x": 0,
        "y": 15
      },
      "hiddenSeries": false,
      "id": 3,
      "legend": {
        "alignAsTable": true,
        "avg": true,
        "current": true,
        "max": false,
        "min": false,
        "rightSide": false,
        "show": true,
        "sort": "current",
        "sortDesc": true,
        "total": false,
        "values": true
      },
      "lines": true,
      "linewidth": 2,
      "links": [],
      "nullPointMode": "connected",
      "options": {
        "alertThreshold": true
      },
      "percentage": false,
      "pluginVersion": "10.1.2",
      "pointradius": 5,
      "points": false,
      "renderer": "flot",
      "seriesOverrides": [],
      "spaceLength": 10,
      "stack": false,
      "steppedLine": false,
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
          },
          "expr": "container_cpu_load_average_10s{image!=\"\"}",
          "format": "time_series",
          "interval": "10s",
          "intervalFactor": 1,
          "legendFormat": "{{ name }}",
          "metric": "container_cpu_user_seconds_total",
          "refId": "A",
          "step": 10
        }
      ],
      "thresholds": [],
      "timeRegions": [],
      "title": "Container CPU usage",
      "tooltip": {
        "msResolution": true,
        "shared": true,
        "sort": 0,
        "value_type": "cumulative"
      },
      "type": "graph",
      "xaxis": {
        "mode": "time",
        "show": true,
        "values": []
      },
      "yaxes": [
        {
          "format": "percentunit",
          "logBase": 1,
          "show": true
        },
        {
          "format": "short",
          "logBase": 1,
          "show": true
        }
      ],
      "yaxis": {
        "align": false
      }
    },
    {
      "aliasColors": {},
      "bars": false,
      "dashLength": 10,
      "dashes": false,
      "datasource": {
        "type": "prometheus",
        "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
      },
      "decimals": 2,
      "editable": true,
      "error": false,
      "fill": 0,
      "fillGradient": 0,
      "grid": {},
      "gridPos": {
        "h": 10,
        "w": 6,
        "x": 6,
        "y": 15
      },
      "hiddenSeries": false,
      "id": 2,
      "legend": {
        "alignAsTable": true,
        "avg": true,
        "current": true,
        "max": false,
        "min": false,
        "rightSide": false,
        "show": true,
        "sort": "current",
        "sortDesc": true,
        "total": false,
        "values": true
      },
      "lines": true,
      "linewidth": 2,
      "links": [],
      "nullPointMode": "connected",
      "options": {
        "alertThreshold": true
      },
      "percentage": false,
      "pluginVersion": "10.1.2",
      "pointradius": 5,
      "points": false,
      "renderer": "flot",
      "seriesOverrides": [],
      "spaceLength": 10,
      "stack": false,
      "steppedLine": false,
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
          },
          "expr": "container_memory_max_usage_bytes{image!=\"\"}",
          "format": "time_series",
          "interval": "10s",
          "intervalFactor": 1,
          "legendFormat": "{{ name }}",
          "metric": "container_memory_usage:sort_desc",
          "refId": "A",
          "step": 10
        }
      ],
      "thresholds": [],
      "timeRegions": [],
      "title": "Container Memory Usage",
      "tooltip": {
        "msResolution": false,
        "shared": true,
        "sort": 0,
        "value_type": "cumulative"
      },
      "type": "graph",
      "xaxis": {
        "mode": "time",
        "show": true,
        "values": []
      },
      "yaxes": [
        {
          "format": "bytes",
          "logBase": 1,
          "show": true
        },
        {
          "format": "short",
          "logBase": 1,
          "show": true
        }
      ],
      "yaxis": {
        "align": false
      }
    },
    {
      "columns": [],
      "datasource": {
        "type": "prometheus",
        "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
      },
      "fontSize": "100%",
      "gridPos": {
        "h": 10,
        "w": 12,
        "x": 12,
        "y": 15
      },
      "id": 23,
      "links": [],
      "scroll": true,
      "showHeader": true,
      "sort": {
        "col": 0,
        "desc": true
      },
      "styles": [
        {
          "alias": "Time",
          "align": "auto",
          "dateFormat": "YYYY-MM-DD HH:mm:ss",
          "pattern": "Time",
          "type": "date"
        },
        {
          "alias": "",
          "align": "auto",
          "colors": [
            "rgba(245, 54, 54, 0.9)",
            "rgba(237, 129, 40, 0.89)",
            "rgba(50, 172, 45, 0.97)"
          ],
          "decimals": 2,
          "pattern": "/.*/",
          "thresholds": [],
          "type": "number",
          "unit": "short"
        }
      ],
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
          },
          "expr": "ALERTS",
          "format": "table",
          "intervalFactor": 1,
          "refId": "A"
        }
      ],
      "title": "Alerts",
      "transform": "table",
      "type": "table-old"
    },
    {
      "aliasColors": {},
      "bars": false,
      "dashLength": 10,
      "dashes": false,
      "datasource": {
        "type": "prometheus",
        "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
      },
      "decimals": 2,
      "editable": true,
      "error": false,
      "fill": 0,
      "fillGradient": 0,
      "grid": {},
      "gridPos": {
        "h": 14,
        "w": 6,
        "x": 0,
        "y": 25
      },
      "hiddenSeries": false,
      "id": 8,
      "legend": {
        "alignAsTable": true,
        "avg": true,
        "current": true,
        "max": false,
        "min": false,
        "rightSide": false,
        "show": true,
        "sort": "current",
        "sortDesc": true,
        "total": false,
        "values": true
      },
      "lines": true,
      "linewidth": 2,
      "links": [],
      "nullPointMode": "connected",
      "options": {
        "alertThreshold": true
      },
      "percentage": false,
      "pluginVersion": "10.1.2",
      "pointradius": 5,
      "points": false,
      "renderer": "flot",
      "seriesOverrides": [],
      "spaceLength": 10,
      "stack": false,
      "steppedLine": false,
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
          },
          "expr": "sort_desc(sum by (name) (rate(container_network_receive_bytes_total{image!=\"\"}[1m] ) ))",
          "interval": "10s",
          "intervalFactor": 1,
          "legendFormat": "{{ name }}",
          "metric": "container_network_receive_bytes_total",
          "refId": "A",
          "step": 10
        }
      ],
      "thresholds": [],
      "timeRegions": [],
      "title": "Container Network Input",
      "tooltip": {
        "msResolution": false,
        "shared": true,
        "sort": 0,
        "value_type": "cumulative"
      },
      "type": "graph",
      "xaxis": {
        "mode": "time",
        "show": true,
        "values": []
      },
      "yaxes": [
        {
          "format": "bytes",
          "logBase": 1,
          "show": true
        },
        {
          "format": "short",
          "logBase": 1,
          "show": true
        }
      ],
      "yaxis": {
        "align": false
      }
    },
    {
      "aliasColors": {},
      "bars": false,
      "dashLength": 10,
      "dashes": false,
      "datasource": {
        "type": "prometheus",
        "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
      },
      "decimals": 2,
      "editable": true,
      "error": false,
      "fill": 0,
      "fillGradient": 0,
      "grid": {},
      "gridPos": {
        "h": 14,
        "w": 6,
        "x": 6,
        "y": 25
      },
      "hiddenSeries": false,
      "id": 9,
      "legend": {
        "alignAsTable": true,
        "avg": true,
        "current": true,
        "max": false,
        "min": false,
        "rightSide": false,
        "show": true,
        "sort": "current",
        "sortDesc": true,
        "total": false,
        "values": true
      },
      "lines": true,
      "linewidth": 2,
      "links": [],
      "nullPointMode": "connected",
      "options": {
        "alertThreshold": true
      },
      "percentage": false,
      "pluginVersion": "10.1.2",
      "pointradius": 5,
      "points": false,
      "renderer": "flot",
      "seriesOverrides": [],
      "spaceLength": 10,
      "stack": false,
      "steppedLine": false,
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
          },
          "expr": "sort_desc(sum by (name) (rate(container_network_transmit_bytes_total{image!=\"\"}[1m] ) ))",
          "format": "time_series",
          "intervalFactor": 2,
          "legendFormat": "{{ name }}",
          "metric": "container_network_transmit_bytes_total",
          "refId": "B",
          "step": 4
        }
      ],
      "thresholds": [],
      "timeRegions": [],
      "title": "Container Network Output",
      "tooltip": {
        "msResolution": false,
        "shared": true,
        "sort": 0,
        "value_type": "cumulative"
      },
      "type": "graph",
      "xaxis": {
        "mode": "time",
        "show": true,
        "values": []
      },
      "yaxes": [
        {
          "format": "bytes",
          "logBase": 1,
          "show": true
        },
        {
          "format": "short",
          "logBase": 1,
          "show": false
        }
      ],
      "yaxis": {
        "align": false
      }
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "custom": {
            "align": "auto",
            "cellOptions": {
              "type": "auto"
            },
            "inspect": false
          },
          "decimals": 2,
          "displayName": "",
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          },
          "unit": "short"
        },
        "overrides": [
          {
            "matcher": {
              "id": "byName",
              "options": "Time"
            },
            "properties": [
              {
                "id": "displayName",
                "value": "Time"
              },
              {
                "id": "unit",
                "value": "time: YYYY-MM-DD HH:mm:ss"
              },
              {
                "id": "custom.align"
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "Prometheus Version"
            },
            "properties": [
              {
                "id": "custom.width",
                "value": 204
              }
            ]
          }
        ]
      },
      "gridPos": {
        "h": 14,
        "w": 12,
        "x": 12,
        "y": 25
      },
      "id": 30,
      "links": [],
      "options": {
        "cellHeight": "sm",
        "footer": {
          "countRows": false,
          "fields": "",
          "reducer": [
            "sum"
          ],
          "show": false
        },
        "frameIndex": 0,
        "showHeader": true,
        "sortBy": []
      },
      "pluginVersion": "10.1.2",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
          },
          "editorMode": "code",
          "expr": "sum by (goos, goversion, instance, version) (prometheus_build_info{})",
          "format": "table",
          "hide": false,
          "instant": false,
          "interval": "15m",
          "intervalFactor": 2,
          "legendFormat": "cAdvisor  Version: {{cadvisorVersion}}",
          "refId": "A"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
          },
          "editorMode": "code",
          "expr": "sum by (kernelVersion, job, instance, cadvisorVersion, osVersion) (cadvisor_version_info{})",
          "format": "table",
          "hide": false,
          "interval": "15m",
          "intervalFactor": 2,
          "legendFormat": "Prometheus Version: {{version}}",
          "range": true,
          "refId": "B"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "d6845e8c-9606-4d7f-b711-2895f53fb803"
          },
          "editorMode": "code",
          "expr": "sum by (goarch, goos, goversion, instance, version) (node_exporter_build_info{})",
          "format": "table",
          "hide": false,
          "interval": "15m",
          "intervalFactor": 2,
          "legendFormat": "Node-Exporter Version: {{version}}",
          "range": true,
          "refId": "C"
        }
      ],
      "title": "Running Versions",
      "transformations": [
        {
          "id": "seriesToRows",
          "options": {
            "reducers": []
          }
        },
        {
          "id": "joinByField",
          "options": {
            "byField": "instance",
            "mode": "outer"
          }
        },
        {
          "id": "organize",
          "options": {
            "excludeByName": {
              "Time 1": true,
              "Time 2": true,
              "Time 3": true,
              "Value #A": true,
              "Value #B": true,
              "Value #C": true,
              "goarch": true,
              "goos 1": true,
              "goos 2": true,
              "goversion": true,
              "goversion 1": true,
              "goversion 2": true,
              "version 1": false
            },
            "indexByName": {},
            "renameByName": {
              "Value #B": "",
              "cadvisorVersion": "Cadvisor Version",
              "goos 1": "",
              "goversion 1": "",
              "job": "Job",
              "kernelVersion": "Cadvisor Kernel Version",
              "osVersion": "Cadvisor OS Version",
              "version 1": "Prometheus Version",
              "version 2": "Node Exporter Version"
            }
          }
        }
      ],
      "type": "table"
    }
  ],
  "refresh": "10s",
  "schemaVersion": 38,
  "style": "dark",
  "tags": [
    "docker",
    "prometheus, ",
    "node-exporter",
    "cadvisor"
  ],
  "templating": {
    "list": [
      {
        "auto": false,
        "auto_count": 30,
        "auto_min": "10s",
        "current": {
          "selected": false,
          "text": "1m",
          "value": "1m"
        },
        "hide": 0,
        "label": "interval",
        "name": "interval",
        "options": [
          {
            "selected": true,
            "text": "1m",
            "value": "1m"
          },
          {
            "selected": false,
            "text": "10m",
            "value": "10m"
          },
          {
            "selected": false,
            "text": "30m",
            "value": "30m"
          },
          {
            "selected": false,
            "text": "1h",
            "value": "1h"
          },
          {
            "selected": false,
            "text": "6h",
            "value": "6h"
          },
          {
            "selected": false,
            "text": "12h",
            "value": "12h"
          },
          {
            "selected": false,
            "text": "1d",
            "value": "1d"
          },
          {
            "selected": false,
            "text": "7d",
            "value": "7d"
          },
          {
            "selected": false,
            "text": "14d",
            "value": "14d"
          },
          {
            "selected": false,
            "text": "30d",
            "value": "30d"
          }
        ],
        "query": "1m,10m,30m,1h,6h,12h,1d,7d,14d,30d",
        "refresh": 2,
        "skipUrlSync": false,
        "type": "interval"
      }
    ]
  },
  "time": {
    "from": "now-1h",
    "to": "now"
  },
  "timepicker": {
    "refresh_intervals": [
      "5s",
      "10s",
      "30s",
      "1m",
      "5m",
      "15m",
      "30m",
      "1h",
      "2h",
      "1d"
    ],
    "time_options": [
      "5m",
      "15m",
      "1h",
      "6h",
      "12h",
      "24h",
      "2d",
      "7d",
      "30d"
    ]
  },
  "timezone": "browser",
  "title": "Docker and Host Monitoring w/ Prometheus",
  "uid": "64nrElFmk",
  "version": 4,
  "weekStart": ""
}
OEF

Tạo file cấu hình cho Prometheus.

cat > /home/asterisk/demo/prometheus/prometheus.yml << 'OEF'
global:
  scrape_interval: 1s
  evaluation_interval: 15s
  external_labels:
    monitor: 'Project-technical-report'

alerting:
  alertmanagers:
  - static_configs:
    - targets:
      - alertmanager:9093
rule_files:
  - "/alertmanager/alert.rules"

scrape_configs:
  - job_name: prometheus
    scrape_interval: 5s
    scrape_timeout: 2s
    honor_labels: true

    static_configs:
    - targets: ['prometheus:9090']
  
  - job_name: node-exporter
    scrape_interval: 5s
    scrape_timeout: 2s
    honor_labels: true
    
    static_configs:
      - targets: ['node-exporter:9100', '192.168.13.184:9100']

  - job_name: cadvisor
    scrape_interval: 5s
    scrape_timeout: 2s
    honor_labels: true

    static_configs:
      - targets: ['cadvisor:8080', '192.168.13.184:8080']
OEF

Tiếp theo là file chứa các thông tin rules giúp phát hiện cảnh báo.

cat > /home/asterisk/demo/prometheus/alert.rules << 'OEF'
groups:
- name: targets
  rules:
  - alert: monitor_service_down
    expr: up == 0
    for: 30s
    labels:
      severity: critical
    annotations:
      summary: "Monitor service non-operational"
      description: "Service {{ $labels.instance }} is down."

- name: host
  rules:
  - alert: HighMemoryLoad
    expr: (sum(node_memory_MemTotal_bytes) - sum(node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes) ) / sum(node_memory_MemTotal_bytes) * 100 > 85
    for: 2m
    labels:
      severity: warning
    annotations:
      summary: "Server memory is almost full"
      description: "Docker host memory usage is {{ humanize $value}}%. Reported by instance {{ $labels.instance }} of job {{ $labels.job }}."

- name: containers
  rules:
  - alert: PHP_FPM_Down
    expr: absent(container_memory_usage_bytes{name="project-report-php-fpm"})
    for: 30s
    labels:
      severity: critical
    annotations:
      summary: "PHP-FPM Container is Down"
      description: "PHP-FPM container is down for more than 30 seconds."
  
  - alert: MySQL_Down
    expr: absent(container_memory_usage_bytes{name="project-report-mysql"})
    for: 30s
    labels:
      severity: critical
    annotations:
      summary: "MySQL Container is Down"
      description: "MySQL container is down for more than 30 seconds."
OEF

Cuối cùng là file Docker Compose.

cat > /home/asterisk/demo/docker-compose.yaml << 'OEF'
version: "3.4"

services:
  cadvisor:
    image: gcr.io/google-containers/cadvisor:latest
    container_name: cadvisor
    ports:
     - 8080:8080
    volumes:
    - /:/rootfs:ro
    - /var/run:/var/run:rw
    - /sys:/sys:ro
    - /var/lib/docker/:/var/lib/docker:ro

  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    ports:
     - 9090:9090
    command:
    - '--config.file=/etc/prometheus/prometheus.yml'
    - '--storage.tsdb.path=/prometheus'
    volumes:
    - ./prometheus:/etc/prometheus
    - prometheus-db:/prometheus
    - ./prometheus/alert.rules:/alertmanager/alert.rules
    depends_on:
    - cadvisor
    - node-exporter
    - alertmanager

  node-exporter:
    image: prom/node-exporter
    container_name: node-exporter
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command: 
      - '--path.procfs=/host/proc' 
      - '--path.sysfs=/host/sys'
      - --collector.filesystem.ignored-mount-points
      - "^/(sys|proc|dev|host|etc|rootfs/var/lib/docker/containers|rootfs/var/lib/docker/overlay2|rootfs/run/docker/netns|rootfs/var/lib/docker/aufs)($$|/)"
    ports:
     - 9100:9100
    restart: always

  alertmanager:
    image: prom/alertmanager:v0.22.2
    container_name: alertmanager
    volumes:
      - ./alertmanager:/etc/alertmanager
    command:
      - '--config.file=/etc/alertmanager/config.yml'
      - '--storage.path=/alertmanager'
    ports:
    - 9093:9093
    restart: unless-stopped


  grafana:
    image: grafana/grafana
    ports:
    - 3000:3000
    volumes:
    - grafana-db:/var/lib/grafana
    - ./grafana/provisioning/:/etc/grafana/provisioning/
    restart: always
    user: "472"
    depends_on:
    - prometheus

volumes:
  grafana-db:
  prometheus-db:
OEF

Dưới đây là 1 ví dụ nếu bạn muốn giám sát thêm 1 Docker host, ví dụ Docker host của mình là 192.168.13.184.

cat > /home/asterisk/demo/docker-compose.yaml << 'OEF'
version: "3.4"

services:
  cadvisor:
    image: gcr.io/google-containers/cadvisor:latest
    container_name: cadvisor
    ports:
     - 8080:8080
    volumes:
    - /:/rootfs:ro
    - /var/run:/var/run:rw
    - /sys:/sys:ro
    - /var/lib/docker/:/var/lib/docker:ro

  node-exporter:
    image: prom/node-exporter
    container_name: node-exporter
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command: 
      - '--path.procfs=/host/proc' 
      - '--path.sysfs=/host/sys'
      - --collector.filesystem.ignored-mount-points
      - "^/(sys|proc|dev|host|etc|rootfs/var/lib/docker/containers|rootfs/var/lib/docker/overlay2|rootfs/run/docker/netns|rootfs/var/lib/docker/aufs)($$|/)"
    ports:
     - 9100:9100
    restart: always
OEF

Chuẩn bị xong rồi thì thực hiện Run Docker Compose nhé.

docker-compose up -d

Kết quả.

$root@flask-13-200:/home/asterisk/demo# docker-compose ps
NAME                COMMAND                  SERVICE             STATUS              PORTS
alertmanager        "/bin/alertmanager -…"   alertmanager        running             0.0.0.0:9093->9093/tcp, :::9093->9093/tcp
cadvisor            "/usr/bin/cadvisor -…"   cadvisor            running (healthy)   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp
demo-grafana-1      "/run.sh"                grafana             running             0.0.0.0:3000->3000/tcp, :::3000->3000/tcp
node-exporter       "/bin/node_exporter …"   node-exporter       running             0.0.0.0:9100->9100/tcp, :::9100->9100/tcp
prometheus          "/bin/prometheus --c…"   prometheus          running             0.0.0.0:9090->9090/tcp, :::9090->9090/tcp

Tiếp tục thực hiện Run Docker Compose trên máy host thứ 2 nếu bạn muốn, ví dụ của mình là 192.168.13.184.

$ docker-compose ps
NAME                COMMAND                  SERVICE             STATUS              PORTS
cadvisor            "/usr/bin/cadvisor -…"   cadvisor            running (healthy)   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp
node-exporter       "/bin/node_exporter …"   node-exporter       running             0.0.0.0:9100->9100/tcp, :::9100->9100/tcp

Tham khảo thêm Dashboard từ trang chủ Grafana, file dashboard ở trên mình lấy từ link dưới này với ID là 179 nhưng mình đã chỉnh sửa lại một số thông tin.

https://grafana.com/grafana/dashboards/179-docker-prometheus-monitoring/

Kết quả Targets trên Prometheus.

Thêm thông tin Datasource trên Grafana.

Import Dashboard và bạn có kết quả.

Kết luận.

Việc xây dựng một hệ thống theo dõi với Grafana, Prometheus, cAdvisor và Node Exporter giúp bạn theo dõi và giám sát hiệu suất của hệ thống một cách hiệu quả. Nhờ vào sự tích hợp mạnh mẽ giữa các công cụ này, bạn có khả năng phát hiện sớm các vấn đề và đảm bảo rằng hệ thống của bạn hoạt động ổn định và tối ưu hóa tài nguyên. Điều này đóng một vai trò quan trọng trong việc đảm bảo sự thành công của ứng dụng và dịch vụ của bạn.

LEAVE A REPLY

Please enter your comment!
Please enter your name here

4,956FansLike
256FollowersFollow
223SubscribersSubscribe
spot_img

Related Stories