K8s Q A Shell Operator

Материал из noname.com.ua
Перейти к навигацииПерейти к поиску

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