Есть кластер 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-функции.



