Разворачиваем k8s локально

0
(0)

У меня есть небольшая ansible роль для развертывания k8s тут. Сразу оговорюсь, что данная роль не подходит для развертывания «production ready» кластеров, как максимум ее можно использовать для dev контуров каких-нибудь «пилотных» проектов, но лучше не надо. Основная задумка роли иметь минимально рабочий вариант полноценного кластера для локальных тестов всякого. Для «продакшен» решений есть например «kubespray».

Итак, в этой статье будет рассмотрена минимальная инсталяция кластера из одного мастера, двух рабочих нод и одной ноды под ingress. Естественно сама реализация роли позволяет вам добавить сколько угодно много мастеров и рабочих нод. Но надо учитывать, что если мы хотим видеть мульти мастер, то для этого нужно реализовывать что-то вроде балансера в виде haproxy. У меня есть похожая, но более расширенная версия данной роли тут.

На этом вступительная часть закончена, давайте писать Vagrantfile:

Ruby
servers=[
  {:hostname => "k8s-master",:ip => "192.168.10.27",:ssh_port => 2222},
  {:hostname => "k8s-worker1",:ip => "192.168.10.26",:ssh_port => 2223},
  {:hostname => "k8s-worker2",:ip => "192.168.10.25",:ssh_port => 2224},
  {:hostname => "k8s-ingress1",:ip => "192.168.10.24",:ssh_port => 2226}
]

#last_vm = servers[(servers.length) -1][:hostname]

Vagrant.configure("2") do |config|
  config.vm.provider "virtualbox" do |vb|
    vb.gui = false
    vb.memory = 1024
    vb.cpus = 1
    vb.check_guest_additions = false
    config.vm.box_check_update = false
    config.vm.box = "bento/ubuntu-22.04"
  end

  servers.each do |machine|
    config.vm.define machine[:hostname] do |node|
      node.vm.hostname = machine[:hostname]
      node.vm.network "public_network", ip: machine[:ip], bridge: "wlo1"
      node.vm.network "forwarded_port", id: "ssh", host: machine[:ssh_port], guest: 22


      if machine[:hostname] == "k8s-master"
        node.vm.provider "virtualbox" do |vb|
          vb.memory = 2048
        end
      end
    end
  end
end

Тут мы определяем массив servers, задаем дефолтные параметры для ВМ и для хоста k8s-master их переопределяем.

Переменная last_vm тут не используется, но она нам может понадобится для запуска ansible provision например после создания всех машин. Возможно об этом будет в следующих статьях.

Собственно как выглядит структура проекта?

В корне лежит Vagrantfile, там же лежит ansible.cfg. Запускать playbook мы будем из корня проекта, следовательно ansible.cfg будет использоваться из него же автоматически.

Давайте его рассмотрим:

Plaintext
[defaults]
host_key_checking = false
inventory = ./ansible/hosts.yaml
roles_path = ./ansible/roles
display_skipped_hosts = no
timeout = 200
pipelining = true
forks = 10

host_key_checking = false — Этот параметр отключает проверку ключей хостов.

SSH использует ключи хостов для проверки подлинности удаленных серверов. Это сделано для предотвращения атак типа «Man-in-the-Middle» (MITM), когда злоумышленник может перехватить SSH-соединение между клиентом и сервером и попытаться подменить удаленный сервер.

Когда вы впервые подключаетесь к удаленному серверу по SSH, сервер генерирует ключ хоста и отправляет его вашему клиенту SSH. Ваш клиент SSH сохраняет этот ключ в файле, известном как known_hosts.

inventory и roles_path — путь до файла hosts и ролей соответственно.

display_skipped_hosts — убираем информацию из вывода ansible о пропущенных хостах.

timeout — время ожидания на установления ssh соединения.

pipelining — Активирует режим «pipelining», который позволяет выполнять модули на удаленных хостах без необходимости создания временных файлов. Это может ускорить выполнение задач.

forks — этот параметр задает максимальное кол-во одновременно выполняем задач.

Заполняем hosts.yaml

YAML
kubernetes:
  children:
    init-master:
      hosts:
        k8s-master:
          ansible_host: 127.0.0.1
          ansible_port: 2222
          ansible_user: vagrant
          ansible_ssh_private_key_file: /srv/vagrant/k8s/.vagrant/machines/k8s-master/virtualbox/private_key
    master:
      hosts: {}  
    node:
      hosts:
        k8s-worker1:
          ansible_host: 127.0.0.1
          ansible_port: 2223
          ansible_user: vagrant
          ansible_ssh_private_key_file: /srv/vagrant/k8s/.vagrant/machines/k8s-worker1/virtualbox/private_key  
        k8s-worker2:
          ansible_host: 127.0.0.1
          ansible_port: 2224
          ansible_user: vagrant
          ansible_ssh_private_key_file: /srv/vagrant/k8s/.vagrant/machines/k8s-worker2/virtualbox/private_key 
    ingress:
      hosts:
        k8s-ingress1:
          ansible_host: 127.0.0.1
          ansible_port: 2226
          ansible_user: vagrant
          ansible_ssh_private_key_file: /srv/vagrant/k8s/.vagrant/machines/k8s-ingress1/virtualbox/private_key

Собственно набор наших нод для группы kubernetes. Параметры для ssh соединения можно посмотреть после создания машин так:

Bash
vagrant ssh-config

На самом деле, все что нам отсюда нужно, это путь до приватного ssh ключа. Хост у vagrant будет всегда 127.0.0.1, порты мы задали с вами в Vagrantfile, а пользователь также всегда vagrant.

Переменные в playbook/vars

YAML
first_master: "k8s-master"
first_master_ip: "192.168.10.27"
pod_network: "10.244.0.0/16"
control_plane_endpoint: "192.168.10.27:6443"
kubeadm_init_log_dir: "/var/log"
kubeadm_init_log: "kubeadm_init.log"
dashboard: "https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml"
deploy_test_application: "no"
test_application_ns: "sock-shop"
manifest_dir: "/tmp"
local_kubeconfig: "/srv/local_k8s.conf"
nodelocaldns:
  domain: "cluster.local"
  localdns: "169.254.25.10"
network_plugin:
  calico: "false"
  flannel: "true"
flannel_interface: "eth1"

first_master — всегда равно имени хоста из inventory файла. На этом строится логика выполнения определенных задач, которые следует выполнять только на этом хосте.

first_master_ip — IP адрес нашего мастера. Используется для инициализации через kubeadm и нужен в контексте vagrant, так как мы завели машины в «публичной» хостовой сети. По умолчанию все машины созданные в vagrant идут за NAT.

pod_network — это под сеть для наших подов. Значение этой переменной попадает в соответствующий конфиг сетевого плагина.

control_plane_endpoint — адрес API сервера.

kubeadm_init_log_dir и kubeadm_init_log — это переменные образующие путь до файла с логом инициализации кластера. Позже мы будем использовать этот файл для генерации динамических переменных и передачи их между разными tasks.

dashboard — манифест для kubernetes-dashboard.

deploy_test_application — включает или отключает деплой после установки кластера тестового приложения «Магазин носков».

test_application_ns — namespace для тестового приложения.

manifest_dir — в эту директорию на удаленном хосте будут скопированы некоторые служебные манифесты.

local_kubeconfig — сюда после установки кластера будет скопирован kubeconfig с удаленной машины на наш хост.

nodelocaldns — адреса для nodelocaldns.

network_plugin — возможность выбора из двух сетевых плагинов. Flannel и Calico. Роль протестирована по большей части только с flannel.

flannel_interface — по умолчанию сетевой плагин захочет работать через дефолт интерфейс, который у нас является NAT. Тут мы определяем наш.

Роутинг tasks:

YAML
---
- import_tasks: pre_install.yaml
  tags: pre_install
- import_tasks: install_requirements.yaml
  tags: requirements
- import_tasks: post_install.yaml
  tags: post_install
- import_tasks: init_master.yaml
  when: inventory_hostname == "{{ first_master }}"
  tags: init_master
- import_tasks: get_dynamic_vars.yaml
  when: inventory_hostname == "{{ first_master }}"
  tags: get_vars
- import_tasks: join_master.yaml
  when: groups['master'] is defined and inventory_hostname in groups['master']
  tags: join_master
- import_tasks: join_worker.yaml
  when: groups['node'] is defined and inventory_hostname in groups['node']
  tags: join_node
- import_tasks: join_worker.yaml
  when: groups['ingress'] is defined and inventory_hostname in groups['ingress']
  tags: join_ingress
- import_tasks: labels_node.yaml
  when: inventory_hostname == "{{ first_master }}"
  tags: labels_node
- import_tasks: install_nodelocaldns.yaml
  when: inventory_hostname == "{{ first_master }}"
- import_tasks: deploy_test_application.yaml
  when: inventory_hostname == "{{ first_master }}" and deploy_test_application == "yes"
  tags: test_deploy
- import_tasks: dashboard_token.yaml
  when: inventory_hostname == "{{ first_master }}"
  tags: show_token
- import_tasks: copy_admin_config.yaml
  when: inventory_hostname == "{{ first_master }}"
  tags: copy_k8s_config

Здесь мы подключаем наши таски и по-сути «роутим» их выполнения. Каждый отдельный task рассмотрен не будет, здесь я просто опишу что они делают.

import_tasks: pre_install

Первым делом отключаем swap а также отключаем его в файле fstab.

import_tasks: install_requirements

Добавляем нужные репозитории для docker, kubernetes, устанавливаем все зависимости. Включаем модули ядра : overlay, br_netfilter.

import_tasks: post_install

Конфигурируем ядро, включая нужное для работы сети. Заливаем KUBELET_EXTRA_ARGS в виде файла. Загружаем confg.tomp для containerd, а также crirctl.yaml. Включаем containerd, kubelet.

crictl нам нужен, так как у нас не будет привычного docker, у нас сразу идет containerd в качестве CRI (CONTAINER RUNTIME INTERFACE). Для взаимодействия с ним мы используем утилиту crictl, по сути команды такие же как и у docker.

import_tasks: init_master

inventory_hostname == «{{ first_master }}» — выполняем этот task только если встречаем имя k8s-master в hosts.yaml, т.е. на других хостах это выполнено не будет.

Здесь мы регистрируем переменную с кол-вом CPU нашей ноды. Это нужно, так как по умолчанию kubeadm при инициализации кластера будет писать ошибку, если у нас всего 1 ядро. Я же не имея много ресурсов хочу таки иметь мастер с одним ядром, поэтому в таске обрабатываю это и разрешаю kubeadm проводить инциализацию.

Лог файл инициализации записывается в kubeadm_init_log_dir и kubeadm_init_log, далее копируем манифесты, применяем манифест сетевого плагина и kubernetes-dashboard, применяем манифесты для создания «аккаунта» в дашборде.

import_tasks: get_dynamic_vars

Тут мы по сути записываем вывод разных shell команд в переменные ansible и на основе их генерируем динамический hosts. Это нужно чтобы потом значения из него получить в другой таске. Внутри собственно будут значения для присоединения других нод к кластеру.

Сама таска генерации динамических хостов довольно простая:

YAML
- name: Add token, hash and certificate vars
  add_host:
    name:   "k8s_join"
    token:  "{{ token.stdout }}"
    hash:   "{{ hash.stdout }}"
    certificate: "{{ certificate.stdout }}"

import_tasks: join_master

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

YAML
when: groups['master'] is defined and inventory_hostname in groups['master']

Т.е. проверяем, что группа master определена и имя хоста находится в этой группе. Именно поэтому мы разделили в hosts.yaml наш мастер инициализации и остальные мастера.

Далее выполняем команду подлкючения к кластеру на основе полученных динамических хостов в предыдущей таске:

YAML
kubeadm join "{{ control_plane_endpoint }}" --token "{{ hostvars['k8s_join']['token'] }}" \
    --discovery-token-ca-cert-hash sha256:"{{ hostvars['k8s_join']['hash'] }}" \
    --control-plane --certificate-key "{{ hostvars['k8s_join']['certificate'] }}" \
    --apiserver-advertise-address="{{ ansible_eth1.ipv4.address }}" \
    --cri-socket=/var/run/"{{ container_runtime }}"/"{{ container_runtime }}".sock

import_tasks: join_worker

Тоже самое что и для master, только меняется условие для воркеров

YAML
when: groups['node'] is defined and inventory_hostname in groups['node']

И для ingress

YAML
when: groups['ingress'] is defined and inventory_hostname in groups['ingress']

import_tasks: labels_node

Помечаем ноды в соответствии с их ролью.

import_tasks: install_nodelocaldns

Устанавливаем nodelocaldns — это по сути кеширующий ДНС сервис для кубера. Призван ускорить работу с ДНС именами.

import_tasks: deploy_test_application

Устанавливаем тествое приложение если нужно.

import_tasks: dashboard_token

Для kubernetes-dashboard в дефолтном варианте нужно авторизовываться либо через kubeconfig либо через токен. Этот task в конце play выведет тот самый токен для авторизации.

import_tasks: copy_admin_config

Копирует admin.conf с удаленного хоста на наш локальный.

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

Как развернуть кластер?

Bash
vagrant up
ansible-playbook ansible/playbook/kubernetes.yaml

Как подключить новую ноду?

Уже существующие в hosts.yaml нужно заккоментировать.

Запустить следующую команду:

Bash
ansible-playbook ansible/playbook/kubernetes.yaml -tags="get_vars,join_node,labels_node"

После установки кластера для удобства, можно прописать в .bashrc, .zhsrc или любой другой «rc» вашей оболочки alias

Bash
alias k_local='kubectl --kubeconfig /srv/local_k8s.conf'

Проверяем ноды

Bash
k_local get nodes

Как попасть в UI?

Тут есть много способов, например через IDE Lens, терминал k9s или тот самый дашборд, который мы установили. Так как у нас пока нет ingress (о них будет в следующих статьях), то нам, чтобы «достучаться» до приложения kubernetes-dashboard нужно пробросить порты из кластера на локальный комп.

Bash
k_local -n kubernetes-dashboard port-forward svc/kubernetes-dashboard 8443:443

Терминал с этой командой не закрываем, а открываем браузер на https://localhost:8443

Мы должны увидеть:

Получаем токен ansible-ом:

Bash
ansible-playbook ansible/playbook/kubernetes.yaml --tags show_token

Насколько статья полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка 0 / 5. Количество оценок: 0

Оценок пока нет. Поставьте оценку первым.

Оставить комментарий