Kubernetes: жизнь пода

в 6:13, , рубрики: devops, kubernetes, Блог компании Флант, системное администрирование

Прим. перев.: эта небольшая (но ёмкая!) статья, написанная Michael Hausenblas из команды OpenShift в Red Hat, настолько пришлась нам «по душе», что практически сразу же после её обнаружения была добавлена в нашу внутреннюю базу знаний по Kubernetes. А поскольку представленная в ней информация явно будет полезной и для более широкого русскоязычного ИТ-сообщества, с удовольствием выкладываем её перевод.

Kubernetes: жизнь пода - 1

Как вы могли догадаться, заголовок этой публикации — отсылка к мультфильму Pixar 1998-го года «A Bug’s Life» (в российском прокате он назывался «Приключения Флика» или «Жизнь насекомого» — прим. перев.), и действительно: между муравьём-рабочим и подом в Kubernetes есть много схожего. Мы внимательно посмотрим на полный жизненный цикл пода с практической точки зрения — в частности, на способы, которыми вы можете повлиять на поведение при старте и завершении работы, а также на правильные подходы к проверке состояния приложения.

Вне зависимости от того, создали вы под сами или же, что лучше, через контролер вроде Deployment, DaemonSet или StatefulSet, под может находиться в одной из следующих фаз:

  • Pending (ожидание): API Server создал ресурс пода и сохранил его в etcd, но под ещё не был запланирован, а образы его контейнеров — не получены из реестра;
  • Running (функционирует): под был назначен узлу и все контейнеры созданы kubelet'ом;
  • Succeeded (успешно завершён): функционирование всех контейнеров пода успешно завершено и они не будут перезапускаться;
  • Failed (завершено с ошибкой): функционирование всех контейнеров пода прекращено и как минимум один из контейнеров завершился со сбоем;
  • Unknown (неизвестно): API Server не смог опросить статус пода, обычно из-за ошибки во взаимодействии с kubelet.

Выполняя kubectl get pod, обратите внимание, что столбец STATUS может показывать и другие (кроме этих пяти) сообщения — например, Init:0/1 или CrashLoopBackOff. Так происходит по той причине, что фаза — это лишь часть общего состояния пода. Хороший способ узнать, что же конкретно произошло, — запустить kubectl describe pod/$PODNAME и посмотреть на запись Events: внизу. Она выводит список актуальных действий: что образ контейнера был получен, под был запланирован, контейнер находится в «проблемном» (unhealthy) состоянии.

Теперь взглянем на конкретный пример жизненного цикла пода от начала до конца, продемонстрированный на следующей схеме:

Kubernetes: жизнь пода - 2

Что здесь произошло? Этапы следующие:

  1. На схеме это не показано, но в самом начале запускается специальный infra-контейнер и настраивает пространства имён, к которым присоединяются остальные контейнеры.
  2. Первый определённый пользователем контейнер, который запускается, — это init-контейнер; его можно использовать для задач инициализации.
  3. Далее одновременно запускаются главный контейнер и хук post-start; в нашем случае это происходит через 4 секунды. Хуки определяются для каждого контейнера.
  4. Затем, на 7-й секунде, вступают в дело liveness- и readiness-пробы, опять же для каждого контейнера.
  5. На 11-й секунде, когда под убит, срабатывает хук pre-stop и главный контейнер убивается после непринудительного (grace) периода. Обратите внимание, что в реальности процесс завершения работы пода несколько сложнее.

Как я пришёл к указанной выше последовательности и её таймингу? Для этого использовался следующий Deployment, созданный специально для отслеживания порядка происходящих событий (сам по себе он не очень полезен):

kind:                   Deployment
apiVersion:             apps/v1beta1
metadata:
  name:                 loap
spec:
  replicas:             1
  template:
    metadata:
      labels:
        app:            loap
    spec:
      initContainers:
      - name:           init
        image:          busybox
        command:       ['sh', '-c', 'echo $(date +%s): INIT >> /loap/timing']
        volumeMounts:
        - mountPath:    /loap
          name:         timing
      containers:
      - name:           main
        image:          busybox
        command:       ['sh', '-c', 'echo $(date +%s): START >> /loap/timing;
sleep 10; echo $(date +%s): END >> /loap/timing;']
        volumeMounts:
        - mountPath:    /loap
          name:         timing
        livenessProbe:
          exec:
            command:   ['sh', '-c', 'echo $(date +%s): LIVENESS >> /loap/timing']
        readinessProbe:
          exec:
            command:   ['sh', '-c', 'echo $(date +%s): READINESS >> /loap/timing']
        lifecycle:
          postStart:
            exec:
              command:   ['sh', '-c', 'echo $(date +%s): POST-START >> /loap/timing']
          preStop:
            exec:
              command:  ['sh', '-c', 'echo $(date +%s): PRE-HOOK >> /loap/timing']
      volumes:
      - name:           timing
        hostPath:
          path:         /tmp/loap

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

$ kubectl scale deployment loap --replicas=0

Мы посмотрели на конкретную последовательность событий в действии и готовы теперь двигаться дальше — к практикам в области управления жизненным циклом пода. Они таковы:

  • Используйте init-контейнеры для подготовки пода к нормальному функционированию. Например, для получения внешних данных, создания таблиц в базе данных или ожидания доступности сервиса, от которого под зависит. При необходимости можно создавать множество init-контейнеров, и все они должны успешно завершиться до того, как будут запущены обычные контейнеры.
  • Всегда добавляйте livenessProbe и readinessProbe. Первая используется kubelet'ом, чтобы понять, нужно ли и когда нужно перезапускать контейнер, и deployment'ом, чтобы решить, был ли успешным rolling update. Вторая — используется service'ом для принятия решения о направлении трафика на под. Если эти пробы не определены, kubelet для обоих предполагает, что они успешно выполнились. Это приводит к двум последствиям: а) политика рестарта не может быть применена, б) контейнеры в поде мгновенно получают трафик от стоящего перед ними service'а, даже если они всё ещё заняты процессом запуска.
  • Используйте хуки для правильной инициализации контейнера и полного его уничтожения. Например, это полезно в случае функционирования приложения, к исходному коду которого у вас нет доступа или нет возможности его модификации, но которое требует некой инициализации или подготовки к завершению работы — например, очистки подключений к базе данных. Заметьте, что при использовании service'а завершение работы API Server, контроллера endpoint'ов и kube-proxy может занять некоторое время (например, удаление соответствующих записей из iptables). Таким образом, завершающий свою работу под может повлиять на запросы к приложению. Зачастую для решения этой проблемы достаточно простейшего хука с вызовом sleep.
  • Для нужд отладки и для понимания в целом, почему под прекратил работу, приложение может писать в /dev/termination-log, а вы — просматривать сообщения с помощью kubectl describe pod …. Эти настройки по умолчанию меняются через terminationMessagePath и/или с помощью terminationMessagePolicy в спецификации пода — подробнее см. в API reference.

В этой публикации не рассматриваются initializers (некоторые подробности о них можно найти в конце этого материалаприм. перев.). Это полностью новая концепция, представленная в Kubernetes 1.7. Инициализаторы работают внутри control plane (API Server) вместо того, чтобы находиться в контексте kubelet, и могут использоваться для «обогащения» подов, например, sidecar-контейнерами или приведением в исполнение политик безопасности. Кроме того, не были рассмотрены PodPresets, которые в дальнейшем могут быть замещены более гибкой концепцией инициализаторов.

P.S. от переводчика

Читайте также в нашем блоге:

Автор: distol

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js