Русский
Русский
English
Статистика
Реклама

Управление инфраструктурой Open Telekom Cloud с помощью Ansible

Open Telekom Cloud

В этой статье расскажу о нашем опыте работы над развитием инструментов управления инфраструктурой облачного сервиса Open Telekom Cloud, как мы столкнулись с особенностями этого облака, какие решения принимали и какие инструменты использовали.

Open Telekom Cloud международная публичная облачная платформа, основанная на OpenStack. Платформа идеально подходит для компаний или стартапов, которые работают с европейскими пользователями, чьи персональные данные должны храниться в пределах Евросоюза: сервис разработан Deutsche Telekom и соответствует стандартам защиты данных GDPR (Генеральный регламент о защите персональных данных) EC.

С чего все начиналось

Почти два года назад в поисках специалистов в Россию пришел проект Open Telekom Cloud. Требовалось много людей на автоматизированное тестирование и несколько человек в спецотряд под названием Ecosystems, требования были очень расплывчатые: Ну, надо знать Python и понимать, как работать с облачными сервисами

В то время, по удачному стечению обстоятельств, завершалось несколько проектов в Воронеже, и около 10 человек были готовы к взятию новых барьеров. Самых опытных из них отправили в команду Ecosystems, остальные отправились в QA.

Команда Ecosystems занималась API мониторингом, мониторингом сервисов, созданием модулей для Ansible, разработкой и поддержкой инструментов управления инфраструктурой Open Telekom Cloud. На данный момент она участвует еще и в разработке Terraform Provider, OpenStack SDK, OpenStack Ansible Collections. Во всех наших инструментах (OTC Extensions, Terraform Provider, Ansible Collections) мы стараемся максимально соответствовать OpenStack и переиспользовать существующие решения для него.

С самого начала с Open Telekom Cloud все оказалось довольно интересно. Разработка находится на стороне Huawei, декларировалось, что облако основано полностью на технологии OpenStack. Но Huawei внесли множество своих решений в сервисы. Многие из них были полностью написаны китайскими коллегами, были заметные отличия нашего API от OpenStack API.

Но тогда это нас не сильно волновало. Первой нашей задачей в Ecosystems было создание мониторингов, которые помогут определить качество работы тех или иных сервисов, в сложных сценариях. Например, использовать балансировщик нагрузки для хостов в разных AZ (availability zone), наблюдать за распределением запросов по каждому из хостов. Или следить за отказоустойчивостью того же балансировщика нагрузки в сценарии, когда один или несколько хостов за ним выключаются по тем или иным причинам.

В качестве инструментов для реализации задачи был выбран Ansible и Terraform, провайдер для Terraform уже существовал, и Huawei его в какой-то степени поддерживал. При создании мониторинга начали изучать и использовать Go для различных задач. Например, нужен был быстрый сервер, который не загнется от потока запросов, что в будущем открыло нам новое направление с поддержкой провайдера Terraform. Во время создания мониторинга находились баги в Terraform провайдере, и казалось, что дождаться их решения будет невозможно, никто их исправлять не спешил.

Что ж, раз Terraform стал для нас критичным инструментом, мы отправились в репозиторий с провайдером и начали исправлять баги. В какой-то момент поддержка провайдера со стороны Huawei прекратилась, да и через некоторое время HashiCorp решили всех сторонних провайдеров убрать из своего репозитория.

Тогда мы решили перетащить провайдер в свою организацию на Github, форкаем golangsdk, называем его gophertelekomcloud и переносим провайдер на него (после всех преобразований gophertelekomcloud в итоге стал самостоятельным проектом, указания, что это форк, больше нет). Но это уже другая история

С начала работы на проекте прошло примерно полгода, из-за провайдера объем работы вырос и стало понятно, что два человека со всем не справятся. Мы набрали в команду толковых ребят, и часть из них стала заниматься развитием и поддержкой Terraform провайдера, часть осталась на мониторингах.

Ansible и коллекции

Опустив некоторые детали, первоначально мониторинг работал примерно так:

Точкой входа был AWX, он вызывал плейбуки с ролью Terraform, результат выполнения Terraform снова передавался в Ansible, и далее выполнялись какие-то сценарии.

Кажется, что все отлично, есть базовый .tf модуль, который создает сетевую часть, и отдельные модули на каждый сценарий, .state всех модулей хранится в s3. Все удобно, работает как часы.

Но подход к мониторингу поменялся. Мы начинали как самостоятельный проект без ограничений на выбор инструментов и не было задачи интегрироваться в какую-то существующую инфраструктуру, но теперь пришла задача интегрироваться в существующую инфраструктуру API-мониторинга с целью сделать более универсальный единый инструмент. В которой нет AWX и Terraform.

Был только Python и Ansible

Учитывая, что Open Telekom Cloud не является решением, на 100% совместимым с OpenStack, в нем присутствуют сервисы собственной разработки, например, RDS (Relational Database Service). С помощью Ansible мы не можем построить все необходимые нам ресурсы используя OpenStack SDK и ansible-collections-openstack, в таком виде, чтобы это было легко поддерживать.

Что ж, надо расширять возможности OpenStack SDK, адаптировать под наш проект и писать собственные коллекции. Для коллекций необходимо описание ресурсов, которых нет в OpenStack SDK, для таких ресурсов был создан проект OTC Extensions.

OTC Extensions

Этот проект дополняет и расширяет функции OpenStack SDK для работы с Open Telekom Cloud, так же если он установлен в качестве python package, в OpenStack Client добавляются дополнительные плагины для работы с облаком.

Взаимодействует с:

python-openstacksdk

python-openstackclient

Структура проекта близка к OpenStack SDK:

otcextensions/    sdk/        compute/            v2/                server.py                _proxy.py    tests/        unit/            sdk/                compute/                    v2/                        test_server.py

Все дополнительные ресурсы унаследованы от базового openstack.resource.Resource, или если мы хотим изменить существующий объект то нужно наследование от него базового класса этого объекта, например, если у openstack.compute.v2.server нет поддержки тэгов или они реализованы иначе:

class Server(server.Server):    def add_tag(self, session, tag):        """Adds a single tag to the resource."""    def remove_tag(self, session, tag):        """Removes a single tag from the specified server."""

И далее патчим Connection в методе load (otcextensions/sdk/__init__.py):

openstack.compute.v2.server.Server.add_tag = server.Server.add_tagopenstack.compute.v2.server.Server.remove_tag = server.Server.remove_tag

В итоге наш connection теперь будет работать с кастомными тегами.

Для нового ресурса:

otcextensions/    sdk/        elb/            v2/                elb_certificate.py                _proxy.py

В файле elb_certificate.py создаем класс, указываем его url, какие методы поддерживает, какие параметры принимает

class Certificate(resource.Resource):resources_key = 'certificates'base_path = ('/lbaas/certificates')# capabilitiesallow_create = Trueallow_fetch = Trueallow_commit = Trueallow_delete = Trueallow_list = True_query_mapping = resource.QueryParameters(    'id', 'name', 'description',    'type', 'domain', 'content',    'private_key', 'marker', 'limit',)# Properties#: Namename = resource.Body('name')#: Idid = resource.Body('id')#: Descriptiondescription = resource.Body('description')#: Certificate type.type = resource.Body('type')#: Domain name associated with the server certificate.domain = resource.Body('domain')#: Private key of the server certificate. *Type: string*private_key = resource.Body('private_key')#: Public key of the server certificate or CA certificate. *Type: string*content = resource.Body('certificate')#: Administrative status of the certificate.admin_state_up = resource.Body('admin_state_up')#: Creation timecreate_time = resource.Body('create_time')#: Specifies the project ID.project_id = resource.Body('tenant_id')#: Time when the certificate expires.expire_time = resource.Body('expire_time')#: Time when the certificate was updated.update_time = resource.Body('update_time')

Рядом обязательно должен быть файл _proxy.py, этот класс адаптер предоставляет интерфейс для работы с инстансом Connection, в нем мы описываем методы ресурса:

class Proxy(_proxy.Proxy):    skip_discovery = True    # ======== Certificate ========    def create_certificate(self, **attrs):        return self._create(_certificate.Certificate, **attrs)    def certificates(self, **query):        return self._list(_certificate.Certificate, **query)    def delete_certificate(self, certificate, ignore_missing=True):        return self._delete(_certificate.Certificate, certificate,                            ignore_missing=ignore_missing)    def get_certificate(self, certificate):        return self._get(_certificate.Certificate, certificate)    def update_certificate(self, certificate, **attrs):        return self._update(_certificate.Certificate, certificate, **attrs)    def find_certificate(self, name_or_id, ignore_missing=False):        return self._find(_certificate.Certificate, name_or_id,                          ignore_missing=ignore_missing)

В otcextensions/sdk/__init__.py eсть структура со всеми нестандартными ресурсами - OTC_SERVICES, добавляем наш ресурс по имени папки в которой он находится:

'elb': {    'service_type': 'elb',    'replace_system': True}

OTC_SERVICES так же в методе load, добавляются в Connection:

for (service_name, service) in OTC_SERVICES.items():    if service.get('replace_system', False):        if service['service_type'] in conn._proxies:            del conn._proxies[service['service_type']]    sd = _get_descriptor(service_name)    conn.add_service(sd)

На этом добавление сервиса завершено, мы можем его использовать через OpenStack SDK.

cfg = openstack.config.get_cloud_region(cloud=TEST_CLOUD_NAME)conn = connection.Connection(config=cfg)sdk.register_otc_extensions(conn)cert = conn.elb.create_certificate(    private_key=PRIVATE_KEY,    content=CERTIFICATE,    name=NAME )

Ansible collections

Окей, ресурсы теперь есть, осталось разобраться как их использовать, есть отличный вариант, создать коллекцию своих модулей и хранить ее в ansible-galaxy, по аналогии с ansible-collections-openstack создаем коллекцию ansible-collection-cloud, которая основана на OTC extensions.

Если модуль, который мы добавляем в коллекцию существует в OpenStack коллекции, то мы стараемся максимально обеспечить обратную совместимость, создавая единый интерфейс для модулей.

Делаем все по гайду (developing-collections):

ansible-collection-cloud/    plugins/        module_utils/            otc.py        modules/            elb_certificate.py            elb_certificate_info.py

В module_utils, храним базовый для всех модулей класс:

class OTCModule:    """Openstack Module is a base class for all Openstack Module classes.    The class has `run` function that should be overriden in child classes,    the provided methods include:    """

В нем создается инстанс Connection, и патчится через OTC extensions, чтобы мы могли использовать кастомные ресурсы.

Все модули делятся на два типа с постфиксом _info возвращают информацию о существующем ресурсе, без него создают/изменяют/удаляют ресурсы.

Например, lb_certificate_info:

from ansible_collections.opentelekomcloud.cloud.plugins.module_utils.otc import OTCModuleclass LoadBalancerCertificateInfoModule(OTCModule):    argument_spec = dict(        name=dict(required=False)    )    otce_min_version = '0.10.0'    def run(self):        data = []        if self.params['name']:            raw = self.conn.elb.find_certificate(name_or_id=self.params['name'], ignore_missing=True)            if raw:                dt = raw.to_dict()                dt.pop('location')                data.append(dt)        else:            for raw in self.conn.elb.certificates():                dt = raw.to_dict()                dt.pop('location')                data.append(dt)        self.exit_json(            changed=False,            elb_certificates=data        )def main():    module = LoadBalancerCertificateInfoModule()    module()if __name__ == '__main__':    main()

аналогично выполнен и lb_certificate.

В настоящий момент идет активная разработка модулей. Сервисы, отмеченные зеленым, полностью покрыты, для остальных в большинстве случаев можно использовать нативные OpenStackмодули.

Установка коллекций

Для начала работы необходимо установить коллекции в окружение, для примера используем venv (venv использовать необязательно, но такой подход имеет свои плюсы):

/$ cd ~~$ python3 -m venv ansiblevenv

Активируем окружение:

~$ source ansiblevenv/bin/activate(ansiblevenv) ~$

Установим OpenStack Client, otcextensions и wheel (необязательно):

(ansiblevenv) ~$ pip install wheel(ansiblevenv) ~$ pip install openstackclient(ansiblevenv) ~$ pip install otcextensions

Для работы с коллекциями далее необходимо установить их из Ansible-Galaxy (Ansible-Galaxy содержит множество свободно распространяемых ролей и коллекций, разрабатываемых сообществом). Дополнительно ставим OpenStack коллекцию для нативных ресурсов:

(ansiblevenv) $ ansible-galaxy collection install opentelekomcloud.cloud(ansiblevenv) $ ansible-galaxy collection install openstack.cloud

В принципе все для работы с облаком готово, осталось разобраться с авторизацией. OpenStackподдерживает несколько способов авторизации.

Авторизация

clouds.yaml

OpenStack client/sdk самостоятельно ищет файл для авторизации в следующих местах:

  1. system-wide (/etc/openstack/{clouds,secure}.yaml)

  2. Home directory / user space (~/.config/openstack/{clouds,secure}.yaml)

  3. Current directory (./{clouds,secure}.yaml)

clouds:  otc:    profile: otc    auth:      username: '<USER_NAME>'      password: '<PASSWORD>'      project_name: '<eu-de_project>'      # or project_id: '<123456_PROJECT_ID>'      user_domain_name: 'OTC00000000001000000xxx'      # or user_domain_id: '<123456_DOMAIN_ID>'    account_key: '<AK_VALUE>' # AK/SK pair for access to OBS    secret_key: '<SK_VALUE>'

После того, как файл создан, указываем переменной окружения OS_CLOUD имя конфигурации, в данном случае:

~$ export OS_CLOUD=otc

Имя может быть любым, и один файл может содержать множество конфигураций.

Чтобы проверить, что все сделано правильно, можно запустить любую команду OpenStack клиента:

~$ openstack server list

Если авторизация успешна, то мы получим список серверов:

Чтобы повысить безопасность, можно вынести чувствительную информацию из clouds.yaml. Рядом с файлом clouds.yaml создаем secure.yaml и помещаем туда все, что хотим скрыть:

clouds:  otc:    auth:      password: '<PASSWORD>'

Переменные окружения

Этот способ заключается в простом использовании переменных окружения, которые можно задавать вручную, либо создать файл, например, .ostackrc:

# .ostackrc fileexport OS_USERNAME="<USER_NAME>"export OS_USER_DOMAIN_NAME=<OTC00000000001000000XYZ>export OS_PASSWORD=<PASSWORD> # optionalexport OS_TENANT_NAME=eu-deexport OS_PROJECT_NAME=<eu-de_PROJECT_NAME>export OS_AUTH_URL=https://iam.eu-de.otc.t-systems.com:443/v3export NOVA_ENDPOINT_TYPE=publicURLexport OS_ENDPOINT_TYPE=publicURLexport CINDER_ENDPOINT_TYPE=publicURLexport OS_VOLUME_API_VERSION=2export OS_IDENTITY_API_VERSION=3export OS_IMAGE_API_VERSION=2

Создаем переменные:

~$ source .ostackrc

С авторизацией разобрались! Теперь можно полноценно использовать коллекции.

Использование коллекции

Как мы знаем в коллекции два типа модулей: с постфиксом info возвращают информацию о существующем ресурсе, без него создают/изменяют/удаляют ресурсы. Все модули вызываются по полному имени: opentelekom.cloud.*

Все info модули поддерживают поиск как по имени, так и по id ресурса, например:

- name: Get loadbalancer info  opentelekomcloud.cloud.loadbalancer_info:    name: "{{ lb_name_or_id }}"  register: result

Если передано имя ресурса, то в ответе вернется dict с параметрами ресурса, если имя не указано, то появится список всех доступных в проекте ресурсов. Не инфо модули также возвращают dict.

Пример сценария

Для примера использования коллекций создадим файл example.yaml и будем там описывать различные ресурсы. Создадим небольшую инфраструктуру: сеть, сервер и балансировщик нагрузки.

Для создания нативных ресурсов всегда используются OpenStack модули, например, сеть:

---- name: Create main network  openstack.cloud.network:    name: my_network  register: net- name: Getting info about external network  openstack.cloud.networks_info:    name: admin_external_net  register: ext_net- name: Create subnet  openstack.cloud.subnet:    name: my_subnet    network_name: "{{ net.network.name }}"    cidr: 192.168.0.0/16    dns_nameservers:          - 100.125.4.25          - 100.125.129.199  register: subnet- name: Create router  openstack.cloud.router:    name: "{{ public_router_scenario }}_router"    enable_snat: true    network: "{{ ext_net.openstack_networks[0].id }}"    interfaces:      - "{{ subnet.subnet.name }}"  register: router

Для сервера нам нужен ключ:

- name: Create key pair  openstack.cloud.keypair:    name: bastion_key_pair    public_key_file: "/tmp/keys/public.pub"  register: keypair

Создадим security group, откроем порты 80, 443 и 22 для ssh, также откроем icmp:

- name: Create security group  openstack.cloud.security_group:    name: bastion_secgroup    description: Allow external connections to ssh, http, https and icmp  register: sec_group- name: Add rules for tcp connection to the security group  openstack.cloud.security_group_rule:    security_group: "{{ sec_group.secgroup.name }}"    protocol: tcp    port_range_min: "{{ item }}"    port_range_max: "{{ item }}"    remote_ip_prefix: 0.0.0.0/0  loop:     - 22    - 80    - 443- name: Add a rule for icmp connection to the security group  openstack.cloud.security_group_rule:    security_group: "{{ secur_group.secgroup.name }}"    protocol: icmp    port_range_min: -1    port_range_max: -1    remote_ip_prefix: 0.0.0.0/0

Для подключения сервера к сети необходимо создать порт:

- name: Create a port for a bastion  openstack.cloud.port:    name: bastion_port    network: net.network.id    security_groups:      - "{{ sec_group.secgroup.name }}"     fixed_ips:       - ip_address: 192.168.200.10  register: port

Для создания сервера тоже используются нативные модули. Например, создадим bastion (это те хосты, которые принято использовать как jump для доступа в недоступные снаружи сети). Здесь также представлен пример инъекции команд при создании сервера через userdata:

- name: Getting information about a current image  openstack.cloud.image_info:    image: Standard_Debian_10_latest  register: image- name: Create a new instance  openstack.cloud.server:    state: present    name: bastion    flavor: s2.medium.2    key_name: bastion_key_pair    availability_zone: eu-de-01    security_groups:     - "{{ sec_group.secgroup.name }}"    timeout: 200    userdata: |      {%- raw -%}#!/usr/bin/env bash                 #setup ssh service config                 file=/etc/ssh/sshd_config                 cp -p $file $file.old &&                     while read key other; do                         case $key in                         GatewayPorts) other=yes ;;                         AllowTcpForwarding) other=yes ;;                         PubkeyAuthentication) other=yes ;;                         PermitTunnel) other=yes ;;                         esac                         echo "$key $other"                     done <$file.old > $file                 sudo service sshd restart                 mkdir -p /etc/sslcerts/live                 #generate Diffie-Hellman for TLS                 sudo openssl dhparam -out /etc/sslcerts/live/dhparams.pem 2048      {% endraw %}    nics:      - port-name: "{{ port.port.name }}"    boot_from_volume: true    volume_size: 5    image: "{{ image.openstack_image.id }}"    terminate_volume: true    delete_fip: true    auto_ip: true  register: bastion

Для динамической регистрации хоста используем add_host:

- name: Register nodes  add_host:    name: "{{ bastion.openstack.name }}"    groups: bastions    ansible_host: "{{ bastion.openstack.interface_ip }}"    ansible_ssh_user: linux    ansible_ssh_private_key_file: "/path/to/key"

После создания сервера можно проверить подключение:

- name: Wait for nodes to be up  hosts: bastions  gather_facts: no  tasks:    - name: Wait for nodes to be up      wait_for_connection:        timeout: 250

Так как одним из самых популярных сервисов является балансировщик нагрузки, то далее приведу пример его создания уже с использованием наших модулей.

После того, как у нас создана сеть и есть хотя бы один сервер, мы можем создать loadbalancer:

- name: Create loadbalancer  opentelekomcloud.cloud.loadbalancer:    name: my_elastic_loadbalancer    state: present    vip_subnet: "{{ subnet.subet.id }}"    vip_address: 192.168.200.100    auto_public_ip: true  register: loadbalancer

Далее для loadbalancer создаем listener, если протокол https, то сразу можем создать сертификат:

- name: Create listener http  opentelekomcloud.cloud.lb_listener:    state: present    name: my_listener_http    protocol: http    protocol_port: 80    loadbalancer: "{{ loadbalancer.loadbalancer.id }}"  register: listener_http- name: Create Server certificate  opentelekomcloud.cloud.lb_certificate:    name: my_https_cetificate    content: "{{ some_https_certificate }}"    private_key: "{{ some_loadbalancer_https_key }}"  register: certificate- name: Create listener https  opentelekomcloud.cloud.lb_listener:    state: present    name: my_listener_https    protocol: terminated_https    protocol_port: 443    loadbalancer: "{{ loadbalancer.loadbalancer.id }}"    default_tls_container_ref: "{{certificate.elb_certificate.id }}"  register: listener_https

Чтобы добавить к балансировщику сервер, необходимо создать пул серверов. Для каждого listener создается отдельный пул:

- name: Create lb pool http  opentelekomcloud.cloud.lb_pool:    state: present    name: my_pool_http    protocol: http    lb_algorithm: round_robin    listener: "{{ listener_http.listener.id }}"  register: lb_pool_http- name: Create lb pool https  opentelekomcloud.cloud.lb_pool:    state: present    name: my_pool_https    protocol: http    lb_algorithm: round_robin    listener: "{{ listener_https.listener.id }}"  register: lb_pool_https

Добавляем сервер в пул:

- name: Create members for a http pool in the load balancer  opentelekomcloud.cloud.lb_member:    state: present    name: my_member_http    pool: "{{ lb_pool_http.server_group.id }}"    address: 192.168.200.10    protocol_port: http    subnet: "{{ subnet.subet.id }}"  register: members_http- name: Create members for a https pool in the load balancer  opentelekomcloud.cloud.lb_member:    state: present    name: my_member_https    pool: "{{ lb_pool_https.server_group.id }}"    address: 192.168.200.10    protocol_port: http    subnet: "{{ subnet.subet.id }}"  register: members_https

И, наконец, добавим healthmonitor для каждого пула, чтобы наблюдать за статусом хостов:

- name: Enable health check for http members  opentelekomcloud.cloud.lb_healthmonitor:    state: present    name: http_healthcheck    pool: "{{ lb_pool_http.server_group.id }}"    delay: 1    max_retries: 2    monitor_timeout: 1    type: http- name: Enable health check for https members  opentelekomcloud.cloud.lb_healthmonitor:    state: present    name: https_healthcheck    pool: "{{ lb_pool_https.server_group.id }}"    delay: 1    max_retries: 2    monitor_timeout: 1    type: http

Если выполнять плейбук с verbosity, то в консоли мы увидим все параметры создаваемых ресурсов.

В результате на консоли можно увидеть наш сервер, балансировщик нагрузки и все остальные ресурсы:

Таким образом мы перевели инфраструктуру наших мониторингов полностью на Ansible.

Насколько мне известно, в России не одна компания пользуется услугами Huawei для создания собственных облачных сервисов, было бы интересно увидеть в комментариях, приходилось ли им решать подобные вопросы касаемо расширения ванильного OpenStack SDK и как они к этому подходили.

Весь код находится в публичном доступе и хранится на Github:

Если тема интересна, то буду рад поделиться своим опытом по работе с другими инструментами. Пишите в комментариях, готов ответить на ваши вопросы!

Источник: habr.com
К списку статей
Опубликовано: 06.05.2021 10:07:53
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Блог компании deutsche telekom it solutions (ex t-systems)

Python

Api

Devops

Облачные сервисы

Ansible

Cloud

Openstack

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru