angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Moet ik mijn Docker Swarm migreren naar Kubernetes?

Ik heb een Docker-Compose project gemigreerd naar Kubernetes en heb besloten om voor Kubernetes te gaan.

15 september 2023
post main image
https://www.pexels.com/nl-nl/@fotios-photos

Als je berichten op het internet leest waarin staat dat Docker Swarm dood is, word je bang. Ik heb een Docker Swarm draaien en ik vind het leuk, het is makkelijk als je al Docker gebruikt.

Wat zijn de alternatieven? We hebben de hele tijd gelezen dat er maar één ding te doen is en dat is migreren naar Kubernetes en al het andere vergeten.

Ik was halverwege mijn migratie van Docker naar Docker Swarm en wilde weten of ik door moest gaan, of mijn focus moest verleggen naar Kubernetes.

Misschien zou de meest logische volgende stap zijn om Docker Desktop te gebruiken, omdat deze Kubernetes bevat. Ik gebruik Docker Desktop echter niet, ik gebruik Docker Swarm ook op mijn ontwikkelmachine en het werkt perfect.

Terug naar Kubernetes. Al mijn applicaties draaien op Ubuntu en Debian en na wat meer lezen leek Microk8s een goede keuze, je kunt het gebruiken voor ontwikkeling en productie. Dus laten we aannemen dat we overstappen op Microk8s, wat moet er dan gebeuren om te migreren? Ik heb een van mijn huidige Docker-Compose projecten gebruikt om daar achter te komen.

Deze post gaat vooral over alle dingen die ik moest doen om het resultaat te bereiken, ik laat niet alle detail's van het Docker-Compose project zien.

Zoals altijd gebruik ik Ubuntu 22.04.

Mijn toepassing

Mijn applicatie bestaat uit meerdere blokken. Elk blok bestaat uit een of meer Docker-Compose projecten. Op dit moment worden alleen de Task Runners beheerd met Docker Swarm. Ze maken verbinding met de Backend via een Ingress overlay-netwerk.

   +----------+   +---------+         +-------------+
 --| Frontend |---| Backend |----+----| Task Runner |   
   +----------+   +---------+    |    +-------------+
                                 |
                                 |    +-------------+
                   Ingress ->    +----| Task Runner |   
                   overlay       |    +-------------+
                   network       //
                                 |    +-------------+
                                 +----| Task Runner |   
                                      +-------------+

          manage with              manage with
   |<--- Docker-Compose --->|<---- Docker Swarm ---->|
                                        v
                                      migrate
                                        v
                                   manage with
                            |<----- Kubernetes ---->|

De Task Runner is een Docker-Compose project met vijf services:

rabbitmq
task
local-web
unbound-cloudflare
unbound-quad9

local-web' is een licht aangepaste Nginx afbeelding, die een aangepaste webpagina teruggeeft.

                          +-------------+
                          |  local-web  |   
                          +-------------+
                                 ^
                                 |
     +----------+       +-----------------+
     |          |       |                 |-+
     |          |------>|                 | |-+ 
 <-->| rabbitmq |       |      task       | | |
     |          |<------|                 | | |
     |          |       |                 | | |
     +----------+       +-----------------+ | |
                              |     | ------+ |
                              |     | --------+
     +--------------------+   |     |
     | unbound-cloudflare |<--+     |
     +--------------------+         |
                                    |
     +--------------------+         |
     | unbound-quad9      |<--------+
     +--------------------+

De rabbitmq-service maakt verbinding met de Backend. Met Docker Swarm maak ik replica's van de task-service.

Hier is een gestripte versie van het Docker-Compose projectbestand:

# docker-compose.yml 

services:
  rabbitmq:
    ...
    networks:
      task-runner-network
  task:
    ...
  local-web:
    ...
  unboud-cloudflare:
    ...

networks:
  task-runner-network:
    external: true

Alleen rabbitmq is verbonden met een extern netwerk. In deze post ga ik een Task Runner draaien op Kubernetes, met de 'task' service gerepliceerd.

Vragen en antwoorden

Voordat ik aan het werk ging, heb ik een lijst met vragen gemaakt en geprobeerd antwoorden te krijgen. Hier zijn ze:

Is een Pod hetzelfde als een Docker Container?

Nee, dat is het niet. Uit de documentatie: Een Pod is de kleinste inzetbare rekeneenheid die je kunt maken en beheren in Kubernetes. Er kunnen meerdere containers in een Pod zitten. Een Pod biedt een omgeving voor containers om te draaien en bronnen, opslag, netwerk en communicatie tussen processen te delen. Ze delen dezelfde netwerknaamruimte en kunnen met elkaar communiceren met behulp van localhost.

Kortom, tenzij je iets speciaals doet, moet je één Pod per container gebruiken.

Pods communiceren via services

Een container in een Pod communiceert met een andere Pod door zijn IP address te gebruiken. Maar dit is niet de juiste manier. Over het algemeen maken we voor elke Pod een Service aan en benaderen we de Pod via de Service. We verwijzen naar een service via de naam, we kunnen hier ook poorten toewijzen.

Als we een Pod met gerepliceerde containers hebben, zal de Service dist verzoeken over deze containers verdelen. In dit geval fungeert de service ook als load balancer. Dit lijkt veel op Docker Swarm.

Wanneer gebruik je Deployments?

Een Deployment wordt gebruikt om een Pod te beheren. Met een Deployment kun je Pod replicas, Pod plaatsing op nodes, hoe nieuwe updates worden uitgebracht specificeren. Dit betekent dat je (bijna) altijd een Deployment nodig hebt voor een Pod. Omdat we een Pod kunnen specificeren binnen een Deployment, hebben we geen aparte YAML bestanden nodig voor de Pods!

Hoe log je Pods ?

De Pods loggen niet. Het zijn de containers die logs genereren. Dit verschilt niet van het loggen van Docker . Op mijn systeem staan de logbestanden in:

/var/log/pods

Kunnen we een map op de host toewijzen aan een Pod zoals Docker Volumes?

Ja, hiervoor kunnen we de richtlijn 'hostPath' gebruiken.

Is Kubernetes alleen voor productie of kunnen we deze ook voor ontwikkeling gebruiken?

Microk8s kan worden gebruikt voor ontwikkeling en productie. Maar voor productie kunnen we ook iets anders gebruiken, zoals een Kubernetes variant van een cloud provider. Op mijn ontwikkelmachine gebruik ik Microk8s naast Docker zonder problemen.

In Docker Swarm hebben we Task.Slot, wat is het equivalent in Kubernetes?

Niet precies hetzelfde maar wel vergelijkbaar is de Kubernetes 'StatefulSet'. Deze heb ik nog niet geprobeerd.

Kunnen we een Docker Swarm netwerk koppelen aan een Kubernetes Pod (container)?

De reden waarom ik dit zou willen is migratie. Natuurlijk kunnen we altijd onze eigen loadbalancer proxy bouwen, maar is er een eenvoudige/standaard manier om dit te doen? Op dit moment maak ik een 'nodePort'-service aan. Hiermee wordt de poort van een service in Kubernetes gekoppeld aan een poort op de host.

Controleer en corrigeer objectnamen vóór de conversie

Ik had al deze mooie namen in mijn Docker-Compose bestanden en ze gebruikten underscores ('_'). In Kubernetes moeten objectnamen voldoen aan RFC 1123, wat betekent:

  • Max 63 tekens.
  • Moet beginnen en eindigen met een kleine letter of cijfer.
  • Moeten kleine letters, cijfers en koppeltekens bevatten.

Voordat ik iets deed, veranderde ik dit in mijn Docker-Compose project.

Kompose: Het Docker-Compose projectbestand converteren

Laten we nu proberen de Task Runner Docker-Compose bestanden te converteren naar iets Kubernetes, met behulp van Kompose. Kompose is een conversieprogramma voor Docker Compose naar Kubernetes. En we lopen meteen tegen problemen aan. Kompose bleek niet overweg te kunnen met het bestand '.env', dat vrij essentieel is. Ook werd er (als gevolg hiervan?) geen ConfigMap gegenereerd. Hiervoor is een probleem gemeld.

Ik zal een van de komende weken kijken of er een nieuwe versie is, maar voor nu vervangen we de omgevingsvariabelen zelf met Docker-Compose 'config':

> docker-compose -f docker_compose_shared_file.yml -f docker_compose_deployment_file.yml config > docker-compose-config.yml

En dan Kompose uitvoeren. We gebruiken hier de optie hostPath omdat ik volumes gebruik die lokale systeemmappen gebruiken:

> kompose -v convert -f docker-compose-config.yml --volumes hostPath

Kompose heeft de volgende bestanden aangemaakt:

local-web-pod.yaml
rabbitmq-pod.yaml
rabbitmq-service.yaml
task-pod.yaml
unbound-cloudflare-pod.yaml
unbound-quad9-pod.yaml

Voor elke Docker-Compose service is een '-pod.yaml' bestand aangemaakt, en slechts voor één een '-service.yaml' bestand. Ik heb ook voorbeelden gezien waarbij Kompose Deployments genereerde in plaats van Pods. Waarom? Voor mij zijn de Kompose resultaten slechts een startpunt, ik moet Deployment YAML bestanden maken van de Pod YAML bestanden en ook Service YAML bestanden toevoegen. Waarschijnlijk kun je betere resultaten krijgen met Kompose (door instructies toe te voegen aan het bestand 'docker-compose.yml'), maar ik heb momenteel geen tijd om te experimenteren.

Microk8s installeren

Laten we nu onze handen vuil maken. Ik zit op Ubuntu 22.04 dus dit is makkelijk, we gebruiken Microk8s om de laatste stabiele release te krijgen:

> sudo snap install microk8s --classic

Laten we eens kijken wat is ingeschakeld:

> microk8s status

Resultaat:

microk8s is running
high-availability: no
  datastore master nodes: 127.0.0.1:19001
  datastore standby nodes: none
addons:
  enabled:
    dns                  # (core) CoreDNS
    ha-cluster           # (core) Configure high availability on the current node
    helm                 # (core) Helm - the package manager for Kubernetes
    helm3                # (core) Helm 3 - the package manager for Kubernetes
  disabled:
    cert-manager         # (core) Cloud native certificate management
    community            # (core) The community addons repository
    dashboard            # (core) The Kubernetes dashboard
    gpu                  # (core) Automatic enablement of Nvidia CUDA
    host-access          # (core) Allow Pods connecting to Host services smoothly
    hostpath-storage     # (core) Storage class; allocates storage from host directory
    ingress              # (core) Ingress controller for external access
    kube-ovn             # (core) An advanced network fabric for Kubernetes
    mayastor             # (core) OpenEBS MayaStor
    metallb              # (core) Loadbalancer for your Kubernetes cluster
    metrics-server       # (core) K8s Metrics Server for API access to service metrics
    minio                # (core) MinIO object storage
    observability        # (core) A lightweight observability stack for logs, traces and metrics
    prometheus           # (core) Prometheus operator for monitoring and logging
    rbac                 # (core) Role-Based Access Control for authorisation
    registry             # (core) Private image registry exposed on localhost:32000
    storage              # (core) Alias to hostpath-storage add-on, deprecated

Hostpath-storage inschakelen

Sommige services in mijn docker-compose.yml bestand gebruiken persistente gegevens in directories op de localhost. Schakel opslagdiensten in Microk8s in/uit:

> microk8s.enable hostpath-storage

Kubernetes dashboard

Microk8s bevat het Kubernetes dashboard. Schakel het eerst in:

> microk8s enable dashboard

Er zijn verschillende opties om het te starten, deze is eenvoudig:

> microk8s dashboard-proxy

Navigeer vervolgens in uw browser naar:

https://127.0.0.1:10443

Accepteer het zelfondertekende certificaat, copy-paste het token van de terminal en je bent binnen.

Stoppen en starten

Microk8s stoppen en starten:

> microk8s stop
> microk8s start

kubectl gebruiken

Kubectl is een opdrachtprompt dat gebruikt wordt om opdrachten uit te voeren om Kubernetes clusters te beheren.

Gebruik 'kubectl' in plaats van 'microk8s kubectl':

> alias kubectl='microk8s kubectl'

Om dit permanent te maken, voeg je dit toe aan je 'bashrc' bestand:

> echo "alias kubectl='microk8s kubectl'" >> ~/.bashrc

Maak veel aliassen, anders worden je vingers moe!

Hulp krijgen over bijvoorbeeld de poorten van een container in een Pod:

> kubectl explain pod.spec.containers.ports

Om Kubernetes objecten aan te maken en bij te werken kunnen we een declaratieve syntaxis gebruiken ('apply'), of een imperatieve ('create').

Hier zijn enkele commando's:

> kubectl get nodes
> kubectl apply -f <manifest file>
> kubectl get pods -o wide
> kubectl get deployments
> kubectl get services
> kubectl delete pods local-web
> kubectl describe pods local-web
> kubectl logs local-web

Om de laatste gebeurtenissen te krijgen:

> kubectl get events

En, om alles te krijgen:

> kubectl get all -A

Onthoud dat een Deployment een ReplicaSet specificatie en een Pod specificatie bevat, wat betekent dat als je een Deployment maakt, je een of meer Pods krijgt, afhankelijk van de replica's die je hebt opgegeven.

Een Pod implementeren

Kunnen we een van de Pods implementeren die door Kompose is gemaakt? Kompose heeft het volgende bestand voor ons gemaakt:

local-web-pod.yaml

Zonder dit bestand te bewerken proberen we deze Pod te deployen:

> kubectl apply -f local-web-pod.yaml

Resultaat:

pod/local-web created

Is de Pod er?

> kubectl get pods

Resultaat:

NAME        READY   STATUS    RESTARTS   AGE
local-web   0/1     Pending   0          119s

Het is er, maar de status is 'Pending'. Laten we de logboeken controleren:

> kubectl logs --timestamps local-web

Geen logs, er wordt niets geretourneerd. Laten we de gebeurtenissen controleren:

> kubectl get events

Resultaat:

LAST SEEN   TYPE      REASON                OBJECT          MESSAGE
9m31s       Warning   FailedScheduling      pod/local-web   0/1 nodes are available: persistentvolumeclaim "local-web-claim0" not found. preemption: 0/1 nodes are available: 1 No preemption victims found for incoming pod..
101s        Warning   FailedScheduling      pod/local-web   0/1 nodes are available: 1 node(s) didn't match Pod's node affinity/selector. preemption: 0/1 nodes are available: 1 Preemption is not helpful for scheduling..

Het bleek dat 'nodeAffinity' de waarde had van het Docker-Compose bestand 'deploy - node.hostname'. Ik moest dit natuurlijk veranderen in de naam van de host die ik op dit moment gebruik ... :-(

Een privé (image) register toevoegen

Pod verwijderen, opnieuw toepassen en status opvragen:

> kubectl delete pod local-web
> kubectl apply -f local-web-pod.yaml
> kubectl get pods

Resultaat:

NAME        READY   STATUS             RESTARTS   AGE
local-web   0/1     ErrImagePull       0          76s

Ik gebruik al een privé register voor Docker . Om dit in Kubernetes te gebruiken, moeten we authenticatie voor dit register toevoegen.

Controleer of er een registry-credential item aanwezig is:

> kubectl get secret registry-credential --output=yaml

Resultaat:

Error from server (NotFound): secrets "registry-credential" not found

Hier gebruik ik de informatie die al aanwezig is in Docker. Maak de registry-credential:

> kubectl create secret generic registry-credential \
    --from-file=.dockerconfigjson=/home/peter/.docker/config.json \
    --type=kubernetes.io/dockerconfigjson

Resultaat:

secret/registry-credential created

Om het te controleren:

> kubectl get secret registry-credential --output=yaml

We pushen eerst de image naar het register. Vervolgens bewerken we de Pod bestanden en voegen we de verwijzing naar de registergegevens toe:

  imagePullSecrets:
    - name: registry-credential

Nu verwijderen we de Pod en passen deze opnieuw toe:

> kubectl get pods

Resultaat:

NAME        READY   STATUS    RESTARTS   AGE
local-web   1/1     Running   0          9s

Eindelijk draait het!

Open de container Pod

Ga de container 'local-web' binnen:

> kubectl exec -it local-web -- /bin/bash

Nu kunnen we de omgevingsvariabelen, mounts, enz. controleren.

Misschien wil je de container ook als root betreden. Mijn Microk8s gebruikt 'runc' (Open Container Initiative runtime) om toegang te krijgen tot containers. Vraag de container-ID op:

> kubectl describe pod <your pod> | grep containerd

Dit zal iets uitvoeren als:

Container ID: containerd://6a060ba8436f575b86b4f3fe10a373125aaf7c125af835180d792f5382836355

Voer dan als root uit in de container:

> sudo runc --root /run/containerd/runc/k8s.io/ exec -t -u 0 6a060ba8436f575b86b4f3fe10a373125aaf7c125af835180d792f5382836355 sh

Pod benaderen via een service

Na het stoppen en starten van een Pod, kan een nieuwe IP address worden toegewezen aan een Pod. Daarom gebruiken we een service om toegang te krijgen tot een Pod, en gebruiken we de naam van de service.

Door een service te maken voor een Pod kunnen we toegang krijgen tot de Pod door de naam te gebruiken in plaats van IP address. Hiervoor hebben we de Kubernetes cluster DNS nodig, deze moet ingeschakeld zijn:

> kubectl get services kube-dns --namespace=kube-system

Resultaat:

NAME       TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                  AGE
kube-dns   ClusterIP   10.152.183.10   <none>        53/UDP,53/TCP,9153/TCP   3d5h

Maak een service aan voor de local-web Pod

Het local-web-pod.yaml bestand had geen ports entry, dus voegde ik een containerPort toe:

      ports:
      - containerPort: 80

Omdat er geen local-web-service.yaml bestand was gemaakt door Kompose, heb ik zelf een bestand gemaakt. Belangrijk: Hier voeg ik een poort toe die toegang geeft tot Pod. In Docker-Compose hadden we deze poort niet nodig omdat de service zich op het interne netwerk bevindt.

apiVersion: v1
kind: Service
metadata:
  name: local-web
  namespace: default
spec:
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
  selector:
    io.kompose.service: local-web

De 'selector' moet overeenkomen met de 'label' van de Pod.

Daarna starten we de service:

> kubectl apply -f local-web-service.yaml

Nu moeten we toegang kunnen krijgen tot de local-web service vanuit een andere Pod container.

Laten we een Busybox container starten. Er zijn problemen met recente Busybox images en Kubernetes, dus we gebruiken versie 1.28.

> kubectl run -i --tty --image busybox:128 test --restart=Never --rm /bin/sh

Vervolgens in de container:

# wget local-web-service:80

Resultaat:

Connecting to local-web-service:80 (10.152.183.30:80)
saving to 'index.html'
index.html           100% |**********************************************************************************************************************************|   400  0:00:00 ETA
'index.html' saved

Geweldig.

Deployments en services maken

Zoals hierboven al vermeld, maken we geen Pod YAML bestanden, maar Deployment YAML bestanden. En we voegen ook een Service toe voor elke Deployment die poorten heeft.

In Kubernetes zijn de servicenamen globaal, in Docker-Compose zijn services op een intern netwerk lokaal voor het Docker-Compose project. Dit betekent dat we in Kubernetes onze services een naamruimte moeten geven.

We kunnen dit op twee manieren doen:

  • Voeg een namespace toe voor dit Docker-Compose project.
  • Voeg een prefix toe aan de naam van de service.

Op dit moment denk ik dat het toevoegen van een prefix de beste optie is:

task-runner-

Dit geeft ons de Deployment namen en Service namen:

task-runner-rabbitmq-deployment
task-runner-local-web-deployment
task-runner-task-deployment
task-runner-unbound-cloudflare-deployment
task-runner-unbound-quad9-deployment

task-runner-rabbitmq-service
task-runner-local-web-service
task-runner-task-service
task-runner-unbound-cloudflare-service
task-runner-unbound-quad9-service

En bestandsnamen:

task-runner-rabbitmq-deployment.yaml
task-runner-local-web-deployment.yaml
task-runner-task-deployment.yaml
task-runner-unbound-cloudflare-deployment.yaml
task-runner-unbound-quad9-deployment.yaml

task-runner-rabbitmq-service.yaml
task-runner-local-web-service.yaml
task-runner-task-service.yaml
task-runner-unbound-cloudflare-service.yaml
task-runner-unbound-quad9-service.yaml

In de Deployment begin ik met 1 replica. Hier is het eerste deel van een Deployment bestand:

# task-runner-local-web-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  # metadata name must not contain dots
  name: task-runner-local-web-deployment
  namespace: default
spec:
  # number of copies of each pod we want
  replicas: 1

  strategy:
    type: Recreate

  # pods managed by this deployment
  selector:
    # match the labels we set on the pod, see below
    matchLabels:
      task-runner.local-web.deployment: task-runner-local-web-deployment

  # template field is a regular pod configuration nested inside the deployment spec
  template:
    metadata:
      # set labels on the pod, used in the deployment selector, see above
      labels:
        task-runner.local-web.deployment: task-runner-local-web-deployment
    spec:
      ...

Maak '.env' bestand beschikbaar wanneer de containers starten

Ik heb veel key-value paren in het '.env' bestand dat gebruikt wordt door Docker-Compose. In Kubernetes kunnen we een ConfigMap gebruiken om deze te importeren in onze Deployment YAML bestanden.

Eerst maken we een ConfigMap. De ConfigMap wordt aangemaakt in de 'standaard' namespace, je kunt dit veranderen door de namespace vlag en waarde toe te voegen:

> kubectl create configmap task-runner-env-configmap --from-env-file=.env

Maak een lijst van de ConfigMaps:

> kubectl get configmaps

Resultaat:

NAME                      DATA   AGE
...
task-runner-env-configmap   51     19m

We kunnen het resultaat bekijken als yaml:

> kubectl get configmaps task-runner-env-configmap -o yaml

Vervolgens verwijderen we in de Deployment YAML bestanden de 'env' sectie en voegen we de 'envFrom' sectie toe.

Van:

    spec:
      containers:
      - env:
        - name: ENV_VAR1
          value: "1"
        ...
        image: ...

To:

    spec:
      containers:
      - envFrom:
        - configMapRef:
            name: task-runner-env-configmap
        env:
          - name: VAR_C = $(VAR_A)/$(VAR_B)
        image: ...

Hier maken we ook een nieuwe omgevingsvariabele VAR_C uit twee omgevingsvariabelen die aanwezig zijn in de ConfigMap.

We kunnen de ConfigMap als volgt bijwerken:

> kubectl create configmap task-runner-env-configmap --from-env-file=.env -o yaml --dry-run | kubectl apply -f -

We hebben een ConfigMap gemaakt om de key-value paren in het '.env' bestand door te geven aan de containers. Maar er is een groot probleem. We kunnen geen omgevingsvariabelen gebruiken in andere delen van onze manifesten. Stel bijvoorbeeld dat je de repository wilt kunnen wisselen met een omgevingsvariabele IMAGE_REGISTRY:

    spec:
      containers:
      - envFrom:
        - configMapRef:
            name: task-runner-env-configmap
        env:
          - name: VAR_C = $(VAR_A)/$(VAR_B)
        image: $(IMAGE_REGISTRY)...

Dit werkt niet! Het is 2023 en omdat deze (triviale functie) niet werkt, maken mensen over de hele wereld hun eigen scripts om dit te laten werken. Prachtig. Hoe dan ook, wat moet het script doen?

  1. Start een nieuwe (Bash) shell.
  2. Exporteer de key-value paren in het '.env' bestand.
  3. Gebruik 'envsubst' om de variabelen in ons manifest te vervangen.

Eerst maken we een nieuwe shell aan om te voorkomen dat dist de huidige omgeving verstoort. In de nieuwe omgeving maken we de omgevingsvariabelen aan met behulp van het bestand '.env'. Tot slot vervangen we de variabelen:

> envsubst < mydeploy.yaml | kubectl apply -f -

PersistentVolumes en VolumeClaims

Kompose heeft voor mij een VolumeMounts en een Volumes sectie gemaakt in de Pods YAML bestanden met behulp van hostPath. Dit betekent dat de eigenlijke localhost mappen hard gecodeerd zijn in de Deployment bestanden. Duidelijk niet wat we willen.

Dus heb ik een aantal PersistentVolume en PersistentVolumeClaim manifesten gemaakt en deze gebruikt in de Deployment bestanden.

Services maken voor de Deployments

Hier laat ik alleen de local-web service zien.

# task-runner-local-web-service.yaml
apiVersion: v1
kind: Service
metadata:
  # metadata name must not contain dots
  name: task-runner-local-web-service
  namespace: default
spec:
  ports:
    - name: http
      port: 80
      targetPort: 80
      protocol: TCP
  selector:
    task-runner.local-web.deployment: task-runner-local-web-deployment

Start de service:

> kubectl apply -f task-runner-local-web-service.yaml

Applicatiecode wijzigen

Ik had slechts kleine wijzigingen nodig in mijn applicatiecode. Zoals hierboven vermeld, heb ik de initiële servicenamen prefix voorzien van 'task-runner-'. Om compatibel te blijven met Docker-Compose gebruik ik nu omgevingsvariabelen voor de servicenamen en poorten, gespecificeerd als key-value paren in het '.env' bestand.

Hoewel poorten nu worden gespecificeerd in alle services, is het niet nodig om iets te veranderen omdat de poorten zijn gebonden aan de services en elke service zijn eigen IP address heeft.

Om te controleren waar een instantie van een Pod draait, sla ik de omgevingsvariabele HOSTNAME op samen met het resultaat van een taak. Voor Docker Swarm gebruikte ik .Node.Hostname. In de logboeken kunnen we dit veld controleren.

nodePort gebruiken om de rabbitmq service aan de back-end bloot te stellen

De Task Runner draait, maar er is geen verbinding met de buitenwereld. Er zijn verschillende manieren om de rabbitmq service te ontsluiten en hier gebruik ik nodePort. We maken gewoon een extra service met het type nodePort en geven het poortnummer op:

# task-runner-rabbitmq-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
  # metadata name must not contain dots
  name: task-runner-rabbitmq-nodeport
  namespace: default
spec:
  type: NodePort
  # The range of valid ports is 30000-32767
  ports:
    - name: amqp
      port: 10763
      targetPort: 5672
      nodePort: 31763
    - name: http
      port: 20763
      targetPort: 15672
      nodePort: 32763
  selector:
    task-runner.rabbitmq.deployment: task-runner-rabbitmq-deployment

Vervolgens kunnen we op de host naar deze Kubernetes Service verwijzen als: localhost:<port>.

Om de Backend Docker container met deze poort te verbinden voegen we een aantal regels toe aan het 'docker-compose.yaml' bestand:

    extra_hosts:
      - "host.docker.internal:host-gateway"

En verwijzen dan naar de service als:

host.docker.internal:<port>

Worker node toevoegen, DNS werkt niet

Natuurlijk wilde ik een nieuw knooppunt toevoegen en daar Pods uitvoeren. Met VitualBox op mijn ontwikkelmachine maakte ik een VM met Ubuntu server 22.04. Daarna voegde ik Microk8s toe en voegde me bij het nieuwe knooppunt.

Problemen ... Kubernetes DNS werkte niet op het nieuwe knooppunt. Er worden veel DNS-problemen genoemd op de Microk8s Github pagina. Dit heeft waarschijnlijk te maken met iptables ...

Ik was in staat om een goed werkend Kubernetes cluster van twee VirtualBox machines te hebben. Momenteel ben ik dit aan het onderzoeken.

Ontwikkelen en problemen oplossen op Kubernetes

Een van de Pods werkte niet. Ik kon in het logboek zien wat er mis was, maar we willen niet in een lus terechtkomen:

--+-> make changes ---> build image ---> test --+--->
  ^                                             |
  |                                             v
  +--------------------<------------------------+ 

Dus heb ik de externe projectcode terug in de container geplaatst, waardoor ik terugging naar de ontwikkelomgeving en het probleem heel snel kon oplossen.

Gereedschap

Ik heb maar één extra tool gebruikt tijdens de migratie: yamllint om manifesten te valideren (YAML bestanden).

> sudo apt-get install yamllint

En dan bijvoorbeeld uitvoeren:

> yamllint task-runner-local-web-deployment.yaml

Samenvatting

Om te beginnen ben ik maar twee weken bezig geweest met Kubernetes (schiet niet op de pianospeler). Ik heb misschien wat verkeerde keuzes gemaakt, maar een deel van mijn applicatie wordt nu uitgevoerd en beheerd door Kubernetes.

Docker Swarm is een makkelijke keuze als het gaat om het orkestreren van Docker-Compose projecten. Het werkt gewoon, zonder ernstige beperkingen. Voor mijn applicatie, die bestaat uit meerdere Docker-Compose projecten, heb ik geen duizenden replica's nodig. Met Docker Swarm start je een nieuwe node op, voeg je wat deploy directives toe aan je docker-compose.yml bestanden en je bent klaar. Docker netwerk is een van de beste functies van Docker. Met Docker Swarm hoef je alleen maar een netwerk in een versleuteld Ingress overlay-netwerk te veranderen om tussen knooppunten te communiceren.

De overstap naar Kubernetes geeft meer inzetflexibiliteit, maar de belangrijkste reden dat Kubernetes de eerste keuze is geworden, is dat het erg wijdverspreid is en dat er veel tools mee geïntegreerd zijn. Dat is waar Docker Swarm achterblijft. Op het moment van schrijven van dit bericht lijkt de ontwikkeling van Docker Swarm min of meer stil te staan (als volledig uitgewerkt beschouwd?). Dat is jammer, want het is een geweldige oplossing voor een zeer complex probleem.

In een ideale wereld is de ontwikkelomgeving identiek aan de productieomgeving. Als het in ontwikkeling draait, draait het ook in productie. Docker Swarm komt hier heel dicht bij, zonder veel complexiteit. Het gebruik van Kubernetes daarentegen creëert een gat. Het is alsof, Ok ontwikkelaar, je hebt je werk gedaan, laat het nu aan de DevOps over om het in productie te zetten. Als de ontwikkelaar wijzigingen aanbrengt in Docker-Compose projecten, moeten de DevOps daar ook aan werken.

Als je Kubernetes voor productie gebruikt, denk ik dat het onvermijdelijk is dat de ontwikkelomgeving ook Kubernetes draait. En in veel gevallen ook Docker Swarm . Maakt dit ontwikkeling en productie gemakkelijker of moeilijker in vergelijking met alleen Docker Swarm? Microk8s is heel eenvoudig te gebruiken in een ontwikkelomgeving, maar dat is nog maar het begin. Het maken van Docker-Compose projectbestanden is heel eenvoudig. Je hebt meestal twee yaml-bestanden en een '.env'-bestand. Na de conversie had mijn Kubernetes project al 14 Kubernetes yaml bestanden en meer.

Het zou een heel goed idee zijn als Kubernetes een aantal extensies zou toevoegen voor de YAML bestanden waarmee andere bestanden (zoals Nginx) en omgevingsgegevens kunnen worden geïmporteerd/toegevoegd. Voorlopig moeten we onze eigen scripts schrijven of iets als Helm gebruiken ... meer dingen om te leren.

Hoe dan ook, in deze post heb ik een poging gedaan om te onderzoeken wat er nodig is om een deel van mijn applicatie te verplaatsen van Docker Swarm naar Kubernetes. Op basis van de resultaten heb ik besloten om over te stappen op Kubernetes, ik blijf Docker Swarm niet gebruiken voor productie. En ik zal ook Kubernetes gebruiken voor ontwikkeling.

Beginnen met Kubernetes is niet erg moeilijk, maar soms wel erg verwarrend. Na een paar dagen begrijp je de meeste concepten en heb je iets werkends. Van daaruit kun je verbeteren en uitbreiden. Er kunnen (zeer) ernstige en tijdrovende problemen zijn, zoals DNS dat niet werkt. Ik wil geen iptables specialist worden, maar er is geen andere manier.

Het aantal wijzigingen dat ik moest maken aan mijn applicatie is zeer beperkt:

  • Namen wijzigen naar RFC1123.
  • Servicenamen voorfixeren met behulp van omgevingsvariabelen.
  • Het opslaan van andere omgevingsvariabelen in resultaten.

Terug naar coderen ...

Links / credits

Configure a Pod to Use a ConfigMap
https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap

Connecting Kubernetes and Docker
https://developers.redhat.com/blog/2017/09/22/connecting-kubernetes-docker

Helm - The package manager for Kubernetes
https://helm.sh

How to go from Docker to Kubernetes the right way
https://www.opensourcerers.org/2021/02/01/how-to-go-from-docker-to-kubernetes-the-right-way

How To Migrate a Docker Compose Workflow to Kubernetes
https://www.digitalocean.com/community/tutorials/how-to-migrate-a-docker-compose-workflow-to-kubernetes

Kompose
https://github.com/kubernetes/kompose

Kubernetes - Documentation - Concepts
https://kubernetes.io/docs/concepts

Kubernetes - Documentation - kubectl Cheat Sheet
https://kubernetes.io/docs/reference/kubectl/cheatsheet

Kustomize - Kubernetes native configuration management
https://kustomize.io

MicroK8s documentation - home
https://microk8s.io/docs

Running Kubernetes locally on Linux with Microk8s
https://kubernetes.io/blog/2019/11/26/running-kubernetes-locally-on-linux-with-microk8s

Using private docker registry inside kubernetes
https://sam-thomas.medium.com/using-private-docker-registry-inside-kubernetes-46a3cede7cb1

When should I use envFrom for configmaps?
https://stackoverflow.com/questions/66352023/when-should-i-use-envfrom-for-configmaps

Laat een reactie achter

Reageer anoniem of log in om commentaar te geven.

Opmerkingen

Laat een antwoord achter

Antwoord anoniem of log in om te antwoorden.