溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何使用Django和Prometheus以及Kubernetes定制應(yīng)用指標(biāo)

發(fā)布時(shí)間:2021-11-10 16:35:10 來(lái)源:億速云 閱讀:144 作者:柒染 欄目:云計(jì)算

這篇文章給大家介紹如何使用Django和Prometheus以及Kubernetes定制應(yīng)用指標(biāo),內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。

這里強(qiáng)調(diào)了應(yīng)用程序定制指標(biāo)的重要性,用代碼實(shí)例演示了如何設(shè)計(jì)指標(biāo)并整合Prometheus到Django項(xiàng)目中,為使用Django構(gòu)建應(yīng)用的開(kāi)發(fā)者提供了參考。

為什么自定義指標(biāo)很重要?

盡管有大量關(guān)于這一主題的討論,但應(yīng)用程序的自定義指標(biāo)的重要性怎么強(qiáng)調(diào)都不為過(guò)。和為Django應(yīng)用收集的核心服務(wù)指標(biāo)(應(yīng)用和web服務(wù)器統(tǒng)計(jì)數(shù)據(jù)、關(guān)鍵數(shù)據(jù)庫(kù)和緩存操作指標(biāo))不同,自定義指標(biāo)是業(yè)務(wù)特有的數(shù)據(jù)點(diǎn),其邊界和閾值只有你自己知道,這其實(shí)是很有趣的事情。

什么樣的指標(biāo)才是有用的?考慮下面幾點(diǎn):

  • 運(yùn)行一個(gè)電子商務(wù)網(wǎng)站并追蹤平均訂單數(shù)量。突然間訂單的數(shù)量不那么平均了。有了可靠的應(yīng)用指標(biāo)和監(jiān)控,你就可以在損失殆盡之前捕獲到Bug。

  • 你正在寫(xiě)一個(gè)爬蟲(chóng),它每小時(shí)從一個(gè)新聞網(wǎng)站抓取最新的文章。突然最近的文章并不新了。可靠的指標(biāo)和監(jiān)控可以更早地揭示問(wèn)題所在。

  • 我認(rèn)為你已經(jīng)理解了重點(diǎn)。

設(shè)置Django應(yīng)用程序

除了明顯的依賴(pip install Django)之外,我們還需要為寵物項(xiàng)目(譯者注:demo)添加一些額外的包。繼續(xù)并安裝pip install django-prometheus-client。這將為我們提供一個(gè)Python的Prometheus客戶端,以及一些有用的Django hook,包括中間件和一個(gè)優(yōu)雅的DB包裝器。接下來(lái),我們將運(yùn)行Django管理命令來(lái)啟動(dòng)項(xiàng)目,更新我們的設(shè)置來(lái)使用Prometheus客戶端,并將Prometheus的URL添加到URL配置中。

啟動(dòng)一個(gè)新的項(xiàng)目和應(yīng)用程序

為了這篇文章,并且切合代理的品牌,我們建立了一個(gè)遛狗服務(wù)。請(qǐng)注意,它實(shí)際上不會(huì)做什么事,但足以作為一個(gè)教學(xué)示例。執(zhí)行如下命令:

django-admin.py startproject demo
python manage.py startapp walker
#settings.py

INSTALLED_APPS = [
    ...
    'walker',
    ...
]

現(xiàn)在,我們來(lái)添加一些基本的模型和視圖。簡(jiǎn)單起見(jiàn),我只實(shí)現(xiàn)將要驗(yàn)證的部分。如果想要完整地示例,可以從這個(gè)demo應(yīng)用 獲取源碼。

# walker/models.py
from django.db import models
from django_prometheus.models import ExportModelOperationsMixin


class Walker(ExportModelOperationsMixin('walker'), models.Model):
    name = models.CharField(max_length=127)
    email = models.CharField(max_length=127)

    def __str__(self):
        return f'{self.name} // {self.email} ({self.id})'


class Dog(ExportModelOperationsMixin('dog'), models.Model):
    SIZE_XS = 'xs'
    SIZE_SM = 'sm'
    SIZE_MD = 'md'
    SIZE_LG = 'lg'
    SIZE_XL = 'xl'
    DOG_SIZES = (
        (SIZE_XS, 'xsmall'),
        (SIZE_SM, 'small'),
        (SIZE_MD, 'medium'),
        (SIZE_LG, 'large'),
        (SIZE_XL, 'xlarge'),
    )

    size = models.CharField(max_length=31, choices=DOG_SIZES, default=SIZE_MD)
    name = models.CharField(max_length=127)
    age = models.IntegerField()

    def __str__(self):
        return f'{self.name} // {self.age}y ({self.size})'


class Walk(ExportModelOperationsMixin('walk'), models.Model):
    dog = models.ForeignKey(Dog, related_name='walks', on_delete=models.CASCADE)
    walker = models.ForeignKey(Walker, related_name='walks', on_delete=models.CASCADE)

    distance = models.IntegerField(default=0, help_text='walk distance (in meters)')

    start_time = models.DateTimeField(null=True, blank=True, default=None)
    end_time = models.DateTimeField(null=True, blank=True, default=None)

    @property
    def is_complete(self):
        return self.end_time is not None

    @classmethod
    def in_progress(cls):
        """ get the list of `Walk`s currently in progress """
        return cls.objects.filter(start_time__isnull=False, end_time__isnull=True)

    def __str__(self):
        return f'{self.walker.name} // {self.dog.name} @ {self.start_time} ({self.id})'
# walker/views.py
from django.shortcuts import render, redirect
from django.views import View
from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpResponseNotFound, JsonResponse, HttpResponseBadRequest, Http404
from django.urls import reverse
from django.utils.timezone import now
from walker import models, forms


class WalkDetailsView(View):
    def get_walk(self, walk_id=None):
        try:
            return models.Walk.objects.get(id=walk_id)
        except ObjectDoesNotExist:
            raise Http404(f'no walk with ID {walk_id} in progress')


class CheckWalkStatusView(WalkDetailsView):
    def get(self, request, walk_id=None, **kwargs):
        walk = self.get_walk(walk_id=walk_id)
        return JsonResponse({'complete': walk.is_complete})


class CompleteWalkView(WalkDetailsView):
    def get(self, request, walk_id=None, **kwargs):
        walk = self.get_walk(walk_id=walk_id)
        return render(request, 'index.html', context={'form': forms.CompleteWalkForm(instance=walk)})

    def post(self, request, walk_id=None, **kwargs):
        try:
            walk = models.Walk.objects.get(id=walk_id)
        except ObjectDoesNotExist:
            return HttpResponseNotFound(content=f'no walk with ID {walk_id} found')

        if walk.is_complete:
            return HttpResponseBadRequest(content=f'walk {walk.id} is already complete')

        form = forms.CompleteWalkForm(data=request.POST, instance=walk)

        if form.is_valid():
            updated_walk = form.save(commit=False)
            updated_walk.end_time = now()
            updated_walk.save()

            return redirect(f'{reverse("walk_start")}?walk={walk.id}')

        return HttpResponseBadRequest(content=f'form validation failed with errors {form.errors}')


class StartWalkView(View):
    def get(self, request):
        return render(request, 'index.html', context={'form': forms.StartWalkForm()})

    def post(self, request):
        form = forms.StartWalkForm(data=request.POST)

        if form.is_valid():
            walk = form.save(commit=False)
            walk.start_time = now()
            walk.save()

            return redirect(f'{reverse("walk_start")}?walk={walk.id}')

        return HttpResponseBadRequest(content=f'form validation failed with errors {form.errors}')

更新應(yīng)用設(shè)置并添加Prometheus urls

現(xiàn)在我們有了一個(gè)Django項(xiàng)目以及相應(yīng)的設(shè)置,可以為 django-prometheus添加需要的配置項(xiàng)了。在 settings.py中添加下面的配置:

INSTALLED_APPS = [
    ...
    'django_prometheus',
    ...
]

MIDDLEWARE = [
    'django_prometheus.middleware.PrometheusBeforeMiddleware',
    ....
    'django_prometheus.middleware.PrometheusAfterMiddleware',
]

# we're assuming a Postgres DB here because, well, that's just the right choice :)
DATABASES = {
    'default': {
        'ENGINE': 'django_prometheus.db.backends.postgresql',
        'NAME': os.getenv('DB_NAME'),
        'USER': os.getenv('DB_USER'),
        'PASSWORD': os.getenv('DB_PASSWORD'),
        'HOST': os.getenv('DB_HOST'),
        'PORT': os.getenv('DB_PORT', '5432'),
    },
}

添加url配置到 urls.py

urlpatterns = [
    ...
    path('', include('django_prometheus.urls')),
]

現(xiàn)在我們有了一個(gè)配置好的基本應(yīng)用,并為整合做好了準(zhǔn)備。


添加Prometheus指標(biāo)

由于django-prometheus提供了開(kāi)箱即用功能,我們可以立即追蹤一些基本的模型操作,比如插入和刪除??梢栽?code>/metricsendpoint看到這些:

如何使用Django和Prometheus以及Kubernetes定制應(yīng)用指標(biāo)django-prometheus提供的默認(rèn)指標(biāo)

讓我們把它變得更有趣點(diǎn)。

添加一個(gè)walker/metrics.py文件,定義一些要追蹤的基本指標(biāo)。

# walker/metrics.py
from prometheus_client import Counter, Histogram


walks_started = Counter('walks_started', 'number of walks started')
walks_completed = Counter('walks_completed', 'number of walks completed')
invalid_walks = Counter('invalid_walks', 'number of walks attempted to be started, but invalid')

walk_distance = Histogram('walk_distance', 'distribution of distance walked', buckets=[0, 50, 200, 400, 800, 1600, 3200])

很簡(jiǎn)單,不是嗎?Prometheus文檔很好地解釋了每種指標(biāo)類型的用途,簡(jiǎn)言之,我們使用計(jì)數(shù)器來(lái)表示嚴(yán)格隨時(shí)間增長(zhǎng)的指標(biāo),使用直方圖來(lái)追蹤包含值分布的指標(biāo)。下面開(kāi)始驗(yàn)證應(yīng)用的代碼。

# walker/views.py
...
from walker import metrics
...

class CompleteWalkView(WalkDetailsView):
    ...
    def post(self, request, walk_id=None, **kwargs):
        ...
        if form.is_valid():
            updated_walk = form.save(commit=False)
            updated_walk.end_time = now()
            updated_walk.save()

            metrics.walks_completed.inc()
            metrics.walk_distance.observe(updated_walk.distance)

            return redirect(f'{reverse("walk_start")}?walk={walk.id}')

        return HttpResponseBadRequest(content=f'form validation failed with errors {form.errors}')

...

class StartWalkView(View):
    ...
    def post(self, request):
        if form.is_valid():
            walk = form.save(commit=False)
            walk.start_time = now()
            walk.save()

            metrics.walks_started.inc()

            return redirect(f'{reverse("walk_start")}?walk={walk.id}')

        metrics.invalid_walks.inc()

        return HttpResponseBadRequest(content=f'form validation failed with errors {form.errors}')

發(fā)送幾個(gè)樣例請(qǐng)求,可以看到新指標(biāo)已經(jīng)產(chǎn)生了。

如何使用Django和Prometheus以及Kubernetes定制應(yīng)用指標(biāo)顯示散步距離和創(chuàng)建散步的指標(biāo)

如何使用Django和Prometheus以及Kubernetes定制應(yīng)用指標(biāo)定義的指標(biāo)此時(shí)已經(jīng)可以在prometheus里查找到了

至此,我們已經(jīng)在代碼中添加了自定義指標(biāo),整合了應(yīng)用以追蹤指標(biāo),并驗(yàn)證了這些指標(biāo)已在/metrics 上更新并可用。讓我們繼續(xù)將儀表化應(yīng)用部署到Kubernetes集群。

使用Helm部署應(yīng)用

我只會(huì)列出和追蹤、導(dǎo)出指標(biāo)相關(guān)的配置內(nèi)容,完整的Helm chart部署和服務(wù)配置可以在 demo應(yīng)用中找到。 作為起點(diǎn),這有一些和導(dǎo)出指標(biāo)相關(guān)的deployment和configmap的配置:

# helm/demo/templates/nginx-conf-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "demo.fullname" . }}-nginx-conf
  ...
data:
  demo.conf: |
    upstream app_server {
      server 127.0.0.1:8000 fail_timeout=0;
    }

    server {
      listen 80;
      client_max_body_size 4G;

      # set the correct host(s) for your site
      server_name{{ range .Values.ingress.hosts }} {{ . }}{{- end }};

      keepalive_timeout 5;

      root /code/static;

      location / {
        # checks for static file, if not found proxy to app
        try_files $uri @proxy_to_app;
      }

      location ^~ /metrics {
        auth_basic           "Metrics";
        auth_basic_user_file /etc/nginx/secrets/.htpasswd;

        proxy_pass http://app_server;
      }

      location @proxy_to_app {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $http_host;
        # we don't want nginx trying to do something clever with
        # redirects, we set the Host: header above already.
        proxy_redirect off;
        proxy_pass http://app_server;
      }
    }

# helm/demo/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
...
    spec:
      metadata:
        labels:
          app.kubernetes.io/name: {{ include "demo.name" . }}
          app.kubernetes.io/instance: {{ .Release.Name }}
          app: {{ include "demo.name" . }}
      volumes:
        ...
        - name: nginx-conf
          configMap:
            name: {{ include "demo.fullname" . }}-nginx-conf
        - name: prometheus-auth
          secret:
            secretName: prometheus-basic-auth
        ...
      containers:
        - name: {{ .Chart.Name }}-nginx
          image: "{{ .Values.nginx.image.repository }}:{{ .Values.nginx.image.tag }}"
          imagePullPolicy: IfNotPresent
          volumeMounts:
            ...
            - name: nginx-conf
              mountPath: /etc/nginx/conf.d/
            - name: prometheus-auth
              mountPath: /etc/nginx/secrets/.htpasswd
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          command: ["gunicorn", "--worker-class", "gthread", "--threads", "3", "--bind", "0.0.0.0:8000", "demo.wsgi:application"]
          env:
{{ include "demo.env" . | nindent 12 }}
          ports:
            - name: gunicorn
              containerPort: 8000
              protocol: TCP
           ...

沒(méi)什么神奇的,只是一些YAML而已。有兩個(gè)重點(diǎn)需要強(qiáng)調(diào)一下:

  1. 我們通過(guò)一個(gè)nginx反向代理將/metrics放在了驗(yàn)證后面,為location塊設(shè)置了auth_basic指令集。你可能希望在反向代理之后部署gunicorn ,但這樣做可以獲得保護(hù)指標(biāo)的額外好處。

  2. 我們使用多線程的gunicorn而不是多個(gè)worker。雖然可以為Prometheus客戶端啟用多進(jìn)程模式,但在Kubernetes環(huán)境中,安裝會(huì)更為復(fù)雜。為什么這很重要呢?在一個(gè)pod中運(yùn)行多個(gè)worker的風(fēng)險(xiǎn)在于,每個(gè)worker將在采集時(shí)報(bào)告自己的一組指標(biāo)值。但是,由于服務(wù)在Prometheus Kubernetes SD scrape配置中被設(shè)置為pod級(jí)別 ,這些(潛在的)跳轉(zhuǎn)值將被錯(cuò)誤地分類為計(jì)數(shù)器重置,從而導(dǎo)致測(cè)量結(jié)果不一致。你并不一定需要遵循上述所有步驟,但重點(diǎn)是:如果你了解的不多,應(yīng)該從一個(gè)單線程+單worker的gunicorn環(huán)境開(kāi)始,或者從一個(gè)單worker+多線程環(huán)境開(kāi)始。

使用Helm部署Prometheus

基于Helm的幫助文檔,部署Prometheus非常簡(jiǎn)單,不需要額外工作:

helm upgrade --install prometheus stable/prometheus

幾分鐘后,你應(yīng)該就可以通過(guò) port-forward 進(jìn)入Prometheus的pod(默認(rèn)的容器端口是9090)。

為應(yīng)用配置Prometheus scrape目標(biāo)

Prometheus Helm chart 有大量的自定義可選項(xiàng),不過(guò)我們只需要設(shè)置extraScrapeConfigs。創(chuàng)建一個(gè)values.yaml文件。你可以略過(guò)這部分直接使用 demo應(yīng)用 作為參考。文件內(nèi)容如下:

extraScrapeConfigs: |
  - job_name: demo
    scrape_interval: 5s
    metrics_path: /metrics
    basic_auth:
      username: prometheus
      password: prometheus
    tls_config:
      insecure_skip_verify: true
    kubernetes_sd_configs:
      - role: endpoints
        namespaces:
          names:
            - default
    relabel_configs:
      - source_labels: [__meta_kubernetes_service_label_app]
        regex: demo
        action: keep
      - source_labels: [__meta_kubernetes_endpoint_port_name]
        regex: http
        action: keep
      - source_labels: [__meta_kubernetes_namespace]
        target_label: namespace
      - source_labels: [__meta_kubernetes_pod_name]
        target_label: pod
      - source_labels: [__meta_kubernetes_service_name]
        target_label: service
      - source_labels: [__meta_kubernetes_service_name]
        target_label: job
      - target_label: endpoint
        replacement: http

創(chuàng)建完成后,就可以通過(guò)下面的操作為prometheus deployment更新配置。

helm upgrade --install prometheus -f values.yaml

為驗(yàn)證所有的步驟都配置正確了,打開(kāi)瀏覽器輸入 http://localhost:9090/targets (假設(shè)你已經(jīng)通過(guò) port-forward進(jìn)入了運(yùn)行prometheus的Pod)。如果你看到demo應(yīng)用在target的列表中,說(shuō)明運(yùn)行正常了。

關(guān)于如何使用Django和Prometheus以及Kubernetes定制應(yīng)用指標(biāo)就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI