Раскатка k8s 1.26 ansible+jenkins

от автора

И вообще managed cluster своими руками за 1000 и один человеко-час.

Приветствую всех! Недавнее масштабное обновление github (когда там часами не работало ничто) побудило меня поделиться своим опытом автоматизации установки k8s на bare metal.

Итак. Задача: развернуть кластер kubernetes последней на данный момент версии 1.26 средствами CI/CD за минимальное время (на моем оборудовании около 3 минут), и вообще, начать с этого построение своих инструментов управления кластером.

Для этого потребуется от 3 серверов под управлением ubuntu, чтобы прошли тесты sonobuoy conformance (адаптация под rhel потребует небольших доработок).

Разработанный процесс больше подойдет для разворачивания тестовой среды, именно для этого акцент сделан на скорости, исключен любой шаг, без которого все и так полностью будет функционировать. Для production кластера, как минимум, придется добавить дополнительные отдельные etcd узлы, данный процесс за рамками этой статьи. Однако, etcd узлы на мастер нодах созданы будут в рассматриваемом процессе.

О роли ansible inventory. 

[masters] k8s [master] k8s [etcd] k8s [workers] r01 r02 [jenkins] k8s [grafana] k8s

Давайте сделаем его простым, очень простым. Чтобы это осуществить, настройте dhcp службу вручную или с помощью API своего маршрутизатора. Будет нелишним настроить домен маршрутизатора, просто укажите любой идентификатор, который будет добавлен к именам ваших узлов. Вам потребуется добавить mac адреса сетевых интерфейсов своих узлов в список соответствия IP адресам в вашей подсети. Иными словами, составьте план статических адресов для своих узлов кластера. 


Для виртуальных машин проверьте, чтобы mac адрес сетевого интерфейса был статическим.
Для виртуальных машин проверьте, чтобы mac адрес сетевого интерфейса был статическим.

Пожалуй, осталось только напомнить разослать public ssh ключи вашего хоста ansible на узлы будущего кластера.

Стратегия: выделить из общего процесса установки и настройки кластера асинхронные, независимые друг от друга и описать их в виде ролей ansible. Данные процессы будут выполняться в параллельных stage пайплайна jenkins. 

pipeline {      agent any     options {         parallelsAlwaysFailFast()     }     stages {          stage('Deploy Kubernetes Cluster conforms to kubeadm 1.26 kubernetes.io official'){             parallel {              stage('Apply system requirements'){                    steps {                       sh 'ansible-playbook -i files/hosts init-phd.yaml'                    }              }              stage('Installing containerd container runtime'){                    steps{                      sh 'ansible-playbook -i files/hosts init-phb.yaml'                    }              }              stage('Configuring systemd cgroup driver'){                    steps{                      sh 'ansible-playbook -i files/hosts init-phc.yaml'                    }              }              stage('Installing kubeadm, kubelet and kubectl'){                    steps {                       sh 'ansible-playbook -i files/hosts init-pha.yaml'                    }              }            }         }          stage('Bootstrap cluster with kubeadm'){             steps {                  sh 'ansible-playbook -i files/hosts init-masters.yaml'             }          }     } }

То есть у нас 5 PLAYs: 4 асинхронных и последний PLAY


Загрузка с помощью kubeadm первого мастер узла
Загрузка с помощью kubeadm первого мастер узла

Обратите внимание в ansible.cfg параметр 

[defaults] forks = 50

Ориентируйтесь на общее количество узлов в inventory, асинхронные задачи плейбука выполняют одинаковую работу на всех узлах.

Для повышения производительности, будет показано далее, мастер узлы и рабочие узлы разворачиваются в разных пайплайнах. Это связано с особенностями ansible. При таком подходе можно гораздо быстрее приступить к присоединению рабочих узлов. Не следует раздавать скачанные тарболы и прочие объемные файлы с мастер узла. Там запустится etcd, для него критично время доступа к своей БД.

PLAYs:

  1. Применение системных требований включает удаление ненужных сервисов, установку нужных пакетов, отключение swap

  2. Установка containerd, среды исполнения runc, сетевых интерфейсов

  3. Конфигурирование драйвера cgroup в linux systemd

  4. Установка репозитория и компонентов kubernetes

  5. Запуск kubeadm для инициализации мастер узла

Теперь к материалам официального сайта kubernetes. В версии 1.26:

  • как обычно, указана совместимость версий компонентов

  • введен новый код для ubuntu, всвязи с deprecated apt-key

    Используем принципы простоты для написания ролей и лучшего кода. Когда его, желательно, вообще нет. Для начала структурируем нашу исходную информацию для шага 3 в defaults:

# defaults file for containerd content:     github.com/containerd/containerd/releases/download/v1.6.4/ :        name: containerd-1.6.4-linux-amd64.tar.gz       path: /usr/local       tag:  tar     github.com/containernetworking/plugins/releases/download/v1.1.1/ :        name: cni-plugins-linux-amd64-v1.1.1.tgz       path: /opt/cni/bin       tag:  tar     github.com/opencontainers/runc/releases/download/v1.1.4/ :        name: runc.amd64       path: /usr/local/sbin/runc       tag:  install     raw.githubusercontent.com/containerd/containerd/main/ :        name: containerd.service       path: /usr/local/lib/systemd/system       tag:  service     containerd-file-config.toml:       name: ./roles/containerd/files/config.toml       path: /etc/containerd       tag: file     cni-template-j2file:       name: ./roles/containerd/templates/10-containerd-net.conflist.j2       path: /etc/cni/net.d/10-containerd-net.conflist       tag: j2t     cni-lo-file:       name: ./roles/containerd/files/99-loopback.conf       path: /etc/cni/net.d       tag: file
  • Ссылки на тарболы официально рекомендуемых версий имеют тэг ‘tar’

  • Модуль runc рекомендуется заносить через утилиту install

  • Файл с описанием сервиса containerd с тэгом ‘service’

  • Конфигурационный файл сетевого интерфейса в виде шаблона jinja тэг ‘j2t’

  • Уже готовая конфигурация containerd под systemd и loopback интерфейс для сетей контейнеров с тэгом file

Более половины некоторых плейбуков состоят из рутинных скачиваний архивов, распаковок, скачиваний сервисов, вызовов install, создания каталогов, совершенно одинакового кода перезапуска systemd и service restart+enable, отработки шаблонов и копирования файлов роли. Мои плейбуки никогда не содержат такого кода, хотя и делают тоже самое. Просто все эти действия совершаются разработанной мной ролью ansible на основании тегов. Объявления переменных в defaults для ansible внутри PLAY составляют общее пространство. Роль:

# tasks file for wgi  - name: Wget content   get_url:     url: "https://{{ item.key }}{{ item.value.name }}"     dest: "/tmp/{{ item.value.name }}"     force: false   loop: "{{ content | dict2items |             rejectattr('value.tag', 'search', 'file' ) |             rejectattr('value.tag', 'search', 'j2t' ) |             list }}"   loop_control:      label: "{{ item.key }}"   - block:    - name: Creates directory     file:       path: "{{                item.value.path                   if not (item.value.tag in ['install','j2t'])                      else ( item.value.path | dirname )               }}"       state: directory       owner: root       group: root       mode: 0755     loop: "{{ content | dict2items  }}"     loop_control:       label: "{{ item.key }}"    - name: Copy services content     copy:       src:  "/tmp/{{ item.value.name }}"       dest: "{{ item.value.path }}"       remote_src: yes     register: content_restart     notify:       - reload systemd       - restart systemd     loop: "{{ content | dict2items | selectattr('value.tag', 'search', 'service' ) | list }}"     loop_control:       label: "{{ item.key }}"            - name: Extract archived     unarchive:       src:  "/tmp/{{ item.value.name }}"       dest: "{{ item.value.path }}"       owner: root       group: root       mode:  0755       remote_src: yes     loop: "{{ content | dict2items | selectattr('value.tag', 'search', 'tar' ) | list }}"     loop_control:       label: "{{ item.key }}"    - name: Install module     shell: install -m 755 "/tmp/{{ item.value.name }}" "{{ item.value.path }}"     args:       creates: "{{ item.value.path }}"     loop: "{{ content | dict2items | selectattr('value.tag', 'search', 'install' ) | list }}"     loop_control:       label: "{{ item.key }}"          - name: Copy plain artifacts     copy:       src:  "/tmp/{{ item.value.name }}"       dest: "{{ item.value.path }}"       owner: root       group: root       mode: u=rw,g=r,o=r       remote_src: yes     loop: "{{ content | dict2items | selectattr('value.tag', 'search', 'plain' ) | list }}"     loop_control:       label: "{{ item.key }}"    - name: Copy role files     copy:       src:  "{{ item.value.name }}"       dest: "{{ item.value.path }}"       owner: root       group: root       mode: u=rw,g=r,o=r     loop: "{{ content | dict2items | selectattr('value.tag', 'search', 'file' ) | list }}"     loop_control:       label: "{{ item.key }}"          - name: Copy role templates     template:       src:  "{{ item.value.name }}"       dest: "{{ item.value.path }}"       owner: root       group: root       mode: u=rw,g=r,o=r     loop: "{{ content | dict2items | selectattr('value.tag', 'search', 'j2t' ) | list }}"     loop_control:       label: "{{ item.key }}"    become: yes 

Возможно, нужно пояснить, перезагрузка systemd происходит через notify. Записи для changed сервисов будут в зарегистрированном массиве, на основании которого handler restart systemd перезапустит перечисленные в массиве сервисы и выполнит команду enabled. Handlers описаны в файле данной роли, при необходимости их можно переопределить, ansible так и работает.

Шаблон jinja2 в templates производит вычисление адреса, который будет присвоен сетевому интерфейсу cni.

В общем, tasks/main.yml для роли containerd оказывается совсем без кода. Кода нет, а чувство удовлетворения есть.

Что важного в файле конфигурации config.toml на основании официальных требований:

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]    ...    [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]      SystemdCgroup = true

И

[plugins."io.containerd.grpc.v1.cri"]    sandbox_image = "registry.k8s.io/pause:3.2"

Файл конфигурации по умолчанию, сгенерированный containerd не содержит требуемых параметров.

Разбираем 4 пункт.

Опять поможет вышеприведенная роль. Скачает ключ для подписания репозитория.

content:   packages.cloud.google.com/apt/doc/:     name: apt-key.gpg     path:  /etc/apt/keyrings/     tag: plain   Add-the-Kubernetes-apt-repository:     name: ./roles/k8s-install/files/kubernetes.list     path: /etc/apt/sources.list.d     tag: file

Файл kubernetes.list из официального источника для роли теперь выглядит так: 

deb [signed-by=/etc/apt/keyrings/apt-key.gpg] https://apt.kubernetes.io/ kubernetes-xenial main

Кода опять нет, роль из шага 4 содержит фактически только defaults.

Только как обычно, не забудьте отработать вот это:

net.bridge.bridge-nf-call-iptables  = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward                 = 1

Остальное при установке осталось по-прежнему в шаге 5:

- hosts: master   become: yes   pre_tasks:     - name: initialize the cluster       shell: kubeadm init --pod-network-cidr=10.244.0.0/16       args:         chdir: $HOME         creates: /etc/kubernetes/admin.conf       register: kubeadm      - debug:          var: kubeadm.stdout_lines   roles:     - k8s-copy-admin

Проверка creates позволяет потом присоединить мастер узлы просто указав группу masters, повторно kubeadm не запустится.

Рабочие узлы добавляем как всегда
Рабочие узлы добавляем как всегда

Запуск пайплайна присоединения рабочих узлов можно производить параллельно с запуском мастер узла. Рабочих узлов обычно больше, чем мастер узлов, поэтому общее время выполнения асинхронной части должно быть не менее времени выполнения на самом медленном рабочем узле. Синхронизация запуска kubeadm нужна, осуществляется проверкой наличия сгенерированного файла конфигурации на мастер узле.

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

Больше материалов по данной теме в моем репозитории https://itoracl.github.io/k8s


ссылка на оригинал статьи https://habr.com/ru/post/713398/