Использование PostStart хука при запуске пода в Kubernetes-кластере

Хуки дают возможность получать информацию о жизненном цикле управления контейнерами и выполнять код, реализованный в обработчике (handler), при срабатывании определенного хука.




Для каждого контейнера в поде хуки определяются отдельно. Существуют два типа хуков — PostStart и PreStop. Первый является асинхронным и выполняется сразу же при создании контейнера, однако нет никакой гарантии, что данный хук будет выполнен до запуска инструкции ENTRYPOINT контейнера. Стоит отметить, что если выполнение PostStart хука занимает очень много времени (или зависает), то контейнер не может перейти в состояние Running.




Хук PreStop, как видно из его названия, выполняется перед тем как контейнер будет остановлен (terminated) — будь то API-запрос или другое событие (например, неудачная liveness probe, “выдавливание” пода с узла кластера, перебор используемых ресурсов). Этот вызов синхронный, а это значит, что он обязательно должен быть завершен до того, как будет отправлен сигнал остановки контейнера.




Для хуков в жизненном цикле контейнеров предусмотрено два варианта обработчиков (handlers):




  • Exec — выполняет определенную команду (скрипт) в пространстве имен контейнера. Ресурсы, которые используются данной командой также учитываются в используемых ресурсах контейнера (важно при определении памяти и CPU);



  • HTTP — выполняет HTTP-запрос на определенный эндпоинт контейнера.




Если какой-то из хуков PostStart или PreStop завершается с ошибкой, то контейнер также будет остановлен. Логи хуков недоступны при выполнении команды kubectl logs <pod_name>, но если по какой-то причине они выполнились неудачно, то происходит событие FailedPostStartHook или FailedPreStopHook соответственно. Эти события можно увидеть выполнив команду kubectl describe pod <pod_name>.




Итак, мы вполне можем использовать PostStart хук для вставки данных в Redis при старте контейнера.




Идея состоит в следующем: с помощью ConfigMap мы добавим файл(ы) внутрь контейнера, причем названием ключа в редисе будет имя, а значением — содержимое этого файла. Далее, используя PostStart хук, мы “обработаем” каждый из файлов и вставим соответствующие данные в БД Redis.




Манифест, содержащий в себе все необходимые объекты Kubernetes, будет выглядеть так:




apiVersion: v1

kind: Service

metadata:

  name: ads-redis-test

  namespace: default

spec:

  selector:

    app: ads-redis-test

  ports:

  - name: redis

    port: 6379

  clusterIP: None

---

apiVersion: v1

kind: ConfigMap

metadata:

  name: ads-redis-test

  namespace: default

data:

  flow-rules-key: |

    [{

      "resource": "loopme.grpc.ssp.v0.AdsTxtRecordService/GetAdsTxtRelationships",

      "count": 100.0,

      "grade": "THREAD",

      "limit-app": "default"

    },{

      "resource": "loopme.grpc.ssp.v0.PublisherAccountService/GetPublisherById",

      "count": 5.0,

      "grade": "THREAD",

      "limit-app": "default"

    },{

      "resource": "loopme.grpc.ssp.v1.PublisherAccountService/GetPublisherById",

      "count": 5.0,

      "grade": "THREAD",

      "limit-app": "default"

    },{

      "resource": "loopme.grpc.ssp.v0.BundleLegacyService/GetBundleByKey",

      "count": 20.0,

      "grade": "THREAD",

      "limit-app": "default"

    },{

      "resource": "loopme.lsm.ssp.v0.BundleService/GetBundleById",

      "count": 20.0,

      "grade": "THREAD",

      "limit-app": "default"

    },{

      "resource": "loopme.lsm.ssp.v0.BundleService/QueryBundle",

      "count": 20.0,

      "grade": "THREAD",

      "limit-app": "default"

    },{

      "resource": "loopme.grpc.ssp.v0.AppLegacyService/GetAppById",

      "count": 10.0,

      "grade": "THREAD",

      "limit-app": "default"

    },{

      "resource": "loopme.grpc.ssp.v0.AppLegacyService/GetAppIdByKey",

      "count": 10.0,

      "grade": "THREAD",

      "limit-app": "default"

    },{

      "resource": "loopme.grpc.ssp.v0.AppLegacyService/GetAppIdByContainerKey",

      "count": 16.0,

      "grade": "THREAD",

      "limit-app": "default"

    },{

      "resource": "loopme.grpc.ssp.v0.AppLegacyService/GetAppByContainerKey",

      "count": 10.0,

      "grade": "THREAD",

      "limit-app": "default"

    },{

      "resource": "ExchangeThrottleRateService/GetThrottleRatesByKeys",

      "count": 20.0,

      "grade": "THREAD",

      "limit-app": "default"

    },{

      "resource": "dsp-fetcher",

      "count": 25.0,

      "grade": "THREAD",

      "limit-app": "default"

    },{

      "resource": "exchange-fetcher",

      "count": 300.0,

      "grade": "THREAD",

      "limit-app": "default"

    },{

      "resource": "kafka_dmp_ads_requests_info",

      "count": 500.0,

      "grade": "QPS",

      "limit-app": "default"

    }]    

  degrade-rules-key: |

    [{

      "resource": "analytics.AnalyticsApiService/AnalyzeCall",

      "count": 10.0,

      "grade": "EXCEPTION_COUNT",

      "time-window": 10,

      "min-request-amount": 100,

      "stat-interval-ms": 20000,

      "slow-ratio-threshold": 0.6

    },{

      "resource": "analytics.AnalyticsApiService/AnalyzeCall",

      "count": 10.0,

      "grade": "EXCEPTION_RATIO",

      "time-window": 10,

      "min-request-amount": 100,

      "stat-interval-ms": 20000,

      "slow-ratio-threshold": 0.6

    }]    

---

apiVersion: apps/v1

kind: Deployment

metadata:

  name: ads-redis-test

  namespace: default

spec:

  replicas: 1

  selector:

    matchLabels:

      app: ads-redis-test

  template:

    metadata:

      labels:

        app: ads-redis-test

    spec:

      containers:

      - name: redis

        image: redis:6.0.9

        ports:

        - name: redis

          containerPort: 6379

        resources:

          limits:

            cpu: "0.5"

            memory: 1Gi

          requests:

            cpu: "0.5"

            memory: 1Gi

        lifecycle:

          postStart:

            exec:

              command: ["/bin/bash", "-c", "cd /script/ && for FILE in *key; do cat ${FILE} | redis-cli -n 2 -x set ${FILE}; done"]

        livenessProbe:

          exec:

            command:

            - sh

            - -c

            - redis-cli -h $(hostname) ping

          initialDelaySeconds: 5

          periodSeconds: 3

        readinessProbe:

          exec:

            command:

            - sh

            - -c

            - redis-cli -h $(hostname) ping

          initialDelaySeconds: 5

          timeoutSeconds: 3

        volumeMounts:

        - mountPath: /script

          name: script

      volumes:

      - name: script

        configMap:

          name: ads-redis-test




Вся “магия” заключается в команде, которая определена в postStart хуке:




command: ["/bin/bash", "-c", "cd /script/ && for FILE in *key; do cat ${FILE} | redis-cli -n 2 -x set ${FILE}; done"]




Здесь для каждого файла в каталоге /script, который заканчивается на key выполняется следующее:




  • с помощью команды cat выводится содержимое файла в STDOUT;



  • через конвейер | передаются следующей команде — консольной утилите redis-cli (здесь в STDIN попадает содержимое STDOUT из предыдущего шага);



  • redis-cli выполняет вставку данных во вторую БД (ключ -n 2) с помощью команды SET.




Примечание Именем ключа будет значение переменной ${FILE} (имя файла), а значением — данные из STDIN (об этом заботится ключ -x).




Источник: https://sidmid.ru/использование-poststart-хука-при-запуске-пода/



2023-01-02T06:04:30
DevOps