Посмотрим, как использовать опубликованные в 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-ресурс.

Комментарии (1)


  1. ultral
    14.02.2019 14:53

    1. а как это коррелируют с automationbroker ?
    2. Чем это решение лучше Ansible Playbook Bundle?