Достать до звезд: Осваиваем операторы Ansible для управления приложениями в Kubernetes

    Посмотрим, как использовать опубликованные в Ansible Galaxy роли (Role) в качестве операторов (Operator), управляющих приложениями в Kubernetes, и разберем это на примере создания оператора, который просто устанавливает приложение, гибко настраивая свое поведение в зависимости от среды.



    Мы будем использовать Ansible Operator и модуль k8s, чтобы показать, как применять Ansible для создания Kubernetes-приложений.

    Ansible Operator входит в состав Operator SDK и позволяет сформулировать операционный регламент приложения (как оно должно устанавливаться и обслуживаться) на языке ролей и плейбуков Ansible. Модуль k8s, в свою очередь, расширяет возможности управления объектами в Kubernetes при создании таких ролей и плейбуков.

    Оператор создается вот так вот просто.

    FROM quay.io/operator-framework/ansible-operator
    
    RUN ansible-galaxy install djzager.hello_world_k8s
    
    RUN echo $'--- \n\
    - version: v1alpha1\n\
      group: examples.djzager.io\n\
      kind: HelloWorld\n\
      role: /opt/ansible/roles/djzager.hello_world_k8s' > ${HOME}/watches.yaml
    

    Ключ на старт


    Для начала пара слов об Ansible-модуле k8s. Он появился в Ansible 2.6 и расширяет возможности работы с Kubernetes-объектами из Ansible, причем в любых дистрибутивах Kubernetes, включая Red Hat OpenShift. В блоге Ansible был отдельный пост про этот модуль и динамический Python-клиент для Red Hat OpenShift. На наш взгляд, работать с Kubernetes-объектами через Ansible без использования модуля k8s – неправильно. Механизм операторов изначально создавался для того, чтобы запускать приложения в Kubernetes, а Operator SDK предоставляет инструменты для сборки, тестирования и упаковки операторов. В свою очередь, Ansible Operator нужен для того, чтобы задать операционный регламент приложения на языке Ansible. Соответствующий рабочий процесс организован довольно просто: сначала делаем operator-sdk new --type=Ansible, чтобы сгенерировать необходимые файлы для Ansible-оператора, затем расписываем Ansible, и, наконец, делаем operator-sdk build, чтобы собрать приложение для работы в Kubernetes. Но если у нас уже есть роль в Ansible Galaxy, которая управляет приложением в Kubernetes, все становится еще проще. Ниже мы проделаем следующее:

    1. Соберем Ansible-роль для управления приложением Hello World в Kubernetes, которая поможет нам продемонстрировать возможности Ansible-модуля k8s.
    2. Опубликуем эту роль в Ansible Galaxy.

    Итак, соберем Ansible-оператор, используя опубликованную в Ansible Galaxy роль. Зачем вообще создавать оператор, используя роль из Ansible Galaxy? Причины две:

    1. Чтобы не повторяться. Если мы уже запрограммировали Ansible-роль для управления приложением Hello World и опубликовали ее в Ansible Galaxy, то логично использовать ее и при создании Ansible-оператора.
    2. Из-за разделения ответственностей. Мы хотим, чтобы Ansible-роль Hello World управляла одноименным приложением в Kubernetes, а операционная (эксплуатационная) логика оставалась внутри оператора. В нашем примере операционная логика предельно проста: она просто вызывает роль djzager.hello_world_k8s при каждом создании или модификации custom-ресурса HelloWorld. Однако в будущем это разделение станет более существенным, например, мы добавим валидацию в приложение Hello World через Ansible-роль, и реализуем управление статусом custom-ресурсов HelloWorld через логику оператора.

    Hello Kubernetes, встречай Ansible


    Что нам понадобится


    1. Ansible – см. руководство по установке, если у вас не установлен Ansible.
    2. Python-клиент для OpenShift (опционально). Нужен только для локального запуска. Инструкции по установке здесь.

    Приступим. Первым делом создаем скелет роли с помощью ansible-galaxy:

    # I like clear names on projects.
    # In meta/main.yml I will make role_name: hello-world-k8s
    $ ansible-galaxy init ansible-role-hello-world-k8s
    

    Сразу после создания новой Ansible-роли зададим все значения по умолчанию, чтобы заодно задокументировать ее допустимые параметры конфигурации. Тем более, наш пример Hello World не особенно сложен в этом плане. Вот как выглядит наш файл defaults/main.yml:

    ---
    # NOTE: meta.name(space) comes from CR metadata when run with Ansible Operator
    # deploy/crds has an example CR for reference
    name: "{{ meta.name | default('hello-world') }}"
    namespace: "{{ meta.namespace | default('hello-world') }}"
    image: docker.io/ansibleplaybookbundle/hello-world:latest
    
    # To uninstall from the cluster
    # state: absent
    state: present
    
    # The size of the hello-world deployment
    size: 1
    

    Задав значения по умолчанию, надо определиться, что же будет делать роль. Приложение Hello World должно будет сделать следующее:

    1. Получить информацию о доступных API в кластере.
    2. Создать несколько шаблонов и убедиться, что они присутствуют или отсутствуют в кластере.

    Поэтому наш файл tasks/main.yml выглядит так:

    ---
    
    - name: "Get information about the cluster"
      set_fact:
        api_groups: "{{ lookup('k8s', cluster_info='api_groups') }}"
    
    - name: 'Set hello-world objects state={{ state }}'
      k8s:
        state: '{{ state }}'
        definition: "{{ lookup('template', item.name) | from_yaml }}"
      when: item.api_exists | default(True)
      loop:
        - name: deployment.yml.j2
        - name: service.yml.j2
        - name: route.yml.j2
          api_exists: "{{ True if 'route.openshift.io' in api_groups else False }}"
    

    Прежде чем перейдем к шаблонам, обратите внимание вот на эту строку в файле задачи:

    api_exists: "{{ True if 'route.openshift.io' in api_groups else False }}"
    

    Используя set_fact, мы получаем список всех доступных в кластере API, чтобы затем выборочно генерировать шаблоны в зависимости того, есть ли там нужный API — в данном случае, route.openshift.io. По умолчанию, в Kubernetes-кластере OpenShift маршруты (Routes) недоступны, и они нам не нужны, поэтому мы работаем с объектом Route, только когда в кластере есть route.openshift.io.

    Мы можем не только выборочно управлять объектами в кластере в зависимости от наличия тех или иных API, но и с помощью шаблонов Jinja2 использовать OpenShift-овский DeploymentConfig в нашем шаблоне Deployment, если в кластере есть API apps.openshift.io. Вот как выглядит наш файл templates/deployment.yml.j2:

    ---
    
    {% if 'apps.openshift.io' in api_groups %}
    apiVersion: apps.openshift.io/v1
    kind: DeploymentConfig
    {% else %}
    apiVersion: apps/v1
    kind: Deployment
    {% endif %}
    metadata:
      name: {{ name }}
      namespace: {{ namespace }}
      labels:
        app: {{ name }}
        service: {{ name }}
    spec:
      replicas: {{ size }}
    {% if 'apps.openshift.io' in api_groups %}
      selector:
        app: {{ name }}
        service: {{ name }}
    {% else %}
      selector:
        matchLabels:
          app: {{ name }}
          service: {{ name }}
    {% endif %}
      template:
        metadata:
          labels:
            app: {{ name }}
            service: {{ name }}
        spec:
          containers:
          - image: {{ image }}
            name: hello-world
            ports:
            - containerPort: 8080
              protocol: TCP
    

    Файл templates/service.yml.j2:

    ---
    
    apiVersion: v1
    kind: Service
    metadata:
      name: {{ name }}
      namespace: {{ namespace }}
      labels:
        app: {{ name }}
        service: {{ name }}
    spec:
      ports:
      - name: web
        port: 8080
        protocol: TCP
        targetPort: 8080
      selector:
        app: {{ name }}
        service: {{ name }}
    

    И, наконец, файл templates/route.yml.j2:

    ---
    
    apiVersion: route.openshift.io/v1
    kind: Route
    metadata:
      name: {{ name }}
      namespace: {{ namespace }}
      labels:
        app: {{ name }}
        service: {{ name }}
    spec:
      port:
        targetPort: web
      to:
        kind: Service
        name: {{ name }}
    

    Мы опустили файл meta/main.yml, но его можно найти здесь.

    В итоге, у нас есть Ansible-роль, управляющая приложением Hello World в Kubernetes, и мы можем использовать имеющиеся в кластере API. Иначе говоря, модуль k8s и динамический клиент упрощают работу с объектами в Kubernetes. Надеемся, что на примере этой роли мы смогли показать потенциал Ansible при работе с Kubernetes.

    Hello Galaxy, встречай Kubernetes


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

    После того, как мы выложили нашу роль на GitHub, осталось только:

    1. Войти в Ansible Galaxy, чтобы дать доступ к нашим репозиториям на GitHub.
    2. Импортировать нашу роль.

    Все, теперь наша роль hello_world_k8s публично доступна в Ansible Galaxy, вот здесь.

    Hello Ansible Operator, встречай Galaxy


    Если внимательно изучить наш проект Hello World на GitHub, то можно заметить, что мы добавили туда несколько вещей, необходимых для создания Ansible-оператора, а именно:

    1. Watches-файл, обеспечивающий сопоставление custom-ресурсов Kubernetes с ролями или плейбуками Ansible.
    2. Dockerfile для сборки нашего оператора.
    3. Deploy-каталог с Kubernetes-объектами, необходимыми для запуска нашего оператора.

    Если вам нужно узнать больше о том, как собирать свои собственные Ansible-операторы, воспользуйтесь Руководством пользователя. Но поскольку мы обещали собрать Ansible-оператор, используя опубликованную в Ansible Galaxy роль, то все, что нам действительно нужно – это Dockerfile:

    FROM quay.io/operator-framework/ansible-operator
    
    RUN ansible-galaxy install djzager.hello_world_k8s
    
    RUN echo $'--- \n\
    - version: v1alpha1\n\
      group: examples.djzager.io\n\
      kind: HelloWorld\n\
      role: /opt/ansible/roles/djzager.hello_world_k8s' > ${HOME}/watches.yaml
    

    Теперь собираем оператор:

    $ docker build -t hello-world-operator -f Dockerfile .
    Sending build context to Docker daemon 157.2 kB
    Step 1/3 : FROM quay.io/operator-framework/ansible-operator
    latest: Pulling from operator-framework/ansible-operator
    Digest: sha256:1156066a05fb1e1dd5d4286085518e5ce15acabfff10a8145eef8da088475db3
    Status: Downloaded newer image for quay.io/water-hole/ansible-operator:latest
     ---> 39cc1d19649d
    Step 2/3 : RUN ansible-galaxy install djzager.hello_world_k8s
     ---> Running in 83ba8c21f233
    - downloading role 'hello_world_k8s', owned by djzager
    - downloading role from https://github.com/djzager/ansible-role-hello-world-k8s/archive/master.tar.gz
    - extracting djzager.hello_world_k8s to /opt/ansible/roles/djzager.hello_world_k8s
    - djzager.hello_world_k8s (master) was installed successfully
    Removing intermediate container 83ba8c21f233
     ---> 2f303b45576c
    Step 3/3 : RUN echo $'--- \n- version: v1alpha1\n  group: examples.djzager.io\n    kind: HelloWorld\n      role: /opt/ansible/roles/djzager.hello_world_k8s' > ${HOME}/watches.yaml
     ---> Running in cced495a9cb4
    Removing intermediate container cced495a9cb4
     ---> 5827bc3c1ca3
    Successfully built 5827bc3c1ca3
    Successfully tagged hello-world-operator:latest
    

    Понятно, что для использования этого оператора, понадобится содержимое каталога deploy из нашего проекта, чтобы создать Service Account, Role и Role Binding, Custom Resource Definition, а также, чтобы развернуть сам оператор. А после развертывания оператора, надо будет создать Custom Resource, чтобы получить экземпляр приложения Hello World:

    apiVersion: examples.djzager.io/v1alpha1
    kind: HelloWorld
    metadata:
      name: example-helloworld
      namespace: default
    spec:
      size: 3
    

    Области действия оператора: пространство имен и кластер


    Чуть выше мы уже предлагали изучить наш каталог deploy и поискать там Kubernetes-объекты, необходимые для запуска оператора. Там есть три вещи, которые ограничивают область действия оператора при управлении custom-ресурсами пространством имен, в котором развернут сам оператор, а именно:

    1. Переменная среды WATCH_NAMESPACE в файле operator.yaml, которая говорит оператору, где искать custom-ресурсы
    2. role.yaml
    3. role_binding

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

    1. Создать ClusterRole вместо Role.
    2. Создать оператор ServiceAccount в том пространстве имен, где будет развернут оператор.
    3. Создать ClusterRoleBinding, который свяжет ServiceAccount из конкретного пространства имен с ClusterRole.
    4. Развернуть оператор с незаданной переменной среды WATCH_NAMESPACE (т.е. "").

    Если проделать все эти вещи, то остальные пользователи кластера смогут развертывать экземпляры нашего приложения Hello World. Если вас заинтересовал эта тема, советуем изучить Operator Lifecycle Manager (входит в состав Operator Framework).

    Звездный путь


    Мы показали, как создать Ansible-роль для управления приложением в Kubernetes, опубликовать эту роль в Ansible Galaxy и использовать ее в Ansible-операторе. Надеемся, что теперь вы:

    1. Будете пользоваться Ansible-модулем k8s.
    2. Начнете публиковать в Ansible Galaxy свои роли для управления Kubernetes-приложениями.
    3. Заинтересуетесь Operator SDK и подпишетесь на нашу рассылку Operator Framework.

    Наше приложение Hello World намеренно сделано предельно простым, однако есть вещи, которые помогут сделать более надежным даже его, и вот какие:

    1. Использование Operator SDK – мы намеренно не делали этого в нашем примере, чтобы подчеркнуть, как легко перейти от Ansible-роли к оператору. Но лучше все же использовать эту роль с SDK (то есть, operator-sdk new), к тому же, это может понадобиться на последующих шагах.
    2. Валидация – в нашем текущем варианте пользователь может создать CR с размером: abc, что неизбежно повлечет ошибку на этапе развертывания. Так что ошибки лучше отлавливать на этапе спецификации, а не после того, как начнется работа.
    3. Жизненный цикл – в более сложных примерах это может быть то же обновление версий. В сценариях вроде нашего, где есть только одна версия приложения Hello World, мы могли бы определять, устарел ли образ работающего контейнера, сравнивая его с доступными образами в соответствующем реестре контейнеров, и при необходимости обновлять запущенные экземпляры.
    4. Тестирование – при разработке и тестировании Ansible-ролей очень пригодится Molecule.
    5. Operator Lifecycle Manager – это набор инструментов для управления операторами. Интеграция с ним поможет выполнять установку и обновление нашего оператора.
    6. Статус – мы могли бы активировать status subresource в нашем Hello World CRD и использовать модуль k8s_status, входящий в состав образа Ansible Operator, чтобы включать информацию о состоянии в custom-ресурс.
    • +11
    • 2,6k
    • 2
    Red Hat
    76,00
    Программные решения с открытым исходным кодом
    Поделиться публикацией

    Комментарии 2

      0
      1. а как это коррелируют с automationbroker ?
      2. Чем это решение лучше Ansible Playbook Bundle?
        0
        1. Никак, это параллельные решения той же проблемы
        2. Операторы имеет более богатый API — не только создать/удалить сервис как у APB, но и реагировать на изменения CRD для переконфигурации готового сервиса, приводить сервис к требуемому виду при внешних измениях. Кроме того, для установки оператора не требуется сторонние расширения вроде APB/Service Catalog. Опциональным аналогом APB для операторов является OLM — он дает возможность устанавливать/обновлять операторы из Operator Hub, просматривать и удалять установленные

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

      Самое читаемое