Уже пару лет Уильям Лам развивает проект под названием vCenter Event Broker Appliance (VEBA), позволяющий выполнять скрипт при наступлении какого-либо события у “Event Provider”. В качестве Event Provider может выступать как vCenter, так и другие системы. В текущей версии 0.7.1 VEBA умеет выступать даже в качестве Webhook-системы для других систем. В качестве языков поддерживаются Python, Go и PowerShell.
Развертывание апплайнса выполняется стандартно. Из обязательных настроек тут только указание адреса vCenter (если вам интересен он), логин и пароль на чтение (для считывания событий). Опционально можно указать учетную запись для регистрации веб-плагина управления к VEBA, но у меня это не взлетело 🙂
Имеется достаточно много примеров – лучше брать примеры на Knative, так как OpenFaas будет удален в релизе 0.8.0.
Я опишу написание своей функции для python. Тем не менее, вы можете воспользоваться также PowerShell или Golang.
Для того, чтобы сделать свою функцию, скопируйте файлы из каталога с примером и измените следующие:
- function.yaml (содержит названия событий, реакцией на которые будет вызов контейнера со скриптом handler.py. Список событий доступен тут. Если вы будете реагировать на несколько событий, необходимо создать в файле несколько “Triggers”, с разным названием и содержимым атрибута Subject. Пример можете посмотреть тут. Также вам необходимо будет опубликовать свой Docker-образ через Docker push и указать его название);
- handler.py (содержит логику вашей функции. При использовании данных из события обязательно посмотрите на тестовое событие для того, чтобы понять – как получить доступ к полям события. Обязательно почитайте рекомендации к разработке своих функций);
- secret.json (содержит всю конфиденциальную информацию, которую нежелательно сохранять в скрипте handler.py).
1 2 3 4 |
{ "NETBOX_URL": "https://netbox.corp.local", "NETBOX_TOKEN": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" } |
Сейчас я приведу пример функции с handler.py, которая при создании ВМ в vSphere делает следующее:
- из события берется имя ВМ и кластера;
- в Netbox по имени кластера ищется его ID;
- создается ВМ в Netbox на кластере.
Подразумевается, что в Netbox требуемый кластер уже создан, иначе скрипт необходимо доработать.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
from flask import Flask, request, jsonify import os import requests from cloudevents.http import from_http import logging import json logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s') app = Flask(__name__) # Change the value to match the secret key in the VEBA appliance where you enter the Slack webook url information # url = os.environ.get('SLACK_SECRET') @app.route('/', methods=['POST']) def slack(): try: try: config = json.loads(os.environ['SECRET']) except json.JSONDecodeError as err: raise Exception(f'Invalid JSON configuration: {err}') except KeyError as err: raise Exception(f'Missing environment variable `SECRET`') except Exception as err: raise Exception(f'Unknown error when reading configuration: {err}') try: netbox_url = config['NETBOX_URL'] netbox_token = config['NETBOX_TOKEN'] except KeyError as err: raise Exception(f'Missing mandatory configuration key: {err}') event = from_http(request.headers, request.get_data(), None) if event._attributes.get("datacontenttype").lower() != "application/json": sc = 400 msg = f'invalid datacontenttype for cloud event: {event._attributes.get("datacontenttype")}' app.logger.error(msg) message = { 'status': sc, 'error': msg, } resp = jsonify(message) resp.status_code = sc return resp # CloudEvent - simple validation vm_name = event.data['Vm']['Name'] cluster_name = event.data['ComputeResource']['Name'] url = netbox_url + 'api/virtualization/clusters/?name=' + cluster_name token = 'TOKEN ' + netbox_token json_response = requests.get( url, headers={ 'Accept': 'application/json', 'Authorization': token }, verify=False ).json() json_post = {'name': vm_name, 'cluster': json_response['results'][0]['id']} url = netbox_url + 'api/virtualization/virtual-machines/' requests.post( url, headers={ 'Accept': 'application/json', 'Content-Type': 'application/json', 'Authorization': token }, json=json_post, verify=False ) # app.logger.info(f'"***cloud event*** {json.dumps(e)}') return {}, 200 except KeyError as e: sc = 400 msg = f'could not decode cloud event: {e}' app.logger.error(msg) message = { 'status': sc, 'error': msg, } resp = jsonify(message) resp.status_code = sc return resp except Exception as e: sc = 500 msg = f'could not send message: {e}' app.logger.error(msg) message = { 'status': sc, 'error': msg, } resp = jsonify(message) resp.status_code = sc return resp # hint: run with FLASK_ENV=development FLASK_APP=handler.py flask run if __name__ == "__main__": app.run() |
После создания своего скрипта handler.py вам понадобится пересобрать контейнер с ним. Примерная инструкция тут, я же распишу ее для чайников 🙂
Контейнер можно разместить, например, на Docker. Для этого вы регистрируетесь на Docker.
Сборка
Затем авторизуетесь из VEBA, выполнив
1 |
docker login |
Устанавливаете туда утилиту pack
1 |
(curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.22.0/pack-v0.22.0-linux.tgz" | sudo tar -C /usr/local/bin/ --no-same-owner -xzv pack) |
Собираете локально контейнер (пример образа – vmind/super_script:1.0):
1 2 |
export IMAGE=<docker-username>/<script_folder_name>:version pack build --builder gcr.io/buildpacks/builder:v1 ${IMAGE} |
Тесты
Запускаете контейнер в SSH-сессии:
1 |
docker run -e PORT=8080 -it --rm -p 8080:8080 --env SECRET=<span class="pl-s"><span class="pl-pds">"</span><span class="pl-pds">$(</span>cat secret.json<span class="pl-pds">)</span><span class="pl-pds">"</span></span> <span class="pl-smi">${IMAGE}</span> |
Редактируете файл testevent.json с тестовым событием в подкаталоге ./test таким образом, чтобы он содержал событие, на которое вам нужна реакция.
Поднимаете вторую SSH-сессию до VEBA-апплайнса, переходите в каталог с функцией и выполняете отправку тестового события в контейнер:
1 |
curl -i -d@test/testevent.json localhost:8080 |
Если вы все сделали правильно, ваш контейнер выполнит скрипт handler.py.
После этого можно возвращаться в первую SSH- сессию с docker run и нажимать там Ctrl+C для прекращения работы контейнера.
Финиш
Публикуете свой контейнер в публичный репозиторий:
1 |
docker push ${IMAGE} |
Создаете “секрет” в kubernetes, который будет содержать адрес Netbox-сервера и токен доступа к нему.
Указываете в function.yaml название вашего образа в Docker (<docker-username>/<script_folder_name>:version. Вместо версии можете указать :latest для того, чтобы всегда брался самый свежий контейнер).
По желанию меняете в нем же минимальное количество Ready-подов и максимальное – которое будет выполнять Kunbernetes при реакции на событие.
1 2 3 |
annotations: autoscaling.knative.dev/maxScale: "1" autoscaling.knative.dev/minScale: "1" |
Теперь собираете приложение Kubernetes, в котором будет выполняться ваш контейнер, в котором лежит ваш скрипт.
Работать это будет в идеологии “Serverless”: контейнер будет запускаться при появлении вашего события, скрипт handler.py будет его обрабатывать, после чего контейнер будет убиваться!
1 2 3 4 5 6 7 8 9 |
export VEBA_NS=vmware-functions # Create secret kubectl -n ${VEBA_NS} create secret generic secret --from-file=SECRET=secret.json # update label for secret to show up in VEBA UI kubectl -n ${VEBA_NS} label secret secret app=veba-ui # Edit the `function.yaml` file with the name of the container image from *Step 1* if you made any changes. #deploy function kubectl apply -n ${VEBA_NS} -f function.yaml |
Вжух
Поздравляю, вы уже немного девопс. Докер, кубернетис, питон и все вот это вот 🙂