Skip to content

YurDuiachenko/mindbox-sre

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 

Repository files navigation

# Здравствуйте, HR и SRE команды Mindbox!
# Меня зовут Дьяченко Юрий, и это тестовое задание на вакансию DevOps-инженер/SRE.

# Первый момент: нашему приложению нужен неймспейс.
# Если мы конечно не хотим, чтобы оно находилось в default (а мы не хотим).
# В целом лучше назвать его как стенд, например dev или qa, 
# но мне кажется более подходящим держать всё в абстрактном виде, поэтому app-namespace.
apiVersion: v1
kind: Namespace
metadata:
  name: app-namespace

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-deployment
  namespace: app-namespace
  labels:
    app: app
spec:
  # На счёт количества реплик.
  # Я попробовал рассмотреть 5 и 4 реплик и выбрать лучший вариант. 
  # Вот аргументы за 5 подов:
  # Да, нагрузочное тестирование показало, что 4 пода справляются с пиковой нагрузкой, 
  # но 5 мне кажется более правильным решением по следующим причинам:
  #   1. это тестирование, то есть реальная нагрузка может быть и больше, в таком случае лучше иметь запас;
  #   2. так как у нас пять нод, в целях обеспечения отказоустойчивости целесообразно было бы разнести по поду на каждую ноду, 
  #   ведь, если упадёт одна нода при 5 репликах, это не повлияет на общую работоспособность приложения, 
  #   а если у нас пик, и реплик изначально 4, и отказывает узел?
  # 
  # Но контраргументировав их, я всё же пришёл к 4:
  #   1. нагрузочное тестирование нужно не просто так, и если известно, что 4 пода выдерживают пик, то ещё один под
  #   это пустая трата ресурсов. Этот под при крайней необходимости и так будет поднят.
  #   2. 4 пода прекрасно разнесутся на 5 нод, ещё и место свободное оставят на всякий случай. Такой вариант экономичнее.
  replicas: 4
  # На счёт обновлений.
  # На самом деле такие значения получатся по умолчанию, но мне кажется лучше держать их в явном виде и не в процентах.
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1 
      maxUnavailable: 1
  selector:
    matchLabels:
      app: app
  template:
    metadata:
      labels:
        app: app
    spec:
      affinity:
        # Помните про идею разнести по поду на ноду? Вот реализация :)
        # Я думаю всё таки это правило должно быть рекомендательным, нежели обязательным, 
        # потому что не хочется чтобы какие-то поды висели в Pending.
        # Они всё равно будут размещены на доступных узлах, даже если одна или даже две ноды будут недоступны. 
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchExpressions:
                    - key: app
                      operator: In
                      values:
                        - app
                topologyKey: "kubernetes.io/hostname"
              # И добавлю ещё нестрогое правило размещения по зонам (ведь кластер у нас мультизональный).
              # Это повысит отказоустойчивость.
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchExpressions:
                    - key: app
                      operator: In
                      values:
                      - app
                topologyKey: "topology.kubernetes.io/zone"
      containers:
        - name: app
          # Думал поставить app:latest, но так лучше не делать, потому что это ведёт к непредвиденному поведению.
          image: app:1.0.0
          # Далее про ресурсы.
          # Сначала я поставил реквесты 0.1 CPU и 128M, а лимиты 1 CPU и 256M. Это было плохое решение.
          #
          # Обо всём по порядку, начнём с CPU:
          # В первом решении я фактически на авось тыкнул пальцем в небо, поставив лимит 1.
          # Затем, когда задумался об экономии ресурсов и использовании hpa, понял, что лимит мне
          # в этом не помогает, а наборот - мешает, ограничивая потребление. Мы не можем знать лимит. 
          # Подбирать его — неблагодарное дело,
          # особенно когда в задании "на первые запросы приложению требуется значительно больше ресурсов CPU", 
          # а значительно много — это сколько? Можно прогадать.
          # Подумав, я пришёл к выводу, что гораздо более правильным вариантом будет отказ от лимита вовсе 
          # и управлять CPU через реквест и HPA.
          # Появилась другая проблема - выставление реквеста: нужно такое значение реквеста, 
          # которое будет экономичным и обеспечит корректную работу HPA, то есть правильное количество подов при нужной нагрузке.
          # После попыток решить это математически, я перешёл к подбору и остановился на 0.2
          # 
          # Теперь о memory:
          # С реквестом понятно, он практически указан в задании - 128Mi, а лимит я снова взял с размахом - 256Mi,
          # написав, что это убережёт нас от нагрузки. Но это слишком много, от нагрузки не убережёт, а ресурсов потратит.
          # Я буквально позволил убивать под, расходующий аномально много, только когда он израсходует в два раза больше.
          # Реквест и лимит на пямять нужно держать как можно ближе к друг другу, в идеале - равными.
          # В задании "всегда “ровно” в районе 128M memory". Я бы всё-таки уточнил, а сколько это - "в районе".
          # Потому что, если в среднем потребление памяти не выходит за 128, тогда реквест и лимит можно поставить одинаково 128.
          # Если выходит, но не сильно, то подойдёт 128 и 150. Я поставлю ровно. 
          resources:
            requests:
              cpu: "0.2"
              memory: "128Mi"
            limits:
              memory: "128Mi"
          # Пригодится проверка на то, готово ли реально приложение принимать трафик.
          # Зная, что ему для старта требуется 5-10 секунд ставим первую пробу на 10, а остальные с интервалом в 5.
          readinessProbe:
            httpGet:
              path: /healthz
              port: 80
            initialDelaySeconds: 10
            periodSeconds: 5
          # В целом задержка на ливинесс пробу больше, чтобы сначал убрать трафик от пода, если он завалит рединес пробу,
          # а потом перезапустить его, если он завалит ещё и ливинесс пробу, и провести пробы заново.
          livenessProbe:
            httpGet:
              path: /healthz
              port: 80
            initialDelaySeconds: 20
            periodSeconds: 20
          ports:
            - containerPort: 80

# Не хватает усиленной экономии ресурсов.
# Ночью нам все поды не нужны, поэтому можно сократить их количесвтво до 3,
# а в моменты пиков нагрузки поднять до 5 при необходимости.
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: app-hpa
  namespace: app-namespace
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: app-deployment
  # Тут спорно, можно minReplicas поставить и поменьше, зависит от того насколько сильно падает нагрузка.
  # В задании "ночью запросов на порядки меньше", то есть в теории можно 2 или даже 1, это очень хорошо сэкономит ресурсы, 
  # но я поставлю 3 на всякий случай.
  minReplicas: 3
  maxReplicas: 5
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 80

# Используем Service ClusterIP с Ingress вместо простого Service LoadBalancer.
# Почему это лучше?
# - Service скрыт внутри кластера и доступ к нему не прямой, а опосредованный, что повышает безопасноcть
# - Ingress поддерживает много вариантов маршрутизации и балансировки
# - Ingress даёт возможность работы с множеством сервисов
---
apiVersion: v1
kind: Service
metadata:
  name: app-service
  namespace: app-namespace
  labels:
    app: app
spec:
  type: ClusterIP
  selector:
    app: app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  namespace: app-namespace
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: app.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: app-service
            port:
              number: 80

# Почему нет VPA и PDB?
# Они кажутся мне избыточными по нескольким причинам:
# - нагрузкой на cpu занимается hpa, значит на vpa остаётся память (использование их вместе на одном ресурсе ведёт к непредвиденому поведению) 
# - но по заданию память ровная
# - pdb ничем не поможет при незапланированных сбоях, а ограничение количества недоступных подов при плановых обновлениях указано явно

# Итоги:
#  1. отказоустойчивость обеспечена распределеним реплик на разные ноды и зоны (по возможности);
#  2. оптимальное потребление ресурсов днём/ночью и первые/последующие запросы реализовано с помощью реквестов и hpa;
#  3. сбои и преждевременная работа с трафиком минимизированы с помощью проверок;
#  4. доступ к приложению через ClusterIP + Ingress, это повысит безопасность, качество маршрутизации и балансировки.

# Спасибо за прочтение!

About

Test task for Mindbox SRE

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors