Есть кластер AWS Elastic Kubernetes Service, у которого есть несколько WorkerNode Group, которые созданы как AWS AutoScaling Groups, всё создаётся через eksctl
, см. AWS Elastic Kubernetes Service: — автоматизация создания кластера, часть 2 — Ansible, eksctl.
Для WorkerNode Group в конфигурации eksctl
прописан набор тегов, которые позволяют проводить инвентаризацию сервисов:
--- apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig metadata: name: "{{ eks_cluster_name }}" region: "{{ region }}" version: "{{ k8s_version }}" nodeGroups: ### Common ### - name: "{{ k8s_common_nodegroup_name }}-{{ item }}-v2021-09" instanceType: "{{ k8s_common_nodegroup_instance_type }}" privateNetworking: true labels: role: common-workers ... tags: Tier: "Devops" Domain: "eks.devops.{{ region }}.{{ env | lower }}.bttrm.local" ServiceType: "EC2" Env: {{ env }} Function: "Kubernetes WorkerNode" NetworkType: "Private" DataClass: "Public" AssetOwner: "{{ asset_owner }}" AssetCustodian: "{{ asset_custodian }}" OperatingSystem: "Amazon Linux" JiraTicket: "{{ jira_ticket }}" ConfidentialityRequirement: "Med" IntegrityRequirement: "Med" AvailabilityRequirement: "Med" ...
Теги задаются в AutoScale Group:
И потом копируются к создаваемым инстансам ЕС2.
Проблема в том, что диски, подключенные к этим ЕС2, теги не копируют. Кроме того, помимо Kubernetes WorkerNodes, в AWS-аккаунте существуют и другие ЕС2, и у некоторых только один, корневой диск, а у некоторых подключены дополнительные EBS для данных или бекапов.
Плюс надо копировать не только существующие теги с ЕС2, к которому подключен диск(и), но к диску(ам) требуется добавить один новый тег, описывающий роль этого диска – Root Volume, Data Volume или Kubernetes PVC Volume.
Как можем это организовать? Как и всё в AWS, что не реализовано в самой AWS Console – с помощью AWS Lambda: создадим функцию, которая будет триггерится при создании новых ЕС2, и будет копировать теги с ЕС2 на все подключенные к нему Elastic Block Store volumes.
Итак, что нам надо в логике работы этой Lambda-функции:
- при создании любого ЕС2 в аккаунте – триггерим Lambda-функцию
- ей передаём ID EC2, используя который находим ID всех его EBS
- копируем теги EC2 на все EBS
- задаём новый тег Role:
- если EBS создан из Kubernetes PVC и смонтирован к ЕС2, созданной из Kubernetes WorkerNode AWS AutoScale Group – то задаём ему тег
Role: "PvcVolume"
- если EBS создан во время запуска обычного EC2 – то определяем точку монтирования, и по нему решаем какой тег задать –
Role: "RootVolume"
, илиRole: "DataVolume"
- если EBS создан из Kubernetes PVC и смонтирован к ЕС2, созданной из Kubernetes WorkerNode AWS AutoScale Group – то задаём ему тег
Поехали.
Содержание
Python скрипт – копирование тегов
Сначала напишем сам скрипт, протестируем его, а потом перейдём к Lambda. Скрипт будет перебирать все ЕС2 в регионе, и для всех EBS задавать теги, а потом, когда дойдём до Lambda, обновим его, и добавим передачу EC2 ID в функцию.
boto3
: получение списка ЕС2 и их EBS
Сначала набросаем получение списка дисков. Для этого нам надо будет авторизоваться в AWS, получить все ЕС2 в регионе, а потом для каждого ЕС2 получить список подключенных к нему EBS.
Потом, имея эту информацию, уже сможем поиграться с тегами.
Пишем скрипт:
#!/usr/bin/env python import os import boto3 ec2 = boto3.resource('ec2', region_name=os.getenv("AWS_DEFAULT_REGION"), aws_access_key_id=os.getenv("AWS_ACCESS_KEY_ID"), aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY") ) def lambda_handler(event, context): base = ec2.instances.all() for instance in base: print("\n[DEBUG] EC2\n\t\tID: " + str(instance)) print("\tEBS") for vol in instance.volumes.all(): vol_id = str(vol) print("\t\tID: " + vol_id) if __name__ == "__main__": lambda_handler(0, 0)
Тут в переменной ec2
создаём объект boto3.resource
с типом ec2, и аутентифицируемся в AWS используя переменные $AWS_ACCESS_KEY_ID
и $AWS_SECRET_ACCESS_KEY
. В Lambda аутентификация будет выполняться через IAM Role, а тут пока тестируем используем ключи. См. Python: boto3 — примеры аутентификации и авторизации (пост старенький, но вроде ещё актуален).
В конце вызываем lambda_handler()
, если скрипт был вызван как отдельный модуль, см. Python: зачем нужен if __name__ == «__main__»?, а в lambda_handler()
через вызов метода ec2.instances.all()
получаем все инстансы в регионе, а потом в цикле for
для каждого ЕС2 через вызов instance.volumes.all()
получаем список всех его EBS.
Аргументы в lambda_handler()
пока передаём в виде 0 и 0, потом в Lambda будем передавать сюда event
и context
.
Задаём переменные:
[simterm]
$ export AWS_ACCESS_KEY_ID=AKI***D4Q $ export AWS_SECRET_ACCESS_KEY=QUC***BTI $ export AWS_DEFAULT_REGION=eu-west-3
[/simterm]
Запускаем скрипт:
[simterm]
$ ./ec2_tags.py [DEBUG] EC2 ID: ec2.Instance(id='i-0df2fe9ec4b5e1855') EBS ID: ec2.Volume(id='vol-0d11fd27f3702a0fc') [DEBUG] EC2 ID: ec2.Instance(id='i-023529a843d02f680') EBS ID: ec2.Volume(id='vol-0f3548ae321cd040c') [DEBUG] EC2 ID: ec2.Instance(id='i-02ab1438a79a3e475') EBS ID: ec2.Volume(id='vol-09b6f60396e56c363') ID: ec2.Volume(id='vol-0d75c44a594e312a1') ...
[/simterm]
Отлично – всё работает. Получили все ЕС2 в регионе eu-west-3, и для каждого из ЕС2 получили список всех дисков, которые к нему подключены.
Что дальше? Дальше надо определить – что за диски мы нашли, и в соответствии с их точкой монтирования можно будет определить, что за диск – корневой, или какой-то дополнительный.
Используем атрибут attachments()
, из которого получим значение Device
.
Задаём новую локальную переменную device_id
:
... for vol in instance.volumes.all(): vol_id = str(vol) device_id = "ec2.vol.Device('" + str(vol.attachments[0]['Device']) + "')" print("\t\tID: " + vol_id + "\n\t\tDev: " + device_id + "\n") ...
Запускаем:
[simterm]
$ ./ec2_tags.py [DEBUG] EC2 ID: ec2.Instance(id='i-0df2fe9ec4b5e1855') EBS ID: ec2.Volume(id='vol-0d11fd27f3702a0fc') Dev: ec2.vol.Device('/dev/xvda') [DEBUG] EC2 ID: ec2.Instance(id='i-023529a843d02f680') EBS ID: ec2.Volume(id='vol-0f3548ae321cd040c') Dev: ec2.vol.Device('/dev/xvda') [DEBUG] EC2 ID: ec2.Instance(id='i-02ab1438a79a3e475') EBS ID: ec2.Volume(id='vol-09b6f60396e56c363') Dev: ec2.vol.Device('/dev/xvda') ID: ec2.Volume(id='vol-0d75c44a594e312a1') Dev: ec2.vol.Device('/dev/xvdbm') ...
[/simterm]
И вот мы нашли ЕС2 i-02ab1438a79a3e475, у которого подключены два диска – vol-09b6f60396e56c363 как /dev/xvda
, и vol-0d75c44a594e312a1 как /dev/xvdbm
.
/dev/xvda
очевидно, что корневой диск, а /dev/xvdbm
– какой-то дополнительный.
boto3
: добавление тегов к EBS
Далее, создадим тег Role
, в который будем вносить одно из трёх допустимых значений:
- проверяем наличие тега
kubernetes.io/created-for/pvc/name
, и если он найден, то задаёмRole: "PvcVolume"
- проверяем точку монтирования, если
device
== “/dev/xvda” – то задаёмRole: "RootVolume"
- если у
device
любое другое значение – то задаёмRole: "DataDisk"
Добавим функцию, которая будет заниматься перебором и созданием списка тегов, и отдельно в функцию is_pvc()
вынесем проверку является ли раздел PVC – проверим наличие тега kubernetes.io/created-for/pvc/name
:
... def is_pvc(vol): try: for tag in vol.tags: if tag['Key'] == 'kubernetes.io/created-for/pvc/name': return True break except TypeError: return False def set_role_tag(vol): device = vol.attachments[0]['Device'] tags_list = [] values = {} if is_pvc(vol): values['Key'] = "Role" values['Value'] = "PvcDisk" tags_list.append(values) elif device == "/dev/xvda": values['Key'] = "Role" values['Value'] = "RootDisk" tags_list.append(values) else: values['Key'] = "Role" values['Value'] = "DataDisk" tags_list.append(values) return tags_list ...
Тут в функции set_role_tag()
сначала задаём переменные, затем передаём значение параметра vol
аргументом в функцию is_pvc()
, в которой через try/except
проверяем наличие тега ‘kubernetes.io/created-for/pvc/name’. try/except
тут нужен для проверки есть ли теги у EBS вообще.
Если тег ‘kubernetes.io/created-for/pvc/name’ есть – то is_pvc()
вернёт True
, если тег не надён – то False
.
Далее, проверяем через if/elif/else
условия – является ли диск PVC, если да – то задаём Role: "PvcVolume"
, если нет – то смонтирован ли как “/dev/xvda“, если да – то задаём Role: "RootVolume"
, и если нет – то задаём Role: "DataDisk".
Добавляем вызов set_role_tag()
в lambda_handler()
– передаём её аргументом в vol.create_tags()
:
... def lambda_handler(event, context): base = ec2.instances.all() for instance in base: print("\n[DEBUG] EC2\n\t\tID: " + str(instance)) print("\tEBS") for vol in instance.volumes.all(): vol_id = str(vol) device_id = "ec2.vol.Device('" + str(vol.attachments[0]['Device']) + "')" print("\t\tID: " + vol_id + "\n\t\tDev: " + device_id) role_tag = vol.create_tags(Tags=set_role_tag(vol)) print("\t\tTags set:\n\t\t\t" + str(role_tag)) ...
Запускаем:
[simterm]
$ ./ec2_tags.py [DEBUG] EC2 ID: ec2.Instance(id='i-0df2fe9ec4b5e1855') EBS ID: ec2.Volume(id='vol-0d11fd27f3702a0fc') Dev: ec2.vol.Device('/dev/xvda') Tags set: [ec2.Tag(resource_id='vol-0d11fd27f3702a0fc', key='Role', value='RootDisk')] ... [DEBUG] EC2 ID: ec2.Instance(id='i-02ab1438a79a3e475') EBS ID: ec2.Volume(id='vol-09b6f60396e56c363') Dev: ec2.vol.Device('/dev/xvda') Tags set: [ec2.Tag(resource_id='vol-09b6f60396e56c363', key='Role', value='RootDisk')] ID: ec2.Volume(id='vol-0d75c44a594e312a1') Dev: ec2.vol.Device('/dev/xvdbm') Tags set: [ec2.Tag(resource_id='vol-0d75c44a594e312a1', key='Role', value='PvcDisk')]
[/simterm]
Проверим диски инстанса i-02ab1438a79a3e47.
Корневой vol-09b6f60396e56c363:
И Kubernetes PVC vol-0d75c44a594e312a1:
Окей – добавление тега Role
мы вроде сделали, теперь надо добавить копирование тегов с “родительского” ЕС2.
boto3
: копирование тегов ЕС2 к его EBS
Копирование тегов тоже вынесем отдельной функций, назовём её copy_ec2_tags()
, и в функцию аргументом будем передавать EC2 ID:
... def copy_ec2_tags(instance): tags_list = [] values = {} for instance_tag in instance.tags: if instance_tag['Key'] == 'Env': tags_list.append(instance_tag) elif instance_tag['Key'] == 'Tier': tags_list.append(instance_tag) elif instance_tag['Key'] == 'DataClass': tags_list.append(instance_tag) return tags_list ...
В фунцкции в цикле проверяем все теги инстанса, если находим один из трёх нужных тегов – то копируем его в список tags_list[]
, который потом вернём в vol.create_tags()
.
Добавляем её вызов в lambda_handler()
:
... def lambda_handler(event, context): base = ec2.instances.all() for instance in base: print("\n[DEBUG] EC2\n\t\tID: " + str(instance)) print("\tEBS") for vol in instance.volumes.all(): vol_id = str(vol) device_id = "ec2.vol.Device('" + str(vol.attachments[0]['Device']) + "')" print("\t\tID: " + vol_id + "\n\t\tDev: " + device_id) role_tag = vol.create_tags(Tags=set_role_tag(vol)) ec2_tags = vol.create_tags(Tags=copy_ec2_tags(instance)) print("\t\tTags set:\n\t\t\t" + str(role_tag) + "\n\t\t\t" + str(ec2_tags)) ...
Запускаем:
[simterm]
$ ./ec2_tags.py [DEBUG] EC2 ID: ec2.Instance(id='i-0df2fe9ec4b5e1855') EBS ID: ec2.Volume(id='vol-0d11fd27f3702a0fc') Dev: ec2.vol.Device('/dev/xvda') Tags set: [ec2.Tag(resource_id='vol-0d11fd27f3702a0fc', key='Role', value='RootDisk')] [ec2.Tag(resource_id='vol-0d11fd27f3702a0fc', key='DataClass', value='Public'), ec2.Tag(resource_id='vol-0d11fd27f3702a0fc', key='Env', value='Dev'), ec2.Tag(resource_id='vol-0d11fd27f3702a0fc', key='Tier', value='Devops')] ... [DEBUG] EC2 ID: ec2.Instance(id='i-02ab1438a79a3e475') EBS ID: ec2.Volume(id='vol-09b6f60396e56c363') Dev: ec2.vol.Device('/dev/xvda') Tags set: [ec2.Tag(resource_id='vol-09b6f60396e56c363', key='Role', value='RootDisk')] [ec2.Tag(resource_id='vol-09b6f60396e56c363', key='Env', value='Dev'), ec2.Tag(resource_id='vol-09b6f60396e56c363', key='DataClass', value='Public'), ec2.Tag(resource_id='vol-09b6f60396e56c363', key='Tier', value='Devops')] ID: ec2.Volume(id='vol-0d75c44a594e312a1') Dev: ec2.vol.Device('/dev/xvdbm') Tags set: [ec2.Tag(resource_id='vol-0d75c44a594e312a1', key='Role', value='PvcDisk')] [ec2.Tag(resource_id='vol-0d75c44a594e312a1', key='Env', value='Dev'), ec2.Tag(resource_id='vol-0d75c44a594e312a1', key='DataClass', value='Public'), ec2.Tag(resource_id='vol-0d75c44a594e312a1', key='Tier', value='Devops')] ...
[/simterm]
Проверяем:
Целиком скрипт теперь выглядит так:
#!/usr/bin/env python import os import boto3 ec2 = boto3.resource('ec2', region_name=os.getenv("AWS_DEFAULT_REGION"), aws_access_key_id=os.getenv("AWS_ACCESS_KEY_ID"), aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY") ) def lambda_handler(event, context): base = ec2.instances.all() for instance in base: print("[DEBUG] EC2\n\t\tID: " + str(instance)) print("\tEBS") for vol in instance.volumes.all(): vol_id = str(vol) device_id = "ec2.vol.Device('" + str(vol.attachments[0]['Device']) + "')" print("\t\tID: " + vol_id + "\n\t\tDev: " + device_id) role_tag = vol.create_tags(Tags=set_role_tag(vol)) ec2_tags = vol.create_tags(Tags=copy_ec2_tags(instance)) print("\t\tTags set:\n\t\t\t" + str(role_tag) + "\n\t\t\t" + str(ec2_tags) + "\n") def is_pvc(vol): try: for tag in vol.tags: if tag['Key'] == 'kubernetes.io/created-for/pvc/name': return True break except TypeError: return False def set_role_tag(vol): device = vol.attachments[0]['Device'] tags_list = [] values = {} if is_pvc(vol): values['Key'] = "Role" values['Value'] = "PvcDisk" tags_list.append(values) elif device == "/dev/xvda": values['Key'] = "Role" values['Value'] = "RootDisk" tags_list.append(values) else: values['Key'] = "Role" values['Value'] = "DataDisk" tags_list.append(values) return tags_list def copy_ec2_tags(instance): tags_list = [] values = {} for instance_tag in instance.tags: if instance_tag['Key'] == 'Env': tags_list.append(instance_tag) elif instance_tag['Key'] == 'Tier': tags_list.append(instance_tag) elif instance_tag['Key'] == 'DataClass': tags_list.append(instance_tag) elif instance_tag['Key'] == 'JiraTicket': tags_list.append(instance_tag) return tags_list if __name__ == "__main__": lambda_handler(0, 0)
С этим готово, осталось запустить его из AWS Lambda-функции, см. AWS: Lambda – копирование тегов EC2 на EBS, часть 2 – создание Lambda-функции.