K8s Q A Shell Operator
Shell Operator
https://habr.com/ru/companies/flant/articles/447442/
Что нужно сделать
Есть cron-job которая
- Падает время от времени (но не всегда, а скорее редко)
- Подчищает за собой POD
- Хочется найти упавший под и собрать с него логи ДО того как под будет удален
- Не хочется сидеть и ждать такой под
- Доступа к централизованной системе логгирования нет (так бывает - логи пишутся централизованно в корпоративную систему)
Как это сделать
Идея такая - на основании shell-operator написать свой скрипт, который подписывается на события в кластере, и при появлении нужного POD сохранит логи в доступном месте.
В целом довольно костыльно - но задачу решает
Shell-Operator
Shell-оператор это фреймворк для написания своих операторов на shell или python
Hook
Для обработки событий использую вот такой скрипт, при запуске с параметром --config
он должен выдать описания событий на которые нужно подписаться,
при запуске без параметров ему передается (чеерз переменную окружения ${BINDING_CONTEXT_PATH}
путь к файлу который содержит данные события
#!/usr/bin/env bash NAMESPACE="stacklight" POD_NAME="elasticsearch-curator" # Эта часть кода запускается при старте оператора, и # определяет на какие именно события будет подписываться этот скрипт # В частности - в каком именно неймспейсе if [[ $1 == "--config" ]] ; then cat <<EOF { "configVersion":"v1", "kubernetes":[ { "apiVersion": "events.k8s.io/v1", "kind": "Event", "namespace": { "nameSelector": { "matchNames": ["${NAMESPACE}"] } }, "fieldSelector": { "matchExpressions": [ { "field": "metadata.namespace", "operator": "Equals", "value": "${NAMESPACE}" } ] } } ] } EOF else # Сохранить содержимое BINDING_CONTEXT (для последующего анализа) cat "${BINDING_CONTEXT_PATH}" >> /tmp/log2 echo "" echo "[events-hook.sh] Starting HOOK" echo "[events-hook.sh] ------------------------------------------" # Вывести в лог (лог пода) cat "${BINDING_CONTEXT_PATH}" echo "[events-hook.sh] " type=$(jq -r '.[0].type' ${BINDING_CONTEXT_PATH}) echo "[events-hook.sh] ------------------------------------------" echo "[events-hook.sh] TYPE=${type}" echo "------------------------------------------" if [[ $type == "Event" ]] ; then echo "[events-hook.sh] Got Event: " echo "[events-hook.sh] ------------------------------------------" jq '.[0].object' ${BINDING_CONTEXT_PATH} echo "[events-hook.sh] ------------------------------------------" podName=$(jq -r '.[0].object.metadata.name' $BINDING_CONTEXT_PATH) echo "[events-hook.sh] Pod Name: '${podName}'" echo "[events-hook.sh] ------------------------------------------" else echo "DATA: ${BINDING_CONTEXT_PATH}" >> /tmp/log fi #jq -r '.[0].object.note' ${BINDING_CONTEXT_PATH} >> /tmp/note.log note=$(jq -r '.[0].object.note' ${BINDING_CONTEXT_PATH}) echo "[events-hook.sh] ------------------------------------------" echo "[events-hook.sh] Got note: ${note}" echo "[events-hook.sh] ------------------------------------------" # Возможно на первой иттерации под не успеет стартовать, но # если он упадет, то его логи будут получены при следующем запуске # Cron Job if [[ "${note}" == "Started container ${POD_NAME}" ]]; then D=$(date +%Y%m%d-%H%M%S) kubectl get pod | grep "${POD_NAME}"| grep -vE "Completed|Running|Pending|ContainerCreating" >> /tmp/list_pods_${D} for failedPod in $(kubectl get pod | grep "${POD_NAME}" | grep -vE "Completed|Running|Pending|ContainerCreating" | awk '{ print $1 }' ); do # Найти под и вывести в логи его описание, дескрайб и логи echo "[events-hook.sh] Found failed POD: ${failedPod}" describeFailedPod=$(kubectl describe pod ${failedPod}) echo "[events-hook.sh] POD Describe: ${describeFailedPod}" echo "${describeFailedPod}" > /tmp/${failedPod}_describe_${D} getFailedPodYaml=$(kubectl get pod ${failedPod} -o yaml) echo "[events-hook.sh] POD Definition: ${getFailedPodYaml}" echo "${getFailedPodYaml}" > /tmp/${failedPod}_yaml_${D} failedPodLogs=$(kubectl \ logs -f ${failedPod} \ --all-containers 2>&1) echo "${failedPodLogs}" > /tmp/${failedPod}_logs_${D} echo "[events-hook.sh] POD Logs: ${failedPodLogs}" done fi fi
Сборка образа
Dockerfile
Так-как это только оператор для единоразовой задачи - все максимально просто, помещаем скрипт в директорию hooks
и все
FROM ghcr.io/flant/shell-operator:latest ADD hooks /hooks
Build
Удалить старый, пересобрать заново, пушнуть на докерхаб
!/bin/bash set -ex for i in $(docker image ls | grep sirmax123/shell-operator-test | awk '{ print $3}'); do docker image rm ${i} done docker build . -t sirmax123/shell-operator-test:1 docker push sirmax123/shell-operator-test:1
RBAC
Для того что бы оператор мог получить доступ к событиям - нужно создать сервисный аккаунт и назначить на него соответствующие разрешения
(можно указать несколько объектов в одном файле)
ServiceAccount
--- apiVersion: v1 kind: ServiceAccount metadata: name: monitor-events-acc
ClusterRole
--- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: monitor-events rules: - apiGroups: ["events.k8s.io"] resources: ["events"] verbs: ["get", "watch", "list"] - apiGroups: [""] resources: ["pods", "pods/log"] verbs: ["get", "watch", "list"]
ClusterRoleBinding
--- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: monitor-events roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: monitor-events subjects: - kind: ServiceAccount name: monitor-events-acc namespace: stacklight
Run Pod
По-хорошему, на случай подения пода стоило бы завести его под какой-то контроллер, но учитывая что в данном случае это единоразовая задача, то я просто забил болт на надежность и запустил 1 POD
--- apiVersion: v1 kind: Pod metadata: name: shell-operator spec: containers: - name: shell-operator image: sirmax123/shell-operator-test:1 imagePullPolicy: Always serviceAccountName: monitor-events-acc