Deployment K3S z użyciem Github Actions

Wstęp

Omawiane rozwiązanie można zastosować do dowolnej konfiguracji opisanej w terraformie. Jako przykład posłuży nam K3S.

K3S jest jednym z dostępnych na rynku implementacji kubernetes. Producentem tego rozwiązania jest firma Rancher, która oferuje również inne technologie takie jak Longhorn czy Harvester (Więcej informacji na stronie: Products | Rancher). K3S charakteryzuje się małym rozmiarem dystrybuowanej binarki (40MB). Został zaprojektowany do działania na urządzeniach IoT oraz brzegowych urządzeniach sieciowych. K3S działa na procesorach z rodziny intela (x86) oraz na architkekturze ARM. Jak zapewnia Rancher – działa doskonale na czymś tak małym jak Raspberry Pi oraz na tak dużym jak AWS EC2 a1.4xlarge.

Architektura środowiska

Przygotowanie maszyn wirtualnych

W celu przetestowania opisywanego w tym artykule rozwiązania należy utworzyć trzy lub cztery maszyny wirtualne. Ja do tego celu wykorzystałem popularnego VirtualBoxa. VM-ki nie muszą być duże. W zupełności wystaczy 2G RAM 1 CPU oraz 25GB dysku twardego.

Github Runner

Jedną z kluczowych maszyn jest Github self-hosted Runner. Jest on odpowiedzialny za wykonywanie zadań skonfigurowanych w planie Actions. W naszym przypadku będzie wdrażał manifesty terraforma na dedykowanych pod cluster kubernetes (k3s) maszynach wirtualnych.

Jest to bardzo fajne rozwiązanie, które pozwoli na odseparowanie domeny publicznej (np. generycznych manifestów terraforma) od konfiguracji środowiska prywatnego. Uruchamiając runnera on-premise nie musimy poświęcać czasu na konfigurację prywatnego pipeline’a CI/CD.

Przygotowanie runnera.

Do prawidłowego przetwarzania opisywanego planu runner wymaga zainstalowania pakietów sshpass oraz terraform.

sudo apt update
sudo apt install software-properties-common gnupg2 curl
curl https://apt.releases.hashicorp.com/gpg | gpg --dearmor > hashicorp.gpg
sudo install -o root -g root -m 644 hashicorp.gpg /etc/apt/trusted.gpg.d/
sudo apt-add-repository "deb [arch=$(dpkg --print-architecture)] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
sudo apt install terraform
sudo apt install sshpass

Rejestracja self-hosted runnera w Githubie

Runner działa w kontekście konkretnego repozytorium, zatem należy założyć dedykowane repozytorium do manifestów terraforma. Proces rejestracji jest bardzo prosty.

Logujemy się do repozytorium i nawigujemy do Settings

Następnie rozwijamy sekcję Actions i przechodzimy do Runners

Klikamy na New self-hosted runner

Wybieramy system operacyjny Linux. Na tej samej stronie, w dolnej części pojawią się instrukcje, które krok po kroku pomogą w instalacji i konfiguracji runnera na dedykowanej maszynie wirtualnej.

Istnieje możliwość konfiguracji runnera jako usługi systemowej. Warto to zrobić, aby proces odpowiedzialny, za jego działanie uruchamiał się automatycznie wraz z systemem. Aby to zrobić należy wykonać poniższe polecenia będąc w katalogu: actions-runner.

sudo ./svc.sh install
sudo ./svc.sh start

Więcej instrukcji znajdziecie na stronie: Configuring the self-hosted runner application as a service – GitHub Docs

Konfiguracja planu wdrożeniowego

Skonfigurowany plan oraz manifesty terraforma, które wdrażają opisywane tutaj rozwiązanie znajdują się w repozytorium https://github.com/kkrolikowski/homelab-tf

Definicja workflow

Do przechowywania definicji planów w githubie służy katalog .github/workflows. Należy go utworzyć w repozytorium. Plan omawianego wdrożenia jest zdefiniowany w pliku: .github/workflows/terraform.yml.

Obsługiwane akcje

on:
  push:
    branches: [ "main" ]
  pull_request:

Workflow będzie uruchamiany po wykonaniu pusha do brancha main oraz na każdy pull request. W tym miejscu można skonfigurować sobie bardziej szczegółowe warunki uruchamiania zadań.

jobs:
  terraform:
    name: 'Terraform'
    runs-on: home-runner

W kluczu jobs musimy zdefiniować uruchamianie zadań na self-hosted runnerze, którego dodawanie opisane zostało we wcześniejszej części artykułu. Służy do tego klucz: runs-on. Jego wartością jest nazwa labelki przypisanej do runnera.

# Checkout the repository to the GitHub Actions runner
- name: Checkout
  uses: actions/checkout@v3

# Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc.
- name: Terraform Init
  id: init
  run: terraform init

# Checks that all Terraform configuration files adhere to a canonical format
- name: Terraform Format
  id: fmt
  run: terraform fmt -check

- name: Terraform Validate
  id: validate
  run: terraform validate -no-color

# Generates an execution plan for Terraform
- name: Terraform Plan
  id: plan
  if: github.event_name == 'pull_request'
  run: terraform plan -no-color -input=false -var-file="/home/ubuntu/tfprod/prod.tfvars"
  continue-on-error: true

W dalszej części pliku znajduje się klucz steps, który zawiera definicję poszczególnych kroków do wykonania. Wszystkie wykonywane są lokalnie na vm-ce runnera.

  • Checkout: Pobranie repozytorium
  • Terraform Init: przygotowanie się do wdrożenia. W tym kroku instalowane są wymagane pluginy
  • Terraform Format: Sprawdzenie czy kod terraforma jest sformatowany zgodnie ze standardami. Krok nie jest wymagany, jednak warto go zdefiniować, jeśli chcemy utrzymać jednolity standard.
  • Terraform Validate: Weryfikacja, czy kod terraforma nie zawiera błędów.
  • Terraform Plan: Wygenerowanie planu wdrożenia. Krok wykonywany jest tylko podczas akcji związanych z pull requestem.

Omawiane w artykule wdrożenie opiera się na założeniu, że wrażliwe dane, takie jak hasła czy adresy IP nie są publikowane w repozytorium. Umieszczone zostały w pliku prod.tfvars na vm-ce runnera. Nie jest on częścią repozytorium, dlatego należy go wskazać terraformowi w ten sposób: -var-file="/home/ubuntu/tfprod/prod.tfvars"

Plik prod.tfvars

master_node = "10.0.0.10"
worker_nodes = ["10.0.0.11", "10.0.0.12"]
vm_user = "admin"
vm_pass = "adminSecret"

- name: Update Pull Request
  uses: actions/github-script@v6
  if: github.event_name == 'pull_request'
  env:
    PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
  with:
    github-token: ${{ secrets.GITHUB_TOKEN }}
    script: |
      const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
      #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
      #### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
      #### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
      <details><summary>Show Plan</summary>
      \`\`\`\n
      ${process.env.PLAN}
      \`\`\`
      </details>
      *Pushed by: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`;
      github.rest.issues.createComment({
        issue_number: context.issue.number,
        owner: context.repo.owner,
        repo: context.repo.repo,
        body: output
      })

Jeśli chcemy aby bot githuba opublikował w pull requeście ładnie sformatowany status wdrożenia, możemy użyć tego kroku. Wymaga on Skonfigurowania Workflow permissions

Settings -> Actions -> General

- name: Terraform Apply
  if: github.ref == 'refs/heads/main' && github.event_name == 'push'
  run: terraform apply -auto-approve -input=false -var-file="/home/ubuntu/tfprod/prod.tfvars"

Ostatnim krokiem jest Terraform Apply, który wdraża zdefiniowaną konfigurację. Wykonywany jest na branchu main podczas akcji push.

Podsumowanie

W ten sposób może zostać zdefiniowana i wdrożona infrastruktura. Jest jeszcze kilka elementów, które można poprawić. Np. utrzymanie samego pliku prod.tfvars, który w opisywanym rozwiązaniu należy aktualizować ręcznie na vm-ce z giithub runnerem. Jednym z możliwych rozwiązań mogłoby być utworzenie on-premise prywatnego repozytorium i skonfigurowanie CI/CD, Można też skorzystać z opcji ustawienia repozytorium githuba jako prywatne.