Использование PostStart хука при запуске пода в Kubernetes-кластере
Dec 8, 2020 08:37 · 776 words · 4 minute read
После написания статьи о вставке данных в redis при запуске контейнера в кластере Kubernetes Александр Косенко вполне резонно заметил, что для решения такой задачи можно использовать PostStart
хук, который предоставляется “из коробки” для управления жизненным циклом контейнера. Давайте разберемся!
Хуки дают возможность получать информацию о жизненном цикле управления контейнерами и выполнять код, реализованный в обработчике (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
).
На этом все, мы успешно вставили данные в Redis
при запуске контейнера используя “родной” функционал Kubernetes
, а именно PostStart
хук управления жизненным циклом контейнера.