Раскатка 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/


Интересные статьи

Интересные статьи

На Хабре ни для кого не секрет, что в текущей повестке практически все сферы частного бизнеса вынуждены реагировать на происходящие изменения. Большое количество привычных всем нам зарубежных сервисов...
Рано или поздно, каждый пэхапешник, пишущий на битриксе, начинает задумываться о том, как бы его улучшить, чтобы и всякие стандарты можно было соблюдать, и современные инструменты разработки использов...
Хочу поделиться опытом автоматизации экспорта заказов из Aliexpress в несколько CRM. Приведенные примеры написаны на PHP, но библиотеки для работы с Aliexpress есть и для...
Существует традиция, долго и дорого разрабатывать интернет-магазин. :-) Лакировать все детали, придумывать, внедрять и полировать «фишечки» и делать это все до открытия магазина.
Некоторое время назад мне довелось пройти больше десятка собеседований на позицию php-программиста (битрикс). К удивлению, требования в различных организациях отличаются совсем незначительно и...