Учебное пособие по ХУЛЛ 06: Письменные услуги и ингрессии


Подготовка

Далее мы рассмотрим объекты из Kubernetes Service API, которые включают в себя Services, Ingresses и Endpoints.

Как и прежде, скопируйте нашу рабочую схему из предыдущей части урока в новую папку:

cd ~/kubernetes-dashboard-hull && cp -R 05_workloads/ 06_service_api && cd 06_service_api/kubernetes-dashboard-hull
Войти в полноэкранный режим Выйти из полноэкранного режима

Чтобы удалить HULL hull.objects из values.full.yaml и подготовить наш тестовый values.yaml войдите:

sed '/  objects:/Q' values.full.yaml > values.yaml
Войти в полноэкранный режим Выйти из полноэкранного режима

и проверьте результат:

cat values.yaml
Войти в полноэкранный режим Выйти из полноэкранного режима

выглядит как:

metrics-server:
  enabled: false
hull:
  config:
    specific:
      protocolHttp: false
      rbac:
        clusterReadOnlyRole: false
        clusterRoleMetrics: true
      settings: {}
      pinnedCRDs: {}
Войти в полноэкранный режим Выход из полноэкранного режима

Проверка наличия существующих объектов Service API

Начните исследование, как вы это делали раньше:

find ../kubernetes-dashboard/templates -type f -iregex '.*(Service|Ingress|Endpoint).*' | sort
Войти в полноэкранный режим Выйти из полноэкранного режима

возвращается:

../kubernetes-dashboard/templates/ingress.yaml
../kubernetes-dashboard/templates/service.yaml
../kubernetes-dashboard/templates/serviceaccount.yaml
../kubernetes-dashboard/templates/servicemonitor.yaml
Войти в полноэкранный режим Выйти из полноэкранного режима

Игнорируя ServiceAccount (уже обработанный) и ServiceMonitor (обработанный позже), существует служба и определение Ingress.

Определение служб в HULL

Содержимое шаблона Service в /templates/service.yaml:

cat ../kubernetes-dashboard/templates/service.yaml
Вход в полноэкранный режим Выйти из полноэкранного режима

выглядит следующим образом:

# Copyright 2017 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

apiVersion: v1
kind: Service
metadata:
  name: {{ template "kubernetes-dashboard.fullname" . }}
  labels:
    {{ include "kubernetes-dashboard.labels" . | nindent 4 }}
    app.kubernetes.io/component: kubernetes-dashboard
    {{ .Values.service.clusterServiceLabel.key | nindent 4}}: {{ .Values.service.clusterServiceLabel.enabled | quote }}
    {{- if .Values.service.labels }}
    {{ toYaml .Values.service.labels | nindent 4 }}
    {{- end }}
    {{- if .Values.commonLabels }}
    {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }}
    {{- end }}
  annotations:
    {{- if .Values.commonAnnotations }}
    {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
    {{- end }}
    {{- if .Values.service.annotations }}
    {{ toYaml .Values.service.annotations | nindent 4 }}
    {{- end }}
spec:
  type: {{ .Values.service.type }}
  ports:
  - port: {{ .Values.service.externalPort }}
{{- if .Values.protocolHttp }}
    targetPort: http
    name: http
{{- else }}
    targetPort: https
    name: https
{{- end }}
{{- if hasKey .Values.service "nodePort" }}
    nodePort: {{ .Values.service.nodePort }}
{{- end }}
{{- if .Values.service.loadBalancerSourceRanges }}
  loadBalancerSourceRanges:
{{ toYaml .Values.service.loadBalancerSourceRanges | nindent 4 }}
{{- end }}
  selector:
{{ include "kubernetes-dashboard.matchLabels" . | nindent 4 }}
    app.kubernetes.io/component: kubernetes-dashboard
{{- if .Values.service.loadBalancerIP }}
  loadBalancerIP: {{ .Values.service.loadBalancerIP }}
{{- end }}
Вход в полноэкранный режим Выход из полноэкранного режима

Снова есть ссылка на свойство .Values.protocolHttp, которое теперь существует в разделе hull.config.specific. Свойства portsname и targetPort устанавливаются в зависимости от protocolHttp, тогда как externalPort определяется централизованно. Поэтому имеет смысл централизовать доступ к externalPort в hull.config.specific, тем более что к externalPort также обращаются при определении Ingress (подробнее об этом позже). Для этого выполните следующие действия:

sed '/^ssssspecific/r'<(
      echo "      externalPort: 443"
    ) -i -- values.yaml
Войти в полноэкранный режим Выйти из полноэкранного режима

Кроме ports есть только selector, который стоит обсудить, остальная часть шаблона Service представляет собой прямые 1:1 сопоставления. Но также проверьте значения по умолчанию блока службы в kubernetes-dashboard в values.yaml:

cat ../kubernetes-dashboard/values.yaml | grep "^service:" -B 1 -A 23
Войти в полноэкранный режим Выйти из полноэкранного режима

который печатает:


service:
  type: ClusterIP
  # Dashboard service port
  externalPort: 443

  ## LoadBalancerSourcesRange is a list of allowed CIDR values, which are combined with ServicePort to
  ## set allowed inbound rules on the security group assigned to the master load balancer
  # loadBalancerSourceRanges: []

  ## A user-specified IP address for load balancer to use as External IP (if supported)
  # loadBalancerIP:

  ## Additional Kubernetes Dashboard Service annotations
  annotations: {}

  ## Here labels can be added to the Kubernetes Dashboard service
  labels: {}

  ## Enable or disable the kubernetes.io/cluster-service label. Should be disabled for GKE clusters >=1.15.
  ## Otherwise, the addon manager will presume ownership of the service and try to delete it.
  clusterServiceLabel:
    enabled: true
    key: "kubernetes.io/cluster-service"
Войти в полноэкранный режим Выйти из полноэкранного режима

К объекту Service добавляется метка kubernetes.io/cluster-service, а в качестве значения выступает enabled, что представлено в этой части шаблона Service:

    {{ .Values.service.clusterServiceLabel.key | nindent 4}}: {{ .Values.service.clusterServiceLabel.enabled | quote }}
Вход в полноэкранный режим Выход из полноэкранного режима

Хотя вы можете подражать этой логике и переместить clusterServiceLabel в раздел hull.config.specific, это не дает никакого преимущества перед добавлением метки к новому экземпляру службы со значением по умолчанию true. Конечно, можно добавить комментарий, чтобы объяснить более глубокий смысл этой метки.

Более подробный взгляд на селекторы служб

Как уже отмечалось ранее, selector matchLabels для рабочих нагрузок автоматически строится из следующих меток:

  • app.kubernetes.io/component: default
  • app.kubernetes.io/instance: release-name
  • app.kubernetes.io/name: kubernetes-dashboard

Объекты Службы selector в HULL следуют тому же принципу построения, поэтому это означает, что любая Служба, чей ключ идентичен экземпляру объекта рабочей нагрузки, selector будет соответствовать, и Служба будет фронтировать стручки рабочей нагрузки. В нашем примере, если вы определите Deployment, DaemonSet или StatefulSet с ключом dashboard, любой объект Службы с тем же ключом dashboard будет соответствовать и будет позиционироваться как объект Службы для стручков на сетевом уровне. Это то, чего вы хотите добиться здесь, — служба для развертывания dashboard. Попутно заметим, что для Jobs свойство selector автоматически опускается HULL, поскольку в данном случае обработка selector Jobs внутренне управляется Kubernetes.

Поэтому если вы опустите спецификацию selector, у вас автоматически будет создан selector по умолчанию, но если вы хотите смоделировать свойство selector самостоятельно, вы можете явно перезаписать его. Примером такого использования является, например, создание дополнительной безголовой службы для StatefulSets или любой другой более тонкий контроль соответствия между службами и бодами. Для этого также существует преобразование HULL, hull.util.transformation.selector или сокращенно _HT&, которое будет использовано позже.

А пока вот преобразованный объект Service для dashboard, использующий уже изученные вами приемы:

echo '  objects:
    service:
      dashboard:
        labels:
          ## Enable or disable the kubernetes.io/cluster-service label. Should be disabled for GKE clusters >=1.15.
          ## Otherwise, the addon manager will presume ownership of the service and try to delete it.
          "kubernetes.io/cluster-service": "true"
        type: ClusterIP
        ports:
          http:
            enabled: _HT?(index . "$").Values.hull.config.specific.protocolHttp
            port: _HT*hull.config.specific.externalPort
            targetPort: http
          https:
            enabled: _HT?(not (index . "$").Values.hull.config.specific.protocolHttp)
            port: _HT*hull.config.specific.externalPort
            targetPort: https' >> values.yaml
Вход в полноэкранный режим Выход из полноэкранного режима

Определение ингрессов для входящего трафика

Далее посмотрите на исходное определение ингресса с помощью:

cat ../kubernetes-dashboard/templates/ingress.yaml
Войти в полноэкранный режим Выйти из полноэкранного режима

возвращение:

# Copyright 2017 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

{{ if .Values.ingress.enabled -}}
{{- $serviceName := include "kubernetes-dashboard.fullname" . -}}
{{- $servicePort := .Values.service.externalPort -}}
{{- $paths := .Values.ingress.paths -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ template "kubernetes-dashboard.fullname" . }}
  labels:
    {{- include "kubernetes-dashboard.labels" . | nindent 4 }}
    {{- range $key, $value := .Values.ingress.labels }}
    {{ $key }}: {{ $value | quote }}
    {{- end }}
    {{- if .Values.commonLabels }}
    {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }}
    {{- end }}
  annotations:
    {{- if .Values.commonAnnotations }}
    {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
    {{- end }}
    {{- if not .Values.protocolHttp }}
    # Add https backend protocol support for ingress-nginx
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
    # Add https backend protocol support for GKE
    service.alpha.kubernetes.io/app-protocols: '{"https":"HTTPS"}'
    {{- end }}
    {{- with .Values.ingress.annotations }}
    {{ toYaml . | nindent 4 }}
    {{- end }}
spec:
  {{- with .Values.ingress.className }}
  ingressClassName: {{ . | quote }}
  {{- end }}
  rules:
  {{- if .Values.ingress.hosts }}
  {{- range $host := .Values.ingress.hosts }}
    - host: {{ $host }}
      http:
        paths:
  {{- if len ($.Values.ingress.customPaths) }}
  {{- "n" }}{{ tpl (toYaml $.Values.ingress.customPaths | nindent 10) $ }}
  {{- else }}
  {{- range $p := $paths }}
          - path: {{ $p }}
            pathType: ImplementationSpecific
            backend:
              service:
                name: {{ $serviceName }}
                port:
                  number: {{ $servicePort }}
  {{- end -}}
  {{- end -}}
  {{- end -}}
  {{- else }}
    - http:
        paths:
  {{- if len ($.Values.ingress.customPaths) }}
  {{- "n" }}{{ tpl (toYaml $.Values.ingress.customPaths | nindent 10) $ }}
  {{- else }}
  {{- range $p := $paths }}
          - path: {{ $p }}
            pathType: ImplementationSpecific
            backend:
              service:
                name: {{ $serviceName }}
                port:
                  number: {{ $servicePort }}
  {{- end -}}
  {{- end -}}
  {{- end -}}
  {{- if .Values.ingress.tls }}
  tls:
{{ toYaml .Values.ingress.tls | nindent 4 }}
  {{- end -}}
{{- end -}}
Вход в полноэкранный режим Выход из полноэкранного режима

Вот что говорит values.yaml об определении ingress:

cat ../kubernetes-dashboard/values.yaml | grep "^ingress:" -B 1 -A 59
Войти в полноэкранный режим Выйти из полноэкранного режима

а именно:


ingress:
  ## If true, Kubernetes Dashboard Ingress will be created.
  ##
  enabled: false

  ## Kubernetes Dashboard Ingress labels
  # labels:
  #   key: value

  ## Kubernetes Dashboard Ingress annotations
  # annotations:
  #   kubernetes.io/ingress.class: nginx
  #   kubernetes.io/tls-acme: 'true'

  ## If you plan to use TLS backend with enableInsecureLogin set to false
  ## (default), you need to uncomment the below.
  ## If you use ingress-nginx < 0.21.0
  #   nginx.ingress.kubernetes.io/secure-backends: "true"
  ## if you use ingress-nginx >= 0.21.0
  #   nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"

  ## Kubernetes Dashboard Ingress Class
  # className: "example-lb"

  ## Kubernetes Dashboard Ingress paths
  ## Both `/` and `/*` are required to work on gce ingress.
  paths:
    - /
  #  - /*

  ## Custom Kubernetes Dashboard Ingress paths. Will override default paths.
  ##
  customPaths: []
  #  - pathType: ImplementationSpecific
  #    backend:
  #      service:
  #        name: ssl-redirect
  #        port:
  #          name: use-annotation
  #  - pathType: ImplementationSpecific
  #    backend:
  #      service:
  #        name: >-
  #          {{ include "kubernetes-dashboard.fullname" . }}
  #        port:
  #          # Don't use string here, use only integer value!
  #          number: 443
  ## Kubernetes Dashboard Ingress hostnames
  ## Must be provided if Ingress is enabled
  ##
  # hosts:
  #   - kubernetes-dashboard.domain.com
  ## Kubernetes Dashboard Ingress TLS configuration
  ## Secrets must be manually created in the namespace
  ##
  # tls:
  #   - secretName: kubernetes-dashboard-tls
  #     hosts:
  #       - kubernetes-dashboard.domain.com

Войти в полноэкранный режим Выйти из полноэкранного режима

Документация values.yaml немного запутана, и шаблон довольно сложно расшифровать сразу, поэтому разберите его на части, чтобы понять, что здесь происходит:

  • аннотации должны быть добавлены в ingress при условии, что protocolHttp не является true. Выдержка из шаблона ingress:
  {{- if not .Values.protocolHttp }}
    # Add https backend protocol support for ingress-nginx
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
    # Add https backend protocol support for GKE
    service.alpha.kubernetes.io/app-protocols: '{"https":"HTTPS"}'
    {{- end }}
Вход в полноэкранный режим Выход из полноэкранного режима
  • Раздел rules записей в основном повторяется в шаблоне, один раз для всех хостов, если они указаны, или один раз без свойства host, если хосты не указаны. Что касается paths для обработки в обоих блоках, шаблон будет записывать все полностью указанные customPaths, если таковые имеются, или будет перебирать предопределенные paths, если customPaths не указаны, где paths все указывают на dashboard Service:
  rules:
  {{- if .Values.ingress.hosts }}
  {{- range $host := .Values.ingress.hosts }}
    - host: {{ $host }}
      http:
        paths:
  {{- if len ($.Values.ingress.customPaths) }}
  {{- "n" }}{{ tpl (toYaml $.Values.ingress.customPaths | nindent 10) $ }}
  {{- else }}
  {{- range $p := $paths }}
          - path: {{ $p }}
            pathType: ImplementationSpecific
            backend:
              service:
                name: {{ $serviceName }}
                port:
                  number: {{ $servicePort }}
  {{- end -}}
  {{- end -}}
  {{- end -}}
  {{- else }}
    - http:
        paths:
  {{- if len ($.Values.ingress.customPaths) }}
  {{- "n" }}{{ tpl (toYaml $.Values.ingress.customPaths | nindent 10) $ }}
  {{- else }}
  {{- range $p := $paths }}
          - path: {{ $p }}
            pathType: ImplementationSpecific
            backend:
              service:
                name: {{ $serviceName }}
                port:
                  number: {{ $servicePort }}
  {{- end -}}
  {{- end -}}
  {{- end -}}
Вход в полноэкранный режим Выход из полноэкранного режима

Вот простой подход к моделированию этого входа, где добавляется правило по умолчанию, как это было бы в оригинальной конфигурации values.yaml:

echo '    ingress:
      dashboard:
        enabled: false
        annotations: |- 
          _HT!{
            {{ if not (index . "$").Values.hull.config.specific.protocolHttp }}
            "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS",
            "service.alpha.kubernetes.io/app-protocols": "{"https":"HTTPS"}"
            {{ end }}
          }
        rules:
          default:
            http:
              paths:
                root:
                  path: /
                  pathType: ImplementationSpecific
                  backend:
                    service:
                      name: dashboard
                      port:
                        number: _HT*hull.config.specific.externalPort' >> values.yaml
Вход в полноэкранный режим Выход из полноэкранного режима

Если вам нужно изменить его во время развертывания, вы можете:

  • добавить host к пути default
  • изменить path для правила default, например, чтобы оно соответствовало нужному вам маршруту
  • установить rule для default: null, в котором оно больше не будет отображаться, и создать свое собственное rule.

В качестве проверки здравомыслия вы должны включить ингрессию, чтобы увидеть, что она печатается, как ожидалось:

echo 'hull:
  objects:
    ingress:
      dashboard:
        enabled: true' > ../configs/enable-ingress.yaml 
&& helm template -f ../configs/disable-default-rbac.yaml -f ../configs/enable-ingress.yaml .
Войти в полноэкранный режим Выйти из полноэкранного режима

и вот оно:

---
# Source: kubernetes-dashboard/templates/hull.yaml
apiVersion: v1
kind: Service
metadata:
  annotations: {}
  labels:
    app.kubernetes.io/component: dashboard
    app.kubernetes.io/instance: release-name
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: kubernetes-dashboard
    app.kubernetes.io/part-of: undefined
    app.kubernetes.io/version: 2.5.0
    helm.sh/chart: kubernetes-dashboard-5.2.0
    kubernetes.io/cluster-service: "true"
  name: release-name-kubernetes-dashboard-dashboard
spec:
  ports:
  - name: https
    port: 443
    targetPort: https
  selector:
    app.kubernetes.io/component: dashboard
    app.kubernetes.io/instance: release-name
    app.kubernetes.io/name: kubernetes-dashboard
  type: ClusterIP
---
# Source: kubernetes-dashboard/templates/hull.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/backend-protocol: HTTPS
    service.alpha.kubernetes.io/app-protocols: '{"https":"HTTPS"}'
  labels:
    app.kubernetes.io/component: dashboard
    app.kubernetes.io/instance: release-name
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: kubernetes-dashboard
    app.kubernetes.io/part-of: undefined
    app.kubernetes.io/version: 2.5.0
    helm.sh/chart: kubernetes-dashboard-5.2.0
  name: release-name-kubernetes-dashboard-dashboard
spec:
  rules:
  - host:
    http:
      paths:
      - backend:
          service:
            name: release-name-kubernetes-dashboard-dashboard
            port:
              number: 443
        path: /
        pathType: ImplementationSpecific
  tls: []
Войти в полноэкранный режим Выход из полноэкранного режима

Поскольку опция protocolHttp оказывает значительное влияние на отображаемый результат, также сделайте быстрый тест, все ли в порядке:

echo 'hull:
  config:
    specific:
      protocolHttp: true
  objects:
    ingress:
      dashboard:
        enabled: true' > ../configs/enable-ingress-http.yaml 
&& helm template -f ../configs/disable-default-rbac.yaml -f ../configs/enable-ingress-http.yaml .
Войти в полноэкранный режим Выйдите из полноэкранного режима

и вот оно:

---
# Source: kubernetes-dashboard/templates/hull.yaml
apiVersion: v1
kind: Service
metadata:
  annotations: {}
  labels:
    app.kubernetes.io/component: dashboard
    app.kubernetes.io/instance: release-name
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: kubernetes-dashboard
    app.kubernetes.io/part-of: undefined
    app.kubernetes.io/version: 2.5.0
    helm.sh/chart: kubernetes-dashboard-5.2.0
    kubernetes.io/cluster-service: "true"
  name: release-name-kubernetes-dashboard-dashboard
spec:
  ports:
  - name: http
    port: 443
    targetPort: http
  selector:
    app.kubernetes.io/component: dashboard
    app.kubernetes.io/instance: release-name
    app.kubernetes.io/name: kubernetes-dashboard
  type: ClusterIP
---
# Source: kubernetes-dashboard/templates/hull.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations: {}
  labels:
    app.kubernetes.io/component: dashboard
    app.kubernetes.io/instance: release-name
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: kubernetes-dashboard
    app.kubernetes.io/part-of: undefined
    app.kubernetes.io/version: 2.5.0
    helm.sh/chart: kubernetes-dashboard-5.2.0
  name: release-name-kubernetes-dashboard-dashboard
spec:
  rules:
  - host:
    http:
      paths:
      - backend:
          service:
            name: release-name-kubernetes-dashboard-dashboard
            port:
              number: 443
        path: /
        pathType: ImplementationSpecific
  tls: []
Войти в полноэкранный режим Выход из полноэкранного режима

Еще одно подведение итогов

Итак, чтобы быстро закончить:

  1. Вот как сейчас должен выглядеть ваш values.yaml:

    metrics-server:
      enabled: false
    hull:
      config:
        specific:
          externalPort: 443
          protocolHttp: false
          rbac:
            clusterReadOnlyRole: false
            clusterRoleMetrics: true
          settings: {}
          pinnedCRDs: {}
      objects:
        service:
          dashboard:
            labels:
              ## Enable or disable the kubernetes.io/cluster-service label. Should be disabled for GKE clusters >=1.15.
              ## Otherwise, the addon manager will presume ownership of the service and try to delete it.
              "kubernetes.io/cluster-service": "true"
            type: ClusterIP
            ports:
              http:
                enabled: _HT?(index . "$").Values.hull.config.specific.protocolHttp
                port: _HT*hull.config.specific.externalPort
                targetPort: http
              https:
                enabled: _HT?(not (index . "$").Values.hull.config.specific.protocolHttp)
                port: _HT*hull.config.specific.externalPort
                targetPort: https
        ingress:
          dashboard:
            enabled: false
            annotations: |-
              _HT!{
                {{ if not (index . "$").Values.hull.config.specific.protocolHttp }}
                "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS",
                "service.alpha.kubernetes.io/app-protocols": "{"https":"HTTPS"}"
                {{ end }}
              }
            rules:
              default:
                http:
                  paths:
                    root:
                      path: /
                      pathType: ImplementationSpecific
                      backend:
                        service:
                          name: dashboard
                          port:
                            number: _HT*hull.config.specific.externalPort
    
  2. Запустите резервную копию values.yaml:

    cp values.yaml values.tutorial-part.yaml
    
  3. Добавьте уже созданные объекты:

    sed '1,/objects:/d' values.full.yaml > _tmp && cp values.yaml values.full.yaml && cat _tmp >> values.full.yaml && rm _tmp
    

Спасибо за участие и надеемся увидеть вас в следующей части туториала по расширенной конфигурации объектов!

Оцените статью
Procodings.ru
Добавить комментарий