K8s Q A Shell Operator: различия между версиями
Sirmax (обсуждение | вклад) |
Sirmax (обсуждение | вклад) |
||
(не показано 13 промежуточных версий этого же участника) | |||
Строка 1: | Строка 1: | ||
[[Категория:K8s]] |
[[Категория:K8s]] |
||
[[Категория:K8s_Вопросы_И_Ответы]] |
[[Категория:K8s_Вопросы_И_Ответы]] |
||
− | [[Категория: |
+ | [[Категория:K8s_Operator]] |
+ | [[Категория:Shell Operator]] |
||
− | |||
=Shell Operator= |
=Shell Operator= |
||
Строка 19: | Строка 19: | ||
=Как это сделать= |
=Как это сделать= |
||
+ | Идея такая - на основании shell-operator написать свой скрипт, который подписывается на события в кластере, и при появлении нужного POD сохранит логи в доступном месте. |
||
+ | <BR> |
||
+ | В целом довольно костыльно - но задачу решает |
||
+ | ==Shell-Operator== |
||
+ | Shell-оператор это фреймворк для написания своих операторов на shell или python |
||
+ | * https://habr.com/ru/companies/flant/articles/447442/ |
||
+ | * https://github.com/flant/shell-operator |
||
+ | ==Hook== |
||
+ | Для обработки событий использую вот такой скрипт, при запуске с параметром <code>--config</code>он должен выдать описания событий на которые нужно подписаться, |
||
+ | при запуске без параметров ему передается (чеерз переменную окружения <code>${BINDING_CONTEXT_PATH}</code> путь к файлу который содержит данные события |
||
+ | <PRE> |
||
+ | #!/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 |
||
+ | </PRE> |
||
+ | ==Сборка образа== |
||
+ | ===Dockerfile=== |
||
+ | Так-как это только оператор для единоразовой задачи - все максимально просто, помещаем скрипт в директорию <code>hooks</code> и все |
||
+ | <PRE> |
||
+ | FROM ghcr.io/flant/shell-operator:latest |
||
+ | ADD hooks /hooks |
||
+ | </PRE> |
||
+ | ===Build=== |
||
+ | Удалить старый, пересобрать заново, пушнуть на докерхаб |
||
+ | <PRE> |
||
+ | !/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 |
||
+ | </PRE> |
||
+ | |||
+ | =<code>RBAC</code>= |
||
+ | Для того что бы оператор мог получить доступ к событиям - нужно создать сервисный аккаунт и назначить на него соответствующие разрешения <BR> |
||
+ | (можно указать несколько объектов в одном файле) |
||
+ | ==<code>ServiceAccount</code>== |
||
+ | <PRE> |
||
+ | --- |
||
+ | apiVersion: v1 |
||
+ | kind: ServiceAccount |
||
+ | metadata: |
||
+ | name: monitor-events-acc |
||
+ | </PRE> |
||
+ | ==<code>ClusterRole</code>== |
||
+ | <PRE> |
||
+ | --- |
||
+ | 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"] |
||
+ | </PRE> |
||
+ | |||
+ | ==<code>ClusterRoleBinding</code>== |
||
+ | <PRE> |
||
+ | --- |
||
+ | 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 |
||
+ | </PRE> |
||
+ | =Run Pod= |
||
+ | По-хорошему, на случай подения пода стоило бы завести его под какой-то контроллер, но учитывая что в данном случае это единоразовая задача, то я просто забил болт на надежность и запустил 1 POD |
||
+ | <PRE> |
||
+ | --- |
||
+ | 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 |
||
+ | </PRE> |
Текущая версия на 20:02, 11 января 2024
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