Mike Logaciuk

Uruchamiamy RKE2 z pomocą Vagrant'a

12 Dec 2022

pic

Wstęp

Aby uruchomić własne RKE2 na potrzeby developerskie przy pomocy Vagranta, generalnie nie potzebujemy doktoratu.

Vagrant to narzędzie do wirtualizacji, które pozwala na tworzenie i zarządzanie wirtualnymi maszynami na lokalnym komputerze. Vagrant wykorzystuje tzw. boxy, czyli gotowe obrazy wirtualnych maszyn, które można pobrać z internetu.

RKE2 to narzędzie do zarządzania klastrami Kubernetes. RKE2 umożliwia instalację i konfigurację klastrów Kubernetes w sposób szybki i łatwy.

Wymagania

Ogólnie rzecz biorąc wystarczy abyśmy mieli zainstalowanego Vagranta (preferowany Linux np. Debian), oraz najlepiej KVM, QEMU oraz stosowne pluginy do Vagranta.

Zapewne jesteś DevOps’em tak więc nad tym elementem rozwodził się nie będę, a w razie potrzeby wszystkie materiały potrzebne do instalacji powyższych znajdziesz w oficjalnej dokumentacji. Wszak, DevOps czy SysOps potrafi szukać czyż nie?

Vagrantfile

Na początku musimy utworzyć folder, w moim przypadku będzie to ~/repos/rke2.

Z racji, że jest to tylko PoC, nie będziemy wystawiać trzech node’ów z racji, że raczej nikt nie wystawia produkcji w Vagrancie.

Pierwsze co, tworzymy folder i plik Vagrantfile

mkdir -p /home/${whoami}/repos/vagrant
cd /home/${whoami}/repos/vagrant
touch Vagrantfile

Uruchamiamy Code’a:

code Vagrantfile

Piszemy IaC

Pliki Vagrantfile zaczynamy od frazy:

Vagrant.configure("2") do |config|

Następnie definiujemy jej obraz oraz adres:

  config.vm.define "rke2" do |machine|
    machine.vm.box = "bento/ubuntu-22.04"
    machine.vm.network "private_network", ip: "192.168.33.189"

Port przekierowujemy w ten sposób:

    # Classic 80/443 ports
    machine.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1"
    machine.vm.network "forwarded_port", guest: 443, host: 8443, host_ip: "127.0.0.1"

    # Kubernetes ports
    machine.vm.network "forwarded_port", guest: 6443, host: 6443, host_ip: "127.0.0.1"
    machine.vm.network "forwarded_port", guest: 2379, host: 2379, host_ip: "127.0.0.1"
    machine.vm.network "forwarded_port", guest: 2380, host: 2380, host_ip: "127.0.0.1"
    machine.vm.network "forwarded_port", guest: 10250, host: 10250, host_ip: "127.0.0.1"
    machine.vm.network "forwarded_port", guest: 10257, host: 10257, host_ip: "127.0.0.1"
    machine.vm.network "forwarded_port", guest: 10259, host: 10259, host_ip: "127.0.0.1"
    (30000...32767).each do |port|
      machine.vm.network "forwarded_port", guest: port, host: port, host_ip: "127.0.0.1"
    end

Z racji, że Vagrant dzieli sie plikami między host’em, a maszyną wirtualną, to by móc skorzystać z tej tajemnej mocy, musimy aktywować synchronizację.

Na potrzeby tego, wymagany będzie na Twoim hoście NFS. No, ale zakładamy, że go masz zainstalowanego.

Do pliku tak więc dodajemy:

    # Folder sync
    machine.vm.synced_folder ".", "/vagrant", type: "nfs", nfs_version: 4, nfs_udp: false

W celu wykonania podstawowego provisioningu maszyny, potrzebujemy jeszcze konfiguracji samej maszyny.

W tym przypadku ustawiamy providera oraz detale maszyny:

    # Libvirt part
    machine.vm.provider :libvirt do |libvirt|
      libvirt.driver = "kvm"
      libvirt.cpu_model = "EPYC-Rome"
      libvirt.cpus = 2
      libvirt.memory = 4096
      libvirt.storage_pool_name = "kvm"
      libvirt.storage :file, :size => '40G'
    end

W przypadku modelu CPU, możemy spróbować użyć jednego z poniższych:

    486
    pentium
    pentium2
    pentium3
    pentiumpro
    coreduo
    n270
    core2duo
    qemu32
    kvm32
    cpu64-rhel5
    cpu64-rhel6
    qemu64
    kvm64
    Conroe
    Penryn
    Nehalem
    Nehalem-IBRS
    Westmere
    Westmere-IBRS
    SandyBridge
    SandyBridge-IBRS
    IvyBridge
    IvyBridge-IBRS
    Haswell-noTSX
    Haswell-noTSX-IBRS
    Haswell
    Haswell-IBRS
    Broadwell-noTSX
    Broadwell-noTSX-IBRS
    Broadwell
    Broadwell-IBRS
    Skylake-Client
    Skylake-Client-IBRS
    Skylake-Client-noTSX-IBRS
    Skylake-Server
    Skylake-Server-IBRS
    Skylake-Server-noTSX-IBRS
    Cascadelake-Server
    Cascadelake-Server-noTSX
    Icelake-Client
    Icelake-Client-noTSX
    Icelake-Server
    Icelake-Server-noTSX
    Cooperlake
    Snowridge
    athlon
    phenom
    Opteron_G1
    Opteron_G2
    Opteron_G3
    Opteron_G4
    Opteron_G5
    EPYC
    EPYC-IBPB
    EPYC-Rome
    EPYC-Milan
    Dhyana

Shell

Dla tych, którzy pracują z Terraformem i image’ami, jasnym jest, że na każdym z obrazów trzeba wykonać cloud-init.

W przypadku Vagrant’a, dysponujemy bardzo prostą składnią, która umożliwia Nam konfigurację VM’a z poziomu shella.

W Naszym przypadku instalujemy pakiety, wcześniej aktualizujemy definicje managera pakietów, instalujemy helm'a oraz kubectl'a:

    machine.vm.provision "shell", inline: <<-SHELL
      apt-get update
      apt-get install apt-transport-https ca-certificates curl btop htop git unzip wget curl -y
      sudo swapoff -a
      swapoff -a && sudo sed -i '/ swap / s/^/#/' /etc/fstab
      sudo snap install helm --classic && sudo snap install kubectl --classic

Następnie instalujemy i inicjalizujemy Naszego ‘PoC’owego Kubernetesa w postaci RKE2:

    machine.vm.provision "shell", inline: <<-SHELL
      apt-get update
      apt-get install apt-transport-https ca-certificates curl btop htop git unzip wget curl -y
      sudo swapoff -a
      swapoff -a && sudo sed -i '/ swap / s/^/#/' /etc/fstab
      sudo snap install helm --classic && sudo snap install kubectl --classic

      # Install RKE2 server
      curl -sfL https://get.rke2.io | INSTALL_RKE2_TYPE="server" sh -

      # Start RKE2 server
      systemctl enable rke2-server.service
      systemctl start rke2-server.service

      # Write the token to a file
      mkdir -p /vagrant
      cat /var/lib/rancher/rke2/server/node-token > /vagrant/node-token

      cp /etc/rancher/rke2/rke2.yaml /vagrant/kubeconfig.yaml

      # Set the KUBECONFIG environment variable for all users
      sudo chmod +r /etc/rancher/rke2/rke2.yaml
      echo "export KUBECONFIG=/etc/rancher/rke2/rke2.yaml" >> /home/vagrant/.bashrc

      echo '1' > /vagrant/node1_ready
    SHELL
  end
end

Testy

Po paru minutach pracy Vagranta, możemy zalogować się do maszyny i wykonać:

$ vagrant ssh && kubectl get pods -A

NAMESPACE     NAME                                                    READY   STATUS      RESTARTS      AGE
kube-system   cloud-controller-manager-vagrant                        1/1     Running     1 (46m ago)   46m
kube-system   etcd-vagrant                                            1/1     Running     0             46m
kube-system   helm-install-rke2-canal-6wmh5                           0/1     Completed   0             46m
kube-system   helm-install-rke2-coredns-xzkg5                         0/1     Completed   0             46m
kube-system   helm-install-rke2-ingress-nginx-htgz5                   0/1     Completed   0             46m
kube-system   helm-install-rke2-metrics-server-vzsfp                  0/1     Completed   0             46m
kube-system   helm-install-rke2-snapshot-controller-crd-4w7fs         0/1     Completed   0             46m
kube-system   helm-install-rke2-snapshot-controller-smkfd             0/1     Completed   1             46m
kube-system   helm-install-rke2-snapshot-validation-webhook-cng55     0/1     Completed   0             46m
kube-system   kube-apiserver-vagrant                                  1/1     Running     0             46m
kube-system   kube-controller-manager-vagrant                         1/1     Running     0             46m
kube-system   kube-proxy-vagrant                                      1/1     Running     0             46m
kube-system   kube-scheduler-vagrant                                  1/1     Running     0             46m
kube-system   rke2-canal-br7cz                                        2/2     Running     0             46m
kube-system   rke2-coredns-rke2-coredns-565dfc7d75-wgz42              1/1     Running     0             46m
kube-system   rke2-coredns-rke2-coredns-autoscaler-6c48c95bf9-jgznp   1/1     Running     0             46m
kube-system   rke2-ingress-nginx-controller-b77vn                     1/1     Running     0             45m
kube-system   rke2-metrics-server-c9c78bd66-sj545                     1/1     Running     0             46m
kube-system   rke2-snapshot-controller-6f7bbb497d-k58l5               1/1     Running     0             46m
kube-system   rke2-snapshot-validation-webhook-65b5675d5c-dpq5h       1/1     Running     0             46m

Rake

Jeżeli chcemy pobawić się bezpośrednio z Naszego host’a, to możemy użyć tego Rakefile, który podmienia Nam konfigurację dla pliku KUBECONFIG:

require 'yaml'
require 'fileutils'

def get_actual_dir
  File.dirname(File.expand_path(__FILE__))
end

def get_user
  %x(whoami).strip
end

def get_node_int_i
  %x('vagrant ssh )
end

username = get_user
kube_config_path = "home/#{username}/.kube/config/"
vagrant_kube_config_path = "#{get_actual_dir}/kubeconfig.yaml"

desc "Clean up old folders"
task :destroy do
  %x("vagrant destroy -f")
  %x("rm -rf .vagrant")
  %x("rm -rf kubeconfig.yaml")
  %x("rm -rf node-token")
  %x("rm -rf node1_ready")
  puts "Cleaning up old folders"
end

desc "Config management"
namespace :config do
  desc "Use RKE2 Kubeconfig"
  task :vagrant do
    file = File.read('kubeconfig.yaml')
    data = YAML.load(file)
    data['clusters'][0]['cluster']['server'] = 'https://192.168.33.189:6443'
    YAML.dump(data, File.open('kubeconfig.yaml', 'w'))

    %x(echo "export KUBECONFIG=#{vagrant_kube_config_path}" >> ~/.bashrc)
    %x(echo "export KUBECONFIG=#{vagrant_kube_config_path}" >> ~/.zshrc)
    puts "Using RKE2 Kubeconfig. Type 'source ~/.bashrc' or '.zshrc' to apply changes."
  end
  desc "Use original Kubeconfig"
  task :original do
    %x(echo "export KUBECONFIG=#{kube_config_path}" >> ~/.bashrc)
    %x(echo "export KUBECONFIG=#{kube_config_path}" >> ~/.zshrc)
    puts "Using original Kubeconfig. Type 'source ~/.bashrc' or '.zshrc' to apply changes."
  end
end

Uwagi

Jak widać po powyższym tekście, Vagrant może stanowić ciekawą alternatywę dla kontenerów z racji jego prostej modularnej budowy oraz możliwości używania Ruby bezpośrednio w konfiguracji maszyn.

Gdyż tak naprawdę, chcąc utworzyć od razu trzy node’y, wystarczy zrobić to tak:

Vagrant.configure("2") do |config|
  (1..3).each do |i|
  # The most common configuration options are documented and commented below.
  # For a complete reference, please see the online documentation at
  # https://docs.vagrantup.com.

  # Every Vagrant development environment requires a box. You can search for
  # boxes at https://vagrantcloud.com/search.

    localhost = "127.0.0.1"

    # Provision three RKE2 nodes (1 server, 2 agents)
    config.vm.define "rke2-node-#{i}" do |node|
      node.vm.box = "bento/ubuntu-22.04"
      node.vm.network "private_network", ip: "192.168.33.10#{i}"

    (...)

        if i == 1
        node.vm.provision "shell", inline: <<-SHELL

          # Install RKE2 server
          curl -sfL https://get.rke2.io | INSTALL_RKE2_TYPE="server" sh -

          # Start RKE2 server
          systemctl enable rke2-server.service
          systemctl start rke2-server.service

          # Write the token to a file
          mkdir -p /vagrant
          cat /var/lib/rancher/rke2/server/node-token > /vagrant/node-token

          cp /etc/rancher/rke2/rke2.yaml /vagrant/kubeconfig.yaml

          (...)
        end

    end
end

Podsumowanie

Zalety środowiska developerskiego RKE2 z wykorzystaniem Vagrant’a, w mej opinii są następujące:

  • Łatwość konfiguracji: Wystawienie PoC’owego / developerskiego RKE2 przy pomocy Vagranta jest banalnie proste. W przypadku 3 node’owych maszyn, trzeba trochę pokombinować jak przekazać z automatu tokeny. Niemniej jednak, wystawianie 3 node’ów na potrzeby developerskie jest delikatnym overkill’em. No chyba, że planujemy przećwiczyć upgrade’y do nowszych wersji Kubernetes. Czy sprawdzić jak zachować się klaster gdy ‘popsujemy’ jeden z node’ów.
  • Elastyczność: Vagrant umożliwia wybór dowolnych boxów, które można wykorzystać do utworzenia wirtualnych maszyn. Począwszy od Windowsów po niezliczoną ilość Linuxów. Choć prym wiodą dystrybucje Ubuntu, Debiana. Natomiast własna instancja RKE2 umożliwia dostosowanie konfiguracji klastra Kubernetes do własnych potrzeb w bezpiecznym wyizolowanym środowisku bez szkody dla produkcji.
  • Oszczędność kosztów: Vagrant i RKE2 pozwalają na tworzenie i zarządzanie klastrami Kubernetes na lokalnym komputerze, daje to Nam napewno oszczędność kosztów związanych np. z infrastrukturą chmurową.

Jak widać utworzenie własnego klastra nie jest trudne. Oczywiście jest to nadal tylko instancja developerska. Raczej rzadkim jest praktyka wystawiania maszyny QEMU z poziomu Vagrant’a na produkcji. Z reguły, jeżeli ktoś używa kombinacji QEMU/KVM - to jako narzędzie infrastruktury jako kodu wybiera raczej Terraforma.

Dodatkowo, instancje produkcyjne wymagają znacznie większej uwagi i konfiguracji. Począwszy od firewall’a (najlepiej software’owego oraz sprzętowego), po cały aspekt certyfikatów, doboru ingress’ów czy konfiguracji storage’u etc.