<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Team Blueshoe Technology Blog</title>
        <link>https://www.blueshoe.de</link>
        <description>Ein kleiner Einblick in unsere Arbeit aus den Bereichen Technologie, Digitalisierung, Marketing und allem, was wir uns sonst noch von der Seele schreiben wollen.</description>
        <lastBuildDate>Thu, 26 Feb 2026 13:10:43 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>Team Blueshoe</generator>
        <language>de</language>
        <copyright>Copyright © 2026 Team Blueshoe</copyright>
        <category>Django</category>
        <category>Docker</category>
        <category>FastAPI</category>
        <category>Gefyra</category>
        <category>Keycloak</category>
        <category>Kubernetes</category>
        <category>Nuxt</category>
        <category>Python</category>
        <category>Rust</category>
        <category>Tailwind CSS</category>
        <category>TypeScript</category>
        <category>Varnish</category>
        <category>Vue.js</category>
        <category>Wagtail</category>
        <category>API</category>
        <category>Betrieb</category>
        <category>Digitalisierung</category>
        <category>Dokumentation</category>
        <category>Entwicklung</category>
        <category>KI</category>
        <category>Performance</category>
        <category>Projekt Management</category>
        <category>SEO</category>
        <category>Security</category>
        <item>
            <title><![CDATA[Agiles Projektmanagement mit Produkt-Backlog]]></title>
            <link>https://blueshoe.de/blog/agiles-projektmanagement-backlog</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/agiles-projektmanagement-backlog</guid>
            <pubDate>Sun, 15 Dec 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Im agilen Projektmanagement ersetzt der Produkt-Backlog den starren Anforderungskatalog. Anforderungen werden als flexible User Stories formuliert, gezielt priorisiert und iterativ weiterentwickelt. So bleibt dein Projekt dynamisch, anpassungsfähig und immer nah am Nutzerfeedback.</p>
<p><img src="/img/blogs/agile-backlog.svg" alt="Blueshoe und FastAPI in Produktion">{.object-cover .max-w-full .mb-5}</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Agiler Projektstart: Weg mit der Starrheit, her mit der Flexibilität
:::
:::GlobalParagraph
Der Start eines Softwareprojekts ist immer eine aufregende Phase: Ideen sprudeln, Visionen entstehen – und genau hier entscheidet sich oft schon, ob ein Projekt ein Erfolg wird. Viele Teams halten sich zu lange an veralteten Methoden fest, wie dem klassischen Anforderungskatalog, der zwar Struktur vorgaukelt, aber oft zum Bremser der Agilität wird.
:::</p>
<p>:::GlobalParagraph
Wir bei BLUESHOE haben gelernt: Flexibilität von Anfang an ist der Schlüssel. Deshalb setzen wir auf den agilen Produkt-Backlog, der den starren Anforderungskatalog ersetzt. Wie das funktioniert? Lies weiter – und entdecke am Ende, wie unser <a href="/leistungen/">RAPID-System</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} deine Projekte noch effizienter macht.
:::</p>
<p>:::GlobalButton{:url="/leistungen/anforderungsanalyse/" :label="Erfahre mehr über unsere Anforderungsanalyse" :color="blue" .mb-6}
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Der Anforderungskatalog: Ein Relikt aus der Wasserfall-Ära
:::
:::GlobalParagraph
Der klassische Anforderungskatalog listet jede Funktion bis ins kleinste Detail auf. Klingt erstmal sinnvoll, oder? Schließlich sorgt er für klare Vorgaben und scheinbare Planungssicherheit. Doch hier lauern die Probleme:
:::</p>
<p>:::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li><strong>Unflexibel</strong>: Änderungen sind aufwendig, teuer und oft nicht vorgesehen.</li>
<li><strong>Keine Dynamik</strong>: Neue Erkenntnisse lassen sich nur schwer integrieren.</li>
<li><strong>Risiko von Fehlentwicklungen</strong>: Was zu Beginn geplant wurde, passt selten noch zum Ziel am Ende.
:::</li>
</ul>
<p>:::GlobalParagraph
Kurz gesagt: Der Anforderungskatalog plant für eine Welt, die es nicht mehr gibt.
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Der Produkt-Backlog: Agil, flexibel, dynamisch
:::
:::GlobalParagraph
Der Produkt-Backlog ist das Herzstück agiler Projekte. Er ist kein starres Dokument, sondern eine lebendige Liste von Aufgaben, die kontinuierlich angepasst wird. So bleibt dein Projekt dynamisch und reaktionsfähig.
:::</p>
<p><img src="/img/blogs/blueshoe-81.webp" alt="Blueshoe">{.object-cover .max-w-full .mb-5}</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Doch wie werden Anforderungen im Backlog definiert?
::
:::GlobalParagraph
Hier kommen die <a href="/leistungen/#projektbeschreibung">User Stories</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} ins Spiel. Sie sind kurze, prägnante Beschreibungen von Anforderungen aus der Perspektive des Endnutzers. Eine typische User Story folgt dieser einfachen Struktur:
:::</p>
<p>:::GlobalParagraph
Struktur: "Als <em>Nutzerrolle</em> möchte ich <em>Ziel/Wunsch</em>, damit <em>Nutzen</em>."
:::</p>
<p>:::GlobalParagraph
Beispiel: "Als Online-Shop-Kunde möchte ich meine Bestellungen verfolgen können, damit ich immer weiß, wann mein Paket ankommt."
:::</p>
<p>:::GlobalParagraph
<strong>Die Vorteile von User Stories:</strong>
:::</p>
<p>:::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li><strong>Fokus auf den Nutzer</strong>: Sie sorgen dafür, dass jede Anforderung echten Mehrwert liefert.</li>
<li><strong>Flexibilität</strong>: Leicht anpassbar, wenn sich Anforderungen ändern.</li>
<li><strong>Klarheit</strong>: Alle im Team – vom Entwickler bis zum Projektmanager – verstehen sofort, worum es geht.
:::</li>
</ul>
<p>:::GlobalParagraph
Durch regelmäßiges <strong>Backlog-Refinement</strong> verfeinern und priorisieren wir diese User Stories, sodass dein Projekt immer auf das Wesentliche fokussiert bleibt.
:::</p>
<p>:::GlobalParagraph
<strong>Die Vorteile im Überblick:</strong>
:::</p>
<p>:::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li><strong>Flexibilität</strong>: Änderungen sind jederzeit möglich.</li>
<li><strong>Kundenorientierung</strong>: Fokus auf den tatsächlichen Mehrwert.</li>
<li><strong>Schnelle Ergebnisse</strong>: Iterative Entwicklung mit sichtbarem Fortschritt.</li>
<li><strong>Transparenz</strong>: Alle Beteiligten sind immer im Bilde.
:::</li>
</ul>
<p>:::GlobalParagraph{.mb-5}
Klingt spannend? Dann wird dich unser <a href="/leistungen/">RAPID-System</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} noch mehr begeistern – es macht deine Prozesse bis zu 35 % schneller.
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
So gelingt der Wechsel vom Katalog zum Backlog
:::
:::GlobalParagraph
Der Übergang zum agilen Arbeiten muss nicht kompliziert sein. Bei BLUESHOE setzen wir auf einen strukturierten, aber flexiblen Ansatz:
:::</p>
<p>:::GlobalBlock{.ol-decimal .mb-4}</p>
<ol>
<li><strong>Anforderungen in User Stories umwandeln</strong>: Statt starrer Vorgaben definieren wir User Stories, die sich an den Bedürfnissen der Nutzer orientieren. Das schafft Klarheit und Fokus.</li>
<li><strong>Prioritäten setzen</strong>: Nicht alles ist gleich wichtig. In Workshops priorisieren wir gemeinsam, was den größten Nutzen bringt – und was warten kann.</li>
<li><strong>Regelmäßiges Backlog-Refinement</strong>: Der Backlog ist kein „Set-and-Forget“-Dokument. Wir passen ihn kontinuierlich an neue Erkenntnisse an, um immer auf Kurs zu bleiben.</li>
<li><strong>Transparenz schaffen</strong>: Dank digitaler Tools und offener Kommunikation hast du jederzeit Einblick in den Projektstatus. Keine Überraschungen, keine Blackboxes.
:::</li>
</ol>
<p>:::GlobalTitle{:size="lg" .mb-5}
Warum der agile Backlog den Projekterfolg steigert
:::</p>
<p>:::GlobalParagraph
Ein agiler Backlog macht den Unterschied:
:::</p>
<p>:::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li><strong>Anpassungsfähig</strong>: Veränderungen? Kein Problem – der Backlog wächst mit.</li>
<li><strong>Fokus auf das Wesentliche</strong>: Klare Prioritäten halten das Team auf Kurs.</li>
<li><strong>Schnelle Ergebnisse</strong>: Iterative Schritte liefern frühzeitig sichtbaren Mehrwert.</li>
<li><strong>Bessere Abstimmung</strong>: Ein gemeinsames Verständnis durch klare Anforderungen.
:::</li>
</ul>
<p>:::GlobalTitle{:size="lg" .mb-5}
Fazit: Dynamik von Anfang an
:::
:::GlobalParagraph
Agil zu starten bedeutet, flexibel zu bleiben. Mit BLUESHOE schaffst du den Sprung vom starren Plan zur dynamischen Umsetzung. Wir helfen dir, Anforderungen sinnvoll zu strukturieren und den Backlog lebendig zu halten. Wenn du dein Projekt agil starten möchtest, lass uns zusammen loslegen – kontaktiere uns und wir machen den ersten Schritt gemeinsam!
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5 .mt-8}
Häufige Fragen
:::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
1. Was ist der Unterschied zwischen Anforderungskatalog und Produkt-Backlog?
::</p>
<p>::GlobalParagraph
Der Anforderungskatalog ist eine starre Liste von Vorgaben. Der Produkt-Backlog ist flexibel und wird regelmäßig an neue Erkenntnisse angepasst.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
2. Warum ist agiles Projektmanagement effizienter?
::</p>
<p>::GlobalParagraph
Weil es Veränderungen als Chance sieht. Statt starrer Pläne setzt es auf iterative Verbesserungen und schnellen Kunden-Feedback.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
3. Braucht jedes Projekt einen Produkt-Backlog?
::</p>
<p>::GlobalParagraph
Für agile Projekte: Ja! Der Backlog ist das zentrale Steuerungsinstrument. Er hält das Team fokussiert und sorgt für Transparenz.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
4. Wie oft sollte der Backlog aktualisiert werden?
::</p>
<p>::GlobalParagraph
Regelmäßig! Idealerweise nach jedem Sprint oder wenn es wichtige neue Erkenntnisse gibt. So bleibt das Projekt immer auf Kurs.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
5. Wie hilft der Produkt-Backlog, den Projekterfolg zu sichern?
::</p>
<p>::GlobalParagraph{.mb-4}
Der Produkt-Backlog sorgt für klare Prioritäten, flexible Anpassungsmöglichkeiten und kontinuierliche Transparenz im Projekt. So können Teams schneller auf Veränderungen reagieren, den Fokus auf den größten Mehrwert legen und effizienter zusammenarbeiten.
::</p>]]></content:encoded>
            <category>Projekt Management</category>
            <category>Digitalisierung</category>
            <enclosure url="https://blueshoe.de/img/blogs/agile-backlog.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Alternativen zu Celery für Django auf Kubernetes]]></title>
            <link>https://blueshoe.de/blog/alternativen-zu-django-celery-in-kubernetes</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/alternativen-zu-django-celery-in-kubernetes</guid>
            <pubDate>Wed, 10 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Du planst Hintergrundjobs im Cluster und fragst dich, ob es neben Celery 'leichtere' Optionen gibt?</p>
<p>Celery ist mächtig, aber manchmal zu schwergewichtig für einfache Setups. In diesem Artikel schauen wir auf Alternativen wie <strong>Django RQ, Dramatiq und Huey</strong>, erklären, warum <strong>KEDA</strong> eine Schlüsselrolle beim Autoscaling spielt und geben dir <strong>Mini-YAML-Beispiele</strong> für sauberes Deployment mit Probes und Graceful Shutdown.</p>
<p><img src="/img/blog/celery-alt.svg" alt="Celery Alternativen"></p>
<h2>Die vier Optionen im Kurzprofil</h2>
<p><em>(Mit Links zu Projekten und Repos, damit du weiter eintauchen kannst.)</em></p>
<h3><a href="https://docs.celeryq.dev/">Celery</a></h3>
<p>Der Klassiker mit großem Ökosystem. Unterstützt Worker und den separaten Scheduler Beat. In großen Setups mit Chains und vielen Integrationen bleibt Celery der Referenzpunkt.</p>
<h3><a href="https://github.com/rq/django-rq">Django RQ</a></h3>
<p>Django-freundliche Integration von RQ auf Redis Basis. Worker starten im Django Kontext. In vielen Fällen genügt das Setzen von <code>DJANGO_SETTINGS_MODULE</code>. Monitoring ist leicht über <a href="https://github.com/rq/rq-dashboard">RQ Dashboard</a> oder Admin-Integrationen.</p>
<h3><a href="https://dramatiq.io/">Dramatiq</a></h3>
<p>Moderne Defaults mit Fokus auf Zuverlässigkeit. Läuft mit Redis oder RabbitMQ. Die Django Integration <a href="https://github.com/Bogdanp/django_dramatiq"><code>django_dramatiq</code></a> bringt das Management Kommando <code>rundramatiq</code>.</p>
<h3><a href="https://huey.readthedocs.io/">Huey</a></h3>
<p>Leichtgewichtig mit eingebautem Scheduler. Saubere Django Integration über das Management Kommando <code>run_huey</code> inklusive Auto-Discovery von <code>tasks.py</code>.</p>
<hr>
<h2>KEDA in 60 Sekunden</h2>
<p><a href="https://keda.sh/">KEDA</a> ist der Kubernetes Event Driven Autoscaler.<br>
Er skaliert Deployments und Jobs abhängig von Ereignissen wie Queue-Längen und kann bei Leerlauf bis auf null skalieren. KEDA ergänzt den Horizontal Pod Autoscaler und arbeitet mit ihm zusammen.</p>
<p>Warum ist KEDA so wichtig?</p>
<ul>
<li>Du brauchst <strong>kein Polling</strong> im Worker.</li>
<li>Pods skalieren dynamisch, wenn Jobs in der Queue liegen.</li>
<li>Bei Leerlauf kannst du die Worker <strong>auf 0 reduzieren</strong> und Ressourcen sparen.</li>
</ul>
<p>Typische Trigger für Django Worker sind <strong>Redis Lists</strong> sowie <strong>RabbitMQ Queues</strong>. Beide sind als Scaler verfügbar.</p>
<hr>
<h2>Vergleich speziell für Kubernetes</h2>
<p>| Tool | Broker | KEDA Trigger | Vorteile | Nachteile | Beste Wahl wenn |
|---|---|---|---|---|---|
| <strong>Celery</strong> | Redis, RabbitMQ | Redis List oder RabbitMQ Queue | <strong>Sehr ausgereift</strong>, viele Integrationen, Worker plus Beat, breite Community. <strong>K8s Plus:</strong> viele Beispiele für Queue-basiertes Autoscaling. | Mehr Betriebsaufwand, zusätzliche Komponenten wie Beat, sorgfältiges Shutdown Handling nötig. | Hohe Last, komplexe Chains, vorhandene Celery Erfahrung. |
| <strong>Django RQ</strong> | Redis | Redis List | <strong>Sehr leichter Start</strong>, Admin Integration, wenige bewegliche Teile. <strong>K8s Plus:</strong> Listenlänge als simpler KEDA Trigger. | Geringere Feature Tiefe, Redis Pflicht. | Web Projekte mit klaren Jobs und schnellem Go Live. |
| <strong>Dramatiq</strong> | Redis, RabbitMQ | Redis List oder RabbitMQ Queue | <strong>Moderne Defaults</strong>, robuste Retries, saubere Django Integration über <code>rundramatiq</code>. <strong>K8s Plus:</strong> gut kombinierbar mit KEDA je nach Broker. | Weniger fertige Django UIs, Einarbeitung ins Actor Modell. | Anspruchsvoll aber schlank, Fokus auf Zuverlässigkeit. |
| <strong>Huey</strong> | Redis | Redis List | <strong>Leichtgewichtig</strong>, Scheduler enthalten, Consumer als Management Kommando. <strong>K8s Plus:</strong> simpler Prozess und KEDA Kopplung. | Kleineres Ökosystem, Monitoring eher minimal. | Wenige Worker, viele periodische Aufgaben. |</p>
<hr>
<h2>Mini YAML Beispiele</h2>
<h3>Beispiel A: Celery Worker mit RabbitMQ und KEDA</h3>
<pre><code class="language-yaml"># Deployment für Celery Worker
apiVersion: apps/v1
kind: Deployment
metadata:
  name: celery-worker
spec:
  selector: { matchLabels: { app: celery } }
  template:
    metadata: { labels: { app: celery } }
    spec:
      terminationGracePeriodSeconds: 60
      containers:
        - name: worker
          image: your-registry/app:latest
          command: ["celery","-A","proj","worker","--loglevel=info"]
          env:
            - name: RABBITMQ_HOST
              valueFrom:
                secretKeyRef: { name: rmq, key: amqp_uri }
---
# ScaledObject für RabbitMQ Queue
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: celery-rabbit
spec:
  scaleTargetRef: { name: celery-worker }
  minReplicaCount: 0
  maxReplicaCount: 20
  triggers:
    - type: rabbitmq
      metadata:
        hostFromEnv: RABBITMQ_HOST
        queueName: celery
        protocol: amqp
        mode: QueueLength
        value: "20"
      authenticationRef:
        name: rmq-auth
</code></pre>
<hr>
<h3>Beispiel B: Django RQ Worker plus KEDA Redis List Scaler</h3>
<pre><code class="language-yaml"># Deployment für RQ Worker
apiVersion: apps/v1
kind: Deployment
metadata:
  name: rq-worker
spec:
  selector: { matchLabels: { app: rq } }
  template:
    metadata: { labels: { app: rq } }
    spec:
      terminationGracePeriodSeconds: 60
      containers:
        - name: worker
          image: your-registry/app:latest
          command: ["bash","-lc","DJANGO_SETTINGS_MODULE=config.settings rq worker default"]
          env:
            - name: REDIS_HOST
              value: "redis:6379"
---
# KEDA ScaledObject mit Redis List Trigger
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: rq-scale
spec:
  scaleTargetRef: { name: rq-worker }
  minReplicaCount: 0
  maxReplicaCount: 10
  triggers:
    - type: redis
      metadata:
        addressFromEnv: REDIS_HOST
        listName: default
        listLength: "20"  # Skalierung ab 20 Jobs
</code></pre>
<hr>
<h3>Beispiel C: Dramatiq unter Django mit Redis</h3>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
  name: dramatiq-worker
spec:
  selector: { matchLabels: { app: dramatiq } }
  template:
    metadata: { labels: { app: dramatiq } }
    spec:
      terminationGracePeriodSeconds: 60
      containers:
        - name: worker
          image: your-registry/app:latest
          command: ["python","manage.py","rundramatiq","--processes","2","--threads","8"]
          env:
            - name: REDIS_HOST
              value: "redis:6379"
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: dramatiq-scale
spec:
  scaleTargetRef: { name: dramatiq-worker }
  minReplicaCount: 0
  maxReplicaCount: 10
  triggers:
    - type: redis
      metadata:
        addressFromEnv: REDIS_HOST
        listName: default
        listLength: "10"
</code></pre>
<hr>
<h3>Beispiel D: Huey Worker mit Redis</h3>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
  name: huey-worker
spec:
  selector: { matchLabels: { app: huey } }
  template:
    metadata: { labels: { app: huey } }
    spec:
      terminationGracePeriodSeconds: 60
      containers:
        - name: worker
          image: your-registry/app:latest
          command: ["python","manage.py","run_huey"]
          env:
            - name: REDIS_HOST
              value: "redis:6379"
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: huey-scale
spec:
  scaleTargetRef: { name: huey-worker }
  minReplicaCount: 0
  maxReplicaCount: 5
  triggers:
    - type: redis
      metadata:
        addressFromEnv: REDIS_HOST
        listName: default
        listLength: "5"
</code></pre>
<hr>
<h2>Kubernetes Betriebsaspekte, die zählen</h2>
<h3>Broker deployen</h3>
<p>Redis und RabbitMQ bekommst du stabil per <a href="https://artifacthub.io/">Helm Chart</a> oder Operator. Für RabbitMQ gibt es zusätzlich einen offiziellen <a href="https://www.rabbitmq.com/kubernetes/operator/">Cluster Operator</a>.</p>
<h3>Probes konfigurieren</h3>
<p>Setze <strong>Readiness, Liveness</strong> und bei längeren Starts eine <strong>Startup Probe</strong>. Beispiel:</p>
<pre><code class="language-yaml">livenessProbe:
  exec: { command: ["pgrep", "rq"] }
  initialDelaySeconds: 20
  periodSeconds: 10
</code></pre>
<p>So verhinderst du Deadlocks und Traffic an nicht bereite Pods.</p>
<h3>Graceful Shutdown</h3>
<p>Nutze <code>terminationGracePeriodSeconds</code> und bei Bedarf einen <code>preStop</code> Hook, damit laufende Tasks sauber zu Ende laufen:</p>
<pre><code class="language-yaml">lifecycle:
  preStop:
    exec:
      command: ["bash","-c","kill -TERM 1 &#x26;&#x26; sleep 30"]
</code></pre>
<p>Kubernetes beendet Container nach Ablauf der Grace Period in jedem Fall – plane also Puffer.</p>
<h3>KEDA Installation</h3>
<p>KEDA lässt sich per <a href="https://keda.sh/docs/">Helm Chart</a> oder YAML installieren. Danach definierst du <strong>ScaledObjects</strong> oder <strong>ScaledJobs</strong> pro Worker.</p>
<hr>
<h2>Code Beispiele für Tasks</h2>
<p>Damit du den Unterschied auch im Django Code siehst:</p>
<h3>Celery</h3>
<pre><code class="language-python">from celery import shared_task

@shared_task
def send_email(user_id):
    # klassische Celery Task
    ...
</code></pre>
<h3>Django RQ</h3>
<pre><code class="language-python">import django_rq

def send_email(user_id):
    ...

# Task in die Queue legen
queue = django_rq.get_queue('default')
queue.enqueue(send_email, user.id)
</code></pre>
<h3>Dramatiq</h3>
<pre><code class="language-python">import dramatiq

@dramatiq.actor
def send_email(user_id):
    ...

# Task dispatchen
send_email.send(user.id)
</code></pre>
<h3>Huey</h3>
<pre><code class="language-python">from huey.contrib.djhuey import task

@task()
def send_email(user_id):
    ...

# Task aufrufen
send_email(user.id)
</code></pre>
<hr>
<h2>Ausfallhandling in Kubernetes Clustern</h2>
<p>Früher oder später passiert’s: Ein Worker geht down oder der Broker fällt aus. Damit deine Tasks trotzdem sauber weiterlaufen, brauchst du etwas Absicherung. So kannst du vorgehen:</p>
<h3>Wenn ein Worker abstürzt</h3>
<p>Kubernetes stellt deine Pods automatisch mit <code>restartPolicy: Always</code> wieder her. Wichtig ist jedoch, dass Jobs erst dann aus der Queue entfernt werden, wenn sie wirklich verarbeitet sind. Andernfalls gehen Tasks verloren.</p>
<p>Alle vier Tools (Celery, RQ, Dramatiq, Huey) unterstützen eingebaute Retries. Diese solltest du aktiv einsetzen.</p>
<p>Ein Beispiel für ein RQ‑Worker‑Deployment:</p>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
  name: rq‑worker
spec:
  replicas: 2
  selector: { matchLabels: { app: rq } }
  template:
    metadata: { labels: { app: rq } }
    spec:
      restartPolicy: Always
      containers:
        - name: worker
          image: your‑registry/app:latest
          command: ["rq","worker","default"]
</code></pre>
<p>Wenn ein Pod abstürzt, startet Kubernetes ihn einfach neu. Die offenen Jobs bleiben in Redis liegen und werden vom nächsten Worker übernommen.</p>
<hr>
<h3>Wenn der Broker ausfällt</h3>
<p>Ohne Redis oder RabbitMQ läuft nichts. Deshalb solltest du diese Komponenten hochverfügbar betreiben.</p>
<h4>Redis als StatefulSet</h4>
<p>Ein Deployment allein reicht nicht aus, wenn Daten dauerhaft gespeichert werden müssen. Ein StatefulSet sorgt dafür, dass Redis‑Pods stabile Namen und ein eigenes Volume haben. Damit bleibt die Queue auch nach Neustarts bestehen.</p>
<p>Kurz gesagt: StatefulSets geben Pods eine feste Identität und persistenten Speicher.</p>
<h4>RabbitMQ mit replizierten Queues</h4>
<p>RabbitMQ‑Queues sind normalerweise an einen Node gebunden. Fällt dieser Node oder Pod aus, ist die Queue verschwunden. Mit Quorum Queues, also replizierten und ausfallsicheren Queues auf Basis des Raft‑Protokolls, bist du sicherer aufgestellt. Der RabbitMQ Kubernetes Operator erleichtert dir den Aufbau eines hochverfügbaren Clusters.</p>
<p>Der Vorteil ist klar: Wenn ein Pod ausfällt, übernimmt ein anderer, ohne dass deine Worker davon viel mitbekommen.</p>
<hr>
<h3>Kubernetes‑Tools, die du nicht vergessen solltest</h3>
<ul>
<li><strong>PodDisruptionBudget (PDB):</strong> sorgt dafür, dass bei Updates nicht zu viele Pods gleichzeitig ausfallen.</li>
<li><strong>Readiness Probes:</strong> stellen sicher, dass nur gesunde Pods Jobs bearbeiten.</li>
<li><strong>Graceful Shutdown:</strong> gibt deinen Workern Zeit, um Tasks bei SIGTERM zu Ende zu bringen.</li>
<li><strong>Backoff und Retries:</strong> ermöglichen es Workern, automatisch wieder zu verbinden, wenn der Broker kurzzeitig nicht erreichbar ist.</li>
</ul>
<hr>
<p>Mit diesem Setup bist du für die häufigsten Fehlerquellen im Cluster gut vorbereitet, egal ob Worker oder Broker einmal ausfallen.</p>
<h2>Entscheidungsleitfaden</h2>
<ul>
<li><strong>Einfach und schnell mit Redis:</strong> Django RQ oder Huey. Kopple über den KEDA Redis List Scaler.</li>
<li><strong>Robust mit flexibler Broker Wahl:</strong> Dramatiq. Nimm Redis oder RabbitMQ und binde KEDA entsprechend an.</li>
<li><strong>Großes Setup mit vorhandenem Know-how:</strong> Celery mit ScaledObjects je Queue und optional Beat oder django-celery-beat für Periodics.</li>
</ul>
<p>Migrationen sind möglich: Viele Task-Definitionen lassen sich mit geringem Aufwand portieren, auch wenn Retry/ACK-Mechanismen unterschiedlich umgesetzt sind.</p>
<hr>
<h2>FAQ</h2>
<h3>1. Was ist KEDA und wofür nutze ich es?</h3>
<p>KEDA skaliert Workloads anhand externer Ereignisse wie Queue Länge und ermöglicht Scale to Zero. Es ergänzt den Horizontal Pod Autoscaler.</p>
<h3>2. Kann ich alle vier Tools mit KEDA autoscalen?</h3>
<p>Ja. Über den Redis Lists Scaler oder den RabbitMQ Queue Scaler, je nach Broker.</p>
<h3>3. Wie wähle ich den passenden Broker?</h3>
<p>Redis ist schnell bereitgestellt und genügt oft für Web Projekte. RabbitMQ lohnt sich bei komplexem Routing oder bestehender AMQP Erfahrung.</p>
<h3>4. Brauche ich für Celery einen separaten Scheduler?</h3>
<p>Für periodische Aufgaben nutzt Celery den Scheduler Beat. Mit <a href="https://github.com/celery/django-celery-beat">django-celery-beat</a> kannst du Zeitpläne im Django Admin pflegen.</p>
<h3>5. Wie starte ich Dramatiq in Django sauber?</h3>
<p>Über <code>python manage.py rundramatiq</code> aus <code>django_dramatiq</code>. Das Kommando ist für die Django Integration gedacht.</p>
<h3>6. Gibt es Dashboards für Django RQ?</h3>
<p>Ja. Es gibt <a href="https://github.com/rq/rq-dashboard">rq-dashboard</a> als Standalone sowie Integrationen für den Django Admin.</p>]]></content:encoded>
            <category>Django</category>
            <category>Kubernetes</category>
            <category>Entwicklung</category>
            <category>Betrieb</category>
            <enclosure url="https://blueshoe.de/img/blog/celery-alt.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Anforderungsanalyse im Projektmanagement]]></title>
            <link>https://blueshoe.de/blog/anforderungsanalyse-projektmanagement</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/anforderungsanalyse-projektmanagement</guid>
            <pubDate>Thu, 20 Aug 2020 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Ob Projekte “laufen wie geschmiert” oder sich “ziehen wie Kaugummi”, ist davon abhängig, wie genau die Kundenanforderungen erhoben und umgesetzt werden. In diesem Artikel wollen wir unsere Erfahrungen aus der Praxis teilen und aufzeigen, wo die Herausforderungen bei der Aufnahme der Kundenanforderungen liegen und wie dieser Prozess in den Projektablauf integriert werden kann.</p>
<p><img src="/img/blogs/requirements-analysis-in-project-management.jpg" alt="requirements-analysis-in-project-management">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Die Aufnahme der Kundenwünsche, die Anforderungsanalyse und -erhebung, wird im Arbeitsalltag oft stiefmütterlich behandelt. Denn das alles kostet bereits vor der Auftragserteilung meist Zeit – und Zeit ist Geld. Das rächt sich dann aber meist in der Umsetzungsphase und auch vermeintlich „leichte” Projekte können ungeahnte Ausmaße annehmen. In diesem Artikel wollen wir deshalb einen genaueren Blick auf diesen Aspekt der Projektimplementierung werfen.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
DAS „MAGISCHE DREIECK”
:::
:::globalParagraph
Das magische Dreieck ist im theoretischen Projektmanagement weit verbreitet. Nur, wenn die Aspekte „Kosten”, „Zeit” und „Qualität” ein gleichseitiges Dreieck bilden, ist die Projektumsetzung ausgewogen. Wir plädieren dafür, den Begriff der Qualität durch den Begriff des „Scopes”, also des Umfangs, zu ersetzen. Denn der Umfang der Leistungserbringung ist viel klarer definierbar als die Qualität der Leistungserbringung. Damit kann der Scope auch viel effektiver an die Aspekte der Kosten und der Zeit gekoppelt werden, wobei die Qualität dann einen Aspekt des Scopes darstellt.
:::</p>
<p><img src="/img/blogs/grafik_blog_erfolgreichespm.jpg" alt="grafik_blog_erfolgreichespm">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalTitle{:size="md" .mb-5}
DEN KUNDENWÜNSCHEN AUF DER SPUR
:::
:::globalParagraph
Die daraus resultierende Frage ist: Wie kann der Scope am genauesten erhoben werden?
Dabei fällt meist der Begriff der Anforderungsanalyse. Also zu analysieren, was der Kunde möchte und welche der Kundenwünsche im konkreten Auftrag umgesetzt werden sollen. Dieser Prozess, der oft als „Anforderungsanalyse” bezeichnet wird, besteht allerdings aus zwei separaten Schritten: Zuerst kommt die Anforderungserhebung und erst im zweiten Schritt folgt die eigentliche Analyse.
:::</p>
<p>:::globalTitle{:size="md" .mb-5}
DIE ANFORDERUNGSERHEBUNG
:::
:::globalParagraph
Bei der Anforderungserhebung geht es darum, herauszufinden was der Kunde „wirklich will”.
Die Anforderungen, die der Kunde an sein künftiges Produkt hat, sind in der Regel in der Sprache und mit dem Vokabular des Kunden formuliert. Es lohnt sich hier eine extra Abstimmungsschleife zu drehen, um sicherzustellen, dass Kunde und Auftragnehmer eine möglichst deckungsgleiche Vorstellung des späteren Produktes haben.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
FUNKTIONALE UND NICHT-FUNKTIONALE ANFORDERUNGEN
:::
:::globalParagraph
Funktionale Anforderungen beschreiben dabei, WAS ein System leisten soll. Nicht-funktionale Anforderungen beschreiben, WIE ein System funktionieren soll.
:::
:::globalParagraph
Beide Aspekte sind im Kundengespräch dabei nicht immer klar voneinander zu trennen. Die Herausforderung für den Auftragnehmer ist es, diese Aspekte zu trennen – gegebenenfalls auch erst nach dem Gespräch mit dem Kunden. Denn für den Kunden sind beide Aspekte oft „ein und dasselbe”. Für die Definition des Scopes ist die Trennung aber immens wichtig: Die funktionalen Anforderungen, also das WAS, müssen den Kern der Anforderungserhebung bilden. Herrscht darüber zwischen Kunde und Auftragnehmer keine Einigkeit, kann das Projekt nicht erfolgreich umgesetzt werden.
:::</p>
<p>:::globalTitle{:size="sm" :tag="h4" .mb-5}
PRAXISBEISPIELE
:::</p>
<p><img src="/img/blogs/nick-fewings.jpg" alt="nick-fewings">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Nehmen wir als Beispiel einen Gastronomen, der ein Reservierungssystem benötigt. Dieser beschreibt die funktionale Anforderung als: “Das System soll anzeigen, wie viele freie Plätze im Restaurant noch verfügbar sind”. Diese Formulierung birgt ein hohes Risiko für Missverständnisse: Bezieht sich die Anforderung rein auf freie Plätze, also wie viele potentielle Gäste das Restaurant aufnehmen kann? Oder muss die Verteilung der Plätze auf die Tische mitberücksichtigt werden?
:::
:::globalParagraph
Ein anderes Beispiel: Für einen Kunden soll eine Suchabfrage aus einer Datenbank realisiert werden. Die funktionale Anforderung ist erfasst und so geklärt, dass Kunde und Auftragnehmer ein gemeinsames Verständnis teilen. Eine nicht-funktionale Anforderung ist die spätere Darstellung der Suchergebnisse. Zum Beispiel, ob die  Suchergebnisse paginiert oder nicht-paginiert dargestellt werden sollen, oder wie viele Suchergebnisse überhaupt angezeigt werden sollen. Aber auch, auf welche Zielgruppe der Suchdienst abzielt und in welcher Form die Suchergebnisse dargestellt werden sollen (sollen z. B. nur der Titel oder auch weitere Details angezeigt werden?).
:::
:::globalParagraph
Die beiden Beispiele zeigen, dass die funktionalen Anforderungen zwar den Kern des Scopes bilden, aber die nicht-funktionalen Aspekte trotzdem nicht vernachlässigt werden dürfen und ebenso gewissenhaft erhoben werden müssen. Denn, auch wenn in einem Projekt alle funktionalen Anforderungen abgebildet werden, ist es möglich, dass das Ergebnis trotzdem nicht dem entspricht, was der Kunde sich vorstellt, da die nicht-funktionalen Anforderungen nicht ausreichend abgeklärt wurden.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
ANFORDERUNGSERHEBUNG = DER DIENSTLEISTER ALS DETEKTIV
:::
:::globalParagraph
Der Fokus der Anforderungserhebung sollte darauf liegen, alle möglichen Eventualitäten in den Kundenanforderungen aufzudecken. Dabei kann der Softwaredienstleister sich als Detektiv verstehen, um den Fokus nicht aus den Augen zu verlieren und sich folgende zwei Fragen stellen:
:::
:::GlobalBlock{.ol-decimal .mb-5}</p>
<ol>
<li>Wer profitiert davon wirklich?
Für wen soll das spätere Produkt nützlich sein? Wer ist die eigentliche Zielgruppe: der Kunde selbst oder der Kunde des Kunden? Die Zielgruppe beeinflusst maßgeblich die funktionalen und nicht-funktionalen Anforderungen. Diese Frage erscheint einfach, oft verstecken sich hier aber essentielle Aspekte für eine umfassende Anforderungserhebung.
What doesn’t fit in?</li>
<li>Was passt nicht ins Bild?
Oft verstecken sich hinter anscheinend banalen Funktionalitäten weitere Funktionen, die für die Umsetzung unabdingbar sind. Die Frage zielt auf Edge-Cases (also Ausnahmefälle) ab. Ausnahmefälle müssen dabei richtiggehend „aufgespürt” werden. Vielen Kunden sind Ausnahmefälle, die bei der Projektimplementierung berücksichtigt werden müssen, oft gar nicht bewusst. In der späteren Implementierung können diese aber dazu führen, dass eine zuvor erstellte Konzeption durch das Auftreten von Edge-Cases komplett neu aufgesetzt werden muss.
:::
:::globalParagraph
Um im Bild eines ermittelnden Kommissars zu bleiben, dient die Anforderungserhebung also dazu, die „wahren Motive” des Kunden zu erkennen.
:::</li>
</ol>
<p>:::globalTitle{:size="lg" .mb-5}
ANFORDERUNGSANALYSE ALS ZWEITER SCHRITT
:::</p>
<p><img src="/img/blogs/abi-ismail-zod.jpg" alt="abi-ismail-zod">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Die eigentliche Analyse der Anforderungen erfolgt erst nach der Anforderungserhebung. Nachdem die Kundenanforderungen aufgenommen wurden, muss – im besten Falle gemeinsam mit dem Kunden – geprüft werden, ob diese überhaupt umsetzbar sind.
:::
:::globalParagraph
Hier gilt es zu klären, ob die notwendigen technischen Voraussetzungen gegeben sind, um die Anforderungen umzusetzen. Außerdem muss geklärt werden, ob Anforderungen im vorgegebenen Zeit- und Kostenrahmen realisierbar sind.
:::
:::globalParagraph
Ist das nicht der Fall, muss geprüft werden, ob es sinnvoll ist, in einer ersten Umsetzungsphase nur einen Teil der Anforderungen umzusetzen. Ist das möglich, ist sowohl aus Kunden- als auch aus Auftragnehmersicht eine Anforderungspriorisierung hilfreich: Es gilt zu klären, welche Elemente für den Kunden am wichtigsten sind, und ob diese auch realisierbar sind.
:::</p>
<p>:::globalTitle{:size="md" .mb-5}
ABGLEICH MIT DER REALITÄT
:::
:::globalParagraph
Was in Büchern zum erfolgreichen Projektmanagement oft fehlt, ist ein Abgleich mit der Realität. In einer idealtypischen Welt haben sowohl Kunde als auch Auftragnehmer genügend Zeit und Muße, die Anforderungen zu erheben und die Anforderungen gemeinsam zu analysieren und zu priorisieren, damit der Auftragnehmer im Anschluss daran ein gut kalkuliertes Angebot erstellen kann.
:::
:::globalParagraph
Als Auftragnehmer haben wir aber die Erfahrung gemacht, dass dies in der Realität so oft nicht abzubilden ist. In anderen Worten: Zeit ist Geld und Geld muss zuerst einmal verdient werden. Der Kunde ist darauf angewiesen, so schnell wie möglich ein Angebot zu erhalten, das er beauftragen kann, damit das gewünschte Produkt so schnell wie möglich umgesetzt wird. Der Auftragnehmer ist ebenfalls an einer schnellen Auftragserteilung interessiert, um Planungssicherheit zu haben. Eine umfassende Anforderungserhebung und -analyse findet manchmal sogar erst nach der Beauftragung statt.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
VIER ERKENNTNISSE FÜR EINE ERFOLGREICHE ANFORDERUNGSPHASE
:::</p>
<p><img src="/img/blogs/cow.jpg" alt="cow">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Im Folgenden stellen wir vier Erkenntnisse aus unserer Erfahrung dar, die zeigen, wie man die Anforderungserhebung und -analyse in Auftragskalkulationen abbilden und die Risiken für Kunde und Auftragnehmer verringern kann.
:::</p>
<p>:::globalTitle{:size="md" .mb-5}
ZEITTRACKING SCHON VOR PROJEKTBEGINN
:::
:::globalParagraph
Als Auftragnehmer sollte man kontinuierlich monitoren, wie viel Zeit für die Auftragsklärung benötigt wird. Nur dann kann man Erfahrungswerte sammeln, und weiß später, wie aufwändig sich der Prozess gestaltet, und kann die Kosten besser einpreisen.
:::</p>
<p>:::globalTitle{:size="md" .mb-5}
KONZEPTIONSPHASEN EINPLANEN
:::
:::globalParagraph
Im Angebot sollte eine zusätzliche Konzeptionsphase dargestellt werden – je nach Umfang von einigen Stunden bis hin zu ganzen Tagen. Darin sollte dann nicht nur die notwendige Zeit für die Softwarearchitektur, sondern auch Zeit für grundsätzliche Absprachen mit dem Kunden berücksichtigt werden.
Vor allem, wenn eine Ausschreibung nicht klar formuliert ist, sollte diese Angebotsposition besonders berücksichtigt werden.
:::</p>
<p>:::globalTitle{:size="md" .mb-5}
GRENZEN DEFINIEREN
:::
:::globalParagraph
Das Angebot sollte klare Grenzen definieren. Vor allem für Kunden ist oft nicht ganz eindeutig, was sich hinter einzelnen Angebotspunkten versteckt. Ggf. ist es sinnvoll, das eigentliche Angebot um ein Dokument zu ergänzen, das die einzelnen Punkte genauer erläutert.
:::</p>
<p>:::globalTitle{:size="md" .mb-5}
PROJEKTMANAGEMENTKOSTEN BESSER AUFTEILEN
:::
:::globalParagraph
Projektmanagement wird oft als “unproduktiver” Overhead gesehen. Je höher die Projektmanagementkosten, desto schwieriger wird es, die Notwendigkeit dieser Position dem Kunden gegenüber zu erläutern.
:::
:::globalParagraph
Wir schlagen deshalb vor, in der Kalkulation die einzelnen technischen Angebotspositionen um einen gewissen Anteil an PM-Zeit zu erhöhen. Das Angebot an den Kunden enthält dann trotzdem den finalen Zeit- und Kostenaufwand. Damit wird der extra ausgewiesene PM-Anteil im Angebot nicht zu hoch. Der zusätzliche PM-Anteil kann für Absprachen mit dem Kunden genutzt werden und sollte im Projektcontrolling auch immer getrennt von der technischen Umsetzung gemonitort werden.
:::
:::globalParagraph
Eine fünfte Erkenntnis für die erfolgreiche Umsetzung von Projekten ist auch die Definition von Abnahmekriterien.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
FAZIT: DER ANFORDERUNGSPROZESS BEI BLUESHOE
:::
:::globalParagraph
Zu Beginn unserer Unternehmensgeschichte haben sich die Entwickler noch stark in den Anforderungsprozess eingebracht und sehr viel persönlich mit den Kunden kommuniziert. Manchmal wurde Anforderungen sogar „on the fly” erhoben und gleichzeitig umgesetzt. Aufgrund der steigenden Anzahl an Kunden und Projekt haben wir erkannt, dass dieses Vorgehen ab einer gewissen Größe nicht mehr möglich ist. Seither nimmt der Anforderungsprozess in unserem Projektmanagement eine zentrale Stelle ein und wird von unseren Projektmanagern durchgeführt.
:::
:::globalParagraph
Uns ist es dabei besonders wichtig, dass wir immer flexibel auf Änderungen in den Anforderungen reagieren können, die sich während der Projektimplementierung ergeben. Die konsequente Anforderungserhebung gibt aber nicht nur uns, sondern vor allem auch unseren Kunden eine wesentlich höhere Planungssicherheit hinsichtlich des Umsetzungszeitraums eines Projektes.
:::</p>]]></content:encoded>
            <category>Projekt Management</category>
            <enclosure url="https://blueshoe.de/img/blogs/requirements-analysis-in-project-management.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Kostenoptimierung eines Azure Kubernetes Clusters]]></title>
            <link>https://blueshoe.de/blog/azure-kubernetes-kostenoptimierung</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/azure-kubernetes-kostenoptimierung</guid>
            <pubDate>Wed, 26 Feb 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Cloud-Ressourcen sind mächtig und praktisch, aber teuer – besonders Kubernetes-Cluster. In diesem Blogpost zeigen wir, wie wir in einem Bestandsprojekt die Azure Kubernetes Service Kostenoptimierung erfolgreich umgesetzt haben. Dabei stellen wir Strategien, Tools und Best Practices vor, die geholfen haben, AKS Kosten zu reduzieren, ohne die Performance zu beeinträchtigen.</p>
<p><img src="/img/blog/aks.svg" alt="Kostenoptimierung eines Azure Kubernetes Clusters">{.object-cover .max-w-full .mb-5}</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Ausgangslage
:::
:::GlobalParagraph
Das Projekt wird vollständig in der Azure Cloud betrieben und nutzt zwei AKS Kubernetes Cluster: ein Staging- und ein Produktiv-System. Diese generieren Cloud-Ressourcen wie virtuelle Maschinen, Festplatten, Load Balancer und IP-Adressen.
:::
:::GlobalParagraph
Zusätzlich kommen diverse Azure-Services zum Einsatz, darunter eine Managed Database, Storage für Media-Daten und eine Redis-Instanz. Vor der Optimierung lagen die Kosten bei ca. 100 € pro Tag.
:::</p>
<p><img src="/img/blog/250212_azure-prior.png" alt="Vorher">{.object-cover .max-w-full .mb-5}</p>
<p>::GlobalTitle{:size="lg" .mb-4}
Analyse mit Azure Cost Management
::
::GlobalParagraph
Der erste Schritt zur Kubernetes Cluster Kostenmanagement war eine Analyse mit Azure Cost Management für Kubernetes. Das Tool bietet detaillierte Einblicke, welche Services wie viel kosten - ein sehr guten Anhaltspunkt, bei welchen Services es lohnend sein kann genauer hinzuschauen.
::</p>
<p>::GlobalParagraph
Direkt fiel uns eine abgeschaltete, aber noch existierende VM auf. Diese hat zwar wenig Kosten verursacht, war aber dennoch unnötig und konnte entfernt werden.
::</p>
<p>::GlobalParagraph
Die größten Kostentreiber waren jedoch die virtuellen Maschinen – insbesondere die Node Pools der beiden AKS Cluster. Ein detaillierter Blick auf die Kubernetes Ressourcenoptimierung zeigte, dass die Anzahl der Nodes nicht optimal auf den tatsächlichen Bedarf abgestimmt war. Auch hier unterstützt Azure – nämlich mit einem Monitoring der CPU- und der Memory-Auslastung.
::</p>
<p>::GlobalParagraph
Auch Azure Log Analytics erwies sich als unerwartet hoher Kostenpunkt. Dieser war für den Production Cluster konfiguriert. Sämtliche Logs der Kubernetes-Ressourcen wurden langfristig gespeichert, ohne aktiv genutzt zu werden.
::</p>
<p>::GlobalTitle{:size="lg" .mb-4}
Getroffene Maßnahmen
::
::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Node Pools optimieren
::</p>
<p>::GlobalParagraph
Basierend auf diesen Erkenntnissen haben wir eine Reihe gezielter Optimierungsmaßnahmen umgesetzt.
::
::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li>Der <strong>Staging-Cluster</strong> wurde von 3 auf 1-3 Nodes umgestellt – mit Auto-Scaling. Damit laufen in der Regel nur noch 2 Nodes.</li>
<li>Der <strong>Production-Cluster</strong> nutzt zwei Nodepools: System-Mode (fix auf 2 Nodes) und User-Mode (Auto-Scaling auf 1-6 Nodes). Das spart Ressourcen und passt sich dynamisch der Last an.
::</li>
</ul>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Virtuelle Maschinen anpassen
::
::GlobalParagraph
Eine zu groß dimensionierte VM für die Managed Database wurde verkleinert. Dank Azure Cost Management für Kubernetes war klar, dass die vorhandene Rechenleistung überdimensioniert war. Die Umstellung erfolgte reibungslos und kann jederzeit rückgängig gemacht werden, falls der Bedarf steigt.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Log Analytics deaktivieren
::
::GlobalParagraph
Die hohen Kosten von Azure Log Analytics standen in keinem Verhältnis zur tatsächlichen Nutzung. Die Deaktivierung führte zu einer erheblichen Einsparung, die sich erst nach einigen Wochen vollständig bemerkbar machte, da Logs bis zu 90 Tage gespeichert wurden.
::</p>
<p><img src="/img/blog/250212_log-analytics.png" alt="Log Analytics deaktivieren">{.object-cover .max-w-full .mb-5}</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Unnötige Ressourcen entfernen
::
::GlobalParagraph
Eine ungenutzte VM eines alten GitLab Runners wurde gelöscht. Zwar verursachte sie nur ca. 10 € pro Monat, aber solche "vergessenen" Ressourcen summieren sich über die Zeit.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Betriebszeiten mit KEDA optimieren
::
::GlobalParagraph
Beide AKS Cluster werden nur werktags während der Arbeitszeiten benötigt. Mittels <a href="/blog/kubernetes-autoscaling-keda/">KEDA Auto-Scaling</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} wurden Betriebszeiten definiert, sodass Anwendungen außerhalb dieser Zeiten automatisch herunterskaliert werden. Dies senkt die AKS Kosten zusätzlich, da weniger Nodes aktiv sind.
::</p>
<p>::GlobalTitle{:size="lg" .mb-4}
Ergebnis: Über 50% Kostenreduktion
::
::GlobalParagraph
Es hat eine gewisse Zeit gedauert, bis das volle Ausmaß der Kostenersparnis ersichtlich war. Aber die Optimierungsmaßnahmen haben sich gelohnt. Die Betriebskosten sanken von ca. 100 € auf <strong>45 € pro Tag</strong> – eine Einsparung von mehr als 50%!
::</p>
<p>::GlobalParagraph
Einige Maßnahmen, wie der Node-Autoscaler, hätten früher umgesetzt werden können. Andere, wie das Kubernetes Cluster Kostenmanagement durch Betriebszeiten-Optimierung, erforderten mehr Planung, zahlen sich aber langfristig aus.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Lessons Learned
::
::GlobalBlock{.mb-4}
✔ Regelmäßige Kostenanalyse lohnt sich – ohne sie hätten wir unnötige Ressourcen nicht entdeckt.<br>
✔ Auto-Scaling konsequent nutzen – feste Node-Größen  ist oft ineffizient.<br>
✔ Logging hinterfragen – nicht jeder Log-Service ist notwendig oder wirtschaftlich.<br>
::</p>
<p>::GlobalParagraph
<strong>Mehr über Kubernetes &#x26; Kostenoptimierung erfahren?</strong>
::
:::GlobalButton{:url="/technologien/docker-kubernetes/" :label="Erfahre mehr über unsere Kubernetes-Entwicklungsdienste" :color="blue" .mb-6}
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5 .mt-8}
Häufige Fragen
:::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
1. Wie kann ich die Kosten meines Azure Kubernetes Clusters senken?
::
::GlobalParagraph
Du kannst die AKS Kosten reduzieren, indem du Auto-Scaling aktivierst, ungenutzte Ressourcen entfernst, Log-Services optimierst und Betriebszeiten mit <a href="/blog/kubernetes-autoscaling-keda/">KEDA</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} anpasst.
::
::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
2. Welche Tools helfen bei der Azure Kubernetes Kostenoptimierung?
::
::GlobalParagraph
Das Azure Cost Management für Kubernetes bietet detaillierte Analysen. Zusätzlich helfen der Kubernetes Autoscaler und KEDA beim dynamischen Skalieren der Nodes.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
3. Warum ist Auto-Scaling wichtig für die Kostenoptimierung in AKS?
::
::GlobalParagraph
Auto-Scaling passt die Anzahl der Nodes an den tatsächlichen Bedarf an und verhindert Überprovisionierung – eine effektive Maßnahme zur Kubernetes Ressourcenoptimierung.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
4. Wie wirkt sich die Deaktivierung von Azure Log Analytics auf die Kosten aus?
::
::GlobalParagraph
Log Analytics kann hohe laufende Kosten verursachen. Falls du die Logs nicht aktiv nutzt, kann die Deaktivierung zu einer deutlichen Einsparung führen.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
5. Gibt es eine Möglichkeit, Kubernetes Cluster außerhalb der Arbeitszeiten zu reduzieren?
::
::GlobalParagraph
Ja! Mit <a href="/blog/kubernetes-autoscaling-keda/">KEDA Auto-Scaling</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} kannst du deine Cluster außerhalb der Geschäftszeiten automatisch herunterskalieren und so Kosten sparen.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
6. Wie oft sollte ich mein Azure Kubernetes Kostenmanagement überprüfen?
::
::GlobalParagraph
Eine monatliche Analyse mit Azure Cost Management hilft, ineffiziente Ressourcen frühzeitig zu erkennen und Kosten langfristig zu senken.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
7. Welche Node-Pool-Strategie ist am kosteneffizientesten für AKS?
::
::GlobalParagraph
Eine Kombination aus System-Mode- und User-Mode-Pools mit Auto-Scaling ist ideal. System-Mode sollte stabil bleiben, während User-Mode dynamisch skaliert.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
8. Wann lohnt sich die Nutzung von Spot-Instances in AKS?
::
::GlobalParagraph
Spot-Instances sind günstiger, aber nicht immer verfügbar. Sie eignen sich für nicht-kritische Workloads mit flexiblem Ausführungszeitpunkt.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
9. Kann ich Kosten sparen, indem ich auf einen anderen Cloud-Anbieter wechsle?
::
::GlobalParagraph
Ja, Azure ist nicht immer die günstigste Option. Es lohnt sich, Alternativen wie Linode oder Hetzner zu prüfen, falls deine Anforderungen es erlauben.
::</p>]]></content:encoded>
            <category>Kubernetes</category>
            <category>Betrieb</category>
            <enclosure url="https://blueshoe.de/img/blog/aks.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Eine barrierefreie Webseite erstellen]]></title>
            <link>https://blueshoe.de/blog/barrierefreie-webseite-erstellen</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/barrierefreie-webseite-erstellen</guid>
            <pubDate>Tue, 10 Apr 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Eine barrierefreie Website – was gehört eigentlich dazu? Was macht sie aus und welche Vorteile bringt sie? Barrierefreiheit ist kein Trend, sondern eine Verantwortung, welche Agenturen und Website-Betreiber zusammen übernehmen müssen. Dieser Artikel bietet einen kurzen Einstieg Rund um das Thema barrierefreier Websites.</p>
<p><img src="/img/blogs/pexels-marcus-aurelius-4064696.jpg" alt="pexels-marcus-aurelius-4064696">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
<strong>BARRIEREFRE WEBSITES UND INKLUSION – FÜR WEN?</strong>
:::
:::globalParagraph
Die erste Frage, welche sich stellt ist doch – für wen lassen sich eigentlich barrierefreie Websites erstellen? Was könnten Schwierigkeiten bei der Bedienung einer Website sein? Bevor wir zu den Tools und Lösungen kommen, gilt es potentielle Nutzer und deren Barrieren zu identifizieren.
:::
:::globalParagraph
<strong>Barrieren durch Einschränkung des Sehvermögens</strong> - Das Web ist ein sehr visuelles Medium zum Konsum von Inhalten. Einschränkungen im Sehvermögen – sei es beispielsweise eine Farbblindheit oder schwache bis nicht vorhandene Sehkraft schränken die Nutzung und Bedienung von Websites ein.
:::
:::globalParagraph
<strong>Barrieren durch Bewegungseinschränkungen</strong> - Oftmals kann der gesamte Inhalt einer Seite im Netz nicht auf einem Bildschirm dargestellt werden – also scrollen wir einfach weiter. Verlinkungen sind an unterschiedlichen Stellen zu finden – also klicken wir diese mit dem Cursor an. Die gute alte Maus sowie Touchpads sind als primäres Eingabemedium zur Navigation auf Websites etabliert. Für Nutzer mit Bewegungseinschränkungen, bspw. Arthrose in der Hand, lassen sich diese Interaktionen nur schwer bis gar nicht durchführen.
:::
:::globalParagraph
<strong>Barrieren durch Taubheit</strong> - Video- und Audio-Streams finden sich nahezu überall im Web. Menschen mit einer gestörten auditiven Wahrnehmung ist es praktisch unmöglich diese zu konsumieren.
:::
:::globalParagraph
<strong>Barrieren durch Lernschwierigkeiten</strong> - Oftmals werden komplexe Zusammenhänge, Probleme oder gar Lösungen als Inhalte auf Websites dargestellt. Menschen mit Lernschwierigkeiten ist es quasi nicht möglich diese zu verstehen und sich somit zu informieren.
:::
:::globalParagraph
Die Auflistung dieser Behinderungen hat keinen Anspruch auf Vollständigkeit, sondern dient zur Veranschaulichung der bestehenden Probleme und den folgenden potentiellen Lösungen.
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
BARRIEREFREIE KONZEPTION EINER WEBSITE
:::
:::globalParagraph
Am Beginn jedes Projekts in der Softwareentwicklung steht natürlich ihre Konzeption. Typischerweise ist hier der erste Schritt die Inhalte zu bestimmen, welche dem Besucher vermittelt und zur Verfügung gestellt werden sollen. Je nachdem, wen man erreichen möchte und auch wie, ist es sehr hilfreich bereits an dieser Stelle zu wissen, ob und wie stark Barrierefreiheit und Inklusion eine Rolle spielen. Dies gibt allen Projektbeteiligten ein besseres Verständnis bzgl. der Zielsetzungen hinsichtlich Barrierefreiheit und resultiert somit einem besseren Ergebnis.
:::
:::globalParagraph
Entsprechend ist es wichtig, bereits bei der Anforderungsanalyse abzuklopfen –
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li><strong>„Wie viel“ Barrierefreiheit brauchen wir?</strong></li>
<li><strong>Was bedeutet dies für das Projekt?</strong></li>
<li><strong>Steht dies im Einklang mit der Strategie des Unternehmens und der Website selbst?</strong></li>
<li><strong>Ist der Aufwand wirtschaftlich vertretbar?</strong>
:::
:::globalParagraph
Nicht zuletzt ist Inklusion und Barrierefreiheit noch ein Premium-Service, welcher von Webseiten-Betreibern zusätzlich bezahlt werden muss. Natürlich bringt dieser nicht nur Pluspunkte im Karma und des Images, sondern auch potenzielle neue User/Besucher/Kunden.
:::</li>
</ul>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
BARRIEREFREIHEIT UND INKLUSION IN DESIGN, PROGRAMMIERUNG UND REDAKTION
:::
:::globalParagraph
Ein barrierefreier und inklusiver Webauftritt ist nicht die Aufgabe einer dedizierten Person. Vielmehr müssen die verschiedenen Bereiche Design, Programmierung und Redaktion zusammen für einen barrierefreien Auftritt arbeiten. Nur so, kann wirklich vollumfänglich für einen barrierefreien und inklusiven Webauftritt gesorgt werden.
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
BARRIEREFREIHEIT IM DESIGN
:::
:::globalParagraph
Hier spielen vor allem visuelle Aspekte wie Kontrastverhältnisse oder Schriftgrößen eine Rolle aber auch der Einsatz der richtigen Bedienelemente wie z.B. Audio- und Videostreams oder auch ein Button zum Umschalten von „normaler“ auf „einfache“ Sprache.
:::</p>
<p><img src="/img/blogs/fontcheck.jpg" alt="fontcheck">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Um den Kontrast von Schrift und Hintergrund einer Website zu überprüfen gibt es bereits Websites wie <a href="http://accessible-colors.com/">http://accessible-colors.com/</a>{.bs-link-blue :target="_blank"}.
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
BARRIEREFREIHEIT IN DER ENTWICKLUNG
:::
:::globalParagraph
Um Websites barrierefrei zu machen ist vor allem eine Initiative bekannt: ARIA (Accessible Rich Internet Application). Diese ist seit 2014 der vom World Wide Web Consortium empfohlene Standard für die Implementierung barrierefreier Webseiten. Hierbei wird der Quelltext einer Webseite mit speziellen Attributen versehen um diese für assistive Technologien wie Screenreader konsumierbar zu machen.  Aber auch einfach Änderungen wie alt-Attribute für Bilder helfen Nutzern mit Screenreader ein Bild zu interpretieren und gleichzeitig gibt es noch Pluspunkte hinsichtlich der SEO der Webseite.
:::</p>
<p><img src="/img/blogs/aria.jpg" alt="aria">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Durch die semantisch korrekte Benutzung von HTML5 in Kombination mit den Empfehlungen der ARIA-Initiative können assistive Technologien Webseiten für Nutzer mit Behinderungen zugänglich machen. Source: <a href="https://www.w3.org/WAI/intro/aria">https://www.w3.org/WAI/intro/aria</a>{.bs-link-blue :target="_blank"}
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
BARRIEREFREIHEIT IN DER REDAKTION
:::
:::globalParagraph
Auch redaktionelle Inhalte können barrierefrei gestaltet werden. Dies fängt bei einfachen Dingen wie Untertitel für Videoinhalte an. Weiterhin ist es möglich eine „einfache“ Sprachversion von einem Text zu hinterlegen, sodass Nutzer mit Lernschwierigkeiten auch Zugriff auf das Wissen der Webseite haben. Videoinhalte mit Gebärdensprache können taube Nutzer beim Konsumieren einer Webseite unterstützen.
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
BARRIEREFREIHEIT FÜR WEBSITES IST NICHT NUR EIN TREND
:::
:::globalParagraph
Wir haben aufgezeigt, dass Barrierefreiheit und Inklusion für einen Webauftritt keine Dinge sind, welche man sich einfach aus dem Ärmel schüttelt. Neben den hier genannten Maßnahmen zur Steigerung der Barrierefreiheit stehen noch viele weitere, welche es zu beachten gibt. Inklusion und Accessibility sind Themen, welche wir in jüngster Zeit immer wieder in den Anfragen unserer Kunden finden.
:::</p>
<p><img src="/img/blogs/barrierefreiblogpost3.jpg" alt="barrierefreiblogpost3">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Wir denken, dass Barrierefreiheit und Inklusion kein rein wirtschaftlich getriebener Gedanke sein darf, sondern dies auch als menschliche und gesellschaftliche Aufgabe angesehen werden muss.
:::
:::globalParagraph
Beispiele für barrierefreie Webseiten:
:::
:::globalParagraph
<a href="http://www.polizei-nrw.de/im/index.html">http://www.polizei-nrw.de/im/index.html</a>{.bs-link-blue :target="_blank"}
:::
:::globalParagraph
<a href="http://www.bundestag.de/">http://www.bundestag.de/</a>{.bs-link-blue :target="_blank"}
:::
:::globalParagraph
<a href="https://www.virginatlantic.com/">https://www.virginatlantic.com/</a>{.bs-link-blue :target="_blank"}
:::</p>]]></content:encoded>
            <category>Digitalisierung</category>
            <enclosure url="https://blueshoe.de/img/blogs/pexels-marcus-aurelius-4064696.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Das Ende einer Ära für Entwickler: Bitnami stellt kostenlose Container-Images weitgehend ein]]></title>
            <link>https://blueshoe.de/blog/bitnami-und-alternativen</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/bitnami-und-alternativen</guid>
            <pubDate>Wed, 24 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Bitnami, eine der beliebtesten Plattformen für vorkonfigurierte Anwendungs-Container und -Images, beendet sein bisheriges kostenloses Angebot in weiten Teilen.</p>
<p>Stichtag für die Umstellung ist der 29. September 2025. Dieser Schritt, der unter der neuen Eigentümerschaft von Broadcom erfolgt, zwingt tausende Entwickler, DevOps-Teams und Unternehmen zum Handeln. In diesem Beitrag analysieren wir die Hintergründe, zeigen die Konsequenzen auf und geben dir einen klaren Fahrplan für die anstehende Migration.</p>
<p><img src="/img/blog/bitnami-alternatives.svg" alt="Bitnami blurred, dollar notes, Blueshoe">{.object-cover .max-w-full .mb-5}</p>
<h2>1. Einleitung: Der Paukenschlag in der DevOps-Welt</h2>
<p>Bitnami war jahrelang die Abkürzung zu produktionsreifen Stacks: ein riesiges Portfolio an Images für populäre Anwendungen wie WordPress, PostgreSQL, Redis, NGINX, Keycloak oder Kafka - vorkonfiguriert, gut dokumentiert und bis dato in großem Umfang kostenlos. Diese Mischung aus einfacher Installation, großer Auswahl und null Einstiegskosten machte Bitnami zum De-facto-Standard in vielen Teams.</p>
<p>Mit der Ankündigung der Umstellung zum 29. September 2025 ist nun klar: <strong>„Bitnami nicht mehr kostenlos“</strong> ist mehr als eine Schlagzeile - es ist ein Paradigmenwechsel. Unter Broadcoms Regie richtet sich die Produktstrategie stärker auf zahlende Unternehmenskunden und das neue Angebot „Bitnami Secure Images“ aus. Für den Großteil der bisherigen, versionierten Images bedeutet dies das Ende regelmäßiger, frei zugänglicher Updates.</p>
<h2>2. Analyse der Umstellung: Was „nicht mehr kostenlos“ konkret bedeutet</h2>
<h3>Das „Legacy“-Repository</h3>
<ul>
<li>Versionierte Images werden in ein Legacy- bzw. Archiv-Repository verschoben und nicht mehr aktualisiert.</li>
<li>Betroffen ist praktisch die gesamte Bandbreite an Tags - mit Ausnahme einer stark begrenzten Auswahl an <code>:latest</code>-Images.</li>
<li>Sicherheitsrisiko: Ohne Patches häufen sich Schwachstellen (CVEs), Compliance-Anforderungen werden verletzt und Audits erschwert.</li>
</ul>
<h3>Das neue „Bitnami Secure Images“-Abonnement</h3>
<ul>
<li>Leistungsversprechen: kontinuierliche Sicherheits-Updates, Kompatibilitäts-Tests, SLA-getriebener Support.</li>
<li>Preisliche Einordnung: Öffentlich genannte Richtwerte bewegen sich - je nach Paket - im Bereich von ca. 50.000 - 72.000 USD pro Jahr. Für viele Startups, kleinere Unternehmen oder Non-Profit-Teams ist das kaum stemmbar.</li>
</ul>
<h3>Was bleibt kostenlos?</h3>
<ul>
<li>Eine stark eingeschränkte Auswahl an <code>:latest</code>-Images.</li>
<li>Warum <code>:latest</code> in Produktion problematisch ist: kein Version Pinning, fehlende Reproduzierbarkeit, schwer nachvollziehbares Drift-Verhalten - ein <strong>No-Go</strong> für stabile CI/CD und deterministische Releases.</li>
</ul>
<h2>3. Die Konsequenzen für Nutzer: Von brüchigen Pipelines bis zu Sicherheitslücken</h2>
<ul>
<li><strong>Broken Builds</strong>: CI/CD-Pipelines, die feste Versionen referenzieren, schlagen fehl, wenn Images verschwinden oder nicht mehr aktualisiert werden. Build-Caching, SBOM-Erzeugung und Vulnerability-Scans werden unzuverlässig.</li>
<li><strong>Sicherheits-Albtraum</strong>: Ungepatchte Schwachstellen in produktiven Systemen erhöhen das Risiko von Incidents, Datenabflüssen und Compliance-Verstößen (z. B. ISO 27001, SOC 2).</li>
<li><strong>Verlust der Stabilität</strong>: Ohne Version Pinning sind Rollbacks, reproduzierbare Deployments und deterministische Tests kaum möglich. Drift zwischen Staging und Produktion nimmt zu.</li>
</ul>
<h3>Fallbeispiel: Das unvorbereitete Team</h3>
<p>Ein mittelständisches SaaS-Unternehmen nutzt <code>bitnami/postgresql:15.6</code> und <code>bitnami/redis:7.2</code> in dutzenden Pipelines. Nach der Umstellung sind die versionierten Tags nicht mehr verfügbar bzw. erhalten keine Patches. Builds brechen, SRE-Teams müssen Security-Exceptions dokumentieren, und Kundenaudits stellen kritische Fragen zur Patch-Policy. Das Team improvisiert mit <code>:latest</code> - was kurzfristig hilft, jedoch neue Instabilitäten und schwer reproduzierbare Fehler einführt. Die Kosten: Wochen an Engineering-Zeit und ein erhöhtes Sicherheitsrisiko.</p>
<h2>4. Handlungsbedarf: Dein Fahrplan für die Migration</h2>
<h3>Schritt 1: Analyse der eigenen Infrastruktur</h3>
<ul>
<li>Bestandsaufnahme aller Bitnami-Images in Nutzung (Container Registry, SBOMs, Deployment-Manifeste).</li>
<li>Suche in <code>Dockerfile</code>, <code>docker-compose.yml</code>, Helm-Charts und Kustomize-Overlays nach <code>bitnami/*</code>-Referenzen.</li>
<li>Erstelle eine priorisierte Liste nach Kritikalität (Produktion > Staging > Entwicklung) und Exposition.</li>
</ul>
<h3>Schritt 2: Kurzfristige Übergangslösungen (Notfallplan)</h3>
<ul>
<li>Umstellung auf das <code>bitnamilegacy</code>-Repository kann Builds kurzfristig retten - ist aber nur eine temporäre Brücke, da Sicherheits-Updates fehlen.</li>
<li>Parallel: Security-Gates schärfen (Scanner, Policy as Code), klare Ausnahmeregeln mit Ablaufdatum etablieren.</li>
</ul>
<h3>Schritt 3: Langfristige Strategien und Alternativen</h3>
<ul>
<li>Zielbild definieren: Offizielle Images, verifizierte Anbieter oder eigene Hardening-Pipeline?</li>
<li>Infrastruktur-As-Code (IaC) und CI/CD so anpassen, dass Version Pinning, SBOM-Erzeugung, Signaturen (Sigstore/Cosign) und regelmäßige Rebuilds Standard werden.</li>
</ul>
<h2>5. Die besten Alternativen zu Bitnami</h2>
<h3>Offizielle Docker-Images (empfohlen, wo verfügbar)</h3>
<ul>
<li>Vorteile: Nähe zum Upstream, schnelle Sicherheits-Updates, klare Roadmaps, große Community.</li>
<li>Beispiele: <a href="https://hub.docker.com/search?image_filter=official">Docker Hub - Official Images</a>, <a href="https://hub.docker.com/_/postgres">PostgreSQL</a>, <a href="https://hub.docker.com/_/nginx">NGINX</a>, <a href="https://hub.docker.com/_/redis">Redis</a>.</li>
</ul>
<h3>Verifizierte Anbieter</h3>
<ul>
<li><strong>Chainguard</strong>: Fokus auf „<a href="https://github.com/wolfi-dev">Wolfi</a>“-basierten, minimalistischen , signierten und häufig aktualisierten Images. Link: https://www.chainguard.dev/</li>
<li><strong>Iron Bank (DoD/Platform One)</strong>: Härtete, geprüfte Images mit Compliance-Fokus. Link: https://repo1.dso.mil/</li>
<li>Weitere Enterprise-Angebote je nach Technologie-Stack und Compliance-Anforderungen evaluieren.</li>
</ul>
<h3>Der „Do-It-Yourself“-Ansatz: Eigene Images bauen</h3>
<ul>
<li>Vorteile: Volle Kontrolle, reproduzierbare Builds, maximale Transparenz (SBOM, Signaturen), abgestimmtes Patch-Management.</li>
<li>Nachteile: Höherer initialer Aufwand, laufende Wartung und Security-Backporting.</li>
<li>Praxis-Tipps:
<ul>
<li>Multi-Stage-Builds, Minimierung der Base-Images (Distroless, Wolfi, Alpine mit Bedacht), Rootless-Container.</li>
<li>Automatisierte Security-Scans (Trivy, Grype), Signieren mit Cosign, Policy Enforcement (OPA/Gatekeeper, Kyverno).</li>
</ul>
</li>
</ul>
<h3>Community-Helm-Charts und Initiativen</h3>
<ul>
<li>Beobachte Community-Projekte, die als Reaktion auf die Bitnami-Änderungen entstehen (Forks, Maintained Charts, Operatoren).</li>
<li>Prüfe Maintainer-Aktivität, Security-Policy, Update-Frequenz und Migrationspfade.</li>
</ul>
<h2>6. Fazit: Eine Chance für mehr Resilienz</h2>
<p>Die Botschaft „<strong>Bitnami nicht mehr kostenlos</strong>“ ist ein Weckruf. Wer sich zu stark auf einen Anbieter verlässt, riskiert Abhängigkeiten mit Sicherheits- und Stabilitätsfolgen. Nutze die Umstellung, um deine Container-Strategie zu modernisieren: Offizielle Images bevorzugen, Security-by-Default etablieren, Version Pinning und Signaturen konsequent durchsetzen und eigene Build-Pipelines stärken.</p>
<p>Beginne jetzt mit der Umstellung, priorisiere kritische Workloads und dokumentiere deine Sicherheitsmaßnahmen. So erhöhst du die Resilienz deiner Plattform - unabhängig von kurzfristigen Marktänderungen.</p>
<hr>
<h2>Häufig gestellte Fragen (FAQs)</h2>
<h3>1. Was kostet Bitnami jetzt?</h3>
<p>Bitnami orientiert sich mit „Bitnami Secure Images“ an Enterprise-Kunden. Genannte Preisbereiche liegen je nach Paket grob bei 50.000 - 72.000 USD jährlich. Konkrete Konditionen variieren nach Umfang, Support-Level und Vertragsdauer.</p>
<h3>2. Welche Alternativen zu Bitnami gibt es?</h3>
<ul>
<li>Offizielle Docker-Images, wo verfügbar.</li>
<li>Verifizierte Anbieter wie Chainguard oder Iron Bank.</li>
<li>Eigene, gehärtete Images mit automatisierten Pipelines, Signaturen und SBOMs.</li>
</ul>
<h3>3. Kann ich Bitnami-Images nach August 2025 weiterhin verwenden?</h3>
<p>Kurzfristig ja - häufig über Legacy-Repositories oder <code>:latest</code>. Für Produktion ist dies wegen fehlender Patches und mangelnder Reproduzierbarkeit nicht empfehlenswert. Plane eine Migration auf alternative, gepflegte Images.</p>
<hr>
<p>Welche Erfahrungen machst du mit der Umstellung von Bitnami? Welche <strong>Bitnami Alternativen</strong> empfiehlst du, und wie adressierst du die <strong>Bitnami Preise</strong> in deinem Budget? Teile deine Ansätze, Lessons Learned und Tool-Empfehlungen in den Kommentaren.</p>]]></content:encoded>
            <category>Kubernetes</category>
            <category>Sicherheit</category>
            <category>Betrieb</category>
            <enclosure url="https://blueshoe.de/img/blog/bitnami-alternatives.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Blueshoe in Ericeira: Team Building und Arbeiten vor malerischer Kulisse]]></title>
            <link>https://blueshoe.de/blog/blueshoe-offsite-2024-ericeira</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/blueshoe-offsite-2024-ericeira</guid>
            <pubDate>Mon, 08 Jul 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Wo lässt es sich perfekt arbeiten? Genau! In Portugal bei schönem Wetter mit Blick auf den Strand! Um noch genauer zu sein: in Ericeira. Unser normalerweise remote arbeitendes Blueshoe Team hat sich für eine Teamweek persönlich in Ericeira getroffen. Portugal bot für uns nicht nur die perfekte Umgebung zum Arbeiten, sondern auch die Möglichkeit bei entspannten Abenden mit Stadtführung, Pubcrawl und Game Evening mit Live-Musik eine wunderschöne Zeit zusammen zu verbringen.</p>
<p><img src="/img/blogs/ericeira_2024.webp" alt="Blueshoe Ericera 2024">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
Tag 1: Anreise und Welcome Evening
:::
:::globalParagraph
Da unser Blueshoe Team über Europa verteilt ist, begann unsere Teamweek mit einer spannenden Anreise, die aber für alle Mitarbeiter reibungslos verlief. Die Flüge waren gut organisiert, und der Shuttleservice ins Hotel "You and the Sea" war komfortabel und pünktlich. Bereits bei der Ankunft spürten wir die entspannte Atmosphäre dieser malerischen Küstenstadt.
:::
<img src="/img/blogs/PXL_20240610_185053904.webp" alt="drink">{.max-w-full .mb-5}
<img src="/img/blogs/PXL_20240610_185025200.webp" alt="gruppe">{.max-w-full .mb-5}</p>
<p>:::globalParagraph
Der Abend begann mit einem herzlichen Empfang an der Strandbar. Bei Finger Food und Cocktails konnten wir den Blick auf das Meer genießen und ersten Eindrücke austauschen. Der "Welcome Evening" war ein gelungener Start, um die Kollegen in einem entspannten Umfeld besser kennenzulernen, neue Kollegen zu begrüßen und sich auf die kommenden Tage einzustimmen.
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
Tag 2: Arbeit und Erkundungstour
:::
:::globalParagraph
Der zweite Tag war als Arbeitstag geplant. Im Hotel "You and the Sea" fanden wir optimale Bedingungen vor, um produktiv zu sein. Die Konferenzräume waren modern ausgestattet, und die Verpflegung ließ keinerlei Wünsche offen. Vom Frühstück über das Mittagessen bis hin zu kleinen Snacks zwischendurch – wir wurden rundum versorgt.
:::
:::globalParagraph
Am Abend stand eine Stadtführung durch Ericeira auf dem Programm. Ein Guide, der aus Ericeira stammte, zeigte uns die schönsten Ecken der Stadt und brachte uns die Geschichte und Kultur der Region auf kurzweilige Weise nah. Der anschließende Pubcrawl bot eine wunderbare Gelegenheit, das Nachtleben von Ericeira kennenzulernen. In verschiedenen Bars und Pubs konnten wir ins nächtliche Ericeira eintauchen und den Tag bei guter Musik und Getränken ausklingen lassen.
:::
<img src="/img/blogs/20240611_190949.webp" alt="beach">{.max-w-full .mb-5}</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
Tag 3: Arbeit und Game Evening
:::
:::globalParagraph
Auch am dritten Tag gab es Zeit zum Arbeiten. Einige nutzten die Gelegenheit, um sich in Ruhe mit Kollegen auszutauschen, Ideen zu entwickeln oder Projekte voranzutreiben. Die Umgebung und das gute Wetter sorgten dafür, dass die Arbeitstage keineswegs anstrengend waren, sondern im Gegenteil, sehr inspirierend wirkten.
:::
:::globalParagraph
Der Abend stand ganz im Zeichen von Spaß und Unterhaltung. Beim "Game Evening" kamen wir alle noch einmal zusammen, um bei Live-Musik und verschiedenen Spielen den Zusammenhalt im Team zu stärken. Es wurde viel gelacht, gesungen und gespielt. Dieser Abend wird uns allen noch lange in Erinnerung bleiben, denn er zeigte, wie wichtig Teamgeist und gemeinsame Erlebnisse für den Erfolg unseres Unternehmens sind.
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
Tag 4: Arbeit und Dinner auswärts
:::
:::globalParagraph
Auch der vierte Tag begann mit produktiver Arbeitszeit. Danach freuten wir uns auf ein gemeinsames Dinner in einem gemütlichen Restaurant außerhalb des Hotels. Mit Blick auf das Meer bot sich die wunderbare Gelegenheit, die Erlebnisse der vergangenen Tage Revue passieren zu lassen und die Teamdynamik weiter zu stärken.
:::
<img src="/img/blogs/PXL_20240610_135905433.webp" alt="Blick vom Balkon">{.max-w-full .mb-5}</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
Fazit
:::
:::globalParagraph
Unser Blueshoe Team offsite in Ericeira war ein voller Erfolg. Die perfekte Mischung aus Arbeit und Freizeitaktivitäten hat uns nicht nur als Team enger zusammengeschweißt, sondern auch dazu beigetragen, neue Energie und Inspiration für die kommenden Aufgaben zu tanken. Ericeira hat sich als hervorragender Ort erwiesen, um Arbeit und Erholung zu verbinden, und wir sind alle dankbar für diese unvergesslichen Tage.
:::
:::globalParagraph
Ericeira, wir kommen bestimmt wieder!
:::
<img src="/img/blogs/PXL_20240611_162525944.webp" alt="Blick vom Balkon">{.max-w-full .mb-5}</p>]]></content:encoded>
            <category>Team Blueshoe</category>
            <enclosure url="https://blueshoe.de/img/blogs/ericeira_2024.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Verwendung von Cilium für Kubernetes-Netzwerke und Beobachtbarkeit]]></title>
            <link>https://blueshoe.de/blog/cilium-kubernetes-networking-observability</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/cilium-kubernetes-networking-observability</guid>
            <pubDate>Wed, 19 Apr 2023 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>In diesem Artikel geben wir eine Einführung in Cilium, eine Netzwerklösung für Kubernetes, die eBPF für leistungsstarke Netzwerkaufgaben, Sicherheit und Beobachtbarkeit verwendet. Wir behandeln die Installation von Cilium, die Konfiguration von Netzwerkrichtlinien und die Verwendung von Hubble zur Beobachtbarkeit.</p>
<p><img src="/img/blogs/cilium-kubernetes.jpg" alt="cilium_kubernetes">{.object-cover .max-w-full .mb-6}</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Einführung in Cilium und seine Verwendung in Kubernetes
:::
:::GlobalParagraph
Cilium ist eine Netzwerklösung für Kubernetes, die fortschrittliche Netzwerk- und Sicherheitsfunktionen bietet. Sie verwendet eBPF, um leistungsstarke Netzwerkaufgaben, Sicherheit und Beobachtbarkeit in Kubernetes durchzuführen.
:::
:::GlobalParagraph
In diesem Artikel werden wir untersuchen, wie man Cilium für das Kubernetes-Netzwerk verwendet. Wir werden die Grundlagen der Einrichtung von Cilium in einem Cluster, die Konfiguration von Netzwerkrichtlinien und die Verwendung von Hubble zur Beobachtbarkeit behandeln. Wir werden auch bewährte Verfahren für die Verwendung von Cilium in Produktionsumgebungen und die Behebung häufiger Probleme besprechen. Fangen wir an, indem wir Cilium in unseren Kubernetes-Cluster installieren!
:::
:::GlobalParagraph
Hinweis: Wir empfehlen dir die Verwendung von kind, um dies auf deinem lokalen Rechner auszuprobieren. K3d (das unter der Haube k3s verwendet) enthält in seinen Knoten-Images kein Bash, was dazu führt, dass die Cilium-Installation fehlschlägt.
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Cilium installieren
:::
:::GlobalParagraph
Zunächst müssen wir das Cilium CLI gemäß der Dokumentation installieren.
:::
:::GlobalParagraph
Sobald die CLI-Installation abgeschlossen ist, können wir Cilium in unseren Cluster installieren, indem wir Folgendes ausführen:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">$ cilium install
</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Dies installiert Cilium in den Cluster, auf den unser aktueller kubectl-Kontext zeigt. Um eine funktionierende Installation zu überprüfen, verwenden wir:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">$ cilium status --wait
</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Die Ausgabe sollte ungefähr so aussehen:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">    /¯¯\
 /¯¯\__/¯¯\    Cilium:         OK
 \__/¯¯\__/    Operator:       OK
 /¯¯\__/¯¯\    Hubble:         disabled
 \__/¯¯\__/    ClusterMesh:    disabled
    \__/

DaemonSet         cilium             Desired: 1, Ready: 1/1, Available: 1/1
Deployment        cilium-operator    Desired: 1, Ready: 1/1, Available: 1/1
Containers:       cilium             Running: 1
                  cilium-operator    Running: 1
Cluster Pods:     0/3 managed by Cilium
Image versions    cilium             quay.io/cilium/cilium:v1.12.5@sha256:06ce2b0a0a472e73334a7504ee5c5d8b2e2d7b72ef728ad94e564740dd505be5: 1
                  cilium-operator    quay.io/cilium/operator-generic:v1.12.5@sha256:b296eb7f0f7656a5cc19724f40a8a7121b7fd725278b7d61dc91fe0b7ffd7c0e: 1
</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Wenn alles gut aussieht, können wir die ordnungsgemäße Netzwerkverbindung überprüfen, indem wir Folgendes ausführen:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">$ cilium connectivity test
</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Dies erstellt einen dedizierten Namespace und führt einige Tests auf vordefinierten Workloads aus, um die Netzwerkverbindung des Clusters zu testen.
:::
:::GlobalParagraph
Die erfolgreiche Ausgabe sieht wie folgt aus:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">All 31 tests (151 actions) successful, 0 tests skipped, 1 scenarios skipped.
</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Wenn alle Tests erfolgreich durchgeführt wurden, herzlichen Glückwunsch! Wir haben Cilium erfolgreich in unseren Kubernetes-Cluster installiert!
:::</p>
<p>:::GlobalPodcastSection{:videoId="5wNTUUSk1jA" :videoPosition="left" .mb-6}
::::GlobalPreTitle{:color="text-bs-green" .mb-3}
UNSER PODCAST: TOOLS FOR THE CRAFT
::::
::::GlobalTitle{:tag="h3" .mb-6}
E3: Deep dive into Getdeck
::::
::::globalParagraph{:font-size="lg" .mb-4}
Michael und Robert stellen Getdeck vor und demonstrieren es. Außerdem vergleichen sie lokale und entfernte Kubernetes- und Pre-Production-Cluster.
::::
::::globalParagraph{:font-size="lg" }
Weitere Ausgaben unseres Podcasts findest du hier:
::::
::::GlobalButton{:url="/kubernetes-podcast/" :label="Show more" :color="green"}
::::
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Konfigurieren von Netzwerkrichtlinien mit Cilium
:::
:::GlobalParagraph
Netzwerkrichtlinien in Kubernetes werden zur Kontrolle und Filterung des Datenverkehrs verwendet. Standardmäßig kann jeder Pod, der in einem Cluster läuft, mit jedem anderen Pod kommunizieren, was je nach Einrichtung unsicher sein kann. Mithilfe von Netzwerkrichtlinien können wir Regeln implementieren, die nur den Verkehr zulassen, den wir explizit zulassen wollen. Cilium ermöglicht es uns, Regeln auf der HTTP-Ebene festzulegen, wodurch die Netzwerkregeln von unserem Anwendungscode entkoppelt werden.
:::
:::GlobalParagraph
Nun, da Cilium in unserem Cluster läuft, wollen wir es testen, indem wir einige Netzwerkrichtlinien anwenden, um festzulegen, welcher Datenverkehr innerhalb des Clusters sowie nach innen und außen erlaubt ist.
:::
:::GlobalParagraph
Die häufig verwendete Richtlinie "default-deny-ingress" kann mit Cilium wie folgt implementiert werden:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "deny-all-ingress"
spec:
  endpointSelector:
    matchLabels:
  ingress:
  - {}
</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Da der matchLabels-Schlüssel leer ist, wird dies auf jeden Endpunkt angewendet und sperrt effektiv den gesamten eingehenden Datenverkehr innerhalb des Clusters.
:::
:::GlobalParagraph
Da wir möchten, dass unsere Dienste miteinander kommunizieren, fügen wir eine Richtlinie hinzu, die explizit den eingehenden Datenverkehr zwischen zwei Diensten erlaubt.
:::
:::GlobalParagraph
Eine einfache "ingress-allow" Richtlinie könnte wie folgt aussehen:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "ingress-allow"
spec:
  endpointSelector:
    matchLabels:
      role: backend-api
  ingress:
  - fromEndpoints:
    - matchLabels:
        role: client
</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Diese Netzwerkrichtlinie erlaubt allen eingehenden Datenverkehr von Endpunkten mit dem Label <code>role: client</code>, die sich mit Endpunkten mit dem Label <code>role: backend-api</code>.
:::
:::GlobalParagraph
Bewegen wir uns auf den OSI-Modellschichten nach oben, können wir auch HTTP-Methoden und Pfade wie folgt einschränken:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "rule1"
spec:
  description: "Allow HTTP GET /api from app=client to app=api"
  endpointSelector:
    matchLabels:
      app: api
  ingress:
  - fromEndpoints:
    - matchLabels:
        app: client
    toPorts:
    - ports:
      - port: "80"
        protocol: TCP
      rules:
        http:
        - method: "GET"
          path: "/api"
</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Dies erlaubt eingehenden HTTP-Verkehr von Endpunkten mit dem Label <code>app: client</code> zu Endpunkten mit dem Label <code>app: api</code>, solange die HTTP-Methode GET ist und der Pfad "/public" ist. Anfragen an andere Ports als 80 werden abgelehnt, während andere HTTP-Verben und andere Pfade abgelehnt werden.
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Cilium für Observability verwenden
:::
:::GlobalParagraph
Cilium Hubble ist ein leistungsstarkes Observability-Tool, das tiefe Einblicke in den Netzwerkverkehr und die Sicherheit eines Kubernetes-Clusters bietet. In diesem Abschnitt werden wir erkunden, wie man Hubble für Observability einrichtet und verwendet.
:::</p>
<p>:::GlobalTitle{:size="md" :tag="h3" .mb-5}
Einrichten von Hubble
:::
:::GlobalParagraph
Um Hubble zu verwenden, müssen wir es, wie folgt, in unserem Kubernetes-Cluster bereitstellen:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">$ cilium hubble enable --ui
</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Wenn wir "cilium status" erneut ausführen, sehen wir, dass Hubble aktiviert und ausgeführt wird.
:::
:::GlobalParagraph
Um die gesammelten Daten zu nutzen, installieren wir das Hubble CLI wie in der Dokumentation beschrieben. Sobald die Installation abgeschlossen ist, können wir den Hubble-API-Zugriff überprüfen, indem wir zuerst ein Port-Forwarding für Hubble erstellen und dann die Hubble CLI verwenden, um den Hubble-Status zu überprüfen und die <a href="/loesungen/api-entwicklung/">API</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} abzufragen. Dies geschieht wie folgt:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">$ cilium hubble port-forward&#x26;
$ hubble status
$ hubble observe
Apr  4 07:14:29.119: 10.244.0.166:37906 (host) -> cilium-test/echo-same-node-9f8754876-ns7tx:8181 (ID:2357) to-endpoint FORWARDED (TCP Flags: ACK)
Apr  4 07:14:29.120: 10.244.0.166:41980 (host) &#x3C;- cilium-test/echo-same-node-9f8754876-ns7tx:8080 (ID:2357) to-stack FORWARDED (TCP Flags: ACK, PSH)
Apr  4 07:14:29.121: 10.244.0.166:41980 (host) -> cilium-test/echo-same-node-9f8754876-ns7tx:8080 (ID:2357) to-endpoint FORWARDED (TCP Flags: ACK, FIN)
Apr  4 07:14:29.121: 10.244.0.166:41980 (host) &#x3C;- cilium-test/echo-same-node-9f8754876-ns7tx:8080 (ID:2357) to-stack FORWARDED (TCP Flags: ACK, FIN)
Apr  4 07:14:29.121: 10.244.0.166:41980 (host) -> cilium-test/echo-same-node-9f8754876-ns7tx:8080 (ID:2357) to-endpoint FORWARDED (TCP Flags: ACK)
Apr  4 07:14:30.119: 10.244.0.166:41986 (host) -> cilium-test/echo-same-node-9f8754876-ns7tx:8080 (ID:2357) to-endpoint FORWARDED (TCP Flags: SYN)
Apr  4 07:14:30.119: 10.244.0.166:41986 (host) &#x3C;- cilium-test/echo-same-node-9f8754876-ns7tx:8080 (ID:2357) to-stack FORWARDED (TCP Flags: SYN, ACK)
Apr  4 07:14:30.119: 10.244.0.166:41986 (host) -> cilium-test/echo-same-node-9f8754876-ns7tx:8080 (ID:2357) to-endpoint FORWARDED (TCP Flags: ACK)
Apr  4 07:14:30.119: 10.244.0.166:37912 (host) -> cilium-test/echo-same-node-9f8754876-ns7tx:8181 (ID:2357) to-endpoint FORWARDED (TCP Flags: SYN)
Apr  4 07:14:30.119: 10.244.0.166:37912 (host) &#x3C;- cilium-test/echo-same-node-9f8754876-ns7tx:8181 (ID:2357) to-stack FORWARDED (TCP Flags: SYN, ACK)
Apr  4 07:14:30.119: 10.244.0.166:37912 (host) -> cilium-test/echo-same-node-9f8754876-ns7tx:8181 (ID:2357) to-endpoint FORWARDED (TCP Flags: ACK)
Apr  4 07:14:30.119: 10.244.0.166:37912 (host) -> cilium-test/echo-same-node-9f8754876-ns7tx:8181 (ID:2357) to-endpoint FORWARDED (TCP Flags: ACK, PSH)
Apr  4 07:14:30.119: 10.244.0.166:41986 (host) -> cilium-test/echo-same-node-9f8754876-ns7tx:8080 (ID:2357) to-endpoint FORWARDED (TCP Flags: ACK, PSH)
Apr  4 07:14:30.120: 10.244.0.166:37912 (host) &#x3C;- cilium-test/echo-same-node-9f8754876-ns7tx:8181 (ID:2357) to-stack FORWARDED (TCP Flags: ACK, PSH)
Apr  4 07:14:30.120: 10.244.0.166:37912 (host) &#x3C;- cilium-test/echo-same-node-9f8754876-ns7tx:8181 (ID:2357) to-stack FORWARDED (TCP Flags: ACK, FIN)
Apr  4 07:14:30.120: 10.244.0.166:37912 (host) -> cilium-test/echo-same-node-9f8754876-ns7tx:8181 (ID:2357) to-endpoint FORWARDED (TCP Flags: ACK, FIN)
Apr  4 07:14:30.120: 10.244.0.166:37912 (host) -> cilium-test/echo-same-node-9f8754876-ns7tx:8181 (ID:2357) to-endpoint FORWARDED (TCP Flags: ACK)
Apr  4 07:14:30.121: 10.244.0.166:41986 (host) &#x3C;- cilium-test/echo-same-node-9f8754876-ns7tx:8080 (ID:2357) to-stack FORWARDED (TCP Flags: ACK, PSH)
Apr  4 07:14:30.121: 10.244.0.166:41986 (host) -> cilium-test/echo-same-node-9f8754876-ns7tx:8080 (ID:2357) to-endpoint FORWARDED (TCP Flags: ACK, FIN)
Apr  4 07:14:30.121: 10.244.0.166:41986 (host) &#x3C;- cilium-test/echo-same-node-9f8754876-ns7tx:8080 (ID:2357) to-stack FORWARDED (TCP Flags: ACK, FIN)
</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Wenn du grafische Benutzeroberflächen bevorzugst, kannst du auch Hubble UI in deinem Cluster bereitstellen.  Hubble UI bietet dir Zugriff auf eine grafische Servicemap, die zur visuellen Überprüfung des Datenverkehrs im Cluster verwendet werden kann. Für unser Beispiel-Setup sieht Hubble UI folgendermaßen aus:
:::</p>
<p><img src="/img/blogs/hubble-ui.jpg" alt="hubble_ui">{.object-cover .max-w-full .mb-6}</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Fazit
:::
:::GlobalParagraph
Zusammenfassend bietet Cilium eine robuste Netzwerklösung für Kubernetes, die es Benutzern ermöglicht, präzise Netzwerkrichtlinien durchzusetzen und Netzwerkaktivitäten in Echtzeit zu verfolgen. Durch sein cloud-natives Design und seine auf eBPF basierende Architektur ist Cilium die erste Wahl für Benutzer, die erweiterte Netzwerkfunktionen in ihren Kubernetes-Setups suchen.
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Weitere Schritte
:::
:::GlobalParagraph
Cilium bietet noch viele weitere Funktionen, die wir in diesem Beitrag nicht behandeln können. Hier ist eine kurze Zusammenfassung dessen, wozu Cilium noch fähig ist.
:::</p>
<p>:::GlobalTitle{:size="md" .mb-5}
Cluster Mesh
:::
:::GlobalParagraph
Mit Cluster Mesh können wir Netzwerkpfade über mehrere Kubernetes-Cluster hinweg erweitern, sodass Endpunkte in verbundenen Clustern miteinander kommunizieren können, während gleichzeitig Netzwerkrichtlinien durchgesetzt werden.
:::</p>
<p>:::GlobalTitle{:size="md" .mb-5}
Service Mesh
:::
:::GlobalParagraph
Cilium Service Mesh zielt darauf ab, traditionelle Service-Mesh-Lösungen wie Linkerd zu ersetzen. Es gibt jedoch einen entscheidenden Unterschied: Während Linkerd auf Sidecar-Proxies zur Verwaltung des Datenverkehrs zwischen Pods angewiesen ist, verwendet Cilium Service Mesh auf der Knotenebene eBPF, um den Datenverkehr zu verwalten. Dies verbessert die Leistung, reduziert die Last und entkoppelt den Service Mesh weiter von den tatsächlichen Workloads.
:::</p>]]></content:encoded>
            <category>Kubernetes</category>
            <category>Betrieb</category>
            <category>Sicherheit</category>
            <enclosure url="https://blueshoe.de/img/blogs/cilium-kubernetes.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Fehlerfreie Webanwendungen mit Cypress]]></title>
            <link>https://blueshoe.de/blog/cypress-end-to-end-testing</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/cypress-end-to-end-testing</guid>
            <pubDate>Tue, 18 Feb 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Das Testen von Webanwendungen ist ein zentraler Bestandteil moderner Softwareentwicklung. Manuelles Testen ist dabei oft zeitaufwendig, fehleranfällig und schwer skalierbar. Cypress ist ein End-to-End-Testing-Tool, das speziell für die Anforderungen des automatisierten Testens von Webanwendungen entwickelt wurde.</p>
<p><img src="/img/blog/cypress.svg" alt="Fehlerfreie Webanwendungen mit Cypress">{.object-cover .max-w-full .mb-5}</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Einleitung
:::
:::GlobalParagraph
In der heutigen Webentwicklung ist die Sicherstellung der Qualität einer Anwendung entscheidend für den Erfolg. Um eine reibungslose Benutzererfahrung zu garantieren, hat sich das automatisierte End-to-End-Testing (E2E) als unverzichtbarer Bestandteil des Entwicklungsprozesses etabliert. Eines der leistungsfähigsten Tools für automatisiertes Testing ist <a href="https://github.com/cypress-io/cypress">Cypress</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}, das wir in diesem Artikel näher beleuchten möchten.
:::
:::GlobalParagraph
Cypress ermöglicht es Entwicklern, Tests direkt im Browser durchzuführen und so die Benutzererfahrung realitätsnah nachzubilden. Mit seinem Fokus auf Geschwindigkeit, Benutzerfreundlichkeit und Stabilität ist Cypress ideal, um Webanwendungen umfassend zu testen und potenzielle Fehler frühzeitig zu erkennen. In diesem Artikel erfährst du, warum Cypress in modernen Webprojekten eine wichtige Rolle spielt und wie du es erfolgreich einsetzen kannst.
:::</p>
<p>:::GlobalButton{:url="/technologien/cypress/" :label="Erfahre mehr über unsere Cypress-Entwicklungsdienste" :color="blue" .mb-6}
:::</p>
<p>::GlobalTitle{:size="lg" .mb-4}
Was ist Cypress?
::
::GlobalParagraph
Cypress ist ein modernes End-to-End-Testing-Framework, das speziell für Webanwendungen entwickelt wurde. Es basiert auf JavaScript und ist tief in den Browser integriert. Das bedeutet, dass Tests in der gleichen Umgebung ausgeführt werden, in der die Benutzer mit der Anwendung interagieren – nämlich direkt im Browser. Dies sorgt für besonders realitätsnahe Ergebnisse. <a href="https://www.cypress.io/">Lerne mehr über Cypress. Zur offiziellen Cypress-Seite.</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}
::</p>
<p>::GlobalTitle{:size="lg" .mb-4}
Warum ist Cypress so besonders?
::
::GlobalParagraph
Cypress hebt sich durch seine enge Integration in den Browser und seine Benutzerfreundlichkeit ab. Hier sind einige seiner <a href="https://docs.cypress.io/app/get-started/why-cypress">herausragenden Eigenschaften</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}:
::
::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li><strong>Einfache Einrichtung:</strong> Kein komplexes Setup – installiere Cypress mit einem Befehl, und du kannst direkt loslegen.</li>
<li><strong>Tests im Browser:</strong> Deine Tests laufen in der gleichen Umgebung wie die Anwendung, was die Ergebnisse besonders realistisch macht.</li>
<li><strong>Schnelles Debugging:</strong> Mit Screenshots, Videos und interaktiven Laufzeiten siehst du sofort, wo es hakt.
::
::GlobalParagraph
Im Vergleich zu anderen Testing-Frameworks wie Selenium bietet Cypress eine einfachere Benutzererfahrung. Die Integration in den Browser und die schnellere Testausführung machen es zur optimalen Wahl für automatisiertes Testen moderner Webanwendungen.
::</li>
</ul>
<p><img src="/img/blog/Cypress_Logotype_Dark-Color.svg" alt="Cypress logo">{.mx-auto .h-48 .max-w-full .my-4}</p>
<p>::GlobalTitle{:size="lg" .mb-4}
Vorteile von Cypress
::
::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Performance und Geschwindigkeit
::
::GlobalParagraph
Cypress bietet Echtzeit-Feedback, sodass Entwickler Fehler sofort erkennen und beheben können. Dadurch wird der gesamte Entwicklungsprozess effizienter. Du kannst Tests schnell schreiben und ausführen, was besonders bei umfangreichen Projekten ein großer Vorteil ist.
::
::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Realitätsnahe Tests
::
::GlobalParagraph
Da Cypress-Tests direkt im Browser ausgeführt werden, spiegeln sie die tatsächliche Benutzererfahrung wider. Dies stellt sicher, dass Anwendungen unter realen Bedingungen fehlerfrei funktionieren.
::
::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Benutzerfreundlichkeit und einfache Integration
::
::GlobalParagraph
Cypress verwendet eine intuitive Syntax, die eine schnelle Einarbeitung ermöglicht.  Zudem lässt sich Cypress problemlos in bestehende Entwicklungs-Workflows integrieren, inklusive Continuous Integration (CI) und Continuous Deployment (CD).
::
::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Flexibilität und Anpassbarkeit
::
::GlobalParagraph
Cypress bietet eine Vielzahl von APIs und Konfigurationsmöglichkeiten, die es ermöglichen, Tests an die spezifischen Anforderungen eines Projekts anzupassen. Neben der Möglichkeit, Benutzeroberflächen zu testen, unterstützt Cypress auch API-Tests und lässt sich nahtlos in CI/CD-Pipelines integrieren.
::</p>
<p>:::GlobalButton{:url="/technologien/cypress/" :label="Möchtest du deine Teststrategie verbessern? Sieh dir unsere Cypress-Entwicklungslösungen an." :color="blue" .mb-6}
:::</p>
<p>::GlobalTitle{:size="lg" .mb-4}
Cypress in der Praxis
::
::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Beispiel 1: Cypress im E-Commerce
::
::GlobalParagraph
Ein führendes E-Commerce-Unternehmen implementierte Cypress, um den gesamten Checkout-Prozess automatisiert zu testen. Dadurch lassen sich Probleme in Zahlungs- und Versandprozessen frühzeitig erkennen, was die Benutzererfahrung verbessert und Kaufabbrüche reduziert.
::
::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Beispiel 2: Cypress für SaaS-Plattformen
::
::GlobalParagraph
Ein Anbieter einer Projektmanagement-Plattform nutzt Cypress, um kritische Funktionen wie Benutzerregistrierung und Team-Kollaboration zu testen. So konnten Entwicklungszyklen verkürzt und die Stabilität der Anwendung verbessert werden.
::
::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Beispiel 3: Finanzplattform mit Continuous Integration
::
::GlobalParagraph
Eine Finanzplattform mit hohen Sicherheitsanforderungen setzt Cypress für automatisiertes End-to-End-Testing in der Continuous Integration ein. Fehler in Transaktionsprozessen werden so frühzeitig erkannt und behoben.
::
::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Integration in bestehende Systeme
::
::GlobalParagraph
Dank <a href="https://learn.cypress.io/advanced-cypress-concepts/running-cypress-in-ci">umfassender Dokumentation und Unterstützung für gängige CI/CD-Tools</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} lässt sich Cypress nahtlos in bestehende Entwicklungsumgebungen integrieren.
::</p>
<p>::GlobalTitle{:size="lg" .mb-4}
Implementierung von Cypress: Schritt-für-Schritt-Anleitung
::
::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Schritt 1: Installation von Cypress
::
::GlobalParagraph
Die Installation von Cypress ist der erste Schritt, um automatisierte Tests in deinem Projekt zu integrieren. Cypress wird als npm-Paket installiert, was bedeutet, dass du Node.js und npm (Node Package Manager) in deinem Projekt eingerichtet haben musst.
::
::GlobalParagraph
Befehl zur Installation:
::
::GlobalBlock{.ol-decimal .mb-4}</p>
<ol>
<li>Öffne dein Terminal oder deine Befehlszeile und navigiere zum Root-Verzeichnis deines Projekts.</li>
<li>Führe den folgenden Befehl aus, um Cypress als Entwicklungsabhängigkeit in deinem Projekt zu installieren:
::</li>
</ol>
<p>::BlogCode{.mb-4}</p>
<pre><code class="language-bash">$ npm install cypress --save-dev
</code></pre>
<p>::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Schritt 2: Cypress starten
::
::GlobalParagraph
Nach der Installation kannst du Cypress zum ersten Mal starten und die interaktive Testumgebung erkunden. Verwende dazu den folgenden Befehl:
::
::BlogCode{.mb-4}</p>
<pre><code class="language-bash">$ npx cypress open
</code></pre>
<p>::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Schritt 3: Erstellen und Strukturieren der ersten Tests
::
::GlobalParagraph
Cypress erwartet, dass deine Tests in einem speziellen Verzeichnis gespeichert werden, das sich unter cypress/integration befindet. Wenn du Cypress zum ersten Mal öffnest, wird dieses Verzeichnis automatisch erstellt.
::
::GlobalBlock{.ol-decimal .mb-4}</p>
<ol>
<li>Erstelle eine neue Testdatei in diesem Verzeichnis, z. B. example.spec.js.</li>
<li>Schreibe einen einfachen Test, um sicherzustellen, dass Cypress funktioniert. Hier ist ein Beispiel für einen Basis-Test, der überprüft, ob deine Website ordnungsgemäß geladen wird:
::</li>
</ol>
<p>::BlogCode{.mb-4}</p>
<pre><code class="language-typescript">describe('Meine erste Cypress-Testreihe', () => {
  it('Besucht die Startseite', () => {
    cy.visit('https://deine-website.com')
    cy.contains('Willkommen')
  })
})
</code></pre>
<p>::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Schritt 4: Testausführung
::
::GlobalParagraph
Nachdem du deinen ersten Test erstellt hast, kannst du ihn in der Cypress Test Runner-Oberfläche ausführen. Wähle einfach die erstellte example.spec.js-Datei im Cypress-Fenster aus, und der Test wird automatisch im Browser ausgeführt. Du kannst dabei die Testschritte in Echtzeit beobachten.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Schritt 5: Cypress in CI/CD-Pipelines integrieren
::
::GlobalParagraph
Ein wichtiger Vorteil von Cypress ist die einfache Integration in CI/CD-Pipelines, damit deine Tests bei jeder Änderung am Code automatisch ausgeführt werden können. Hier ist ein allgemeiner Ablauf, wie du Cypress in eine CI-Umgebung wie GitLab CI, Jenkins oder GitHub Actions integrieren kannst:
::
::GlobalBlock{.ol-decimal .mb-4}</p>
<ol>
<li>Erstelle eine CI-Konfigurationsdatei in deinem Projekt (z. B. <code>.gitlab-ci.yml</code> oder <code>.github/workflows/test.yml</code>).</li>
<li>Füge die Cypress-Befehle in die Pipeline-Konfiguration ein. Hier ist ein einfaches Beispiel für GitLab CI:
::
::BlogCode{.mb-4}</li>
</ol>
<pre><code class="language-yaml">stages:
  - test

cypress-test:
  image: cypress/base:14
  stage: test
  script:
    - npm ci
    - npx cypress run
</code></pre>
<p>::</p>
<p>::GlobalBlock{.ol-decimal .mb-4}
3. Führe die Pipeline aus, um sicherzustellen, dass Cypress-Tests automatisch bei jedem neuen Commit oder Merge-Request ausgeführt werden.
::</p>
<p>:::GlobalCallAnExpert{:bg="bg-bs-blue"}
#buttonBlockTitle
Wir können auch deine Anwendung mit Cypress Tests fehlerfrei machen.
:::</p>
<p>::GlobalTitle{:size="lg" .mb-4}
Best Practices für das Schreiben von Cypress-Tests
::
::GlobalParagraph
Um das Beste aus Cypress herauszuholen und deine Tests effizient und wartbar zu gestalten, gibt es einige Best Practices, die du beachten solltest:
::
::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li>Vermeide direkte Abhängigkeiten zwischen Tests: Jeder Test sollte unabhängig von anderen Tests sein. Dies erleichtert das Debuggen und macht deine Tests robuster.</li>
<li>Nutze Mocks und Stubs, um API-Abhängigkeiten zu reduzieren: Verwende Cypress' <code>cy.intercept()</code>-Funktion, um API-Aufrufe zu simulieren und unnötige Abhängigkeiten zu vermeiden.</li>
<li>Verwende Selektoren gezielt: Verwende gezielte Datenattribute wie <code>data-cy="element-name"</code>, um deine Tests stabiler zu gestalten, selbst wenn sich das Layout ändert.</li>
<li>Vermeide zu viele "wait"-Befehle: Nutze die eingebauten Funktionen von Cypress, um auf das Laden von Inhalten zu warten, z. B. <code>cy.get()</code>, anstatt willkürliche <code>cy.wait()</code>-Befehle hinzuzufügen.</li>
<li>Nutze die Cypress-Dashboard-Option: Für größere Test-Suiten bietet das Dashboard detaillierte Berichte, Screenshots und Videos über fehlgeschlagene Tests.
::</li>
</ul>
<p>::GlobalTitle{:size="lg" .mb-4}
Fehlerbehebung und Debugging mit Cypress
::
::GlobalParagraph
Cypress bietet leistungsstarke Debugging-Tools, die dir helfen, Probleme schnell zu finden und zu beheben:
::
::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li>Interaktive Laufzeit: Wenn ein Test fehlschlägt, kannst du in der Test Runner-Oberfläche sehen, welcher Schritt fehlschlug und warum.</li>
<li>Screenshots und Videos: Cypress nimmt automatisch Screenshots und Videos von jedem fehlgeschlagenen Test auf, damit du den genauen Moment des Fehlers nachvollziehen kannst.</li>
<li>Chrome DevTools Integration: Cypress erlaubt dir, die Chrome DevTools zu verwenden, während deine Tests laufen, um detaillierte Netzwerk- und Konsolenfehler zu untersuchen.
::</li>
</ul>
<p>::GlobalTitle{:size="lg" .mb-4}
Warum Blueshoe für Cypress-Implementierungen?
::
::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Unsere Expertise
::
::GlobalParagraph
Blueshoe hat umfassende Erfahrung mit Cypress Testing und konnte es bereits in zahlreichen Projekten erfolgreich implementieren. Unsere Entwicklerteams optimieren Teststrategien für End-to-End Testing in verschiedensten Webanwendungen.
::
::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Maßgeschneiderte Lösungen
::
::GlobalParagraph
Jedes Projekt hat individuelle Anforderungen. Ob automatisiertes End-to-End-Testing für Benutzeroberflächen, komplexe Workflows oder CI/CD-Integration – Blueshoe bietet Lösungen, die genau auf dein Projekt zugeschnitten sind.
::</p>
<p>::GlobalTitle{:size="lg" .mb-4}
Cypress für effizientes Testing: Jetzt die Qualität deiner Webanwendung steigern
::
::GlobalParagraph
Cypress bietet eine leistungsstarke und benutzerfreundliche Lösung für automatisiertes End-to-End-Testing. Durch die schnelle Testausführung, die einfache Integration in bestehende Workflows und die realitätsnahen Tests im Browser ist es ein unverzichtbares Werkzeug für die Qualitätssicherung in der Webentwicklung.
::
::GlobalParagraph
Möchtest du die Qualität deiner Webanwendungen verbessern? Lass uns gemeinsam automatisierte Tests implementieren, die deine Entwicklungsprozesse beschleunigen und die Fehlerquote minimieren. <a href="https://blueshoe.io/michael/">Kontaktiere uns</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} für ein unverbindliches Beratungsgespräch und erfahre, wie wir dein Projekt unterstützen können.
::</p>
<p>:::GlobalTitle{:size="lg" .mb-5 .mt-8}
Häufige Fragen
:::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
1. Was ist Cypress und wofür wird es verwendet?
::
::GlobalParagraph
Cypress ist ein End-to-End-Testing-Framework für Webanwendungen. Es ermöglicht Entwicklern, automatisierte Tests direkt im Browser auszuführen, um sicherzustellen, dass eine Anwendung wie erwartet funktioniert.
::
::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
2. Welche Vorteile bietet Cypress gegenüber Selenium?
::
::GlobalParagraph
Cypress unterscheidet sich von Selenium durch:
::
::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li><strong>Schnellere Testausführung</strong>, da es direkt im Browser läuft.</li>
<li><strong>Einfachere Einrichtung</strong>, ohne zusätzliche WebDriver oder Plugins.</li>
<li><strong>Bessere Debugging-Möglichkeiten</strong>, inklusive Screenshots und Videos von fehlgeschlagenen Tests.
::</li>
</ul>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
3. Wie kann Cypress in CI/CD-Pipelines integriert werden?
::
::GlobalParagraph
Cypress lässt sich nahtlos in Continuous Integration/Deployment (CI/CD) integrieren. Dazu wird es in der CI-Konfiguration (z. B. GitLab CI, GitHub Actions) als Testschritt definiert. So werden Tests automatisch bei jeder Codeänderung ausgeführt.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
4. Welche Best Practices gibt es für Cypress-Tests?
::
::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li><strong>Unabhängige Tests</strong>: Jeder Test sollte für sich allein lauffähig sein.</li>
<li><strong>Gezielte Selektoren</strong>: Verwendung von <code>data-cy</code>-Attributen für stabile Tests.</li>
<li><strong>API-Interception nutzen</strong>: Anstatt auf feste Wartezeiten (<code>cy.wait()</code>) zu setzen, lieber API-Responses mit <code>cy.intercept()</code> überwachen.</li>
<li><strong>Tests modular aufbauen</strong>: Wiederkehrende Testlogik in Custom Commands oder Helper-Funktionen auslagern.
::</li>
</ul>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
5. Für welche Anwendungen ist Cypress nicht geeignet?
::
::GlobalParagraph
Cypress unterstützt nur <strong>moderne Webanwendungen</strong>. Einschränkungen bestehen bei:
::
::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li><strong>Tests in mehreren Browser-Tabs oder verschiedenen Domains gleichzeitig</strong> (workarounds notwendig).</li>
<li><strong>Mobile App Testing</strong>, da Cypress nicht für native Apps gedacht ist.</li>
<li><strong>Tests für Internet Explorer oder ältere Browser</strong>, da Cypress nur moderne Browser unterstützt.
::</li>
</ul>]]></content:encoded>
            <category>Sicherheit</category>
            <category>Typescript</category>
            <category>Entwicklung</category>
            <category>Dokumentation</category>
            <enclosure url="https://blueshoe.de/img/blog/cypress.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[So gelingt der perfekte Post auf Facebook und Instagram!]]></title>
            <link>https://blueshoe.de/blog/der-perfekte-facebook-instagram-post</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/der-perfekte-facebook-instagram-post</guid>
            <pubDate>Wed, 11 Apr 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Bei Facebook und Instagram möchte man in aller Regel eines: Eine breite Masse an Menschen erreichen und die eigene Zielgruppe nach Möglichkeit zuverlässig ansprechen. Dabei spielt vor allem die Qualität der Posts eine wichtige Rolle, wie eine jüngst veröffentlichte Analyse von mehr als 10 Millionen Datensätzen darstellt. Doch wie gelingt der perfekte Facebook Post? Und wie erreicht man bei Instagram eine große Aufmerksamkeit? Hier gibt es Antworten.</p>
<p><img src="/img/blogs/pexels-omkar-patyane-238480.jpg" alt="pexels-omkar-patyane-238480">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
RELEVANZ VON SOCIAL MEDIA
:::
:::globalParagraph
Prinzipiell gilt, dass Social-Media-Posts nicht nur eine weitere Möglichkeit zur Außendarstellung eines Unternehmens sind, sondern eben auch dabei helfen können wertvolle Inhalte, die im Zuge einer Inbound-Marketing-Strategie entwickelt wurden, weiter zu verbreiten. Auch wenn Social Media Posts sich nicht die überdimensionierte SEO-Relevanz entwickelt haben, wie man vor einigen Jahren noch dachte, so sind die Interaktionen auf Verlinkungen Sozialen Netzwerken dennoch ein Ranking-Faktor den auch Google in seiner Bewertung für Inhalte mit einbezieht. Eben auch aus diesen Gründen ist es natürlich hilfreich was man tun muss, um den perfekten Facebook oder Instagram-Post zu veröffentlichen.
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
DER PERFEKTE FACEBOOK POST – SO KLAPPT ES
:::
:::globalParagraph
Bei Facebook gibt es generell drei unterschiedliche Typen von Postings: Bilder, Videos und Links. Dazu kommen noch reine Text-Postings, die allerdings einen eher geringeren Werbeeffekt haben. Am meisten werden Links bei Facebook gepostet, die von den zu verbreitenden Medien noch den geringsten Erfolg versprechen. Videos sind am besten geeignet und bieten eine doppelt so hohe Erfolgsquote wie Bilder. Links erreichen hingegen nur rund ein Viertel der Performance von Video-Beiträgen.
:::</p>
<p><img src="/img/blogs/facebook_post1.jpg" alt="facebook_post1">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Die erfolgreichsten Facebook Posts werden in der Woche platziert – 80 Prozent der Postings erfolge generell in der Woche, lediglich 20 Prozent erfolgen am Wochenende. Am Wochenende ist dabei die Rate der Interaktionen ein wenig besser. Für den perfekten Post auf Facebook sollte man dabei auch die Länge der Zeichen berücksichtigen. Um die 50 Zeichen bieten bei der Performance das größte Potential. Ganz ohne Text erreicht ein Post die schlechteste Performance. Heißt: Kurz und bündig auf den Inhalt hinweisen, das reicht.
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
WELCHE ROLLE SPIELEN EMOJIS?
:::
:::globalParagraph
Immer noch wird in rund 80 Prozent der Facebook Posts auf Emojis verzichtet – dabei performen solche Postings deutlich schlechter. Ein bis drei Smileys sorgen zumeist für mehr Interaktionen und solche Beiträge können somit mehr Reichweite bringen.
:::
:::globalParagraph
Bei Hashtags verhält es sich bei Facebook anders. Meist wird darauf verzichtet, was allerdings nicht nachteilig ist. Ohne Hashtags funktionieren Facebook Posts oftmals besser – der Unterschied ist aber nur gering.
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
AUF EINEN BLICK: DER PERFEKTE FACEBOOK POST
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>Emojis sollten in Maßen genutzt werden</li>
<li>Mindestens ein, maximal 50 Zeichen sind optimal</li>
<li>Videos bieten die beste Performance</li>
<li>Beste Posting Zeit: das Wochenende
:::</li>
</ul>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
SO VERÖFFENTLICHT MAN DEN PERFEKTEN INSTAGRAM POST
:::
:::globalParagraph
Bei Instagram bieten Bilder die meiste Präsenz – kein Wunder, sind doch rund drei Viertel hier Bildbeiträge. Videos und andere Inhalte – wie Karussell-Posts – machen den Rest aus. Videos bieten trotz der Bildermacht die bessere Instagram Performance. Bilder liegen sogar noch hinter den Karussell Postings.
:::</p>
<p><img src="/img/blogs/instagram_1.jpg" alt="instagram_1">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Für den optimalen Post bei Instagram ist das Wochenende ebenso gut geeignet – hier erscheinen nur rund 20 Prozent der Beiträge im Netzwerk. Die Rate an Interaktionen ist hier allerdings am größten, sodass sich dieser Zeitraum bezahlt machen kann. Zwischen einem und 50 Zeichen sind bei Instagram ebenfalls eine gute Idee und bieten eine ideale Performance. Aber auch gänzlich ohne Zeichen kann man eine optimale Reichweite erzielen. Auffällig ist weiterhin, dass Emojis keine große Rolle spielen müssen – eine hohe Interaktion bieten aber Postings mit rund zehn Emojis bei Instagram.
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
HASHTAGS HABEN EINE GRÖSSERE RELEVANZ
:::
:::globalParagraph
Bei Instagram sind Hashtags deutlich wichtiger als bei Facebook. Sind sie bei Facebook in aller Regel überflüssig, gehören ein bis drei Hashtags bei Instagram zu einer optimalen Performance dazu. Ebenso sind aber auch Posts ganz ohne Hashtags oftmals erfolgreich, sie folgen direkt dahinter. Dies funktioniert meist aber nur bei Posts von erfolgreichen Stars, die bereits als Marke auftreten oder bei Accounts von Firmen und Influencern, die ohnehin eine große Reichweite haben.
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
<strong>ZUSAMMENGEFASST: SO GELINGT DER PERFEKTE INSTAGRAM POST</strong>
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>Videos bieten die beste Performance</li>
<li>Wochenendepostings sorgen für viele Interaktionen</li>
<li>Hashtags sollten gezielt genutzt werden</li>
<li>Text mit bis zu 50 Zeichen ist optimal
:::</li>
</ul>]]></content:encoded>
            <category>Team Blueshoe</category>
            <enclosure url="https://blueshoe.de/img/blogs/pexels-omkar-patyane-238480.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Digitales Lernen mit dem Digital Learning Lab]]></title>
            <link>https://blueshoe.de/blog/digital-learning-lab</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/digital-learning-lab</guid>
            <pubDate>Thu, 19 Mar 2020 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Das Potenzial der digitalen Bildung wird in Deutschland längst noch nicht ausgeschöpft. Doch das könnte sich mit der Corona-Krise ändern. Wir bei Blueshoe haben für die Technische Universität Hamburg das “Digital Learning Lab” gebaut, eine Plattform für digitale Unterrichtsgestaltung. Hier können Lehrkräfte aus ganz Deutschland digitale Tools nutzen und Unterrichtsbausteine für den eigenen (Fern)unterricht nutzen.</p>
<p><img src="/img/blogs/design_ohne_titel_2.jpg" alt="design_ohne_titel_2">{.object-cover .max-w-full .mb-5}</p>
<p>::globalParagraph
Nachdem die Schulen in Deutschland aufgrund des Corona-Virus für einige Wochen geschlossen sind, muss der Unterricht plötzlich von Zuhause aus weitergehen. Diese Entwicklung stellt viele Lehrkräfte vor eine Herausforderung. Ende 2019 bestätigten die Ergebnisse der ICLIS-Studie (International Computer and Information Literacy Study), welche die Medienkompetenz von Achtklässlerinnen und Achtklässlern untersucht, dass Deutschland viel mehr für digitale Bildung tun muss.
::</p>
<p>::globalTitle{:size="lg" .mb-5}
<strong>CORONA ALS CHANCE FÜR DIGITALES LERNEN</strong>
::</p>
<p>::globalParagraph
So verrückt es klingt, aber Corona könnte eine Chance sein - in Bezug auf die Digitalisierung der deutschen Schulen. 5.200 bayerische Schulen sind bereits Teil der digitalen Lernplattform des Landesmedienzentrums Bayern (mebis), die seit 2017 existiert.
::
::globalParagraph
Und auch andere Länder ziehen nach: Für die Technische Hochschule Hamburg (TUHH) haben wir bei Blueshoe vor einiger Zeit die Website des <strong>Digital Learning Labs</strong>  gebaut. Das Konzept für die digitale Lernplattform hat die Hochschule in Zusammenarbeit mit dem ebenfalls in Hamburg ansässigen Institut für Technische Bildung und Hochschuldidaktik (ITBH) erarbeitet.
::</p>
<p><img src="/img/blogs/header_breit_crop_subsamp.jpg" alt="header_breit_crop_subsamp">{.object-cover .w-full .mb-5}</p>
<p>::globalTitle{:size="lg" .mb-5}
<strong>WAS IST DAS DIGITAL LEARNING LAB?</strong>
::
::globalParagraph
Das Digital Learning Lab ist eine Art <strong>digitales Kompetenzzentrum für die Unterrichtsgestaltung</strong> - von der Grundschule bis zum Abitur. Lehrkräfte finden hier Trends, Tools und Tutorials, um ihren Unterricht für eine digitalisierte Welt weiterzuentwickeln. Sie können selbst Unterrichtsbausteine hochladen oder sich von den Bausteinen der Kollegen inspirieren lassen. Mitmachen kann jede Lehrkraft, deutschlandweit und das <strong>komplett kostenlos</strong>. Die Schüler sollen durch die Einbeziehung der Plattform in den Unterricht verschiedene Kompetenzen erlernen bzw. weiterentwickeln. Diese sind:
::
::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>Suchen, Verarbeiten &#x26; Aufbewahren</li>
<li>Analysieren &#x26; Reflektieren</li>
<li>Problemlösen &#x26; Handeln</li>
<li>Schützen &#x26; sicher Agieren</li>
<li>Produzieren &#x26; Präsentieren</li>
<li>Kommunizieren &#x26; Kooperieren
::
::globalParagraph
Die drei inhaltlichen Elemente des Digital Learning Labs der Technischen Hochschule Hamburg sind: digitale <strong>Unterrichtsbausteine</strong>, eine umfangreiche <strong>Toolbox</strong> sowie <strong>Trendbeobachtungen</strong> durch relevante Forschungsergebnisse, Fortbildungen und inspirierende Praxisbeispiele. Im Folgenden stellen wir kurz die unterschiedlichen Elemente bzw. Funktionen vor:
::</li>
</ul>
<p>::globalTitle{:size="lg" .mb-5}
<strong>UNTERRICHTSBAUSTEINE</strong>
::
::globalParagraph
Alle Themenbausteine können nach Unterrichtsfach oder Kompetenz gefiltert werden. Von “Quellen im Sachunterricht” über “Mobile Reporting - Videobeiträge mit dem Smartphone produzieren” bis hin zu “Eine Learning-App zum Thema gegensätzliche Adjektive erstellen” findet man auf der digitalen Lernplattform zahlreiche inspirierende Unterrichtsmodule.
::</p>
<p><img src="/img/blogs/unterrichtsbaustein_bsp.jpg" alt="unterrichtsbaustein_bsp">{.object-cover .w-full .mb-5}</p>
<p>::globalTitle{:size="lg" .mb-5}
<strong>TOOLS</strong>
::
::globalParagraph
Um einen modernen Unterricht zu unterstützen, können verschiedene digitale Tools genutzt werden. Hierzu zählen neben Desktop-Programmen auch mobile Apps, Webseiten und Online-Plattformen. Zu den Tools, die verwendet werden können zählen u. a.  Trello (Aufgaben- und Projektkooridnation), Draw.io (Diagramme und Visualisierungen) oder Wordpress (Blog-Erstellung).
::</p>
<p><img src="/img/blogs/tool_bsp_crop_subsampling.jpg" alt="tool_bsp_crop_subsampling">{.object-cover .w-full .mb-5}</p>
<p>::globalTitle{:size="lg" .mb-5}
<strong>TRENDS</strong>
::
::globalParagraph
Die Welt der Schülerinnen und Schüler verändert sich rasend schnell, deshalb ist es das Ziel von Digital Learning Lab, Lehrkräften die Chance zu geben, sich schneller über wissenschaftliche Studien, Kooperationsprojekte und Forschungstrends zu informieren. Das Thema Fake News ist und war in letzter Zeit beispielsweise immer wieder ein wichtiger Diskussionsgegenstand in den Schulen.
::</p>
<p><img src="/img/blogs/trend_bsp_crop_subsamplin.jpg" alt="trend_bsp_crop_subsamplin">{.object-cover .w-full .mb-5}</p>
<p>::globalTitle{:size="lg" .mb-5}
<strong>FAZIT</strong>
::
::globalParagraph
In Zeiten von Corona ist es wichtiger als je zuvor, dass Schülerinnen und Schüler von Zuhause aus lernen können. Die digitale Lernplattform Digital Learning Lab , für die wir bei Blueshoe die <a href="/technologien/python-django-agentur/">technische Entwicklung übernommen</a>{.bs-link-blue} haben, bietet allen Lehrerinnen und Lehrern die Möglichkeit, ihren Unterrichtsstoff schneller und effizienter digital aufzubereiten.
::
::globalParagraph
Eine <strong>wirklich gute und hilfreiche Sache</strong> - nicht nur aufgrund der Corona-Krise, sondern auch im Hinblick auf die noch fehlende Digitalisierung des <strong>deutschen Bildungssystems</strong>.
::</p>]]></content:encoded>
            <category>Wagtail</category>
            <category>Digitalisierung</category>
            <enclosure url="https://blueshoe.de/img/blogs/design_ohne_titel_2.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Digitale Transformation und Industrie 4.0 stehen vor der Tür]]></title>
            <link>https://blueshoe.de/blog/digitale-transformation-und-industrie-4-0</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/digitale-transformation-und-industrie-4-0</guid>
            <pubDate>Wed, 28 Jun 2017 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Digitale Transformation und Digitalisierung – Begriffe, die in aller Munde sind. Aber nur darüber sprechen bringt uns nicht ans Ziel! Ein Ziel, welches wahrscheinlich, vom heutigen Standpunkt, nicht einmal klar umrissen werden kann. Oder doch?</p>
<p><img src="/img/blogs/pexels-alex-knight-2599244.jpg" alt="pexels-alex-knight">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
„Wir beantworten unsere Kundenanfragen auch schon per E-Mail.“ Ist das Digitalisierung? Vielleicht. Ein wenig. Allerdings so wenig, dass es nicht einmal an der Oberfläche der Möglichkeiten kratzt, welche uns in der heutigen Zeit offen- und zur Verfügung stehen.
:::
:::globalParagraph
„Auf unserer Webseite sind alle Informationen zu finden, die unsere Kunden benötigen, um sich über unsere Produkte zu informieren.“ Das ist gut. Aber finden diese Kunden diese Informationen auch? Finden diese Kunden die Webseite selbst? Welche Informationen können über die Besucher der Webseite gesammelt werden? Fragen dieser Art lassen sich beliebig ausbauen und fortführen – und werden leider zu häufig von den Unternehmen selbst nicht gestellt.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
DIGITALISIERUNG FÜR DEN SELBSTZWECK
:::
:::globalParagraph
Man hat eine Webseite, „weil sich das ja heute so gehört“. „Die Konkurrenz hat auch eine neue Webseite.“ Dies sollten nicht die Gründe sein, die eigene Webseite neu in Auftrag zu geben oder auch seine Prozesse zu digitalisieren. Die digitale Transformation eines Prozesses sollte immer als Investment in einen schlankeren und effektiveren Ablauf im Unternehmen gesehen werden, und nicht, um sich selbst, den Vorstand oder das Gewissen zu beruhigen. Doch was kann die Digitalisierung wirklich? Wie können wir die Industrie 4.0 erreichen und für uns nutzen? Die grundsätzlichen Fragen, die sich Unternehmen stellen müssen, werden oftmals nicht ausführlich und erschöpfend genug beantwortet: Was will ich mit meiner Digitalstrategie eigentlich erreichen und was muss ich dafür tun?
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
INDUSTRIE 4.0 UND DIGITALISIERUNG – EINE BEGRIFFSKLÄRUNG
:::
:::globalParagraph
Industrie 4.0 – eine Hightech-Strategie der Bundesregierung, welche darauf abzielt, die industrielle Produktion mit moderner Informations- und Kommunikationstechnik zu verknüpfen. Alle in der Produktion verankerten Systeme werden vernetzt und durch „intelligente Algorithmen“ angetrieben.[1]
:::
:::globalParagraph
Digitalisierung – die Umstrukturierung manueller Prozesse hin zu digitalen Prozessen, welche mittels Informations- und Kommunikationstechnologien abgebildet werden. Oftmals nennt man diese Veränderung auch „Digitale Transformation“.[2]
:::
:::globalParagraph
Beide Begriffe haben einen gemeinsamen Kern: ein oder mehrere Teile des Wertschöpfungsprozesses eines Unternehmens wird bzw. werden zunehmend technologisch abgebildet. Dadurch können in der Regel Kosten reduziert werden. Beispielsweise kann physischer Platz (z. B. im Lager) gewonnen oder die Kosten für Personal (durch das Einsetzen intelligenter Algorithmen) eingespart werden.
:::
:::globalParagraph
Diese Einsparungen steigern also direkt den Gewinn, der wiederum weitere Investitionen ermöglicht. Gibt es mehr Platz, so kann ein Unternehmen auch mehr lagern. Ist das Personal entlastet, so kann es eventuell andere Aufgaben übernehmen, welche nicht einfach durch Software abgebildet werden können. Je nach Situation kann sich eine Reinvestition der ersparten Ressourcen schnell bezahlt machen.
:::
:::globalParagraph
Doch solange ein Entscheider die Möglichkeiten nicht kennt, welche durch die Digitalisierung von Prozessen geboten werden können, ist auch keine sinnvolle Investition in die Optimierung von Prozessen möglich.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
BEISPIELE FÜR DIGITALE TRANSFORMATION VON PROZESSEN
:::
:::globalParagraph
Täglich werden neue Möglichkeiten und Technologien angepriesen, die man nutzen kann, um seine Prozesse zu optimieren. Hier den Überblick zu behalten, ist extrem schwierig. Blueshoe hat hier immer das Ohr auf der Schiene, um entsprechend mit Rat und Tat zur Seite stehen zu können. Es folgt ein Auszug unserer Erfahrungen:
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
DIGITALE KUNDENGEWINNUNG
:::
:::globalParagraph
Muss die Neugewinnung von Kunden immer ein manueller Prozess sein? Definitiv nicht. Eine gute Webseite kann eine gute Möglichkeit zur Kundenakquise bieten. Allerdings reicht eine Webseite allein dafür nicht. Sie muss gepflegt werden, die Nutzer stets mit neuen Inhalten bereichert und begeistert werden und vor allem ist es wichtig, mit ihnen in Kontakt zu treten. Die Website ist schlussendlich nur das Vehikel, das ein Unternehmen einsetzen kann, um die gesetzten Ziele zu erreichen. Es geht in den meisten Fällen darum, Neu- und Mehrgeschäfte zu generieren. Dies kann nur erreicht werden, wenn ein Webauftritt auch in eine entsprechende Kommunikationsstrategie integriert ist. Achtung, ein weiteres Buzzword: Inbound Marketing.
:::
:::globalParagraph
Die Tools des Inbound Marketing sind dabei so vielfältig wie die Ansprüche von Kunden. Newsletter, Live-Chats, Landing Pages, E-Books, White Paper, Blogging, Social Media, Smart Content, Formulare, um nur einige zu nennen, können alles Möglichkeiten sein, den Vertrieb mittels einer Webseite zu unterstützen. Im Idealfall greifen all diese Teildisziplinen ineinander und unterstützen sich gegenseitig. Das heißt, der Nutzer wird durch die komplette Kette dieser Tools begleitet, so kann ein Unternehmen möglichst viel über ihn erfahren und ihn genau im richtigen Moment mit der passenden Ansprache abholen.
:::</p>
<p><img src="/img/blogs/digitalisierung_icons_kundengewinnung.jpg" alt="digitalisierung_icons_kundengewinnung">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Tools wie Zendesk und Intercom können dabei helfen, mit den Kunden direkt in Kontakt zu treten. Hiermit ist der entsprechende Mitarbeiter schneller im unmittelbaren Kontakt mit dem Kunden und kann diesem beratend zur Seite stehen oder gar Supportanfragen beantworten.
:::
:::globalParagraph
Ein weiteres nützliches Tool ist Hubspot. Es ermöglicht dem Nutzer, eine strukturierte Sales-Pipeline aufzusetzen und effektives Inbound-Marketing zu betreiben. Artikel, Newsletter, Formulare oder Landing Pages helfen dabei, einen unbekannten zu einem regelmäßigen Besucher oder gar zu einem Kunden konvertieren zu lassen. Es versetzt den Betreiber einer Webseite in die Lage, maßgeschneiderte Inhalte an den User auszuspielen. Ein Beispiel: Ein Besucher deiner Webseite liest sich die komplette Seite zu deinem Produkt A durch. Er schaut auch auf die Seite von Produkt B, aber nur flüchtig. Im Suchfeld deiner Webseite sucht der Nutzer nun nach weiteren Informationen zu Produkt A. Egal, ob er diese findet oder nicht, du weißt nun, dass dieser Nutzer anscheinend ein gewisses Interesse an Produkt A hat. Schaffe es jetzt, dass der Nutzer seine E-Mail-Adresse hinterlässt, sei es für einen Newsletter, ein Gewinnspiel oder im Live-Chat, paart Hubspot diese Daten mit dem zuvor gewonnenen Wissen. Dieses Wissen lässt sich nun in eine zielgenaue Kundenansprache umwandeln.
:::
:::globalParagraph
Die grundsätzliche Methodik dieses Vorgehens sieht dabei so aus:
:::
:::globalParagraph
<strong>Anziehen</strong> - Blogs, SEO, Kampagnen, Social Media
:::
:::globalParagraph
<strong>Konvertieren</strong> - Formulare, Landing Pages, Call-to-Action, E-Books, White Paper
:::
:::globalParagraph
<strong>Abschließen</strong> - CRM, Triggerketten, aktive Ansprache, Smart Content
:::
:::globalParagraph
<strong>Begeistern</strong> - Kundenservice, Umfragen, Prämienprogramme, Kunden werben Kunden, Bewertungen
:::
:::globalParagraph
Es gilt, diesen Prozess bis zur letzten Stufe perfekt durchzuführen. Ein begeisterter Kunde ist der perfekte Marken-Botschafter. Wer begeistert ist, empfiehlt weiter.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
BESTELLEINGÄNGE OPTIMIEREN
:::
:::globalParagraph
OCR - Bot scannt BilddateienEin Bestandteil der Kommunikation mit dem Kunden ist beispielsweise das Aufnehmen von Bestellungen. Die folgende Statistik (2015) [3] beschäftigt sich mit den Kontaktkanälen der deutschen Customer Service Center. Dort werden sowohl Kundenanfragen beantwortet als auch Bestellungen entgegengenommen. Lediglich für zwei Medien scheinen die befragten Unternehmen zukünftig weder ein Wachstum noch einen Rückgang zu prognostizieren: Briefe/Postkarten und Fax.
:::</p>
<p><img src="/img/blogs/digitalisierung_icons_ocr.jpg" alt="digitalisierung_icons_ocr">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Es schreibt doch heute jeder E-Mails, oder? Nein. Offenbar nicht. Dies deckt sich mit den Erfahrungen, die Blueshoe gemacht hat. Ein großer Teil der Bestellungen, vor allem im B2B-Sektor, werden heute noch per Fax getätigt. Ist es möglich, einen so analogen Prozess zu digitalisieren? Natürlich!
:::
:::globalParagraph
Es ist möglich, ein Fax in Form einer PDF-Datei zu empfangen, beispielsweise per E-Mail. Der Text aus dieser PDF-Datei kann nun mithilfe von OCR-Software extrahiert werden. Dieser Text wird dann in ein strukturiertes Format gebracht und kann nun maschinell verarbeitet werden. Hierdurch liegen dann beispielsweise der Name des Bestellenden, die Bestellnummer, die bestellten Artikel und deren Anzahl sowie die Versandadresse vor, was es wiederum möglich macht, die Bestellung direkt ins Warenwirtschaftssystem zu übertragen. Ganz ohne den Aufwand, das Fax abtippen zu müssen.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
FIRST-LEVEL-SUPPORT
:::
:::globalParagraph
Guter und schneller Support ist ein wichtiger Bestandteil der Kommunikation mit dem Kunden. So umfangreich die FAQs oder Hilfetexte eines Produktes auch sein mögen, der direkte Kontakt mit dem Kundensupport via Telefon oder Chat bleibt nicht aus.
:::</p>
<p><img src="/img/blogs/digitalisierung_icons_chatbots.jpg" alt="digitalisierung_icons_chatbots">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Dazu kommt die Tatsache, dass sich Anfragen von Kunden ähneln oder gar komplett gleichen. Bandansagen oder der Versuch, den Kunden dazu zu bringen, sich mithilfe von Nummerneingabe durch akustische Menüs zu navigieren, senken die Qualität einer Supportanfrage und nerven den Kunden nur. Doch gibt es Möglichkeiten, sich wiederholende Supportanfragen schon zu beantworten, bevor diese tatsächlich beim Support ankommen?
:::
:::globalParagraph
Chatbots sind mittlerweile in der Realität angekommen. Sie sind nicht mehr nur statische Antwortgeneratoren. Sie können auf unterschiedliche Formulierungen für denselben Sachverhalt gleichermaßen kompetent antworten. Weiterhin bieten sie die Möglichkeit, häufig gestellte Fragen automatisch zu beantworten. Mit einem guten Design der „Persönlichkeit“ des Chatbots wirkt der Chat nicht einmal künstlich. Ein Chatbot beantwortet häufige Fragen, berät oder verweist direkt an den richtigen Ansprechpartner. Wie könnte so etwas aussehen?
:::
:::globalParagraph
„Gibt es Produkt X in der Größe L?“ – „Ja, wir haben noch 3 Stück auf Lager. Du findest das Produkt hier.“
:::
:::globalParagraph
„Wie lange dauert es, bis Produkt X wieder auf Lager ist?“ -‒ „Das kann ich dir leider nicht sagen, aber ich habe deine Anfrage an einen Mitarbeiter weitergeleitet.“
:::
:::globalParagraph
Je nach Wissensstand des Chatbots kann dieser dem Kunden weiterhelfen, diesen an einen Mitarbeiter aus dem Support weiterleiten oder gar auf die richtige Telefonnummer verweisen.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
OHNE DIGITALISIERUNG KEIN ÜBERLEBEN – DER MITTELSTAND MUSS ANGREIFEN
:::
:::globalParagraph
Auch wenn die zuvor genannten Schritte nicht auf jede Unternehmung mit ihren individuellen Prozessen anwendbar sind, ist die digitale Transformation ein wichtiger Schritt zum Fortbestand und zur Weiterentwicklung unserer Wirtschaft. Es lassen sich garantiert in jedem Unternehmen Möglichkeiten finden, Prozesse zu optimieren und zu digitalisieren. Tun wir das nicht, bleiben wir stehen oder werden gar von der Konkurrenz überholt. Die großen Dax-Unternehmen haben das längst begriffen und eine Digitalisierungsagenda aufgesetzt. Dennoch werden sie es schwer haben, denn eine radikale Transformation ist in Dickschiff-Unternehmen mit mehreren Tausend Mitarbeitern ein zäher Prozess, der von vielen Widrigkeiten geprägt sein kann. Der Vorteil vieler kleiner Start-ups ist hierbei, dass diese ihre Probleme oftmals sehr digital lösen und dadurch auch ihren Kunden schnellere Antwort- oder Lieferzeiten bieten können. Mittelständische Unternehmungen haben wiederum den Vorteil, durch ihre Vertriebsstruktur, ihre Bestandskunden, ihre Mitarbeiter und ihr Kapital ihre Prozesse mithilfe der Digitalisierung auszubauen und somit ihre Position am Markt zu sichern.
:::
:::globalParagraph
Es ist wichtig, heute aktiv die Digitalisierung der eigenen Prozesse anzugehen, damit morgen ein Fortbestand der freien Marktwirtschaft möglich ist.
:::
:::globalParagraph
Der Mittelstand wirkt diesbezüglich noch oftmals viel zu behäbig, obwohl er eigentlich im Vorteil ist. Der klassische Mittelständler hat das Potential, schneller Entscheidungen zu treffen und umzusetzen als ein Dax-Konzern. Auf die gleiche Geschwindigkeit wie ein Start-up wird er nicht kommen, kann aber dafür Vorteile in anderen Teilbereichen ausspielen:
:::
:::globalParagraph
Markenbekanntheit, Budget oder Markterfahrung sind weitere Beispiele. Konzentrieren sich die Mittelständler aktiv darauf, eine digitale Agenda zu entwickeln und diese stringent und mit Unterstützung kompetenter Partner umzusetzen, so ist die digitale Transformation eher eine „Möglichkeit“ als eine „Gefahr“. Einzig das Mindset muss stimmen.
:::
:::globalParagraph
Also, liebe Mittelständler: in die Hände gespuckt und los geht’s!
:::
:::globalParagraph
Gern beraten wir dich unverbindlich zu Themen der digitalen Transformation.
:::</p>
<p>:::globalParagraph
[1] <a href="https://de.wikipedia.org/wiki/Industrie_4.0">https://de.wikipedia.org/wiki/Industrie_4.0</a>{.bs-link-blue :target="_blank"}</p>
<p>[2] <a href="https://de.wikipedia.org/wiki/Digitalisierung">https://de.wikipedia.org/wiki/Digitalisierung</a>{.bs-link-blue :target="_blank"}</p>
<p>[3] <a href="https://de.statista.com/statistik/daten/studie/499494/umfrage/umfrage-zu-kundenkanaelen-im-deutschen-customer-service/">https://de.statista.com/statistik/daten/studie/499494/umfrage/umfrage-zu-kundenkanaelen-im-deutschen-customer-service/</a>{.bs-link-blue :target="_blank"}
:::</p>]]></content:encoded>
            <category>Digitalisierung</category>
            <enclosure url="https://blueshoe.de/img/blogs/pexels-alex-knight-2599244.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Django Caching meistern: Der ultimative Guide von Cachalot bis zur Low-Level API]]></title>
            <link>https://blueshoe.de/blog/django-caching-cachalot-performance-guide</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/django-caching-cachalot-performance-guide</guid>
            <pubDate>Wed, 14 Jan 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Ist deine Django-Anwendung langsamer als sie sein sollte? Lange Ladezeiten frustrieren nicht nur deine Nutzer, sondern werden auch von Suchmaschinen abgestraft. Die Lösung liegt oft in einer intelligenten Caching-Strategie. Doch wo fängt man an?</p>
<p><img src="/img/blog/django-caching.svg" alt="Django Caching Low-level and Cachalot">{.object-cover .max-w-full .mb-5}</p>
<p>In diesem Guide führen wir dich durch die Welt des Django Cachings. Wir stellen dir den "magischen" Helfer <a href="https://django-cachalot.readthedocs.io/en/latest/">Django Cachalot</a>{target="_blank"} vor, der dir viel Arbeit abnimmt, und vergleichen ihn mit den mächtigen Bordmitteln von Django – vom Template-Caching bis zur granularen Low-Level API. Am Ende wirst du genau wissen, welche Technik für deinen Anwendungsfall die richtige ist.</p>
<h2>1. Warum ist Caching in Django unverzichtbar? Die Jagd nach Millisekunden</h2>
<p>Wenn eine Django-App langsam wird, liegt das fast immer an denselben Verdächtigen:</p>
<ul>
<li>Zu viele oder zu teure Datenbankabfragen</li>
<li>Komplexes Template-Rendering</li>
<li>Rechenintensive Logik in den Views</li>
</ul>
<p>Diese Probleme summieren sich zu langen Ladezeiten, ungeduldigen Usern und schlechteren SEO-Rankings.</p>
<p>Caching ist hier der Gamechanger. Es speichert häufig genutzte Ergebnisse zwischen und liefert sie blitzschnell aus, ohne dass Django alles neu berechnen muss. Das spart Serverlast, senkt den Datenbank-Load und verbessert die User Experience enorm.</p>
<hr>
<h2>2. Django Cachalot: Der Autopilot für dein ORM</h2>
<p>Django Cachalot ist ein Open-Source-Paket (<a href="https://django-cachalot.readthedocs.io/en/latest/">https://django-cachalot.readthedocs.io/en/latest/</a>{target="_blank"}), das automatisch alle ORM-Abfragen cached. Du musst dafür keine einzige Zeile Cache-Logik schreiben.</p>
<h3>Wie funktioniert es?</h3>
<p>Cachalot merkt sich die Ergebnisse deiner ORM-Queries und löscht sie automatisch, sobald du Daten veränderst, also bei <strong>INSERT</strong>, <strong>UPDATE</strong> oder <strong>DELETE</strong>.<br>
Die Auto-Invalidierung sorgt dafür, dass dein Cache immer aktuell bleibt, ohne dass du dich aktiv darum kümmern musst.</p>
<h2>Der große Vorteil: Auto-Invalidierung</h2>
<p>Wenn also z.b. ein neuer Post gespeichert wird:</p>
<pre><code class="language-python">post.title = "Neuer Titel"
post.save()
</code></pre>
<p>Dann invalidert Cachalot automatisch:</p>
<ul>
<li>alle gecachten Querysets des Post-Models</li>
<li>Querysets, die über ForeignKeys auf Post zugreifen</li>
<li>Querysets, die Joins enthalten, die durch Post beeinflusst werden</li>
</ul>
<h3>Setup in 3 Schritten:</h3>
<pre><code class="language-bash">pip install django-cachalot
</code></pre>
<p>Füge anschließend in deiner <code>settings.py</code> hinzu:</p>
<pre><code class="language-python">INSTALLED_APPS = [
    # ...
    "cachalot",
]
</code></pre>
<p>Wähle dein Cache-Backend, zum Beispiel Redis:</p>
<pre><code class="language-python">CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
    }
}
</code></pre>
<h3>Vorteile von Django Cachalot</h3>
<ul>
<li>Enorme Zeitersparnis bei der Entwicklung</li>
<li>Keine manuelle Cache-Logik im Code nötig</li>
<li>Besonders effektiv für leseintensive Anwendungen</li>
</ul>
<h3>Nachteile und Grenzen von Django Cachalot</h3>
<ul>
<li>Weniger granulare Kontrolle</li>
<li>Mögliche Fallstricke bei sehr komplexen Abfragen oder Raw-SQL</li>
<li>Manchmal werden veraltete Ergebnisse ausgeliefert, wenn die automatische Invalidierung nicht greift</li>
</ul>
<hr>
<h2>3. Das Standard-Repertoire: Djangos eingebaute Caching-Werkzeuge</h2>
<p>Manchmal brauchst du mehr Kontrolle, als Cachalot dir bieten kann. In diesen Fällen lohnen sich die eingebauten Caching-Tools von Django.</p>
<h3>3a) Template-Fragment-Caching: Die statischen Inseln</h3>
<p>Template-Fragment-Caching eignet sich, wenn du nur bestimmte Bereiche einer Seite cachen möchtest, zum Beispiel Navigationen, Teaserboxen oder Sidebar-Komponenten mit vielen Datenbankabfragen.</p>
<pre><code class="language-html">{% raw %}{% load cache %}{% endraw %}
{% raw %}{% cache 600 sidebar %}{% endraw %}
  &#x3C;div class="sidebar">
    {% raw %}{% for post in popular_posts %}{% endraw %}
      &#x3C;a href="{{ post.get_absolute_url }}">{{ post.title }}&#x3C;/a>
    {% raw %}{% endfor %}{% endraw %}
  &#x3C;/div>
{% raw %}{% endcache %}{% endraw %}
</code></pre>
<h3>3b) View-Level-Caching: Die ganze Seite auf einmal</h3>
<p>Wenn eine Seite für alle Besucher gleich aussieht, zum Beispiel eine Blog-Übersicht oder eine Landingpage, kannst du die komplette View cachen.</p>
<pre><code class="language-python">from django.views.decorators.cache import cache_page
from django.shortcuts import render
from .models import Post

@cache_page(60 * 15)  # 15 Minuten
def blog_list(request):
    posts = Post.objects.all()
    return render(request, "blog/list.html", {"posts": posts})
</code></pre>
<h3>3c) Die Low-Level Cache API: Maximale Kontrolle für Profis</h3>
<p>Mit der Low-Level API steuerst du selbst, welche Daten in den Cache gelangen, wie lange sie dort bleiben und wann sie erneuert werden sollen. Perfekt für komplexe Objekte, externe API-Calls oder spezifische QuerySets.</p>
<pre><code class="language-python">from django.core.cache import cache

def get_weather_data(city):
    cache_key = f"weather_{city}"
    data = cache.get(cache_key)

    if not data:
        # Externer API Call, zum Beispiel OpenWeather
        data = fetch_weather_from_api(city)
        cache.set(cache_key, data, timeout=3600)  # 1 Stunde

    return data
</code></pre>
<p>Super! Du kennst jetzt die Grundlagen von Django Cachalot und den eingebauten Caching-Mechanismen. Aber wann setzt du was ein? Der nächste Abschnitt hilft dir bei der Entscheidung.</p>
<h2>4. Head-to-Head: Cachalot vs. Low-Level API, wann nehme ich was</h2>
<p>In vielen Projekten stellt sich die Frage: Reicht mir ein automatisches Tool wie Django Cachalot oder brauche ich die volle Kontrolle der Low-Level API. Die folgende Tabelle hilft dir bei der Einordnung.</p>
<p>| Kriterium         | Django Cachalot                  | Low-Level API                                |
| :---------------- | :------------------------------- | :------------------------------------------- |
| Kontrolle         | Gering, da automatisch           | Maximal, da vollständig manuell              |
| Einrichtungsaufwand | Sehr gering                    | Eher hoch                                    |
| Anwendungsbereich | ORM-Abfragen                     | Beliebige Objekte und Datenquellen           |
| Ideal für         | Schnelle Erfolge, leseintensive Seiten | Komplexe Logik und feines Performance Tuning |</p>
<h2>5. Praxis-Guide: Die richtige Strategie für dein Projekt</h2>
<p>Die Theorie ist schön, aber wie sieht das in echten Projekten aus. Schau dir diese drei typische Szenarien an.</p>
<h3>Szenario 1: Ein Blog oder ein News-Portal</h3>
<p>Viele Lesezugriffe, vergleichsweise wenig Schreibzugriffe, dafür viele wiederkehrende Besucher.</p>
<p>Empfehlung:</p>
<ul>
<li>Nutze Django Cachalot für die Artikellisten, zum Beispiel für die Startseite und Kategorien.</li>
<li>Nutze View-Level-Caching für die Detailseiten, also einzelne Artikel, die sich nicht bei jedem Seitenaufruf ändern.</li>
</ul>
<h3>Szenario 2: Ein E-Commerce-Shop</h3>
<p>Im Shop werden Produktdaten eher selten geändert, aber Warenkörbe und personalisierte Empfehlungen sind sehr dynamisch.</p>
<p>Empfehlung:</p>
<ul>
<li>Setze Template-Fragment-Caching für Produktlisten und Kategorieseiten ein.</li>
<li>Verwende die Low-Level API für Warenkorb, Checkout und personalisierte Empfehlungen, da hier stark nutzerabhängige Daten im Spiel sind.</li>
</ul>
<h3>Szenario 3: Eine komplexe B2B-Anwendung mit vielen Daten</h3>
<p>In B2B-Anwendungen werden oft viele Daten aggregiert, gefiltert und berechnet. Die Business Logik ist meist deutlich komplexer.</p>
<p>Empfehlung:</p>
<ul>
<li>Nutze die Low-Level API, um berechnete Kennzahlen, Reports oder Dashboards gezielt zu cachen.</li>
<li>Nutze Django Cachalot für Standard-Listenansichten, also dort, wo einfach nur viele Datensätze angezeigt werden.</li>
</ul>
<hr>
<h2>6. Fazit: Der Mix machts</h2>
<p>Caching ist kein Alles-oder-nichts-Feature, sondern ein Baukasten. Mit den richtigen Bausteinen sorgst du dafür, dass deine Django-App sich schnell anfühlt, selbst wenn im Hintergrund komplexe Dinge passieren.</p>
<p>Eine sinnvolle Herangehensweise ist:</p>
<ul>
<li>Starte einfach, zum Beispiel mit Django Cachalot oder View-Level-Caching.</li>
<li>Miss die Wirkung mit Werkzeugen wie der <a href="https://django-debug-toolbar.readthedocs.io/en/latest/">Django Debug Toolbar</a>{target="_blank"}.</li>
<li>Optimiere gezielt dort nach, wo du die größten Performance Probleme siehst, zum Beispiel mit der Low-Level API.</li>
</ul>
<p>Die Faustregel lautet:</p>
<blockquote>
<p>Beginne so einfach wie möglich und füge Komplexität nur dort hinzu, wo sie sich wirklich lohnt.</p>
</blockquote>
<p>Welche Caching-Technik ist dein Favorit für Django-Projekte. Hast du noch einen Geheimtipp. Teile deine Erfahrungen in den Kommentaren und hilf anderen Entwicklern dabei, ihre Django Performance zu verbessern.</p>
<hr>
<h2>7. Häufig gestellte Fragen (FAQ)</h2>
<h3>Kann ich Django Cachalot mit Redis verwenden.</h3>
<p>Ja, das ist sogar eine sehr gängige Kombination. Redis ist extrem schnell und als Cache-Backend sehr zuverlässig. Du musst lediglich dein <code>CACHES</code> Setting entsprechend konfigurieren.</p>
<h3>Wie messe ich, ob mein Caching funktioniert.</h3>
<p>Du kannst die <a href="https://django-debug-toolbar.readthedocs.io/en/latest/">Django Debug Toolbar</a> nutzen, um zu sehen, wie viele Datenbankabfragen pro Request durchgeführt werden und ob sich diese Zahl nach dem Einbau von Caching verringert. Zusätzlich helfen Monitoring Tools wie Sentry Performance oder APM Lösungen, um Antwortzeiten im Blick zu behalten.</p>
<h3>Veraltet der Cache mit Django Cachalot niemals.</h3>
<p>Doch, Django Cachalot invalidiert automatisch alle gecachten Ergebnisse, sobald sich Daten ändern, zum Beispiel durch Speichern im Admin oder durch eine API. Dadurch stellst du sicher, dass Nutzer keine veralteten Daten sehen.</p>
<h3>Wann sollte man den Django Low-Level Cache verwenden.</h3>
<p>Die Low-Level Cache API lohnt sich immer dann, wenn du selbst bestimmen möchtest, welche Daten gecached werden und wie lange. Typische Beispiele sind:</p>
<ul>
<li>Externe API Requests</li>
<li>Teure Berechnungen</li>
<li>Komplexe zusammengesetzte Objekte, die aus mehreren Quellen stammen</li>
</ul>
<h3>Kann Caching Fehler verursachen.</h3>
<p>Caching kann dann Probleme machen, wenn veraltete Daten zurückgegeben werden oder wenn sich Business Logik ändert, aber der Cache noch alte Ergebnisse enthält. Wichtig ist deshalb eine durchdachte Invalidierungsstrategie. Bei Django Cachalot wird dieser Teil bereits stark vereinfacht, da Änderungen an Modellen automatisch zum Löschen der betroffenen Cache-Einträge führen.</p>
<hr>]]></content:encoded>
            <category>Django</category>
            <category>Entwicklung</category>
            <category>Performance</category>
            <enclosure url="https://blueshoe.de/img/blog/django-caching.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Django-Celery meistern: Asynchrone Aufgaben wie ein Profi verwalten]]></title>
            <link>https://blueshoe.de/blog/django-celery-in-produktion</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/django-celery-in-produktion</guid>
            <pubDate>Tue, 15 Jul 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Die Integration von Celery in deine Django-Anwendung ermöglicht leistungsstarke, asynchrone Aufgabenverarbeitung. Erfahre, wie du Django-Celery effektiv einrichtest und für Produktionsumgebungen optimierst, um deine Anwendung stabil und performant zu machen.</p>
<p>Kennst du das? Eine Bestellung geht ein – und plötzlich hängt deine Django-App, weil gerade E-Mails verschickt oder PDFs generiert werden? Hier kommt <a href="https://docs.celeryq.dev/en/latest/django/first-steps-with-django.html">Django Celery</a>{target="_blank"} ins Spiel. Die Integration in deine Django-Anwendung ermöglicht leistungsstarke, asynchrone Aufgabenverarbeitung. Erfahre, wie du Django-Celery effektiv einrichtest und für Produktionsumgebungen optimierst, um deine Anwendung stabil und performant zu machen.</p>
<p><img src="/img/blog/celery.svg" alt="Alles, was du über Django-Celery wissen musst"></p>
<h2>Warum Django-Celery?</h2>
<p>Viele Aufgaben in Webanwendungen müssen nicht synchron abgearbeitet werden. Dazu zählen z. B. das Versenden von E-Mails, das Generieren von PDFs oder das Verarbeiten großer Datenmengen. Genau hier kommt Celery ins Spiel: Es ermöglicht dir, diese Aufgaben im Hintergrund (asynchron) zu erledigen.</p>
<p><strong>Typische Anwendungsfälle für Celery in Django:</strong></p>
<ul>
<li>E-Mail-Versand</li>
<li>Bildverarbeitung</li>
<li>Externe API-Aufrufe</li>
<li>Datenbankbereinigung oder -analyse</li>
<li>Wiederkehrende Aufgaben (z. B. Reportgenerierung mit celery beat)</li>
</ul>
<h2>So richtest du Celery ein</h2>
<h3>1. Installation der notwendigen Pakete</h3>
<pre><code class="language-bash">pip install celery redis
</code></pre>
<p>Falls du periodische Tasks brauchst:</p>
<pre><code class="language-bash">pip install django-celery-beat
</code></pre>
<h3>2. Projektstruktur vorbereiten</h3>
<p>Erstelle eine Datei <code>celery.py</code> in deinem Projektordner:</p>
<pre><code class="language-python"># myproject/celery.py
import os
from celery import Celery

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")

app = Celery("myproject")
app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks()
</code></pre>
<p>In <code>__init__.py</code>:</p>
<pre><code class="language-python">from .celery import app as celery_app
__all__ = ["celery_app"]
</code></pre>
<h3>3. Beispielkonfiguration in <code>settings.py</code></h3>
<pre><code class="language-python">CELERY_BROKER_URL = "redis://localhost:6379/0"
CELERY_RESULT_BACKEND = "redis://localhost:6379/0"
CELERY_ACCEPT_CONTENT = ["json"]
CELERY_TASK_SERIALIZER = "json"
</code></pre>
<p>Teste dein Setup mit einem simplen Task.</p>
<hr>
<h2>So sehen Celery-Tasks in Django aus</h2>
<h3>1. Einfacher Task</h3>
<pre><code class="language-python">@shared_task
def send_email_to_user(user_id):
    print(f"E-Mail an User {user_id} gesendet")
</code></pre>
<p>Aufgerufen wird dieser jetzt mit:</p>
<pre><code class="language-python">send_email_to_user.delay(user.id)
</code></pre>
<h3><code>.delay()</code> vs. <code>.apply_async()</code></h3>
<ul>
<li><code>.delay()</code> ist ein Shortcut für <code>.apply_async()</code> mit Standardparametern.</li>
<li>Mit <code>.apply_async()</code> kannst du z. B. <code>countdown</code>, <code>eta</code> oder <code>retry</code> nutzen.</li>
</ul>
<h3>2. Komplexere Aufgaben mit Transaktionen und Logging</h3>
<pre><code class="language-python">@shared_task
def run_customer_basket_groups_processing(basket_id):
    from shop.order.processing import BasketProcessor
    from shop.models import Basket
    from shop.exceptions import ReachedAdvertisingMediumQuotaWarning
    from django.db import transaction
    import logging

    logger = logging.getLogger(__name__)

    basket = Basket.objects.get(pk=basket_id)

    try:
        if basket.user.groups.exists():
            with transaction.atomic():
                for cbg in basket.customerbasketgroups.all():
                    user = cbg.consignee.user
                    customer_basket = Basket.objects.create(
                        user=user,
                        field_staff=basket.user,
                        discount=basket.discount,
                        discount_code=basket.discount_code,
                        shipment_options=basket.shipment_options
                    )
                    customer_basket.add_basket_lines_to_basket(
                        cbg.basket_lines, as_stock_order=True, check_quotas=False
                    )
                    basket_processor = BasketProcessor(basket=customer_basket)
                    basket_processor.process_basket()
    except Exception as e:
        logger.error(f"Fehler beim Verarbeiten von Basket {basket_id}: {e}")

</code></pre>
<p><code>transaction.atomic()</code> sorgt dafür, dass bei einem Fehler keine halbfertige Bestellung angelegt wird.</p>
<hr>
<h2>Periodische Aufgaben mit <code>django-celery-beat</code></h2>
<h3>Migrationen:</h3>
<pre><code class="language-bash">python manage.py migrate
</code></pre>
<h3>App aktivieren:</h3>
<pre><code class="language-bash">INSTALLED_APPS += ["django_celery_beat"]
</code></pre>
<h3>Beispieltask anlegen:</h3>
<pre><code class="language-python">from django_celery_beat.models import PeriodicTask, IntervalSchedule

schedule, _ = IntervalSchedule.objects.get_or_create(every=10, period=IntervalSchedule.SECONDS)
PeriodicTask.objects.create(interval=schedule, name="Beispiel Task", task="myapp.tasks.send_email_to_user")
</code></pre>
<p>Fertig! Deine erste periodische Aufgabe ist erstellt. Das bedeutet: Celery ruft in diesem Intervall die Task auf – etwa um regelmäßig eine Benachrichtigung oder Erinnerungs-E-Mail zu versenden. Diese Konfiguration ist besonders nützlich für wiederkehrende Aufgaben, die zeitgesteuert ausgelöst werden sollen.</p>
<p>Die Admin-Oberfläche zeigt dir dann alle eingerichteten Tasks mit Status und Zeitplan – ideal für Monitoring und Management im Live-Betrieb.</p>
<p><img src="/img/blog/celery-1.png" alt="Django-Celery Admin"></p>
<hr>
<h2>Celery in der Produktion</h2>
<h3>1. Worker starten</h3>
<pre><code class="language-bash">celery -A myproject worker -l info
</code></pre>
<h3>2. Beat starten</h3>
<pre><code class="language-bash">celery -A myproject beat -l info
</code></pre>
<h3>3. Deployment-Tools nutzen</h3>
<ul>
<li>Verwende Supervisor oder systemd zum Prozessmanagement.</li>
<li>Logge Fehler z. B. mit Sentry oder externen Tools.</li>
<li>Implementiere Retries bei temporären Fehlern.</li>
</ul>
<h2>Häufige Stolpersteine</h2>
<ul>
<li>Redis nicht gestartet → <code>ConnectionRefusedError</code></li>
<li><code>.autodiscover_tasks()</code> vergessen → Tasks werden nicht gefunden</li>
<li>Task hängt → Worker nicht gestartet oder Deadlock in Datenbank</li>
</ul>
<h2>Best Practices für saubere Tasks</h2>
<ul>
<li>Halte Tasks klein, schnell und wiederholbar</li>
<li>Nutze Logging (z. B. <code>logger.info()</code>, <code>logger.error()</code>)</li>
<li>Plane Timeouts und Retry-Logik ein</li>
<li>Verwende <code>transaction.atomic()</code> bei Datenbank-Aktionen</li>
<li>Verwende <code>.apply_async(countdown=...)</code> für geplante Ausführung</li>
</ul>
<h2>Fazit</h2>
<p>Celery ist ein kraftvolles Werkzeug, das deine Django-App flexibler, schneller und robuster machen kann. Ob einfache E-Mail-Tasks oder komplexe Bestellverarbeitung - mit dem richtigen Setup und ein paar Best Practices bist du dann auf der sicheren Seite.</p>
<p>Lege jetzt los - und bringe Django-Celery in deine App!</p>]]></content:encoded>
            <category>Django</category>
            <category>Docker</category>
            <category>Entwicklung</category>
            <category>Betrieb</category>
            <enclosure url="https://blueshoe.de/img/blog/celery.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Django CMS Layout / Grid-System mit Bootstrap]]></title>
            <link>https://blueshoe.de/blog/django-cms-grid-bootstrap</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/django-cms-grid-bootstrap</guid>
            <pubDate>Sun, 19 Feb 2017 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Django CMS ermöglicht einfaches editieren seiner Inhalte. Durch einfache Aktionen wie Doppelklick können beispielsweise Texte geändert, Bilder ausgetauscht und Videoplayer eingebettet werden. Doch wie sieht aus mit der Anordnung dieser Elemente aus?</p>
<p><img src="/img/blogs/pankaj-patel-Ylk5n.jpg" alt="pankaj-patel">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalTitle{:size="lg" .mb-5}
Django CMS: Das großartige Content-Management-System
:::
:::globalParagraph
Es zeichnet sich durch eine hervorragende Usability, gute Wartbarkeit und leichte Erweiterbarkeit aus. Wie gut es letztendlich auf den Redakteur/Benutzer angepasst ist liegt letztendlich, in den meisten Fällen, am Entwickler selbst. Da uns stets die Usability unserer Lösungen am Herzen liegt, haben wir früh angefangen eine Lösung zum Erstellen des Layouts von Seiten im CMS gesucht. Bei dieser Suche stößt man schnell auf djangocms-column und aldryn-bootstrap3. Im Folgenden möchte ich diese 2 Plugins analysieren und gegenüberstellen. Daraufhin folgt eine Vorstellung unserer eigenen Lösung: djangocms-layouter.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
Djangocms-column
:::
:::globalParagraph
Djangocms-column ist ein simples Plugin zum Strukturieren der Seiteninhalte. Fügt man ein „Mehrspalten“-Plugin im Struktur-Modus hinzu, so In erscheint folgender Dialog.
:::</p>
<p><img src="/img/blogs/1_column-blog.jpg" alt="1_column-blog">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Hier kann der Nutzer nun einstellen wie viele Spalten mit welcher Breite erstellt werden sollen. Die Breite der Spalten verhält sich auf allen Bildschirmgrößen gleich. Spalten können im Nachhinein noch in ihrer Breite angepasst werden, sodass auch Spalten mit unterschiedlichen Breiten innerhalb eines Mehrspalten-Plugins möglich sind:
:::</p>
<p><img src="/img/blogs/2_column-blog.jpg" alt="2_column-blog">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
In jede der Spalten können nun nach Belieben weitere Plugins hinzugefügt werden.
:::</p>
<p><img src="/img/blogs/3_column-blog.jpg" alt="3_column-blog">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Bei der oben dargestellten Variante, stellt sich schnell heraus, dass die erste Spalte (mit 50%) nicht dargestellt wird, solange diese keinen Inhalt hat. Man kann also keine Inhalte vom linken Rand wegschieben.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
BOOTSTRAP IN DJANGO CMS: ALDRYN-BOOTSTRAP3
:::
:::globalParagraph
Das Bootstrap3 Plugin von Aldryn/Divio ist definitiv komplexer als djangocms-columns. Hier wird die Grid Logik, welche sonst Frontend-Entwickler implementieren, dem User als Werkzeug in die Hand gegeben. Fügt der Nutzer eine Zeile (Row) hinzu, so erscheint folgender Dialog:
:::</p>
<p><img src="/img/blogs/4_column.jpg" alt="4_column">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Hier kann der Nutzer nun entscheiden, wie viele Spalten (Columns) er erstellen möchte, und wie diese sich auf verschiedenen Bildschirmgrößen verhalten.
:::
:::globalParagraph
Hierzu ein kurzer Exkurs zu Bootstrap3. Das populäre Frontend-Framework sieht vor, dass man seine Inhalte in Zeilen gliedert, welche wiederrum in Spalten unterteilt sind. Insgesamt ist der Platz von 12 Spalten zu vergeben. Dabei kann eine Spalte verschiedene Breiten einnehmen und zwar 1 bis 12. Entsprechend kann man in einer Zeile beispielsweise 12 Spalten der Breite 1 einordnen oder auch 6 Spalten der Breite 2. Die Breite der Spalten kann man basierend auf der Breite des Endgerätes auf dem die Website angeschaut wird definieren. Daraus ergibt sich, dass eine Zeile auf dem Desktop 6 Spalten der Breite 2 hat, auf dem Tablet werden in der Darstellung dann aber 2 Zeilen mit jeweils 3 Zeilen der Breite 4. Lange Rede, kurzer Sinn auf http://getbootstrap.com/css/#grid-example-mixed lässt sich sehr gut beobachten, wie sich entsprechende Spalten verhalten.
:::
:::globalParagraph
Hat man nun seine Zeile mit ihren Spalten erstellt, kann man Inhalte in die Spalten ziehen.
:::</p>
<p><img src="/img/blogs/5_column.jpg" alt="5_column">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Somit gibt das Aldryn-Bootstrap 3 Plugin dem Nutzer quasi die Freiheit des Frontend-Entwicklers bei der Strukturierung der Inhalte. Weiterhin können noch Klassen ergänzt, sowie der Tag für die Spalte gesetzt werden.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
AUSWERTUNG VON DJANGOCMS-COLUMN UND ALDRYNS BOOTSTRAP3 PLUGIN
:::
:::globalParagraph
Ein Fazit zu djangocms-column lässt sich schnell ziehen: Das Plugin bietet wenig Flexibilität, funktioniert nicht responsive und bietet auch keine visuelle Unterstützung beim Anlegen der Seitenstruktur. Vielleicht war es auch gar nicht die Absicht des Entwicklers responsive Funktionalität in dem Plugin zu verankern. Es eignet sich lediglich in solchen Fällen, in denen die Anordnung der Inhalte auf Endgeräten verschiedener Größe gleich sein sollte. Dies ist aber eher untypisch.
:::
:::globalParagraph
Da das Aldryn Bootstrap3 Plugin wesentlich komplexer ist, muss man hier beim Ziehen eines Fazits auch tiefer einsteigen. Es ist vorrangig sehr gut geeignet Inhalte für verschiedene Bildschirmgrößen anzuordnen. Durch die Möglichkeit die Tags zu bestimmen, kann eine saubere Strukturierung der Seite gewährleistet werden. Weiterhin können durch das Feld „Classes“ CSS-Klassen ergänzt werden, wodurch der Nutzer auch Zugriff auf das Styling der Inhalte bekommt.
:::
:::globalParagraph
Bei all der Flexibilität, die das Bootstrap3 Plugin bietet, die Bedienung ist für einen durchschnittlichen Autor oder Editor durchaus nicht leicht, da meistens das Wissen über das Grid-System und seine Funktionsweise nicht bekannt ist.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
Django CMS LAYOUTER AUF BOOTSTRAP BASIS
:::
:::globalParagraph
Die Strukturierung/Anordnung der Inhalte, ohne komplizierte Bedienung oder Spezialwissen. Dies ist das hauptsächliche Ziel, welches der <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} Layouter erreichen sollte. Der Nutzer sollte intuitiv wählen können, wie Inhalte auf seinem Bildschirm angeordnet sein sollten, sich jedoch nicht um die Optimierung auf anderen Auflösungen kümmern müssen. Somit haben wir die Entscheidung getroffen, vorerst, auf gewisse Flexibilität zu verzichten, zugunsten von einer einfacheren Bedienung. Der Nutzer sollte das Grid nicht nur in der Strukturansicht sehen können, sondern auch in der Inhaltsansicht. Dies würde das Verständnis für den Aufbau und die Strukturierung von Seiten maßgeblich vereinfachen. Die Baumstruktur in der Strukturansicht von <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} sollte nicht unnötig tief sein; diese führt sonst auch zur einer Erschwerung der Bearbeitung der Inhalte. Fehler sollten dem Nutzer kenntlich gemacht werden und das Layout der Seite möglichst wenig beeinflussen.
:::
:::globalParagraph
Aus diesen Gedanken heraus haben wir Django CMS Layouter gestartet.
:::
:::globalParagraph
Ergebnis ist eine schlanke Administration zur Strukturierung der eignen Plugins:
:::</p>
<p><img src="/img/blogs/6_column.jpg" alt="6_column">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Man wählt die Struktur und den Abstand nach außen, welche/n man für seine Plugins vorsehen möchte. Wird ein spezielles Styling benötigt, so kann man dies einfach per CSS Klassen, getrennt durch Leerzeichen hinzufügen.
:::
:::globalParagraph
Django-CMS Layouter basiert auf dem populären Frontend-Framework Bootstrap. Dadurch, dass Zeilen und Spalten gleichzeitig angelegt werden, hat man eine flache Baustruktur in der Strukturansicht.
:::
:::globalParagraph
Fügt man zu viele Kind-Plugins in das <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} Layouter Plugin ein, so wird man durch einen roten Warntext darauf aufmerksam gemacht:
:::</p>
<p><img src="/img/blogs/7_column.jpg" alt="7_column">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Nutzer haben nun die Möglichkeit, ihre Plugins einfach in einen Container zu ziehen, welcher sich um die Anordnung kümmert. Die Anordnung auf kleineren Bildschirmen, wie Tablets und Smartphones wird ebenfalls automatisch über das Plugin gesteuert und erleichtert somit auch den Umgang für den Nutzer.
:::
:::globalParagraph
Um die Anordnung der Plugins in der Inhaltsansicht besser zu verstehen wurde die Toolbar um einen Button ergänzt: Toggle Grid.
:::</p>
<p><img src="/img/blogs/8_column-toggle_grid.jpg" alt="8_column-toggle_grid">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Dieser Button ermöglicht es das Grid, welches die Inhalte umschließt anzuzeigen. Hierdurch bekommt der Nutzer nun einen besseren Eindruck, warum seine Inhalte entsprechend angeordnet sind.
:::</p>
<p><img src="/img/blogs/9_column.jpg" alt="9_column">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Mit der erleichterten Bedienung kommt, wie so oft, auch ein Verlust an Flexibilität. Im Gegensatz zum Aldryn Bootstrap 3 Plugin ist es mit dem <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} Layouter nicht möglich die Darstellung der Spalten auf den verschiedenen Auflösungen granular zu steuern. Weiterhin sind die Spalten im Django CMS Layouter immer zentriert, sobald man Ihnen einen Rand zuweist, in Aldryn’s Bootstrap 3 Plugin, kann man den Spalten auch einzeln Offsets zuweisen und somit die Einrückung im Detail steuern. Um Spalten im Grid zu überspringen ist ein „Spacer-Plugin“ angedacht, aber noch nicht verfügbar.
:::
:::globalParagraph
Welches Plugin sollte man nun wann verwenden? Wir empfehlen bei erfahrenen Nutzern, die bestenfalls sogar mit dem Bootstrap 3 Framework vertraut sind, auf Aldryn Bootstrap 3 zu setzen. Dieses bietet eine großartige Flexibilität, wird aktiv gepflegt und eignet sich sehr gut um Inhalte zu strukturieren.
:::
:::globalParagraph
Für Autoren und Editoren, welche nicht entsprechende Erfahrungen in der Gestaltung von Webseiten haben, empfehlen wir unseren <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} Layouter.
:::
:::globalParagraph
Als Nachtrag noch ein kleines GIF zum <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} Layouter:
:::</p>
<p><img src="/img/blogs/layouter.png" alt="layouter.png">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Der Django CMS Layouter ist Open-Source und kann hier gefunden werden:
:::
:::globalParagraph
<a href="https://github.com/Blueshoe/djangocms-layouter">https://github.com/Blueshoe/djangocms-layouter</a>{.bs-link-blue :target="_blank"}
:::
:::globalParagraph
Und hier geht's zu <a href="/projekte/">Projekten</a>{.bs-link-blue} die wir mit <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} umgesetzt haben .
:::</p>]]></content:encoded>
            <category>Django CMS</category>
            <category>Django</category>
            <category>Python</category>
            <enclosure url="https://blueshoe.de/img/blogs/pankaj-patel-Ylk5n.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Django CMS Plugins und Apps]]></title>
            <link>https://blueshoe.de/blog/django-cms-plugins-und-apps</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/django-cms-plugins-und-apps</guid>
            <pubDate>Sun, 30 Jul 2017 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Egal ob als neuer Benutzer, oder als Entwickler, der Unterschied zwischen Plugins und Apps ist nicht immer sofort klar. Im Folgenden wollen wir den Unterschied sowohl für Anwender, als auch für Entwickler erklären.</p>
<p><img src="/img/blogs/clint-patterson-exfrR9KkzlE.jpg" alt="clint-patterson">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
DJANGO CMS PLUGINS - LAYOUTING UND GROSSE FREIHEITE
:::
:::globalParagraph
Bei der Entwicklung von Templates in <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} werden sogenannte Platzhalter in diesen eingesetzt. Die Platzhalter können dann später von den Nutzern des CMS mit Plugins befüllt werden.
:::
:::globalParagraph
Plugins erlauben dem Nutzer Seiteninhalte zu erstellen und zu strukturieren. Vom einfachen Darstellen eines Bildes, über Tabellen, Videos, Kacheln ist wirklich alles möglich. Viele Plugins gibt es bereits für <a href="/technologien/django-cms/">DjangoCMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}, die Entwicklung von maßgeschneiderten Plugins ist sehr gut dokumentiert. Plugins werden immer innerhalb der Platzhalter eingesetzt. In der Strukturansicht kann man ihre Reihenfolge einfach per Drag’n’Drop manipulieren. In der Inhaltsansicht lassen sich ihre Inhalte einfach per Doppelklick bearbeiten.
:::</p>
<p><img src="/img/blogs/text_edit_django_cms.png" alt="text_edit_django_cms">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Plugins sind verschachtelbar. Beispielsweise kann ein Textplugin auch Bilder enthalten.
:::
:::globalParagraph
Alle Plugins unterliegen dem Entwurf- und Veröffentlichen-Status einer Seite. Änderungen an Plugins werden also erst veröffentlicht, wenn der blaue Button „Änderungen Veröffentlichen“ oben rechts in der Toolbar betätigt wird.
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
DJANGO CMS PLUGINS - WAS SIND DIE STANDARDS?
:::
:::globalParagraph
Welche Plugins sind bei <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} vorinstalliert? Die Dokumentation von django CMS bietet eine Liste der django CMS Core Addons, welche mit der Installation des CMS bereits vorinstalliert sind. Diese umfasst die folgenden Plugins:
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>Django Filer – Management von Dateien und Bildern</li>
<li>django CMS Text CKEditor – der „standard“ WYSIWYG Editor für Benutzer von Django CMS</li>
<li>django CMS Link – zum Verlinken von Inhalten</li>
<li>django CMS Picture – zum Hinzufügen von Bildern</li>
<li>django CMS File – zum Einbinden von Dateien</li>
<li>django CMS Style - für Nutzer, welche manuelle Anpassungen am Styling vornehmen wollen</li>
<li>django CMS Snippet – zum Einfügen von HTML Code</li>
<li>django CMS Audio – zum Einfügen von Audio Dateien</li>
<li>django CMS Video – zum Einfügen von Videos (intern und extern z.B. Youtube)</li>
<li>django CMS GoogleMap – Darstellung von Google Maps, basierend auf Adressen oder Koordinaten
:::</li>
</ul>
<p>:::globalParagraph
Natürlich ist dies nicht das Ende der Fahnenstange. Es gibt zahlreiche weitere Plugins, welche von der Community entwickelt, betreut und konstant verbessert werden.
:::
:::globalParagraph
Weiterhin besteht natürlich immer die Möglichkeit Individual- oder Neuentwicklung für Plugins zu betreiben. Später in diesem Artikel ist ein Absatz zur Entwicklung von Plugins zu finden – und es ist gar nicht so schwer, wie man denken könnte. Indem Blueshoe Github Repository sind eine unserer eigenen Entwicklungen zu finden.
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
KOMPLEXE ANSICHTEN UND ANWENDUNGEN IN DJANGO CMS
:::
:::globalParagraph
Django-CMS-Apps werden auf Seitenebene eingebunden. Sie können komplexe Zusammenhänge abbilden und erzeugen festgelegte Ansichten automatisch. Ihr Layout ist entsprechend oft statisch, jedoch ist die Verwendung von Plugins innerhalb von Apps nicht ausgeschlossen. Während sich die Funktionalität von Plugins auf ihre jeweilige Seite beschränken lässt, können Apps selbstständig Ansichten erzeugen. Ein typisches Beispiel für eine App ist ein Blog. Man aktiviert die App für eine Seite im CMS, z.B. /blog. Werden nun Artikel geschrieben, generiert die Blog-App selbstständig durchsuchbare Listen, Kategorie-Ansichten sowie die Detail-Ansichten der Blogeinträge. Ein weiteres Beispiel für eine App könnte ein Fragenbogen sein, welche vom Redakteur erstellt wird. Je nach Beantwortung der Fragen wird der Nutzer nach Abschluss auf eine speziell zugeschnittene Ergebnisseite weitergeleitet.
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
STANDARD-APPS IN DJANGO CMS
:::
:::globalParagraph
Mit der Installation von <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} werden keine Apps im Sinne eine Django CMS App installiert. Das hat den einfachen Grund, dass Apps immer spezielle Anforderungen abbilden, welche nicht auf alle Benutzer abgebildet werden können (im Gegensatz zu z.B. Text und Bild Plugin). Nicht jeder braucht zwangsweise einen Blog, oder eine Verwaltung und Darstellung für Events. Dahingehend verfolgt <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} das Prinzip den Kern der Software schlank zu halten und zusätzlich benötigte Komponenten nachträglich zu installieren. Dieses Github Repository bietet eine, leider nicht ganz aktuelle, aber umfangreiche Sammlung von guten Django CMS Add-ons (Plugins und Apps).
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
DER UNTERSCHIED ZWISCHEN APPS UND PLUGINS
:::
:::globalParagraph
Plugins dienen als Bausteine zur Gestaltung einer einzelnen Seite. Sie erlauben es dem Redakteur oder Editor auf den Seiten in <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} Inhalte einzupflegen und auf verschiedene Art und Weise darzustellen. Alle Einstellungen, welche für ein Plugin getroffen werden, gelten nur für die einzelne Instanz und nicht übergreifend. Plugins werden i.d.R. mehrmals im CMS verwendet. Ihre Inhalte können direkt aus dem Input der Benutzer des Systems kommen, aber auch über Schnittstellen und vom CMS generierte Daten (z.B. Listen von Artikeln).
:::
:::globalParagraph
Apps definieren die gesamte Funktion einer Seite im CMS. Sie haben die Möglichkeit über eine Seite hinaus weitere Ansichten (Unterseiten) zu erzeugen. Auch Apps haben sind über Einstellungen individualisierbar und können selber über Platzhalter verfügen, in welche wiederum Plugins eingesetzt werden können.
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
WIE ENTWICKELT MAN EIN DJANGO CMS PLUGIN?
:::
:::globalParagraph
Jedes <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} Plugin besteht aus drei Basiskomponenten: eine Konfiguration, einem Publisher und einem Template. Die Konfiguration entspricht leitet sich von der Klasse cms.models.pluginmodel.CMSPlugin ab, wobei diese selbst von der bekannten Django Model Klasse ableitet. Sie enthält alle wichtigen Einstellungen, welche benötigt werden um die jeweilige Instanz darzustellen:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">class ImageWithCaptionModel(CMSPlugin):
    image = models.Imagefield(...)
    caption = models.TextField(...)
</code></pre>
<p>:::
:::globalParagraph
In dieser konkreten Klasse werden also die Daten gehalten, welche eine Instanz der Plugins dann später zur Darstellung von einem Bild mit Unterschrift verwenden kann.
:::
:::globalParagraph
Der Publisher ist im Django-Jargon die „Admin-Oberfläche“ zum jeweiligen Model. Die Klasse CMSPluginBase, welcher zur Erstellung dieser Publisher verwendet wird, leitet selbst auch die ModelAdmin Klasse ab. Publisher kontrollieren, wie die Daten ins Template gelangen, welches Template zum rendern verwendet werden soll, ob Caching beim rendern verwendet werden sollte, welche Felder der Konfiguration der Redakteur bearbeiten kann und einiges mehr.
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">class ImageWithCaptionPlugin(CMSPluginBase):
    model = ImageWithCaptionModel
</code></pre>
<p>:::
:::globalParagraph
Dies ist eine simple Version eines Publishers, welche es erlaubt neue ImageWithCaption Instanzen im CMS zu erzeugen, diese zu bearbeiten und zu löschen.
:::
:::globalParagraph
Zu guter Letzt benötigt man ein Template, welches vorgibt wie das Plugin auf einer Seite dargestellt wird. Es enthält alle Variablen, welche vom Publisher in den Kontext gegeben werden. Standardmäßig ist das die Instanz des Models.
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">&#x3C;img src=“{% thumbnail instance.image 200x200 %}“>
&#x3C;div>{{ instance.caption }}&#x3C;/div>
</code></pre>
<p>:::
:::globalParagraph
Die Entwicklung von <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} Plugins ist weitestgehend in der Dokumentation beschrieben.
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
<strong>WIE ENTWICKELT MAN EINE DJANGO CMS APP?</strong>
:::
:::globalParagraph
Eine <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} App ist im Grunde genommen eine normale Django App – mit ein paar Anpassungen. Wie bereits zuvor erwähnt werden Apps auf Seitenebene angelegt und zwar mit sogenannten Apphooks. Diese ermöglichen es dem CMS eine App, welche gegebenenfalls eine eigene Seitenstruktur mitbringt, einzubinden.
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">class ShowRoomApphook(CMSApp):
    name = _("Showroom")
    app_name = "showroom"

    def get_urls(self, page=None, language=None, **kwargs):
        return ["showroom.urls"]

apphook_pool.register(ShowRoomApphook)
</code></pre>
<p>:::
:::globalParagraph
In diesem Beispiel bauen wir eine Showroom-App – sie dient zur Darstellung der Kunstwerke eines Malers, welche die Webseite betreibt. Nachdem wir unsere URLs nun auch noch in die urls.py unseres Projektes eingetragen haben, ist prinzipiell der <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} spezifische Teil um:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">url(r'^showroom/', include('showroom.urls', namespace='showroom', app_name='showroom')),
</code></pre>
<p>:::
:::globalParagraph
Die urls.py für die App selbst muss auch angelegt werden:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">urlpatterns = patterns('showroom.views',
    url(r'^$', CategoryListView.as_view(), name='category_list'),
    url(r'^artpiece/(?P&#x3C;cat_id>\d+)/$',
        ArtPieceListView.as_view(), name='artpiece_list'),
)
</code></pre>
<p>:::
:::globalParagraph
Nun erstellen wir 2 Models, damit wir die Werke in der Datenbank repräsentieren können:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">class Category(models.Model):
    name = models.Charfield(...)
    slug = models.Charfield(...)
</code></pre>
<p>:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">class ArtPiece(models.Model):
    name = models.Charfield(...)
    image = models.ImageField(...)
    category = models.Foreignkey('Category', ...)
</code></pre>
<p>:::
:::globalParagraph
Weiterhin benötigen wir nun Views, zur Darstellung der Kunstwerke im Browser:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">class CategoryListView(ListView):
    model = Category
    template = 'category_list.html'

class ArtPieceListView(ListView):
    model = ArtPiece
    template = 'art_piece_list.html'

  def get_queryset(self):
        qs = super(...)
        cat_id = kwargs.get('cat_id', None)
        if cat_id:
            qs = qs.filter(category__id=cat_id)
        return qs
</code></pre>
<p>:::
:::globalParagraph
Diese benutzen wiederum 2 Templates zur Darstellung der jeweiligen Listen. Der CategoryListView gibt eine Liste alle Kategorien der Kunstwerke aus, der ArtPieceListView gibt eine gefilterte Liste der Kunstwerke (gefiltert nach Kategorie). Diese beiden Views werden nun den URLs im Apphook hinzugefügt und schon können wir unsere neue App verwenden. Natürlich müssen wir die Models vorher Migrationen durchgeführt werden.
:::
:::globalParagraph
Eine detailierte Dokumentation zum Erstellen von Apps in <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} ist hier zu finden: <a href="http://docs.django-cms.org/en/release-3.4.x/how_to/apphooks.html">http://docs.django-cms.org/en/release-3.4.x/how_to/apphooks.html</a>{.bs-link-blue :target="_blank"}.
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
UNTERSCHIED UND GLEICHHEIT – DJANGO CMS APPS UND PLUGINS
:::
:::globalParagraph
Nachdem wir nun herausgestellt haben, wie sich die Unterschiede zwischen Apps und Plugins sowohl für Benutzer als auch für Entwickler darstellen sollte hier auch nochmal gesagt werden: Bei entsprechender Implementierung, kann man dieselbe Darstellung von bestimmten Problemen sowohl mit Apps als auch mit Plugins erreichen. Es gibt auch Problemstellungen, bei denen es nun nicht offensichtlich ist, ob man eine App braucht, oder ein Plugin. Dazu sollte man sich immer genau den Verwendungszweck anschauen, für welchen die Komponente erstellt wird. Sollen viele Informationen automatisiert dargestellt werden, werden Unterseiten gebraucht, bzw. Logiken und Workflows, welche sicher immer wieder wiederholen? Dann ist wohl in der Regel eine App die richtige Lösung. Wird viel Flexibilität in der Darstellung gebraucht und lässt sich kein Muster in der Verwendung erkennen, so sollte man eher dazu tendieren ein Plugin als Problemlösung herzunehmen.
:::
:::globalParagraph
Die Implementierung der <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} App ist auch auf Github zu finden: <a href="https://github.com/Blueshoe/django_cms_example">https://github.com/Blueshoe/django_cms_example</a>{.bs-link-blue :target="_blank"}.
:::</p>]]></content:encoded>
            <category>Django CMS</category>
            <category>Django</category>
            <enclosure url="https://blueshoe.de/img/blogs/clint-patterson-exfrR9KkzlE.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Django-CORS: Sicherheit & Best Practices]]></title>
            <link>https://blueshoe.de/blog/django-cors-in-produktion</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/django-cors-in-produktion</guid>
            <pubDate>Fri, 17 Jan 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>CORS (Cross-Origin Resource Sharing) richtig zu konfigurieren, ist entscheidend für die Sicherheit und Funktionalität deiner Django-Anwendung. Erfahre, wie du Django-CORS effektiv einsetzt, um externe Anfragen sicher und zuverlässig zu handhaben.</p>
<p><img src="/img/blogs/cors.webp" alt="Blueshoe und FastAPI in Produktion">{.object-cover .max-w-full .mb-5}</p>
<p>::GlobalTitle{:size="lg" .mb-4}
Optimierte CORS-Konfiguration für moderne Webanwendungen
::</p>
<p>::GlobalParagraph
Django-CORS ist ein unverzichtbares Werkzeug für die sichere Kommunikation zwischen Diensten in einer Cross-Origin-Umgebung. Besonders in verteilten Architekturen und Container-Umgebungen spielt eine flexible und gut konfigurierte CORS-Strategie eine Schlüsselrolle. Erfahre, welche Pakete du benötigst, wie du sie installierst und konfigurierst und welche Best Practices es gibt.
::</p>
<p>:::GlobalButton{:url="/technologien/python-django-agentur/" :label="Erfahre mehr über unsere Django-Entwicklungsdienste" :color="blue" .mb-6}
:::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Einführung in Django-CORS
::</p>
<p>::GlobalParagraph
CORS (Cross-Origin Resource Sharing) definiert, welche Domains Zugriff auf Ressourcen deiner Anwendung haben. Browser blockieren standardmäßig solche Anfragen, wenn sie von einer anderen Domain stammen. Mit <a href="https://github.com/adamchainz/django-cors-headers">django-cors-headers</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} kannst du diese Beschränkung gezielt aufzuheben und <a href="/loesungen/api-entwicklung">API</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}-Zugriffe sicher und kontrolliert gestalten.
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Pakete und Installation
::</p>
<p>::GlobalParagraph
Für die CORS-Konfiguration in Django wird das Paket <a href="https://github.com/adamchainz/django-cors-headers">django-cors-headers</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} verwendet. Dieses Paket ist speziell für Django entwickelt und integriert sich nahtlos in die bestehende Middleware.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
1. Installiere das Paket mit pip
::</p>
<p>::BlogCode</p>
<pre><code class="language-bash">pip install django-cors-headers
</code></pre>
<p>::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
2. Füge das Paket in deinen Django-Einstellungen hinzu
::</p>
<p>::BlogCode</p>
<pre><code class="language-python">INSTALLED_APPS += [
    'corsheaders',
]
</code></pre>
<p>::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
3. Registriere die Middleware – sie muss ganz oben in der Liste stehen
::</p>
<p>::BlogCode</p>
<pre><code class="language-python">MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    *MIDDLEWARE,
]
</code></pre>
<p>::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Beispiel: Webanwendung mit CORS
::</p>
<p>::GlobalParagraph
Für eine typische Webanwendung, bei der Frontend und Backend auf unterschiedlichen Domains laufen, kannst du <code>CORS_ALLOWED_ORIGINS</code> so konfigurieren:
::</p>
<p>::BlogCode</p>
<pre><code class="language-python">CORS_ALLOWED_ORIGINS = [
    "https://frontend.example.com",
    "https://partner.example.com",
]
</code></pre>
<p>::</p>
<p>::GlobalParagraph
Möchtest du zusätzliche Header erlauben, z. B. für Authentifizierung, kannst du dies erweitern:
::</p>
<p>::BlogCode</p>
<pre><code class="language-python">from corsheaders.defaults import default_headers
CORS_ALLOW_HEADERS = list(default_headers) + [
    "Authorization",
    "X-Custom-Header",
]
</code></pre>
<p>::</p>
<p>:::GlobalCallAnExpert{:bg="bg-bs-blue"}
#buttonBlockTitle
Wir können CORS auch für deine Django Apps einrichten.
:::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
CORS in Container-Umgebungen
::</p>
<p>::GlobalParagraph
In Container- und verteilten Systemen wird Django häufig als Backend verwendet, das von verschiedenen Diensten (Frontend, API-Gateways, Authentifizierungsserver) angesprochen wird. Hier ist es entscheidend, die CORS-Regeln auch für interne Container-Kommunikation korrekt zu setzen.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Herausforderungen:
::</p>
<p>::GlobalBlock{.ol-decimal .my-4}</p>
<ol>
<li><strong>Dynamische Ursprünge:</strong> Dienste können in Containern unterschiedliche IP-Adressen oder dynamische Subdomains verwenden.</li>
<li><strong>Preflight-Anfragen:</strong> Optionen-Anfragen (OPTIONS) können von Proxies oder Load-Balancern blockiert werden, was zu Problemen führt.
::</li>
</ol>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Lösung:
::</p>
<p>::GlobalParagraph
Mit regex-basierten Whitelists und flexiblen Methoden:
:</p>
<p>::BlogCode</p>
<pre><code class="language-python">CORS_ORIGIN_REGEX_WHITELIST = [
    r"^https://.*\.example\.com$",
]
CORS_ALLOW_METHODS = [
    "GET",
    "POST",
    "PUT",
    "DELETE",
    "OPTIONS",
]
</code></pre>
<p>::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Häufige Stolpersteine
::</p>
<p>::GlobalBlock{.ol-decimal .my-4}</p>
<ol>
<li><strong>Wildcard-Konfiguration</strong>: Vermeide <code>CORS_ALLOW_ALL_ORIGINS=True</code> in der Produktion - das ist ein Sicherheitsrisiko.</li>
<li><strong>Preflight-Anfragen in Containern</strong>: Stelle sicher, dass API-Gateways oder Proxies die <code>OPTIONS</code>-Anfragen korrekt durchleiten.</li>
<li><strong>Logs und Debugging</strong>: Überwache fehlerhafte CORS-Anfragen mithilfe von Django-Logs, um Fehlkonfigurationen schnell zu erkennen.
::</li>
</ol>
<p>::GlobalTitle{:size="lg" .mb-5}
Best Practices für Django-CORS
::</p>
<p>::GlobalBlock{.ol-decimal .my-4}</p>
<ol>
<li>Nutze Umgebungsvariablen, um Konfigurationen zwischen Entwicklungs- und Produktionsumgebung zu trennen.</li>
<li>Teste regelmäßig deine CORS-Konfiguration, z. B. mit Tools wie <a href="https://www.postman.com/">Postman</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} oder Browser-Developer-Tools.</li>
<li>Reduziere die Anzahl erlaubter Ursprünge und Header auf das absolut Notwendige.
::</li>
</ol>
<p>::GlobalTitle{:size="lg" .mb-5}
Fazit
::</p>
<p>::GlobalParagraph
Mit django-cors-headers kannst du sicherstellen, dass Cross-Origin-Anfragen kontrolliert und sicher abgewickelt werden – unabhängig davon, ob du lokal entwickelst oder in einer skalierbaren Container-Umgebung arbeitest. Besonders in verteilten Systemen ist die richtige CORS-Konfiguration entscheidend, um eine reibungslose Kommunikation zwischen Diensten zu gewährleisten.
::</p>
<p>:::GlobalButton{:url="/technologien/python-django-agentur/" :label="Lust auf mehr Django-Insights?" :color="blue" .mb-6}
:::</p>
<p>::GlobalParagraph
Hast du Fragen oder möchtest du Unterstützung bei der Umsetzung? <a href="https://blueshoe.io/michael/">Kontaktiere uns</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} – wir helfen dir, deine Djangp Apps erfolgreich umzusetzen!
::</p>
<p>:::GlobalTitle{:size="lg" .mb-5 .mt-8}
Häufige Fragen
:::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
1. Was ist Django-CORS und warum ist es wichtig?
::</p>
<p>::GlobalParagraph
Django-CORS (Cross-Origin Resource Sharing) ermöglicht es, gezielt zu definieren, welche Domains auf Ressourcen einer Django-Anwendung zugreifen dürfen. Das ist besonders in modernen, verteilten Systemen relevant, um APIs sicher und flexibel zu gestalten.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
2. Wie konfiguriere ich CORS in Django?
::</p>
<p>::GlobalParagraph
Die Konfiguration erfolgt mit dem Paket django-cors-headers. Dieses wird per pip installiert und in den <code>INSTALLED_APPS</code> sowie der <code>MIDDLEWARE</code> eingetragen. Domains, die zugreifen dürfen, werden in den Einstellungen wie <code>CORS_ALLOWED_ORIGINS</code> definiert.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
3. Welche Herausforderungen gibt es bei CORS in Container-Umgebungen?
::</p>
<p>::GlobalParagraph
In Container-Architekturen treten oft dynamische Ursprünge und blockierte Preflight-Anfragen auf. Diese können durch regex-basierte Whitelists (<code>CORS_ORIGIN_REGEX_WHITELIST</code>) und die richtige Proxy-Konfiguration gelöst werden.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
4. Was sind Best Practices für eine sichere Django-CORS-Konfiguration?
::</p>
<p>::GlobalParagraph</p>
<ul>
<li>Vermeide die Verwendung von <code>CORS_ALLOW_ALL_ORIGINS=True</code> in der Produktion.</li>
<li>Reduziere erlaubte Ursprünge und Header auf das Nötigste.</li>
<li>Nutze Umgebungsvariablen, um Konfigurationen zwischen Entwicklungs- und Produktionsumgebungen zu trennen.
::</li>
</ul>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
5. Wie teste ich meine Django-CORS-Konfiguration?
::</p>
<p>::GlobalParagraph{.mb-4}
Die CORS-Einstellungen können mit Tools wie Postman oder den Developer-Tools des Browsers überprüft werden. Preflight-Anfragen sollten korrekt durchgeleitet und Logs auf Fehler analysiert werden.
::</p>]]></content:encoded>
            <category>Django</category>
            <category>Docker</category>
            <category>API</category>
            <category>Entwicklung</category>
            <category>Betrieb</category>
            <enclosure url="https://blueshoe.de/img/blogs/cors.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Django Entwicklung für Kubernetes]]></title>
            <link>https://blueshoe.de/blog/django-fuer-kubernetes</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/django-fuer-kubernetes</guid>
            <pubDate>Sun, 27 Dec 2020 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Wir haben uns erneut Gedanken gemacht, wie wir das Ausführen von Django-Anwendungen mehr Cloud Native und "ready for kubernetes" gestalten können. Als Resultat ist Django-Hurricane herausgekommen, welches wir in diesem Blogpost vorstellen und als Open Source-Projekt zur Verfügung stellen.</p>
<p><img src="/img/blogs/hurricane_logo_text.jpg" alt="hurricane_logo">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Wir bei Blueshoe sind schon immer Fans von <strong>Open Source</strong>. In sehr vielen unserer Projekten kommen Open Source-Komponenten zum Einsatz, wir nutzen ja auch u. a. sehr stark das <strong>Django Framework</strong> und wir bemühen uns auch, Open Source-Projekte bei der Maintenance und Weiterentwicklung zu unterstützen. Seit einiger Zeit hat nun auch <strong>Kubernetes</strong> bei uns Einzug erhalten, wodurch wir uns mehr und mehr der Herausforderung stellen, wie wir möglichst <strong>Cloud Native</strong> entwickeln können. Dazu haben wir auch schon Blog-Posts geschrieben, z. B.: <a href="/loesungen/cloud-native-development/">Cloud Native Kubernetes development</a>{.bs-link-blue}.
:::
:::globalParagraph
Heute möchten wir euch unser neues Open Source-Projekt <strong>Django-Hurricane</strong> vorstellen.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
“Klassisches” Deployment
:::
:::globalParagraph
Zunächst wollen wir kurz betrachten, wie Django oftmals auf einem Server, bzw. einer VM ausgeführt wird. Üblicherweise verwenden wir dazu einen Stack mit nginx als Web Server, sowie uwsgi als Application Server, welcher den Django-Code ausführt. Appserver wie uwsgi haben ein <strong>hochgradig optimiertes</strong> Prozessmodell für diese Art von Deployments mit vielen CPUs, mehreren Threads, usw. Mit genügend Wissen über die Anwendung und genügend Erfahrung im Betrieb von Django-Anwendungen, lässt sich der Application Server nun möglichst optimal konfigurieren, wofür i. d. R. genügend Optionen zur Verfügung stehen.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
Kubernetes Deployment von Django-Anwendungen
:::
<img src="/img/blogs/django-development-for-kubernetes-1.jpg" alt="Kubernetes deployment of Django">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
In <strong>Kubernetes</strong> sieht das nun etwas anders aus. Auch dort lässt sich unsere Django-Anwendung in einem <strong>Container</strong> mittels uwsgi ausführen. Allerdings ist eine auf die Hardware abgestimmte <strong>Optimierung</strong> des Application Servers im Container eher fehl am Platze. Schon allein, da sich ja nicht direkt beeinflussen lässt, auf welcher Node im Cluster der Container nun tatsächlich ausgeführt wird. In Kubernetes kann weiterhin mit dem <strong>Replication-Wert</strong> die <strong>horizontale</strong> Skalierung der Anwendung konfiguriert werden. Zusätzlich kann mit dem Horizontal Pod Autoscaler eine Mindest- und Maximalanzahl an Pods spezifiziert werden. Kommt die App beispielsweise mit den Requests nicht mehr zurecht, kann Kubernetes einfach einen <strong>neuen</strong> Container <strong>parallel</strong> starten.
:::
:::globalParagraph
Es gibt noch weitere Punkte, die wir in unserer Django-Anwendung beachten müssen, wenn sie mit Kubernetes ausgeführt wird. Hier sind z. B. die <strong>Liveness-, Readiness-</strong> und <strong>Startup-Probes</strong> genannt. Unsere Anwendung muss Endpunkte für die Probes zur Verfügung stellen, die regelmäßig von Kubernetes abgefragt werden, um zu beurteilen, ob ein Container z. B. neu gestartet werden muss, oder ob er gerade <strong>Traffic</strong> empfangen kann. Das kann dann doch zu etwas <strong>Boilerplate-Code</strong> führen, oder auch dazu, dass die Django-Anwendung u. U. Funktionalität einfach nur für das <strong>Hosting</strong> bereitstellen muss.
:::
:::globalParagraph
All das wollen wir eigentlich nicht: Wir wollen einen <strong>Application Server, der direkter mit Kubernetes verzahnt ist.</strong>
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
Django-Hurricane
:::
<img src="/img/blogs/django-development-for-kubernetes-2.jpg" alt="Django Hurricane">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Aus diesem Grund haben wir <strong>Django Hurricane</strong> entwickelt. Dabei bauen wir auf dem Tornado web server auf. Hurricane lässt sich als einfacher <strong>Django-Management-Command</strong> ausführen und mittels <strong>Flags</strong> auch konfigurieren. Es sind keine weiteren Konfigurationsdateien nötig.
:::
:::globalParagraph
Die Installation kann einfach mittels <em>pip</em> erfolgen:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">pip install hurricane
</code></pre>
<p>:::</p>
<p>:::globalParagraph
Hurricane muss daraufhin in die INSTALLED_APPS des Django-Projekts hinzugefügt werden. Weiterhin sollte auch ein Logger konfiguriert werden, damit Log-Nachrichten angezeigt werden:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-json">INSTALLED_APPS += (
  'hurricane',
)

LOGGING = {
    # [...]
    “loggers”: {
        # [...]
        “hurricane:”: {
          "handlers": ["console"],
          "level": os.getenv("HURRICANE_LOG_LEVEL", "INFO"),
          "propagate": False,
        },
    },
    # [...]
}
</code></pre>
<p>:::</p>
<p>:::globalParagraph
Starten lässt sich Django-Hurricane mit dem Management-Command:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">python manage.py serve
</code></pre>
<p>:::</p>
<p>:::globalParagraph
Auch die Konfiguration findet komplett mit Optionen des Management-Commands statt. Hier ist eine Auflistung der momentan vorhandenen Optionen:
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>static: Ausliefern von Static-Files</li>
<li>media: Ausliefern von Media-Files</li>
<li>auto-reload: Code-Änderungen dynamisch laden</li>
<li>debug: Debug Flag von Tornado setzen (nicht mit djangos DEBUG=True zu verwechseln)</li>
<li>port: Der Port auf dem Tornado laufen soll (default: 8000)</li>
<li>probe: Der Pfad für die Liveness- und Readiness-Probes (default: /alive)</li>
<li>probe-port: Der Port unter welchem die Probes zu erreichen sind (default: ein Port nach --port)</li>
<li>no-probe: Probe-Endpunkt deaktivieren
:::</li>
</ul>
<p>:::globalParagraph
Mit diesen grundlegenden Konfigurationsmöglichkeiten lassen sich bereits einige Szenarien abdecken und wir können Django-Hurricane damit bereits auf <strong>Produktivsystemen</strong> einsetzen.
:::
:::globalParagraph
Damit die Probes auch ihren Sinn erfüllen können, muss gewährleistet sein, dass sie tatsächlich eine Aussage über die <strong>Erreichbarkeit</strong> und <strong>Verfügbarkeit</strong> der Anwendung treffen und es nicht einfach nur ein Endpunkt ist, der eher unabhängig von der Anwendung zu erreichen ist. Bei Hurricane werden der Django-Code und der Probe-Endpunkt zwar auf zwei verschiedenen Ports ausgeliefert, da die komplette Code-Basis in der gleichen <strong>asyncio-Loop</strong> ausgeführt wird, lässt sich aber implizit ein Rückschluss auf die Erreichbarkeit und Verfügbarkeit der Anwendung treffen.
:::
:::globalParagraph
Es kann durchaus nötig sein, dass es mehr Logik benötigt, um die Liveness oder Readiness einer Anwendung zu ermitteln, als einfach nur einen Endpunkt, der eine 200 zurückliefert. Hurricanes Probe-Endpunkt ruft Djangos Check Framework  auf. Dadurch ist es möglich, die benötigte <strong>zusätzliche Logik</strong> für die Anwendung in Django-Checks abzubilden.
:::</p>
<p>:::globalTitle{:size="md" .mb-5}
Roadmap und weitere Entwicklungen
:::
<img src="/img/blogs/django-development-for-kubernetes-3.jpg" alt="Roadmap">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Auch wenn Hurricane bereits für Produktivsysteme verwendet werden kann, steht die Entwicklung erst ganz am <strong>Anfang</strong>. Uns schweben noch einige <strong>weitere Features</strong> vor, die To-do-Liste im GitHub Repository bietet noch einige freie Checkboxen an. <strong>Unterstützung</strong> bei der Weiterentwicklung ist natürlich wie bei allen Open Source-Projekten immer willkommen. Wir bleiben auf jeden Fall weiter am Ball, es wird künftig sicherlich auch noch den ein oder anderen Blogpost, z. B. über <strong><a href="/blog/neue-features-fuer-django-hurricane/">neue Django-Hurricane-Features</a>{.bs-link-blue}</strong>, oder auch Anwendungsszenarien geben.
:::</p>]]></content:encoded>
            <category>Django</category>
            <category>Kubernetes</category>
            <category>Entwicklung</category>
            <category>Betrieb</category>
            <enclosure url="https://blueshoe.de/img/blogs/hurricane_logo_text.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Django Hurricane: Deine Django-Anwendung auf Kubernetes-Kurs]]></title>
            <link>https://blueshoe.de/blog/django-hurricane-kubernetes</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/django-hurricane-kubernetes</guid>
            <pubDate>Fri, 22 Aug 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Mit Django Hurricane entwickelst du skalierbare Anwendungen, die perfekt zu Kubernetes passen. Erfahre, wie du deine Workflows optimierst und die volle Power moderner Container-Orchestrierung für dich nutzt.</p>
<p>Wer <a href="https://www.djangoproject.com/">Django</a>{target="_blank"} liebt, weiß seine Stärken zu schätzen: schnelle Entwicklung, ein robustes ORM und eine riesige Community. Doch wenn es um den Einsatz in modernen, cloud-nativen Umgebungen wie Kubernetes geht, stößt das traditionelle Django-Setup an seine Grenzen. Hier kommt Django Hurricane ins Spiel – ein Projekt, das deine Django-Anwendung fit für die Orchestrierung macht.</p>
<p><img src="/img/blog/django-hurricane.svg" alt="Django Hurricane">{.object-cover .max-w-full .mb-5}</p>
<p>::GlobalBlogLevelInfo</p>
<ul>
<li>Kubernetes Basics</li>
<li>Das Projekt <a href="https://django-hurricane.io/">Django Hurricane</a>{target="_blank"}</li>
<li><a href="https://kubernetes.io/de/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/">Kubernetes Probes</a>{:target="_blank"}</li>
<li><a href="https://kubernetes.io/de/docs/tasks/run-application/horizontal-pod-autoscale/">Kubernetes Horizontal Pod Autoscaler</a>{:target="_blank"}</li>
<li>Optional <a href="https://cloud.google.com/kubernetes-engine?hl=de">Google Kubernetes Engine</a>{:target="_blank"}</li>
</ul>
<p>Solltest du Fragen haben, oder dir etwas unklar sein, kannst du die Kommentarfunktion unter dem Artikel nutzen.
::</p>
<p><a href="https://django-hurricane.io/">Django Hurricane</a>{target="_blank"} integriert Konzepte wie Liveness- und Readiness-Probes direkt in den Django-Management-Kontext. Das bedeutet, Kubernetes kann jederzeit den Zustand deiner Anwendung verstehen und intelligent darauf reagieren – zum Beispiel, indem es Traffic erst dann an einen neuen Pod sendet, wenn dieser wirklich bereit ist.</p>
<p>Doch das ist nur der Anfang. Die letzten Updates (Version 1.5.0 und 1.6.0) haben eine Reihe mächtiger neuer Funktionen mitgebracht, die das Entwicklerleben noch einfacher machen. Schauen wir uns die Highlights genauer an.</p>
<h2>Neue Superkräfte: Monitoring, Ressourcen-Kontrolle und feines Tuning</h2>
<p>Die jüngsten Versionen von Hurricane haben sich darauf konzentriert, dir mehr Kontrolle und bessere Einblicke in deine Anwendung zu geben – genau das, was man in einer dynamischen Kubernetes-Umgebung braucht.</p>
<h2>Besseres Monitoring und Logging mit structlog &#x26; Prometheus (v1.5.0)</h2>
<p>Gute Observability ist das A und O für stabile Systeme. Hurricane macht hier einen großen Schritt nach vorne.</p>
<h3>Prometheus-Metriken out-of-the-box</h3>
<p>Mit dem Start des Servers via <code>$ python manage.py serve</code> wird standardmäßig ein Metrik-Endpunkt unter <code>/metrics</code> auf dem <em>internen Port</em> bereitgestellt. Diese Metriken sind im Prometheus-Format und können von deinem Monitoring-System einfach ausgelesen werden. Das ist die Grundlage für Alarme und Dashboards. Falls du das nicht benötigst, kannst du es einfach mit der Option <code>--no-metrics</code> deaktivieren. 📈</p>
<h3>Strukturiertes Logging</h3>
<p>Sobald du <code>structlog</code> zu deinem Projekt hinzufügst, integriert sich Hurricane automatisch. Strukturiertes Logging ist ein Game-Changer, weil Logs nicht mehr nur Textwüsten, sondern maschinenlesbare Daten sind. Das erleichtert die Analyse und Fehlersuche ungemein.
Tipp: Für Nutzer der <em>Google Kubernetes Engine (GKE)</em> empfiehlt sich zusätzlich <code>structlog-gcp</code>, um die Logs perfekt für das Google Cloud Logging aufzubereiten.</p>
<h2>Intelligentes Ressourcenmanagement (v1.5.0)</h2>
<p>In einer Container-Welt sind Ressourcen wie Speicher kostbar und müssen gut verwaltet werden.</p>
<h3>Speicherlimit setzen</h3>
<p>Speicherlimit setzen: Mit <code>--max-memory</code> kannst du eine Obergrenze für den Arbeitsspeicher festlegen (z. B. <code>--max-memory 512M</code>). Überschreitet deine Anwendung dieses Limit, wird sie kontrolliert neu gestartet. Das verhindert unkontrollierte Abstürze durch "Out of Memory"-Fehler und überlässt Kubernetes die Steuerung über den Neustart des Pods.</p>
<h3>Worker-Threads anpassen</h3>
<p>Die Option <code>--workers</code> gibt dir die Kontrolle über die Anzahl der Threads im <code>ThreadPoolExecutor</code>. Damit kannst du die Performance für I/O-intensive Aufgaben gezielt optimieren.</p>
<hr>
<h2>Mehr Flexibilität und Kontrolle (v1.6.0)</h2>
<p>Version 1.6.0 legt den Fokus auf Konfiguration und die Handhabung spezieller Anwendungsfälle.</p>
<h3>Umgang mit großen Datenmengen</h3>
<p>Deine App verarbeitet große Datei-Uploads oder riesige JSON-Payloads? Mit <code>--max-body-size</code> und <code>--max-buffer-size</code> kannst du das Verhalten des zugrundeliegenden Tornado-Servers präzise steuern. Damit verhinderst du Pufferüberläufe und sorgst auch bei hohem Datenaufkommen für Stabilität.</p>
<h3>Konfiguration auf deine Art</h3>
<p>Flexibilität ist Trumpf. Hurricane lässt sich nun über drei Wege konfigurieren: <strong>klassische CLI-Argumente</strong>, Einträge in den <strong>Django Settings</strong> oder – und das ist besonders für Kubernetes-Deployments ideal - über <strong>Umgebungsvariablen</strong>. Dies folgt dem bewährten <a href="https://12factor.net/">"12-Factor App"-Prinzip</a>{target="_blank"} und ermöglicht saubere, umgebungsspezifische Konfigurationen ohne Code-Änderungen.</p>
<hr>
<h2>Deep Dive: API-Skalierung mit HPA und Prometheus-Metriken</h2>
<p>Eine der größten Stärken von Kubernetes ist die Fähigkeit zur automatischen Skalierung. Aber die Standardskalierung nach CPU- oder Speicherauslastung ist für I/O-intensive Anwendungen wie Django-APIs oft nicht ideal. Eine API kann unter Volllast stehen (z.B. durch viele langsame Datenbankabfragen), ohne dass die CPU-Last signifikant ansteigt.</p>
<p>Eine viel bessere Metrik ist der <em>request backlog</em> – also die Anzahl der Anfragen, die gerade aktiv von einem Pod bearbeitet werden. Wenn diese Zahl steigt, bedeutet das, dass die Anwendung an ihre Belastungsgrenze kommt und wir mehr Instanzen benötigen.</p>
<p>Im Folgenden zeige ich dir, wie man genau das mit Django Hurricane auf der Google Kubernetes Engine (GKE) umsetzt.</p>
<h3>Schritt 1: Die richtige Metrik identifizieren</h3>
<p>Django Hurricane stellt über seinen Prometheus-Endpunkt <code>/metrics</code> eine Vielzahl von Messwerten bereit. Für unseren Anwendungsfall ist eine Metrik wie <code>request_queue_length</code> ideal. Sie zeigt uns die Anzahl der gleichzeitig verarbeiteten Anfragen pro Pod. Unser Ziel ist es, zu sagen: "Wenn im Durchschnitt mehr als 5 Anfragen pro Pod aktiv sind, starte einen neuen Pod."</p>
<p><img src="/img/blogs/gke-hpa-1.png" alt="GKE Metrics">{.mx-auto .w-1/2}</p>
<h3>Schritt 2: Prometheus-Metriken in GKE sammeln</h3>
<p>Der einfachste Weg, Prometheus-Metriken in GKE zu nutzen, ist der <a href="https://cloud.google.com/stackdriver/docs/managed-prometheus">Google Cloud Managed Service for Prometheus</a>{target="_blank"}. Wenn dieser in deinem Cluster aktiviert ist, musst du Kubernetes nur noch sagen, wo es die Metriken deiner Anwendung finden kann.</p>
<p>Wir weisen Kubernetes an, den <em>Port 8081</em> (den internen Port von Django Hurricane) nach Metriken abzusuchen.
Hier ein Beispiel für ein Deployment-Manifest:</p>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-django-api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: my-django-api
  template:
    metadata:
      labels:
        app: my-django-api
    spec:
      containers:
      - args:
        - python /app/manage.py serve --req-queue-len 100 --command "collectstatic
          --no-input" --port 8080 --static --max-memory 950
        name: django-hurricane
        image: your-django-app-image:latest
        ports:
        - containerPort: 8080
          name: http
          protocol: TCP
        - containerPort: 8081
          name: metrics
          protocol: TCP
</code></pre>
<p>Dann brauchen wir noch ein <code>PodMonitoring</code>:</p>
<pre><code class="language-yaml">apiVersion: monitoring.googleapis.com/v1
kind: PodMonitoring
metadata:
  name: my-django-api
spec:
  endpoints:
  - interval: 5s  # Interval um den Metrics-Endpunkt abzufragen
    metricRelabeling:
    - action: keep
      regex: request_queue_.+  # Wir exportieren nur diese eine Metrik
      sourceLabels:
      - __name__
    port: metrics  # Das ist der Name des Ports (siehe oben am Deployment)
  selector:
    matchLabels:
      name: my-django-api  # Der Pod Selector
  targetLabels:
    metadata:
    - pod
    - container
</code></pre>
<p>Damit landen die Daten auch schon in der Google Cloud Console.</p>
<h3>Schritt 3: Den Horizontal Pod Autoscaler (HPA) anlegen</h3>
<p>Jetzt kommt der spannendste Teil. Wir definieren einen <a href="https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/">Horizontal Pod Autoscaler (HPA)</a>{target="_blank"}, der die Metrik aus dem Managed Prometheus Service ausliest und darauf reagiert.</p>
<p>Die von Google Cloud Managed Prometheus gesammelten Metriken erhalten ein spezielles Präfix. Unsere Metrik <code>request_queue_length</code> wird in Google Cloud Monitoring als <code>prometheus.googleapis.com|request_queue_length|gauge</code> verfügbar sein.</p>
<p>Das HPA-Manifest sieht dann so aus:</p>
<pre><code class="language-yaml">apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-django-api-hpa
spec:
  # Beschreibung des Scaling-Verhaltens
  behavior:
    scaleDown:
      policies:
      - periodSeconds: 15
        type: Percent
        value: 100
      selectPolicy: Max
      stabilizationWindowSeconds: 300
    scaleUp:
      policies:
      - periodSeconds: 15
        type: Pods
        value: 4
      - periodSeconds: 15
        type: Percent
        value: 100
      selectPolicy: Max
      stabilizationWindowSeconds: 0
  # Ziel-Deployment, das skaliert werden soll
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-django-api
  
  # Skalierungsgrenzen
  minReplicas: 2
  maxReplicas: 20
  
  # Metriken, die zur Skalierung verwendet werden
  metrics:
  - type: Pods # Wir messen einen Wert pro Pod
    pods:
      metric:
        # Der vollständige Name der Metrik in Google Cloud Monitoring
        name: "prometheus.googleapis.com|request_queue_length|gauge"
      target:
        type: AverageValue # Wir zielen auf einen Durchschnittswert
        averageValue: "5" # Skaliere hoch, wenn der Durchschnitt über 5 liegt
</code></pre>
<p><strong>Was passiert hier genau?</strong></p>
<ol>
<li>Django Hurricane liefert's: Jeder Pod deiner Django-Anwendung stellt unter <code>:8081/metrics</code> die Anzahl der aktiven Anfragen bereit.</li>
<li>Prometheus sammelt's: Der Managed Prometheus Collector in GKE sieht die Annotationen, liest die Metriken regelmäßig aus und speichert sie.</li>
<li>HPA beobachtet's: Der HPA fragt kontinuierlich den Wert für <code>prometheus.googleapis.com|request_queue_length|gauge</code> ab.</li>
<li>HPA reagiert: Er berechnet den Durchschnittswert über alle laufenden Pods. Liegt dieser Wert über unserem Ziel von 5, weist der HPA das Deployment an, die Anzahl der Replicas zu erhöhen (bis zum Maximum von 20). Fällt der Wert wieder, werden Pods elegant heruntergefahren (bis zum Minimum von 2).</li>
</ol>
<p>Mit diesem Setup schaffst du eine hocheffiziente und reaktionsschnelle API, die sich perfekt an die tatsächliche Last anpasst – und das alles mit Bordmitteln von Django Hurricane, Kubernetes und GKE.</p>
<h2>Fazit: Django und Kubernetes – Ein echtes Dream-Team</h2>
<p>Django Hurricane schließt die Lücke zwischen der Entwicklungsfreundlichkeit von Django und den betrieblichen Anforderungen von Kubernetes. Die neuen Features für Monitoring, Ressourcenmanagement und flexible Konfiguration machen es zu einem unverzichtbaren Werkzeug für alle, die skalierbare und robuste Webanwendungen in der Cloud betreiben wollen.</p>
<p>Wenn du deine Django-Projekte auf das nächste Level heben möchtest, gib <a href="/produkte/django-hurricane/">Django Hurricane</a> eine Chance. Deine Anwendung – und dein DevOps-Team – werden es dir danken.</p>]]></content:encoded>
            <category>Django</category>
            <category>Betrieb</category>
            <enclosure url="https://blueshoe.de/img/blog/django-hurricane.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Django Ninja vs. FastAPI: Ein detaillierter Vergleich]]></title>
            <link>https://blueshoe.de/blog/django-ninja-vs-fastapi</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/django-ninja-vs-fastapi</guid>
            <pubDate>Wed, 23 Jul 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>FastAPI hat die Python-Webentwicklung revolutioniert – doch mit Django Ninja gibt es eine Alternative, die das Beste aus zwei Welten vereint.</p>
<p>Während FastAPI mit seiner modernen, asynchronen Architektur und maximaler Flexibilität punktet, bringt Django Ninja die elegante API-Syntax von FastAPI in das bewährte Django-Ökosystem. In diesem umfassenden Vergleich erfährst du nicht nur die technischen Unterschiede bei Setup, Schema-Definitionen und Middleware-Konzepten, sondern erhältst auch eine fundierte Entscheidungshilfe: Wann ist die radikale Freiheit von FastAPI der richtige Weg, und wann profitierst du mehr von Django Ninjas nahtloser Integration in die Django-Welt?</p>
<p><img src="/img/blog/djninja-fastapi.svg" alt="Django Ninja vs. FastAPI Vergleich">{.object-cover .max-w-full .mb-5}</p>
<p>::GlobalBlogLevelInfo</p>
<ul>
<li><a href="https://de.wikipedia.org/wiki/Representational_State_Transfer">REST API</a>{target="_blank"}</li>
<li><a href="https://www.djangoproject.com/">Django</a>{target="_blank"} Basics, <a href="https://docs.djangoproject.com/en/stable/topics/db/">ORM</a>{target="_blank"}, <a href="https://docs.djangoproject.com/en/stable/topics/http/middleware/">Middlewares</a>{target="_blank"}</li>
<li><a href="https://fastapi.tiangolo.com/">FastAPI</a>{target="_blank"}</li>
<li>Optional <a href="https://de.wikipedia.org/wiki/JSON_Web_Token">JSON Web Token</a>{:target="_blank"}</li>
</ul>
<p>Solltest du Fragen haben, oder dir etwas unklar sein, kannst du die Kommentarfunktion unter dem Artikel nutzen.
::</p>
<h2>Django Ninja vs FastAPI: Ein detaillierter Vergleich zweier moderner Python Web-Frameworks</h2>
<p>Wenn du moderne REST APIs in Python entwickeln möchtest, stößt du unweigerlich auf <a href="https://fastapi.tiangolo.com/">FastAPI</a>{target="_blank"} - das Framework, das die Python-Web-Entwicklung in den letzten Jahren revolutioniert hat. Aber kennst du auch <a href="https://django-ninja.dev/">Django Ninja</a>{target="_blank"}? Diese elegante Alternative bringt die Geschwindigkeit und Type-Safety von FastAPI in die Django-Welt. Lass uns gemeinsam erkunden, wie sich diese beiden Frameworks unterscheiden und wann du welches einsetzen solltest.</p>
<h2>Schnellübersicht: Django Ninja vs. FastAPI auf einen Blick</h2>
<p>| Feature         | Django Ninja                               | FastAPI                                      |
|----------------|--------------------------------------------|----------------------------------------------|
| <strong>Grundlage</strong>  | Auf Django aufgebaut                       | Eigenständiges Framework (basiert auf Starlette) |
| <strong>Performance</strong> | Sehr gut, aber synchron                 | Exzellent, primär asynchron (ASGI)           |
| <strong>Datenbank</strong>  | Django ORM (integriert)                   | Jedes ORM (z.B. SQLAlchemy, Tortoise ORM)    |
| <strong>Admin-Panel</strong> | Django Admin (out-of-the-box)            | Muss selbst gebaut/integriert werden         |
| <strong>Authentifizierung</strong> | Django Auth (integriert)          | Muss selbst implementiert werden             |
| <strong>Lernkurve</strong>  | Sehr flach für Django-Entwickler         | Flach für Python-Entwickler                  |
| <strong>Flexibilität</strong> | Weniger flexibel, aber "batteries-included" | Maximale Flexibilität, aber mehr Setup-Aufwand |
| <strong>Ideal für</strong>  | Bestehende Django-Projekte, schnelle CRUD-APIs | Hochperformante Microservices, neue Projekte |</p>
<h2>Die Grundidee hinter beiden Frameworks</h2>
<p>Bevor wir in die technischen Details eintauchen, lasst mich kurz erklären, was beide Frameworks besonders macht. FastAPI ist ein eigenständiges, asynchrones Web-Framework, das von Grund auf für moderne API-Entwicklung konzipiert wurde. Django Ninja hingegen ist eine Erweiterung für Django, die es erlaubt, APIs mit einer FastAPI-ähnlichen Syntax zu schreiben, während du weiterhin das gesamte Django-Ökosystem nutzen kannst.</p>
<h2>Setup-Vergleich: Der erste Eindruck zählt</h2>
<h3>FastAPI Setup</h3>
<p>Bei FastAPI beginnst du praktisch bei null. Das hat Vor- und Nachteile. Der Vorteil ist die absolute Freiheit - du entscheidest über jeden Aspekt deiner Anwendung. Ein minimales FastAPI-Projekt sieht so aus:</p>
<pre><code class="language-python"># main.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"}

# Starten mit: fastapi dev main.py
</code></pre>
<p>Das war's! Mit nur wenigen Zeilen hast du eine funktionierende API. Aber sobald du Datenbanken, Authentifizierung oder andere Features brauchst, musst du alles selbst konfigurieren:</p>
<pre><code class="language-python"># Ein realistischeres FastAPI Setup
from fastapi import FastAPI
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
import uvicorn

# Datenbank Setup
SQLALCHEMY_DATABASE_URL = "postgresql://user:password@localhost/dbname"
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

app = FastAPI(title="Meine API", version="1.0.0")

# Dependency für Datenbank-Sessions
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()
</code></pre>
<h3>Django Ninja Setup</h3>
<p>Bei Django Ninja hast du bereits ein vollständiges Django-Projekt als Grundlage. Das bedeutet, dass Datenbank-Konfiguration, Migrationen, Admin-Interface und vieles mehr bereits vorhanden sind:</p>
<pre><code class="language-python"># Nach django-admin startproject und pip install django-ninja

# views.py
from ninja import NinjaAPI

api = NinjaAPI()

@api.get("/hello")
def hello(request):
    return {"message": "Hello from Django Ninja"}

# urls.py
from django.urls import path
from my_app.views import api

urlpatterns = [
    path("api/", api.urls),
]
</code></pre>
<p>Der große Unterschied? Mit Django Ninja bekommst du das Beste aus beiden Welten: Die intuitive API-Syntax von FastAPI und die bewährte Infrastruktur von Django. Du musst nicht bei null anfangen, sondern baust auf einem soliden Fundament auf.</p>
<h2>Model Schemas: Der größte Unterschied</h2>
<p>Hier wird es richtig interessant! Beide Frameworks nutzen Python Type Hints für die Validierung, aber die Implementierung unterscheidet sich fundamental.</p>
<h3>FastAPI mit Pydantic</h3>
<p>FastAPI setzt vollständig auf Pydantic für die Datenvalidierung. Pydantic-Modelle sind eigenständige Klassen, die deine Datenstrukturen definieren:</p>
<pre><code class="language-python">from pydantic import BaseModel, Field, validator
from datetime import datetime

class UserBase(BaseModel):
    # es gibt auch eine E-Mail Klasse in Pydantic!
    email: str = Field(..., description="Die E-Mail-Adresse des Benutzers")
    username: str = Field(..., min_length=3, max_length=20)
    
    @validator('email')
    def email_must_be_valid(cls, v):
        if '@' not in v:
            raise ValueError('Keine gültige E-Mail-Adresse')
        return v

class UserCreate(UserBase):
    password: str = Field(..., min_length=8)

class UserResponse(UserBase):
    id: int
    created_at: datetime
    is_active: bool = True
    
    class Config:
        # Erlaubt die Verwendung von ORM-Objekten
        orm_mode = True
</code></pre>
<p>Die Stärke von Pydantic liegt in seiner Flexibilität und den umfangreichen Validierungsmöglichkeiten. Du kannst komplexe Validierungslogik implementieren, automatische Konvertierungen durchführen und sogar verschachtelte Modelle erstellen.</p>
<h3>Django Ninja's Schema-System</h3>
<p>Django Ninja bietet dir zwei Möglichkeiten: Du kannst entweder Pydantic verwenden oder Ninja's eigenes Schema-System. Das Schema-System von Ninja ist speziell für die Integration mit Django ORM optimiert:</p>
<pre><code class="language-python">from ninja import Schema, ModelSchema
from django.contrib.auth.models import User
from typing import Optional

# Option 1: Manuelles Schema
class UserSchema(Schema):
    id: int
    username: str
    email: str
    first_name: Optional[str] = None
    last_name: Optional[str] = None

# Option 2: ModelSchema - Automatisch aus Django Model
class UserModelSchema(ModelSchema):
    class Config:
        model = User
        model_fields = ['id', 'username', 'email', 'first_name', 'last_name']

# Option 3: Erweiterte ModelSchema mit zusätzlichen Feldern
class UserDetailSchema(ModelSchema):
    full_name: str
    post_count: int = 0
    
    class Config:
        model = User
        model_fields = '__all__'
    
    @staticmethod
    def resolve_full_name(obj):
        return f"{obj.first_name} {obj.last_name}"
</code></pre>
<p>Der entscheidende Vorteil von Django Ninja's Schema-System ist die nahtlose Integration mit Django Models. Du musst nicht ständig zwischen ORM-Objekten und Pydantic-Modellen konvertieren. Die <code>ModelSchema</code> Klasse generiert automatisch Schemas basierend auf deinen Django Models, was Duplikation vermeidet.</p>
<h3>Der tiefere Vergleich: Pydantic vs Ninja Schema</h3>
<p>Lass uns die "philosophischen" Unterschiede verstehen:</p>
<p><strong>Pydantic in FastAPI:</strong></p>
<ul>
<li>Vollständig entkoppelt von der Datenbank</li>
<li>Explizite Konvertierung zwischen ORM und Pydantic nötig</li>
<li>Mehr Kontrolle, aber auch mehr Boilerplate</li>
<li>Perfekt für komplexe Validierungen und Transformationen</li>
</ul>
<pre><code class="language-python"># FastAPI mit SQLAlchemy
@app.post("/users/", response_model=UserResponse)
def create_user(user: UserCreate, db: Session = Depends(get_db)):
    # Manuelle Konvertierung nötig
    db_user = UserModel(
        email=user.email,
        username=user.username,
        hashed_password=hash_password(user.password)
    )
    db.add(db_user)
    db.commit()
    db.refresh(db_user)
    # Pydantic konvertiert automatisch dank orm_mode
    return db_user
</code></pre>
<p><strong>Ninja Schema:</strong></p>
<ul>
<li>Eng mit Django ORM verzahnt</li>
<li>Automatische Konvertierung zwischen Django Models und Schemas</li>
<li>Weniger Code, aber auch weniger Flexibilität bei Bedarf</li>
<li>Ideal für CRUD-Operationen mit Django Models</li>
</ul>
<pre><code class="language-python"># Django Ninja
@api.post("/users/", response=UserModelSchema)
def create_user(request, user_data: UserCreateSchema):
    # Direkte Arbeit mit Django ORM
    user = User.objects.create_user(
        username=user_data.username,
        email=user_data.email,
        password=user_data.password
    )
    # Automatische Konvertierung zum Response Schema
    return user
</code></pre>
<h2>Endpoint-Definition: Subtile aber wichtige Unterschiede</h2>
<p>Auf den ersten Blick sehen die Endpoint-Definitionen sehr ähnlich aus, aber der Teufel steckt im Detail.</p>
<h3>FastAPI Endpoints</h3>
<pre><code class="language-python">from fastapi import FastAPI, Path, Query, Body, Header, Depends
from typing import Optional, List

app = FastAPI()

@app.get("/items/{item_id}")
async def read_item(
    item_id: int = Path(..., title="Die ID des Items", ge=1),
    q: Optional[str] = Query(None, max_length=50),
    x_token: str = Header(...),
    current_user: User = Depends(get_current_user)
):
    # Async ist optional aber empfohlen
    return {"item_id": item_id, "q": q}

@app.post("/items/")
async def create_item(
    item: Item = Body(..., embed=True),
    importance: int = Body(...)
):
    return {"item": item, "importance": importance}
</code></pre>
<h3>Django Ninja Endpoints</h3>
<pre><code class="language-python">from ninja import Router, Path, Query, Body, Header
from django.shortcuts import get_object_or_404

router = Router()

@router.get("/items/{item_id}")
def read_item(
    request,  # Django Request Objekt ist immer der erste Parameter
    item_id: int = Path(..., title="Die ID des Items", ge=1),
    q: Optional[str] = Query(None, max_length=50),
    x_token: str = Header(...)
):
    # Nutze Django's ORM direkt
    item = get_object_or_404(Item, id=item_id)
    return {"item_id": item.id, "q": q}

@router.post("/items/", response=ItemSchema)
def create_item(request, item_data: ItemCreateSchema):
    # Django's Authentifizierung ist direkt verfügbar
    if not request.user.is_authenticated:
        return 401, {"detail": "Not authenticated"}
    
    item = Item.objects.create(**item_data.dict(), owner=request.user)
    return item
</code></pre>
<p>Die wichtigsten Unterschiede:</p>
<ol>
<li>
<p><strong>Request-Objekt</strong>: Django Ninja übergibt immer das Django Request-Objekt als ersten Parameter. Das gibt dir Zugriff auf Sessions, User-Objekt und andere Django-Features.</p>
</li>
<li>
<p><strong>Async-Support</strong>: FastAPI ist von Grund auf async, während Django Ninja primär synchron arbeitet (async Support ist experimentell).</p>
</li>
<li>
<p><strong>Response-Handling</strong>: Django Ninja erlaubt es dir, direkt Django Model-Instanzen zurückzugeben, während FastAPI explizite Konvertierung benötigt.</p>
</li>
</ol>
<h2>Middlewares und Lifecycles: Verschiedene Philosophien</h2>
<h3>FastAPI Middleware und Lifecycle</h3>
<p>FastAPI bietet ein minimalistisches aber mächtiges Middleware-System:</p>
<pre><code class="language-python">from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
import time

app = FastAPI()

# CORS Middleware
app.add_middleware(
    CORSMiddleware,
    # ! ACHTUNG: never ever in Production !
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)

# Custom Middleware
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    start_time = time.time()
    response = await call_next(request)
    process_time = time.time() - start_time
    response.headers["X-Process-Time"] = str(process_time)
    return response

# Lifecycle Events
@app.on_event("startup")
async def startup_event():
    # Initialisierung, z.B. Datenbankverbindung
    print("Starting up...")

@app.on_event("shutdown")
async def shutdown_event():
    # Aufräumen
    print("Shutting down...")
</code></pre>
<h3>Django Ninja Middleware</h3>
<p>Django Ninja nutzt das bewährte Django Middleware-System:</p>
<pre><code class="language-python"># Django Middleware (in middleware.py)
class ProcessTimeMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        start_time = time.time()
        response = self.get_response(request)
        process_time = time.time() - start_time
        response['X-Process-Time'] = str(process_time)
        return response

# In settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'myapp.middleware.ProcessTimeMiddleware',
    # ... andere Middlewares
]

# Django Ninja spezifische Features
from ninja import NinjaAPI

api = NinjaAPI()

# API-Level Middleware/Hooks
@api.exception_handler(ValidationError)
def validation_error_handler(request, exc):
    return api.create_response(
        request,
        {"detail": exc.errors()},
        status=422
    )
</code></pre>
<p>Der Hauptunterschied liegt in der Philosophie: FastAPI gibt dir ein leeres Blatt und du baust deine Middleware-Pipeline selbst auf. Django hingegen kommt mit einer umfangreichen Middleware-Sammlung (Security, Session, CSRF, etc.), die du bei Django Ninja automatisch nutzen kannst.</p>
<h2>Automatische Dokumentationsgenerierung: Beide glänzen</h2>
<p>Sowohl FastAPI als auch Django Ninja generieren automatisch interaktive API-Dokumentation. Aber es gibt feine Unterschiede.</p>
<h3>FastAPI Dokumentation</h3>
<pre><code class="language-python">from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI(
    title="Meine tolle API",
    description="Das ist eine ausführliche Beschreibung meiner API",
    version="2.5.0",
    terms_of_service="https://blueshoe.de/terms/",
    contact={
        "name": "API Support",
        "email": "support@blueshoe.de"
    }
)

class Item(BaseModel):
    """Ein Item in unserem System"""
    name: str = Field(..., example="Beispiel Item", description="Der Name des Items")
    price: float = Field(..., gt=0, example=29.99, description="Preis in EUR")
    tax: Optional[float] = Field(None, example=2.5)

@app.post(
    "/items/",
    response_model=Item,
    summary="Erstelle ein neues Item",
    description="Erstelle ein neues Item mit allen Details",
    response_description="Das erstellte Item",
    tags=["items"]
)
async def create_item(item: Item):
    """
    Erstelle ein Item mit allen Informationen:
    
    - **name**: Eindeutiger Name des Items
    - **price**: Preis muss größer als 0 sein
    - **tax**: Optionale Steuerangabe
    """
    # Tatsächlicher Code ....
    return item
</code></pre>
<p>FastAPI generiert sowohl Swagger UI (unter <code>/docs</code>) als auch ReDoc (unter <code>/redoc</code>). Die Dokumentation ist hochgradig anpassbar und nutzt die Pydantic-Modelle für Beispiele.</p>
<h3>Django Ninja Dokumentation</h3>
<pre><code class="language-python">from ninja import NinjaAPI, Schema
from typing import Optional

api = NinjaAPI(
    title="Django Ninja API",
    version="1.0.0",
    description="Meine Django-basierte API"
)

class ItemSchema(Schema):
    """Schema für Items"""
    name: str = Field(..., example="Beispiel Item")
    price: float = Field(..., gt=0, example=29.99)
    tax: Optional[float] = None
    
    class Config:
        schema_extra = {
            "example": {
                "name": "Tolles Produkt",
                "price": 49.99,
                "tax": 3.5
            }
        }

@api.post(
    "/items/",
    response=ItemSchema,
    summary="Erstelle ein neues Item",
    tags=["items"],
    operation_id="create_item"
)
def create_item(request, item: ItemSchema):
    """
    Erstelle ein neues Item.
    
    Diese Funktion speichert das Item in der Django-Datenbank.
    """
    # Tatsächlicher Code ....
    return item
</code></pre>
<p>Django Ninja generiert ebenfalls eine Swagger UI (standardmäßig unter <code>/api/docs</code>). Ein Vorteil ist die Integration mit Django's Authentifizierung - die Dokumentation kann automatisch die verfügbaren Authentifizierungsmethoden anzeigen.</p>
<h2>Authentifizierung &#x26; Sicherheit in der Praxis</h2>
<p>Die Sicherheit von APIs ist entscheidend für den Schutz sensibler Daten. Lassen Sie uns die Authentifizierungsmöglichkeiten in beiden Frameworks vergleichen.</p>
<h3>Token-Authentifizierung</h3>
<p><strong>FastAPI mit JWT:</strong></p>
<pre><code class="language-python">from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext

app = FastAPI()

# Konfiguration
SECRET_KEY = "dein_geheimes_token"
ALGORITHM = "HS256"
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    # Hier würde die Benutzerüberprüfung stehen
    user_dict = {"username": form_data.username}
    access_token = jwt.encode(user_dict, SECRET_KEY, algorithm=ALGORITHM)
    return {"access_token": access_token, "token_type": "bearer"}
</code></pre>
<p><strong>Django Ninja mit JWT:</strong></p>
<pre><code class="language-python">from ninja.security import HttpBearer
from ninja import NinjaAPI
import jwt

class AuthBearer(HttpBearer):
    def authenticate(self, request, token):
        try:
            return jwt.decode(token, "dein_geheimes_token", algorithms=["HS256"])
        except:
            return None

api = NinjaAPI(auth=AuthBearer())

@api.post("/token")
def login(request, username: str, password: str):
    # Benutzerüberprüfung hier
    return {"token": jwt.encode({"username": username}, "dein_geheimes_token")}
</code></pre>
<h3>Berechtigungen (Permissions)</h3>
<p><strong>FastAPI:</strong></p>
<pre><code class="language-python">from fastapi import Depends, HTTPException, status

def get_current_user(token: str = Depends(oauth2_scheme)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
        return username
    except JWTError:
        raise credentials_exception

@app.get("/secure/")
async def read_secure(current_user: str = Depends(get_current_user)):
    return {"message": f"Hallo {current_user}"}
</code></pre>
<p><strong>Django Ninja:</strong></p>
<pre><code class="language-python">from ninja.security import django_auth
from django.contrib.auth.models import User

@api.get("/secure/", auth=django_auth)
def secure_route(request):
    return f"Hallo {request.user.username}"
</code></pre>
<h3>Sicherheits-Middleware</h3>
<p><strong>FastAPI Sicherheits-Header:</strong></p>
<pre><code class="language-python">from fastapi import FastAPI
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware

app = FastAPI()

# Erzwinge HTTPS
app.add_middleware(HTTPSRedirectMiddleware)

# Erlaube nur bestimmte Hosts
app.add_middleware(
    TrustedHostMiddleware,
    allowed_hosts=["example.com", "*.example.com"],
)
</code></pre>
<p><strong>Django Ninja (erbt von Django):</strong></p>
<pre><code class="language-python"># settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    # ...
]

# Sicherheitseinstellungen
SECURE_HSTS_SECONDS = 31536000  # 1 Jahr
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
</code></pre>
<h3>Zusammenfassung der Sicherheitsfeatures</h3>
<ul>
<li>
<p><strong>FastAPI</strong>: Bietet maximale Flexibilität, erfordert aber manuelle Konfiguration vieler Sicherheitsaspekte. Ideal für Entwickler, die volle Kontrolle benötigen.</p>
</li>
<li>
<p><strong>Django Ninja</strong>: Nutzt Djangos bewährte Sicherheitsfeatures wie CSRF-Schutz, Clickjacking-Schutz und sichere Cookies. Bietet eine höhere Sicherheit durch Voreinstellungen, ist aber weniger flexibel in der Anpassung.</p>
</li>
</ul>
<h2>Projektstruktur und Deployment</h2>
<p>Eine klare Projektstruktur und ein reibungsloses Deployment sind entscheidend für den Erfolg Ihrer API. Hier sind die Best Practices für beide Frameworks.</p>
<h3>Projektstruktur</h3>
<p><strong>FastAPI empfohlene Struktur:</strong></p>
<pre><code>fastapi_project/
├── app/
│   ├── __init__.py
│   ├── main.py              # Hauptanwendung
│   ├── config.py            # Konfiguration
│   ├── database.py          # Datenbankverbindung
│   ├── models/              # Pydantic Modelle
│   │   └── schemas.py
│   ├── api/                 # API-Endpunkte
│   │   ├── __init__.py
│   │   ├── v1/              # API Version 1
│   │   │   ├── __init__.py
│   │   │   ├── endpoints/   # Endpunkt-Module
│   │   │   └── deps.py      # Abhängigkeiten
│   ├── core/                # Kernlogik
│   └── tests/               # Tests
├── alembic/                 # Datenbank-Migrationen
├── static/                  # Statische Dateien
├── requirements.txt
└── .env                    # Umgebungsvariablen
</code></pre>
<p><strong>Django Ninja empfohlene Struktur:</strong></p>
<pre><code>django_project/
├── manage.py
├── requirements.txt
├── .env
├── config/
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── asgi.py
├── apps/
│   ├── __init__.py
│   └── api/
│       ├── __init__.py
│       ├── schemas.py      # Pydantic-Schemas
│       ├── routers.py      # API-Router
│       └── tests/
└── static/
</code></pre>
<h3>Deployment mit Docker</h3>
<p><strong>FastAPI Dockerfile:</strong></p>
<pre><code class="language-dockerfile"># Basis-Image mit Python 3.9
FROM python:3.9-slim

# Arbeitsverzeichnis setzen
WORKDIR /app

# Abhängigkeiten installieren
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Anwendung kopieren
COPY . .

# Port freigeben
EXPOSE 8000

# Startbefehl
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
</code></pre>
<p><strong>Docker Compose für FastAPI:</strong></p>
<pre><code class="language-yaml">version: '3.8'

services:
  web:
    build: .
    ports:
      - "8000:8000"
    env_file: .env
    depends_on:
      - db
    restart: unless-stopped

  db:
    image: postgres:13
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: ${DB_NAME}
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    ports:
      - "5432:5432"

volumes:
  postgres_data:
</code></pre>
<p><strong>Django Ninja Dockerfile:</strong></p>
<pre><code class="language-dockerfile"># Basis-Image mit Python 3.9
FROM python:3.9-slim

# Arbeitsverzeichnis setzen
WORKDIR /app

# System-Abhängigkeiten installieren
RUN apt-get update &#x26;&#x26; apt-get install -y --no-install-recommends \
    build-essential \
    libpq-dev \
    &#x26;&#x26; rm -rf /var/lib/apt/lists/*

# Python-Abhängigkeiten installieren
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Anwendung kopieren
COPY . .

# Statische Dateien sammeln
RUN python manage.py collectstatic --noinput

# Port freigeben
EXPOSE 8000

# Startbefehl
CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000"]
</code></pre>
<h3>Deployment-Optionen</h3>
<p><strong>FastAPI:</strong></p>
<ul>
<li><strong>Uvicorn/ASGI-Server</strong>: Für Produktionseinsatz hinter einem Reverse-Proxy</li>
<li><strong>Gunicorn mit Uvicorn Workern</strong>: Für bessere Auslastung</li>
<li><strong>Cloud-Anbieter</strong>: AWS (ECS, EKS), Google Cloud Run, Azure App Service</li>
<li><strong>Serverless</strong>: AWS Lambda mit Mangum</li>
</ul>
<p><strong>Django Ninja:</strong></p>
<ul>
<li><strong>Gunicorn/Uvicorn</strong>: Für synchrone/asynchrone Endpunkte</li>
<li><strong>Daphne</strong>: ASGI-Server für volle asynchrone Unterstützung</li>
<li><strong>Cloud-Anbieter</strong>: Wie FastAPI, plus spezielle Django-Hosting-Anbieter</li>
<li><strong>PaaS</strong>: Heroku, PythonAnywhere, Railway</li>
</ul>
<h3>CI/CD Integration</h3>
<p>Beide Frameworks können problemlos in CI/CD-Pipelines integriert werden. Hier ein Beispiel für GitHub Actions:</p>
<pre><code class="language-yaml">name: Django CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:13
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: test
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
    - uses: actions/checkout@v3
    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.9'
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
    - name: Run tests
      env:
        DATABASE_URL: postgresql://test:test@localhost:5432/test
      run: |
        python manage.py test
</code></pre>
<h2>Praktische Pro-Tipps</h2>
<h3>1. HTTP-Status-Codes richtig nutzen</h3>
<p><strong>Problem:</strong> Django Ninja hat standardmäßig keine integrierte HTTP-Status-Code-Enumeration.</p>
<p><strong>Lösung:</strong> Erstelle eine wiederverwendbare <code>http_status.py</code> Datei:</p>
<pre><code class="language-python"># http_status.py
from http import HTTPStatus
from typing import Any, Dict, Optional
from ninja import Response

def json_response(
    data: Any = None,
    status: int = HTTPStatus.OK,
    headers: Optional[Dict[str, str]] = None,
) -> Response:
    """Erstellt eine JSON-Response mit korrektem Status-Code."""
    return Response(
        content=data,
        status=status.value,
        headers=headers or {},
        content_type="application/json",
    )

# Verwendung:
# return json_response({"message": "Erfolg!"}, status=HTTPStatus.CREATED)
</code></pre>
<h3>2. Performance-Optimierung für Django Ninja</h3>
<pre><code class="language-python"># 1. Verwende select_related und prefetch_related
@api.get("/users/{user_id}")
def get_user(request, user_id: int):
    user = get_object_or_404(User.objects.select_related('profile'), id=user_id)
    return {"name": user.name, "email": user.email}

# 2. Paginierung für bessere Performance
from ninja.pagination import paginate, PageNumberPagination

@api.get("/articles/", response=List[ArticleSchema])
@paginate(PageNumberPagination, page_size=20)
def list_articles(request):
    return Article.objects.all()
</code></pre>
<h3>3. FastAPI Performance-Tuning</h3>
<pre><code class="language-python"># 1. Verwende Pydantic's orm_mode für bessere Performance
class UserResponse(BaseModel):
    id: int
    username: str
    email: str
    
    class Config:
        orm_mode = True

# 2. Caching für häufig abgerufene Endpunkte
from fastapi_cache import FastAPICache
from fastapi_cache.backends.redis import RedisBackend
from fastapi_cache.decorator import cache

@router.get("/expensive-query/")
@cache(expire=300)  # Cache für 5 Minuten
async def expensive_query():
    # Langsame Abfrage hier
    return {"result": "Daten aus dem Cache oder berechnet"}
</code></pre>
<h3>4. Fehlerbehandlung</h3>
<p><strong>Django Ninja:</strong></p>
<pre><code class="language-python">from ninja import NinjaAPI
from ninja.errors import HttpError

api = NinjaAPI()

@api.exception_handler(ValueError)
def handle_validation_error(request, exc):
    return api.create_response(
        request,
        {"detail": str(exc)},
        status=400,
    )
</code></pre>
<p><strong>FastAPI:</strong></p>
<pre><code class="language-python">from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse

app = FastAPI()

@app.exception_handler(ValueError)
async def validation_exception_handler(request: Request, exc: ValueError):
    return JSONResponse(
        status_code=400,
        content={"detail": str(exc)},
    )
</code></pre>
<h3>5. Dokumentation verbessern</h3>
<p><strong>FastAPI:</strong></p>
<pre><code class="language-python">@app.get(
    "/items/{item_id}",
    response_model=Item,
    summary="Hole ein Item",
    description="""
    Diese Route gibt ein einzelnes Item zurück.
    - **item_id**: Die eindeutige ID des Items
    """,
    responses={
        200: {"model": Item, "description": "Das angeforderte Item"},
        404: {"description": "Item nicht gefunden"},
    },
)
async def read_item(item_id: int):
    return {"id": item_id, "name": "Beispiel"}
</code></pre>
<p><strong>Django Ninja:</strong></p>
<pre><code class="language-python">@api.get(
    "/items/{item_id}",
    response=ItemSchema,
    summary="Hole ein Item",
    description="""
    Diese Route gibt ein einzelnes Item zurück.
    - **item_id**: Die eindeutige ID des Items
    """,
    tags=["Items"],
    operation_id="get_item",
)
def get_item(request, item_id: int):
    return {"id": item_id, "name": "Beispiel"}
</code></pre>
<h3>6. Umgebungsvariablen verwalten</h3>
<p>Erstelle eine <code>config.py</code> für beide Frameworks:</p>
<pre><code class="language-python"># config.py
import os
from pydantic import BaseSettings, PostgresDsn

class Settings(BaseSettings):
    # Datenbank
    DATABASE_URL: str = "postgresql://user:password@localhost/dbname"
    
    # Sicherheit
    SECRET_KEY: str = "your-secret-key"
    ALGORITHM: str = "HS256"
    ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
    
    class Config:
        env_file = ".env"
        case_sensitive = True

settings = Settings()
</code></pre>
<h3>7. Logging einrichten</h3>
<p><strong>Für beide Frameworks:</strong></p>
<pre><code class="language-python">import logging
from pathlib import Path

# Logger konfigurieren
log_dir = Path("logs")
log_dir.mkdir(exist_ok=True)

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    handlers=[
        logging.FileHandler(log_dir / "api.log"),
        logging.StreamHandler(),
    ],
)

logger = logging.getLogger(__name__)

# Verwendung:
# logger.info("API gestartet")
# logger.error("Ein Fehler ist aufgetreten", exc_info=True)
</code></pre>
<h2>Fazit: Wann solltest du welches Framework wählen?</h2>
<p>Nach diesem tiefen Einblick fragst du dich sicher: "Welches Framework ist das richtige für mich?" Die Antwort hängt von deinen Anforderungen ab.</p>
<p><strong>Wähle FastAPI wenn:</strong></p>
<ul>
<li>Du eine neue API von Grund auf entwickelst</li>
<li>Maximale Performance und async-Support kritisch sind</li>
<li>Du volle Kontrolle über jeden Aspekt deiner Anwendung willst</li>
<li>Dein Team bereits Erfahrung mit modernen Python-Patterns hat</li>
<li>Du primär APIs entwickelst (keine traditionellen Web-Views)</li>
</ul>
<p><strong>Wähle Django Ninja wenn:</strong></p>
<ul>
<li>Du bereits ein Django-Projekt hast oder planst</li>
<li>Du Django's Utilities (ORM, Admin, Auth, etc.) nutzen möchtest</li>
<li>Dein Team Django-Erfahrung hat</li>
<li>Du eine Mischung aus API und traditionellen Django-Views brauchst</li>
<li>Schnelle Entwicklung wichtiger ist als maximale Performance</li>
</ul>
<p>Beide Frameworks sind hervorragend und die Entscheidung ist keine Entweder-Oder-Frage. In der Praxis kannst du sogar beide in verschiedenen Projekten oder Microservices einsetzen. Das Wichtigste ist, dass du die Stärken und Schwächen verstehst und das richtige Werkzeug für deine spezifische Aufgabe wählst.</p>
<p>Die Entscheidung zwischen FastAPI und Django Ninja ist keine reine Technologiefrage – es geht darum, das richtige Werkzeug für deine spezifischen Anforderungen zu finden. Beide Frameworks ermöglichen es dir, moderne, performante APIs zu entwickeln, die den höchsten Standards entsprechen. Die Kunst liegt darin, die Stärken des gewählten Frameworks optimal zu nutzen und eine Architektur zu entwickeln, die nicht nur heute funktioniert, sondern auch morgen noch erweiterbar ist.</p>
<p>Stehst du vor der Entscheidung, welches Framework für dein nächstes Projekt das richtige ist? Oder planst du eine Migration deiner bestehenden API? Bei Blueshoe haben umfangreiche Erfahrung mit beiden Frameworks und unterstützen dich gerne dabei, die optimale Lösung für deine Anforderungen zu finden. Von der Architekturberatung über die Implementierung bis hin zur Performance-Optimierung – lass uns gemeinsam deine API-Vision Wirklichkeit werden lassen. Kontaktiere uns für ein unverbindliches Beratungsgespräch und profitiere von unserer Expertise in der modernen Python-Webentwicklung!</p>
<h2>Häufig gestellte Fragen (FAQs)</h2>
<h3>1. Kann ich von FastAPI zu Django Ninja (oder umgekehrt) migrieren?</h3>
<p>Die Migration ist definitiv möglich, aber der Aufwand hängt stark von deiner Codebasis ab. Von FastAPI zu Django Ninja ist oft einfacher, da du "nur" ein Django-Projekt drumherum bauen musst. Die API-Syntax ist so ähnlich, dass viele Endpoints mit minimalen Anpassungen übernommen werden können. Der größte Aufwand entsteht bei der Migration der Datenbank-Layer - von SQLAlchemy zu Django ORM.</p>
<p>Die umgekehrte Richtung (Django Ninja zu FastAPI) erfordert mehr Arbeit, da du die gesamte Django-Infrastruktur ersetzen musst. Du musst Authentifizierung, Datenbankzugriff, Middleware und andere Django-Features neu implementieren. Mein Tipp: Plane eine schrittweise Migration über mehrere Sprints und nutze die Übergangszeit, um beide APIs parallel zu betreiben.</p>
<h3>2. Welches Framework ist schneller in der Performance?</h3>
<p>FastAPI hat in reinen Benchmarks meist die Nase vorn, besonders bei asynchronen Operationen. Die Zahlen können beeindruckend sein - FastAPI kann teilweise doppelt so viele Requests pro Sekunde verarbeiten. Aber Vorsicht: Diese synthetischen Benchmarks spiegeln selten die Realität wider!</p>
<p>In der Praxis ist der Flaschenhals fast nie das Web-Framework selbst, sondern Datenbankzugriffe, externe API-Calls oder komplexe Business-Logik. Django Ninja's Performance ist für die allermeisten Anwendungen mehr als ausreichend. Wenn du wirklich jede Millisekunde brauchst, ist FastAPI mit async/await die bessere Wahl. Aber bedenke: Premature optimization is the root of all evil. Optimiere erst, wenn du echte Performance-Probleme identifiziert hast.</p>
<h3>3. Wie sieht es mit Testing aus?</h3>
<p>Beide Frameworks bieten exzellente Testing-Unterstützung, aber mit unterschiedlichen Ansätzen.</p>
<p>Bei FastAPI nutzt du typischerweise TestClient von Starlette:</p>
<pre><code class="language-python">from fastapi.testclient import TestClient
client = TestClient(app)
response = client.get("/items/1")
assert response.status_code == 200
</code></pre>
<p>Django Ninja profitiert vom ausgereiften Django-Test-Framework mit all seinen Features wie Fixtures, Transactional Tests und der Test-Datenbank. Du kannst sowohl Django's TestCase als auch Ninja's eigenen Test-Client verwenden. Der große Vorteil: Du kannst deine API-Tests nahtlos mit anderen Django-Tests kombinieren und hast Zugriff auf Dinge wie Factory Boy oder Model Mommy für Test-Daten.</p>
<h3>4. Kann ich beide Frameworks im selben Projekt verwenden?</h3>
<p>Technisch gesehen ja, aber ich rate davon ab! Du könntest theoretisch FastAPI für hochperformante Endpoints und Django (mit Ninja) für den Rest verwenden. In der Praxis führt das aber zu unnötiger Komplexität: zwei verschiedene Routing-Systeme, zwei Arten von Middlewares, potenzielle Konflikte bei Dependencies.</p>
<p>Eine bessere Strategie wäre eine Microservice-Architektur, wo verschiedene Services unterschiedliche Frameworks nutzen. Zum Beispiel: Django Ninja für deine Haupt-API mit Nutzerverwaltung und Business-Logik, FastAPI für einen spezialisierten Service, der Echtzeit-Datenverarbeitung macht.</p>
<h3>5. Welches Framework hat die steilere Lernkurve?</h3>
<p>Das hängt stark von deinem Hintergrund ab! Wenn du bereits Django kennst, wirst du dich bei Django Ninja sofort zuhause fühlen. Die API-Syntax ist intuitiv und du kannst dein Django-Wissen direkt anwenden.</p>
<p>FastAPI hat initial eine flachere Lernkurve, wenn du bei null startest. Die Konzepte sind modern und klar strukturiert. Aber sobald du über "Hello World" hinausgehst, musst du viele Entscheidungen treffen und Dinge selbst implementieren. Das kann überwältigend sein, wenn du nicht genau weißt, was du brauchst.</p>
<p>Mein Rat: Wenn du Python-Webentwicklung lernen willst, starte mit FastAPI für das Verständnis moderner API-Konzepte. Wenn du produktiv werden willst und bereits etwas Erfahrung hast, könnte Django Ninja der schnellere Weg sein.</p>
<h3>6. Wie steht es um die Zukunftssicherheit beider Frameworks?</h3>
<p>FastAPI hat enormen Momentum gewonnen und wird von einer sehr aktiven Community unterstützt. Große Unternehmen wie Microsoft, Netflix und Uber setzen es ein. Die Entwicklung ist sehr aktiv und das Framework wird ständig verbessert.</p>
<p>Django Ninja ist jünger und hat eine kleinere Community, profitiert aber von der Stabilität des Django-Ökosystems. Solange Django relevant bleibt (und dafür spricht alles), wird auch Django Ninja eine solide Wahl sein. Der Vorteil: Selbst wenn Django Ninja die Entwicklung einstellen würde, könntest du relativ einfach auf Django REST Framework oder pure Django-Views migrieren.</p>
<h3>7. Gibt es Situationen, wo keines der beiden Frameworks passt?</h3>
<p>Absolut! Wenn du eine GraphQL-API bauen willst, schau dir Strawberry oder Graphene an. Für WebSocket-heavy Anwendungen könnte Starlette oder Django Channels besser passen.</p>
<p>Für sehr einfache APIs oder Prototypen könnte auch Flask mit Flask-RESTX ausreichen. Und wenn du in der Data Science Welt unterwegs bist, könnte Streamlit oder Gradio für API-ähnliche Interfaces interessant sein. Das Schöne am Python-Ökosystem ist die Vielfalt - es gibt für jeden Use Case das passende Tool!</p>]]></content:encoded>
            <category>Django</category>
            <category>FastAPI</category>
            <category>API</category>
            <category>Entwicklung</category>
            <enclosure url="https://blueshoe.de/img/blog/djninja-fastapi.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[django-o365: Ein modernes Mailbackend für Django und Exchange Online]]></title>
            <link>https://blueshoe.de/blog/django-o365-ein-modernes-mailbackend-fur-django-und-exchange-online</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/django-o365-ein-modernes-mailbackend-fur-django-und-exchange-online</guid>
            <pubDate>Wed, 11 Feb 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><em>django-o365</em> ist ein Open-Source Mailbackend für Django, das den Versand von E-Mails über Exchange Online mit OAuth 2.0 ermöglicht. Entstanden aus realen Projektanforderungen, richtet sich das Paket an Teams, die Microsoft 365 einsetzen und ihren E-Mail-Versand sicher, wartbar und ohne Workarounds umsetzen möchten. Dieser Artikel gibt einen Überblick über Motivation, Funktionsumfang und Einsatz.</p>
<p><img src="/img/blog/django-mailing-o365.svg" alt="django-o365 Mailbackend für Django und Exchange Online">{.object-cover .max-w-full .mb-5}</p>
<h2>Moderne E-Mail-Authentifizierung für Django-Anwendungen</h2>
<p>E-Mails sind ein zentraler Bestandteil vieler Webanwendungen – und genau deshalb besonders kritisch, wenn sie nicht zuverlässig funktionieren. Viele Unternehmen setzen dabei auf Microsoft 365 und Exchange Online, während klassische SMTP-Authentifizierung zunehmend eingeschränkt oder ganz abgeschaltet wird. OAuth 2.0 ist inzwischen der empfohlene Standard.</p>
<p>Mit <a href="https://github.com/Blueshoe/django-o365">django-o365</a>{target="_blank"} haben wir ein Mailbackend für <a href="/technologien/python-django-agentur/">Django</a>{target="_blank"} entwickelt, das den Versand von E-Mails über Exchange Online mit OAuth-2.0-Authentifizierung ermöglicht und sich nahtlos in den bestehenden Django-Mail-Workflow integriert. In diesem Beitrag zeigen wir, auf welchen Bausteinen es aufsetzt und wie du es in deinem eigenen Projekt einsetzen kannst.</p>
<h2>Ausgangslage: Mailversand mit django-exchange</h2>
<p>In einem Bestandsprojekt eines unserer Kunden wurde der Mailversand bisher mit einem lokalen Postfach via Microsoft Exchange realisiert. Zu diesem Zweck haben wir damals das Paket <a href="https://github.com/Blueshoe/django-exchange">django-exchange</a>{target="_blank"} entwickelt. Das Postfach wurde nun zu Exchange Online mit OAuth 2.0 Authentifizierung migriert. Da es für Django noch kein geeignetes Paket gab, haben wir django-o365 entwickelt. Unsere größte Motivation dafür war eine saubere Django-Integration, sodass wir in unserem Projekt lediglich das Email-Backend austauschen müssen.</p>
<h2>Technisches Fundament: python-o365</h2>
<p>Als technisches Fundament setzt django-o365 auf das etablierte Python-Paket <a href="https://github.com/O365/python-o365">python-o365</a>{target="_blank"}. Dieses stellt eine vollständige Abstraktion der Microsoft-Graph-API bereit und übernimmt unter anderem die Authentifizierung via OAuth 2.0 sowie den eigentlichen Versand der E-Mails über Exchange Online.</p>
<p>django-o365 nutzt python-o365 gezielt als Transport-Schicht, kapselt dessen Funktionalität jedoch vollständig hinter dem bekannten Django-Mail-Interface. So profitieren wir von einer stabilen und aktiv gepflegten Basis, ohne diese direkt im Anwendungscode verwenden zu müssen.</p>
<p>Auf eine eigene Implementierung der Microsoft-Graph-API haben wir bewusst verzichtet: python-o365 deckt den benötigten Funktionsumfang bereits zuverlässig ab und erlaubt es uns, den Fokus auf eine saubere Django-Integration, Wartbarkeit und einfache Nutzung zu legen.</p>
<h2>Architektur &#x26; Funktionsweise</h2>
<p>django-o365 ist bewusst schlank aufgebaut und folgt dem Prinzip, sich nahtlos in bestehende Django-Strukturen einzufügen. Kern des Pakets ist ein eigenes Django-Mailbackend, das die standardisierte Django-Mail-API implementiert. Sobald Django eine E-Mail versendet, übernimmt django-o365 die Übergabe an python-o365, welches wiederum den Versand über die Microsoft Graph API abwickelt.</p>
<p>Die Authentifizierung erfolgt vollständig über OAuth 2.0. Access- und Refresh-Tokens werden dabei von python-o365 verwaltet und erneuert, ohne dass der Anwendungscode davon etwas mitbekommt. django-o365 fungiert somit als Bindeglied zwischen Django und Exchange Online: Es übersetzt Djangos Mailversand in Graph-API-Aufrufe, ohne zusätzliche Logik in der Anwendung zu erzwingen.</p>
<p>Diese klare Trennung hat zwei Vorteile: Zum einen bleibt der Django-Code unverändert und unabhängig von Microsoft-spezifischen Details, zum anderen können wir auf eine bewährte Bibliothek für die komplexe OAuth- und API-Logik zurückgreifen.</p>
<h2>Verwendung in Django</h2>
<p>Da django-o365 aktuell noch nicht auf PyPI veröffentlicht ist, wird es direkt aus dem Git-Repository eingebunden, z.B. über die requirements.txt. Weitere Abhängigkeiten, insbesondere python-o365, werden automatisch mitinstalliert.</p>
<p>Auf Microsoft-Seite ist eine App-Registrierung in Microsoft Entra ID (ehemals Azure AD) erforderlich. Dort wird eine Anwendung angelegt, die die Berechtigung zum Versand von E-Mails über Microsoft Graph erhält (z.B. <code>Mail.Send</code>). Für die OAuth-Authentifizierung werden eine Client-ID, ein Client-Secret sowie der Tenant benötigt. Zusätzlich muss festgelegt werden, ob der Versand über ein Benutzerkonto oder ein Shared Mailbox Postfach erfolgen soll.</p>
<p>In Django beschränkt sich die Konfiguration auf die Settings. Dort wird lediglich das Mailbackend auf django-o365 umgestellt und die benötigten Zugangsdaten hinterlegt:</p>
<pre><code class="language-python">EMAIL_O365_TENANT_ID = 'my-tenant-id'
EMAIL_O365_CLIENT_ID = 'my-client-id'
EMAIL_O365_CLIENT_SECRET = 'my-super-secret'
EMAIL_O365_SENDER = 'mysender@example.com'

# set backend
EMAIL_BACKEND = 'django_o365.backend.O365EmailBackend'
</code></pre>
<p>Am eigentlichen Anwendungscode muss nichts geändert werden. Bestehende Aufrufe von <code>send_mail</code>, <code>EmailMessage</code> oder <code>EmailMultiAlternatives</code> funktionieren weiterhin unverändert. Der Wechsel auf Exchange Online mit OAuth 2.0 erfolgt ausschließlich über Konfiguration – genau das war eines der zentralen Ziele bei der Entwicklung von django-o365.</p>
<h2>Open Source &#x26; Ausblick</h2>
<p>Wir bei Blueshoe sind überzeugt vom Open Source Ansatz. Somit ist django-o365 selbstverständlich als Open-Source-Projekt konzipiert und wird offen auf GitHub entwickelt. Der aktuelle Fokus liegt bewusst auf einem klaren, stabilen Kern: dem zuverlässigen Mailversand über Exchange Online mit OAuth-2.0-Authentifizierung im Credentials-Workflow. Der Versand von E-Mails inklusive Anhängen wird bereits vollständig unterstützt und deckt damit die gängigen Anwendungsfälle in Webprojekten ab.</p>
<p>Für die Zukunft sind weitere Authentifizierungs-Workflows denkbar, etwa stärker benutzerbezogene Szenarien. Der Anspruch bleibt dabei derselbe: eine saubere Django-Integration ohne unnötige Komplexität im Anwendungscode. Feedback, Anregungen und Beiträge aus der Community sind ausdrücklich willkommen – sie helfen dabei, das Paket gezielt weiterzuentwickeln und praxisnah zu halten.</p>]]></content:encoded>
            <category>Python</category>
            <category>Django</category>
            <category>Entwicklung</category>
            <enclosure url="https://blueshoe.de/img/blog/django-mailing-o365.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Docker Desktop und Kubernetes]]></title>
            <link>https://blueshoe.de/blog/docker-desktop-und-kubernetes</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/docker-desktop-und-kubernetes</guid>
            <pubDate>Wed, 08 Mar 2023 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>In diesem Artikel werfen wir einen Blick auf Docker Desktop im Jahr 2023 und konzentrieren uns darauf, wie Entwickler mit Kubernetes veröffentlicht. Wir möchten die bequemste Entwicklererfahrung ("DX") für Kubernetes-basierte Entwicklungsworkflows bieten, und Docker Desktop könnte eine gute Grundlage sein. Dann schauen wir es uns an.</p>
<p><img src="/img/blogs/docker-desktop-and-kubernetes.jpg" alt="mein Bild">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalTitle{:size="lg" .mb-5 .mt-8}
Installation
:::
:::globalParagraph
Die Installation von Docker Desktop ist für alle gängigen Plattformen recht einfach. Es gibt auch Installationskandidaten für Apples neue Silicon- und Intel-basierte Chipsätze. Das ist praktisch und erleichtert den Einstieg für Entwickler, die mit der Container-basierten Entwicklung beginnen möchten.
:::
:::globalParagraph
Als jemand, der mit Linux (speziell mit Ubuntu) arbeitet, stört es mich jedoch ein wenig, ein >500 MiB Installationspaket aus dem Browser auf meinen Rechner herunterzuladen. Normalerweise möchte ich ein Software-Repository hinzufügen und damit automatisch Update-Strategien konfigurieren. Wenn ich Docker Desktop aktualisieren möchte (ja, Docker Desktop sagt mir in der Benutzeroberfläche Bescheid), muss ich ein weiteres Installationspaket von der Website herunterladen und den Updater entsprechend ausführen.
:::
:::globalParagraph
Im Kern ist die Idee einfach: Führe einen logischen oder "virtuellen" Kubernetes-Cluster innerhalb eines physischen oder "Host"-Kubernetes-Clusters aus. Dieses Konzept bringt eine neue Ebene der Flexibilität, da es Entwicklern ermöglicht, schnell isolierte Umgebungen zu erstellen, ohne den Aufwand, mehrere physische Cluster zu verwalten. Mit diesem Ansatz können Entwickler Produktionsumgebungen replizieren, sicher experimentieren und Anwendungen für das Kubernetes-Ökosystem optimieren.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5 .mt-8}
VM-Backend auf allen Plattformen erforderlich
:::
:::globalParagraph
Docker Desktop benötigt ein virtuelles Maschinen (VM)-Backend auf allen Plattformen, einschließlich Linux. Das ist erforderlich, da Software-Container eine Linux-native Technologie sind und daher auf Windows und Mac nicht direkt unterstützt werden. Auf Windows kannst du entweder das WSL2-Subsystem verwenden (eine leichte VM mit einem begrenzten Hyper-V-Toolset, um den Linux-Kernel auszuführen) oder ein Hyper-V-Backend. Je nach Windows-Edition wird die erste oder zweite Option empfohlen. Auf macOS läuft Docker Desktop mit HyperKit, einem Toolkit zur Einbettung von Hypervisor-Funktionen in eine Anwendung. Auf Linux wird QEMU verwendet.
:::
:::globalParagraph
Die Frage, warum Docker Desktop auf Linux genau eine VM benötigt, wird hier beantwortet. Ich finde die aufgeworfenen Punkte nachvollziehbar, auch wenn es bedeutet, dass ich Leistungseinbußen hinnehmen muss.
:::
:::globalParagraph
Zumindest unter Linux besteht die Alternative darin, nur den Docker-Daemon (auch als Docker-Engine bezeichnet) auszuführen. Dadurch kann ich Container mit systemweiter Leistung ausführen und verwalten. Das ist ein großer Vorteil gegenüber anderen Plattformen. Es scheint jedoch nicht möglich zu sein, Docker Desktop mit einem nativen Docker-Daemon zu verwenden. Das wäre das Beste aus beiden Welten: die Funktionen und Bequemlichkeit von Docker Desktop und die Leistung einer nativen Docker-Engine. Nun, diese Option wird wahrscheinlich nie eintreten.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5 .mt-8}
Leistung &#x26; Bequemlichkeit
:::
:::globalParagraph
Wenn wir über Bequemlichkeit sprechen: Worum geht es genau bei der Entwicklererfahrung mit Docker Desktop? Lass mich mit dem wichtigsten Nachteil im Jahr 2023 beginnen.
:::
:::globalParagraph
Da ich täglich mit der Docker CLI (zusammen mit der Docker-Engine) in Linux arbeite, bin ich sehr daran gewöhnt. Bisher war der Docker-Daemon (prominent) auf Windows oder macOS nicht verfügbar und konnte nur über die Docker Desktop-VM auf diesen Plattformen installiert werden. In der Vergangenheit habt mein Team bei Blueshoe erhebliche Unterschiede in der Geschwindigkeit der Containerausführung und dem Ressourcenverbrauch des VM-basierten Workarounds von Docker Desktop festgestellt. Wir haben beispielsweise <a href="/blog/kubernetes-development/">lokale Kubernetes-Cluster</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}
mit k3d auf Basis von Docker-Containern ausgeführt, was für Linux-Benutzer in Ordnung war, aber für Mac-Benutzer selbst mit vergleichbaren oder besseren Hardware-Spezifikationen (d.h. CPU, Speicher usw.) nahezu unmöglich war.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5 .mt-8}
Echte Rechenleistung erforderlich
:::
:::globalParagraph
Die Startzeit von Docker Desktop, insbesondere auf macOS, ist sehr lang und die Containerleistung ist ehrlich gesagt ziemlich schlecht. Da du die Systemressourcen nur für die gesamte VM anpassen kannst, musst du viel verfügbare Kapazität deines Host-Systems zugunsten der Docker-VM abzweigen. Der Nachteil ist, dass du eine sehr unflexible Ressourcenzuweisung erhältst. Wenn du viel Kapazität in die Docker-VM steckst, wirst du sie wahrscheinlich auf deinem Host-System vermissen und umgekehrt. Nur eine sehr leistungsstarke Maschine (mit mehr als 8 Kernen und mehr als 16 GiB Speicher) wird für ernsthafte containerbasierte Entwicklungsaufgaben ausreichen. In der Realität betreibst du auch eine ressourcenintensive IDE, einen Webbrowser mit vielen geöffneten StackOverflow-Tabs, einige Instant-Messenger und mehr auf deiner Entwicklungsmaschine. Das frisst auch viel Kapazität. Das Ausführen von Docker Desktop erfordert eine weitere enorme Anforderung, die einen gewöhnlichen Entwicklungsscomputer wahrscheinlich in die Knie zwingen wird. Das mindert die Bequemlichkeit und die Entwicklererfahrung von Docker Desktop erheblich.
:::
:::globalParagraph
Das Starten der integrierten Kubernetes-Distribution dauert ebenfalls sehr lange. Dies hat sich mit der WSL2-Integration unter Windows deutlich verbessert. Ich vermute, dass die Ressourcenzuweisung von WSL2 flexibler ist als bei Hyper-V-basierten VMs. Auf macOS ist dies immer noch sehr zeitaufwändig und frustrierend, wenn es mehrmals am Tag durchgeführt wird.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5 .mt-8}
Viel besser als nur eine CLI
:::
:::globalParagraph
Aber abgesehen von der Leistung bietet Docker Desktop eine sehr saubere und intuitive grafische Benutzeroberfläche.Meiner Meinung nach ist es sehr einfach, wenn du bereits zuvor mit Containern gearbeitet hast. Zum Beispiel ist es ein großer Vorteil, mit einem Klick von Logs zu interaktiven Terminals zu Umgebungsvariablen zu wechseln. Das ist ein großer Vorteil gegenüber der reinen CLI (auch wenn du damit sehr schnell bist) und fühlt sich eher wie ein Entwicklungstool an, das nicht nur zum "Ausführen eines Software-Containers" entwickelt wurde. Möchtest du mit Kubernetes arbeiten? - Kein Problem. Aktiviere einfach das Kontrollkästchen in den Einstellungen und warte, bis Kubernetes verfügbar ist.
:::
:::globalParagraph
Alles, was du zusätzlich benötigst, ist kubectl, die primäre Schnittstelle zu jedem Kubernetes-Cluster. Die kubeconfig wird automatisch mit dem richtigen Kontext vorbereitet, sodass kubectl sofort einsatzbereit ist.
:::</p>
<p><img src="/img/blogs/docker-desktop-and-kubernetes-1.jpg" alt="kubernetes">{.object-cover .w-full .mb-5}</p>
<p>:::globalTitle{:size="lg" .mb-5 .mt-8}
Einrichten von Docker Desktop Kubernetes
:::
:::globalParagraph
Die Arbeit mit Kubernetes in Docker Desktop erfordert eine ziemlich leistungsstarke Entwicklungsmaschine. Abgesehen von dieser Anforderung kannst du auf die Einschränkung stoßen, dass nur eine Kubernetes-Version verfügbar ist: diejenige, die mit der installierten Docker Desktop-Version geliefert wird. Das kann ein Problem sein, wenn du die spezifische Kubernetes-Version deines Produktionssystems abstimmen möchtest (im Hinblick auf die Übereinstimmung von Entwicklung und Produktion).
:::
:::globalParagraph
<a href="/blog/minikube-vs-k3d-vs-kind-vs-getdeck-beiboot/">Wenn du eine bestimmte Kubernetes-Version installieren möchtest, schaue dir diesen Artikel an</a>{.bs-link-blue}. Du könntest minikube mit dem --kubernetes-version-Flag und dem Docker-Treiber verwenden, um jede verfügbare Kubernetes-Version auf Docker Desktop zu installieren. Das könnte sogar hinsichtlich der Startzeit schneller sein.
:::
:::globalParagraph
Die mit Docker Desktop gelieferte Kubernetes-Distribution ist neutral. Das ist ein Vorteil, wenn du mit einer produktionsnahen Umgebung arbeiten möchtest. Der Nachteil ist jedoch, dass Entwickler alle Bausteine selbst bereitstellen müssen. Zum Beispiel ist standardmäßig kein Ingress-Controller verfügbar. Im Vergleich zu k3d sind einige zusätzliche Schritte erforderlich, um ihn verfügbar zu machen:
:::</p>
<p>:::globalTitle{:size="lg" .mb-5 .mt-8}
Welcher Ingress-Controller sollte verwendet werden?
:::
:::globalParagraph
Du kannst aus einer Reihe verschiedener Ingress-Controller für Kubernetes wählen. Die Plattform "learnk8s" pflegt eine sehr umfassende Tabelle, die praktisch alle Kubernetes Ingress-Controller vergleicht:
:::</p>
<p><img src="/img/blogs/docker-desktop-and-kubernetes-2.jpg" alt="kubernetes">{.object-cover .w-full .mb-5}</p>
<p>:::globalParagraph
Diese Entscheidung sollte für Entwickler jedoch nicht sehr relevant sein. Wenn du immer noch nicht weißt, welchen Ingress-Controller du mit Docker Desktop verwenden sollst, kannst du wahrscheinlich jeden von ihnen wählen. Ich persönlich empfehle den Ingress-Nginx-Controller, der auch ein offizielles Kubernetes-Projekt ist.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5 .mt-8}
Installiere Ingress-Nginx auf deinem Docker Desktop Kubernetes
:::
:::globalParagraph
Um es zu installieren, musst du zunächst sicherstellen, dass dein kubeconfig-Kontext auf docker-desktop festgelegt ist. In einem Terminal kannst du den folgenden Befehl ausführen:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-json">> kubectl config current-context

docker-desktop
</code></pre>
<p>:::</p>
<p>:::globalParagraph
Wenn dieser Befehl etwas anderes sagt, setze den Kontext auf docker-desktop mit:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-json">> kubectl config use-context docker-desktop

Switched to context "docker-desktop".
</code></pre>
<p>:::</p>
<p>:::globalParagraph
Wende dann einfach die Ingress-Controller-Konfigurationen für Kubernetes an, etwa so:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-json">> kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.6.4/deploy/static/provider/cloud/deploy.yaml
</code></pre>
<p>:::</p>
<p>:::globalParagraph
Du kannst den ingress-controller mit diesem Befehl überprüfen und er sollte folgendes anzeigen:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-json">> kubectl -n ingress-nginx get pod

  NAME                                        READY   STATUS      RESTARTS   AGE
  ingress-nginx-admission-create-szn97        0/1     Completed   0          70s
  ingress-nginx-admission-patch-plpcx         0/1     Completed   0          70s
  ingress-nginx-controller-6b94c75599-vpjjd   1/1     Running     0          70s
</code></pre>
<p>:::</p>
<p>:::globalParagraph
Et voilà, dein lokaler Kubernetes-Cluster ist bereit, echten HTTP-Verkehr zu bedienen.
:::
:::globalParagraph
<strong>Wichtig:</strong> Das Kubernetes von Docker Desktop bindet direkt an die Ports deines lokalen Computers. Das bedeutet, dass der gerade installierte Ingress-Controller auf Port 80 gestartet wird, der auf deinem Computer frei sein muss.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5 .mt-8}
Testen des Ingress-Controllers mit einer kleinen Demo-Anwendung
:::
:::globalParagraph
Bestätigen wir, dass es funktioniert, indem wir eine Test-Workload anwenden und sie mit einem Ingress-Objekt freigeben:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-json">> kubectl apply -f https://raw.githubusercontent.com/gefyrahq/gefyra/main/testing/workloads/hello_dd.yaml
</code></pre>
<p>:::</p>
<p>:::globalParagraph
Dies ist die "hello-nginx" Anwendung, von unserem Kubernetes-Entwicklungstool Gefyra
:::
:::globalParagraph
Wenn du die Ingress-Objekte überprüfen möchtest, verwende:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-json">> kubectl get ingress

NAME            CLASS    HOSTS                    ADDRESS   PORTS   AGE
hello-ingress   &#x3C;none>   hello.127.0.0.1.nip.io             80      7s
</code></pre>
<p>:::
:::globalParagraph
Du erreichst das hello-ingress-Objekt unter http://hello.127.0.0.1.nip.io (dafür müssen nip.io-Domains in deinem aktuellen Netzwerk funktionieren; falls nicht, suche nach "DNS rebind protection"). Sobald du deinen Browser auf diese Adresse richtest, wirst du mit dem Hello Nginx-Bildschirm begrüßt.
:::</p>
<p><img src="/img/blogs/docker-desktop-and-kubernetes-3.jpg" alt="kubernetes">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Es gibt einige Informationen über deinen Cluster preis: den Namen des Pods, der diesen einfachen Prozess ausführt, und die IP-Adresse des Pods.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5 .mt-8}
Wie sieht die Docker Desktop GUI aus?
:::
:::globalParagraph
Wenn du zu Docker Desktop zurückkehrst, findest du alle Container, die wir gerade in Kubernetes installiert haben, auch im Abschnitt "Container".
:::</p>
<p><img src="/img/blogs/docker-desktop-and-kubernetes-4.jpg" alt="kubernetes">{.object-cover .w-full .mb-5}</p>
<p>:::globalParagraph
Durch Klicken auf die Schaltfläche "Details anzeigen" des Containers "k8s_hello-nginx_hellp-nginxdemo" erhältst du Protokolle und viele andere nützliche Informationen. Du kannst sogar über den Tab "Terminal" eine interaktive Shell starten.
:::</p>
<p><img src="/img/blogs/docker-desktop-and-kubernetes-5.jpg" alt="kubernetes">{.object-cover .w-full .mb-5}</p>
<p>:::globalParagraph
Das ist sehr praktisch, wenn du den Zustand des Containers überprüfen oder einen einmaligen Verwaltungsbefehl deiner Anwendung ausführen möchtest.
:::</p>
<p>:::GlobalPodcastSection{:videoId="tyvE9VlSWkE" :videoPosition="left" .mb-6}
::::GlobalPreTitle{:color="text-bs-green" .mb-3}
UNSER PODCAST: TOOLS FOR THE CRAFT
::::
::::GlobalTitle{:tag="h3" .mb-6}
E3: Tiefere Einblicke in Getdeck
::::
::::globalParagraph{:font-size="lg" .mb-4}
Michael und Robert stellen Getdeck vor, demonstrieren es und vergleichen lokale und entfernte Kubernetes- und Vorproduktionscluster.
::::
::::globalParagraph{:font-size="lg" }
Weitere Ausgaben unseres Podcasts findest du hier:
::::
::::GlobalButton{:url="/kubernetes-podcast/" :label="Mehr anzeigen" :color="green"}
::::
:::</p>
<p>:::globalTitle{:size="lg" .mb-5 .mt-8}
Code schreiben mit Docker Desktop Kubernetes
:::
:::globalParagraph
Als Entwickler ist es nur natürlich, dass ich Code schreiben und schnell iterieren möchte. In vielen modernen Umgebungen findet man Kubernetes-basierte Produktionsumgebungen. Daher ist es konsequent, dass auch Kubernetes-basierte Entwicklungsumgebungen im Trend liegen.
:::
:::globalParagraph
Wenn DevOps viel Aufwand betreiben, um gute Kubernetes-Konfigurationen für alle erforderlichen Ressourcen zu schreiben, die die Anwendung ausmachen, warum stellen wir diese Ressourcen dann nicht auch unseren Entwicklern zur Verfügung?
:::
:::globalParagraph
Angenommen, du möchtest eine Kubernetes-basierte Entwicklungsumgebung erstellen. Hier ist, was du brauchst:
:::</p>
<p>:::GlobalBlock{.ol-decimal .mb-5}</p>
<ol>
<li>Sammele alle Ressourcen zur Bereitstellung eines Kubernetes-Clusters (z.B. brauchst du Helm-Charts, Kustomize-Ressourcen oder einfache YAML-Dateien). Wenn du unsicher bist, frage deinen DevOps, dir Anleitungen zur Verfügung zu stellen.</li>
<li>Installiere alle Komponenten, die die Anwendung ausmachen, in deinem lokalen Kubernetes-Cluster  (z.B. erforderliche Datenbanken, Caches, Indizes, <a href="/loesungen/api-entwicklung/">APIs</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} und andere Anwendungen). Alternativ kannst du nur die Komponenten installieren, die wirklich für deine spezielle Anwendung erforderlich sind, um Rechenkapazität zu sparen.</li>
<li>Stelle sicher, dass alles läuft.
:::
:::globalParagraph
Von hier aus hast du im Grunde genommen einige Optionen, um die Entwicklungswerkzeuge einzurichten.
:::</li>
</ol>
<p>:::globalTitle{:size="lg" .mb-5 .mt-8}
Lokale Container-Images in Kubernetes ausführen
:::
:::globalParagraph
Kubernetes ruft normalerweise Images aus einem Container-Register ab, was bedeutet, dass du das von uns erstellte Image nach jeder Änderung hochladen und herunterladen musst. Docker Desktop erleichtert dies, indem es dockershim verwendet, um den Image-Cache zwischen dem Docker-Engine und Kubernetes gemeinsam zu nutzen.
:::
:::globalParagraph
Das dockershim ist eine Komponente, die mit Docker Desktop Kubernetes geliefert wird und zwischen dem Kubernetes kubelet und der Docker-Engine übersetzt.
:::
:::globalParagraph
Das ist ein großer Vorteil für Entwickler, da sie ihre Container-Images nicht hochladen und herunterladen müssen, bevor sie sie in Docker Desktop Kubernetes ausführen können. Diese Funktion ist in gewisser Weise ähnlich zu dem, was du mit k3d, minikube und kind erreichen kannst, indem du das Container-Image auf deine Cluster-Knoten importierst. Mit Docker Desktop musst du jedoch das Container-Image wie gewohnt erstellen und es nicht in einen Kubernetes-Knoten importieren. Das ist sogar schneller als bei anderen lokalen Kubernetes-Lösungen.
:::
:::globalParagraph
Wenn du die Ausgabe der oben genannten "hello-nginx"-Anwendung ändern möchtest, wirf zuerst einen Blick auf die Workload-Spezifikation:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-json">apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-nginxdemo
spec:
  selector:
    matchLabels:
      app: hello-nginx
  replicas: 1
  template:
    metadata:
      labels:
        app: hello-nginx
      spec:
        containers:
            - name: hello-nginx
            image: nginxdemos/hello
            ports:
            - containerPort: 80
</code></pre>
<p>:::</p>
<p>:::globalParagraph
Es sagt, dass das ausgeführte Container-Image "nginxdemos/hello" ist, das vom Dockerhub abgerufen wurde. Du kannst diese Informationen auch mit Docker Desktop im Abschnitt "Container" finden (siehe Screenshot von oben).
:::
:::globalParagraph
Jetzt kannst du entweder eine neue Version dieses Container-Images mit docker build . -t nginxdemos/hello erstellen und den Kubernetes-Pod löschen:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-json">> kubectl get pod

NAME                               READY   STATUS    RESTARTS   AGE
hello-nginxdemo-6c4f96b577-2rkcp   1/1     Running   0          51m

> kubectl delete pod hello-nginxdemo-6c4f96b577-2rkcp

pod "hello-nginxdemo-6c4f96b577-2rkcp" deleted
</code></pre>
<p>:::</p>
<p>:::globalParagraph
Du erhältst fast sofort einen neuen Pod, der jedoch diesmal dein benutzerdefiniertes Container-Image ausführt, das du gerade erstellt hast.
:::
:::globalParagraph
Anstatt das ursprüngliche Container-Image, das mit der Kubernetes-Ressource geliefert wird, wiederzuverwenden, kannst du auch ein benutzerdefiniertes Container-Image erstellen und es beliebig benennen. Anschließend kannst du die Bereitstellung einfach ändern und dieses Image ausführen:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-json">> kubectl patch deployment hello-nginxdemo -p '{"spec":{"template":{"spec":{"containers":[{"name":"hello-nginx","image":"my-custom-image"}]}}}}'
</code></pre>
<p>:::</p>
<p>:::globalParagraph
Dies führt zum gleichen Ergebnis: Der Pod wird mit dem neuen Image neu gestartet, das du gerade erstellt habst. Kubernetes wird sich nicht die Mühe machen, das Image abzurufen, da der Docker-Engine, den du zum Erstellen des Container-Images verwendest, genau derselbe ist wie der von Docker Desktop Kubernetes verwendete. Sie teilen sich den gleichen Image-Cache, was es sehr bequem macht, ein beliebiges Image im lokalen Cluster auszuführen.
:::
:::globalParagraph
Übrigens werden dem Container alle Kubernetes-Ressourcen und -Konfigurationen zur Verfügung stehen. Dies macht ihn der Produktionsumgebung sehr nahe.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5 .mt-8}
Verwendung zusätzlicher Tools für noch schnellere Interaktion
:::
:::globalParagraph
Wenn du das Container-Image nicht jedes Mal neu erstellen möchtest, wenn du eine Code-Änderung vornimmst, brauchst du zusätzliche Tools. Wenn du einen Debugger an den Container-Prozess anhängen möchtest, kannst du dies nicht einfach mit dem <a href="/blog/kubernetes-development/">lokalen Kubernetes</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} tun. Darüber hinaus musst du deine Kubernetes-Konfigurationen im Cluster ändern, wenn du Umgebungsvariablen überschreiben musst (sagen wir, du möchtest einen DEBUG-Flag setzen).
:::
:::globalParagraph
Blueshoe hat <a href="/tools">Gefyra</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} genau für diesen Anwendungsfall entwickelt. Damit kannst du einfach docker run ... – pardon, ich meinte gefyra run ... (es ist fast dasselbe) – dein Container-Image ausführen, ohne einen Kubernetes-Pod neu laden zu müssen. Auf diese Weise kannst du dein aktuelles Arbeitsverzeichnis in den Container einbinden und das Hot-Reloading-Flag in deinem Prozess aktivieren (falls verfügbar). Du kannst ganz einfach Umgebungsvariablen setzen und das Beste daran ist, dass du diesen Container auf einem dedizierten lokalen Port exponieren kannst, um noch schneller iterieren zu können. Der Container verhält sich wie in einem Kubernetes-Pod (einschließlich Netzwerksemantik) und erreicht daher alle auf Kubernetes basierenden Ressourcen wie Datenbanken oder andere Anwendungen.
:::
:::globalParagraph
Dies funktioniert mit der Befehlszeilenanwendung gefyra oder alternativ können Entwickler von der Gefyra Docker Desktop-Erweiterung aus dem Marketplace profitieren.
:::
:::globalParagraph
Schaue dir Gefyra und unseren Getting Started Guide zur Verwendung mit Docker Desktop Kubernetes oder der Docker Desktop-Erweiterung an.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5 .mt-8}
Abschließende Bemerkungen
:::
:::globalParagraph
Obwohl Docker Desktop die Entwicklung mit Kubernetes vereinfacht, gibt es immer noch einige Herausforderungen. Die Ressourcenanforderungen sind überwältigend und der Aufbau eines Clusters, der der Produktion nahe kommt, erfordert Zeit und Wissen. Ich habe eine Diskussion im Docker-Ideenboard auf GitHub erstellt, um diesen Prozess in Zukunft einfacher zu gestalten. Vielleicht wird es aufgegriffen und umgesetzt.
:::
:::globalParagraph
Es gibt bereits einige interessante Docker Desktop-Erweiterungen im Marketplace, die das Entwicklungserlebnis noch weiter verbessern können. Bleib dran für einen weiteren Artikel, in dem wir bestehende Kubernetes-basierte Entwicklungserweiterungen vorstellen möchten.
:::
:::globalParagraph
Langfristig betrachtet sehe ich die lokale Kubernetes-Entwicklung jedoch nicht als nachhaltige Option. Remote-Entwicklungsumgebungen sind die Zukunft! Getdeck Beiboot wird alle auf Kubernetes basierenden Ressourcen ausführen, und mit <a href="/technologien/">Tools</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} wie Gefyra ermöglichen wir Entwicklern, in einer echten Kubernetes-basierten Entwicklungsumgebung mit nur wenigen lokal ausgeführten Containern zu arbeiten.
:::
:::globalParagraph
Wenn du mehr über die Kubernetes-basierte Entwicklung erfahren möchtest, folge mir auf LinkedIn oder schreibe uns bei Blueshoe.
:::</p>]]></content:encoded>
            <category>Kubernetes</category>
            <category>Docker</category>
            <category>Entwicklung</category>
            <enclosure url="https://blueshoe.de/img/blogs/docker-desktop-and-kubernetes.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Docker vs. Podman]]></title>
            <link>https://blueshoe.de/blog/docker-vs-podman</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/docker-vs-podman</guid>
            <pubDate>Mon, 13 Feb 2023 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>In diesem Artikel vergleichen wir Podman und Docker, um zu sehen, wie sie sich im Vergleich zueinander schlagen. Wir beginnen mit einem Überblick darüber,  was die beiden Tools sind und warum du dich für das eine oder das andere entscheiden solltest. Dann gehen wir ins Detail, was jedes Tool einzigartig macht, bevor wir zu unserem Fazit kommen, welches Tool am besten für deine Bedürfnisse geeignet ist: Podman oder Docker!</p>
<p><img src="/img/blogs/docker-vs-podman.jpg" alt="Docker vs. Podman">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalTitle{:size="lg" .mb-5}
Was ist Docker im Jahr 2023?
:::
:::globalParagraph
Docker ist ein langjähriger Akteur in der Container-Welt und existiert seit 2013. Wenn du die Branche schon eine Weile verfolgst, hast du sicherlich schon von Docker gehört oder es sogar selbst verwendet!
:::
:::globalParagraph
In den letzten Jahren hat sich das Unternehmen darauf konzentriert, die Entwicklererfahrung zu verbessern und sicherzustellen, dass Container in allen Phasen des Anwendungslebenszyklus effektiv von Entwicklern genutzt werden können. Es bietet auch eine umfangreiche Funktionssammlung für den Betrieb von Containern in der Produktion. Das Unternehmen hinter der Technologie, Docker Inc., hat auch einen fantastischen geschäftlichen Schwenk vollzogen und ist nun mit seinem abonnementbasierten Modell sehr profitabel. Sich auf Docker als kommerziell unterstütztes Produkt zu verlassen, könnte eine solide Entscheidung für die Zukunft sein.
:::
:::globalParagraph
Da Docker schon lange auf dem Markt ist, unterstützt es auch viele Funktionen, wie den Rootless-Modus (dazu später mehr). Dadurch ist es nicht mehr erforderlich, den Docker-Daemon mit Root-Rechten auf den Servern auszuführen. Das macht es für alle einfacher, da du Container verwenden kannst, ohne dich um privilegierten Zugriff oder die Sicherheitsprobleme kümmern zu müssen, die du als Root-Benutzer sonst hättest.
:::
:::globalParagraph
Docker bietet eine sehr umfassende Dokumentation zu nahezu jedem Thema, das bei der Arbeit mit Containern aufkommt.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
Was ist das Besondere an Podman?
:::
:::globalParagraph
Podman ist eine relativ neue Container-Laufzeitumgebung, hat aber bereits Einzug in viele Standard-Software-Repositories für Linux erhalten. Du musst keine externen Quellen hinzufügen, um es auf deinem Host zu installieren. Es ist manchmal bereits bei einer frischen Installation verfügbar.
:::
:::globalParagraph
Podman läuft ohne Daemon und bietet eine Entwicklungserfahrung, die der von Docker sehr nahe kommt, d.h., die meisten Befehle in der Podman-CLI sind identisch mit denen der Docker-CLI. Podman Desktop, eine grafische Benutzeroberfläche für Podman, sieht ebenfalls fast genauso wie Docker Desktop aus.
:::
:::globalParagraph
Die Dokumentation von Podman ist ehrlich gesagt nicht so gut und lässt bestimmte Themen vollständig aus.
:::
:::globalParagraph
Die folgende Abbildung zeigt einen Graphen von Google Trends, der das wachsende Interesse an Podman in den letzten Jahren deutlich zeigt.
:::
<img src="/img/blogs/docker-vs-podman-1.jpg" alt="docker-vs-podman">{.object-cover .max-w-full .mb-5}
:::globalParagraph
Podman ist ein von Red Hat gesponsertes Community-Projekt.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
Was unterscheidet Podman von Docker?
:::
:::globalParagraph
Podman und Docker haben viele ähnliche Eigenschaften. Beide sind Tools zur Verwaltung von Containern unter Linux, die auf den gleichen Kernel-Funktionen (wie Namespaces und cgroups) aufbauen, die es ihnen ermöglichen, Prozesse voneinander isoliert auszuführen ('Sandboxing'). Die Benutzeroberfläche ist nahezu identisch, was einen einfachen und praktischen Wechsel zwischen den beiden ermöglicht. Du kannst sogar die meisten Container-Images, die du bereits hast, weiterhin verwenden (sofern sie mit dem OCI-Container-Image-Format kompatibel sind).
:::
:::globalParagraph
Lass uns einen genaueren Blick auf die Unterschiede zwischen Podman und Docker werfen.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
Daemon – oder kein Daemon
:::
:::globalParagraph
Docker führt einen Daemon-Prozess ('docked') auf dem Host-System aus, der in der Regel mit Root-Rechten ausgestattet ist. Was macht der Daemon-Prozess in den Tiefen des Systems? Nun, im Grunde alles, was zur Verwaltung von Containern auf dem Host benötigt wird: Überwachung laufender Container-Instanzen, Verwaltung der Container-Images, Bereitstellung von Speichervolumes und vieles mehr. Er erstellt Container-Netzwerke auf Anfrage und kümmert sich um alle niedrigstufigen Container-Dinge, insbesondere containerd und runc. Der Daemon-Prozess erstellt eine API-Schnittstelle mit einem HTTP-Protokoll, um seine Funktionalität für alle Arten von Benutzeroberflächen zugänglich zu machen, einschließlich der Docker-Befehlszeile. Je nach Plattform wird die Docker-Schnittstelle über einen Unix-Socket, eine benannte Pipe oder einen TCP-Port (mit vielen Optionen, um sie sicher zu machen) realisiert. Der Docker-Daemon läuft mit sehr geringem Ressourcenverbrauch.
:::</p>
<p>:::globalTitle{:size="sm" :tag="h3" .mb-5}
Kein Daemon mit Podman
:::
:::globalParagraph
Podman verzichtet dagegen auf den Daemon-Prozess ('daemonlose Container-Engine'). Das Container-Management erfolgt direkt vom Client aus. Daher erlaubt das Aufrufen von Podman mit einem anderen Benutzer als root nur die Operationen, die dieser Benutzer ausführen darf. Dies beschränkt natürlich die Möglichkeiten des Benutzers – aber auch die von Eindringlingen, die einen Container von innen übernehmen könnten.
:::
:::globalParagraph
Aus Sicherheitssicht ist das 'rootless'-Phänomen also eine ziemlich gute Idee. Und doch könnte es irgendwann schnell zu Ende gehen. Darauf werden wir im nächsten Abschnitt genauer eingehen.
:::</p>
<p>:::globalTitle{:size="sm" :tag="h3" .mb-5}
Podman für die Produktion - etwas komplizierter
:::
:::globalParagraph
Ein entscheidenderer Nachteil des Verzichts auf einen Daemon-Prozess wird deutlich, wenn versucht wird, Podman für Produktions-Workloads zu verwenden. Mit Docker kannst du beispielsweise einfach eine 'Restart-Policy' für Container angeben und sicherstellen, dass sie im Falle eines Absturzes neu gestartet werden. Podman führt keinen Prozessmonitor aus und muss diese Aufgabe daher an eine andere Stelle delegieren: Hier kommt unser guter alter Freund systemd ins Spiel. Obwohl systemd sehr verbreitet ist und wahrscheinlich von den meisten Systemadministratoren gut verstanden wird, handelt es sich dennoch um eine sehr komplexe zusätzliche Lösung, die mit eigenen Kosten verbunden ist. Podman unterstützt den Benutzer, indem es die systemd-Einheiten generiert (die Konfiguration, um systemd mitzuteilen, wie ein Prozess überwacht und verwaltet werden soll), aber dies ist ein völlig anderes Ökosystem. Wenn du von Docker kommst, kann dies eine gewisse Einarbeitungszeit erfordern, um alles mit dem gewünschten Verhalten zum Laufen zu bringen. Ein weiterer Pluspunkt für systemd ist jedoch, dass eine Einheit genauso gut mit verringerten Benutzerrechten ausgeführt werden kann.
:::
:::globalParagraph
Ob Docker oder Podman - wenn die Produktionsbereitstellungen ernst genommen werden sollen, muss letztendlich irgendwo ein Daemon-Prozess involviert sein. Und natürlich gibt es einen Dienstprozess (Daemon), wenn du die Podman REST API verwenden möchtest..
:::</p>
<p>:::GlobalPodcastSection{:videoId="tyvE9VlSWkE" :videoPosition="left" .mb-6}
::::GlobalTitle{:tag="h3" .mb-6}
Tools for the craft - Ausgabe 1: Kubernetes-Entwicklungsumgebungen
::::
::::globalParagraph{:font-size="lg" .mb-4}
Hier findest du die erste Ausgabe unseres Kubernetes-Podcasts "Tools für das Handwerk: Die Navigation im Kubernetes-Ökosystem". Michael und Robert sprechen ausführlich über die Feinheiten der lokalen Kubernetes-Entwicklung und geben auch einige echte Codierungsbeispiele.
::::
::::globalParagraph{:font-size="lg" }
Weitere Ausgaben unseres Podcasts findest du hier:
::::
::::GlobalButton{:url="/kubernetes-podcast/" :label="Weitere Podcast-Ausgaben anzeigen" :color="green"}
::::
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
Rootful und rootless
:::
:::globalParagraph
Eine sehr nützliche (und manchmal unterschätzte) Funktion von Docker sind Overlay-Netzwerke. Diese ähneln 'echten' (virtuellen) Netzwerken auf einer Host-Maschine. Docker-Netzwerke ermöglichen alle Arten von komplexen Verbindungstopologien mit Routen, NATs und IP-Pools usw. Das ist besonders nützlich in Situationen, in denen eine bestimmte Produktionsumgebung erreicht und verschiedene Dienste, die eine Anwendung ausmachen, lose gekoppelt werden sollen. Tatsächlich läuft jeder Container in seinem eigenen Namespace im Linux-Kernel, sodass es möglich ist, Ressourcenbeschränkungen für jeden Container, Netzwerkeinstellungen usw. festzulegen. Einer der grundlegenden Gedanken bei der Aufteilung des Linux-Kernels in mehrere Räume war die Prozesssicherheit. Im Moment ist dies größtenteils nur mit Root-Rechten möglich. Dennoch ist Sandboxing mit Namespaces auch mit nicht privilegierten Benutzern möglich.
:::</p>
<p>:::globalTitle{:size="sm" :tag="h3" .mb-5}
Wie es in der Realität aussieht
:::
:::globalParagraph
Wichtige Funktionen verschwinden, wenn Docker im Rootless-Modus ausgeführt wird, und das gilt auch für Podman. Ich habe herausgefunden, dass es eine Option gibt, Podman rootful auszuführen, um diese Fähigkeiten, insbesondere ein ordentliches Netzwerk, zu erhalten.
:::
:::globalParagraph
In der Praxis hat das Podman-Entwicklungsteam eine meiner bescheidenen Meinung nach fragwürdige Lösung für das fehlende Networking geschaffen, indem es das Konzept des „Pods" als Alternative eingeführt hat.
:::
:::globalParagraph
Mit Podman kannst du mehrere Container in einem Pod kompilieren. „Pod" ist der Name für eine höherstufige Organisation von Kernel-Namespaces. Alle Container, die denselben Pod teilen, befinden sich tatsächlich im selben Kernel-Namespace(s). Am wichtigsten ist, dass sie denselben Netzwerk-Namespace teilen. Dadurch können die Container-Prozesse über TCP-Sockets miteinander kommunizieren. Du kannst beispielsweise einen Prozess auf Port localhost:8000 und einen anderen Prozess auf localhost:8001 ausführen. Beide Prozesse können über den TCP-Socket auf localhost miteinander kommunizieren. Dies wäre nicht möglich für zwei separate „podman run ..." (oder mit „docker run ..."), da sie standardmäßig voneinander getrennt sind. Die Verwendung des Pod-Konzepts beseitigt letztendlich die Notwendigkeit für Networking vollständig und somit auch die Notwendigkeit für eine rootful-Betrieb.
:::</p>
<p>:::globalTitle{:size="md" :tag="h3" .mb-5}
Der Podman "Infra Container"
:::
:::globalParagraph
Übrigens: Jedes Podman-Pod erhält einen speziellen Container namens "Infra-Container". Er tut nichts weiter, als einzuschlafen, sobald das Pod erstellt ist. Alle Attribute, die das Pod definieren, werden tatsächlich diesem speziellen Container zugewiesen, einschließlich Port-Bindungen, Kernel-Namespaces, Ressourcenbeschränkungen usw. Sobald das Pod erstellt ist, können diese Attribute nie wieder geändert werden. Angenommen, du erstellst ein neues Pod und fügst später einen Container hinzu, der einen neuen Port mit dem Host bindet - Podman wird dazu nicht in der Lage sein. Du musst das gesamte Pod mit der neuen Port-Bindung (oder anderen Attributen) neu erstellen.
:::</p>
<p>:::globalTitle{:size="md" :tag="h3" .mb-5}
Privilegien vs. Fähigkeiten
:::
:::globalParagraph
Jeden Prozess mit herabgesetzten Rechten auszuführen, bringt erhebliche Einschränkungen mit sich. Das macht Sinn, besonders um zu verhindern, dass ausgenutzte Containerprozesse Systemänderungen vornehmen oder auf andere Prozesse zugreifen können. Das Herabsetzen der Ausführungsrechte ist generell vorzuziehen, und ich nehme dieses Thema sehr ernst. Allerdings führt der Tausch des Sandbox-Mechanismus zugunsten fehlender Netzwerkfähigkeiten eine weitere Klasse von Systemanfälligkeiten ein.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
Podman-Pods und Kubernetes
:::
:::globalParagraph
Das Podman-Team behauptet, dass die Arbeit mit Podman-Pods den Übergang zu Kubernetes viel einfacher macht. Tatsächlich kannst du mit Podman ein Pod erstellen (alle Container, die du benötigst, hinzufügen; bestimmte Attribute festlegen) und automatisch eine gültige Kubernetes YAML-Datei daraus generieren. Und ja, die technische Grundlage ist die gleiche. Aber trotzdem, wer hat nach diesem Feature gefragt?
:::</p>
<p>:::globalTitle{:size="sm" :tag="h3" .mb-5}
Der monolithische Pod
:::
:::globalParagraph
Ich habe dieses Tutorial gefunden, das vorschlägt, einen Webserver, einen Anwendungsserver und die Datenbank in einen Podman-Pod zu packen. Das wäre praktisch, wenn ich dies mit Podman auf einem Serverhost betreiben wollte. Aber hier ist der Punkt. Wer mit Erfahrung in Kubernetes würde jemals eine solche Pod-Definition in einer Kubernetes-Umgebung anwenden? Ich erhalte einen monolithischen Pod, der alles enthält, was eine Anwendung ausmacht. Was ist mit Skalierbarkeit, Ausfallsicherheit und natürlich Sicherheit? Eine ernsthafte Kubernetes-Bereitstellung verwendet abstrakte Arbeitslastdefinitionen, die in "Deployments", "StatefulSets" und anderen höheren Kubernetes-Objekten deklariert sind. Ich habe dieses Muster noch nie in der realen Welt gesehen (was nicht bedeutet, dass es nicht existiert). Die Verwendung von nackten Pods scheint für Kubernetes überhaupt kein praktischer Ansatz zu sein. Wenn es jedoch verwendet wird, um echte Kubernetes-Strukturbereitstellungsmuster wie Sidecars oder Adapter zu konstruieren, wäre ich sehr glücklich.
:::</p>
<p>:::globalTitle{:size="sm" :tag="h3" .mb-5}
Ungerechtfertigte Versprechungen
:::
:::globalParagraph
Daher finde ich dieses Feature irreführend, insbesondere in Bezug auf die Kommunikation und Dokumentation von Podman. Nein, ich kann keinen Podman-Pod auf einem lokalen Rechner definieren und ihn so einfach in die Produktionsumgebung von Kubernetes migrieren. In Kubernetes verwenden wir starke Netzwerkmechanismen wie Lastenausgleicher, IP-Routing, Netzwerkrichtlinien und damit lose Kopplung.
:::</p>
<p>:::globalTitle{:size="md" .mb-5}
Fazit
:::
:::globalParagraph
Ich hoffe, dieser Artikel hat dir ein besseres Verständnis für die Unterschiede zwischen Podman und Docker gegeben. Wie du sehen kannst, gibt es viele Ähnlichkeiten zwischen den beiden Tools, aber sie haben auch einige wesentliche Unterschiede, die je nach Anwendungsfall die eine Option geeigneter machen könnten als die andere. Obwohl Podman sich noch in einem frühen Entwicklungsstadium befindet, hat es bereits Anzeichen dafür gezeigt, eine würdige Alternative zu Docker zu sein, indem es eine einfachere Benutzererfahrung bietet und gleichzeitig die Kompatibilität mit vorhandenen Images aus anderen Registern wie Docker Hub oder Google Container Registry (GCR) beibehält. Ich bin gespannt, wie sich diese Tools im Laufe der Zeit weiterentwickeln, während sie beide neue Funktionen hinzufügen. Schau dir auch Podman Desktop an. Ich bin mir nicht sicher, ob Podman auch den Weg der Entwicklererfahrung („DX“) wie Docker geht oder ob sie versuchen, Produktionsserver zu betreiben. Lass mich wissen, was du denkst.
:::
:::globalParagraph
Fühle dich frei, folge mir auf LinkedIn oder trete unserem Discord bei.
:::</p>]]></content:encoded>
            <category>Docker</category>
            <category>Entwicklung</category>
            <enclosure url="https://blueshoe.de/img/blogs/docker-vs-podman.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[B2B vs B2C E-Commerce]]></title>
            <link>https://blueshoe.de/blog/e-commerce-b2b-b2c</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/e-commerce-b2b-b2c</guid>
            <pubDate>Tue, 07 Mar 2017 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Während im B2C-Bereich Online Shops für Hersteller und Händler mittlerweile Standard sind, fängt der B2B-Sektor gerade erst an das Thema ernsthaft wahrzunehmen. Warum der B2B-Sektor Online ganz anders funktioniert als B2C klären wir hier.</p>
<p><img src="/img/blogs/ruchindra-gunasekara-GK8x_XCcDZ.jpg" alt="ruchindra-gunasekara">{.object-cover .max-w-full .mb-5}</p>
<p>Online-Shopping ist aus unserem Alltag nicht mehr wegzudenken. Vom kleinen Händler hin zu den Dickschiffen wie Zalando, Amazon, Otto - im B2C-Sektor hat sich längst rumgesprochen, dass man ohne einen Online-Shop mindestens Geld auf der Straße liegen lässt, wenn nicht sogar sein Schicksal besiegelt.</p>
<p>Im B2B fangen wir gerade erst so richtig an dieses Verständnis zu wecken. Es wirkt aktuell vieles wie der B2C E-Commerce vor 10 Jahren. Viele Marktteilnehmer setzen erstmal irgendetwas auf. Oftmals halbherzig. Immer das Budget im Blick, entscheidet man sich dann für Standardlösungen aus dem B2C-Umfeld um „erstmal zu schauen wie das läuft". Das ihr Sektor dabei völlig andere Voraussetzungen hat, dass vergessen die B2B-Unternehmen ganz schnell und wundern sich dann, dass der Webshop nicht so läuft wie gedacht.</p>
<p>Aber was sind denn nun diese unterschiedlichen Voraussetzungen?</p>
<h2>GRUNDSÄTZLICHE UNTERSCHIEDE IM B2B / B2C E-COMMERCE</h2>
<p>Zunächst einmal haben selbst große B2B-Händler meist einen viel kleineren Kundenstamm als ein breit aufgestellter B2C-Händler. Logisch, ein Fachhändler für Dachdeckerbedarf hat natürlich eine kleinere Zielgruppe als ein Online-Schuhhändler. Die Umsätze die der Händler für Dachdeckerbedarf aber an einem Kunden erwirtschaften kann, sind natürlich weitaus höher als bei unserem Schuhshop-Beispiel. Ein Dachdecker der mal eben 2000 Dachziegel bestellt, hat natürlich einen üppigeren Warenkorbwert.</p>
<p>Heißt also, der ARPU (Average Revenue per User) ist bei B2B > B2C. Und im Umkehrschluss lassen sich damit folgende simple Thesen aufstellen:</p>
<ul>
<li>Der Wert einer einzelnen Kundenakquise (CLV) im B2B ist deutlich höher als im B2C</li>
<li>Der Verlust (Churn) eines Kunden wiegt im B2B deutlich schwerer als im B2C</li>
</ul>
<p>Aber in die Marketing Economics dieses Themas steigen wir an dieser Stelle nicht tiefer ein und heben uns das für ein anderes Mal auf. Schauen wir uns lieber an welche Unterschiede tatsächlich in den Online-Shops eine Rolle spielen.</p>
<h2>SPEZIELLE KONDITIONEN FÜR BESTELLER</h2>
<p>Im B2B ist es gang und gäbe das Händler mit deinen Kunden viele Bedingungen der Geschäftsbeziehungen einzeln aushandeln. Je größer der potentielle Kunde, desto größer sein Verhandlungshebel. Im Einzelnen heißt das, dass Kunden individuelle Preislisten haben, einzeln verhandelte Zahlungsziele und Modalitäten, unterschiedliche Regelungen für Skonto oder Rabatte, spezielle Lieferbedingungen oder gar Verpackungsbedingungen. Im B2C gibt es diese Unterschiede auf Kundenebene nicht. Im B2B bedeutet das, dass im Prinzip jeder Kunde seinen eigenen Shop haben müsste, in dem seine eigenen verhandelten Konditionen abgebildet würden.</p>
<p>Eine Thematik die in einem B2B-Online-Shop über den Login des Kunden abgebildet würde. In einer Kundenstammdatenbank sind alle spezifischen Konditionen des Kunden hinterlegt und der Webshop ist smart genug um nun alle dieser Logiken für den einzelnen Kunden abbilden zu können.</p>
<h2>KUNDEN BESTELLEN FÜR KUNDEN</h2>
<p>Beispiel: Ein Hersteller beliefert ein Netzwerk an Händlern die seine und die Produkte anderer Hersteller vertreiben. Die Produkte des Herstellers sind individualisierbar und werden vom Endkunden beim Händler nach einer ausgiebigen Beratung bestellt. Der Händler bestellt nun also beim Hersteller ein spezifisch konfiguriertes Produkt für einen spezifischen Kunden. Der Händler möchte also schon im Bestellprozess angeben für welchen Kunden das entsprechende Produkt ist, damit später die Zuordnung leichter fällt sowie eventuelle Retourenprozesse.</p>
<p>Der Händler macht dies ggf. äußerst regelmäßig und möchte im Onlineshop einen Überblick über seine unterschiedlichen Bestellungen für seine Kunden haben. Im Prinzip hat der Händler im Onlineshop seine eigene Kundenkartei und Bestellverwaltung und er hat den Anspruch die Produkte die dann bei Ihm im Laden eintreffen problemlos zuordnen zu können, weil der Hersteller vom Händler gewünschte Merkmale bei der Lieferung mit aufgreift und abbildet. Dies könnten z.B. die Daten des Endkunden sein oder eine händlerspezifische Bestellnummer.</p>
<p>Je einfacher ein E-Commerce-System solche Prozesse für einen B2B-Kunden abbilden kann, desto besser. In einem Standard-Shopsystem gehören solche Funktionalitäten eher nicht zum regulären Funktionsumfang.</p>
<h2>KOMPLEXE SCHNITTSTELLEN UND ANBINDUNG AN BESTANDSPROZESSE</h2>
<p>Gerade im B2B-Sektor sind die Bestandsunternehmen oft schon seit Jahrzehnten am Markt. Dabei sind die Geschäftsprozesse sowie die verwendeten Software-Systeme nicht so schnell mitgewachsen wie man das hoffen würde. Gibt es ein funktionierendes System, gibt es oft wenig Grund dieses zu wechseln, denn Systemwechsel sind bekanntlich immer sehr teuer.</p>
<p>Nun, dies bedeutet, dass B2B E-Commerce-Systeme oft auf Systemlandschaften aufsetzen müssen, die bspw. noch aus den 2000er Jahren stammen. Der B2B-Händler passt seine ERP-Software, seine Produktinformationsverwaltung, sein CRM, seine Produktionsschnittstellen eher nicht an ein E-Commerce-System an, sondern erwartet, dass das E-Commerce-System mit seiner bestehenden Systemlandschaft reibungslos funktioniert. Hier steckt der Teufel im Detail und die Systemintegration ist absolute Expertenarbeit. Versuche das mal mit einem Woo Commerce umzusetzen ;) .</p>
<h2>ANBINDUNG IN PRODUKTIONSPROZESSE</h2>
<p>Wie schon im letzten Absatz angedeutet, sind Integrationen in Bestandslandschaften ein besonders heikles Thema. Dies wird nochmal expliziter, wenn der Online-Shop automatisiert in Produktionsabläufe eingreift. Im B2B-Sektor ist On-Demand Produktion keine Seltenheit. Hier dürfen bei der Produktkonfiguration und Datenübergabe in Produktionssoftware keine Fehler passieren. Ggf. müssen hier Logiktests abgebildet werden oder der Webshop muss mit Rückfragen aus der Produktion umgehen können.</p>
<p>Der Kunde soll möglicherweise auch darüber informiert in welchem Produktionsschritt sich seine Bestellung gerade befindet und wann er mit dem Produkt rechnen kann. Eine solche Integration sollte man entsprechend von Experten begleiten lassen.</p>
<h2>KONFIGURATOREN</h2>
<p>Hier gibt es zur Abwechslung mal eine klare Gemeinsamkeit von B2C- und B2B-Sektor. In beiden Sektoren erfreuen sich Produktkonfiguratoren großer Beliebtheit. Das macht natürlich auch Sinn. Je visueller und flexibler, desto besser. Das Problem: Konfiguratoren sind immer individuelle Entwicklungen. Ein Thema das klassische E-Commerce-Shopsysteme also grundsätzlich schon nicht out-of-the-box anbieten können.</p>
<h2>SCHNELLBESTELLUNGEN</h2>
<p>B2B-Kunden haben deutlich öfter gleiche bzw. sich wiederholende Warenkörbe als B2C-Kunden. Ein Fachhändler bestellt möglicherweise wöchentlich oder monatlich das gleiche oder ein nahezu gleiches Kontingent an Produkten für sein Lager. Diesem Fakt sollten B2C E-Commerce-Systeme Rechnung tragen indem sie solche wiederkehrenden Bestellungen dem Kunden so einfach wie möglich machen. Eine solche Monatsbestellung sollte im Kundenkonto also mit möglichst wenigen Klicks abbildbar sein. Es ist auch denkbar, automatische Bestellungen für den Kunden auszuführen, wenn bspw. dessen eigene Warenwirtschaft mit dem Online-Shop verknüpft ist.</p>
<p>Auch direkte Bestellungen über Artikelnummern, die eigene oder sogar die des Kunden, sowie Bestellungen über einen EAN-Scanner können zu den Features gehören, die Anspruchsvolle B2B-Kunden erwarten.</p>
<h2>SERVICE</h2>
<p>Zugegeben, Anspruch auf guten Service haben B2C- als auch B2B-Kunden. Der B2B-Kunde bedarf hier aber sicherlich einer etwas intensiveren Betreuung. Retourenmanagement, Rechnungen, Lieferscheine, Kommissionsnummern, Kundenstammverwaltung, etc. es gibt wahnsinnig viele unterschiedliche Themen, zu denen ein B2B-Kunde Hilfe benötigen könnte. Wünschenswert ist dafür, dass dem Kunden möglichst viele unterschiedliche Kontaktkanäle zur Verfügung gestellt werden. Nur E-Mail ist dabei eigentlich etwas zu wenig. Telefon, E-Mail, Live-Chat gehören zum Standard-Repertoire. Das Backend des B2B E-Commerce-Systems sollte dabei so aufgestellt sein, dass Service-Mitarbeiter schnell und unkompliziert unterschiedliche Anfragen von Kunden beantworten und bearbeiten können. Die Komplexität des B2B-Kunden sollte sich daher auch in diesem Service-Backend widerspiegeln.</p>
<h2>KUNDENBINDUNG</h2>
<p>Wie schon eingangs des Artikels erwähnt, hat ein einzelner B2B-Kunde einen erheblichen Wert für den Hersteller bzw. Händler. Dementsprechend ist es wichtig, Kunden möglichst lange und nachhaltig an das Unternehmen zu binden. Kundenbindungsprogramme sind im B2C bereits weit verbreitet. Punkte sammeln, Gutscheine bekommen, Freunde einladen, etc. das ist alles kein Neuland mehr. Im B2B hingegen werden auch diese Themen recht stiefmütterlich behandelt. Klar, dass der Händler für Dachdeckerbedarf plötzlich im Payback-Programm auftaucht ist natürlich unwahrscheinlich und wohl auch nicht zielführend. Dennoch können B2B-Händler mit individuellen Kundenbindungs- bzw. Incentiveprogrammen auch bei ihren Kunden punkten und langfristige Geschäftsbeziehungen fördern. Aber auch hier gilt, man sollte sich im Einzelfall anschauen, was Sinn ergibt, was man erreichen möchte und was für den Kunden attraktiv ist.</p>
<p>Zu unsere vergangenen Projekte <a href="/projekte/">hier entlang</a>!</p>]]></content:encoded>
            <category>Digitalisierung</category>
            <category>SEO</category>
            <enclosure url="https://blueshoe.de/img/blogs/ruchindra-gunasekara-GK8x_XCcDZ.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Elasticsearch: Die leistungsstarke Suchlösung für deine Daten.]]></title>
            <link>https://blueshoe.de/blog/elasticsearch-unter-der-lupe</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/elasticsearch-unter-der-lupe</guid>
            <pubDate>Mon, 13 Jan 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Elasticsearch ist eine Open-Source-Such- und Analyse-Engine, die auf Apache Lucene basiert. Sie ermöglicht es, große Mengen an Daten in Echtzeit zu durchsuchen und zu analysieren. In diesem Artikel werfen wir einen genaueren Blick auf Elasticsearch, erkunden seine wichtigsten Funktionen und zeigen dir anhand von Praxisbeispielen, wie du Elasticsearch in dein Projekt integrieren kannst.</p>
<p><img src="/img/blogs/elasticsearch.svg" alt="Blueshoe und FastAPI in Produktion">{.object-cover .max-w-full .mb-5}</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Einführung
::</p>
<p>::GlobalParagraph
In der heutigen datengetriebenen Welt ist die Fähigkeit, Informationen schnell und effizient zu finden, von entscheidender Bedeutung. Elasticsearch bietet eine leistungsstarke Lösung, die es Unternehmen ermöglicht, ihre Daten zu indexieren und in Echtzeit abzufragen. Doch was genau ist Elasticsearch, und warum solltest du es in deinem Unternehmen oder Projekt einsetzen?
::</p>
<p>:GlobalButton{:url="/technologien/elasticsearch/" :label="Erfahre mehr über unsere Elasticsearch-Entwicklungsdienste" :color="blue" .mb-6}</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Was ist Elasticsearch?
::</p>
<p>::GlobalParagraph
Elasticsearch ist eine verteilte, RESTful Such- und Analyse-Engine, die für ihre Geschwindigkeit und Skalierbarkeit bekannt ist. Sie ermöglicht es, Daten in nahezu Echtzeit zu speichern, zu suchen und zu analysieren. Mit Funktionen wie Volltextsuche, facettierter Suche und Aggregationen ist Elasticsearch eine ideale Lösung für Anwendungen, die große Datenmengen verarbeiten müssen.
::</p>
<p>::GlobalTitle{:size="lg" .mt-4 .mb-5}
Vorteile von Elasticsearch
::</p>
<p>::GlobalParagraph
Elasticsearch bietet zahlreiche Vorteile, sowohl für Entwickler als auch für Unternehmen:
::</p>
<p>::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li><strong>Echtzeit-Suche</strong>: Benutzer können Daten in Echtzeit durchsuchen, was die Benutzererfahrung erheblich verbessert.</li>
<li><strong>Skalierbarkeit</strong>: Elasticsearch kann horizontal skaliert werden, um mit wachsenden Datenmengen umzugehen.</li>
<li><strong>Flexibilität</strong>: Dank der Unterstützung für verschiedene Datentypen und Abfragesprachen kann Elasticsearch in einer Vielzahl von Anwendungen eingesetzt werden.</li>
<li><strong>Integration</strong>: Elasticsearch lässt sich leicht in bestehende Systeme integrieren und kann mit anderen Tools wie Kibana und Logstash kombiniert werden.
::</li>
</ul>
<p>::GlobalTitle{:size="lg" .mt-4 .mb-5}
Technische Grundlagen und Funktionsweise
::</p>
<p>::GlobalParagraph
Im Kern basiert Elasticsearch auf Apache Lucene, einer leistungsstarken Such-Engine-Bibliothek. Während Lucene die grundlegende Suchfunktionalität bereitstellt, macht Elasticsearch diese durch eine REST-API und verteilte Architektur wesentlich zugänglicher. Die Anfragesprache von Elasticsearch ist JSON-basiert und ermöglicht sowohl einfache Volltextsuchen als auch komplexe analytische Abfragen. Mit der Query DSL (Domain Specific Language) können Entwickler präzise Suchanfragen formulieren, die von exakten Matches bis hin zu Fuzzy-Suchen reichen.
::</p>
<p>::GlobalParagraph
Hier ein paar Query Beispiele:</p>
<p>:::BlogCode</p>
<pre><code class="language-json">// 1. Einfache Volltextsuche
// Sucht nach Dokumenten, die das Wort "elasticsearch" im Feld "description" enthalten
{
  "query": {
    "match": {
      "description": "elasticsearch"
    }
  }
}

// 2. Fuzzy-Suche mit mehreren Bedingungen
// Findet auch Dokumente mit Tippfehlern und kombiniert mehrere Suchkriterien
{
  "query": {
    "bool": {
      "must": [
        {
          "fuzzy": {
            "title": {
              "value": "elastcsearch",
              "fuzziness": "AUTO"
            }
          }
        },
        {
          "range": {
            "price": {
              "gte": 10,
              "lte": 100
            }
          }
        }
      ]
    }
  }
}
// 3. Komplexe Volltextsuche mit Highlighting
// Sucht nach Phrasen und hebt die gefundenen Stellen hervor
{
  "query": {
    "multi_match": {
      "query": "cloud computing",
      "fields": ["title^2", "description"],
      "type": "phrase"
    }
  },
  "highlight": {
    "fields": {
      "title": {},
      "description": {
        "number_of_fragments": 2,
        "fragment_size": 150
      }
    }
  }
}
</code></pre>
<p>:::
::</p>
<p>::GlobalParagraph
Um diese Queries zu nutzen, können sie über die Elasticsearch REST API gesendet werden, zum Beispiel:
<code>curl -X GET "localhost:9200/my-index/_search" -H "Content-Type: application/json" -d' {query hier einfügen}'</code>
::</p>
<p>::GlobalParagraph
Ein weiteres cooles Feature von Elasticsearch is die Validate API. Mit dieser können Queries vor "richtiger" Ausführung überprüft werden.
Genutzt werden kann sie mittels:
<code>curl -X GET "localhost:9200/my-index/_validate" -H "Content-Type: application/json" -d' {query hier einfügen}'</code>
::</p>
<p>::GlobalParagraph
Besonders hervorzuheben sind Features wie Autocomplete und Suggestions, die durch spezielle Analyzer und Token-Filter realisiert werden. Die NGram- und Edge-NGram-Token-Filter ermöglichen es, Texte in kleinere Einheiten zu zerlegen, was für Autovervollständigung und Teilwortsuche essentiell ist. Darüber hinaus bietet Elasticsearch fortgeschrittene Funktionen wie Geo-Spatial-Suche, Aggregationen für analytische Zwecke und ein ausgefeiltes Scoring-System zur Bewertung der Suchergebnisse.
::</p>
<p>::GlobalTitle{:size="lg" .mt-4 .mb-5}
Deployment und Konfiguration
::</p>
<p>::GlobalParagraph
Elasticsearch lässt sich in verschiedenen Szenarien deployen, von einzelnen Entwicklungsinstanzen bis hin zu großen Produktivclustern. In Kubernetes-Umgebungen erfolgt das Deployment typischerweise über den Elastic Cloud on Kubernetes (ECK) Operator oder durch managed Services wie Elastic Cloud. Die Konfiguration erfolgt über YAML-Dateien, wobei wichtige Aspekte wie Speicherallokation, Netzwerkeinstellungen und Sicherheitsfeatures definiert werden.
::</p>
<p>::GlobalParagraph
Ein typisches Produktions-Setup umfasst mehrere Nodes mit unterschiedlichen Rollen (Master, Data, Ingest) und redundanter Datenhaltung durch Repliken. Für die Entwicklung eignet sich auch ein lokales Setup mit Docker Compose, das schnell aufgesetzt werden kann und die wichtigsten Features für Entwicklung und Tests bereitstellt.
::</p>
<p>::GlobalTitle{:size="lg" .mt-4 .mb-5}
Integration und Ökosystem
::</p>
<p>::GlobalParagraph
Das Elasticsearch-Ökosystem ist besonders umfangreich. Für Python-Entwickler steht der offizielle <a href="https://elasticsearch-py.readthedocs.io/en/v8.17.0/">elasticsearch-py Client</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} zur Verfügung, der eine intuitive API für alle Elasticsearch-Operationen bietet. Content Management Systeme wie Wagtail integrieren Elasticsearch für leistungsfähige Suchfunktionen in Websites. Die Integration erfolgt dabei meist über den Python-Client und ermöglicht Features wie automatische Indizierung von Inhalten und facettierte Suche.
::</p>
<p>::GlobalParagraph
Für das Monitoring und die Verwaltung stehen verschiedene Tools zur Verfügung. Kibana bietet umfangreiche Visualisierungs- und Management-Funktionen, während Grafana sich besonders für das Monitoring von Performance-Metriken eignet. Beats und Logstash ergänzen das Ökosystem um Datenaggregation und -transformation.
::</p>
<p>:::GlobalCallAnExpert{:bg="bg-bs-blue"}
#buttonBlockTitle
Wir können auch deine Daten mit Elasticsearch durchsuchbar machen.
:::</p>
<p>::GlobalTitle{:size="lg" .mt-4 .mb-5}
Vergleich mit Alternativen
::</p>
<p>::GlobalParagraph
Im Vergleich zu Apache Solr, dem anderen großen Player im Bereich Suchengines, zeichnet sich Elasticsearch durch seine einfachere Handhabung und bessere Unterstützung für verteilte Systeme aus. Während Solr traditionell in Enterprise-Umgebungen stark ist, punktet Elasticsearch besonders bei modernen, Cloud-nativen Anwendungen. OpenSearch, ein Fork von Elasticsearch, bietet eine vollständig Open-Source-Alternative mit ähnlichen Funktionen.
::</p>
<p>::GlobalParagraph
PostgreSQL mit seiner Volltext-Suchfunktionalität kann für kleinere Anwendungen ausreichend sein, bietet aber nicht die Skalierbarkeit und spezialisierten Suchfeatures von Elasticsearch. Meilisearch und Typesense sind neuere Alternativen, die sich auf Benutzerfreundlichkeit und schnelle Integration fokussieren, aber nicht die gleiche Funktionstiefe bieten.
::</p>
<p>::GlobalTitle{:size="lg" .mt-4 .mb-5}
Lizenzen und Kosten
::</p>
<p>::GlobalParagraph
Seit 2021 verwendet Elasticsearch eine duale Lizenzierung: Die grundlegende Version ist unter der Server Side Public License (SSPL) verfügbar, während zusätzliche Features unter der Elastic License stehen. Diese Lizenzänderung hat zur Entstehung von OpenSearch geführt, das unter der Apache 2.0 Lizenz steht. Die Kosten für Elasticsearch variieren je nach Nutzungsszenario: Die Self-Hosted-Version verursacht hauptsächlich Infrastrukturkosten, während die Elastic Cloud als managed Service nach Ressourcenverbrauch abgerechnet wird. Enterprise-Funktionen wie maschinelles Lernen oder erweiterte Sicherheitsfunktionen sind kostenpflichtig.
::</p>
<p>::GlobalTitle{:size="lg" .my-5}
Praxisbeispiel - Mayan EDMS in Kubernetes
::</p>
<p>::GlobalParagraph
Als Beispiel für die Verwendung von Elasticsearch möchte ich die Verschmelzung mit dem bekannten Mayan EDMS heranziehen. Hier haben wir das EDMS in einem Kubernetes Cluster eingerichtet. Mayan basiert per Standart auf einem lokalen Such-Index, welcher nicht verwendet werden kann wenn verschiedene Pods ins Spiel gebracht werden. Da jeder Pod seinen eigenen Such-Index bekommt funktioniert die Globale Suche im verteilten Kubernetes Mayan nicht mehr. Hier schafft Elasticsearch Abhilfe. Als globaler Index ermöglicht es die Suche nach Dokumenten, auch wenn diese von unterschiedlichen Pods indiziert werden.
::</p>
<p>::GlobalParagraph
Die größte Herausforderung hierbei war die richtige Konfiguration des Elasticsearch Stacks. Da es nur noch veraltete Helm-Charts für Elasticsearch selbst gibt und das Deployment des kompletten <a href="https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-deploy-eck.html">ECK-Stacks</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} außer Frage stand, mussten wir den unkonventionelleren Weg gehen. Dieser sieht vor, dass man Elasticsearch als einzelne Node deployed. Der Elasticsearch-Operator wird dann automatisch mit deployed. Diese Methode ist die "unkonventionelle", da man sich selbst um richtiges Resourcen-Management sowie Volume Konfiguration kümmern muss. Nach dem Elasticsearch Setup kann man dann einfach die Mayan Instanz mittels der generierten Zugangsdaten zur Elasticsearch Node verbinden und die Dokumente werden indiziert. Wer sich für die Einrichtung von Elasticsearch als einzelne Node interessiert sollte unbedingt diese <a href="https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-deploy-elasticsearch.html">Anleitung</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} auschecken.
::</p>
<p>::div{.mb-8 .mt-8 .relative }
:::GlobalSliderSection{:numberCards=2 data-title="Successful projects" :bg="bg-bs-gray"}
::::GlobalTitle{.mb-6 :size="lg" :tag="h3"}
Beispielprojekte, bei denen wir Elasticsearch eingesetzt haben.
::::</p>
<pre><code>::::GlobalParagraph
Schnellere Suchen, Autocomplete (die Vervollständigung von Suchwörtern) und clevere Vorschläge: in diesen Projekten haben wir Elasticsearch eingesetzt.
::::
</code></pre>
<p>#card1
:GlobalPartial{content=slides/luma-3-blog}</p>
<p>#card2
:GlobalPartial{content=slides/wuc-1-blog}
:::
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Fazit
::</p>
<p>::GlobalParagraph
Elasticsearch ist ein unverzichtbares Werkzeug für Unternehmen, die mit großen Datenmengen arbeiten. Es bietet nicht nur eine leistungsstarke Such- und Analyse-Engine, sondern auch eine Vielzahl von Funktionen, die die Datenverarbeitung erheblich erleichtern. Wenn du nach einer effektiven Lösung für die Suche und Analyse deiner Daten suchst, ist Elasticsearch die richtige Wahl.
::</p>
<p>::GlobalParagraph
Wenn du mehr über die Implementierung von Elasticsearch in deinem Projekt erfahren möchtest, kontaktiere uns! Wir helfen dir dabei, Elasticsearch in dein System zu integrieren und maßgeschneiderte Lösungen zu entwickeln, um deine Daten optimal zu nutzen.
::</p>
<p>::GlobalTitle{:size="lg" .mb-5 .mt-8}
Häufige Fragen
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
1. Wie skaliert man Elasticsearch?
::</p>
<p>::GlobalParagraph
Elasticsearch ist horizontal skalierbar. Du kannst:
::</p>
<p>::GlobalBlock{.ul-disk}</p>
<ul>
<li>Knoten hinzufügen: Neue Server in den Cluster integrieren.</li>
<li>Sharding verwenden: Indizes in kleinere Shards aufteilen, um die Last zu verteilen.</li>
<li>Replikate konfigurieren: Replikationen erhöhen, um Leseanfragen besser zu bewältigen und die Ausfallsicherheit zu verbessern.
::</li>
</ul>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
2. Was ist ein Index in Elasticsearch?
::</p>
<p>::GlobalParagraph
Ein Index ist eine Sammlung von Dokumenten mit ähnlichen Eigenschaften. Er ist vergleichbar mit einer Datenbank in einer relationalen Datenbank. Jeder Index hat einen Namen und kann in mehrere Shards (Datenblöcke) aufgeteilt werden, um die Skalierbarkeit zu erhöhen.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
3. Was sind Shards und Replikate?
::</p>
<p>::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>Shards: Elasticsearch teilt große Indizes in kleinere Teile, sogenannte Shards, um die Verarbeitung auf mehrere Server zu verteilen.</li>
<li>Replikate: Kopien von Shards, die zur Verbesserung der Verfügbarkeit und Ausfallsicherheit dienen.
::</li>
</ul>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .my-5}
4. Welche Arten von Abfragen gibt es in Elasticsearch?
::</p>
<p>::GlobalParagraph
Elasticsearch unterstützt mehrere Abfragetypen, darunter:
::</p>
<p>::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li>Match Query: Für Volltextsuche.</li>
<li>Term Query: Für genaue Übereinstimmungen.</li>
<li>Range Query: Zum Suchen nach Werten innerhalb eines Bereichs.</li>
<li>Bool Query: Kombiniert mehrere Abfragen mithilfe von <code>must</code>, <code>should</code> und <code>must_not</code>.
::</li>
</ul>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
5. Was ist der Unterschied zwischen Elasticsearch, Logstash und Kibana (ELK-Stack)?
::</p>
<p>::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li>Elasticsearch: Die zentrale Such- und Analyse-Engine, die Daten speichert und verarbeitet.</li>
<li>Logstash: Ein Tool zur Datenverarbeitung, das Daten aus verschiedenen Quellen sammelt, transformiert und an Elasticsearch sendet.</li>
<li>Kibana: Eine Visualisierungsplattform, die Daten aus Elasticsearch in Dashboards und Diagrammen darstellt.
::</li>
</ul>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
6. Warum sollten man Elasticsearch wählen?
::</p>
<p>::GlobalParagraph
Elasticsearch hat sich als eine der führenden Suchlösungen etabliert und bietet eine Vielzahl von Funktionen, die es Entwicklern ermöglichen, leistungsstarke Suchanwendungen zu erstellen. Mit der Möglichkeit, große Datenmengen schnell zu verarbeiten und zu analysieren, ist Elasticsearch eine hervorragende Wahl für Unternehmen, die ihre Daten effektiv nutzen möchten.
::</p>]]></content:encoded>
            <category>Elasticsearch</category>
            <category>Kubernetes</category>
            <category>Docker</category>
            <category>Betrieb</category>
            <category>Entwicklung</category>
            <category>Digitalisierung</category>
            <enclosure url="https://blueshoe.de/img/blogs/elasticsearch.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Die Evolution der Applikationsentwicklung zu einem cloud-native Ansatz]]></title>
            <link>https://blueshoe.de/blog/evolution-der-applikationsentwicklung-zu-einem-cloud-native-ansatz</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/evolution-der-applikationsentwicklung-zu-einem-cloud-native-ansatz</guid>
            <pubDate>Thu, 22 Jun 2023 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Ein durch und durch cloud-native Unternehmen zu werden, ist kein leichtes Unterfangen. Dazu bedarf es einer ganzen Evolution. Aber durch welche Phasen dieser Evolution müssen Unternehmen gehen, um beim gewünschten Zustand anzukommen? Und wie sieht dieser Zustand überhaupt aus? Schauen wir es uns einmal zusammen an!</p>
<p><img src="/img/blogs/evolution-of-application-development-to-cloud-native.jpg" alt="The evolution of application development to cloud native">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalTitle{:size="lg" .mb-5}
Phase 1: Normalisierung
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>Teams für die Anwendungsentwicklung verwenden Versionsverwaltung.</li>
<li>Teams für die Anwendungsentwicklung verwenden die üblichen Entwicklungsverfahren.
:::
:::globalParagraph
Dies geschieht zum Beispiel, wenn Git oder SVN verwendet werden. Es gibt dabei die Hauptversionen sowie zusätzliche Features, die es einfacher machen, die Kontrolle über den Anwendungscode zu behalten. Zudem gibt es Versionierungen für Release-Prozesse.
:::
:::globalParagraph
Falls dein Unternehmen noch nicht in dieser Phase angekommen ist, habt ihr in den letzten 20 Jahren wohl in einer Höhle gelebt. Setze die Versionsverwaltung umgehend durch. Jetzt sofort.
:::</li>
</ul>
<p>:::globalTitle{:size="lg" .mb-5}
Phase 2: Standardisierung
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>Teams entwickeln mit einer Reihe Standardtechnologien.</li>
<li>Teams deployen an eine Standard-Betriebsplattform.
:::
:::globalParagraph
Standardisierung kann die Effizienz deines Unternehmens drastisch erhöhen. Das bezieht sich zwar nicht nur auf dein Tech-Team, aber speziell für die heißt das, dass sie mit Container-Technologie wie <a href="/blog/strategien-fur-schlanke-docker-images/">Docker</a>{.bs-link-blue}, Docker-Compose oder <a href="/blog/kubernetes-development/">Kubernetes</a>{.bs-link-blue} arbeiten werden.
:::
:::globalParagraph
Diese Phase ist das Fundament für das Unterfangen, ein Unternehmen cloud-native zu machen.
:::</li>
</ul>
<p><img src="/img/blogs/unikube_c-ndem_Infographic.jpg" alt="unikube_c-ndem_Infographic">{.object-cover .w-full .mb-5}</p>
<p>:::globalTitle{:size="lg" .mb-5}
Phase 3: Expansion
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>Anwendungen bestehen aus mehreren kleineren, beweglichen Elementen und lose gekoppelten Services.</li>
<li>Anwendungen sind auf den Umfang, die Widerstandfähigkeit und die Geschwindigkeit von Veränderungen ausgerichtet.
:::
:::globalParagraph
Dein Unternehmen verwendet serviceorientierte Architektur, Message-Brokering, Event-Streams und lose gekoppelte Interfaces (REST-, GraphQL- etc.). Ganz nach dem Motto „Divide et impera“, also „Teile und herrsche“, können dadurch spezialisierte Teams kreiert und schnellere Entwicklung sowie eine bessere Handhabung komplexer Anwendungen umgesetzt werden.
:::
:::globalParagraph
Um in dieser Phase anzukommen, ist es wichtig, für deine Anwendung bereits von Anfang an eine Struktur aus individuell entwickelten Services einzuplanen. Es ist nämlich wirklich anstrengend, eine monolithische Anwendung zu einem späteren Zeitpunkt aufzuteilen.
:::</li>
</ul>
<p>:::globalTitle{:size="lg" .mb-5}
Phase 4: Automatisierte Bereitstellung der Anwendung
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>Teams verwenden existierende Deployment-Muster erneut.</li>
<li>Versionsverwaltung für Deployment-Muster und Konfigurationen.</li>
<li>Automatische Provisionierung der Entwicklungsumgebung.</li>
<li>Teams verwenden einen Standardsatz an Build- und Testsystemen.</li>
<li>Service-Discovery wird in Anwendungen verwendet.</li>
<li>Security-Teams sind am Design und Deployment beteiligt.</li>
<li>Automatisiertes Security-Profiling von Code und Config-Manifesten.
:::
:::globalParagraph
Zu den Technologien und Ansätzen, die möglicherweise während der 4. Phase eingesetzt wurden, gehören Helm, Quay, GitHub Actions, Continuous Integration, ArgoCD, Service Mesh, Network Policies und Pod Disruption Budget.
:::
:::globalParagraph
Anwendungen (oder besser gesagt: kleine, robuste Services) haben eine hohe Release-Häufigkeit. Umfassende automatisierte Deployment-Muster (z. B. bei der Verwendung von Helm in allen Bereichen) mit GitOps sind vorhanden. Ein „Anstoß“ des Source-Management-Systems löst nachvollziehbare Änderungen an der Infrastruktur und den Anwendungen aus. Alle Teammitglieder (mit besonderem Schwerpunkt auf die Entwickler) kennen die Schlüsselelemente der kontinuierlichen Integrationspipeline und lösen aufkommende Probleme ganz selbstständig. Zudem sind auch Mitglieder eines spezialisierten Security-Teams an der Entwicklung von Architekturen und Services beteiligt. Sobald Sicherheitslücken auftreten, werden Security-Updates umgehend erstellt.
:::</li>
</ul>
<p>:::globalTitle{:size="lg" .mb-5}
Phase 5: Automatisiertes Application-Management
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>Entwicklerteams können auf alle Services für die Entwicklung zugreifen.</li>
<li>Produktion kann für das Deployment nachgebildet werden.</li>
<li>Anwendungen verwenden Muster von fortgeschrittenen Betriebsplattformen.</li>
<li>Anwendungen verwalten sich selbst sowie die Betriebsplattform.
:::
:::globalParagraph
Beispiele: Operators, CRD (Custom Resource Definitions), Auto Scaling und Probes.
:::</li>
</ul>
<p>:::globalParagraph
Dein Entwicklungsteam jagt einen komplexen Bug, der peinlicherweise alle deine Service beeinflusst? Kein Problem – in Phase 5 können deine Teams die Komplexität deiner Produktionsentwicklung nämlich im Handumdrehen provisionieren. Und obendrauf managen alle Service ihre eigenen Lifecycles, ohne dass manuell eingegriffen werden muss.
:::
:::globalParagraph
Ein neues Update benötigt eine Datenmigration? Auch kein Problem, denn deine Kubernetes-Operators ermitteln das verfügbare Update in deinem Registry und wenden die nötigen Scripts automatisch an, um die Daten deiner Anwendung konstant zu halten. Anwendungen informieren Kubernetes dabei selbst über ihren Zustand – sind sie bereit, neue Anfragen zu bearbeiten oder wird mehr Kapazität benötigt? Können wir es ein bisschen zurückschrauben, um etwas Geld zu sparen? In Phase 5 muss sich dein Team um solche Fragen glücklicherweise keine Sorgen mehr machen.
:::
:::globalParagraph
Bist du bereits bei Phase 5 angekommen? Wenn ja – Glückwunsch! Falls nicht, mach dir aber keine Sorgen. Soweit wir es beurteilen können, sind bisher sehr wenige Unternehmen wirklich in Phase 5 angekommen. Wir empfehlen, dass jedes Unternehmen möglichst schnell bei Phase 3 ankommen sollte und daraufhin sofort versuchen sollte, Phase 4 als mittelfristiges Ziel zu verfolgen. Falls das deine Strategie ist, dann bist du schonmal auf einem guten Weg zum zukünftigen Erfolg.
:::
:::globalParagraph
Wenn du mehr Einblicke in das Kubernetes-Ökosystem erhalten möchtest, folge einfach Michael Schilonka auf LinkedIn.
:::</p>]]></content:encoded>
            <category>Kubernetes</category>
            <category>Gefyra</category>
            <category>Entwicklung</category>
            <category>Projekt Management</category>
            <category>Sicherheit</category>
            <enclosure url="https://blueshoe.de/img/blogs/evolution-of-application-development-to-cloud-native.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[FastAPI in Produktion: So klappt's!]]></title>
            <link>https://blueshoe.de/blog/fastapi-in-produktion</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/fastapi-in-produktion</guid>
            <pubDate>Fri, 13 Dec 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Mit FastAPI kannst du von einer Vielzahl von Vorteilen profitieren, darunter Geschwindigkeit, Modernität und Flexibilität. Doch um sicherzustellen, dass deine FastAPI-Anwendung in der Produktion erfolgreich läuft, benötigst du die richtigen Strategien und Werkzeuge. Hier findest du die wichtigsten Best Practices und Tools, um deine Anwendung effizient und sicher zu betreiben.</p>
<p><img src="/img/blogs/api-technologies-fastapi.svg" alt="Blueshoe und FastAPI in Produktion">{.object-cover .max-w-full .mb-5}</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Einführung
:::
:::GlobalParagraph
In der heutigen digitalen Welt ist die Implementierung von FastAPI-Anwendungen in der Produktion ein wichtiger Schritt für Unternehmen und Entwickler. FastAPI bietet eine schnelle, moderne und flexible Möglichkeit, APIs zu entwickeln und zu betreiben. Doch wie bringst du deine FastAPI-Anwendung erfolgreich in die Produktion? Erfahre, welche Best Practices und Tools dir dabei helfen, deine FastAPI-Anwendung performant und sicher zu betreiben. Wir starten zuerst mit ein paar grundlegenden Begrifflichkeiten und Konzepten bevor wir uns die Optimierung der Produktionsumgebung ansehen.
:::</p>
<p>:::GlobalButton{:url="/loesungen/api-entwicklung/" :label="Erfahre mehr über unsere API-Entwicklungsdienste" :color="blue" .mb-6}
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Was ist FastAPI überhaupt?
:::
:::GlobalParagraph
FastAPI ist ein modernes, schnelles (High-Performance), Web-Framework für Python, das auf Starlette und Pydantic basiert. Es wurde von <a href="https://github.com/tiangolo">Sebastián Ramírez a.k.a tiangolo</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} geschrieben und ist eine der am schnellsten wachsenden Python-Web-Frameworks. Es ist auch sehr effizient APIs in FastAPI zu schreiben, da es die Vorteile von Python Typ-Überprüfungen und asynchronen Programmiermodellen nutzt. Darüber hinaus generiert FastAPI automatisch interaktive API-Dokumentationen mit Swagger UI und ReDoc, was den Entwicklungsaufwand erheblich reduziert.
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
The 12 Factor App - Was eine gute Applikation ausmacht
:::
:::GlobalParagraph
Blueshoe ist starker Vertreter des 12 Factor App Ansatzes. Die 12 Factor App ist eine Methode um Software-As-A-Service Apps zu bauen, welche (laut Zitat) folgende Anforderungen erfüllen sollen:
:::
:::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li>deklarative Formate benutzen für die Automatisierung der Konfiguration, um Zeit und Kosten für neue Entwickler im Projekt zu minimieren</li>
<li>einen sauberen Vertrag mit dem zugrundeliegenden Betriebssystem haben, maximale Portierbarkeit zwischen Ausführungsumgebungen bieten</li>
<li>sich für das Deployment auf modernen Cloud-Plattformen eignen, die Notwendigkeit von Servern und Serveradministration vermeiden</li>
<li>die Abweichung minimieren zwischen Entwicklung und Produktion, um Continuous Deployment für maximale Agilität ermöglichen</li>
<li>und skalieren können ohne wesentliche Änderungen im Tooling, in der Architektur oder in den Entwicklungsverfahren
:::</li>
</ul>
<p>:::GlobalParagraph
So beinhaltet der Ansatz Konzepte wie die gute Umsetzung der Konfiguration einer Applikation oder parität der Dev und Produktions Umgebung. Nachzulesen sind alle Faktoren auf der <a href="https://12factor.net/de/">12 Factor App Website</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}. Mit FastAPI lässt sich dieser Ansatz gut umsetzen.
Dank der leichtgewichtigkeit des Frameworks kann zum Beispiel Faktor IX. recht simpel erfüllt werden und eine schnelle Startzeit der Applikation garantiert werden.
:::</p>
<p>:::GlobalParagraph
Nachdem nun alle Konzepte und Grundlegenden Begriffe für <em>die</em> SaaS-App geklärt sind folgt nun das perfekte FastAPI Produktions Setup.
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Server Runner - Wer lässt deine App laufen?
:::</p>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Gunicorn + Uvicorn - Der alte Weg
:::</p>
<p><img src="/img/blog/old_way.gif" alt="old way gif">{.object-contain .w-128}</p>
<p>:::GlobalParagraph
Vor einigen Monaten war die Kombination von Gunicorn mit Uvicorn-Workern die bevorzugte Wahl, um eine FastAPI-API zu serven. Gunicorn wurde verwendet, weil es die Fähigkeit hat, eine große Anzahl von Prozessen zu verwalten, während die Leistung konstant bleibt. Dies ermöglichte es, die Last auf mehrere Prozesse zu verteilen und die Verfügbarkeit der API zu erhöhen. Uvicorn wurde als Worker-Prozess verwendet, um die Anfragen zu bearbeiten, da es eine effiziente Implementierung des ASGI-Standards bietet. Allerdings unterstützte Uvicorn bis vor kurzen das Management und Neustarten von Workern nicht direkt, was die Verwendung von Gunicorn als Prozessmanager erforderlich machte. Durch die Kombination von Gunicorn und Uvicorn konnten Entwickler die Vorteile beider Tools nutzen, um eine skalierbare und zuverlässige API-Infrastruktur aufzubauen.
:::</p>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Die FastAPI CLI - der neue Weg
:::</p>
<p>:::GlobalParagraph
Inzwischen wird diese Kombination von Gunicorn und Uvicorn nicht mehr benötigt, da Uvicorn das Management von Workern nun auch selbst hin bekommt.
Man kann nun also Uvicorn allein benutzen, <em>oder</em> man benutzt die neue FastAPI CLI. Diese basiert auf Uvicorn, erleichtert das Entwickler leben aber ungemein indem sie mit Management Kommandos für FastAPI daher kommt. Auch kann sie als entrypoint Kommando benutzt werden um Produktions APIs zu serven.
:::</p>
<p>:::GlobalParagraph
Um nun die maximale Performance, in einer Multiprozessor Umgebung, herauszuholen kann dem FastAPI CLI <em>run</em> Kommando eine Flag mitgegeben werden um die Anzahl an Service Workern festzulegen. Der resultierende Kommando sieht so aus:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-bash">fastapi run --workers 4 &#x3C;Pfad zur main Datei>
</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Dies führt Uvicorn mit Optimierungen für Produktion aus und erzeugt 4 Worker welche nun Nebenläufig Anfragen abarbeiten.
Wenn du deine Applikation aber in Kubernetes hosten möchtest ist es empfohlen die Replikation über den Cluster laufen zu lassen und nicht in jedem einzelnen Container. Das heißt, du braucht dann die <em>workers</em> Flag nicht, sondern solltest in deine Deployment Konfiguration schauen.</p>
<p>:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Reverse Proxies
:::</p>
<p>:::GloablParagraph
Ein "Reverse Proxy" ist ein Server, der zwischen den Clients (z. B. Browsern oder anderen Anwendungen) und den Backend-Servern einer Anwendung sitzt und als Vermittler fungiert. Statt dass die Clients direkt mit dem Backend kommunizieren, leitet der Reverse Proxy die Anfragen weiter und gibt die Antworten des Backends zurück an die Clients. Dadurch verbirgt er die tatsächlichen Backend-Server vor den Clients und fungiert als zentraler Zugangspunkt zur Anwendung.
:::</p>
<p>:::GlobalParagraph
Reverse Proxies wie <code>Nginx</code> oder <code>Traefik</code> sind also ein unverzichtbares Werkzeug für das Deployment moderner API-Anwendungen, da sie zahlreiche Vorteile bieten, die sowohl die Performance als auch die Sicherheit und Skalierbarkeit verbessern. Durch die Übernahme von Aufgaben wie SSL/TLS-Terminierung sorgt ein Reverse Proxy dafür, dass die API sicher über HTTPS erreichbar ist, ohne die Backend-Anwendung selbst zu belasten. Gleichzeitig ermöglicht er Load Balancing, indem eingehende Anfragen effizient auf mehrere Backend-Instanzen verteilt werden, was die Stabilität und Verfügbarkeit der Anwendung erhöht. Darüber hinaus kann ein Reverse Proxy statische Inhalte wie Bilder oder CSS-Dateien direkt ausliefern, was die Performance weiter steigert, indem es die API-Server entlastet. Nicht zuletzt bietet ein Reverse Proxy zusätzliche Sicherheitsfunktionen wie Rate Limiting oder IP-Blocking, um APIs vor bösartigen Angriffen zu schützen. Diese Kombination aus Performance-Optimierung, Sicherheit und Flexibilität macht Nginx, Traefik und ähnliche Tools zu einem unverzichtbaren Bestandteil jeder professionellen API-Deployment-Strategie.
:::</p>
<p>::GlobalCallAnExpert{:bg="bg-bs-blue"}
#buttonBlockTitle
Wir können auch Deine FastAPI-App betreiben.
::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Und zuletzt die Sicherheit
:::</p>
<p>:::GlobalParagraph
Sicherheit ist ein zentraler Aspekt beim Deployment von APIs, und es gibt bewährte Maßnahmen, die helfen, deine Anwendung vor Angriffen zu schützen. HTTPS sollte stets verwendet werden, um die Kommunikation zwischen Client und Server zu verschlüsseln. Ein SSL-Zertifikat kann einfach und kostenlos über Dienste wie Let’s Encrypt bereitgestellt werden. Weiterhin erhöhen Sicherheitsheader den Schutz vor häufigen Angriffen wie Cross-Site-Scripting (XSS). Dies lässt sich in FastAPI einfach mit der SecurityMiddleware von Starlette umsetzen. Zusätzlich schützt der Einsatz von TrustedHostMiddleware vor Host-Header-Angriffen, indem nur definierte Domains erlaubt werden:</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-python">from starlette.middleware.trustedhost import TrustedHostMiddleware
from starlette.middleware import Middleware

app = FastAPI(
  middleware=[
    Middleware(
      TrustedHostMiddleware,
      allowed_hosts=["yourdomain.com", "*.yourdomain.com"]
    ),
  ]
)
</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Wenn deine API mit externen Clients interagiert, ist eine korrekte Konfiguration von CORS (Cross-Origin Resource Sharing) entscheidend, um nur vertrauenswürdige Ursprünge zuzulassen:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-python">from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://trusteddomain.com"],
    allow_methods=["GET", "POST"],
    allow_headers=["*"],
)
</code></pre>
<p>:::
:::GlobalParagraph
Für den Schutz von Datenbankzugängen sollten sensible Informationen wie Zugangsdaten niemals im Code hartcodiert werden. Stattdessen können Umgebungsvariablen oder Secrets-Management-Tools genutzt werden. Um SQL-Injection zu verhindern, sollten Sie ORM-Tools wie SQLAlchemy oder Tortoise ORM verwenden, die Abfragen sicher parameterisieren.
Hier kommt wieder das 12 Factor App Konzept ins Spiel.
:::
:::GlobalParagraph
Zusätzlich können Sie durch Rate Limiting die Häufigkeit eingehender Anfragen begrenzen und damit sowohl Missbrauch als auch Überlastungen vorbeugen. Reverse Proxies wie Nginx oder Traefik eignen sich hervorragend, um dies effizient zu implementieren. Mit diesen Maßnahmen können Sie die Sicherheit Ihrer API signifikant erhöhen.
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Beispiel Dockerfile
:::</p>
<p>:::GlobalParagraph
Hier noch ein Beispiel eines Dockerfiles für eine Produktionsumgebung in Kubernetes (der Cluster übernimmt die Replikation):</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-dockerfile">FROM python:3.12-slim

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# Set the working directory
WORKDIR /app

# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy the application code
COPY src/ .

# Expose the port the app runs on
EXPOSE 8000

# Command to run the FastAPI application using the FastAPI CLI -> no workers flag as Kubernetes handles replication
CMD ["fastapi", "run", "main:app", "--host", "0.0.0.0", "--port", "8000"]
</code></pre>
<p>:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Honorable Mentions
:::</p>
<p>:::GlobalBlock{.ul-disk}</p>
<ul>
<li>Mit Caching kann auch viel für die Performance einer API getan werden. Bei Blueshoe benutzen wir vor allem <a href="https://varnish-cache.org/">Varnish</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}.</li>
<li>I/O und Ressourcen intensive Tasks sollten in <a href="https://fastapi.tiangolo.com/tutorial/background-tasks/">FastAPIs BackgroundTasks</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} ausgeführt werden
:::</li>
</ul>
<p>:::GlobalTitle{:size="lg" .mb-5 .mt-8}
Fazit
:::</p>
<p>:::GlobalParagraph
FastAPI ist nicht nur schnell und flexibel, sondern bietet durch seine Integration mit modernen Deployment- und Sicherheitsstandards eine ideale Grundlage für produktionsreife APIs. Mit den vorgestellten Best Practices und Tools lassen sich Anwendungen performant, sicher und skalierbar betreiben. Entwickler profitieren von der schnellen Startzeit, asynchronen Verarbeitung und integrierten Werkzeugen zur Optimierung. Der Einsatz von Reverse Proxies, Sicherheitsmaßnahmen und modernen Deployment-Strategien machen FastAPI zur idealen Wahl für zukunftsorientierte API-Entwicklung.
:::</p>]]></content:encoded>
            <category>FastAPI</category>
            <category>API</category>
            <category>Entwicklung</category>
            <enclosure url="https://blueshoe.de/img/blogs/api-technologies-fastapi.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[FastAPI Dokumentation mit Programmierbeispielen]]></title>
            <link>https://blueshoe.de/blog/fastapi-mit-beispielen</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/fastapi-mit-beispielen</guid>
            <pubDate>Thu, 14 Nov 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Es gibt eine Sache, die uns immer wieder umtreibt, wenn wir eine gute API bauen wollen - die Dokumentation. Insbesondere für öffentlich konsumierbare APIs weiß man nie, wer diese einsetzt und wie die Voraussetzungen des jeweiligen Entwicklers sind. Für eine besonders gute DX (= Developer Experience) statten wir unsere OpenAPI Dokumentationen mit Code-Beispielen in vielen Sprachen aus.</p>
<p><img src="/img/blogs/api-technologies-fastapi.svg" alt="Blueshoe und FastAPI: Dokumentation mit Programmierbeispielen">{.object-cover .max-w-full .mb-5}</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Dokumentation via OpenAPI
:::
:::GlobalParagraph
Zuallererst die Grundlagen - <a href="https://fastapi.tiangolo.com/">FastAPI</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} ist eines der Frameworks, welche wir verwenden, um APIs zu schreiben. Es basiert auf Python und wirbt mit sehr guter Performance. FastAPI ist stark typisiert, was bedeutet, dass die Typen Eingabe- und Ausgabewerte im Programm weitestgehend bekannt sind (in dem Rahmen, in dem das in Python möglich ist).
:::</p>
<p>:::GlobalButton{:url="/loesungen/api-entwicklung/" :label="Erfahre mehr über unsere API-Entwicklungsdienste" :color="blue" .mb-6}
:::</p>
<p>:::GlobalParagraph</p>
<p>Durch die bekannten Typen lässt sich ein Schema der API generieren. Dieses Schema beinhaltet alle notwendigen Informationen zu den verfügbaren Endpunkten. Als Standardformat wird hierfür OpenAPI hergenommen.
Beispiel für ein OpenAPI Endpunkt:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-json">openapi: 3.1.0
info:
  title: Redocly Museum API
  description: An imaginary, but delightful Museum API for interacting with museum services and information. Built with love by Redocly.
  version: 1.0.0
  contact:
    email: team@redocly.com
    url: 'https://redocly.com/docs/cli/'
  x-logo:
    url: 'https://redocly.github.io/redoc/museum-logo.png'
    altText: Museum logo
  license:
    name: MIT
    url: 'https://opensource.org/license/mit/'
servers:
  - url: 'https://api.fake-museum-example.com/v1'
paths:
  /museum-hours:
    get:
      summary: Get museum hours
      description: Get upcoming museum operating hours
      operationId: getMuseumHours
      x-badges:
        - name: 'Beta'
          position: before
          color: purple
      tags:
        - Operations
      parameters:
        - $ref: '#/components/parameters/StartDate'
        - $ref: '#/components/parameters/PaginationPage'
        - $ref: '#/components/parameters/PaginationLimit'
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GetMuseumHoursResponse'
              examples:
                default:
                  summary: Museum opening hours
                  value:
                    - date: 2023-09-11
                      timeOpen: '09:00'
                      timeClose: '18:00'
                    - date: 2023-09-12
                      timeOpen: '09:00'
                      timeClose: '18:00'
                    - date: 2023-09-13
                      timeOpen: '09:00'
                      timeClose: '18:00'
                    - date: 2023-09-17
                      timeOpen: '09:00'
                      timeClose: '18:00'
                closed:
                  summary: The museum is closed
                  value: []

        '400':
          description: Bad request
        '404':
          description: Not found
</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Die erzeugte Spezifikation enthält Beispiele, mögliche Antworten, sowie Beschreibung der Parameter des Endpunktes.
:::
:::GlobalParagraph
Programme wie Swagger oder Redoc greifen dieses Schema auf und erzeugen eine für Menschen lesbare Ansicht im <a href="https://redocly.github.io/redoc/">Browser</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}:
:::</p>
<p><img src="/img/blogs/fastapi-redoc1.png" alt="Redoc Beispiel">{.mx-auto .w-1/2}</p>
<p>:::GlobalParagraph
Hier werden Informationen wie Authentifizierung, Pagination und Filter für den Endpunkt dargestellt.
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Beispiele in API Dokumentation
:::</p>
<p>:::GlobalParagraph
Ein großer Mehrwert des OpenAPI Standards ist, dass aus den strukturierten Daten auch noch Beispiele erzeugt werden können. Dies sieht in Redoc dann beispielsweise folgendermaßen aus:
:::</p>
<p><img src="/img/blogs/fastapi-redoc2.png" alt="Redoc API Aufruf">{.mx-auto .w-1/2}</p>
<p>:::GlobalParagraph
Die Beispiele können explizit bei der Entwicklung angegeben werden oder sie werden mit einfachen Daten (wie 0 für Integer, "string" für Zeichenketten) automatisch erzeugt.
Diese Beispieldaten für einen HTTP Post Request zu unserer FastAPI zeigen allerdings nur den Payloads. Zur Unterstützung der Entwickler, die unsere API benutzen, wollen wir konkrete Programmierbeispiele einbetten.
Dies lässt sich mit Redoc via dem <a href="https://redocly.com/docs/api-reference-docs/specification-extensions/x-code-samples">x-codeSamples</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} Tag einfach realisieren. Doch woher bekommen wir die Beispiele, ohne jede Programmiersprache selbst zu lernen?
:::</p>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Erzeugung von Codebeispielen - Pakete
:::
:::GlobalParagraph
Auf Github sind wir auf 2 Pakete gestoßen, welche zur Generierung von API Code Beispielen geeignet scheinen: <a href="https://github.com/postmanlabs/postman-code-generators">postman-code-generators</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} und <a href="https://github.com/Kong/httpsnippet">httpsnippet</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}. <em>postman-code-generators</em> stammt von <a href="https://www.postman.com/">Postman</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}, einer Firma welche sich rund um Entwickler-Tooling für APIs beschäftigt. Es hat seine eigene Datenstruktur, um für HTTP Anfragen abzubilden.
:::
:::GlobalParagraph
<em>httpsnippet</em> stammt von Kong, einer Firma, welche vornehmlich ein API-Gateway entwickelt. Es arbeitet mit dem Standard von HTTP Archives.
Beide Pakete sind gut gewartet und wir entscheiden uns an dieser Stelle aus pragmatischen Gründen für <em>postman-code-generators</em>. Die Daten, die wir in unserem OpenAPI Schema vorhanden haben, lassen sich recht leicht in die Strukturen von Postman überführen
:::</p>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Umwandlung der OpenAPI in Postman Strukturen
:::
:::GlobalParagraph
Zuerst zur Struktur des Postman SDKs. Um mit postman-code-generators zu arbeiten, wird zusätzlich das SDK <code>postman-collection</code> benötigt.
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-typescript">const codegen = require('postman-code-generators')
const sdk = require('postman-collection')

const baseUrl = 'https://unsere-api.de'
const openapiJSON = getOpenApiJSON()
const supportedCodegens = codegen.getLanguageList()
</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
<em>postman-code-generators</em> kommt mit einer Liste von Sprachen, über die wir einfach iterieren, um Codebeispiele zu erzeugen.
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-typescript">for (const codegen of supportedCodegens) {
  language = codegen.key
  languageLabel = codegen.label
  for (const variation of codegen.variants) {
    variant = variation.key
    generateSamples()
  }
}
</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Jede Sprache hat verschiedene Varianten, welche i.d.R. einfach verschiedene Möglichkeiten abbilden, HTTP Anfragen zu stellen - z.B. http und requests für Python.
Unsere <code>generateSamples</code> Funktion erzeugt nun einfach für jeden Endpunkt (hier nur POST Endpunkte) ein Codebeispiel in der aktuellen Sprache:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-typescript">function generateSamples() {
  // generate samples for open api endpoint paths
  const paths = openapiJSON.paths
  for (const [path, operation] of Object.entries(paths)) {
    currentPath = path
    const data = generateExamplePayload(operation.post, openapiJSON)
    convertEndpoint(path, 'POST', data, addEntry)
  }
}
</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Im Rahmen der Erzeugung des Payloads (<code>generateExamplePayload</code>) wird dann das Postman Request Objekt erzeugt:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-typescript">function buildRequest(url, method, data) {
  // build postman request
  return new sdk.Request({
    url: `${baseUrl}${url}`,
    method,
    body: buildBody(data),
    header: {
      ...header,
    }
  })
}
</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Da wir in unserer openapi.json Datei mit Schemata arbeiten, nutzen wir ein weiteres Paket, welches uns hilft Beispiel-Payloads für diese Schemata zu erzeugen:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-typescript">const OpenAPISampler = require('openapi-sampler')
const schema = spec.components.schemas[name]
// Generate sample data
return OpenAPISampler.sample(
  schema,
  {},
  spec
)
</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Zum Schluss stecken wir unsere Schleife und die Code Generierung zusammen:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-typescript">function convertEndpoint(path, method, data, cb) {
  const request = buildRequest(path, method, data)
  codegen.convert(language, variant, request, sampleFormattingOptions, cb)
}
</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Als letzten Schritt fügen wir die erzeugten Beispiele in unsere openapi.json Datei ein:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-typescript">function addEntry(error, snippet) {
  if (error) {
    console.log(error, language, variant)
    return
  }
  const xCodeSample = {
    lang: languageLabel,
    label: `${languageLabel} (${variant})`,
    source: snippet
  }
  if (openapiJSON.paths[currentPath].post['x-codeSamples']) {
    openapiJSON.paths[currentPath].post['x-codeSamples'].push(xCodeSample)
  }
  else {
    openapiJSON.paths[currentPath].post['x-codeSamples'] = [xCodeSample]
  }
}
</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Und schon beinhaltet unsere openapi.json Datei Codebeispiele aus allen möglichen Sprachen.
:::</p>
<p><img src="/img/blogs/fastapi-redoc3.png" alt="Redoc alle Clients">{.mx-auto .w-1/2}</p>
<p>:::GlobalTitle{:size="lg" .mb-5 .mt-8}
Fazit
:::</p>
<p>:::GlobalParagraph{.mb-8}
Die Entwicklung einer API und das Schreiben einer Dokumentation dient nicht nur dem Selbstzweck. Es gilt, die Benutzer / Entwickler in Betracht zu ziehen, welche beispielsweise unsere REST API konsumieren. Die API Konsumenten dort abzuholen wo sie stehen, sodass die Verwendung der API weiter erleichtert wird, sollte insbesondere  das Ziel für öffentliche Schnittstellen sein.
:::</p>]]></content:encoded>
            <category>API</category>
            <category>Python</category>
            <category>FastAPI</category>
            <category>Dokumentation</category>
            <category>Entwicklung</category>
            <enclosure url="https://blueshoe.de/img/blogs/api-technologies-fastapi.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[FastAPI vs. Robyn: Ein detaillierter Vergleich]]></title>
            <link>https://blueshoe.de/blog/fastapi-v-robyn</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/fastapi-v-robyn</guid>
            <pubDate>Tue, 03 Jun 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>In der Welt der modernen API-Entwicklung stehen Entwickler oft vor der Frage: Welches Framework ist das richtige für mein Projekt? FastAPI und Robyn sind zwei der (mehr oder weniger) aufstrebenden Stars in der Python-API-Entwicklung. Beide bieten moderne Features und hohe Performance, unterscheiden sich aber in einigen wichtigen Aspekten. In diesem Artikel werfen wir einen detaillierten Blick auf die Gemeinsamkeiten und Unterschiede dieser beiden Frameworks.</p>
<p><img src="/img/blogs/robyn-fastapi.svg" alt="FastAPI vs. Robyn Vergleich">{.object-cover .max-w-full .mb-5}</p>
<h2>Einführung</h2>
<p>Die Wahl des richtigen API-Frameworks ist entscheidend für den Erfolg eines Projekts. Während <a href="https://fastapi.tiangolo.com/">FastAPI</a>{target="_blank"} bereits seit einigen Jahren etabliert ist, gewinnt <a href="https://robyn.tech/">Robyn</a>{target="_blank"} als neuerer Player zunehmend an Popularität. Beide Frameworks versprechen hohe Performance und moderne Entwicklungsansätze, aber sie unterscheiden sich in ihrer Implementierung und ihren Stärken. In diesem Artikel werden wir die wichtigsten Aspekte beider Frameworks vergleichen und dir helfen, die richtige Wahl für dein Projekt zu treffen.</p>
<h2>Gemeinsamkeiten</h2>
<p>FastAPI und Robyn teilen einige grundlegende Eigenschaften, die sie zu modernen und effizienten API-Frameworks machen:</p>
<ul>
<li>Beide sind moderne, asynchrone Web-Frameworks</li>
<li>Sie bieten eine hohe Performance durch asynchrone Verarbeitung</li>
<li>Beide unterstützen OpenAPI/Swagger-Dokumentation</li>
<li>Sie nutzen moderne Python-Features wie Type Hints</li>
<li>Beide sind leichtgewichtig und modular aufgebaut</li>
</ul>
<h2>Unterschiede</h2>
<h3>Runtime</h3>
<p>FastAPI basiert auf Starlette und Uvicorn als ASGI-Server, während Robyn seine eigene Runtime in Rust implementiert hat. Dies führt zu interessanten Unterschieden: FastAPI profitiert von der reifen Python-Ökosystem-Integration und der breiten Community-Unterstützung, während Robyn durch seine Rust-Implementierung potenziell bessere Performance bei bestimmten Workloads bieten kann. Allerdings bedeutet die Rust-Implementierung auch, dass Robyn weniger flexibel bei der Integration von Python-Bibliotheken ist und möglicherweise mehr Wartungsaufwand erfordert.</p>
<h3>Endpoint-Handhabung</h3>
<p>Während beide Frameworks ähnliche Ziele verfolgen, unterscheiden sie sich in ihrer Implementierung von Endpoints:</p>
<p>FastAPI nutzt einen dekoratorbasierten Ansatz mit starkem Fokus auf Typisierung und Validierung, sowie request Destrukturierung und Injektion:</p>
<pre><code class="language-python">from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    id: str
    name: str
    price: float

@app.post("/items/")
async def create_item(item: Item):
    # create item in db
    # ...
    return item

@app.put("/items/{item_id}")
async def update_item(item_id: str, item: Item):
    """
    A client would call me like: PUT {base_url}/items/0485fd43-1345-4336-877c-4b47758102ea

    And `item_id` is automatically made available to this endpoint!
    """
    # update item in db
    # ...
    return item

@app.put("/items/{item_id}/move")
async def move_item(item_id: str, item: Item, directory: str | None = None):
    """
    A client would call me like: PUT {base_url}/items/0485fd43-1345-4336-877c-4b47758102ea/move?directory=new-n-shiny

    And `directory` is automatically made available to this endpoint!
    """
    # move item
    # ...
    return item
</code></pre>
<p>Robyn hingegen bietet einen etwas flexibleren, aber <strong>meiner Meinung</strong> nach umständlicheren, Ansatz der Injektion von Pfad- und Queryparametern:</p>
<pre><code class="language-python"># Why import types from three different modules?
from robyn import Robyn, Request
from robyn.types import PathParams
from robyn.robyn import QueryParams

app = Robyn(__file__)

@app.post("/items/")
async def create_item(request: Request):
    data = await request.json()
    return data

@app.put("/items/:item_id")
async def update_item(
    request: Request,
    path_parameters: PathParams,  # NOTE: variable name has to be `path_parameters` for injection to work
):
    item_id: str = str(path_parameters["item_id"])
    # update item in db
    # ...
    return item

@app.put("/items/:item_id")
async def move_item(
    request: Request,
    path_parameters: PathParams,
    query_parameters: QueryParams,  # NOTE: variable name has to be `query_parameters` for injection to work
):
    item_id: str = str(path_parameters["item_id"])
    directory: str = query_parameters.get("directory", None)
    # move item
    # ...
    return item
</code></pre>
<p>Übrigens sind alle Pfad- und Queryparameter auch im Request Model verfügbar. Man kann sich also die Injektion theoretisch sparen.</p>
<h3>ORM-Unterstützung</h3>
<p>Die Unterstützung von Datenbankoperationen ist ein wichtiger Aspekt bei der Framework-Auswahl:</p>
<p>FastAPI:</p>
<ul>
<li>Ermöglicht einfache Integration mit ORMs wie SQLAlchemy - dank Pydantic</li>
<li>Unterstützt asynchrone ORMs wie Tortoise ORM</li>
<li>Verfügt über eine aktive Community mit zahlreichen Beispielen und Best Practices für ORM-Nutzung</li>
<li>Generiert automatisch OpenAPI-Dokumentation auf Basis der Datenbankmodelle</li>
<li>Bietet mit <a href="https://sqlmodel.tiangolo.com/">SQLModel</a>{target="_blank"} eine eigene ORM-Option auf Basis von SQLAlchemy und Pydantic</li>
</ul>
<p>Robyn:</p>
<ul>
<li>Flexibler in der Wahl der Datenbank-Lösung (auch Rust ORMs!)</li>
<li>Weniger vordefinierte Patterns für Datenbankoperationen</li>
</ul>
<h3>Serialisierung und Validierung</h3>
<p>Die Art und Weise, wie Daten validiert und serialisiert werden, unterscheidet sich deutlich:</p>
<p>FastAPI:</p>
<ul>
<li>Nutzt <a href="https://docs.pydantic.dev/latest/">Pydantic</a>{target="_blank"} für Validierung und Serialisierung</li>
<li>Strikte Typisierung und Validierung</li>
<li>Automatische Generierung von OpenAPI-Schemas</li>
<li>Umfangreiche Validierungsmöglichkeiten (durch Pydantic)</li>
</ul>
<p>Robyn:</p>
<ul>
<li>Flexiblere Validierungsmöglichkeiten</li>
<li>Weniger strikte Typisierung</li>
<li>Manuelle Serialisierung/Deserialisierung (zum Beispiel über <code>jsonify</code>)
Auch wenn dies nicht unbedingt notwendig ist. Die Dokumentation von Robyn ist hier nicht 100% eindeutig:
<img src="/img/blogs/robyn-jsonify-docs.png" alt="Robyn jsonify Docs">
(<code>jsonify</code> wird zwar importiert aber nicht verwendet; Im Test musste man <code>jsonify</code> auch gar nicht importieren, ein Dict als Response reicht völlig aus)</li>
</ul>
<h3>FFIs</h3>
<p>Zumindest wenn man CPython verwendet steht es einem schon immer frei gewisse Teile seines Python Codes in C / C++ umzusetzen (falls nicht schon passiert; <code>stdlib</code>, etc.) und damit zu beschleunigen. Da Robyn über eine Rust Runtime verfügt, ermöglicht dieses Framework allerdings eine Kinderleichte Integration von Rust Code!</p>
<p>Für unseren kleinen Performance-Vergleich lasse ich Fibonacci Zahlen bis zur (maximal) 30er Größe generieren. Hierbei werden keine Tricks wie Memoization verwendet, um die rohe Python Performance zu zeigen:</p>
<pre><code class="language-python">def py_fib(n: int):
    if n &#x3C; 2:
        return n
    return py_fib(n - 1) + py_fib(n - 2)
</code></pre>
<p>Diesen "Generator" habe ich auch (krude, und ohne Rekursion) in Rust implementiert:</p>
<pre><code class="language-rust">// rustimport:pyo3

//:
//: [dependencies]
//: num-bigint = "0.4"
//: num-traits = "0.2"

use pyo3::prelude::*;
use num_bigint::BigUint;
use num_traits::{Zero, One};

#[pyfunction]
fn fibonacci(term: u64) -> PyResult&#x3C;String> {
    if term == 0 {
        return Ok("0".to_string());
    }
    
    let (mut a, mut b): (BigUint, BigUint) = (Zero::zero(), One::one());
    for i in 1..=term {
        let temp = b.clone();
        b = a + b;
        a = temp;
    }
    
    Ok(b.to_string())
}
</code></pre>
<p>Kinderleicht war das ganze, da ich mich nur um die Implementierung kümmern musste. Dependencies können über Kommentare definiert werden und werden automatisch von der Robyn CLI aufgelöst.</p>
<p>Nachdem man also seinen Rust Code geschrieben hat reicht folgender Befehl:</p>
<pre><code class="language-bash">robyn --compile-rust-path "my-robyn-rust-dir"
</code></pre>
<p>Und schon wird wie durch Magie (PyO3) der Rust Code compiliert und in eine Plattformabhängige Library gepackt!</p>
<p>Diese kann dann wie ein normales Python Module importiert werden (meine Rust Datei heißt <code>native_fib.rs</code> und liegt im gleichen Verzeichnis wie die Robyn <code>main.py</code>):</p>
<pre><code class="language-python">from native_fib import fibonacci as rs_fibonnacci
</code></pre>
<h2>Performance-Vergleich</h2>
<p>Wie schon erwähnt wird zum Vergleich der Performance die Generierung von Fibonacci Zahlen sowie folgendes Setup benutzt:</p>
<ol>
<li>Ein Endpoint <code>/</code> welcher ein einfaches JSON zurück gibt:
<pre><code class="language-python"># fastapi implementation
@app.get("/")
async def root():
    return {"response": "success"}

# robyn implementation
@app.get("/")
async def root(request: Request):
    return {"response": "success"}
</code></pre>
</li>
<li>Eine <a href="https://locust.io/"><code>locust</code></a>{target="_blank"} Konfigurationsdatei mit einem Task:
<ol>
<li>Ruft <code>/</code> auf mit 10, 1000 und 10.000 Usern</li>
</ol>
</li>
<li>Ein Endpoint <code>/fibonacci/:size</code> mit optionalen Queryparameter <code>use_native</code></li>
<li>Eine <a href="https://locust.io/"><code>locust</code></a>{target="_blank"} Konfigurationsdatei mit drei Tasks:
<ol>
<li>Ruft <code>/fibonacci/10</code> auf; mit Gewichtung 3</li>
<li>Ruft <code>/fibonacci/20</code> auf; mit Gewichtung 2</li>
<li>Ruft <code>/fibonacci/30</code> auf; ohne Gewichtung</li>
<li>Verteilt über 100 User mit 1 Ramp User pro Sekunde</li>
</ol>
</li>
<li>Eine <a href="https://locust.io/"><code>locust</code></a>{target="_blank"} Konfigurationsdatei mit drei Tasks (für die Rust Implementierung):
<ol>
<li>Ruft <code>/fibonacci/10?use_native=true</code> auf; mit Gewichtung 3</li>
<li>Ruft <code>/fibonacci/20?use_native=true</code> auf; mit Gewichtung 2</li>
<li>Ruft <code>/fibonacci/30?use_native=true</code> auf; ohne Gewichtung</li>
<li>Verteilt über 100 User mit 1 Ramp User pro Sekunde</li>
</ol>
</li>
</ol>
<p>Allgemein wurde mit <em>1</em> Prozess und <em>1</em> Worker getestet.</p>
<h3>Test-Machine</h3>
<p>Lenovo ThinkPad P14s Gen 2i</p>
<ul>
<li>Prozessor: 11th Gen Intel® Core™ i7-1165G7</li>
<li>RAM Größe: 32GiB</li>
</ul>
<p>Dann schauen wir uns doch mal die Ergebnisse an!</p>
<h3>Simple</h3>
<p>Dieser Load-Test ruft den sehr einfachen JSON Response Endpoint auf und erhöht die User Zahlen stetig, bis zu einem Maximum von 10.000 Usern.</p>
<p>Wie zu erkennen ist, geht FastAPI (zumindest auf meinem Laptop) bereits mit 246.4 Requests pro Sekunde an seine Grenzen. So treten vermehrt Fehler auf bei denen die locust Requests nicht mehr (oder zu spät) verarbeitet wurden.</p>
<p>Ein <code>ConnectionResetError</code> ist ein Netzwerkfehler, der auftritt, wenn die Gegenstelle (in diesem Fall der Server) eine bestehende TCP-Verbindung abrupt beendet, typischerweise durch Senden eines RST-Pakets (Reset). Dies ist keine ordnungsgemäße Beendigung (wie ein FIN-Paket) und signalisiert oft, dass etwas Unerwartetes passiert ist oder der Server die Verbindung nicht mehr verarbeiten kann.</p>
<p>Unter hoher Last kann ein Server überfordert sein. Dies kann verschiedene Ursachen haben: Erschöpfung von Systemressourcen (wie CPU, Speicher, Dateideskriptoren) oder eine Überlastung der Event-Loop, die für die Verarbeitung der asynchronen Operationen zuständig ist. Wenn die Event-Loop nicht schnell genug neue Verbindungen annehmen oder Daten auf bestehenden Sockets verarbeiten kann, können Timeouts auftreten oder der Server muss Verbindungen zwangsweise schließen, um Ressourcen freizugeben oder eine Überlastung zu verhindern.</p>
<p>Im Kontext von Python und ASGI-Servern wie Uvicorn (der von FastAPI genutzt wird) spielt die Art und Weise, wie Python Netzwerk-I/O handhabt, eine Rolle. Obwohl Python mit <code>asyncio</code> und ASGI asynchrone Operationen ermöglicht und theoretisch viele Verbindungen gleichzeitig verwalten kann, während auf I/O gewartet wird, gibt es Grenzen. Die Ausführung von Python-Code selbst unterliegt dem Global Interpreter Lock (GIL), der verhindert, dass mehrere native Threads gleichzeitig Python-Bytecode auf mehreren CPU-Kernen ausführen. Bei sehr hoher Last, selbst wenn die Endpoints I/O-gebunden sind (wie das einfache JSON-Response), kann die schiere Menge an Kontextwechseln, das Scheduling der Coroutinen und die kurze Zeit, die jede Anfrage im Python-Code verbringt (auch wenn es nur das Serialisieren des JSONs ist), dazu führen, dass die Event-Loop überlastet wird. Der Serverprozess verbringt zu viel Zeit mit der Ausführung von Python-Code (auch wenn er asynchron ist), um schnell genug auf neue oder bestehende Socket-Ereignisse zu reagieren.</p>
<p>Die <code>ConnectionResetError</code>s in diesem Test zeigen, dass FastAPI (bzw. die zugrundeliegende Python/Uvicorn-Schicht) schon recht früh an seine Grenzen stößt. Die Event-Loop kann die eingehenden Anfragen nicht schnell genug verarbeiten, was dazu führt, dass der Server die Verbindungen zurücksetzt, anstatt sie ordnungsgemäß zu bedienen. Dies ist ein Indikator dafür, dass die Python-Runtime unter dieser spezifischen extremen Last zum Engpass wird. Während Robyn, das eine Rust-Runtime ohne GIL nutzt, diese Art von Engpass bei der Verarbeitung von Netzwerkverbindungen besser umschiffen können sollte.</p>
<p>Das Load Test Ergebnis für Robyn sieht dagegen deutlich performanter aus:</p>
<p>Was sehen wir? Nicht nur keine Failures (da alle Requests verarbeitet werden konnten) sondern auch durch die Bank schnellere Antwortzeiten!
Spannend, aber nun zu den Load-Tests die eine Größere Herausforderung darstellen.</p>
<h3>Fibonacci</h3>
<p>Hier direkt die Ergebnisse (links FastAPI, rechts Robyn):</p>
<p><img src="/img/blog/py-fib-comparison.png" alt="Python Fibonacci Vergleich">{.object-cover .max-w-full .mb-5}</p>
<p>Wie wir sehen können, ist Robyn in den Antwortzeiten um ein paar Millisekunden besser als FastAPI. Auch trat hier unter FastAPI <em>ein einziger</em> Request Fail auf:</p>
<p><img src="/img/blog/fastapi-py-fib-fail.png" alt="FastAPI Python Fibonacci Fail">{.object-cover .max-w-full .mb-5}</p>
<p>Interessanterweise trat dieser zwischen 25 und 27 <strong>Requests per Second (RPS)</strong> auf und kam dann nie wieder vor. Dies kann also durchaus ein Ausreißer gewesen und auf meine nicht für API Hosting ausgelegte Hardware zurückzuführen sein.</p>
<p>Aber: Robyn unterstützt ja auch noch Rust nativen Code. Wie sieht es denn hier aus?</p>
<p><img src="/img/blog/py-fib-native-fib-comparison.png" alt="Python vs. native Fibonacci Vergleich">{.object-cover .max-w-full .mb-5}</p>
<p>Das Ergebnis? Drastisch.</p>
<p>Spaß bei Seite, wie wir sehen können sind die Antwortzeiten in der (lange nicht optimierten) Rust Implementierung fast verschwindend gering. Hier die locust Zusammenfassung:</p>
<p><img src="/img/blog/native-fib-request-summary.png" alt="Native Fibonacci Response Time Stats">{.object-cover .max-w-full .mb-5}</p>
<p>Hier fällt auf: Eine größere Fibonacci Zahl anzufordern scheint im Mittel und Maximum weniger Zeit als kleinere zu brauchen? Eine mögliche Erklärung hierfür könnte sein, dass der "Handover" von Python an Rust (und zurück von Rust an Python) die meiste Zeit in Anspruch nimmt.</p>
<h3>Zusammenfassung</h3>
<p>Wir haben FastAPI und Robyn mit einem simplen JSON-Endpoint sowie einem komplexeren Fibonacci-Endpoint unter Last getestet.</p>
<ul>
<li>FastAPI erreicht auf meinem Laptop etwa 246 RPS, dann treten vermehrt Fehler auf.</li>
<li>Robyn bleibt auch bei 10.000 Usern stabil – schneller und ohne Fehler.</li>
<li>Bei der Fibonacci-Berechnung (Python) ist Robyn um ein paar Millisekunden flotter, während FastAPI bei 25–27 RPS einen Ausreißer zeigt.</li>
<li>Mit Rust-nativem Code in Robyn sehen wir eine drastische Verbesserung der Antwortzeiten – und sogar weniger Zeit bei größeren Fibonacci-Zahlen!</li>
</ul>
<p><strong>Fazit</strong>: Robyn skaliert besser als FastAPI, besonders bei hoher Last. Und Rust zeigt eindrucksvoll, wie viel mehr Performance möglich ist, wenn wir Python-Bottlenecks auslagern.</p>
<h2>Projektstruktur und Entwickler-Workflow</h2>
<p>Wie organisiert man ein Projekt mit FastAPI im Vergleich zu Robyn?</p>
<ul>
<li>
<p><strong>FastAPI-Projektstruktur:</strong></p>
<ul>
<li>Typischerweise modular aufgebaut, oft nach Features oder Domänen getrennt (z.B. <code>routers/</code>, <code>models/</code>, <code>services/</code>).</li>
<li>Verlässt sich stark auf das Python-Ökosystem für Tooling: <code>black</code> für Formatierung, <code>pytest</code> für Tests, <code>alembic</code> für Datenbank-Migrationen.</li>
<li>Die <code>main.py</code> ist oft nur der Einstiegspunkt, der die verschiedenen Router und Konfigurationen zusammenführt.</li>
</ul>
</li>
<li>
<p><strong>Robyn-Projektstruktur:</strong></p>
<ul>
<li>Oft monolithischer, da das Framework weniger "opinionated" ist. Viele Entwickler beginnen mit einer einzigen <code>app.py</code>.</li>
<li>Die Rust-Integration erfordert eine klare Trennung zwischen Python- und Rust-Code (z.B. in einem <code>native/</code> Verzeichnis).</li>
<li>Robyn bringt eine eigene CLI mit, die für das Kompilieren von Rust-Code und das Starten der Anwendung verwendet wird, was den Workflow leicht verändert.</li>
</ul>
</li>
<li>
<p><strong>Entwicklungs-Server:</strong></p>
<ul>
<li><strong>FastAPI:</strong> Nutzt <code>fastapi dev main.py</code> für einen schnellen Hot-Reload-Entwicklungsserver.</li>
<li><strong>Robyn:</strong> Bietet mit <code>robyn --dev</code> einen eingebauten Hot-Reload-Server, der auch Änderungen am Rust-Code erkennen und neu kompilieren kann.</li>
</ul>
</li>
</ul>
<h2>Deployment in der Praxis: Von Lokal zu Live</h2>
<ul>
<li>
<p><strong>Containerisierung (Docker):</strong></p>
<ul>
<li><strong>FastAPI:</strong> Das <code>Dockerfile</code> ist unkompliziert. Es basiert auf einem Python-Image und startet die Anwendung mit <code>fastapi-cli</code> (was intern uvicorn verwendet).
<pre><code class="language-dockerfile">FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["fastapi", "run", "main.py", "--host", "0.0.0.0", "--port", "80"]
</code></pre>
</li>
<li><strong>Robyn:</strong> Das <code>Dockerfile</code> ist komplexer, wenn Rust-Code involviert ist. Es erfordert oft einen Multi-Stage-Build: eine Stage, um die Rust-Toolchain zu installieren und den Code zu kompilieren, und eine zweite, um die kompilierte Bibliothek in ein schlankes Python-Image zu kopieren.
<pre><code class="language-dockerfile"># Stage 1: Build Rust
FROM rust:latest as builder
WORKDIR /app
COPY native/ .
# Hier würde der Compile-Schritt für Rust stehen

# Stage 2: Final Image
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /app/target/release/libnative_fib.so .
# ... Rest des Setups
</code></pre>
</li>
</ul>
</li>
<li>
<p><strong>Produktions-Server:</strong></p>
<ul>
<li><strong>FastAPI:</strong> Läuft auf jedem ASGI-kompatiblen Server (Uvicorn, Hypercorn, Daphne).</li>
<li><strong>Robyn:</strong> Bringt seine eigene Rust-basierte Server-Runtime mit und benötigt keinen separaten ASGI-Server.</li>
</ul>
</li>
</ul>
<h2>Fehlerbehandlung und Debugging</h2>
<ul>
<li>
<p><strong>FastAPI:</strong></p>
<ul>
<li>Bietet ein robustes System für Exception-Handler. Du kannst globale Handler für <code>HTTPException</code> oder benutzerdefinierte Exceptions definieren.</li>
<li>Die Validierungsfehler von Pydantic sind extrem detailliert und geben genau an, welches Feld im Request-Body fehlerhaft ist.</li>
<li>Das Debugging ist Standard-Python-Debugging.</li>
</ul>
</li>
<li>
<p><strong>Robyn:</strong></p>
<ul>
<li>Bietet ebenfalls Exception-Handler, die jedoch weniger strukturiert sind als in FastAPI.</li>
<li>Das Debugging kann komplexer sein, wenn Fehler in der Rust-Runtime oder an der Schnittstelle zwischen Python und Rust auftreten. Dies erfordert möglicherweise Kenntnisse in beiden Sprachen.</li>
</ul>
</li>
</ul>
<h2>Wann nun welches Framework?</h2>
<p>Die Wahl zwischen FastAPI und Robyn hängt von den spezifischen Anforderungen deines Projekts ab:</p>
<p>FastAPI ist ideal für:</p>
<ul>
<li>Projekte mit komplexen Data Klassen</li>
<li>Teams, die strikte Typisierung bevorzugen</li>
<li>Anwendungen mit umfangreichen API-Dokumentationsanforderungen</li>
<li>Projekte, die eine etablierte Community und viele Ressourcen benötigen</li>
</ul>
<p>Robyn eignet sich besser für:</p>
<ul>
<li>Einfachere API-Projekte</li>
<li>Teams, die sich mehr Flexibilität bei der Implementierung wünschen</li>
<li>Projekte mit spezifischen Performance-Anforderungen, welche mit nativer Rust Integration und Runtime "heraus gequetscht" werden kann</li>
</ul>
<hr>
<h2>Fazit</h2>
<p>Sowohl FastAPI als auch Robyn sind moderne, leistungsstarke API-Frameworks, die unterschiedliche Stärken haben. FastAPI bietet eine umfassendere Lösung mit starker Typisierung und Validierung, während Robyn mehr Flexibilität und einen minimalistischen Ansatz bietet. Die endgültige Wahl sollte von den spezifischen Anforderungen deines Projekts abhängen.</p>
<hr>
<h2>FAQ – Häufige Fragen zur richtigen Framework Wahl</h2>
<h3>Welches Framework bietet bessere Performance?</h3>
<p>Robyn zeigt in den Performance-Tests bessere Ergebnisse, besonders bei einfachen Endpoints und hoher Last. Allerdings bietet FastAPI eine ausgereiftere Ökosystem-Integration und Community-Unterstützung.</p>
<h3>Kann ich Rust-Code in beiden Frameworks verwenden?</h3>
<p>Robyn bietet eine native Integration von Rust-Code durch seine Rust-Runtime, während FastAPI dies nicht direkt unterstützt. Bei FastAPI müsste man auf traditionelle CPython-FFIs zurückgreifen.</p>
<h3>Welches Framework ist besser für komplexe Datenmodelle geeignet?</h3>
<p>FastAPI ist besser für komplexe Datenmodelle geeignet, da es Pydantic für Validierung und Serialisierung nutzt und eine strikte Typisierung bietet. Robyn bietet hier mehr Flexibilität, aber weniger vordefinierte Strukturen.</p>
<h3>Wie unterscheiden sich die ORM-Möglichkeiten?</h3>
<p>FastAPI bietet native Integration mit SQLAlchemy, Tortoise ORM und SQLModel. Robyn hat keine native ORM-Integration, erlaubt aber die Verwendung von Rust ORMs und bietet mehr Flexibilität bei der Datenbankwahl.</p>
<h3>Für welche Art von Projekten ist welches Framework besser geeignet?</h3>
<p>FastAPI eignet sich besser für komplexe Projekte mit umfangreichen API-Dokumentationsanforderungen und strikter Typisierung. Robyn ist ideal für einfachere API-Projekte und Teams, die mehr Flexibilität und native Rust-Integration wünschen.</p>]]></content:encoded>
            <category>FastAPI</category>
            <category>API</category>
            <category>Entwicklung</category>
            <enclosure url="https://blueshoe.de/img/blogs/robyn-fastapi.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Effektives Fehler-Tracking in Django mit Sentry]]></title>
            <link>https://blueshoe.de/blog/fehler-tracking-django-sentry</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/fehler-tracking-django-sentry</guid>
            <pubDate>Mon, 19 May 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Fehler in der produktiven Umgebungen passieren. Und das Allerschlimmste ist, wenn man erst durch die Nutzer davon erfährt. Genau hier kommt Sentry ins Spiel. Ein Blick ins Dashboard reicht und du siehst, was gerade passiert: Ein übersichtliches Stacktrace, der betroffene Benutzer und wie oft der Fehler schon aufgetreten ist.</p>
<p><img src="/img/blog/sentry_blog_header.webp" alt="Effektives Fehler-Tracking in Django mit Sentry"></p>
<h2>Warum du Sentry unbedingt brauchst</h2>
<p>Fehler in Live-Umgebungen können teuer werden. Wenn du dich auf E-Mails deiner Nutzer:innen verlässt, ist es oft schon zu spät. <a href="https://sentry.io">Sentry</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}) bringt dir Echtzeit-Fehlermeldungen direkt ins Dashboard – inkl. User, Stacktrace und sogar Umgebung (Staging, Prod etc.).</p>
<p>:::GlobalButton{:url="/technologien/python-django-agentur/" :label="Entdecke, wie wir mit Django Software entwickeln" :target="_blank" :color="blue" .mb-6}
:::</p>
<h2>Schnellstart: So integrierst du Sentry in Django</h2>
<h3>1. SDK installieren</h3>
<pre><code class="language-bash">pip install --upgrade sentry-sdk
</code></pre>
<h3>2. Projekt bei Sentry.io erstellen</h3>
<p><img src="/img/blog/sentry-1.png" alt="Sentry: Projekt erstellen">
Melde dich auf sentry.io an oder logge dich ein. Erstelle dort ein neues Projekt und wähle „Django“ als Plattform. Sentry generiert dir daraufhin automatisch einen DSN, das ist eine eindeutige URL, über die deine Anwendung Fehler an das richtige Projekt bei Sentry sendet.</p>
<p>Beispiel-DSN:</p>
<pre><code class="language-bash">https://examplePublicKey@o0.ingest.sentry.io/0
</code></pre>
<p>Diesen DSN kopierst du und bindest ihn im nächsten Schritt in deine Django-App ein.</p>
<h3>3. In <code>settings.py</code> integrieren</h3>
<p>Integriere den DSN und die grundlegenden Einstellungen für Sentry in dein Projekt. Wichtig dabei sind drei Parameter:</p>
<ul>
<li><strong>dsn</strong>: Verbindet deine App mit deinem Projekt bei Sentry.</li>
<li><strong>traces_sample_rate</strong>: Gibt an, wie viel Prozent der Performance-Daten (z. B. Langsamkeit von Views) gesammelt werden sollen. Bei <code>1.0</code> werden alle Daten erfasst. Für produktive Systeme sind Werte wie <code>0.2</code> üblich, um Datenmenge und Kosten zu reduzieren.</li>
<li><strong>send_default_pii</strong>: Aktiviert das Mitsenden personenbezogener Daten (z. B. eingeloggte User), damit du besser nachvollziehen kannst, wen ein Fehler betrifft.</li>
</ul>
<pre><code class="language-python">import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration

sentry_sdk.init(
    dsn=SENTRY_DSN,
    traces_sample_rate=0.1,
    profiles_sample_rate=0.1,
    environment=SENTRY_ENV,
    default_integrations=False,
    release=VERSION,
    integrations=[
        DjangoIntegration()
    ],
    send_default_pii=True
)
</code></pre>
<h3>Environments trennen</h3>
<p>Gerade bei größeren Projekten willst du unterscheiden können, ob ein Fehler in deiner Entwicklungs-, Staging- oder Produktionsumgebung auftritt. Sonst besteht die Gefahr, dass du bei Tests generierte Fehlermeldungen für Live-Probleme hältst.</p>
<p>Die environment-Angabe im Sentry-Setup hilft dir dabei, das sauber zu trennen:</p>
<pre><code class="language-python">ENVIRONMENT = os.getenv("ENVIRONMENT", "development")
</code></pre>
<p>Du kannst diesen Wert z.B. über Umgebungsvariablen pro Server setzen. Im Sentry-Dashboard kannst du später nach Environment filtern oder gezielte Alerts nur für Production-Fehler anlegen.</p>
<h2>Manuelles Logging</h2>
<p>Um deinen ersten Fehler zu testen, kannst du Sentry auch manuell ansprechen. Das ist besonders hilfreich, wenn du einen Fehler in einem bestimmten Kontext erfassen willst. Zum Beispiel, wenn du einen Fehler in einer bestimmten Funktion oder bei einem bestimmten Nutzer erfassen möchtest.</p>
<pre><code class="language-python">try:
    ...
except Exception as e:
    sentry_sdk.capture_exception(e)
</code></pre>
<p>Damit wäre die Einrichtung von Sentry in Django abgeschlossen. Du kannst jetzt Fehler in deiner App überwachen und bekommst sofort eine Benachrichtigung, wenn etwas schiefgeht. Super! Nun noch ein paar Tipps, wie du Sentry optimal nutzen kannst.</p>
<p>:::GlobalCallAnExpert{:bg="bg-bs-blue"}
#buttonBlockTitle
Wir können das Error-Tracking auch in Deiner Django App verbessern
:::</p>
<h2>Best Practices für eine saubere Fehlerüberwachung</h2>
<p>Einmal eingerichtet, kannst du mit ein paar Tipps das Beste aus Sentry herausholen:</p>
<h3>1. Konsequent Environments nutzen</h3>
<p>Ordne alle Fehler sauber nach <code>production</code>, <code>staging</code> oder <code>development</code>. So kannst du gezielt nach Problemen im Live-System suchen – und behältst bei Tests die Übersicht.</p>
<h3>2. Release-Tags verwenden</h3>
<p>Wenn du regelmäßig neue Versionen deployst, solltest du <code>release="myapp@1.2.3"</code> setzen. Damit kannst du erkennen, ob ein Fehler erst seit einer bestimmten Version auftritt. Praktisch bei Regressions oder Hotfixes.</p>
<h3>3. Nutzerkontext aktivieren</h3>
<p>Sentry kann dir sagen, welcher eingeloggte Nutzer betroffen war. Das hilft enorm beim Nachvollziehen und Debuggen.</p>
<pre><code class="language-python">set_user({"email": user.email})
</code></pre>
<h3>4. Fehler gezielt ignorieren</h3>
<p>Nicht jede Exception ist kritisch. Du kannst Regeln in Sentry anlegen, um z.B. 404-Fehler oder bestimmte Warnungen auszublenden. So bleibt dein Alert-Feed übersichtlich.</p>
<h3>5. Alert-Regeln definieren</h3>
<p>Setze Alerts z. B. bei mehr als 10 Fehlern in 5 Minuten oder wenn eine bestimmte Umgebung betroffen ist. Diese Regeln kannst du per Slack, E-Mail oder Webhook triggern lassen.</p>
<h2>Das Sentry-Dashboard verstehen</h2>
<ul>
<li>Das Dashboard ist dein zentraler Einstiegspunkt. Es zeigt dir sofort:</li>
<li>Neue und häufige Fehler</li>
<li>Welche Releases und Environments betroffen sind</li>
<li>Den Zeitverlauf und Trends von Fehlern</li>
</ul>
<p>Hier erhältst du eine Übersicht über alle Fehler, die in deiner App aufgetreten sind. Du kannst sie nach Schweregrad, Häufigkeit und Umgebung filtern.</p>
<p><img src="/img/blog/sentry-2.png" alt="Sentry Dashboard Beispiel"></p>
<hr>
<h2>Fehler und Issues im Detail</h2>
<p>Klickst du im Dashboard auf einen Fehler, bekommst du eine detaillierte Übersicht:</p>
<ul>
<li>Stacktrace mit Codezeilen</li>
<li>Kontext der Anfrage (Header, URL, etc.)</li>
<li>User-Daten (falls aktiviert)</li>
<li>Link zum betroffenen Release</li>
</ul>
<p>Sentry gruppiert identische Fehler automatisch, sodass du nicht jeden einzelnen Vorfall manuell prüfen musst.
<img src="/img/blog/sentry-3.png" alt="Sentry Fehlerliste">
Hier siehst du auf einen Blick: Was ist kritisch? Was wurde zuletzt gesehen? Wie viele Nutzer:innen sind betroffen?</p>
<hr>
<h2>Fazit</h2>
<p>Mit Sentry bist du im Fehlerfall immer sofort informiert – und kannst handeln, bevor es eskaliert. Einrichten lohnt sich, nicht nur in der Produktion, sondern auch in Staging &#x26; Dev.</p>]]></content:encoded>
            <category>Django</category>
            <category>Entwicklung</category>
            <category>Betrieb</category>
            <category>Dokumentation</category>
            <category>Sicherheit</category>
            <enclosure url="https://blueshoe.de/img/blog/sentry_blog_header.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Untersuchung der Vor- und Nachteile: Function-as-a-Service (FaaS) vs. Container-Orchestrierung mit Kubernetes]]></title>
            <link>https://blueshoe.de/blog/function-as-a-service-faas-vs-kubernetes</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/function-as-a-service-faas-vs-kubernetes</guid>
            <pubDate>Thu, 20 Jul 2023 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Erkunde die wichtigsten Unterschiede zwischen Function-as-a-Service (FaaS) und Kubernetes in dieser detaillierten Analyse. Mit Einblicken zu Vorteilen, Nachteilen, Anwendungsfällen und Kostenüberlegungen hilft dieser Beitrag Technologieprofis bei der Auswahl der optimalen Plattform für ihre spezifischen Cloud-Computing-Anforderungen.</p>
<p><img src="/img/blogs/superluminar-aws-kubernetes.jpg" alt="Function-as-a-Service (FaaS) vs. Container-Orchestrierung mit Kubernetes">{.object-cover .max-w-full .mb-6}</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Function-as-a-Service (FaaS) vs Kubernetes in der Welt des Cloud Computing
:::
:::GlobalParagraph
Die Welt der modernen Computertechnologie ist eine umfangreiche und sich ständig weiterentwickelnde Landschaft, die eine Vielzahl von Plattformen und Frameworks zur Auswahl bietet. In diesem Blogbeitrag möchten wir zwei prominente Paradigmen erkunden und vergleichen: Function-as-a-Service (FaaS)/Serverless und Kubernetes. Diese Plattformen haben erhebliche Aufmerksamkeit erregt und die Art und Weise, wie Anwendungen entwickelt, bereitgestellt und verwaltet werden, revolutioniert.
:::
:::GlobalParagraph
Mit dem Fortschreiten der Technologie suchen Organisationen nach Möglichkeiten, ihre Infrastruktur zu optimieren, die Skalierbarkeit zu verbessern und die Entwicklungsworkflows zu optimieren. FaaS, mit seinem Versprechen von Skalierbarkeit und ereignisgesteuerter Ausführung, hat sich als eine überzeugende Option für Entwickler etabliert. Auf der anderen Seite bietet Kubernetes, der Branchenstandard für die Container-Orchestrierung, eine robuste und flexible Plattform, die es Organisationen ermöglicht, Anwendungen in großem Maßstab zu entwickeln, bereitzustellen und zu verwalten.
:::
:::GlobalParagraph
In diesem Vergleich gehen wir auf die grundlegenden Prinzipien und Eigenschaften von FaaS und Kubernetes ein und möchten damit ihre einzigartigen Stärken und Schwächen beleuchten. Durch die Untersuchung ihrer architektonischen Unterschiede, Ressourcenverwaltungsmöglichkeiten, Entwicklererfahrungen, betrieblichen Komplexitäten und Kostenüberlegungen möchten wir dich mit dem nötigen Wissen ausstatten, um fundierte Entscheidungen bei der Auswahl der optimalen Plattform für deine spezifischen Anwendungsfälle zu treffen.
:::
:::GlobalParagraph
Bitte beachte, dass der folgende Inhalt einen umfassenden Überblick und Vergleich von FaaS/Serverless und Kubernetes bieten soll, ohne in detaillierte Informationen einzutauchen. Der Fokus liegt auf den einzigartigen Merkmalen und Überlegungen jeder Plattform, um dir die Möglichkeit zu geben, fundierte Entscheidungen basierend auf deinen Anforderungen zu treffen. Lassen wir uns auf diese spannende Reise in die Welt von FaaS und Kubernetes ein!
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Serverless Computing mit Function as a Service (FaaS)
:::
:::GlobalParagraph
Function as a Service (FaaS) ist ein relativ neuer Ansatz für das Computing, der die Art und Weise verändert, wie Unternehmen Anwendungen entwickeln und bereitstellen. Mit FaaS schreiben und implementieren Entwickler kleine, zustandslose Funktionen, die durch Ereignisse wie HTTP-Anfragen, Datenbankänderungen und Nachrichten von anderen Diensten ausgelöst werden. Dieser Ansatz beseitigt die Notwendigkeit, Server und Infrastruktur zu verwalten, und ermöglicht es Entwicklern, sich auf das Schreiben von Code und die Bereitstellung von Mehrwert für ihre Kunden zu konzentrieren.
:::</p>
<p>:::GlobalTitle{:size="md" :color="text-bs-blue" :tag="h3" .mb-5}
Vorteile von FaaS
:::
:::GlobalParagraph
FaaS bietet mehrere Vorteile gegenüber traditionellen serverbasierten Architekturen:
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li><strong>Verringerte Verwaltungskosten für die Infrastruktur:</strong> Mit serverlosem Computing müssen Entwickler die zugrunde liegende Infrastruktur nicht verwalten, was die Infrastrukturkosten erheblich reduzieren kann.</li>
<li><strong>Schnellere Time-to-Market:</strong> Serverloses Computing vereinfacht die Bereitstellung und beschleunigt das Schreiben und Bereitstellen von Code, sodass Unternehmen Produkte und Dienstleistungen schneller auf den Markt bringen können.</li>
<li><strong>Hohe Skalierbarkeit:</strong> FaaS skaliert Ressourcen automatisch nach Bedarf, was sicherstellt, dass Anwendungen plötzliche Spitzen im Datenverkehr ohne Ausfallzeiten bewältigen können.</li>
<li><strong>Erhöhte Ausfallsicherheit:</strong> Serverlose Plattformen bieten in der Regel eine integrierte Fehlertoleranz und können Fehler automatisch behandeln, was sicherstellt, dass Anwendungen hoch ausfallsicher sind und unerwartete Fehler ohne Ausfallzeiten bewältigen können.</li>
<li><strong>Kostenflexibilität:</strong> Mit FaaS zahlst du nur für die tatsächlich genutzte Rechenzeit deines Codes, was eine genauere Kostenkontrolle und potenziell niedrigere Gesamtkosten im Vergleich zu traditionellen serverbasierten Architekturen ermöglicht, bei denen du für den gesamten Server oder eine feste Menge an Rechenressourcen bezahlen musst, unabhängig von der tatsächlichen Nutzung.</li>
<li><strong>Nahtlose Integration in die Cloud:</strong> AWS Lambda integriert sich nahtlos in andere AWS-Cloud-Services wie DynamoDB und SQS, was es Entwicklern ermöglicht, komplexe und skalierbare Anwendungen zu erstellen, die mehrere Services nutzen. Dies ermöglicht Unternehmen die Erstellung maßgeschneiderter Lösungen, die ihren spezifischen Anforderungen entsprechen und sich problemlos in bestehende Workflows integrieren lassen.
:::</li>
</ul>
<p>:::GlobalTitle{:size="md" :color="text-bs-blue" :tag="h3" .mb-5}
Potenzielle Nachteile von FaaS
:::
:::GlobalParagraph
Trotz der Vorteile von FaaS gibt es auch einige Einschränkungen zu beachten. Funktionen sind in Bezug auf Speicher und Ausführungszeit begrenzt, und die Startlatenz (die anfängliche Startzeit der Laufzeit deiner Funktion) kann sich auf die Leistung der Anwendung auswirken, wenn Funktionen selten aufgerufen werden. Es ist jedoch möglich, die Infrastruktur im Voraus bereitzustellen, um die Startlatenz zu minimieren. Es ist auch wichtig zu beachten, dass serverlose Funktionen häufig auf einer proprietären Plattform ausgeführt werden, was bedeutet, dass sie möglicherweise nicht mit anderen Cloud-Plattformen oder On-Premise-Infrastrukturen kompatibel sind.
:::
:::GlobalParagraph
Darüber hinaus sind Funktionen von Natur aus zustandslos, d.h. sie haben keine Erinnerung an vorherige Aufrufe oder Interaktionen. Diese Einschränkung kann jedoch durch die Nutzung von serverlosen Datenbanken oder anderen zustandsbehafteten Diensten überwunden werden. Durch die Verwendung dieser Dienste können Funktionen Daten erstellen und abrufen, die über Aufrufe hinweg bestehen bleiben, was eine zustandsbehaftetes Verhalten in einer ansonsten zustandslosen Umgebung ermöglicht.
:::</p>
<p>:::GlobalTitle{:size="md" :color="text-bs-blue" :tag="h3" .mb-5}
Anwendungsfälle für FaaS
:::
:::GlobalParagraph
Function as a Service wurde von Unternehmen jeder Größe genutzt, um innovative und spannende Lösungen zu entwickeln. Hier sind einige Beispiele:
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li><strong>Ereignisgesteuerte Verarbeitung:</strong> FaaS ist eine ideale Lösung für ereignisgesteuerte Verarbeitung, bei der Code nur in Reaktion auf bestimmte Ereignisse ausgeführt werden muss. Dies kann die Verarbeitung von Daten aus Sensoren, die Reaktion auf Benutzeraktionen in einer Webanwendung oder die Automatisierung von Geschäftsprozessen umfassen.</li>
<li><strong>Serverlose APIs:</strong> FaaS ist eine gute Option für den Aufbau serverloser <a href="/loesungen/api-entwicklung/">APIs</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}, bei denen du nur für die genutzten Rechenressourcen bezahlst. Dies kann nützlich sein, um kleine, fokussierte <a href="/loesungen/microservice-architektur-beratung/">Microservices</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} zu erstellen, die ohne komplexe Infrastrukturverwaltung einfach bereitgestellt und skaliert werden können.</li>
<li><strong>Leichtgewichtige Anwendungen:</strong> FaaS ist eine gute Option für leichtgewichtige Anwendungen, bei denen du Anwendungen schnell bereitstellen und skalieren musst, ohne umfangreiche Infrastrukturverwaltung zu benötigen. Dies kann insbesondere für kleine Startups oder einzelne Entwickler nützlich sein, die sich auf den Aufbau ihrer Anwendungen konzentrieren müssen, anstatt die Infrastruktur zu verwalten.
:::
:::GlobalParagraph
AWS Lambda ist eine der beliebtesten FaaS-Lösungen, die heute verfügbar sind. Wenn du das volle Potenzial des serverlosen Computings nutzen möchtest, sind superluminar die Experten, die du an deiner Seite brauchst. Als AWS Advanced Consulting Partner haben sie sich auf die Entwicklung innovativer und kosteneffizienter Lösungen spezialisiert, insbesondere mit serverlosem Computing, einschließlich AWS Lambda als Kernkomponente.
:::</li>
</ul>
<p>:::GlobalTitle{:size="lg" .mb-5}
Kubernetes Verstehen
:::
:::GlobalParagraph
<a href="/blog/kubernetes-development/">Kubernetes</a>{.bs-link-blue} ist eine Open-Source-Container-Orchestrierungsplattform, die die Art und Weise, wie Anwendungen verwaltet und bereitgestellt werden, revolutioniert hat. Es bietet eine umfassende Lösung zur Automatisierung der Bereitstellung, Skalierung und Verwaltung von containerisierten Anwendungen. Mit seinen leistungsstarken Funktionen und Komponenten bietet Kubernetes eine skalierbare und flexible Plattform für die moderne Anwendungsentwicklung.
:::</p>
<p>:::GlobalTitle{:size="md" :color="text-bs-blue" :tag="h3" .mb-5}
Vorteile der Verwendung von Kubernetes
:::
:::GlobalParagraph
Einer der Hauptvorteile von Kubernetes besteht darin, dass es die Verwaltung von Anwendungen automatisiert und vereinfacht. Kubernetes ermöglicht es Entwicklern, den gewünschten Zustand ihrer Anwendung zu definieren und zu deklarieren, und die Plattform kümmert sich um die Orchestrierung der Bereitstellung und Skalierung von Containern entsprechend. Diese Automatisierung reduziert die Komplexität und den manuellen Aufwand, der für die Verwaltung von containerisierten Umgebungen in großem Maßstab erforderlich ist.
:::
:::GlobalParagraph
Kubernetes bietet auch integrierte Unterstützung für Autoscaling, Lastenausgleich und Service Discovery, um sicherzustellen, dass der Datenverkehr effizient auf die Anwendungsinstanzen verteilt wird. Es bietet fortschrittliche Netzwerkfunktionen, die es Containern ermöglichen, nahtlos miteinander zu kommunizieren. Dies erleichtert die Entwicklung von verteilten und auf <a href="/loesungen/microservice-architektur-beratung/">Microservices basierenden Architekturen</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}.
:::
:::GlobalParagraph
Ein weiterer bedeutender Vorteil von Kubernetes sind seine Fehlertoleranz und Selbstheilungsfähigkeiten. Kubernetes überwacht kontinuierlich den Zustand der Anwendungsinstanzen und startet oder ersetzt automatisch fehlgeschlagene Container. Dies gewährleistet hohe Verfügbarkeit und Widerstandsfähigkeit, minimiert Ausfallzeiten und verbessert die Gesamtzuverlässigkeit der Anwendung.
:::</p>
<p>:::GlobalTitle{:size="md" :color="text-bs-blue" :tag="h3" .mb-5}
Potenzielle Nachteile von Kubernetes
:::
:::GlobalParagraph
Obwohl Kubernetes als robustes Container-Orchestrierungssystem an Beliebtheit gewonnen hat, hat es bestimmte Nachteile, darunter höhere Verwaltungskosten und Komplexität. Die Implementierung und Wartung von Kubernetes-Clustern erfordert spezialisierte Fähigkeiten, was zu erhöhten Betriebskosten führen kann. Darüber hinaus ist die Lernkurve von Kubernetes steil und erfordert umfangreiche Schulungen und kontinuierliche Weiterbildung für Teams. Die kontinuierliche Überwachung, Skalierung und Fehlerbehebung erhöhen die Verwaltungslast. Darüber hinaus kann die Bereitstellung und Konfiguration von Anwendungen auf Kubernetes komplex sein, insbesondere für kleinere Projekte oder Teams ohne dedizierte Ressourcen. Diese Faktoren tragen gemeinsam zu den potenziellen Nachteilen von Kubernetes bei.
:::</p>
<p>:::GlobalTitle{:size="md" :color="text-bs-blue" :tag="h3" .mb-5}
Anwendungsfälle für Kubernetes
:::
:::GlobalParagraph
Kubernetes eignet sich besonders gut für die Verwaltung komplexer und skalierbarer Anwendungen. Seine flexible Architektur und seine Ressourcenverwaltungsfähigkeiten machen es zu einer idealen Wahl für <a href="/loesungen/microservice-architektur-beratung/">Microservices-Architekturen</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}, bei denen Anwendungen aus mehreren kleinen, unabhängigen Services bestehen. Kubernetes ermöglicht eine effiziente Bereitstellung, Skalierung und Überwachung (unter Verwendung zusätzlicher Tools wie prometheus) dieser Services und ermöglicht es Unternehmen, hochresiliente und anpassungsfähige Systeme aufzubauen.
:::
:::GlobalParagraph
Datenintensive Anwendungen, wie beispielsweise solche, die Big Data-Verarbeitung oder maschinelles Lernen beinhalten, können von Kubernetes stark profitieren. Es ermöglicht Organisationen, die Leistung verteilten Rechnens zu nutzen, indem Workloads effizient über einen Cluster von Maschinen verteilt werden. Kubernetes bietet die erforderlichen Tools und Abstraktionen zur Verwaltung von datenintensiven Verarbeitungspipelines und ist daher eine attraktive Option für datengetriebene Anwendungen.
:::
:::GlobalParagraph
Darüber hinaus ist es auch möglich, die elegante FaaS-Architektur auf Kubernetes mithilfe von Software wie OpenFaaS zu nutzen.
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Vergleich von FaaS und Kubernetes
:::
:::GlobalTitle{:size="md" :color="text-bs-blue" :tag="h3" .mb-5}
Architektur und Skalierbarkeit
:::
:::GlobalParagraph
Bei einem Vergleich von Functions as a Service (FaaS) und Kubernetes ist einer der wichtigsten Aspekte ihre architektonischen Unterschiede. FaaS-Plattformen wie AWS Lambda oder Azure Functions sind für ereignisgesteuertes, serverloses Computing konzipiert. Sie führen einzelne Funktionen als Reaktion auf Ereignisse aus und ermöglichen eine hohe Skalierbarkeit und Ressourceneffizienz. Kubernetes hingegen folgt einer containerbasierten Architektur, bei der Anwendungen in kleinere, isolierte Anwendungen (Container) aufgeteilt werden, die unabhängig voneinander skaliert werden können. Kubernetes bietet robuste Skalierungsfähigkeiten, mit denen Organisationen ihre Anwendungen sowohl horizontal als auch vertikal je nach Bedarf skalieren können.
:::</p>
<p>:::GlobalTitle{:size="md" :color="text-bs-blue" :tag="h3" .mb-5}
Entwicklungserfahrung und Bereitstellungsworkflow
:::
:::GlobalParagraph
Die Entwicklungserfahrung und der Bereitstellungsworkflow unterscheiden sich ebenfalls zwischen FaaS und Kubernetes. Bei FaaS konzentrieren sich Entwickler hauptsächlich auf das Schreiben und Bereitstellen einzelner Funktionen. FaaS-Plattformen kümmern sich um die zugrunde liegende Infrastruktur und skalieren Funktionen automatisch basierend auf eingehenden Ereignissen. Dieses vereinfachte Entwicklungsmodell reduziert die Betriebslast für Entwickler. Im Gegensatz dazu erfordert Kubernetes, dass Entwickler ihre Anwendungen in Container verpacken und den gewünschten Zustand des Systems mithilfe von YAML- oder Konfigurationsdateien definieren. Entwickler haben mehr Kontrolle über die Infrastruktur und können komplexe, mehrere Container umfassende Anwendungen bereitstellen. Dies erfordert jedoch mehr anfängliche Konfiguration und Verwaltungsaufwand.
:::</p>
<p>:::GlobalTitle{:size="md" :color="text-bs-blue" :tag="h3" .mb-5}
Komplexität des Betriebs und Wartung
:::
:::GlobalParagraph
Die operationale Komplexität und Wartungsüberlegungen unterscheiden sich zwischen FaaS und Kubernetes. FaaS-Plattformen abstrahieren einen Großteil der zugrunde liegenden Infrastruktur und ermöglichen es Entwicklern, sich ausschließlich auf den Code zu konzentrieren. Dies vereinfacht den Betrieb, da die Plattform Skalierbarkeit, Fehlertoleranz und Infrastrukturwartung übernimmt. Im Gegensatz dazu erfordert Kubernetes von Organisationen, die gesamte Container-Orchestrierungsinfrastruktur zu verwalten und zu warten, einschließlich der Verwaltung des Clusters, Überwachung, Skalierung und Gewährleistung hoher Verfügbarkeit. Während Kubernetes mehr Kontrolle bietet, erfordert es auch mehr operationelles Fachwissen und Aufwand.
:::</p>
<p>:::GlobalTitle{:size="md" :color="text-bs-blue" :tag="h3" .mb-5}
Kostenüberlegungen
:::
:::GlobalParagraph
Kostenüberlegungen spielen eine wichtige Rolle bei einem Vergleich von FaaS und Kubernetes. FaaS-Plattformen arbeiten nach dem Pay-per-Use-Modell, bei dem Organisationen basierend auf der Anzahl der Funktionsaufrufe und deren Ressourcenverbrauch in Rechnung gestellt werden. Dies kann kostengünstig für Anwendungen mit sporadischem oder unvorhersehbarem Traffic sein. Kubernetes hingegen erfordert von Organisationen, ihre eigene Infrastruktur bereitzustellen und zu verwalten, was möglicherweise anfängliche Kosten für Hardware oder Cloud-Ressourcen mit sich bringt. Kubernetes-Anwendungen müssen kontinuierlich betrieben werden, um die Serviceverfügbarkeit sicherzustellen, unabhängig davon, ob eingehende Anfragen vorliegen oder nicht. Sobald eingerichtet, bietet Kubernetes jedoch mehr Kostenkontrolle und -vorhersagbarkeit für lang laufende oder kontinuierlich aktive Anwendungen.
:::</p>
<p>:::GlobalTitle{:size="lg" :tag="h3" .mb-5}
Überlegungen zu Function-as-a-Service (FaaS) und Kubernetes
:::
:::GlobalParagraph
Zusammenfassend lässt sich sagen, dass sowohl Functions as a Service (FaaS)/Serverless als auch Kubernetes einzigartige Stärken und Möglichkeiten für die Entwicklung und Bereitstellung moderner Anwendungen bieten. Das Verständnis der Nuancen und Kompromisse der einzelnen Plattformen ist entscheidend, um fundierte Entscheidungen für deine spezifischen Anwendungsfälle zu treffen.
:::
:::GlobalParagraph
FaaS-Plattformen zeichnen sich durch ihre ereignisgesteuerte, serverlose Architektur aus, die es Entwicklern ermöglicht, sich ausschließlich auf das Schreiben von Funktionen zu konzentrieren, ohne sich um die Verwaltung der Infrastruktur zu kümmern. Sie bieten nahtlose Skalierbarkeit und Ressourceneffizienz, was sie zu einer attraktiven Wahl für Anwendungen mit sporadischen oder unvorhersehbaren Verkehrsmustern macht.
:::
:::GlobalParagraph
Auf der anderen Seite bietet Kubernetes eine robuste Plattform für die Container-Orchestrierung, die es Unternehmen ermöglicht, komplexe Anwendungen in großem Umfang zu erstellen, bereitzustellen und zu verwalten. Sie bietet Flexibilität, Kontrolle und erweiterte Funktionen für die Verwaltung verteilter Systeme und eignet sich daher für <a href="/loesungen/microservice-architektur-beratung/">Microservices-Architekturen</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} und datenintensive Anwendungen.
:::
:::GlobalParagraph
Letztlich gibt es keine Einheitslösung für alle. Die Wahl zwischen FaaS und Kubernetes hängt von der Art deiner Anwendung, den Skalierungsanforderungen, den Entwicklungspräferenzen und den Budgetbeschränkungen ab. In einigen Fällen könnte ein hybrider Ansatz oder die Kombination beider Plattformen die beste Strategie sein.
:::
:::GlobalParagraph
Im Zuge der technologischen Entwicklung werden neue Fortschritte und Plattformen auftauchen, die die Möglichkeiten für Entwickler und Unternehmen noch erweitern. Durch das Verfolgen von Branchentrends und die kontinuierliche Bewertung der Eignung von Plattformen stellst du sicher, dass du an der Spitze der Innovation bleibst und optimale Lösungen für deine Anwendungen liefern kannst.
:::</p>]]></content:encoded>
            <category>Kubernetes</category>
            <enclosure url="https://blueshoe.de/img/blogs/superluminar-aws-kubernetes.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Gefyra Roadmap 2025]]></title>
            <link>https://blueshoe.de/blog/gefyra-roadmap-2025</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/gefyra-roadmap-2025</guid>
            <pubDate>Thu, 27 Mar 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Gefyra plant Großes für 2025! Von verbesserten Developer-Tools über neue Integrationen bis hin zu mehr Performance – die Roadmap verspricht spannende Neuerungen. In diesem Blogpost werfen wir einen Blick auf die kommenden Features und zeigen, wie Gefyra die Entwicklung und das Debugging in Kubernetes weiter revolutionieren will. Bleib dran für einen Ausblick auf das nächste Kapitel!</p>
<p><img src="/img/blog/gefyra-roadmap-2025.svg" alt="Gefyra Roadmap 2025">{.object-cover .max-w-full .mb-5}</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Gefyra für Kubernetes-Entwicklung: Das kommt 2025
::
::GlobalParagraph
Du entwickelst in Kubernetes und willst endlich schneller testen, ohne Deploy-Schleifen? Dann wirst du Gefyra lieben – und das kommende Jahr noch mehr. Denn 2025 bringt fette Features, die deine Dev-Experience auf ein neues Level heben. Hier liest du, was die <strong>Gefyra Roadmap</strong> bereithält – inklusive Performance-Boost, neuer Config-Datei, CI-Integration und der Game-Changer: <strong>User Bridges</strong>.
::</p>
<p>:::GlobalButton{:url="https://gefyra.dev/" :label="Entdecke, wie Gefyra dein Dev-Setup verändert" :target="_blank" :color="blue" .mb-6}
:::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Was ist Gefyra?
::
::GlobalParagraph
Falls du neu bist: <a href="https://gefyra.dev/">Gefyra</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} ist ein Open-Source-Tool, das deinen lokalen Code direkt im Kubernetes-Cluster ausführt – <strong>ohne Build- und Push-Zyklus</strong>. Das spart dir jede Menge Zeit und Nerven beim Entwickeln und Debuggen.
::</p>
<p>::GlobalParagraph
Seit der Version 2.0 Ende 2023 hat sich einiges getan. Zeit also für eine klare <strong>Roadmap für 2025</strong>, mit allem, was kommt.
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
1. Quartal 2025: Das ist schon da
::
::GlobalParagraph
Da das erste Quartal 2025 bereits fast um ist, hier eine kurze Zusammenfassung was sich bisher getan hat:
::
::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li>Neuer cli Parameter --registry: Dieser erlaubt es eine custom Docker Registry, von welcher die Gefyra Images gepulled werden sollen, anzugeben. Dies ist besonders hilfreich in “Air-Gapped Environments”.</li>
<li>Neuer cli Parameter --mtu: Dieser Parameter erlaubt es, den standard Wert der Wireguard MTU (1340) zu verändern. Dies ist hilfreich wenn man bereits durch einen VPN auf seinen Kubernetes Cluster zugreift und nun eine Gefyra Verbindung aufbauen möchte.
::</li>
</ul>
<p><img src="/img/blog/Demonstration-neue-Paramenter-Gefyra.gif" alt="Demonstration neue Parameter Gefyra">{.mx-auto .max-w-full}</p>
<p>::GlobalParagraph
Hier eine kurze Demonstration der neuen Parameter und wie sich diese auf das Client File auswirken.
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Was 2025 noch bringt
::</p>
<p>::GlobalParagraph
Aber auch für den Rest von 2025 ist Großes geplant!
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
1. Copy &#x26; Copy Sync
::
::GlobalParagraph
Fangen wir mit dem <strong>Copy &#x26; Copy Sync Feature</strong> an: Dieses soll es ermöglichen, lokale Dateien mit Dateien im Cluster, und andersherum, zu synchronisieren. Wie bestimmt schnell zu erkennen ist, hilft dies vor allem bei Konfigurationsdateien, welche man in einem Feature einführt und die noch nicht im Cluster verfügbar sind.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
2. UDP-over-TCP-Support
::
::GlobalParagraph
Seit April 2024 (und auch schon ein bisschen davor) existiert ein <a href="https://github.com/gefyrahq/gefyra/issues/586">GitHub Issue</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}, dass zur Zeit die Gefyra Verbindung nur über UDP möglich ist. Das wollen wir endlich ändern! So soll es in Zukunft möglich sein, mittels <a href="https://github.com/mullvad/udp-over-tcp">udp-over-tcp</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} existierende Workloads in Kubernetes Cluster, die nur TCP Load Balancing erlauben, zu integrieren.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
3. Gefyra-Konfigurationsdatei
::
::GlobalParagraph
Ein weiteres spannendes Feature wird die Gefyra Konfigurationsdatei. Diese wird 2025 eine entscheidende Rolle spielen und die Grundarbeit für viele weitere Features legen. Sinn und Zweck der Datei ist es, Gefyra Setups ein wenig einfacher und reproduzierbarer zu gestalten. Man wird zum Beispiel konfigurieren können, dass beim <code>gefyra run</code> zwei Container lokal starten sollen. Und für einen davon soll automatisch eine Bridge angelegt werden.
::
::GlobalParagraph
Adé also die Tage des Durchsuchens der Bash History nach dem richtigen <code>run</code> oder <code>bridge</code> Befehl.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
4. CI-Integration
::
::GlobalParagraph
Sobald das Konfigurationsdatei Feature verfügbar ist, wird Gefyra dieses Jahr auch angepasst für CI Workflows. Dies soll kurzlebige Sessions und Reproduzierbarkeit ermöglichen / verbessern und dazu beitragen, dass eure CIs echte Use-Cases abbilden können.
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Das Highlight: User Bridges
::
::GlobalParagraph
Nun aber zum größten geplanten Feature für 2025. Die <strong>User Bridges</strong>!
::
::GlobalParagraph
Was genau sind aber User Bridges? Wie ihr bestimmt schon wisst, ermöglicht Gefyra das “bridgen” eines lokalen Containers und all seiner Netzwerk Requests in den target Cluster. Dadurch können echte Flows und Interaktionen mit anderen Micro-Services / Datenbanken / Task-Runnern getestet werden. Ganz ohne die Notwendigkeit, den Code zu deployen.
::
::GlobalParagraph
Das große Problem an den bisherigen globalen Bridges ist, dass nur eine Person einen bestimmten Workload bridgen kann. Ein simultanes Entwickeln an zwei verschiedenen Features im gleichen Service ist also nicht möglich.
::
::GlobalParagraph
Und hier kommen die User Bridges ins Spiel. Das Konzept sieht vor, dass das Ziel Workload im Cluster “kopiert” wird.
::</p>
<p><img src="/img/blog/Gefyra-User-Bridges.png" alt="Gefyra User Bridges">{.mx-auto .max-w-full}</p>
<p>::GlobalParagraph
Gefyras Carrier setzt dann, wie bekannt, am originalen Workload an. Wenn nun ein Request, welcher bestimmten Regeln entspricht (mehr dazu gleich), registriert wird, wird dieser vom gepatchten Workload verarbeitet. Ansonsten wird er an die Kopie weitergereicht und vom originalen Code verarbeitet.
::</p>
<p>::GlobalParagraph
Um all dies zu ermöglichen muss natürlich einiges geschehen:
::</p>
<p>::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li>Carrier wird eine Version II bekommen. Diese wird mit <a href="https://github.com/cloudflare/pingora">Pingora</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}, Cloudflares Rust Networking Bibliothek, umgesetzt. Durch Einsatz von pingora erhoffen wir uns <strong>schnellere Start-up-Zeiten, mehr Reliabilität und weniger Ressourcenverbrauch</strong> als mit der bisherigen nginx Lösung.</li>
<li>Es wird in Zukunft sogenannte <strong>“bridge mount objects”</strong> geben. Diese sind Kubernetes CRDs, welche die User Bridges vorbereiten sollen und so ein noch <strong>schnelleres bridgen</strong> erlauben. Damit werden User Bridges der neue Status Quo!</li>
<li><strong>Header &#x26; URL matching.</strong> Diese Änderung ist wegweisend für die User Bridges. Der User soll mit diesen Optionen Regeln aufstellen, wie die User Bridge Requests verarbeitet. Hierbei ist eurer Fantasie keine Grenze gesetzt! Ein Header mit Regex Pfad matching auf die 5. Subroute? Kein Problem!
::</li>
</ul>
<p>::GlobalTitle{:size="lg" .mb-5}
Gefyra 2025: ein Jahr voller Developer-Optimierungen
::
::GlobalParagraph
Die Roadmap für 2025 zeigt, dass Gefyra sich weiter in eine Richtung entwickelt, die das <strong>Kubernetes-Development noch flexibler und effizienter</strong> macht. Mit Features wie Copy Sync, UDP-over-TCP-Unterstützung und einer Konfigurationsdatei wird die Developer Experience deutlich verbessert. Besonders die User Bridges versprechen, ein Game-Changer zu sein, indem sie das <strong>simultane Entwickeln an mehreren Features im selben Service</strong> ermöglichen.
::
::GlobalParagraph
Die kommenden Monate werden spannend, und wir freuen uns darauf, diese neuen Funktionen gemeinsam mit der Community umzusetzen. Hast du Feedback oder Ideen zu den geplanten Features? Dann komm gerne auf uns zu – Gefyra lebt von der Zusammenarbeit mit seinen Nutzern!
::</p>
<p>:::GlobalTitle{:size="lg" .mb-5 .mt-8}
Häufige Fragen
:::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
1. Was ist der Vorteil der neuen User Bridges in Gefyra?
::
::GlobalParagraph
Mit den User Bridges kannst du parallel mit anderen Entwickler:innen am selben Kubernetes-Service arbeiten – ohne Deploys und ohne Konflikte. Das macht Teamwork im Microservices-Kontext deutlich einfacher.
::
::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
2. Wie hilft mir die Gefyra-Konfigurationsdatei im Alltag?
::
::GlobalParagraph
Die neue gefyra.yaml erlaubt es dir, dein Setup einmal zu definieren und immer wieder reproduzierbar zu starten – ideal für Teams, CI/CD und strukturierte Workflows.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
3. Wird Gefyra 2025 auch für CI/CD nutzbar sein?
::
::GlobalParagraph
Ja! Kurzlebige Sessions, automatisches Bridging und reproduzierbare Environments machen Gefyra bald CI-ready – perfekt, um echte Use Cases in deinen Pipelines zu testen.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
4. Warum ist Copy &#x26; Sync ein wichtiges Feature für Kubernetes-Development?
::
::GlobalParagraph
Copy &#x26; Sync synchronisiert Dateien zwischen lokalem System und Cluster – z. B. Konfigurationsdateien, die im Cluster noch nicht vorhanden sind. Das spart Zeit und verhindert Fehlkonfigurationen.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
5. Welche Vorteile bringt die Unterstützung für UDP-over-TCP?
::
::GlobalParagraph
Damit kannst du Gefyra auch in Clustern nutzen, die ausschließlich TCP-Traffic zulassen – etwa bei restriktiven Load Balancer-Settings. Mehr Kompatibilität, weniger Einschränkungen.
::</p>]]></content:encoded>
            <category>Kubernetes</category>
            <category>Gefyra</category>
            <category>Entwicklung</category>
            <category>Betrieb</category>
            <enclosure url="https://blueshoe.de/img/blog/gefyra-roadmap-2025.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Auslesen der Gesundheitskarte (eGK) mit Python]]></title>
            <link>https://blueshoe.de/blog/gesundheitskarte-auslesen</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/gesundheitskarte-auslesen</guid>
            <pubDate>Thu, 30 Jan 2020 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Mit der Einführung der ePA (elektronische Patientenakte) schreitet die Digitalisierung des deutschen Gesundheitssystems mit großen Schritten voran. Einer der wichtigsten Schritte im Rahmen des Rollouts ist die Ausgabe der elektronische Gesundheitskarte (eGK) an alle Kunden der gesetzlichen Krankenkassen. Wofür die eGK verwendet wird, welche Daten auf ihr gespeichert sind und wie man die Versicherungsstammdaten mit einem Python Skript ausliest, soll in diesem Blogpost behandelt werden.</p>
<p><img src="/img/blogs/design_ohne_titel_1.jpg" alt="design_ohne_titel">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalTitle{:size="lg" .mb-5}
WAS IST EINE ELEKTRONISCHE PATIENTENAKTE/GESUNDHEITSKARTE?
:::
:::globalParagraph
Im Gegensatz zu ihrem Vorgänger, der KVK (Krankenversicherungskarte), kann die eGK mehr als nur für die Abrechnung verwendet zu werden. Sie dient zur Authentifizierung von Patienten in Praxen und Kliniken. Mithilfe der Karte und einer zugehörigen PIN ist es Patienten möglich, ihrem behandelnden Mediziner Zugriff auf die eigene ePA zu geben.
:::
:::globalParagraph
Hierbei wird es zu Beginn so sein, dass ein Arzt Zugriff auf die komplette Akte bekommt, später ist ein beschränkter denkbar. Außerdem können vom behandelnden Mediziner Daten bzw. Dokumente wie Röntgenbilder, MRTs oder Arztbriefe in der elektronischen Akte des Patienten gespeichert werden. Die eigene Akte kann der Patient dann beispielsweise bei einer Überweisung an einen anderen Arzt auch via Gesundheitskarte freigeben.
:::
:::globalParagraph
Die elektronischen Patientenakten werden in der Telematikinfrastruktur (TI) gespeichert. Zugriff auf diese erhält man nur mit entsprechendem Heilberufsausweis. Dieser kommt wie die eGK in Form einer Chipkarte und dient zur Identifizierung eines Arztes.
:::
:::globalParagraph
Mehr Details zu dem Aufbau, der Funktionsweise und den Problemen der TI sind anschaulich in diesen beiden Vorträgen erklärt:
:::</p>
<p>:::globalTitle{:size="sm" :tag="h4" .mb-5}
„Hacker hin oder her“: Die elektronische Patientenakte kommt!
:::
:::globalParagraph
<a href="https://www.youtube.com/embed/q6l_B2fgJjM">https://www.youtube.com/embed/q6l_B2fgJjM</a>{.bs-link-blue :target="_blank"}
:::
:::globalParagraph
(Quelle: YouTube-Kanal media.ccc.de; Martin Tschirsich cbro, Dr. med. Christian Brodowski, Dr. André Zilch)
:::</p>
<p>:::globalTitle{:size="sm" :tag="h4" .mb-5}
15 Jahre deutsche Telematikinfrastruktur (TI)
:::
:::globalParagraph
<a href="https://www.youtube.com/watch?v=lHdXeH9ROog">https://www.youtube.com/watch?v=lHdXeH9ROog</a>{.bs-link-blue :target="_blank"}
:::
:::globalParagraph
(Quelle: YouTube-Kanal media.ccc.de; Christoph Saatjohann)
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
TECHNISCHE BETRACHTUNG DER EGK
:::
:::globalParagraph
Vor der Implementierung eines Python Skripts zum Auslesen der Gesundheitskarte sollte ein Verständnis über die technischen Grundlagen entwickelt werden. Betrachtet werden also die physische Karte sowie die Datenstrukturen auf dem Chip selbst.
:::</p>
<p>:::globalTitle{:size="md" .mb-5}
<strong>ÄUSSERLICHKEITEN</strong>
:::
:::globalParagraph
Die elektronische Gesundheitskarte ist eine einfache Chipkarte, eine sogenannte „Smart Card“ oder ICC (Integrated Circuit Card). Neben dem Chip sind Informationen über den Karteninhaber auf die Oberfläche gedruckt.
:::
:::globalParagraph
Ein weitere interessante Information stellt das kleine „G2“ in der oberen rechten Ecke der Karte da. Dieses steht für „Generation 2“ und stellt somit die Version der Karte dar. Darüber hinaus befindet sich auf der Vorderseite sichtbar der Chip, auf welchem alle Daten gespeichert sind.
:::
:::globalParagraph
<strong>So sieht beispielsweise eine Vorderseite und eine Rückseite aus:</strong>
:::</p>
<p><img src="/img/blogs/muster-elektronische-gesundheitskarte.jpg" alt="muster-elektronische-gesundheitskarte">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Auf der Rückseite findet man den Namen des Besitzers, den Firmennamen der Versicherung, eine Kennziffer für Besitzer und eine für die Karte, sowie das Geburtsdatum des Besitzers und das Ablaufdatum der Karte.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
DATEN AUF DER EGK
:::
:::globalParagraph
Wie bereits erwähnt handelt es sich im eine sogenannte „Smart Card“. Diese werden hauptsächlich als ROM (Read Only Memory) verwendet - also um Zugriff auf Daten zu geben, nicht jedoch um neu beschrieben zu werden. Die elektronische Gesundheitskarte ist jedoch nicht nur als ROM ausgelegt, sondern kann auch neu beschrieben werden, beispielsweise um die Versicherungsstammdaten des Besitzers zu aktualisieren (z. B. bei Umzug oder nach Namensänderung).
:::
:::globalParagraph
Daten auf der eGK sind hierarchisch abgelegt. Dabei wird in Verzeichnisse (DF) und Datenspeicher (EF) unterschieden. Interessant ist an dieser Stelle vorrangig das Verzeichnis HCA (Health Care Application). In diesem befinden sich die Datenspeicher PD (persönliche Versichertendaten) und VD (Versicherungsdaten). (Quelle: Fachportal gematik )
:::
:::globalParagraph
Neben diesen sind noch weitere Datenspeicher und Verzeichnisse verfügbar - diese sind hervorragend in einem Artikel des Fraunhofer-Instituts für offene Kommunikationssysteme erklärt: BMG AsK
:::</p>
<p><img src="/img/blogs/egk-pythonblog.jpg" alt="egk-pythonblog">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalTitle{:size="lg" .mb-5}
DATEN VON SMART CARDS LESEN
:::
:::globalParagraph
Um Daten von einer Smart Card zu lesen, benötigt man zunächst ein Kartenlesegerät. Man benötigt jedoch kein spezielles Lesegerät für die Gesundheitskarte - eine kurze Suche im Internet nach einem „Chipkartenleser“ oder einem „Smart Card Reader“ liefert viele Ergebnisse.
:::
:::globalParagraph
Die Kommunikation zwischen dem Lesegerät und der Karte erfolgt mittels APDU (Application Protocol Data Unit). Die Struktur von APDU ist in der Norm ISO/IEC 7816-4 definiert. Man unterscheidet zwischen Command APDU und Response APDU.
:::globalParagraph
Command APDU übertragen ein Kommando oder einen Befehl von dem Kartenlesegerät an die Chipkarte. Dieser besteht aus einem Header und einem optionalen Body. Response APDU dienen als Antwort der Karte auf ein Command APDU. Dieser besteht wiederum aus einem Trailer und einem optionalen Body. Der Aufbau im Detail ist im folgenden Wikipedia Artikel gut erklärt: Application Protocol Data Unit – Wikipedia
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
AUSLESEN VIA PYTHON SKRIPT
:::
:::globalParagraph
Zum Auslesen der elektronischen Gesundheitskarte wird ein Kartenlesegerät sowie ein funktionstüchtiger Python Interpreter benötigt. Zudem legen wir uns den „Implementierungsleitfaden zur Einbindung der eGK in die Primärsysteme der Leistungserbringer“ der gematik parat: Fachportal gematik
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
APDU VIA PYTHON
:::
:::globalParagraph
Sucht man nach Paketen, um APDU Kommandos via Python abzusetzen oder mit Smart Cards zu kommunizieren, kommt man nicht an dem Paket pyscard Python Smart Card Library vorbei. Die Bibliothek ermöglicht uns genau das, was wir wollen - via Python APDU Kommandos abzusenden und zu empfangen. Also erstellen wir ein virtuelle Umgebung (VirtualEnv) und installieren das Paket dort.
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">virtualenv -p python3 ~/egk
pip3 install pyscard
</code></pre>
<p>:::
:::globalParagraph
Nach der Installation erzeugen wir eine Python Datei - /egk.py/:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">touch egk.py
</code></pre>
<p>:::
:::globalParagraph
In dieser wird nun der Code zum Auslesen der Karte untergebracht. Als erstes gilt es nun die Verbindung zu Lesegerät herzustellen
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">from smartcard.System import readers
</code></pre>
<p>:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">class HealthCardReader:
    def init(self):
        r = readers()
        if len(r) &#x3C; 1:
        raise Exception('No reader found.')
        self.reader = r[0]
        self.connection = self.reader.createConnection()

        self.connection.connect()

healthcard_reader = HealthCardReader()
</code></pre>
<p>:::
:::globalParagraph
An dieser Stelle nutzen wir die 'readers' Funktion der pyscard-Bibliothek. Sie gibt uns eine Liste von verfügbaren Kartenlesegeräten zurück. Sollte keines verfügbar sein, wird eine Exception ausgelöst. Ansonsten erhält unser 'HealthCardReader' Objekt eine 'connection'. Mithilfe dieser können wir nun Command APDUs an die Karte übertragen:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">...
healthcard_reader = HealthCardReader()
healthcard_reader.connection.transmit(apdu)
</code></pre>
<p>:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
DAS WEITERE VORGEHEN ERGIBT SICH AUS DER SPEZIFIKATION DER GEMATIK:
:::
:::globalParagraph
Dieses Diagramm stellt den Programmablauf zum Auslesen von persönlichen Daten bzw. Versicherungsdaten dar.
:::</p>
<p><img src="/img/blogs/image3-egk-pythonblog.jpg" alt="image3-egk-pythonblog">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Die entsprechenden APDU- Kommandos können ebenfalls aus der Spezifikation entnommen werden:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">COMMANDS = {
    'SELECT_MF': [0x00, 0xA4, 0x04, 0x0C, 0x07, 0xD2, 0x76, 0x00, 0x01, 0x44, 0x80, 0x00],
    'SELECT_HCA': [0x00, 0xA4, 0x04, 0x0C, 0x06, 0xD2, 0x76, 0x00, 0x00, 0x01, 0x02],
    'EF_VERSION_1': [0x00, 0xB2, 0x01, 0x84, 0x00],
    'EF_VERSION_2': [0x00, 0xB2, 0x02, 0x84, 0x00],
    'EF_VERSION_3': [0x00, 0xB2, 0x03, 0x84, 0x00],
    'SELECT_FILE_PD': [0x00, 0xB0, 0x81, 0x00, 0x02],
    'SELECT_FILE_VD': [0x00, 0xB0, 0x82, 0x00, 0x08]
}
</code></pre>
<p>:::
:::globalParagraph
Die Begriffe Root und MF (Masterfile) können hier als äquivalent angesehen werden. Zuerst erweitern wir unsere Klasse um ein paar Utilities:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">class HealthCardReader:
    def init(self):
        r = readers()
        if len(r) &#x3C; 1:
        raise Exception('No reader found.')
        self.reader = r[0]
        self.connection = self.reader.createConnection()
        self.connection.connect()

  def create_read_command(self, pos, length):
    bpos = [pos >> 8 &#x26; 0xFF, pos &#x26; 0xFF]
    return [0x00, 0xB0, bpos[0], bpos[1], length]

    def read_file(self, offset, length):
    data = []
    max_read = 0xFC
    pointer = offset
    while len(data) &#x3C; length:
        bytes_left = length - len(data)
        readlen = bytes_left if bytes_left &#x3C; max_read else max_read
        data_chunk = self.run_command(self.create_read_command(pointer, readlen))
        pointer += readlen
        data.extend(data_chunk)
    return data

    def run_command(self, apdu):
        data, sw1, sw2 = self.connection.transmit(adpu)
        if (sw1, sw2) == (0x90, 0x00):
            return data
        raise Exception('Bad Status')
</code></pre>
<p>:::
:::globalParagraph
Mithilfe der neuen Utils können wir bequem Kommandos absetzen und Dateien von der Karte lesen. 'create_read_command' erlaubt es uns ein Command APDU zu erstellen, um einen bestimmten Bereich einer Datei zu lesen. 'read_file' wiederum erlaubt es uns eine komplette Datei auszulesen. 'run_command' erlaubt es uns jedes mal automatisch zu testen, ob unser Kommando erfolgreich war und wenn dies der Fall ist, das Ergebnis zurückzugeben. Nun können wir loslegen, um die Version der Karte zu erkennen und die PD auszulesen:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">healthcard_reader = HealthCardReader()
healthcard_reader.run_command(COMMANDS['SELECT_MF'])
ef_v_1 = healthcard_reader.run_command(COMMANDS['EF_VERSION_1'])
ef_v_2 = healthcard_reader.run_command(COMMANDS['EF_VERSION_2'])
ef_v_3 = healthcard_reader.run_command(COMMANDS['EF_VERSION_3'])

if ef_v_1 == '3.0.0' and ef_v_2 == '3.0.0' and ef_v_3 == '3.0.2':
    generation = 'G1'
elif ef_v_1 == '3.0.0' and ef_v_2 == '3.0.1' and ef_v_3 == '3.0.3':
    generation = 'G1 plus'
elif ef_v_1 == '4.0.0' and ef_v_2 == '4.0.0' and ef_v_3 == '4.0.2':
    generation = 'G2'

# Selektieren der PD
healthcard_reader.run_command(COMMANDS['SELECT_HCA'])
healthcard_reader.run_command(COMMANDS['SELECT_FILE_PD'])

# Auslesen der ersten beiden Bytes - diese enthalten die Länge der PD
data = healthcard_reader.run_command(healthcard_reader.create_read_command(0x00, 0x02))
pd_length = (data[0] &#x3C;&#x3C; 8) + data[1]
# Abziehen der ersten beiden Bytes
pd_length -= 0x02

# Nochmaliges Selektieren der PD
healthcard_reader.run_command(COMMANDS['SELECT_MF'])
healthcard_reader.run_command(COMMANDS['SELECT_HCA'])
healthcard_reader.run_command(COMMANDS['SELECT_FILE_PD'])

# Auslesen der komprimierten Daten nach den ersten beiden Bytes, mit der emittelten Länge
patient_data_compressed =  healthcard_reader.read_file(0x02, pd_length)
</code></pre>
<p>:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
ENTPACKEN DER DATEN MIT PYTHON-STANDARDFUNKTIONEN
:::
:::globalParagraph
Nachdem Rohdaten von der Karte extrahiert sind müssen diese noch entpackt werden, da diese aktuell noch via gzip komprimiert vorliegen.
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker"># Vorbeugen von zlib.error (truncated stream)
patient_data_compressed.extend([0x00] * 16)
patient_data_compressed = bytearray(patient_data_compressed)
patient_data_compressed = bytes(patient_data_compressed)
# Dekomprimieren der Rohdaten
patient_data_xml = zlib.decompress(patient_data_compressed, 15 + 16)
</code></pre>
<p>:::
:::globalParagraph
Damit liegen die persönlichen Daten des Patienten im XML-Format vor. Diese können nun nach belieben via 'lxml' oder einer anderen XML-Bibliothek verarbeitet werden.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
BLUESHOE VERÖFFENTLICHT PYTHON-HEALTHCARD
:::
:::globalParagraph
Alle Erfahrungen, die wir als Blueshoe während unserer Arbeit mit der elektronischen Gesundheitskarte gemacht haben sowie alle zu beachtenden Tücken, haben wir in einem Paket zum Auslesen der elektronischen Gesundheitskarte zusammengefasst. Das Paket python-healthcard ist OpenSource aus dem Hause Blueshoe und wird auch fortlaufend betreut. Damit habt ihr vielleicht genau die Hilfe, die ihr in eurem nächsten Software-Entwicklungsprojekt braucht.
:::
:::globalParagraph
python-healthcard gewährt dem Benutzer Zugang zu den persönlichen Daten sowie den Versicherungsdaten, welche auf einer eGK gespeichert sind. In unserem Projekt “Ofa Smart Scan” - einem digitalen Körperscanner für den Sanitätsfachhandel - findet die python-healthcard aktuell bereits Anwendung. Die Technologie erlaubt es, neue Kunden eines Sanitätshauses mitsamt ihrer persönlichen Daten innerhalb von Sekunden anzulegen und direkt einen Messvorgang für beispielsweise Kompressionsstrümpfe anzustoßen.
:::
:::globalParagraph
Wir freuen uns über Verbesserungsvorschläge, Lob und Kritik - gerne via GitHub oder per Mail an <a href="mailto:healthcard@blueshoe.de">healthcard@blueshoe.de</a>{.bs-link-blue}
:::</p>]]></content:encoded>
            <category>Python</category>
            <enclosure url="https://blueshoe.de/img/blogs/design_ohne_titel_1.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Handoff vom Designer zum Entwickler: Was wir für die Frontend-Umsetzung benötigen]]></title>
            <link>https://blueshoe.de/blog/handoff-vom-designer-zum-entwickler</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/handoff-vom-designer-zum-entwickler</guid>
            <pubDate>Tue, 25 Nov 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Der Übergang vom Design zur Entwicklung ist ein kritischer Moment in jedem Frontend-Projekt. Ein durchdachter Handoff kann Wochen an Entwicklungszeit sparen, während ein unvollständiger Handoff zu endlosen Rückfragen, Verzögerungen und Kompromissen führt.</p>
<p><img src="/img/blog/design_handoff.webp" alt="Handoff vom Designer zum Entwickler – Frontend Checkliste"></p>
<p>In diesem Artikel erklären wir, welche Informationen Designer bereitstellen sollten, damit Entwickler Frontend-Projekte effizient und präzise umsetzen können. Basierend auf unseren Erfahrungen aus zahlreichen Kundenprojekten haben wir die häufigsten Probleme identifiziert und zeigen, wie sie vermieden werden können.</p>
<p>:GlobalDesignHandoffChecklist</p>
<h2>Warum ein durchdachter Design-Handoff so wichtig ist</h2>
<h3>Das Problem: Unvollständige Design-Dokumentation</h3>
<p>In vielen Projekten erhalten Entwickler Design-Dateien, die zwar visuell ansprechend sind, aber wichtige technische Details vermissen lassen. Das führt zu Situationen, in denen Entwickler selbst entscheiden müssen, wie Komponenten in verschiedenen Zuständen aussehen sollen, welche Breakpoints verwendet werden oder wie die Mobile-Navigation funktionieren soll.</p>
<p><strong>Die Folgen:</strong></p>
<ul>
<li><strong>Verzögerungen</strong> durch Rückfragen und Klärungsbedarf</li>
<li><strong>Inkonsistenzen</strong> zwischen Design und Umsetzung</li>
<li><strong>Mehraufwand</strong> durch nachträgliche Anpassungen</li>
<li><strong>Frustration</strong> auf beiden Seiten – Designer und Entwickler</li>
</ul>
<h3>Die Lösung: Strukturierter Design-Handoff</h3>
<p>Ein strukturierter Design-Handoff ist mehr als nur das Teilen einer Figma-Datei. Es ist eine umfassende Dokumentation, die alle Aspekte der visuellen und interaktiven Gestaltung abdeckt. Entwickler sollten in der Lage sein, das Design zu verstehen und umzusetzen, ohne ständig nachfragen zu müssen.</p>
<h3>Alternative Ansatz: Vorgefertigte Design-Systeme verwenden</h3>
<p>Eine Alternative zu individuellen Designs ist die Verwendung vorgefertigter Design-Systeme oder UI-Komponenten-Bibliotheken. Diese Systeme bieten fertige Komponenten, die Entwickler mit Design-Tokens (Farben, Typography, Spacing) anpassen können. Ein Beispiel ist ein Framework wie <a href="https://flowbite.com/">Flowbite</a>{target="_blank"}, das Figma-Design-Dateien bereitstellt, die Designer anpassen können, sodass Entwickler vertraute UI-Bibliotheken nutzen können.</p>
<p><strong>Wann vorgefertigte Design-Systeme sinnvoll sind:</strong></p>
<ul>
<li>Das Team hat sich auf eine spezifische UI-Bibliothek geeinigt</li>
<li>Das Projekt kann innerhalb der Grenzen des Design-Systems arbeiten</li>
<li>Entwickler sind bereits mit dem gewählten Framework vertraut</li>
<li>Time-to-Market ist kritisch und Anpassungsbedarf ist minimal</li>
</ul>
<p><strong>Die Realität:</strong>
In der Praxis kennen jedoch viele Designer diese Frameworks nicht oder arbeiten mit kundenspezifischen, markenspezifischen Designs, die nicht mit Standard-Systemen übereinstimmen. Viele Projekte erfordern einzigartige visuelle Identitäten, die über das hinausgehen, was vorgefertigte Systeme bieten.</p>
<p><strong>Der beste Ansatz:</strong>
Idealerweise sollten Designer und Entwickler frühzeitig abstimmen, ob ein vorgefertigtes Design-System verwendet oder individuelle Designs erstellt werden sollen. Wenn ein Framework gewählt wird, können Designer darauf aufbauen und die Design-Tokens anpassen. Wenn individuelle Designs erstellt werden, ist ein vollständiger Handoff mit allen in diesem Artikel beschriebenen Informationen für eine präzise Umsetzung unerlässlich.</p>
<p>Dieser Artikel fokussiert auf das Szenario, in dem Designer individuelle Designs liefern – in diesen Fällen benötigen Entwickler eine umfassende Dokumentation, um das Design präzise umzusetzen.</p>
<h2>Die essentiellen Bestandteile eines Design-Handoffs</h2>
<h3>1. Farbpalette: Die Grundlage der visuellen Identität</h3>
<p>Eine vollständige Farbpalette ist fundamental für jede Frontend-Umsetzung. Sie sollte nicht nur die Grundfarben enthalten, sondern auch alle Varianten, die im Projekt verwendet werden.</p>
<p><strong>Was eine vollständige Farbpalette enthalten sollte:</strong></p>
<ul>
<li><strong>Primärfarben</strong> mit allen Varianten (hell, dunkel, gesättigt)</li>
<li><strong>Sekundärfarben</strong> für Akzente und Highlights</li>
<li><strong>Neutrale Farben</strong> für Hintergründe, Texte und Borders</li>
<li><strong>Zustandsfarben</strong> für Hover, Fokus, Disabled, Error, Success</li>
<li><strong>Hex-Werte oder CSS-Variablen</strong> für direkte Umsetzung</li>
</ul>
<p><strong>Beispiel-Struktur einer Farbpalette:</strong></p>
<pre><code class="language-css">/* Primärfarben */
--color-primary: #0066CC;
--color-primary-hover: #0052A3;
--color-primary-light: #E6F2FF;
--color-primary-dark: #004080;

/* Sekundärfarben */
--color-secondary: #FF6B35;
--color-secondary-hover: #E55A2B;

/* Neutrale Farben */
--color-background: #FFFFFF;
--color-surface: #F5F5F5;
--color-text: #1A1A1A;
--color-text-light: #666666;
--color-border: #E0E0E0;

/* Zustandsfarben */
--color-error: #DC3545;
--color-success: #28A745;
--color-warning: #FFC107;
--color-info: #17A2B8;
--color-disabled: #CCCCCC;
</code></pre>
<p><strong>Warum das wichtig ist:</strong>
Ohne eine vollständige Farbpalette müssen Entwickler Farbwerte erraten oder selbst definieren, was zu Inkonsistenzen führt. Eine präzise Farbpalette ermöglicht eine konsistente Umsetzung und erleichtert spätere Wartung und Anpassungen.</p>
<h3>2. Komponenten mit verschiedenen Zuständen</h3>
<p>Komponenten sind das Herzstück jeder Frontend-Anwendung. Für eine präzise Umsetzung benötigen Entwickler nicht nur das Standard-Design, sondern alle Zustände und Varianten einer Komponente.</p>
<p><strong>Zustände, die dokumentiert sein sollten:</strong></p>
<ul>
<li><strong>Default:</strong> Der Standard-Zustand der Komponente</li>
<li><strong>Hover:</strong> Wie sieht die Komponente beim Darüberfahren aus?</li>
<li><strong>Fokus:</strong> Wie wird der Fokus visuell dargestellt (wichtig für Accessibility)?</li>
<li><strong>Active:</strong> Der Zustand während einer Interaktion</li>
<li><strong>Disabled:</strong> Wie sieht die deaktivierte Variante aus?</li>
<li><strong>Error:</strong> Fehlerzustände mit entsprechenden visuellen Hinweisen</li>
<li><strong>Success:</strong> Erfolgszustände für Bestätigungen</li>
</ul>
<p><strong>Zusätzliche Varianten:</strong></p>
<ul>
<li><strong>Größen:</strong> Small, Medium, Large</li>
<li><strong>Farbvarianten:</strong> Primary, Secondary, Tertiary</li>
<li><strong>Layout-Varianten:</strong> Mit/ohne Icon, verschiedene Textlängen</li>
<li><strong>Kontext-Varianten:</strong> In verschiedenen Umgebungen (Header, Footer, Modal)</li>
</ul>
<p><strong>Beispiel: Button-Komponente</strong></p>
<p>Eine vollständig dokumentierte Button-Komponente sollte folgende Varianten enthalten:</p>
<pre><code>Button Primary
├── Default (Hintergrund: Primärfarbe, Text: Weiß)
├── Hover (Hintergrund: Primärfarbe dunkler, Cursor: Pointer)
├── Fokus (Outline: 2px solid Fokusfarbe, Outline-Offset: 2px)
├── Active (Hintergrund: Primärfarbe noch dunkler)
├── Disabled (Hintergrund: Grau, Opacity: 0.6, Cursor: not-allowed)
└── Loading (Spinner-Icon, Text: "Wird geladen...")

Button Secondary
├── Default (Hintergrund: Transparent, Border: Primärfarbe)
├── Hover (Hintergrund: Primärfarbe hell, Text: Primärfarbe)
└── [weitere Zustände...]

Größen
├── Small (Padding: 8px 16px, Font-Size: 14px)
├── Medium (Padding: 12px 24px, Font-Size: 16px)
└── Large (Padding: 16px 32px, Font-Size: 18px)
</code></pre>
<p><strong>Warum das wichtig ist:</strong>
Fehlende Zustandsdefinitionen führen dazu, dass Entwickler selbst entscheiden müssen, wie Komponenten in verschiedenen Situationen aussehen. Das führt zu Inkonsistenzen und kann die User Experience beeinträchtigen.</p>
<h3>3. Typography: Mehr als nur Schriftarten</h3>
<p>Typography ist ein zentraler Bestandteil des Designs, der oft unterschätzt wird. Eine vollständige Typography-Dokumentation sollte alle Textstile enthalten, die im Projekt verwendet werden.</p>
<p><strong>Was eine Typography-Dokumentation enthalten sollte:</strong></p>
<ul>
<li><strong>Schriftfamilien:</strong> Welche Fonts werden verwendet (Web-Fonts, System-Fonts)?</li>
<li><strong>Schriftgrößen:</strong> Alle verwendeten Font-Sizes mit entsprechenden Line-Heights</li>
<li><strong>Schriftgewichte:</strong> Regular, Medium, Bold, etc.</li>
<li><strong>Textstile:</strong> Headings (H1-H6), Body-Text, Captions, Labels</li>
<li><strong>Responsive Typography:</strong> Wie ändern sich Schriftgrößen auf verschiedenen Breakpoints?</li>
<li><strong>Farbzuordnungen:</strong> Welche Textfarbe wird in welchem Kontext verwendet?</li>
</ul>
<p><strong>Beispiel-Typography-System:</strong></p>
<pre><code class="language-css">/* Headings */
--font-heading-1: 48px / 56px, Bold, 'Inter', sans-serif;
--font-heading-2: 36px / 44px, Bold, 'Inter', sans-serif;
--font-heading-3: 28px / 36px, SemiBold, 'Inter', sans-serif;
--font-heading-4: 24px / 32px, SemiBold, 'Inter', sans-serif;
--font-heading-5: 20px / 28px, Medium, 'Inter', sans-serif;
--font-heading-6: 18px / 24px, Medium, 'Inter', sans-serif;

/* Body Text */
--font-body-large: 18px / 28px, Regular, 'Inter', sans-serif;
--font-body: 16px / 24px, Regular, 'Inter', sans-serif;
--font-body-small: 14px / 20px, Regular, 'Inter', sans-serif;

/* Special */
--font-caption: 12px / 16px, Regular, 'Inter', sans-serif;
--font-label: 14px / 20px, Medium, 'Inter', sans-serif;
--font-button: 16px / 24px, Medium, 'Inter', sans-serif;
</code></pre>
<p><strong>Warum das wichtig ist:</strong>
Typography beeinflusst Lesbarkeit, Hierarchie und Gesamterscheinung einer Website erheblich. Unklare Typography-Definitionen führen zu inkonsistenten Textdarstellungen und können die User Experience negativ beeinflussen.</p>
<h3>4. Breakpoints: Die Basis für responsive Design</h3>
<p>Responsive Design ist heute Standard, aber viele Designer liefern nur 2-3 Breakpoints, obwohl 6 Breakpoints für eine präzise Umsetzung optimal wären. Wir empfehlen die Verwendung der Bootstrap-Standard-Breakpoints als Basis, da sie eine bewährte und konsistente Grundlage für responsive Layouts bieten.</p>
<p><strong>Bootstrap-Standard-Breakpoints (empfohlen):</strong></p>
<ul>
<li><strong>Extra Small (xs):</strong> &#x3C;576px (kleine Smartphones im Hochformat)</li>
<li><strong>Small (sm):</strong> ≥576px (große Smartphones im Querformat)</li>
<li><strong>Medium (md):</strong> ≥768px (Tablets)</li>
<li><strong>Large (lg):</strong> ≥992px (Standard-Desktops)</li>
<li><strong>Extra Large (xl):</strong> ≥1200px (große Desktops)</li>
<li><strong>Extra Extra Large (xxl):</strong> ≥1400px (sehr große Bildschirme)</li>
</ul>
<p><strong>Was für jeden Breakpoint dokumentiert sein sollte:</strong></p>
<ul>
<li><strong>Layout-Änderungen:</strong> Wie ändert sich das Grid-System?</li>
<li><strong>Komponenten-Anpassungen:</strong> Welche Komponenten werden anders dargestellt?</li>
<li><strong>Navigation:</strong> Wie funktioniert die Navigation auf verschiedenen Bildschirmgrößen?</li>
<li><strong>Typography:</strong> Wie ändern sich Schriftgrößen?</li>
<li><strong>Spacing:</strong> Wie ändern sich Abstände zwischen Elementen?</li>
</ul>
<p><strong>Beispiel-Breakpoint-Dokumentation (Bootstrap-Standard):</strong></p>
<pre><code>Extra Small (xs) - &#x3C;576px
├── Navigation: Hamburger-Menü, Full-Screen Overlay
├── Grid: 1 Spalte, Padding: 16px
├── Typography: H1: 32px, Body: 14px
└── Komponenten: Stack-Layout, volle Breite

Small (sm) - ≥576px
├── Navigation: Hamburger-Menü, Side-Drawer
├── Grid: 1-2 Spalten, Padding: 20px
├── Typography: H1: 36px, Body: 15px
└── Komponenten: Angepasstes Layout, größere Touch-Targets

Medium (md) - ≥768px
├── Navigation: Hamburger-Menü, Side-Drawer oder kompaktes Menü
├── Grid: 2 Spalten, Padding: 24px
├── Typography: H1: 40px, Body: 16px
└── Komponenten: 2-Spalten-Layout möglich

Large (lg) - ≥992px
├── Navigation: Horizontales Menü, Dropdowns
├── Grid: 12 Spalten, Padding: 32px
├── Typography: H1: 48px, Body: 16px
└── Komponenten: Flex-Layout, verschiedene Breiten

Extra Large (xl) - ≥1200px
├── Navigation: Horizontales Menü mit erweiterten Features
├── Grid: 12 Spalten, max-width Container, Padding: 40px
├── Typography: H1: 48px, Body: 16px
└── Komponenten: Optimiertes Layout für große Bildschirme

Extra Extra Large (xxl) - ≥1400px
├── Navigation: Horizontales Menü, erweiterte Navigation
├── Grid: 12 Spalten, max-width Container, Padding: 48px
├── Typography: H1: 52px, Body: 18px
└── Komponenten: Maximale Breiten, optimierte Abstände
</code></pre>
<p><strong>Warum das wichtig ist:</strong>
Zu wenige Breakpoints führen zu Kompromissen bei der Umsetzung. Entwickler müssen dann selbst entscheiden, wie das Layout auf Bildschirmgrößen aussehen soll, die nicht im Design definiert sind. Die Verwendung der Bootstrap-Standard-Breakpoints bietet mehrere Vorteile: Sie sind bewährt, gut dokumentiert und werden von vielen Entwicklern bereits verstanden. Zudem ermöglichen sie eine konsistente Umsetzung, besonders wenn Bootstrap oder ähnliche Frameworks verwendet werden. Mehr Breakpoints bedeuten mehr Kontrolle und präzisere Umsetzung über alle Geräteklassen hinweg.</p>
<h3>5. Abstände zwischen Komponenten: Das unsichtbare Design-Element</h3>
<p>Spacing ist ein fundamentales Design-Element, das oft übersehen wird. Konsistente Abstände schaffen visuelle Hierarchie und verbessern die Lesbarkeit.</p>
<p><strong>Was dokumentiert sein sollte:</strong></p>
<ul>
<li><strong>Spacing-System:</strong> Ein konsistentes Spacing-System (z.B. 4px, 8px, 16px, 24px, 32px, 48px, 64px)</li>
<li><strong>Komponenten-Abstände:</strong> Spezifische Abstände zwischen verschiedenen Komponenten</li>
<li><strong>Container-Padding:</strong> Innenabstände von Containern und Sections</li>
<li><strong>Responsive Spacing:</strong> Wie ändern sich Abstände auf verschiedenen Breakpoints?</li>
</ul>
<p><strong>Beispiel-Spacing-System:</strong></p>
<pre><code class="language-css">--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
--spacing-2xl: 48px;
--spacing-3xl: 64px;
--spacing-4xl: 96px;
</code></pre>
<p><strong>Anwendungsbeispiele:</strong></p>
<ul>
<li>Abstand zwischen Buttons: <code>--spacing-md</code> (16px)</li>
<li>Abstand zwischen Sections: <code>--spacing-3xl</code> (64px)</li>
<li>Padding in Cards: <code>--spacing-lg</code> (24px)</li>
<li>Abstand zwischen Form-Elementen: <code>--spacing-md</code> (16px)</li>
</ul>
<p><strong>Warum das wichtig ist:</strong>
Inkonsistente Abstände führen zu einem unprofessionellen Erscheinungsbild. Ein dokumentiertes Spacing-System ermöglicht konsistente Umsetzung und erleichtert die Wartung.</p>
<h2>Die größten Herausforderungen: Navigation und Mobile-Design</h2>
<h3>Navigation mit Hover-Effekten: Ein häufiges Problem</h3>
<p>Die Navigation ist oft einer der komplexesten Bereiche einer Website, besonders wenn Hover-Effekte und Dropdown-Menüs involviert sind. In der Vergangenheit haben wir häufig das Problem gehabt, dass viele Informationen nicht vorhanden waren oder nicht durchdacht waren.</p>
<p><strong>Was bei Navigation-Designs häufig fehlt:</strong></p>
<ul>
<li><strong>Hover-Zustände:</strong> Wie sehen Hover-Effekte genau aus? Welche Animationen werden verwendet?</li>
<li><strong>Dropdown-Verhalten:</strong> Wie öffnen sich Dropdowns? Gibt es Animationen? Wie schließen sie sich?</li>
<li><strong>Mobile-Navigation:</strong> Wie funktioniert die Navigation auf mobilen Geräten? Gibt es ein Hamburger-Menü? Wie sieht das aus?</li>
<li><strong>Aktive Zustände:</strong> Wie wird der aktive Menüpunkt markiert?</li>
<li><strong>Übergänge:</strong> Welche Transitions werden verwendet?</li>
</ul>
<p><strong>Was eine vollständige Navigation-Dokumentation enthalten sollte:</strong></p>
<pre><code>Desktop Navigation
├── Default-Zustand
│   ├── Logo-Position und Größe
│   ├── Menüpunkt-Styling (Font, Größe, Farbe)
│   └── Abstände zwischen Menüpunkten
├── Hover-Zustand
│   ├── Hintergrundfarbe oder Unterstreichung
│   ├── Textfarbe-Änderung
│   ├── Transition-Dauer und Easing
│   └── Cursor-Änderung
├── Dropdown-Menü
│   ├── Trigger-Verhalten (Hover vs. Click)
│   ├── Dropdown-Position und Ausrichtung
│   ├── Dropdown-Styling (Hintergrund, Schatten, Border)
│   ├── Animation (Fade-in, Slide-down, etc.)
│   └── Sub-Menü-Verhalten
└── Aktiver Zustand
    ├── Visuelle Markierung
    └── Styling-Unterschiede

Mobile Navigation
├── Hamburger-Menü
│   ├── Icon-Position und Größe
│   ├── Animation beim Öffnen/Schließen
│   └── Icon-Transformation (zu X)
├── Off-Canvas-Menü
│   ├── Slide-Richtung (von links, rechts, oben)
│   ├── Hintergrund-Overlay
│   ├── Menü-Breite und Position
│   └── Animation und Transition
└── Menü-Items
    ├── Layout (Liste, Grid, etc.)
    ├── Abstände zwischen Items
    └── Touch-Target-Größen (mindestens 44x44px)
</code></pre>
<p><strong>Warum das wichtig ist:</strong>
Die Navigation ist oft der erste Kontaktpunkt für Nutzer. Unklare Navigation-Designs führen zu Verzögerungen in der Umsetzung und können die User Experience erheblich beeinträchtigen. Besonders problematisch ist, wenn Desktop-Designs vorhanden sind, aber Mobile-Varianten fehlen.</p>
<h3>Mobile-Umsetzung: Oft vernachlässigt, aber kritisch</h3>
<p>Die Mobile-Umsetzung ist einer der häufigsten Schmerzpunkte im Design-Handoff. Viele Designer liefern nur Desktop-Designs und erwarten, dass Entwickler selbst entscheiden, wie die Mobile-Variante aussehen soll.</p>
<p><strong>Häufige Probleme bei Mobile-Designs:</strong></p>
<ul>
<li><strong>Fehlende Mobile-Designs:</strong> Nur Desktop-Designs werden geliefert</li>
<li><strong>Unvollständige Mobile-Designs:</strong> Einige Seiten haben Mobile-Designs, andere nicht</li>
<li><strong>Unklare Breakpoints:</strong> Es ist nicht klar, ab welcher Bildschirmgröße welche Variante verwendet wird</li>
<li><strong>Touch-Interaktionen:</strong> Keine Spezifikationen für Touch-Gesten und Interaktionen</li>
<li><strong>Performance:</strong> Keine Berücksichtigung von Ladezeiten und Performance auf mobilen Geräten</li>
</ul>
<p><strong>Was eine vollständige Mobile-Dokumentation enthalten sollte:</strong></p>
<ul>
<li><strong>Mobile-First-Ansatz:</strong> Designs für kleine Bildschirme zuerst</li>
<li><strong>Touch-Targets:</strong> Mindestgrößen für klickbare Elemente (44x44px)</li>
<li><strong>Navigation:</strong> Detaillierte Mobile-Navigation-Designs</li>
<li><strong>Komponenten-Anpassungen:</strong> Wie sehen Komponenten auf Mobile aus?</li>
<li><strong>Layout-Änderungen:</strong> Welche Layout-Änderungen sind auf Mobile nötig?</li>
<li><strong>Interaktionen:</strong> Touch-Gesten, Swipe-Verhalten, etc.</li>
</ul>
<p><strong>Beispiel-Mobile-Spezifikationen:</strong></p>
<pre><code>Mobile Navigation
├── Hamburger-Menü
│   ├── Position: Top-Left oder Top-Right
│   ├── Größe: 44x44px (Touch-Target)
│   ├── Icon: 3 Linien, Animation zu X beim Öffnen
│   └── Z-Index: Über allen anderen Elementen
├── Off-Canvas-Menü
│   ├── Slide-Richtung: Von links
│   ├── Breite: 80% der Bildschirmbreite, max. 320px
│   ├── Hintergrund: Weiß mit Schatten
│   ├── Overlay: Dunkler Hintergrund mit 50% Opacity
│   └── Animation: 300ms ease-in-out
└── Menü-Items
    ├── Layout: Vertikale Liste
    ├── Padding: 16px pro Item
    ├── Font-Size: 18px (größer für bessere Lesbarkeit)
    └── Touch-Target: Mindestens 44px Höhe

Mobile Komponenten
├── Buttons: Volle Breite oder angepasste Größe
├── Forms: Stack-Layout, größere Input-Felder
├── Cards: Volle Breite, angepasste Padding
└── Images: Responsive, optimiert für Mobile
</code></pre>
<p><strong>Warum das wichtig ist:</strong>
Mobile-Nutzung übersteigt in vielen Projekten die Desktop-Nutzung. Fehlende Mobile-Designs führen zu Verzögerungen, da Entwickler selbst entscheiden müssen, wie die Mobile-Variante aussehen soll. Eine gründliche Mobile-Umsetzung ist essentiell für den Projekterfolg.</p>
<h2>Best Practices für einen erfolgreichen Design-Handoff</h2>
<h3>1. Strukturierte Figma-Datei</h3>
<p>Eine gut organisierte Figma-Datei erleichtert die Arbeit erheblich. Entwickler sollten schnell finden können, was sie suchen.</p>
<p><strong>Empfohlene Struktur:</strong></p>
<pre><code>Design System
├── Colors
│   ├── Primary
│   ├── Secondary
│   ├── Neutrals
│   └── States
├── Typography
│   ├── Headings
│   ├── Body
│   └── Special
├── Components
│   ├── Buttons (alle Zustände)
│   ├── Forms
│   ├── Cards
│   └── Navigation
├── Spacing
│   └── Spacing System
├── Breakpoints
│   └── Responsive Guidelines
└── Pages
    ├── Desktop
    │   ├── Homepage
    │   ├── About
    │   └── Contact
    └── Mobile
        ├── Homepage
        ├── About
        └── Contact
</code></pre>
<h3>2. Kommentare und Anmerkungen</h3>
<p>Kommentare in Figma können wichtige Kontextinformationen liefern, die im Design nicht offensichtlich sind.</p>
<p><strong>Was kommentiert werden sollte:</strong></p>
<ul>
<li><strong>Interaktionen:</strong> Wie funktionieren Hover-Effekte, Animationen, etc.?</li>
<li><strong>Zustände:</strong> Welche Zustände gibt es und wann werden sie verwendet?</li>
<li><strong>Breakpoints:</strong> Ab welcher Bildschirmgröße ändert sich das Layout?</li>
<li><strong>Spezielle Anforderungen:</strong> Accessibility-Features, Performance-Überlegungen, etc.</li>
</ul>
<h3>3. Export-Optionen</h3>
<p>Entwickler benötigen Assets in verschiedenen Formaten und Größen. Eine klare Export-Strategie spart Zeit.</p>
<p><strong>Empfohlene Export-Formate:</strong></p>
<ul>
<li><strong>Icons:</strong> SVG (skalierbar, kleine Dateigröße)</li>
<li><strong>Bilder:</strong> WebP oder optimiertes JPG/PNG</li>
<li><strong>Logos:</strong> SVG für Vektorgrafiken, PNG für Rastergrafiken</li>
<li><strong>Farben:</strong> Hex-Werte oder CSS-Variablen</li>
</ul>
<h2>Häufige Fehler und wie man sie vermeidet</h2>
<h3>Fehler 1: Nur Desktop-Designs liefern</h3>
<p><strong>Problem:</strong> Viele Designer liefern nur Desktop-Designs und erwarten, dass Entwickler die Mobile-Variante selbst entwickeln.</p>
<p><strong>Lösung:</strong> Immer Mobile-Designs mitliefern, mindestens für die wichtigsten Seiten. Wenn Zeit knapp ist, zumindest die Navigation und Hauptkomponenten auf Mobile designen.</p>
<h3>Fehler 2: Fehlende Zustandsdefinitionen</h3>
<p><strong>Problem:</strong> Komponenten werden nur im Default-Zustand gezeigt, Hover, Fokus und andere Zustände fehlen.</p>
<p><strong>Lösung:</strong> Alle Zustände dokumentieren. Wenn Zeit knapp ist, zumindest die wichtigsten Zustände (Hover, Fokus, Disabled) definieren.</p>
<h3>Fehler 3: Unklare Breakpoints</h3>
<p><strong>Problem:</strong> Es ist nicht klar, ab welcher Bildschirmgröße welche Variante verwendet wird.</p>
<p><strong>Lösung:</strong> Breakpoints explizit definieren und dokumentieren. Kommentare in Figma verwenden, um Breakpoint-Änderungen zu markieren.</p>
<h3>Fehler 4: Inkonsistente Spacing</h3>
<p><strong>Problem:</strong> Abstände werden "nach Gefühl" gesetzt, ohne konsistentes System.</p>
<p><strong>Lösung:</strong> Ein Spacing-System definieren und konsequent anwenden. Dies kann auch nachträglich dokumentiert werden.</p>
<h3>Fehler 5: Fehlende Navigation-Spezifikationen</h3>
<p><strong>Problem:</strong> Navigation wird nur visuell gezeigt, ohne Details zu Hover-Effekten, Dropdowns oder Mobile-Varianten.</p>
<p><strong>Lösung:</strong> Navigation gründlich durchdenken und alle Interaktionen dokumentieren. Besonders Mobile-Navigation nicht vernachlässigen.</p>
<h2>Fazit: Investition in Qualität zahlt sich aus</h2>
<p>Ein durchdachter Design-Handoff ist eine Investition, die sich mehrfach auszahlt. Die Zeit, die Designer in die vollständige Dokumentation investieren, spart Entwicklern Wochen an Arbeit und führt zu besseren Ergebnissen.</p>
<p><strong>Die wichtigsten Takeaways:</strong></p>
<ul>
<li><strong>Vollständigkeit:</strong> Alle Zustände, Varianten und Breakpoints dokumentieren</li>
<li><strong>Mobile-First:</strong> Mobile-Designs nicht vernachlässigen</li>
<li><strong>Struktur:</strong> Design-Dateien gut organisieren</li>
<li><strong>Kommunikation:</strong> Kommentare und Anmerkungen für Kontext nutzen</li>
<li><strong>Konsistenz:</strong> Design-Systeme und Spacing-Systeme definieren</li>
</ul>
<p>Ein guter Design-Handoff ist wie eine detaillierte Landkarte – er zeigt Entwicklern nicht nur das Ziel, sondern auch alle wichtigen Wegmarken und Hindernisse auf dem Weg dorthin. Mit einer vollständigen Dokumentation können Entwickler effizient arbeiten und hochwertige Ergebnisse liefern.</p>
<hr>
<p><em>Hast du Erfahrungen mit Design-Handoffs gemacht? Teile deine Erkenntnisse in den Kommentaren und lass uns wissen, welche Informationen für dich am wichtigsten sind!</em></p>
<hr>
<p>:GlobalDesignHandoffChecklist</p>
<h2>FAQ – Häufige Fragen zum Design-Handoff</h2>
<h3>1. Welche Informationen benötigen Entwickler für einen erfolgreichen Design-Handoff?</h3>
<p>Entwickler benötigen ein durchdachtes Figma-File oder ähnliches mit Farbpaletten, Komponenten in verschiedenen Zuständen (Hover, Fokus, Disabled), Typography-Definitionen, mindestens 6 Breakpoints (Bootstrap-Standard: xs, sm, md, lg, xl, xxl), Abstände zwischen Komponenten, sowie detaillierte Navigation-Designs inklusive Mobile-Varianten. Besonders wichtig sind durchdachte Hover-Effekte und eine gründliche Mobile-Umsetzung.</p>
<h3>2. Warum ist die Mobile-Umsetzung beim Design-Handoff so wichtig?</h3>
<p>Die Mobile-Umsetzung ist kritisch, weil viele Designer nur Desktop-Designs liefern. Entwickler benötigen jedoch detaillierte Mobile-Varianten für Navigation, Komponenten-Layouts und Interaktionen. Fehlende Mobile-Designs führen zu Verzögerungen, da Entwickler selbst entscheiden müssen, wie Elemente auf kleinen Bildschirmen dargestellt werden sollen.</p>
<h3>3. Wie viele Breakpoints sollten im Design definiert sein?</h3>
<p>Im Optimalfall sollten 6 Breakpoints definiert sein, basierend auf dem Bootstrap-Standard: Extra Small (xs) &#x3C;576px, Small (sm) ≥576px, Medium (md) ≥768px, Large (lg) ≥992px, Extra Large (xl) ≥1200px und Extra Extra Large (xxl) ≥1400px. Diese Breakpoints bieten eine präzise responsive Umsetzung und sind bewährt, gut dokumentiert und werden von vielen Entwicklern bereits verstanden. In der Praxis werden oft nur 2-3 Breakpoints geliefert, was zu Kompromissen bei der Umsetzung führt. Mehr Breakpoints bedeuten mehr Kontrolle über das Layout auf verschiedenen Bildschirmgrößen.</p>
<h3>4. Was gehört zu einer vollständigen Komponenten-Dokumentation?</h3>
<p>Eine vollständige Komponenten-Dokumentation sollte alle Zustände einer Komponente enthalten: Default, Hover, Fokus, Disabled, Active, Error und Success. Zusätzlich sollten Varianten für verschiedene Größen, Farben oder Kontexte dokumentiert sein. Dies ermöglicht Entwicklern eine präzise Umsetzung ohne Rückfragen.</p>
<h3>5. Warum sind Hover-Effekte bei der Navigation problematisch?</h3>
<p>Hover-Effekte bei der Navigation sind problematisch, weil sie oft nicht durchdacht sind. Entwickler benötigen klare Spezifikationen für Übergänge, Animationen, Dropdown-Verhalten und Mobile-Alternativen. Fehlende Informationen führen zu Verzögerungen und Kompromissen bei der Umsetzung.</p>
<h3>6. Welche Rolle spielt die Farbpalette im Design-Handoff?</h3>
<p>Die Farbpalette ist fundamental wichtig, da sie die visuelle Identität definiert. Entwickler benötigen nicht nur die Grundfarben, sondern auch Varianten für Hover-Zustände, Fehlerzustände, Erfolgsmeldungen und Disabled-Zustände. Eine vollständige Farbpalette mit Hex-Werten oder CSS-Variablen erleichtert die Umsetzung erheblich.</p>
<h3>7. Wie sollte eine Figma-Datei für den Handoff strukturiert sein?</h3>
<p>Eine gut strukturierte Figma-Datei sollte klare Bereiche für Design System (Farben, Typography, Komponenten), Spacing-System, Breakpoints und Seiten-Designs (Desktop und Mobile) haben. Kommentare und Anmerkungen helfen, Kontext zu vermitteln. Eine logische Ordnerstruktur erleichtert Entwicklern die Navigation.</p>]]></content:encoded>
            <category>Projekt Management</category>
            <category>Entwicklung</category>
            <enclosure url="https://blueshoe.de/img/blog/design_handoff.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Hands on: Eine Einführung in Flutter.]]></title>
            <link>https://blueshoe.de/blog/hands-on-flutter</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/hands-on-flutter</guid>
            <pubDate>Mon, 09 Sep 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Heute nehmen wir euch mit auf eine spannende Reise in die Welt der mobilen App-Entwicklung mit Flutter. Wir stellen euch „blue_todo“ vor, eine kleine aber elegante ToDo-App, die zeigt, wie leistungsstark und vielseitig Flutter sein kann. In diesem Artikel bekommt ihr nicht nur einen Einblick in die Basics von Flutter sondern auch einen kleinen Deep Dive in Best Practices. Lasst uns gemeinsam einen Blick auf die Technik und das Design hinter „blue_todo“ werfen!</p>
<p><img src="/img/blogs/flutter.svg" alt="Flutter bei Blueshoe: Hands on.">{.object-cover .max-w-full .mb-5}</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Einführung
:::
:::GlobalParagraph
Hattest du schon einmal den Drang, Code zu schreiben und ihn überall auszuführen? Wenn ja, dann hast du wahrscheinlich schon Frameworks wie React Native und Flutter ausprobiert. Und wenn nicht, dann ist dieser Beitrag genau das Richtige für dich!
:::
:::GlobalParagraph
In diesem Artikel werden wir uns mit der erstaunlichen und sich schnell entwickelnden Welt (fast so schnell wie die Entwicklung von JS-Frameworks) der Multiplattform-Frameworks beschäftigen. Das Vorzeigebeispiel heute: Flutter.
:::
:::GlobalParagraph
Ich beginne mit einer kurzen Zusammenfassung der Geschichte von Flutter und den Vorteilen. Dann leite ich über zu einem kleinen App-Beispiel, das nativ auf Linux läuft und beende den Artikel mit einer kleinen Anmerkung, warum du für dein nächstes App-Projekt zu uns kommen solltest.
:::</p>
<p>:::GlobalButton{:url="/technologien/flutter/" :label="Erfahre mehr über unsere Flutter-Entwicklungsdienste" :color="blue" .mb-6}
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Geschichte und Vorteile
:::
:::GlobalParagraph
Nach ersten Entwürfen bereits im Jahr 2015 wurde Flutter 2017 mit Unterstützung für Android und iOS veröffentlicht. Im Jahr 2019 kündigte das Flutter-Team Unterstützung für Web- und Desktop-Plattformen an. Seitdem sind das Flutter-Framework selbst und sein Ökosystem gewachsen und haben sich zu einer wirklich einheitlichen Erfahrung auf allen Plattformen entwickelt. Einige der jüngsten Verbesserungen sind die neue Impeller-Rendering-Engine und Webassembly-Unterstützung für Web.
:::
:::GlobalParagraph{.mb-5}
Flutter ist eine Neuheit in der Welt der UI-Frameworks! Es liefert Anwendungen mit einer eigenen Rendering-Engine, die Pixeldaten direkt auf dem Bildschirm ausgibt. Dies steht im Gegensatz zu vielen anderen Frameworks, die sich auf die Zielplattform verlassen, um eine Rendering-Engine bereitzustellen. Native Android-Apps sind auf das Android-SDK auf Geräteebene angewiesen, während React Native dynamisch den integrierten UI-Stack der Zielplattform nutzt. Die Kontrolle über die Rendering-Pipeline von Flutter ist ein entscheidender Faktor für die Unterstützung mehrerer Plattformen. Sie ermöglicht es Entwicklern, identischen UI-Code für alle Zielplattformen zu verwenden, was die Erstellung plattformübergreifender Anwendungen einfacher denn je macht!
:::</p>
<p><img src="/img/blogs/Google-flutter-logo.svg" alt="Flutter logo">{.mx-auto .h-48 .max-w-full}</p>
<p>:::GlobalParagraph{.mt-5}
Darüber hinaus bietet das Framework mit einem JIT-Compiler, der Codeänderungen in Sekundenschnelle neu verarbeitet, eine hervorragende DX. Dies ermöglicht schnelles Prototyping und einfaches Ausprobieren von Funktionalitäten.
:::
:::GlobalParagraph
Ein weiterer Vorteil ist, dass Flutter auf der Sprache Dart aufbaut. Diese Sprache bietet eine typsichere Umgebung. Sie nutzt eine Mischung aus statischer Typprüfung und Typinferenz, wobei die Typannotationen optional sind. Der Dart-Compiler verfügt außerdem über eine eingebaute Null-Prüfung, die alle lästigen Null-/None-Fehler direkt in der IDE abfängt.
:::
:::GlobalParagraph
Und nicht zuletzt: Flutter hat eine großartige Community mit einer Vielzahl von Plugins, die über den eingebauten Paketmanager pub einfach genutzt werden können.
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Flutter Hands-On
:::
:::GlobalParagraph
Für diesen Blogartikel möchte ich eine kleine ToDo-App (was denn sonst?) bauen, die nativ unter Linux läuft. Flutter hat eine sehr gute Integration mit Firebase (Googles Plattform zur einfachen Entwicklung von Apps) oder anderen ähnlichen Cloud Infrastructure as a Service (CIaaS) Produkten wie Supabase. Aber für dieses Beispiel möchte ich, dass die App eine lokale Datenbank verwendet. Nämlich eine meiner Lieblings-NoSQL-Datenbanken: ObjectBox. Sie unterstützt Dart / Flutter und ist gebaut für Geschwindigkeit.
:::</p>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Flutter-Einrichtung
:::
:::GlobalParagraph
Bevor du mit der Entwicklung deiner Apps beginnen kannst, muss das Flutter-SDK installiert werden. Dies ist von Plattform zu Plattform unterschiedlich, aber ein guter Startpunkt ist die <a href="https://docs.flutter.dev/get-started/install">SDK Installations Seite</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} des Flutter Projekts. Falls du so faul bist wie ich, empfehle ich version-fox. Das ist ein SDK-Versionsmanager geschrieben in GoLang. Nach der Installation und Konfiguration von <a href="https://vfox.lhan.me/">version-fox</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}, kannst du folgende Befehle ausführen um das Flutter Plugin hinzuzufügen und das SDK zu installieren:
:::</p>
<p>::BlogCode{.mb-5}</p>
<pre><code class="language-bash">vfox add flutter
vfox search flutter // this will open a selection with all available flutter versions
vfox use -g flutter@{use your version here}

</code></pre>
<p>::</p>
<p>:::GlobalParagraph
Nach dem Ausführen dieser Befehle kann es schon losgehen! Um zu überprüfen ob alles geklappt hat kannst du folgenden Befehl ausführen:
:::</p>
<p>::BlogCode{.mb-5}</p>
<pre><code class="language-bash">flutter doctor -v

</code></pre>
<p>::</p>
<p>:::GlobalParagraph
Dieser Befehl wird einige Informationen über deine aktuelle Flutter Installation ausgeben. Abhängig von deiner Plattform musst du eventuell einige Entwicklungspakete installieren. Bitte konsultiere die SDK Installationsseite, um die richtigen Pakete zu finden.
:::</p>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Projekt Start
:::
:::GlobalParagraph
Der gesamte, für dieses Projekt erstellte, Code kann in diesem <a href="https://github.com/Blueshoe/blue_todo">Github Repository</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} gefunden werden.
:::
:::GlobalParagraph
Flutter hat (wie <em>Django</em>) Management Befehle, um schnell Projekte und andere Templates, wie Plugins zu erstellen. Folgender Befehl kann genutzt werden um ein neues Projekt mit dem Base Template zu erstellen:</p>
<p>:::</p>
<p>::BlogCode{.mb-5}</p>
<pre><code class="language-bash">flutter create --org de.blueshoe blue_todo

</code></pre>
<p>::
:::GlobalParagraph
Dieser Befehl kreiert ein neues Projekt mit der folgenden Ordnerstruktur in blue_todo:
:::
<img src="/img/blogs/flutter1.png" alt="Order Struktur erzeugt durch Flutter">{.mx-auto .object-cover .max-w-full}</p>
<p>:::GlobalParagraph
Wie du sehen kannst, hat Flutter automatisch Verzeichnisse für alle unterstützten Plattformen erstellt. Wir werden nur das <em>lib-</em> und das <em>linux</em>-Verzeichnis verwenden. <em>lib</em> enthält den dart- und plattform unspezifischen Code, während <em>linux</em> den plattformspezifischen Linux Code enthält. Flutter bietet die Kommunikation mit nativen Funktionen und Bibliotheken über sogenannte „Platform-Channels“. Diese sind ein mächtiges Werkzeug, um native Funktionalität und Leistung zu nutzen. Eine Erklärung dieser würde allerdings den Rahmen dieses Artikels sprengen. Aber vielleicht kommt ja bald ein neuer Artikel dazu?
:::</p>
<p>:::GlobalParagraph
Als nächstes installieren wir ein paar Abhängigkeiten! Du kannst auf <a href="https://pub.dev">pub.dev</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} nach diesen suchen.
:::
::BlogCode{.mb-5}</p>
<pre><code class="language-bash">flutter pub add objectbox objectbox_flutter_libs:any flutter_bloc gap intl
flutter pub add --dev build_runner objectbox_generator:any
</code></pre>
<p>::
:::GlobalParagraph
Dadurch werden die erforderlichen <code>ObjectBox</code> Abhängigkeiten sowie <code>build_runner</code> hinzugefügt, mit dem ObjectBox Code generieren kann. Außerdem wird <code>flutter_bloc</code> installiert, eine <em>Business Logic Controller (BLoC)</em> Implementierung für Flutter. BLoC ermöglicht eine einfacheres und besseres State Management (wir kommen später darauf zu sprechen, was State Mangement genau ist).
Außerdem werden die <code>Gap</code>- und <code>Intl</code>-Abhängigkeiten installiert. Gap ist ein sehr hilfreiches Widget zum Anordnen von Widgets in einer Spalte oder Zeile. Und <code>intl</code> ermöglicht das Formatieren eines Datums.
:::</p>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .pt-10 .mb-5}
Projekt Implementierung
:::
:::GlobalParagraph
Tauchen wir nun in den Code ein! Die Standarddatei <code>main.dart</code> enthält die folgende Implementierung einer Counter-App:
:::</p>
<p>::BlogCode{.mb-5}</p>
<pre><code class="language-dart">import 'package:flutter/material.dart';

void main() {
 runApp(const MyApp());
}

class MyApp extends StatelessWidget {
 const MyApp({super.key});

 // This widget is the root of your application.
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     title: 'Flutter Demo',
     theme: ThemeData(
       colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
       useMaterial3: true,
     ),
     home: const MyHomePage(title: 'Flutter Demo Home Page'),
   );
 }
}

class MyHomePage extends StatefulWidget {
 const MyHomePage({super.key, required this.title});

 final String title;

 @override
 State&#x3C;MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State&#x3C;MyHomePage> {
 int _counter = 0;

 void _incrementCounter() {
   setState(() {
     _counter++;
   });
 }

 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       backgroundColor: Theme.of(context).colorScheme.inversePrimary,
       title: Text(widget.title),
     ),
     body: Center(
        child: Column(
         mainAxisAlignment: MainAxisAlignment.center,
         children: &#x3C;Widget>[
           const Text(
             'You have pushed the button this many times:',
           ),
           Text(
             '$_counter',
             style: Theme.of(context).textTheme.headlineMedium,
           ),
         ],
       ),
     ),
     floatingActionButton: FloatingActionButton(
       onPressed: _incrementCounter,
       tooltip: 'Increment',
       child: const Icon(Icons.add),
     ),
   );
 }
}

</code></pre>
<p>::</p>
<p>:::GlobalParagraph
Dieser Code deckt alle grundlegenden Prinzipien ab, wie man eine Flutter-Anwendung erstellt. Flutter basiert auf Widgets. Widgets sind die Bausteine der Benutzeroberfläche, und alles andere (Ihre Business-Logik) wird über den State gesteuert. State macht deine Anwendung reaktiv und interaktiv. Im Flutter-Beispiel haben wir einen FloatingActionButton (FAB) (ein Widget aus der MaterialDesign-Bibliothek). Der, wenn er gedrückt wird, eine Zählervariable inkrementiert. Das Framework ist dafür verantwortlich, seinen State neu zu generieren, wenn <em>setState</em> aufgerufen wird, und zeigt den inkrementierten Zähler an.
:::</p>
<p>:::GlobalParagraph
Dann lass uns doch mal <code>flutter run -d linux</code> ausführen und schauen wie das Beispiel aussieht:
:::</p>
<p><img src="/img/blogs/flutter2.png" alt="Eine App in Flutter">{.mx-auto .object-cover .max-w-full}</p>
<p>:::GlobalParagraph
Wie im Screenshot zu sehen, habe ich den FAB 5 Mal gedrückt und die Benutzeroberfläche wurde aktualisiert, um dies anzuzeigen. Flutter zeigt auch ein schönes Debug-Banner an, damit ich weiß, dass ich die Anwendung im Debug-Modus gestartet habe.
:::</p>
<p>:::GlobalParagraph
Beginnen wir nun mit der Implementierung unserer ToDo-App! Zuerst werde ich ein neues Verzeichnis namens <em>business_logic</em> erstellen. Dieses Verzeichnis wird unseren Flutter BLoC-Code beherbergen. Es folgen zwei weitere Verzeichnisse namens <em>models</em> und <em>components</em>. Bei einer größeren App sieht die Verzeichnisstruktur natürlich anders aus. Aber für dieses kleine Single Page Application Projekt ist das ausreichend.
Nach den Verzeichnissen fügen wir den Business Logic Code hinzu:
Wir werden in diesem Beispiel ein sogenanntes Cubit verwenden. Du kannst dir ein Cubit als einen weniger komplexen BLoC vorstellen. Zuerst müssen wir die States des Cubits definieren:
:::</p>
<p>::BlogCode{.mb-5}</p>
<pre><code class="language-dart">// todo_state.dart
part of 'todo_cubit.bloc.dart';

sealed class TodoState extends Equatable {
  const TodoState();

  @override
  List&#x3C;Object> get props => [];
}

final class TodoInitial extends TodoState {}

final class TodoLoading extends TodoState {}

final class TodoDone extends TodoState {
  final List&#x3C;Todo> todos;

  const TodoDone(this.todos);

  @override
  List&#x3C;Object> get props => [
        todos,
      ];
}

// in case anything goes wrong
final class TodoFailed extends TodoState {}

</code></pre>
<p>::</p>
<p>:::GlobalParagraph
States sind Klassen, die vom Cubit verwendet werden, um zwischen, nun ja, seinem aktuellen State zu unterscheiden. Das Ändern des States im Cubit wird sich in der Benutzeroberfläche widerspiegeln, und du solltest für verschiedene States unterschiedliche Dinge anzeigen.
:::
:::GlobalParagraph
Danach können wir unser Cubit hinzufügen:
:::</p>
<p>::BlogCode{.mb-5}</p>
<pre><code class="language-dart">// todo_cubit.bloc.dart
import 'package:blue_todo/models/todo.dart';
import 'package:blue_todo/objectbox.g.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

part 'todo_state.dart';

class TodoCubit extends Cubit&#x3C;TodoState> {
  final Box todoBox;

  TodoCubit({required this.todoBox}) : super(TodoInitial()) {
    loadTodos();
  }

  loadTodos() async {
    emit(TodoLoading());

    try {
      final todos = List&#x3C;Todo>.from(await todoBox.getAllAsync());
      emit(TodoDone(todos));
    } catch (ex) {
      emit(TodoFailed());
    }
  }

  addTodo(Todo todo) async {
    final state = this.state;

    if (state is TodoDone) {
      final id = await todoBox.putAsync(todo);
      todo.id = id;
      emit(
        TodoDone(
          [
            todo,
            ...state.todos,
          ],
        ),
      );
    }
  }

  deleteTodo(Todo toDelete) async {
    final state = this.state;

    if (state is TodoDone) {
      await todoBox.removeAsync(toDelete.id);
      emit(
        TodoDone(
          state.todos.where((t) => t.id != toDelete.id).toList(),
        ),
      );
    }
  }
}

</code></pre>
<p>::</p>
<p>:::GlobalParagraph
Es gibt eine Menge zu sehen, also schauen wir uns den Code Schritt für Schritt an.
Beginnen wir mit dem Cubit-Konstruktor. Er wird mit der Datenbank und dem Anfangs-State <em>TodoInitial</em> initiert. Dies kann ein beliebiger State sein, den Du definieren kannst. Er ruft auch <em>loadTodos</em> auf, sobald er initialisiert ist.
:::
:::GlobalParagraph
<em>loadTodos</em> “emitted” zuerst <em>TodoLoading</em>, was signalisieren soll, dass wir auf Daten warten oder etwas anderes im Hintergrund tun. <em>emit</em> ist die Methode, um States in einem BLoC/Cubit zu ändern. Dann holt <em>loadTodos</em> alle Todos aus der Datenbank und gibt den State <em>TodoDone</em> aus. Dieser State ist besonders, weil er die Todos speichert, die wir anzeigen wollen.
:::
:::GlobalParagraph
Es gibt zwei weitere Methoden im Cubit: <em>addTodo</em> und <em>deleteTodo</em>. Diese Methoden dienen zum Hinzufügen bzw. Entfernen eines Todos. Beide prüfen, ob wir uns im richtigen State befinden, bevor sie ihre jeweiligen Aktionen ausführen. Wenn der State nicht stimmt, wäre es klug, eine Fehlerbehandlung hinzuzufügen oder dem Benutzer einen Hinweis zu geben, dass etwas schief gelaufen ist.
:::
:::GlobalParagraph
Damit ist die gesamte Logik, die wir brauchen, fertig!
:::
:::GlobalParagraph
Jetzt können wir den UI-Code hinzufügen. Der komplette UI Code kann <a href="https://github.com/Blueshoe/blue_todo/blob/main/lib/main.dart">hier</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} eingesehen werden.
:::</p>
<p>:::GlobalParagraph
Ansonsten ist es am wichtigsten zu verstehen, wie wir <em>BlocProvider</em> und <em>BlocBuilder</em> benutzen können, um das UI, abhängig vom <em>TodoState</em>, zu verändern.
:::
:::GlobalParagraph
Lass uns deswegen den folgenden Code-Abschnitt ansehen:
:::</p>
<p>::BlogCode{.mb-5}</p>
<pre><code class="language-dart">BlocBuilder&#x3C;TodoCubit, TodoState>(builder: (context, state) {
  if (state is TodoInitial || state is TodoLoading) {
    return const Center(
      child: CircularProgressIndicator.adaptive(),
    );
  }

  if (state is TodoFailed) {
    return const Center(
      child: Text("Failed to load ToDo's :("),
    );
  }

  final doneState = state as TodoDone;

  if (doneState.todos.isEmpty) {
    return const Text(
      "No ToDo's added yet. Add some via the bottom right FloatingActionButton!",
    );
  }

  ...
}

</code></pre>
<p>::
:::GlobalParagraph
Wie du sehen kannst, wird das <em>BlocBuilder</em> Widget verwendet, um zwischen den <em>TodoStates</em> zu unterscheiden. Für jeden State geben wir etwas anderes und kontextbezogenes zurück. Du kannst auch auf andere Dinge als den State prüfen, z.B. ob die Todos leer sind.
:::
:::GlobalParagraph
Wenn keine der vorherigen Überprüfungen erfüllt ist, werden alle Todos mit dem folgenden Codeschnipsel angezeigt:
:::</p>
<p>::BlogCode{.mb-5}</p>
<pre><code class="language-dart">return Expanded(
        child: ListView.separated(
          itemCount: doneState.todos.length,
          itemBuilder: (context, index) => Card(
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: ListTile(
                title: Text(doneState.todos[index].title!),
                subtitle: Text(doneState.todos[index].description!),
                trailing: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Text(
                      "due at: ${DateFormat('dd.MM.yyyy').format(doneState.todos[index].dueDate!)}",
                    ),
                    const Gap(8.0),
                    IconButton(
                        onPressed: () => _deleteTodo(
                              context,
                              doneState.todos[index],
                            ),
                        icon: Icon(MdiIcons.trashCanOutline))
                  ],
                ),
              ),
            ),
          ),
          separatorBuilder: (context, index) => const Divider(),
        ),
      );

</code></pre>
<p>::
:::GlobalParagraph
<em>Expanded</em> ist ein Widget, das dem untergeordneten Widget mitteilt, dass es den gesamten verbleibenden Platz in der Hauptachse des übergeordneten Widgets einnehmen soll. In diesem Fall sollte die zugrunde liegende <em>ListView</em> den gesamten verbleibenden vertikalen Platz in der übergeordneten <em>Column</em> einnehmen.
:::
:::GlobalParagraph
Die <em>ListView</em> hat eine <em>Builder</em>-Eigenschaft, die für die Erstellung von Kindern der <em>ListView</em> und deren Anzeige verantwortlich ist. Die Kinder bestehen im Wesentlichen aus einer <em>ListTile</em>, einem Widget, das eine bestimmte Struktur für seine Kinder vorgibt. Auf diese Weise kannst Du einen Titel und einen Untertitel hinzufügen, die sich automatisch und immer untereinander auf der linken Seite des Widgets befinden. Die Eigenschaft <em>Trailing</em> dient zum Hinzufügen von Widgets am Ende der <em>ListTile</em>. Hier haben wir das Fälligkeitsdatum und einen Button zum Löschen hinzugefügt. Der Lösch-Button ruft <em>_deleteTodo</em> auf, was wiederum die <em>deleteTodo</em>-Methode des Cubits aufruft, wodurch der State geändert und die Benutzeroberfläche neu gezeichnet wird.
:::
:::GlobalParagraph
Ein weiterer interessanter Teil des Codes ist das Hinzufügen von ToDos. Wenn Du den FloatingActionButton drückst, erscheint ein Dialog, der einige Eingaben abfragt (z.B. Titel und Beschreibung) und wenn Du auf Speichern klickst, wird das ToDo an den Cubit übertragen und in der Datenbank gespeichert.
:::
:::GlobalParagraph
Die speziellen Widgets <em>BlueshoeTextfield</em> und <em>BlueshoeDatefield</em> sind im <a href="https://github.com/Blueshoe/blue_todo/tree/main/lib/components">Git Repository auf GitHub</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} zu finden.
:::
:::GlobalParagraph
Und wie sieht das alles aus? Hier einige Screenshots:
:::</p>
<p><img src="/img/blogs/flutter3.png" alt="Eine App in Flutter">{.mx-auto .object-cover .max-w-full}</p>
<p>:::GlobalParagraph
Ohne ToDo’s
:::</p>
<p><img src="/img/blogs/flutter4.png" alt="Eine App in Flutter">{.mx-auto .object-cover .max-w-full}</p>
<p>:::GlobalParagraph
Ein ToDo hinzufügen
:::</p>
<p><img src="/img/blogs/flutter5.png" alt="Eine App in Flutter">{.mx-auto .object-cover .max-w-full}</p>
<p>:::GlobalParagraph
Sobald ein ToDo hinzugefügt ist
:::
:::GlobalParagraph
Ich hoffe, dieses kurze Beispiel hat Dir gezeigt, dass man mit Flutter sehr schnell vielseitige und schöne Apps bauen kann!
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Blueshoe x Flutter
:::</p>
<p>:::GlobalParagraph
Bei Blueshoe streben wir nach höchster Performance und Usability in unseren Anwendungen. Mit Flutter können wir dieses Versprechen halten und alle Erwartungen unserer Kunden erfüllen.
:::</p>
<p>:::GlobalParagraph
Wenn Du jemanden für Deine nächste App suchst, dann zögere nicht, kontakt mit uns aufzunehmen!
:::</p>]]></content:encoded>
            <category>Entwicklung</category>
            <enclosure url="https://blueshoe.de/img/blogs/flutter.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Keycloak für Entwickler: So gelingt die Integration]]></title>
            <link>https://blueshoe.de/blog/keycloak-integration-fuer-entwickler</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/keycloak-integration-fuer-entwickler</guid>
            <pubDate>Mon, 21 Oct 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>In der heutigen digitalen Welt ist eine sichere Identitäts- und Zugriffsverwaltung (IAM) essenziell. Doch die Implementierung kann kompliziert sein – Keycloak macht es einfach! In diesem Artikel lernst du, wie du Keycloak mit SSO, OAuth2 &#x26; OpenID Connect in dein Projekt integrierst – egal ob Django, Kubernetes oder Node.js.</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Was ist Keycloak?
:::</p>
<p>::GlobalPodcastSection{:videoId="6b_DXrESzgY" :videoPosition="right" .mb-5}
:::GlobalParagraph
<a href="/technologien/keycloak-iam-loesung/">Keycloak</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} ist eine Open-Source IAM-Lösung, die Single Sign-On (SSO), Benutzerverwaltung und Multi-Faktor-Authentifizierung (MFA) ermöglicht.
:::
:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Vorteile für Entwickler:
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li><strong>Einfache Integration</strong> mit OAuth2, OpenID Connect &#x26; SAML</li>
<li><strong>Höchste Sicherheit</strong> mit MFA &#x26; rollenbasierter Zugriffskontrolle (RBAC)</li>
<li><strong>Skalierbar</strong> – für kleine Teams &#x26; große Unternehmen
:::
:::GlobalButton{:url="/technologien/keycloak-iam-loesung/" :label="Jetzt Keycloak mit Blueshoe in dein Projekt einbinden" :color="blue" .mb-6}
:::
::</li>
</ul>
<p>:::GlobalTitle{:size="lg" .mb-5}
Keycloak Integration: So bindest du Keycloak ein
:::</p>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Schritt 1: Keycloak-Server einrichten
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>Lade Keycloak als Docker-Container oder installiere es manuell.</li>
<li>Erstelle eine Realm für dein Projekt.</li>
<li>Lege Benutzer und Rollen im Keycloak Admin-Panel an.
:::</li>
</ul>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Schritt 2: Keycloak mit deiner Anwendung verbinden
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li><strong><a href="/technologien/python-django-agentur/">Django</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}:</strong> Nutze <code>pycloak</code> für eine einfache OAuth2-Anbindung.</li>
<li><strong>Spring Boot:</strong> Verwende den Keycloak-Adapter für Spring Security.</li>
<li><strong>Node.js:</strong> Integriere Keycloak mit <code>passport-keycloak</code> für Authentifizierung.
:::</li>
</ul>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Schritt 3: SSO &#x26; Token-Authentifizierung nutzen
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>Implementiere <strong>Single Sign-On (SSO)</strong> mit OpenID Connect.</li>
<li>Nutze <strong>Access Tokens</strong> für API-Sicherheit.</li>
<li>Setze <strong>Multi-Faktor-Authentifizierung (MFA)</strong> für höhere Sicherheit ein.
:::</li>
</ul>
<p>:::GlobalTitle{:size="lg" .mb-5}
Best Practices für eine sichere Keycloak-Implementierung
:::</p>
<p>:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li><strong>SSL/TLS aktivieren:</strong> Alle Verbindungen zu Keycloak müssen verschlüsselt sein.</li>
<li><strong>Rollenbasierten Zugriff nutzen:</strong> RBAC (Role-Based Access Control) verhindert unbefugten Zugriff.</li>
<li><strong>Token-Lebensdauer begrenzen:</strong> Verhindert Missbrauch abgelaufener Authentifizierungsdaten.</li>
<li><strong>Regelmäßige Updates:</strong> Halte Keycloak auf dem neuesten Stand für maximale Sicherheit.
:::</li>
</ul>
<p>:::GlobalParagraph
<strong>Tipp:</strong> Mit unserem <a href="/leistungen">RAPID-Framework</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} setzen wir Keycloak <strong>35% schneller</strong> um
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Praxisbeispiel: Keycloak in Aktion
:::</p>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Integration in bestehende Systeme
:::
:::GlobalParagraph
Die Integration von Keycloak in bestehende Systeme ist unkompliziert, da es flexible API-Schnittstellen bietet und sich gut in Frameworks wie Spring Boot, Django, Node.js und viele andere Technologien integrieren lässt. Egal, ob du eine Cloud-basierte Infrastruktur oder On-Premise-Systeme betreibst, Keycloak passt sich an die Anforderungen deines Unternehmens an. Dank seiner Open-Source-Natur ist es zudem vollständig anpassbar. Zum Beispiel haben wir bei Blueshoe ein <a href="https://github.com/Blueshoe/keycloak-theme-template">Cookie-Cutter Template</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} für die einfache Anpassung des Keycloak Themes erstellt.
:::</p>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Praxisbeispiel: FondsFinanz Makler Bildungsplattform
:::
:::GlobalParagraph
Ein konkretes Beispiel für eine erfolgreiche Implementierung von Keycloak ist die Backoffice-Welt der <strong>FondsFinanz Makler Bildungsplattform</strong>, eine Django-basierte Kubernetes-Anwendung mit verschiedenen Services. Hier verwaltet eine zentrale Keycloak-Instanz die Authentifizierung für alle Dienste und sorgt so für eine einheitliche und sichere Benutzerverwaltung. Dieses Setup ermöglicht den Entwicklern, sich auf die Geschäftslogik zu konzentrieren, während Keycloak sich um die Sicherheit kümmert.
:::
:::GlobalParagraph
Hierbei spielt auch <a href="https://github.com/Blueshoe/pycloak">pycloak</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} eine große Rolle. Durch <em>pycloak</em> konnten wir Keycloak mittels des <a href="https://oauth2-proxy.github.io/oauth2-proxy/">oauth2-proxy Projekts</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} nahtlos in die Django-Anwendung integrieren.
:::</p>
<p><img src="https://raw.githubusercontent.com/Blueshoe/pycloak/refs/heads/main/docs/static/img/pycloak-arch.png" alt="pycloak-flow"></p>
<p>:::GlobalParagraph
Die obige Abbildung zeigt den Flow, wie pycloak die Authentifizierung, mittels des <em>oauth2-proxys</em>, in der Django-Anwendung umsetzt. Die Client-Andwendung navigiert zuerst zur gewünschten Resource und wird vom <em>oauth2-proxy</em> abgefangen. Dieser leitet (falls die Client-Anwendung nicht bereits authentifiziert ist) den Benutzer zur Keycloak-Login-Seite weiter. Hier muss sich der Benutzer authentifizieren und kann anschließend mit dem ausgegeben Access-Token die gewünschte Anwendung aufrufen.
:::</p>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Praxisbeispiel: LUMA Delikatessen
:::
:::GlobalParagraph
Ein weiteres Beispiel ist die <a href="https://www.luma-delikatessen.ch/"><strong>LUMA Delikatessen</strong>-Website</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}, die auf Nuxt.js und Django basiert. Hier wird Keycloak ebenfalls als zentrale Authentifizierungslösung genutzt, um die Sicherheit und Benutzerfreundlichkeit zu gewährleisten. So haben wir zum Beispiel mittels unserem <a href="https://github.com/Blueshoe/keycloak-theme-template">keycloak-theme-template</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} die Registrierungsseite und die standardmäßig verwendeten Keycloak Felder angepasst. Unter anderem wurde die Frage nach dem Geburtsdatum hinzugefügt, um etwaige Rabattaktionen zu ermöglichen. Das ganze sieht dann so aus:
:::
<img src="/img/blogs/luma-register.png" alt="LUMA Delikatessen">{height="70%"}</p>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Praxisbeispiel: LMU Munich Media Monitoring
:::
:::GlobalParagraph
Keycloak bietet auch eine API an, die wir für das <a href="https://m3.ifkw.lmu.de/">Munich Media Monitoring</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} der LMU verwenden, um Nutzer-Accounts nach einer gewissen Zeit zum Neu-Verifizieren ihrer Email-Adresse aufzurufen. Mit der User-Profile Funktion von Keycloak haben wir zusätzliche Felder zu den Nutzer-Accounts hinzugefügt, um zu markieren wann ein Nutzer die Email-Adresse neu verifizieren muss. Ein Kubernetes Cronjob kommuniziert mit der Keycloak API, um genau diese Nutzer zu ermitteln und mit einem POST über die Keycloak API die Email-Verifizierung zurückzusetzen. Verwendet einer dieser Nutzer danach das Munich Media Monitoring, kommt automatisch die Aufforderung von Keycloak zur Verifizierung der Email-Adresse.
:::</p>
<p>:::GlobalTitle{:size="lg" .my-5}
Warum Blueshoe als Partner für deine Keycloak-Integration?
:::
:::GlobalParagraph
Wir sind Experten für <a href="/loesungen/identity-access-management/">Open-Source IAM-Lösungen</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} und helfen Unternehmen seit über 10 Jahren bei der sicheren Keycloak-Implementierung. Zudem bieten wir mit <a href="https://github.com/Blueshoe/pycloak">pycloak</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} eine maßgeschneiderte Lösung für Python-basierte Anwendungen. Pycloak ist ein Package, das es Entwicklern ermöglicht, Keycloak nahtlos in Python-Projekte zu integrieren. Durch diese Entwicklung ist es Blueshoe gelungen, Keycloak noch zugänglicher und anpassbarer für Entwickler zu machen.
:::
:::GlobalParagraph
<strong>Unsere Vorteile:</strong>
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li><strong>35 % schnellere Umsetzung</strong> durch unser <a href="/leistungen">RAPID-Framework</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}</li>
<li><strong>Maßgeschneiderte</strong> <a href="/technologien/keycloak-iam-loesung/">Keycloak-Integration</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} für <a href="/technologien/python-django-agentur/">Django</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}, <a href="/technologien/docker-kubernetes/">Kubernetes</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} &#x26; Co.</li>
<li><strong>Expertise</strong> in OAuth2, OpenID Connect &#x26; SAML
:::</li>
</ul>
<p>::GlobalCallAnExpert{:bg="bg-bs-blue"}
#buttonBlockTitle
Sichere dein IAM-Projekt mit Keycloak
::</p>
<p>:::GlobalTitle{:size="lg" .mb-5 .mt-8}
Häufige Fragen
:::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
1. Wie kann ich Keycloak in meine bestehende Anwendung integrieren?
::</p>
<p>::GlobalParagraph
Keycloak bietet verschiedene Möglichkeiten zur Integration:
::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li><strong>Web-Anwendungen:</strong> Nutze die Keycloak JavaScript-Adapter oder OAuth2-Flow für sichere Anmeldungen.</li>
<li><strong><a href="/loesungen/api-entwicklung/">Backend-APIs</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}:</strong> Implementiere OpenID Connect oder OAuth2 zur Token-Validierung.</li>
<li><strong><a href="/loesungen/microservice-architektur-beratung/">Microservices/Kubernetes</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}:</strong> Verwende oauth2-proxy oder Keycloak als Identity-Provider für dein Cluster.</li>
<li><strong>Tipp:</strong> Die Keycloak REST-API ermöglicht eine feingranulare Benutzerverwaltung.
:::</li>
</ul>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
2. Welche Authentifizierungs-Protokolle unterstützt Keycloak?
::</p>
<p>::GlobalParagraph
Keycloak unterstützt alle gängigen Protokolle für sichere Authentifizierung:
::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li><strong>OAuth2</strong> (für <a href="/loesungen/api-entwicklung/">API-Authentifizierung</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid})</li>
<li><strong>OpenID Connect</strong> (für Webanwendungen &#x26; SSO)</li>
<li><strong>SAML</strong> (für ältere Unternehmenssysteme)</li>
<li><strong>LDAP-Integration</strong> (für bestehende Nutzerverzeichnisse)</li>
<li><strong>Tipp:</strong> Wähle das passende Protokoll je nach Anwendung – OpenID Connect ist meist die beste Wahl für Web &#x26; Mobile.
:::</li>
</ul>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
3. Wie setze ich rollenbasierte Zugriffskontrolle (RBAC) mit Keycloak um?
::</p>
<p>::GlobalParagraph
Keycloak bietet ein flexibles <strong>Rollen- und Berechtigungssystem</strong>:
::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li><strong>Definiere Rollen &#x26; Berechtigungen</strong> in der Keycloak-Konsole.</li>
<li><strong>Weise Rollen Nutzern oder Gruppen zu</strong> (z. B. Admin, User, Editor).</li>
<li><strong>Validiere Rollen im Code:</strong> Dein Backend kann Keycloak-Token auslesen &#x26; Berechtigungen prüfen.</li>
<li><strong>Tipp:</strong> Nutze Keycloak’s "Authorization Services", um komplexe Zugriffskontrollen zu verwalten.
:::</li>
</ul>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
4. Wie kann ich Keycloak lokal für Entwicklung &#x26; Tests starten?
::</p>
<p>::GlobalParagraph
Keycloak kann lokal per Docker oder Standalone-Modus gestartet werden:
::
::BlogCode{.mb-4}</p>
<pre><code class="language-bash">docker run -p 8080:8080 --name keycloak -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak start-dev
</code></pre>
<p>::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li><strong>Vorteile:</strong> Schnelles Setup für lokale Entwicklung &#x26; Tests</li>
<li><strong>Tipp:</strong> Falls du Keycloak mit PostgreSQL oder Kubernetes nutzt, konfiguriere die Datenbank entsprechend mit Umgebungsvariablen.
:::</li>
</ul>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
5. Wie setze ich Single Sign-On (SSO) mit Keycloak um?
::</p>
<p>::GlobalParagraph
Keycloak ermöglicht SSO mit OpenID Connect oder SAML:
::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>Richte Keycloak als Identity-Provider ein.</li>
<li>Verbinde deine Anwendungen mit Keycloak über OpenID Connect.</li>
<li>Nutze einen zentralen Login für mehrere Apps – Benutzer müssen sich nur einmal anmelden.</li>
<li>Tipp: Falls du bestehende Nutzerkonten migrieren musst, kannst du LDAP oder eine Custom-Authentifizierung nutzen.
:::</li>
</ul>]]></content:encoded>
            <category>Keycloak</category>
            <category>SSO</category>
            <category>Sicherheit</category>
            <category>Entwicklung</category>
            <enclosure url="https://blueshoe.de/img/blogs/keycloak.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Kubernetes Basics: Der Aufbau einer Plattform mit Kubernetes]]></title>
            <link>https://blueshoe.de/blog/kubernetes-aufbau-plattform-kubernetes</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/kubernetes-aufbau-plattform-kubernetes</guid>
            <pubDate>Thu, 01 Jun 2023 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Kubernetes ist eine umfangreiche Technologie, die auch erfahrene Entwickler immer wieder vor neue Herausforderungen stellt. Deshalb haben zahlreiche Anbieter Tools entwickelt, um die Arbeit mit und um Kubernetes zu erleichtern.</p>
<p>Wir werfen einen Blick in den Werkzeugkasten, erläutern Einsatzmöglichkeiten der verschiedenen Tools und geben Beispiele, welche Lösungen angeboten werden.</p>
<p><img src="/img/blogs/kubernetes_gefyra_getdeck.jpg" alt="kubernetes_gefyra_getdeck">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Kubernetes  ist eine Open-Source-Technologie, um containerisierte Software zu verwalten und hilft Entwicklern dabei, einzelne Anwendungen für die Endnutzer hochverfügbar zu halten.
:::
:::globalParagraph
Die Technologie Kubernetes kann durch Entwickler vollkommen eigenständig genutzt werden - eben um “nur” Software zu verwalten. Eine Applikation kann dann i.d.R. eigenständig auf dem Cluster ausgeführt werden. Soll diese Applikation jedoch nicht nur im Cluster ausgeführt werden, sondern auch “von außen” erreichbar sein, z.B. durch einen Endanwender, ist zusätzlich zu Kubernetes noch eine Technologie wie Ingress notwendig. Damit beginnt schon der Aufbau einer ganzen Plattform.
:::
:::globalParagraph
Kubernetes kann mit einer Vielzahl eigenständiger Applikationen und anderen Technologien ergänzt werden, um so eine beliebig komplexe Plattform aufzubauen, bei der Kubernetes dann ein zentraler Bestandteil ist. Solche Plattformen können ganz individuelle Entwicklungsprozesse abbilden, je nachdem, wie einzelne Teams ihre Prozesse organisieren oder welche Unternehmensanforderungen abgebildet werden sollen.
:::
:::globalParagraph
In diesem Text wollen wir einzelne Technologien und Applikationen, die für den Aufbau einer solchen Plattform in Frage kommen, vorstellen und erläutern. Unsere Zielgruppe dabei sind einerseits Entwickler, die noch keine Erfahrung mit Kubernetes haben, andererseits aber auch Personen, die zwar im Feld der Softwareentwicklung tätig sind, aber selbst nicht entwickeln, sondern z.B. Projekte managen.
:::
:::globalParagraph
Die Liste der hier vorgestellten Applikationen und Technologien stellt dabei nur eine Auswahl der Möglichkeiten für einzelne Teilbereiche dar und ist nicht abschließend.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
Managed Cluster und Kubernetes-Distributionen
:::
:::globalTitle{:size="md" :tag="h3" .mb-5}
Managed Kubernetes-Cluster
:::
:::globalTitle{:size="sm" :tag="h4" .mb-5}
<strong>Was machen Managed Kubernetes-Cluster?</strong>
:::
:::globalParagraph
Wie bereits in dem Artikel <a href="/blog/kubernetes-fuer-anfaenger">Kubernetes für Anfänger</a>{.bs-link-blue}, handelt es sich bei Kubernetes (abgekürzt auch k8s) um eine Open-Source-Technologie, die von jedem frei genutzt werden kann. Zusätzlich gibt es aber noch Anbieter von so genannten managed Kubernetes-Clustern, die für die Nutzung von Kubernetes sowohl die Infrastruktur als auch eine erste Benutzeroberfläche bereitstellen.
:::</p>
<p>:::globalTitle{:size="sm" :tag="h4" .mb-5}
<strong>Welche Manageds Kubernetes-Cluster Anbieter gibt es?</strong>
:::
:::globalParagraph
Die bekanntesten Anbieter für managed Kubernetes-Cluster sind Azure Kubernetes Service (AKS), Google Kubernetes Engine (GKE) and Amazon Elastic Kubernetes Service (EKS).
:::
:::globalParagraph
Alle Anbieter stellen für die Arbeit mit Kubernetes eine Benutzeroberfläche und weitere Services bereit. Ob einzelne Services bereits im Basispreis enthalten sind oder über Lizenzen noch weiter zugekauft werden müssen, variiert stark je nach Anbieter.
:::
:::globalParagraph
Im Einzelfall sollte von den Entwicklern geprüft werden, welche Services einzelne Anbieter zur Verfügung stellen und ob diese notwendig und sinnvoll sind. AKS beispielsweise stellt in der Basis-Version bereits ein gutes Logging zur Verfügung, bei EKS muss ein solches zusätzlich zur Basisversion erworben werden.
:::</p>
<p>:::globalTitle{:size="md" :tag="h3" .mb-5}
Kubernetes-Distributionen
:::
:::globalTitle{:size="sm" :tag="h4" .mb-5}
<strong>Was sind Kubernetes-Distributionen?</strong>
:::
:::globalParagraph
Die Abgrenzung zwischen Anbietern von managed Kubernetes-Clustern und Anbietern von Kubernetes Distributionen ist nicht ganz einfach.
:::
:::globalParagraph
Ein Kubernetes-Cluster mit den notwendigen Nodes und Pods kann vollständig autonom betrieben werden. Dazu bedarf es auch nicht der Nutzung eines managed Clusters.
:::
:::globalParagraph
Die im weiteren Verlauf dieses Textes beschriebenen Tools können, müssen aber nicht zwangsläufig genutzt werden, wenn man einen Kubernetes-Cluster betreibt. Durch die Nutzung dieser Tools können beliebig komplexe Plattformen erstellt werden, deren Kernstück der Kubernetes-Cluster ist. Diese Tools wollen wir im Folgenden als “Ecosystem-Tool” bezeichnen.
:::
:::globalParagraph
Anbieter von Kubernetes-Distributionen stellen eine bereits vordefinierte Plattform mit einzelnen Ecosystem-Tools zur Verfügung. Anbieter von managed Kubernetes-Clustern hingegen stellen demgegenüber für die Nutzung von Kubernetes “nur” die Infrastruktur und eine erste Benutzeroberfläche mit einzelnen Funktionen zur Verfügung, die um beliebige Ecosystem-Tools erweitert werden können.
:::
:::globalParagraph
Kubernetes-Distributionen bieten den Mehrwert, dass Entwickler diese Tools nicht selbst in den Cluster integrieren müssen. Es ist davon auszugehen, dass alle in dieser Plattform genutzten Tools untereinander und mit dem Kubernetes-Cluster aufeinander abgestimmt konfiguriert sind, automatisiert regelmäßige Updates erhalten und somit fehlerfrei ausgeführt werden.
:::
:::globalParagraph
Es steht den Entwicklern aber frei, diese Plattformen wiederum in einen managed Kubernetes-Cluster zu integrieren und einige Anbieter von managed Kubernetes-Clustern bieten auch bereits eine Kubernetes-Distribution an.
:::
:::globalParagraph
Zusammengefasst kann man also sagen, dass managed Kubernetes-Cluster ein vorgefertigtes Grundgerüst für die Arbeit mit Kubernetes zur Verfügung stellen. Kubernetes-Distributionen gehen einen Schritt weiter und liefern zusätzlich bereits integrierte Tools als Paket.
:::</p>
<p>:::globalTitle{:size="sm" :tag="h4" .mb-5}
<strong>Welche Anbieter von Kubernetes-Distributionen gibt es?</strong>
:::
:::globalParagraph
Die bekanntesten Anbieter für eine Kubernetes-Distribution sind wahrscheinlich Red Hat OpenShift und Rancher Kubernetes Engine (RKE).
:::</p>
<p><img src="/img/blogs/managedk8cluster-k8distribution.jpg" alt="managedk8cluster-k8distribution">{.object-cover .w-full .mb-6}</p>
<p>:::globalTitle{:size="lg" .mb-5}
Technologien, die <strong>in</strong> einem Kubernetes-Cluster installiert werden
:::
:::globalTitle{:size="md" :tag="h3" .mb-5}
Kubernetes Dashboard: Das Standard-Frontend
:::
:::globalTitle{:size="sm" :tag="h4" .mb-5}
<strong>Was ist das Kubernetes Dashboard?</strong>
:::
:::globalParagraph
Beim Kubernetes Dashboard handelt es sich um ein web-basiertes User-Interface. Kubernetes Dashboard ist ein Bestandteil von Kubernetes und bietet eine Benutzeroberfläche um beispielsweise containerisierte Software auf einen Cluster zu deployen oder dessen Ressourcen zu managen. Man bekommt damit einen Überblick über alle Applikationen und Services, die in einem Cluster laufen oder kann individuelle Kubernetes Ressourcen (wie Jobs, Deployments, etc.) modifizieren.
:::
:::globalParagraph
Kubernetes Dashboard als Benutzeroberfläche ist aber nicht per Default in einem Cluster installiert, sondern muss explizit dafür ausgewählt werden.
:::
:::globalParagraph
Ein Kubernetes Cluster kann auch ohne das Kubernetes Dashboard über die Kommandozeile verwaltet und gemonitored werden. Die Bedienbarkeit wird durch das Dashboard aber wesentlich vereinfacht.
:::</p>
<p>:::globalTitle{:size="sm" :tag="h4" .mb-5}
<strong>Welche Tools gibt es?</strong>
:::
:::globalParagraph
Das Kubernetes Dashboard ist das Standard-Frontend für einen Kubernetes-Cluster. Alternativ bieten auch Anbieter von managed Clustern wie AKS oder GKE ein eigenes Dashboard an.
:::</p>
<p>:::globalTitle{:size="md" :tag="h3" .mb-5}
Kommandozeilentool: Technologien, um mit einem Cluster zu kommunizieren
:::
:::globalTitle{:size="sm" :tag="h4" .mb-5}
<strong>Was sind Kommandozeilentools?</strong>
:::
:::globalParagraph
Einen Cluster muss man sich zwar als eigenständiges, in sich abgeschlossenes Gebilde vorstellen, aber unabhängig von seiner Umgebung ist ein Cluster dennoch nicht. Ein Cluster macht nur das, was ihm als Kommando vorgegeben wird. Es muss also einen Weg geben, wie Entwickler mit dem Cluster kommunizieren können.
:::
:::globalParagraph
Dafür gibt es Benutzeroberflächen, die Informationen zum Cluster gebündelt darstellen können und es Entwicklern erlauben, mit dem Cluster in Verbindung zu treten. Allerdings arbeiten die meisten Entwickler gerne mit sogenannten Komandozeilentools: Informationen können ohne große grafische Aufbereitung abgefragt oder an den Cluster weitergegeben werden.
:::
:::globalParagraph
Kommandozeilentools muss man sich wie integrierte Entwicklungsumgebungen bei der Softwareentwicklung vorstellen: Verschiedenen Tools haben einen unterschiedlichen Funktionsumfang und legen ihren jeweiligen Fokus auf unterschiedliche Anwendungsbereiche (siehe z.B. "Was ist eine IDE?"). Analog dazu gibt es auch unterschiedliche Kommandozeilentools, die die Entwickler je nach Präferenz für die Arbeit mit einem Kubernetes-Cluster nutzen können.
:::</p>
<p>:::globalTitle{:size="sm" :tag="h4" .mb-5}
<strong>Welche Komandozeilentools gibt es?</strong>
:::
:::globalParagraph
Tools, die hier zur Verfügung stehen, sind beispielsweise kubectl, kubectx oder kube-shell.
:::
:::globalParagraph
Um eine Analogie zum Auto zu bemühen, kann man sich vorstellen, dass Kubernetes selbst das Konzept der Autotür beinhaltet, über die Entwickler mit einem Kubernetes-Cluster in Verbindung treten können. Kommandozeilentools wiederum setzten das Konzept der Tür unterschiedlich um: Einmal öffnet sich die Türe nach vorne, einmal nach hinten und das dritte Tool ist eine Flügeltüre. Welches Kommandozeilentool genutzt wird, ist aber grundsätzlich egal und liegt in der Entscheidung der Entwickler. Unterschiedliche Entwickler, die aber mit dem gleichen Cluster arbeiten, können auch unterschiedliche Kommandozeilentools benutzen.
:::</p>
<p>:::globalTitle{:size="md" :tag="h3" .mb-5}
Service-Mesh: Technologie, um die Kommunikation zwischen Cluster-Bestandteilen zu managen
:::
:::globalTitle{:size="sm" :tag="h4" .mb-5}
<strong>Was machen Service-Meshes?</strong>
:::
:::globalParagraph
“Klassische” Anwendungen sind eher in der Form eines Monolithen konzipiert. “Moderne” cloud-native <a href="/loesungen/microservice-architektur-beratung/">Anwendungsarchitekturen</a>{.bs-link-blue} hingegen setzen auf einzelne Microservices und die Anwendung entsteht erst durch eine Verflechtung und Interaktion einzelner Microservices untereinander. Einzelne Services sind in Container verpackt, die in einzelnen Pods zusammengefasst sind, welche wiederum miteinander kommunizieren und Informationen austauschen (siehe dazu <a href="/blog/kubernetes-fuer-anfaenger/">"Kubernetes für Anfänger"</a>{.bs-link-blue}).
:::
:::globalParagraph
Die Kommunikation zwischen einzelnen Pods (,die den containerisierten Code enthalten) erfolgt in einem Kubernetes Cluster selbst und wird durch die Entwickler definiert. Zusätzlich kann durch die Entwickler noch ein sogenanntes Service Mesh eingesetzt werden, das es erlaubt, die Kommunikation zwischen Pods noch genauer zu spezifizieren.
:::
:::globalParagraph
Um das zu veranschaulichen, wollen wir uns an dieser Stelle einen Online-Shop vorstellen. Dem Kunden stehen beim Checkout beispielsweise 2 Zahlungsmöglichkeiten zur Verfügung: auf Rechnung und per Kreditkarte. Die Shopbetreiber wollen künftig aber auch die Bezahlung per Paypal möglich machen. Nachdem die Entwickler den dafür notwendigen Code erarbeitet und in einer Testumgebung geprüft haben, soll es nun einen ersten Test im Live-Shop geben.
:::
:::globalParagraph
Durch die Nutzung eines Service-Mesh kann die Neuentwicklung (Auswahl zwischen 3 Zahlungsmöglichkeiten) auf einem eigenen Pod im Cluster zur Verfügung gestellt werden. Die ursprüngliche Umsetzung (Auswahl nur zwischen 2 Zahlungsmöglichkeiten) kann im Cluster aber bestehen bleiben. Mit dem Service-Mesh können die Entwickler definieren, dass 80% der eingehenden Anfrage weiterhin auf den Pod mit dem ursprünglichen Stand (Auswahl nur von 2 Zahlungsmöglichkeiten) geleitet wird und nur 20% auf den Pod mit der Neuentwicklung.
:::
:::globalParagraph
Service-Meshes können aber noch mehr: Im obigen Beispiel wird über Service-Meshes definiert, was an die einzelnen Pods kommuniziert wird. Über Service-Meshes kann aber auch definiert werden, wie innerhalb des Clusters kommuniziert wird. Grundsätzlich ist die Kommunikation im Cluster beispielsweise nicht verschlüsselt, durch Service-Meshes kann eine zusätzliche Verschlüsselung definiert werden.
:::</p>
<p>:::globalTitle{:size="sm" :tag="h4" .mb-5}
<strong>Welche Service-Meshes Tools gibt es?</strong>
:::
:::globalParagraph
Tools, die die Technologie des Service-Mash bereitstellen, sind beispielsweise Istio, Linkerd oder Cilium.
:::
:::globalParagraph
Diese Tool stellen jeweils wieder einen unterschiedlichen Umfang an nutzbaren Möglichkeiten dar. Linkerd verfügt beispielsweise über sogenannte “Sidecar Proxies”, die den Aufbau einer verschlüsselten Kommunikation der Pods untereinander im Cluster erlauben. Istio hingegen bietet diese Funktion nicht. Dafür ist Istio im Vergleich zu Linkerd weniger komplex, in der Architektur schlanker und erfordert keine Code-Änderungen an der Kubernetes-Applikation selbst. Welches das Mittel der Wahl ist, muss also im Einzelfall durch die Entwickler geprüft und beurteilt werden (siehe auch "Was ist Linkerd").
:::</p>
<p>:::globalTitle{:size="md" :tag="h3" .mb-5}
Ingress-Controller: Technologie um Anfragen an den Cluster zu kontrollieren
:::
:::globalTitle{:size="sm" :tag="h4" .mb-5}
<strong>Was bedeutet Ingress?</strong>
:::
:::globalParagraph
‘Ingress traffic’ bezeichnet den Datenverkehr, der seinen Ursprung außerhalb eines Computernetzwerks hat und an dieses gerichtet ist. Bezogen auf einen Cluster bedeutet das, dass eine Anfrage von außerhalb des Clusters an diesen gestellt wird, also z.B. dass ein User eine Website oder einen Service aufruft, der in einem Cluster betrieben wird. Die Technologie oder Ressource “Ingress” macht HTTP- und HTTPS-Anfragen von außerhalb des Clusters für Dienste innerhalb des Clusters verfügbar.
:::
:::globalParagraph
Analog zum technischen Konzept von Kubernetes handelt es sich auch bei der Technologie “Ingress” um einen abstrakten technischen Bauplan. Die genaue Umsetzung des technischen Bauplans von Ingress liegt wieder beim jeweiligen Hersteller. Um auch hier die Analogie des Autos zu nutzen, liegt es in der Entscheidung des jeweiligen Autobauers, ob der Motor als Verbrennungs- oder Elektromotor umgesetzt wird.
:::
:::globalParagraph
Ingress selbst ist also das Konzept, wie externe Anfrage an einen Kubernetes-Cluster gestellt werden. Dazu zählt zum Beispiel, wie die Anzahl der externen Anfragen im Kubernetes Cluster ausbalanciert werden, oder dass einer im Cluster verfügbaren Applikation überhaupt eine von extern erreichbare URL zugewiesen wird. Die Ausführung des Konzepts liegt dann bei einem so genannten Ingress-Controller, also der Anbieter-spezifischen Ausgestaltung von Ingress.
:::
:::globalParagraph
Ingress-Controller sind dabei nicht nur in Zusammenhang mit Kubernetes-Clustern relevant, sondern für die Nutzung von allen Services, die innerhalb eines Computernetzwerkes für Datenverkehr von außen ansprechbar sein sollen - also auch für Services, die auf individuellen Servern gehostet werden.
:::</p>
<p>:::globalTitle{:size="sm" :tag="h4" .mb-5}
<strong>Welche Ingress-Controller gibt es?</strong>
:::
:::globalParagraph
Es gibt eine ganze Reihe an verfügbaren Ingress controllers.  Zu den bekanntesten und in Kombination mit Kubernetes-Clustern häufig verwendeten Ingress-Controllern zählen Nginx und Traefik.
:::
:::globalParagraph
Beide haben wieder diverse Vor- und Nachteile, je nach Anwendungsfall. Welcher Ingress-Controller genutzt werden soll, muss zwingend von spezialisierten Entwicklern beurteilt werden und würde den Rahmen dieses Artikels bei weitem übersteigen. Wir können euch jedoch diese beiden Artikel über Ingress und Ingress controllers von der Kubernetes-Website empfehlen, damit ihr eure Research fortsetzen könnt.
:::</p>
<p><img src="/img/blogs/technology-in-cluster.jpg" alt="technology_in_cluster">{.object-cover .w-full .mb-6}</p>
<p>:::globalTitle{:size="lg" .mb-5}
Technologien, die <strong>um</strong> einen Kubernetes-Cluster herum installiert werden
:::
:::globalTitle{:size="md" :tag="h3" .mb-5}
Technologien, um Code in Container zu verpacken
:::
:::globalTitle{:size="sm" :tag="h4" .mb-5}
<strong>Container Images</strong>
:::
:::globalParagraph
Kubernetes ist eine Technologie um containerisierte Software zu orchestrieren. Siehe dazu auch unseren Blog Posts <a href="/blog/kubernetes-fuer-anfaenger/">Kubernetes für Anfänger</a>{.bs-link-blue}, um mehr über dieses Thema zu erfahren. Ohne containerisierte Software ist die Nutzung von Kubernetes selbst gegenstandslos - eben weil Kubernetes nur mit containerisierter Software arbeiten kann.
:::
:::globalParagraph
Nachdem der Code für eine Anwendung durch Entwickler erarbeitet wurde, wird daraus ein sogenanntes ‘container image’ erstellt. In Kubernetes wird dann später auf das jeweilige Container Image referenziert. Verwaltet werden die Container Images entweder in einer eigenständigen Container Registry außerhalb des Clusters oder direkt im Cluster selbst. Erst wenn dieses referenzierte Container Image in Kubernetes ausgeführt wird, wird es technisch gesehen zu einem eigenständigen Container.
:::
:::globalParagraph
Ein Container Image ist ein read-only Template des Codes einer Anwendung, inklusive aller notwendigen Informationen, die für das Ausführen des Codes relevant sind, z.B. Konfigurationsdateien, Umgebungsvariablen, Bibliotheken, etc. Man kann sich ein Container Image also als unveränderliches digitales Bild des Codes vorstellen. Der Vorteil von Container Images liegt darin, dass sie dupliziert und von mehreren Entwickler gemeinsam genutzt werden können. Damit sind Container Images die idealen Ressourcen, um in einem Cluster geteilt zu werden. Anwendungscode kann so innerhalb eines Clusters zum Beispiel auf mehreren Pods ausgeführt und damit skaliert werden.
:::
:::globalParagraph
Ein weiterer Vorteil der Nutzung von Container Images besteht darin, dass das Image die Konfigurationen für die später entstehenden Container gleich enthält. Anders als bei der Ausführung von Softwarecode auf einem eigenen Server erhalten die Container mit den Informationen aus den Images direkt alle notwendigen Konfigurationsinformationen. Alle später auf Basis des Images generierten Container sind damit immer gleich konfiguriert. Wird Softwarecode auf jeweils eigenen Servern ausgeführt, muss die Konfiguration für jeden Server neu und einzeln vorgenommen werden - ein fehleranfälliges und zeitaufwändiges Vorgehen. Informiere dich sich weiter über Docker Images
:::</p>
<p>:::globalTitle{:size="sm" :tag="h4" .mb-5}
<strong>Tools zur Erstellung von Container Images</strong>
:::
:::globalParagraph
Der bekannteste Anbieter von Tools, um aus Softwarecode ein Container-Image zu erstellen, ist Docker. Mit Docker oder anderen Tools, wie rkt von CoreOS oder LXC, wird ein Container Image erstellt.
:::
:::globalParagraph
Bei Docker handelt es sich um ein virtualisiertes Betriebssystem für Container und verhält sich ähnlich wie eine Virtuelle Maschine (VM). Eine VM virtualisiert dabei Serverhardware, während Container das Betriebssystem eines Servers virtualisieren.
:::
:::globalParagraph
Erhalte hier einen guten Überblick über verschiedene Anbieter mit Vor- und Nachteilen im Vergleich zu Docker als Marktführer. Welches Tool zur Umsetzung dieser Technologie genutzt wird, hängt wesentlich von den technischen Voraussetzung und Präferenz der jeweiligen Entwickler ab (z.B. wird mit Windows, Linux oder Mac gearbeitet?).
:::
:::globalParagraph
Zur Verwaltung von Container Images kann beispielsweise quay.io, genutzt werden. Eine Reihe von Anbietern alternativer Tools zu quay.io findet sich heir Hervorheben möchten wir das Tool  Harbor. Während es sich bei quay.io um ein Tool handelt, das eine Container Registry außerhalb des Cluster bereitstellt, kann Harbor direkt in den Cluster installiert werden und die Verwaltung der Images erfolgt damit auch direkt im Cluster. Ein zusätzlicher externer Dienst außerhalb des Clusters ist damit nicht mehr notwendig. Welche Variante hier sinnvoller ist, ist eine Entscheidung des Entwicklerteams und hängt von den individuellen Anforderungen an die zu erstellende Software ab.
:::</p>
<p>:::globalTitle{:size="md" :tag="h3" .mb-5}
Technologien, um Apps und Konfigurationen in einem Cluster zu managen
:::
:::globalTitle{:size="sm" :tag="h4" .mb-5}
<strong>Konfiguration eines Clusters mit yaml-Dateien</strong>
:::
:::globalParagraph
Nur weil ein Cluster existiert, ist er aber noch nicht sofort für den produktiven Betrieb bereit. Jeder Cluster verfügt über eine spezifische Konfiguration und jede neue Applikation in einem Cluster muss diese Konfiguration berücksichtigen, damit sie korrekt ausgeführt werden kann.
:::
:::globalParagraph
Die Konfiguration eines Clusters wird in sogenannten yaml-Dateien definiert. Yaml-Dateien enthalten Spezifikationen für das Deployment.
:::
:::globalParagraph
Sie können händisch durch die Entwickler erstellt werden. Nachteilig ist dabei, dass ein händisches Erstellen von Dateien grundsätzlich fehleranfällig ist. Zusätzlich benötigt ein komplexer Cluster mehrere yaml-Dateien, die alle dem gleichen Standard folgen müssen. Das bedeutet, dass jeder Entwickler in einem Team den Standard auch kennen und anwenden muss. Wird der Standard geändert, benötigt es zusätzliche Absprachen.
:::
:::globalParagraph
Um diesen Prozess effizienter, stabiler und effektiver zu gestalten, gibt es Tools, die Templates zur Verfügung stellen, so dass alle yaml-Dateien “gleich aussehen”.
:::</p>
<p>:::globalTitle{:size="sm" :tag="h4" .mb-5}
<strong>Helm zur Erzeugung von yaml-Dateien</strong>
:::
:::globalParagraph
Das bekannteste Tool, das außerhalb eines Clusters installiert wird, um yaml-Dateien zu erzeugen, ist Helm.
:::
:::globalParagraph
In sogenannten Helm Charts wird definiert, welche Abhängigkeiten zwischen den einzelnen Applikationen innerhalb des Cluster bestehen, welche Ressourcen aus Kubernetes benötigt werden und was sonst noch notwendig ist, um Container-Anwendungen bereitzustellen und auszuführen.
:::
:::globalParagraph
Ein Helm Chart kann dabei beliebig oft im Cluster genutzt werden um beliebig viele Instanzen einer Anwendung zu realisieren und das System so leicht zu skalieren (siehe auch "Was ist Helm?").
:::
:::globalParagraph
Helm Charts können auch mit anderen Personen geteilt werden. Sie sind also die zentrale Instanz um eine Anwendung einmalig zu definieren und dann mit minimalem Aufwand von vielen Personen verwalten zu lassen.
:::</p>
<p>:::globalTitle{:size="md" :tag="h3" .mb-5}
Technologien zur lokalen Ausführung von Kubernetes
:::
:::globalTitle{:size="sm" :tag="h4" .mb-5}
<strong>Softwareentwicklung auf lokalen Rechnern</strong>
:::
:::globalParagraph
Dazu machen wir einen ganz kurzen Ausflug in die Arbeitswelt der Entwickler. In der Regel werden einzelne Bestandteile eines komplexen Codes entwickelt, einzelne Features, die später zusammengeführt werden. Entwickler produzieren Code also lokal auf ihren eigenen Rechnern und nicht direkt in komplexen Produktiv- oder Testumgebungen. Erst später werden die einzelnen Bestandteile dann zusammengeführt.
:::
:::globalParagraph
Dafür ist notwendig, dass Entwickler die projektspezifische Entwicklungsumgebung auf ihren eigenen Rechnern verfügbar haben, also die Rahmenkonfigurationen der späteren Test- und Produktivumgebung. Das ist immer notwendig, egal ob für den späteren Betrieb der Software Kubernetes genutzt wird oder nicht.
:::
:::globalParagraph
Die Herausforderung für die Entwickler liegt darin, diese Umgebung auf ihren lokalen Rechner richtig zu konfigurieren. Nur wenn gegen die richtige Konfiguration entwickelt wird, kann der Code später in der Produktiv- oder Testumgebung auch fehlerfrei ausgeführt werden. Bisher lag die große Herausforderung darin, dass jeder einzelne Entwickler die entsprechenden Konfigurationen selbst vornehmen musste. Enge Absprachen zwischen den Teams, die Software entwickeln (Development-Teams), und den Teams, die für die Konfiguration der später genutzten Server zuständig sind (Operations-Teams), sind dafür notwendig. Die Kommunikation zwischen beiden Teams läuft dabei oft nicht reibungslos.
:::
:::globalParagraph
Wird für die Ausführung von Softwarecode Kubernetes genutzt, gibt es nun aber eine ganze Reihe von Tools, die es ermöglichen, dass das Operations-Team die Konfiguration des Cluster selbstständig vornimmt, pflegt und wartet und die Entwicklerteams die Konfigurationen durch den Einsatz spezialisierter Tools ohne selbstständig Konfigurationsmaßnahmen oder notwendigen Absprachen auf ihren Rechner installieren können. Auch wenn sich die Konfigurationen am Cluster ändern, ermöglichen diese Tools, dass die geänderten Konfigurationen ohne weitere Absprachen zwischen den Teams auch auf die Rechner der Entwickler übernommen werden.
:::</p>
<p>:::globalTitle{:size="sm" :tag="h4" .mb-5}
<strong>Lokale Kubernetes Entwicklungstools</strong>
:::
:::globalParagraph
Beispiele für Tools, die diese Technologie anbieten, sind minikube, kind oder auch K3s.
:::
:::globalParagraph
Einen guten Überblick über die unterschiedlichen Umfänge dieser Tools und mögliche Anwendungsfälle findet sich hier.
:::</p>
<p>:::globalTitle{:size="md" :tag="h3" .mb-5}
Technologien, um Container zu “bridgen”
:::
:::globalTitle{:size="sm" :tag="h4" .mb-5}
Was bedeutet "bridgen"?
:::
:::globalParagraph
Zuerst müssen wir voranstellen, dass das Wort “bridgen” keine anerkannte Terminologie ist, um zu beschreiben, was die hier vorgestellten Tools machen. Um ehrlich zu sein: Wir haben den Begriff bei uns eingeführt, weil er den Zweck dieser Technologien sehr anschaulich beschreibt. Wenn ihr also das Internet nach dem Terminus “bridgen” in Zusammenhang mit Kubernetes durchsucht, werdet ihr vermutlich kaum sinnvolle Treffer erhalten. Daher möchten wir an dieser Stelle den Terminus “bridgen” und die dazugehörigen Tools näher vorstellen.
:::
:::globalParagraph
Um einen Cluster zu betreiben, bedarf es einiger Ressourcen: Vor allem Rechenleistung. Und Rechenleistung kostet Geld. Geld ist nicht unbegrenzt verfügbar und dass es sowohl in der kommerziellen als auch nicht-kommerziellen Softwareentwicklung nicht unbegrenzt verfügbar ist, müssen wir hier nicht philosophisch erörtern.
:::
:::globalParagraph
Auch wenn Entwickler einen Cluster bei sich lokal verfügbar machen, um die Softwareentwicklung selbst effektiver zu gestalten, kostet das Ressourcen und damit Geld. Man stelle sich nur vor, dass ein großes Entwicklerteam an einer Vielzahl kleinerer neuer Features für eine große Hotelbuchungsplattform arbeitet und dass jeder Entwickler einen lokal verfügbaren Cluster besitzt, um einen Vorstellung von der Größenordnung der notwendigen Ressourcen zu bekommen.
:::
:::globalParagraph
Auch um dieses Problem zu lösen, gibt es eine maßgeschneiderte Technologie. Installieren sich Entwickler ein Tool dieser Technologie lokal auf ihren Rechnern ist es möglich, dass sie den Code lokal entwickeln und in Containern verpacken, bei der Ausführung des Codes im Container auf den eigenen Rechner dem Container aber "vorgegaukelt" wird, er würde sich in einem Cluster befinden.
:::
:::globalParagraph
Dieses “Vorgaukeln” ist aber noch nicht das, was “bridgen” eigentlich meint. Bridgen beginnt technisch gesehen erst, wenn Entwickler an bereits bestehenden Code arbeiten, zum Beispiel um einen Bugfix auszuführen. Dabei kann ein Entwickler mit den entsprechenden Tools (siehe unten) einen Container auf seinem lokalen Rechner “klonen” und an bereits bestehenden Code arbeiten. Der überarbeitete Code kann im bestehenden Cluster auf einen Container gelegt und getestet werden, wobei er noch immer ausschließlich auf dem lokalen System des Entwicklers ausgeführt wird. Solange diese Bridge besteht, können alle User, die über die URL verfügen, auch auf diese Code-Änderung zugreifen.
:::
:::globalParagraph
Dabei ist aber zu bedenken, dass aller Traffic auf diesen Container dann auch über die lokale Entwicklungsumgebung des jeweiligen Entwicklers geht, dieses Verfahren ist also vor allem sinnvoll, um Arbeiten in einem Staging-Cluster durchzuführen: Bugfixes können damit direkt im Staging getestet werden. Für Arbeiten an einem Produktivcluster sollte dieses Verfahren eher nicht angewendet werden.
:::</p>
<p>:::globalTitle{:size="sm" :tag="h4" .mb-5}
Welche Bridging-Tools gibt es?
:::
:::globalParagraph
Eines der bekanntesten Tools zum Bridgen ist “Telepresence”.
:::
:::globalParagraph
Blueshoe hat für diese Zwecke auch ein eigenes Tool namens ‘Gefyra’ entwickelt. Gefyra ist zwar hinsichtlich der Funktionalitäten nicht ganz so umfangreich wie Telepresence, ist mit seinem Fokus in der Nutzung aber wesentlich komfortabler für die Entwickler bei der Erstellung einer Bridge. Eine Gegenüberstellung beider Produkte könnt ihr im Blog Post "An Alternative to Telepresence 2: Gefyra".
:::</p>
<p><img src="/img/blogs/bridge.jpg" alt="bridge">{.object-cover .w-full .mb-6}</p>
<p>:::globalTitle{:size="md" :tag="h3" .mb-5}
Technologien, um den Entwicklern eine Entwicklungsumgebung zur Verfügung zu stellen, die der Produktionsumgebung entspricht
:::
:::globalTitle{:size="sm" :tag="h4" .mb-5}
<strong>Anforderungen an die Entwicklungsumgebung</strong>
:::
:::globalParagraph
Sind Arbeiten an Softwarecode erforderlich, der in einem Cluster ausgeführt wird, ist es notwendig, dass die Entwickler in der lokalen Entwicklungsumgebung ebenfalls über einen Cluster verfügen.
:::
:::globalParagraph
Dafür ist es sinnvoll, dass der lokale Cluster in der Konfiguration weitgehend dem Cluster entspricht, auf dem der Softwarecode später ausgeführt wird. Dafür gibt es Tools wie z.B. minikube (siehe oben).
:::
:::globalParagraph
Zusätzlich ist es genauso sinnvoll, wenn der lokale Entwicklungscluster auch "vorgefüllt" ist, d.h. dass die im Cluster vorhandenen Daten ebenfalls weitgehend den Livedaten entsprechen. Dazu gehören zum Beispiel Datenbanken, Datenbankeinträge, integrierte Drittsysteme, wie zum Beispiel Tools zum Identifikationsmanagement, etc..
:::
:::globalParagraph
Das heißt, wir haben einerseits Tools, damit Cluster auf den lokalen Entwicklungsumgebung einzelner Entwickler erstellt werden können. Diese Tools stellen sicher, dass alle Entwickler jeweils über die gleichen Konfigurationen verfügen. Ein Beispiel dafür ist zum Beispiel minikube (siehe oben).
:::
:::globalParagraph
Andere Tools erlauben es den Entwicklern, bereits bestehenden Code im lokalen Entwicklungscluster zu bearbeiten und diesen Code dann vor dem Deployment im Cluster “auszuprobieren”. Beispieltools dafür sind “Telepresence” oder “Gefyra” (siehe oben).
:::
:::globalParagraph
Und dann gibt es noch die in diesem Abschnitt vorgestellten Tools, die es erlauben, lokale Cluster mit Daten und/oder Drittsystemen, die möglichst nahe am Livesystem sind, für die Entwickler zu provisionieren.
:::</p>
<p>:::globalTitle{:size="sm" :tag="h4" .mb-5}
<strong>Welche Tools gibt es?</strong>
:::
:::globalParagraph
Ein Tool, das die Vorprovisionierung von lokalen Clustern erlaubt, ist das von Blueshoe entwickelte Tool “Getdeck”. Wir sind von unserem Tool überzeugt und wenden es in unserer täglichen Arbeit an - gerne könnt ihr einen <a href="/kontakt/">Termin mit uns buchen</a>{.bs-link-blue}, in dem wir euch Getdeck näher vorstellen.
:::</p>
<p>:::globalTitle{:size="md" :tag="h3" .mb-5}
Technologien, um die Code-Qualität sicherzustellen
:::
:::globalTitle{:size="sm" :tag="h4" .mb-5}
Vorteile von CI/CD
:::
:::globalParagraph
Als Software nur auf Medien wie Disketten oder CD-Roms zur Verfügung stand, lag der Fokus für Entwickler auf der Erarbeitung einer dauerhaften Version von Software. Gab es ein Update, musste sich der User eine neue CD-Rom mit dem aktualisierten Code-Stand besorgen.
:::
:::globalParagraph
Heute ist dieses Vorgehen weitgehend überholt: Software entwickelt sich ständig weiter, Updates sind ständig verfügbar. Das heißt, dass heute auch ständig einzelne Programmbestandteile zusammengeführt und permanent auf deren Kompatibilität geprüft werden müssen.
:::
:::globalParagraph
Wird dieser Prozess rein linear gestaltet und die Komptabilität einzelner Softwarebestandteile erst ganz am Ende geprüft, können erhebliche Probleme auftreten. Für die Entwickler kann das zur “Integrationshölle” werden: Der Code für ein neues oder überarbeitetes Feature ist zwar fertiggestellt, aufgrund nicht-absehbarer Abhängigkeiten mit anderen Code-Bestandteilen läuft aber nichts wie geplant. Siehe dazu auch "Was ist Continuous Integration und was sind die Vorteile?".
:::
:::globalParagraph
Die Methode CI/CD möchte dafür Abhilfe schaffen. CI steht für “Continuous Integration”, einem Automatisierungsprozess für Entwickler. CD bedeutet sowohl “Continuous Delivery” (Codeänderungen werden automatisch getestet) als auch “Continuous Deployment” (Freigabeprozess von Codeänderungen beim Verfügbarmachen für die Endnutzer - also dem Deployment).
:::</p>
<p>:::globalParagraph{.mt-4}
<strong>Continuous Integration</strong>
:::
:::globalParagraph
Codeänderungen einzelner Entwickler werden regelmäßig miteinander zusammengeführt. Das bietet daher vor allem den Vorteil, dass Inkompatibilitäten wesentlich früher aufgedeckt werden können.
:::</p>
<p>:::globalParagraph{.mt-4}
<strong>Continuous Delivery &#x26; Continuous Deployment</strong>
:::
:::globalParagraph
Codeänderungen werden automatisierten Tests unterzogen und in Repositorys wie GitHub verfügbar gemacht. Dabei soll auch geprüft werden, wie sich der neue Code im Zusammenspiel mit bereits bestehendem Code auf dem Live-System verhält.
:::
:::globalParagraph
Mittlerweile gibt es spezifische Tools, die den CD-Prozess speziell für Software, die in einem Kubernetes-Cluster ausgeführt wird, verfügbar machen, Prüfungstools, die also explizit auf ein Kubernetes-Umfeld ausgelegt sind.
:::</p>
<p>:::globalTitle{:size="sm" :tag="h4" .mb-5 .mt-4}
<strong>Das Tool Argo CD</strong>
:::
:::globalParagraph
Wir bei Blueshoe nutzen dafür das Tool Argo CD. Argo CD ist ein Kubernetes Controller der ständig eine laufende Applikation überwacht und den aktuellen Livestand eines Codes gegen einen gewünschten Stand abgleicht, wie er in einem Git-Repository definiert ist (hier kann auch der neue Softwarecode enthalten sein). Abweichungen kann Argo CD entweder automatisiert beheben oder visualisiert Abweichungen für Entwickler, damit diese schnell manuell behoben werden können
:::</p>
<p>:::globalTitle{:size="md" :tag="h3" .mb-5}
Technologien zum Geheimnismanagement
:::
:::globalTitle{:size="sm" :tag="h4" .mb-5}
Geheimhaltung und Verschlüsselung von Daten
:::
:::globalParagraph
Bereits kleine Projekte benötigen für den Betrieb einige Daten, die geheim bleiben müssen und nur denjenigen Personen/Apps verfügbar sind, die sie auch wirklich brauchen. Dazu gehören unter anderem Passwörter für die Autorisierung bei anderen Services (Datenbank, <a href="/loesungen/api-entwicklung/">API</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}
, etc.) oder Keys für die Verschlüsselung von gespeicherten Daten. Da diese nicht in falsche Hände gelangen sollten, dürfen sie nicht unverschlüsselt (plain-text) in die versionierten Kubernetes-Ressourcen (Kustomize-Manifeste, Helm-Charts, …) geschrieben werden. Es gibt verschiedene Tools, die das Management von solchen Geheimnissen ermöglichen und dabei unterschiedliche Ansätze verfolgen.
:::</p>
<p>:::globalTitle{:size="sm" :tag="h4" .mb-5}
<strong>Welche Tools gibt es?</strong>
:::
:::globalParagraph
<strong>Secrets Plugin für Helm</strong> verschlüsselt Werte in den Helm-YAML-Dateien lokal mithilfe eines Keys (z.B. mit Mozilla SOPS), der nicht im Repo lebt und den Bearbeitern auf anderem Wege gegeben wird. Versioniert werden dann nur die verschlüsselten Geheimnisse. Bei der Anwendung der Charts entschlüsselt das Plugin diese Werte und bringt so die geheimen Daten in den Cluster.
:::
:::globalParagraph
<strong>Bitnami Sealed Secrets</strong> verfährt ähnlich, verschlüsselt die geheimen Daten allerdings im Cluster und generiert eigene Objekte, vom Typ SealedSecret, die versioniert werden können und die bei der Anwendung der Ressourcen dann von einem Operator entschlüsselt und in "echte" Kubernetes Secrets umgewandelt werden.
:::
:::globalParagraph
Andere Tools/Technologien, welche allein oder im Zusammenspiel mit den genannten Tools genutzt werden können, sind z.B. <strong>HashiCorp Vault,</strong> <strong>Azure Key Vault</strong> and <strong>AWS Secrets Manager.</strong>
:::</p>
<p>:::globalTitle{:size="md" :tag="h3" .mb-5}
3.8 Technologien für Monitoring, Logging und Metrics Collection
:::
:::globalParagraph
Um in einem System mit vielen Bestandteilen den Überblick zu behalten, ist es sinnvoll, Logs und andere Daten, die Aufschluss über den Zustand einzelner Komponenten geben, an einer zentralen Stelle zusammenfließen zu lassen und übersichtlich aufzubereiten. Tools, welche in diesem Kontext genutzt werden, sind z.B. <strong>Prometheus</strong>, <strong>Open Telemetry</strong>, <strong>Grafana</strong>, <strong>Logstash.</strong>
:::</p>
<p><img src="/img/blogs/technology-ausserhalb-cluster.jpg" alt="technology_ausserhalb-_luster">{.object-cover .w-full .mb-6}</p>
<p>:::globalTitle{:size="lg" .mb-5}
Cloud Native Development
:::
:::globalParagraph
<a href="/loesungen/cloud-native-development/">Cloud Native Development</a>{.bs-link-blue} beschreibt einen Software-Entwicklungsansatz, bei dem Applikationen von Anfang an für den Einsatz in der Cloud konzipiert werden (Ionos). Folglich ist es sinnvoll, auch die Entwicklung selbst schon so weit wie möglich in der späteren Cloud-Umgebung stattfinden zu lassen.
:::
:::globalParagraph
Mit unserem Blueshoe-eigenen Technologie-Stack bestehend aus <a href="/technologien/#tools">Unikube, Gefyra und Getdeck</a>{.bs-link-blue} haben wir wesentlich dazu beigetragen, diesen Prozess für ganze Entwicklerteams effizienter und effektiver zu gestalten.
:::
:::globalParagraph
Trotzdem möchten wir nicht verschweigen, dass es zu unseren eigenen Produkten auch einige nennenswerte Konkurrenten gibt, wie beispielsweise <strong>Okteto</strong> und <strong>Skaffold</strong>.
:::
:::globalParagraph
Trotzdem sind wir von unseren Produkten überzeugt: Sie bieten genau das, was Entwicklerteams benötigen, sind von uns umfangreich erprobt und finden immer weitere Wege in die Integration in andere Tools: <a href="/technologien/#tools">Gefyra</a>{.bs-link-blue} ist mittlerweile zu einer eigenen Docker Desktop Extension geworden.
:::
:::globalParagraph
Wenn du neugierig auf unsere Produkte geworden bist und mehr erfahren möchtest, <a href="/kontakt/">kontaktiere uns</a>{.bs-link-blue}!
:::</p>]]></content:encoded>
            <category>Projekt Management</category>
            <enclosure url="https://blueshoe.de/img/blogs/kubernetes_gefyra_getdeck.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Effiziente Betriebszeiten mit KEDA: Dynamisches Autoscaling für Kubernetes-Cluster]]></title>
            <link>https://blueshoe.de/blog/kubernetes-autoscaling-keda</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/kubernetes-autoscaling-keda</guid>
            <pubDate>Fri, 20 Dec 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Kubernetes ist leistungsstark, doch ohne optimierte Betriebszeiten können unnötige Ressourcen und Kosten entstehen. KEDA (Kubernetes Event - Driven Autoscaling) ermöglicht es, Workloads dynamisch zu skalieren und z.B. außerhalb definierter Betriebszeiten zu pausieren. In diesem Blogpost zeigen wir, wie du mit KEDA deinen Cluster an Arbeitszeiten anpasst – für mehr Effizienz und reduzierte Hosting-Kosten.</p>
<p><img src="/img/blogs/kubernetes-keda.svg" alt="Effiziente Betriebszeiten mit KEDA">{.object-cover .max-w-full .mb-5}</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Einführung in Kubernetes Autoscaling
:::
:::GlobalParagraph
Kubernetes bringt bereits viel Automatismus und Möglichkeiten zur Effizienzoptimierung mit sich. Sei es das Zuweisen von Workloads zu den Nodes, Readiness- und Liveness - Probes, um hängende Container neu zu starten, oder ein dynamisches Hinzufügen oder Entfernen von Nodes mittels Cluster Autoscaler. Auch ein dynamisches Skalieren einzelner Workloads anhand der Ressourcen-Auslastung ist mittels Horizontal Pod Autoscaling möglich.
Doch damit lassen sich nicht alle Anforderungen abdecken. Was ist z.B. mit einem Cluster, der nur werktags zu den Arbeitszeiten benötigt wird? In diesem Blogpost betrachten wir, wie du Kubernetes Workloads mittels KEDA anhand eines zeitbasierten Schedules skalieren kannst.
:::</p>
<p>:::GlobalButton{:url="/technologien/docker-kubernetes/" :label="Erfahre mehr, wie wir dir mit Docker und Kubernetes behilflich sein können" :color="blue" .mb-6}
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Was ist KEDA?
:::</p>
<p>:::GlobalParagraph
KEDA greift den Gedanken des Pod Autoscaling auf und erweitert ihn um die Möglichkeit, nicht nur anhand der Ressourcenauslastung zu skalieren, sondern auch anhand allgemeinerer Events. Die Events können z.B. auf Datenbank-Queries basieren, auf Metriken eines Message-Brokers, auf einem Cron-Schedule und vielem mehr.
:::
:::GlobalParagraph
Derzeit unterstützt KEDA 71 sogenannte Scaler, die du als Basis für das eventbasierte Autoscaling verwenden kannst. Du musst dazu lediglich ein <code>ScaledObject</code> anlegen, eine <code>CustomResourceDefinition</code> von KEDA. Der keda-operator, der im Cluster deployt ist, erzeugt daraufhin dynamisch eine entsprechende <code>HorizontalPodAutoscaler</code> Ressource, um deine selektierten Workloads anhand der spezifizierten Events zu skalieren.
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Cron-Scaler um Betriebszeiten zu definieren
:::
:::GlobalParagraph
Um die Workloads eines Clusters außerhalb der Betriebszeiten auf 0 zu skalieren, nutzen wir den <a href="https://keda.sh/docs/2.16/scalers/cron/">Cron-Scaler</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}. Dieser ermöglicht es, ein Cron-Schedule mit Start- und Endzeit zu definieren, innerhalb dessen die Workloads auf eine gewünschte Anzahl an Replicas skaliert werden. Außerhalb des Schedules werden die Workloads auf die spezifizierte Mindestanzahl skaliert.
:::
:::GlobalParagraph{.mb-4}
Anhand eines exemplarischen <code>ScaledObjects</code> zum beschriebenen Szenario wirst du sehen, wie einfach die Konfiguration für unseren Anwendungsfall ist:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-yaml">apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: cron-scaledobject
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-deployment
  minReplicaCount: 0
  maxReplicaCount: 3
  cooldownPeriod: 60
  triggers:
    - type: cron
      metadata:
        timezone: Europe/Berlin
        start: 0 6 * * 1-5
        end: 0 20 * * 1-5
        desiredReplicas: '3'
</code></pre>
<p>:::</p>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Was passiert hier?
:::
:::GlobalParagraph
Das selektierte Deployment ist <code>my-deployment</code>, im <code>default</code>-Namespace. Der <code>minReplicaCount</code> ist 0, damit es außerhalb der Betriebszeiten auf 0 skaliert wird. Start und Ende der Betriebszeit sind mit den Cron-Schedules <code>0 6 * * 1-5</code>, bzw. <code>0 20 * * 1-5</code> spezifiziert. D.h. montags bis freitags zwischen 6:00 und 20:00 wird das Deployment auf die 3 Replicas skaliert, was durch den Parameter <code>desiredReplicas</code> angegeben ist. Dies sorgt für einen effizienteren Ressourcenverbrauch.
:::</p>
<p>::GlobalCallAnExpert{:bg="bg-bs-blue"}
#buttonBlockTitle
Wir können auch Deine Apps dynamisch an den Bedarf anpassen.
::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Wie hoch ist die Kostenersparnis?
:::
:::GlobalParagraph
Die wirklich spannende Frage ist: Wie groß ist der Effizienzgewinn und somit die Kostenersparnis durch die Umsetzung von Betriebszeiten? Das hängt ganz stark von deinem Setup ab.
:::</p>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Voraussetzung
:::
:::GlobalParagraph
Der Cluster Autoscaler muss aktiv sein, damit ungenutzte Nodes entfernt werden können. Die Höhe der Einsparungen hängt dann davon ab, wie viele Workloads außerhalb der Betriebszeiten pausiert werden können.
:::</p>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Beispiel
:::
:::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li>Wenn in deinem Cluster 10 Anwendungen laufen und nur eine außerhalb der Betriebszeiten skaliert wird, bleibt der Effekt gering.</li>
<li>Kannst du hingegen 9 Workloads pausieren, reduziert sich der Bedarf an Nodes erheblich – das spart spürbare Kosten.
:::</li>
</ul>
<p>:::GlobalTitle{:size="lg" .mb-5}
Fazit
:::
:::GlobalParagraph
KEDA macht es sehr einfach, Kubernetes-Workloads anhand zeitbasierter Events dynamisch zu skalieren. Die Installation und die Spezifikation der <code>ScaledObjects</code> sind unkompliziert und nehmen wenig Zeit in Anspruch.
:::
:::GlobalParagraph
Auch wenn die genaue Kostenersparnis nicht pauschal prognostiziert werden kann, lohnt sich der Einsatz von KEDA langfristig, wenn deine Workloads eine zeitbasierte Charakteristik aufweisen.
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5 .mt-8}
Häufige Fragen
:::</p>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
1. Was ist KEDA und wie unterscheidet es sich vom Kubernetes Horizontal Pod Autoscaler (HPA)?
:::
:::GlobalParagraph
<strong>KEDA (Kubernetes Event-Driven Autoscaling)</strong> erweitert das klassische Horizontal Pod Autoscaling (HPA) in Kubernetes. Während HPA Workloads basierend auf Ressourcen wie CPU- oder Speicherauslastung skaliert, ermöglicht KEDA das Autoscaling basierend auf externen Ereignissen. Dazu gehören Datenbank-Abfragen, Message-Broker-Metriken oder zeitgesteuerte Trigger. KEDA arbeitet ergänzend zum HPA, indem es externe Metriken übermittelt und so flexiblere Skalierungsmöglichkeiten schafft.
:::</p>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
2. Welche Vorteile bietet KEDA für das dynamische Autoscaling von Kubernetes-Workloads?
:::
:::GlobalParagraph
KEDA bietet folgende Vorteile:
:::
:::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li><strong>Flexibilität</strong>: Skalierung basierend auf externen Ereignissen (z. B. Kafka, Prometheus oder Azure Event Hubs).</li>
<li><strong>Zero Scaling</strong>: Workloads können auf 0 Pods reduziert werden, wenn keine Ressourcen benötigt werden.</li>
<li><strong>Einfache Integration</strong>: KEDA arbeitet nahtlos mit HPA zusammen und nutzt bestehende Kubernetes-Mechanismen.</li>
<li><strong>Kostenersparnis</strong>: Durch bedarfsgerechte Skalierung können unnötige Ressourcen vermieden werden.</li>
<li><strong>Breite Unterstützung</strong>: Mit über 70 Scalers ist KEDA vielseitig einsetzbar.
:::</li>
</ul>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
3. Wie funktioniert der Cron-Scaler von KEDA für zeitbasiertes Autoscaling?
:::
:::GlobalParagraph
Der <strong>Cron-Scaler</strong> in KEDA ermöglicht die zeitgesteuerte Skalierung von Workloads. Du definierst ein Cron-Schedule mit Start- und Endzeit sowie der gewünschten Anzahl an Replicas. Außerhalb dieses Zeitfensters werden Workloads auf die spezifizierte Mindestanzahl (z. B. 0 Pods) skaliert.
:::
:::GlobalParagraph
<strong>Beispiel:</strong> Ein Deployment kann von Montag bis Freitag zwischen 6:00 und 20:00 auf 3 Pods hochskaliert und außerhalb dieser Zeiten auf 0 reduziert werden.
:::</p>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
4. Welche Ereignisquellen (Scaler) unterstützt KEDA für das Autoscaling?
:::
:::GlobalParagraph
KEDA unterstützt über <strong>70 Scalers</strong>, darunter:
:::
:::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li><strong>Message-Broker</strong>: Kafka, RabbitMQ, Azure Event Hubs.</li>
<li><strong>Datenbanken</strong>: MySQL, PostgreSQL, MongoDB.</li>
<li><strong>Metrikquellen</strong>: Prometheus, AWS CloudWatch, Azure Monitor.</li>
<li><strong>Zeitbasierte Trigger</strong>: Cron-Schedules.</li>
<li><strong>Weitere</strong>: Redis, GitHub Actions, AWS SQS, und mehr.
:::
:::GlobalParagraph
KEDA lässt sich zudem durch benutzerdefinierte Scaler erweitern, um nahezu jede Ereignisquelle zu integrieren.
:::</li>
</ul>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
5. Was bedeutet Zero Scaling und wie hilft KEDA dabei?
:::
:::GlobalParagraph
<strong>Zero Scaling</strong> bedeutet, dass Workloads vollständig deaktiviert werden, indem ihre Anzahl an Pods auf 0 gesetzt wird. KEDA ermöglicht dies durch die Nutzung von Ereignisquellen, die bei Bedarf Pods aktivieren. Dies reduziert die Ressourcennutzung drastisch, wenn keine Ereignisse vorliegen, und hilft, Kosten zu sparen, während die Cluster-Effizienz maximiert wird.
:::</p>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
6. Ist KEDA für alle Kubernetes-Anwendungen geeignet oder gibt es Einschränkungen?
:::
:::GlobalParagraph
KEDA ist für viele Anwendungen geeignet, insbesondere für:
:::
:::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li><strong>Event-getriebene Anwendungen</strong> (z. B. Message-Broker, Datenbankoperationen).</li>
<li><strong>Workloads mit zeitbasierten Anforderungen</strong> (z. B. Geschäftszeiten).</li>
<li><strong>Metrikbasierte Anwendungen</strong>, die auf externe Signale reagieren.
:::
:::GlobalParagraph
<strong>Einschränkungen:</strong>
:::
:::GlobalBlock{.ul-disk .mb-4}</li>
<li>KEDA ist auf Anwendungen ausgelegt, die skalierbare Architekturen verwenden (z. B. Deployments, Jobs).</li>
<li>Workloads, die dauerhaft laufen müssen, profitieren weniger von KEDA, da Zero Scaling hier nicht möglich ist.</li>
<li>Dein Cluster benötigt einen aktiven Cluster Autoscaler, um Nodes bei Zero Scaling zu entfernen.
:::
:::GlobalParagraph{.mb-8}
<strong>Tipp:</strong> Für Anwendungen mit festen Ressourcenanforderungen oder kontinuierlicher Verfügbarkeit ist HPA allein möglicherweise ausreichend.
:::</li>
</ul>]]></content:encoded>
            <category>Kubernetes</category>
            <category>Entwicklung</category>
            <category>Betrieb</category>
            <enclosure url="https://blueshoe.de/img/blogs/kubernetes-keda.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Kubernetes Backup in Storage Buckets]]></title>
            <link>https://blueshoe.de/blog/kubernetes-backup-storage-bucket</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/kubernetes-backup-storage-bucket</guid>
            <pubDate>Thu, 03 Apr 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Wir nutzen neben Kubernetes auch Cloud Storage, Cloud SQL und GitHub Actions, um unsere Anwendungen effizient zu betreiben – und zu sichern. In diesem Artikel zeigen wir dir, wie du eine zuverlässige Kubernetes Backup Strategie umsetzt, um Datenbanken, Medien und Code vor Verlust zu schützen.</p>
<p><img src="/img/blog/k8s_backup_header1.png" alt="Kubernetes Backup in Storage Buckets">{.object-cover .max-w-full .mb-5}</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Warum sind Backups so wichtig?
::
::GlobalParagraph
Warum wir Backups grundsätzlich brauchen, muss ich - so hoffe ich - niemandem erklären. Tatsächlich gibt es aber neben den einfachen unternehmerischen Zielen auch betriebswirtschaftliche Bereiche, die sich dediziert mit dem Thema “Sicherung und Wiederherstellung” (und anderen) beschäftigen: Dem <em>Business Continuity Planning (<a href="https://en.wikipedia.org/wiki/Business_continuity_planning">BCP</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid})</em>. Darin werden auch die Begriffe “Resilienz” und “Kontinuität” eingeführt, welche im Zusammenhang mit IT-Systemen zwei wichtige Metriken definieren. Aber nicht nur unternehmerische Pläne sind zielgebend, sondern auch rechtliche Anforderungen müssen bedient werden (vor allem in Europa). So stellt die DSGVO, überall da wo personenbezogene Daten verarbeitet werden, mit ihrem Schutzziel der “Verfügbarkeit”, die im engen Zusammenhang mit der “Vertraulichkeit” und “Integrität” stehen, ebenso hohe Anforderungen an technische Maßnahmen und Prozesse. Ähnliches gilt für nationale Werbemittel-Gesetze, die beispielsweise auch Fristen für die Dauer der Aufbewahrung vorgeben.
::
::GlobalParagraph
Leider hat die jüngere Geschichte gezeigt, dass <a href="https://www.linkedin.com/pulse/faq-buchbinder-data-breach-from-data-protection-stefan-hessel/">Backups und das damit verbundene “Disaster Recovery”</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} immer noch nicht den Stellenwert genießen, die es eigentlich haben sollte. Es ist natürlich nachvollziehbar, dass Strategien zur Adressierung der oben beschriebenen Punkte keinen direkten positiven Einfluss auf den Unternehmenserfolg haben und daher intuitiv als zweitrangig bewertet werden. Aber resiliente Unternehmen, die eine gewisse Immunität gegenüber Störungen in ihren Abläufen entwickelt haben (sei es durch eigenes personelles Versagen, oder Einflüsse von außen, z.B. Hacker-Angriffe) und für ihre eigene Kontinuität sorgen, werden langfristig erfolgreicher sein. Denn der nächste Ausfall kommt bestimmt…
::</p>
<p>::GlobalButton{:url="/technologien/docker-kubernetes/" :label="Arbeitest du bereits mit Kubernetes? Schau dir unsere Lösungen für eine effiziente Infrastruktur an!" :color="blue" .mb-6}
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Backup-Methoden im Überblick
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Snapshots – Die schnelle und einfache Lösung
::</p>
<p>::GlobalParagraph
Starten wir mit etwas Background: Je nachdem, welche Anforderungen konkret gestellt sind, muss die Backup-Strategie angepasst werden. Die Basis stellen aber in jedem Fall regelmäßige <em>Snapshots</em> dar: Diese sichern einmal den kompletten Datenbestand zum Zeitpunkt der Snapshot-Anforderung. Snapshots sind in der Regel einfach zu Organisieren, verbrauchen aber den meisten Speicherplatz und bieten dafür die schnellste und sicherste Form zur Wiederherstellung, da man mit Snapshots einen IT-Dienst 1-zu-1 starten kann. Die gesetzte Regelmäßigkeit für ein Snapshot-Backup stellt dann auch direkt den maximalen Zeitraum für einen mittelbar zulässigen Datenverlust im Falle einer Störung dar.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Differentielle Backups – Platzsparender, aber komplexer
::</p>
<p>::GlobalParagraph
Zusätzlich zu den Snapshost können differentielle Backups erstellt werden. Diese sichern nur die Daten zwischen einem Snapshot (auch “Baseline”) und dem Zeitpunkt der Erstellung des differentiellen Backups. Je nach betroffenem IT-Dienst stellt das differentielle Backup aber bereits einen sehr viel größeren Aufwand zur Erstellung, Verwaltung und Wiederherstellung dar. Dafür kann aber Speicherplatz gegenüber einem Snapshot, und damit langfristig Kosten, gespart werden.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Point-in-Time Backups – Maximale Flexibilität
::</p>
<p>::GlobalParagraph
Punkt in der Vergangenheit wiederherzustellen. Diese Form der Backups ist der Goldstandard, aber in komplexen (verteilten) IT-Systemen möglicherweise nicht zweifelsfrei zu erreichen. Dabei wird jede Transaktion, die das System in jeder beliebigen Form verändert, bereits zur Zeit der Bestätigung zusätzlich gesichert. Erfahrungsgemäß ist diese Backup-Strategie die aufwändigste. Es ist sehr anspruchsvoll, Point-in-Time Backups zu erstellen, diese zu organisieren und auch die Wiederherstellung von Systemen dauert entsprechend lange und ist aufwändig. Denn: Zunächst muss eine Baseline (aus einem Snapshot) eingespielt und danach alle Transaktionen in einem “Replay”-Verfahren in der korrekten Reihenfolge angewendet werden. Sollten dabei Bedingungen außerhalb der Systemgrenzen auftreten, die zum Zeitpunkt der Wiederherstellung nicht mehr gegeben sind (z.B. Daten in einem anderen Dienst haben sich verändert), kann auch der Wiederherstellungsprozess gestört werden.
::</p>
<p>::GlobalParagraph
Da wir in jedem Falle einen guten Plan für Snapshot-Backups brauchen, konzentrieren wir uns in diesem Artikel, basierend auf dem Tech-Stack: Kubernetes, Google Cloud Storage, Cloud SQL und GitHub Actions, wie wir verlässliche Backups erstellen können. Ziel ist also, alles zu sichern, was notwendig ist, um den Betrieb eines IT-Dienstes aus einem Backup wiederherzustellen.
::</p>
<p>::GlobalButton{:url="/technologien/docker-kubernetes/" :label="Setzt du bereits auf Docker? Damit kannst du dein Backup-Handling noch weiter optimieren!" :color="blue" .mb-6}
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Ziel der Datenträger: Cloud Storage
::
::GlobalParagraph
Fangen wir mal von hinten an: Wo sollen die Backups gespeichert werden? Cloud Storage eignet sich für viele Arten von Anwendungen. Zum einen können wir produktive (online, transaktionale) Daten speichern und ausliefern (z.B. Assets von einer Website wie Bilder oder Dokumente). Zum anderen können wir ihn als Transitmedium verwenden für den Austausch zwischen einem Quell- und Zielsystem - oder auch für die langfristige Lagerung von Daten. In den meisten Fällen endet aus unserer Sicht die Reise für das Backup hier, aber für das Backup geht es oftmals noch weiter in unternehmensnahe Speichermedien für eine redundante Ablage.
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Die Einrichtung aller notwendiger Backups
::
::GlobalParagraph
Was soll denn eigentlich gesichert werden? Wir sprechen immer von <em>Assets</em> (Bilder, Videos, Dokumente, alle “beweglichen” Daten außerhalb der Datenbank, die nicht Teil der Code-Basis sind), den <em>Datenbanken</em> (also alle persistenten Datenspeicher) und dem Quellcode für die Anwendung. Darauf aufbauend gibt es immer noch reproduzierbare Artefakte, die auf Basis der bevorstehenden Daten erstellt werden können, z.B. der Inhalt eines <em>Caches</em>, Software Container Images (z.B. Docker). Diese sind nicht Teil der Backup-Strategie, weil sie entweder recht schnell und einfach neu aufgebaut werden können (z.B. ein Zwischenspeicher) oder oft auch sehr sicher aufbewahrt werden (z.B. Software Container Registries).
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Speicherort für Backups: Cloud Storage nutzen
::</p>
<p>::GlobalParagraph
Es macht Sinn, auch den transaktionalen Cloud Storage zu sichern. Warum? Möglicherweise wurde ein <em>Bucket</em> nicht hochverfügbar konfiguriert (redundante Ablage aller Daten) oder das <em>BCG</em> (siehe oben) sieht auch die Absicherung gegen den Ausfall des Cloud-Providers vor. Alle typischen Cloud-Anbieter sehen hier bereits eine eingebaute und einfache Möglichkeit vor. Am Beispiel der Google Cloud kann das Backup eines Buckets in ein anderes Bucket per <em>ClickOps</em> einmalig eingerichtet werden.
::</p>
<p><img src="/img/blog/Backup-Storage-Transfer.png" alt="Backup Storage Transfer">{.object-cover .max-w-full .mb-5}</p>
<p>::GlobalParagraph
Mithilfe von <a href="https://console.cloud.google.com/transfer/">Google Transfer</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} können also ganz einfach Quellen, Ziele und Ausführungszeitpunkte eingestellt werden. Daneben gibt es noch weitere Einstellungen, z.B. wie mit gelöschten Daten umgegangen werden soll.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Datenbank Backup in Kubernetes mit Cloud SQL
::
::GlobalParagraph
Interessanter wird es mit Cloud SQL Backups. Diese werden erfahrungsgemäß nicht “einfach“ zur Verfügung gestellt. Für eine PostgreSQL-kompatible Datenbank-Instanz kann also nicht per on-board Mittel ein <a href="https://www.postgresql.org/docs/current/backup-dump.html">SQL-Dump</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} erstellt werden (weder in der Google Cloud, noch bei AWS). Ich vermute, die vereinfachte Migration zu anderen Service-Anbietern steckt hier hinter dem fehlenden Dienst. Statt eines portablen SQL-Dumps kann also nur eine Cloud-proprietäre Backup-Lösung genutzt werden. Aber wir haben ja einen Kubernetes Cluster mit Zugriff auf die betreffenden Datenbanken verfügbar.
::</p>
<p>::GlobalParagraph
Benötigt wird also ein Kubernetes <em>CronJob</em> der sich im gewünschten Intervall zur Datenbank verbindet und aus dieser ein Snapshot erstellt. Das ist glücklicherweise recht einfach.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Dockerfile für PostgreSQL Backup:
::</p>
<p>::GlobalParagraph
Als Basis nutzen wir das offizielle <em>postgres</em>-Image. Das funktioniert so aber auch mit fast allen anderen (No-)SQL-Datenbanken mit offiziellen Clients.
::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-docker"># --- Dockerfile ---
FROM postgres:latest-alpine

RUN apk add --update curl zip python3
RUN curl -sSL https://sdk.cloud.google.com | bash
ENV PATH $PATH:/root/google-cloud-sdk/bin

COPY backup.sh /
RUN chmod +x /backup.sh
</code></pre>
<p>:::</p>
<p>::GlobalParagraph
Dieses container image ist bereits mit der <code>psql</code>-Client Anwendung vorbereitet. Zusätzlich benötigen wir aber noch die Tools, um Daten auf einem Google Cloud Storage abzulegen, sowie das <em>Bash</em>-Script, das das Backup anfordert und ablegen kann. Dieses container image muss gebaut und zugänglich gemacht werden.
::</p>
<p>::BlogCode{.mb-4}</p>
<pre><code class="language-bash"># --- backup.sh ---

#!/bin/bash

if [ ! -z $DEBUG ]; then
    set -x
fi

# ENV variables for Postgres
HOSTNAME=$PG_HOSTNAME
PASSWORD=$PG_PASSWORD
USERNAME=$PG_USERNAME
DATABASE=$PG_DATABASE
OUTPUT_DIR="${PG_OUTPUT_DIR:-/pgbackup}"
NAME_PREFIX="${PG_PREFIX:-noprefix}"
ZIP_PASSWORD="${ZIP_PASSWORD:-setme}"
if [ $ZIP_PASSWORD = "setme" ]; then
    ZIP_PASSWORD=`cat /etc/gcp/zip-password`
fi
GS_STORAGE_BUCKET="${GS_BUCKET:-nonpublic}"

gcloud auth activate-service-account --key-file /etc/gcp/sa_credentials.json

date1=$(date +%Y%m%d-%H%M)
mkdir $OUTPUT_DIR
filename=$OUTPUT_DIR"/"$date1"-$NAME_PREFIX-$DATABASE.pg"

PGPASSWORD="$PASSWORD" pg_dump -h "$HOSTNAME" -p 5432 -U "$USERNAME" "$DATABASE" -Fc > $filename

du -h $filename

zip -r --encrypt -P $ZIP_PASSWORD $filename".zip" $filename
du -h $filename".zip"

gcloud storage cp $filename".zip" "gs://"$GS_STORAGE_BUCKET"/database/"$NAME_PREFIX"/"

</code></pre>
<p>::</p>
<p>::GlobalParagraph
Dieses beispielhafte Bash-Script wird ausschließlich per Umgebungsvariablen gesteuert. Diese werden per Kubernetes Workload-Beschreibung eingebracht, z.B. über ein <a href="https://kubernetes.io/docs/concepts/configuration/secret/">Kubernetes secret</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}.
Das Script unterstützt alle notwendigen Parameter um:
::</p>
<p>::GlobalBlock{.ol-decimal .mb-4}</p>
<ol>
<li>sich zu einer PostgreSQL-Datenbank zu verbinden,</li>
<li>von dieser einen SQL-Dump (im portablen Postgres-Format) anzufordern,</li>
<li>diesen SQL-Dump zusätzlich per Zip-Passwort zu verschlüssel und nachvollziehbar zu benennen,</li>
<li>und schlussendlich in einen Cloud Storage Bucket hochzuladen.
::</li>
</ol>
<p>::GlobalParagraph
<strong>Wichtig:</strong> In diesem Beispiel wird ein <a href="https://cloud.google.com/iam/docs/service-account-overview"><em>Service Account</em></a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} genutzt um Daten in das “-nonpublic”-Cloud Storage Bucket zu speichern. Hier unterscheiden sich die einzelnen Cloud-Anbieter in der Einrichtung des Service Accounts. In Google Kubernetes Engine wird anschließend das <em>Service Account Token</em> in einem Kubernetes secret hinterlegt und dann an den entsprechend Pod angehängt (unter <em>/etc/gcp/sa_credentials.json</em>).
::</p>
<p>::GlobalCallAnExpert{:bg="bg-bs-blue"}
#buttonBlockTitle
Du willst deine Backup-Strategie in Kubernetes richtig aufsetzen oder optimieren?
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Kubernetes CronJob zur Automatisierung
::</p>
<p>::GlobalParagraph
Mit folgendem Kubernetes Workload-Objekt können wir schlussendlich einen regelmäßigen Job starten, der das Datenbank Backup nachvollziehbar ablegt. Die Notation von Kubernetes CronJobs kann in der <a href="https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/">Kubernetes Dokumentation nachgeschlagen</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} werden.
::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">apiVersion: batch/v1
kind: CronJob
metadata:
  name: pg-backup
spec:
  timeZone: "Europe/Berlin"
  schedule: "45 1 * * *"
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 0
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      template:
        spec:
          # this secret is manually created holding 1) the service account key, and 2) the zip-password string
          volumes:
            - name: gcs-service-account
              secret:
                secretName: gcsbackup
          containers:
            - name: pgbackup
              image: gcr.io/backup-images/pgbackup
              imagePullPolicy: Always
              command: ["/backup.sh"]
              volumeMounts:
                - mountPath: "/etc/gcp/"
                  name: gcs-service-account
                  readOnly: true
              env:
                - name: PG_DATABASE
                  valueFrom:
                    secretKeyRef:
                      name: shop-secret
                      key: DATABASE_NAME
                - name: PG_USERNAME
                  valueFrom:
                    secretKeyRef:
                      name: shop-secret
                      key: DATABASE_USER
                - name: PG_PASSWORD
                  valueFrom:
                    secretKeyRef:
                      name: shop-secret
                      key: DATABASE_PASSWORD
                - name: PG_HOSTNAME
                  valueFrom:
                    secretKeyRef:
                      name: shop-secret
                      key: DATABASE_HOST
                - name: PG_PREFIX
                  value: production
                # - name: DEBUG
                #   value: "on"
          restartPolicy: OnFailure
</code></pre>
<p>:::
::GlobalParagraph
Auf diese Art und Weise können alle Parameter (z.B. die Backup-Kadenz, Zugänge) einfach per Kubernetes hinterlegt werden. Insbesondere bei einer Rotation des Passworts benötigt es also lediglich eine Anpassung im entsprechenden Kubernetes secret.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Code Backup mit GitHub Actions
::</p>
<p>::GlobalParagraph
Nicht nur die beweglichen Daten müssen gespeichert werden, sondern auch die Code-Basis. Warum? Möglicherweise müssen <em>statics</em> (also Medien-Dateien, die Teil des Code sind, Logos, kleine Bilder, etc.) abgelegt werden oder aber die <em>BCP</em> sieht eben auch einen Plan für den Ausfall von GitHub vor. Natürlich ist von einem Ausfall von GitHub noch nicht der unmittelbare Betrieb der Anwendung betroffen, aber wenn es zu einem längeren Ausfall kommt, muss auch hier die Kontinuität sichergestellt werden.
::</p>
<p>::GlobalParagraph
Glücklicherweise ist das Einrichten einer Backup-Action für ein Repository schnell eingerichtet.
::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-yaml"># --- .github/workflows/code_backup.yaml ---

name: Backup Repo to Google Storage Bucket
on:
  schedule:
    - cron: '0 0 * * 0'
  push:
    branches:
      - main
jobs:
  backup_repo:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write

    steps:
      - name: Checkout Repo
        uses: actions/checkout@v4

      - id: auth
        uses: google-github-actions/auth@v2
        with:
          credentials_json: ${{ secrets.GCP_CREDENTIALS }}

      - name: Set up Cloud SDK
        uses: google-github-actions/setup-gcloud@v2

      - name: Zip and Upload
        run: 'cd .. &#x26;&#x26; zip --encrypt -P ${{ secrets.ZIP_PASSWORD }} -r our-backend.zip our-backend &#x26;&#x26; gcloud storage cp our-backend.zip gs://-nonpublic/code/our-backend.zip'
</code></pre>
<p>:::</p>
<p>::GlobalParagraph
Alle typischen Cloud-Anbieter stellen bereits eigene GitHub <em>Actions</em> bereit, um in einer GitHub Pipeline automatisch Daten in einem Cloud Storage Bucket abzulegen. In diesem Beispiel wird das Repository also ausgecheckt, auch wieder Zip-verschlüsselt und schließlich in das Cloud Storage Bucket hochgeladen.
::</p>
<p><img src="/img/blog/Action-Secrets.png" alt="Action Secrets">{.object-cover .max-w-full .mb-5}</p>
<p>::GlobalParagraph
Die verwendeten Umgebungsvariablen werden per <em>Action Secrets</em> in den Kontext eingefügt.
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Fazit: Kubernetes Backup Strategie für maximale Sicherheit
::
::GlobalParagraph
Backups sind wichtig. In diesem Artikel habe ich einen Weg gezeigt, wie man mit Kubernetes, GitHub und einem Cloud Storage Bucket schnell und einfach ein Snapshot-Backup realisieren kann. Diese Backups können nun aus dem Cloud Storage wieder heruntergeladen werden, um sie auf anderen Speichermedien für die vorgegebene Zeit (und am besten nicht online) und für den Fall einer nötigen Wiederherstellung zu bewahren.
::</p>
<p>::GlobalParagraph
Das Vorgehen, um eine Wiederherstellung durchzuführen, haben wir in diesem Artikel bewusst nicht behandelt, wollen das aber in einem der folgenden Artikel nachholen.
::</p>
<p>::GlobalParagraph
Auch die Möglichkeiten von differentiellen Backups und Point-in-Time Backups wären eine sinnvolle Ergänzung.
::</p>
<p>::GlobalParagraph
Aber was denkst du? Hast du Verbesserungsvorschläge oder bereits Erfahrungen mit diesem System gemacht? Lasst es uns unten in den Kommentaren wissen!
::</p>
<p>:::GlobalTitle{:size="lg" .mb-5 .mt-8}
Häufige Fragen
:::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
1. Warum brauche ich eine Backup-Strategie für Kubernetes?
::
::GlobalParagraph
Kubernetes-Umgebungen sind dynamisch und bestehen aus vielen beweglichen Teilen. Datenbankeinträge, Code-Änderungen und Mediendateien können jederzeit verloren gehen – sei es durch Fehlkonfigurationen, Systemausfälle oder Cyberangriffe. Eine durchdachte Backup-Strategie stellt sicher, dass du deine Anwendungen und Daten schnell wiederherstellen kannst.
::
::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
2. Wie oft sollte ich ein Backup meiner Datenbank machen?
::
::GlobalParagraph
Das hängt von deinen Anforderungen ab. Eine Snapshot-Strategie mit täglichen Backups ist für viele Anwendungen ein guter Start. Falls du eine hohe Datenänderungsrate hast, solltest du differenzielle oder Point-in-Time Backups in Betracht ziehen. Kubernetes CronJobs helfen dir, diese Automatisierungen effizient umzusetzen.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
3. Wo sollte ich meine Backups speichern?
::
::GlobalParagraph
Cloud Storage ist eine der besten Optionen für sichere und skalierbare Backups. Google Cloud Storage, AWS S3 oder Azure Blob Storage bieten hohe Verfügbarkeit und integrierte Verschlüsselung. Alternativ kannst du Backups auf lokalen Servern oder externen Speichermedien ablegen, um eine zusätzliche Sicherheitsebene zu schaffen.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
4. Kann ich mit GitHub Actions auch meinen Code automatisch sichern?
::
::GlobalParagraph
Ja! GitHub Actions kann genutzt werden, um automatische Code-Backups in ein Cloud Storage Bucket zu laden. So bist du abgesichert, falls es zu einem GitHub-Ausfall oder versehentlichen Löschvorgängen kommt. Unser Artikel enthält eine Beispiel-Workflow-Datei für dein Repository.
::</p>]]></content:encoded>
            <category>Kubernetes</category>
            <category>Docker</category>
            <category>Betrieb</category>
            <enclosure url="https://blueshoe.de/img/blog/k8s_backup_header1.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Wie geht Kubernetes Development?]]></title>
            <link>https://blueshoe.de/blog/kubernetes-development</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/kubernetes-development</guid>
            <pubDate>Thu, 14 May 2020 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>"Kubernetes Development" a.k.a. “Entwicklung containerisierter Micro-Services in einem lokalen Kubernetes Cluster” bedeutet, dass Anwendungen für eine Kubernetes-Architektur konzipiert und entwickelt werden, also ein Entwickler auch lokal mit einer Kubernetes-Architektur arbeitet. In diesem Blogpost zeigen wir euch, wie wir bei Blueshoe diese Aufgabe bewerkstelligen.</p>
<p><img src="/img/blogs/joseph-barrientos.jpg" alt="joseph-barrientos">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
“Cloud Native Kubernetes Development” a.k.a. wie kann ich möglichst <strong>viele techy Buzzwords</strong> in einen kurzen Blogtitel verpacken? Eigentlich möchte man auch noch “k8s” unterbringen, was als Kurzform für “Kubernetes” verwendet wird, aber wir wollen es auch nicht übertreiben. Um diesen Blogpost zu verstehen, wird ein gewisses <strong>Grundverständnis von Kubernetes</strong> vorausgesetzt. Falls das nicht gegeben ist, können wir diesen Comic von Google empfehlen.
:::</p>
<p><img src="/img/blogs/bildschirmfoto_3.jpg" alt="bildschirmfoto_3">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Nehmen wir an, du entwickelst ein neues Projekt, hast ein paar unabhängige Services darin identifiziert und bist zu dem Entschluss gekommen, dass es durchaus Sinn macht, diese in <strong>separate Container</strong> zu deployen und von <strong>Kubernetes orchestrieren</strong> zu lassen. Da es ein größeres Projekt ist, arbeiten mehrere Programmierer daran, die jeder für sich oder in kleinen Teams nur an je einem der Services arbeiten.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
<strong>STATUS-QUO</strong>
:::
:::globalParagraph
Das gerade beschriebene <strong>Projektbeispiel</strong> hat sich mittlerweile zu einem durchaus üblichen Szenario entwickelt. Wie können wir nun gewährleisten, dass unsere Programmierer auch auf ihrem lokalen Laptop möglichst äquivalent zur Kubernetes-Architektur entwickeln können? Ein üblicher Weg, um Docker-Container lokal auszuführen ist <em><strong>docker-compose</strong></em>. Dieses ist vor allem in der Handhabung sehr einfach, hat aber den großen Nachteil, dass ein docker-compose-Setup nicht die letztendliche Production-Umgebung, also das Kubernetes-Setup abbildet. Im schlimmsten Fall hast du also etwas programmiert, das lokal in deinem docker-compose-Setup funktioniert, im Production-System dann aber nicht, da das Image anders ausgeführt wird.
:::
:::globalParagraph
Als Alternative wurden Technologien entwickelt, um Kubernetes Cluster auf dem lokalen Rechner zu simulieren. <em><strong>Minikube</strong></em> ist eine recht verbreitete Lösung, in letzter Zeit sind aber auch mehr und mehr Alternativen auf dem Vormarsch. Hier sind z.B. <em><strong>microk8s</strong></em> von Canonical zu nennen, oder <em><strong>k3s</strong></em> und <em><strong>k3d</strong></em> von Rancher, welche ressourcenschonender sind. K3d benutzt k3s, um mehrere <em><strong>Worker Nodes</strong></em> im lokalen Kubernetes-Cluster zu simulieren. Üblicherweise wird dann <em><strong>kubectl</strong></em> benutzt, um mit dem Cluster zu interagieren.
:::
:::globalParagraph
Als Entwickler musst du nun lediglich ein Docker-Image deines Services builden und deinen Kollegen zur Verfügung stellen. Diese können das Image in ihrem lokalen Cluster deployen und haben daraufhin den aktuellsten Stand deines Services lokal zur Verfügung.
:::</p>
<p>:::globalTitle{:size="md" :tag="h4" .mb-5}
<strong>ZWEI SPANNENDE HERAUSFORDERUNGEN SIND ZU DIESEM ZEITPUNKT ALLERDINGS NOCH OFFEN:</strong>
:::
:::GlobalBlock{.ol-decimal .mb-5}</p>
<ol>
<li>Wie kann ich an meinem Service arbeiten und stets den aktuellen Stand in meinem Cluster zur Verfügung haben, ohne ein neues Image builden und deployen zu müssen?</li>
<li>Wie kann ich lokal den Debugger verwenden?
:::</li>
</ol>
<p>:::globalTitle{:size="lg" .mb-5}
<strong>KUBERNETES (CLOUD NATIVE) DEVELOPMENT BEI BLUESHOE</strong>
:::
:::globalParagraph
In den nächsten Abschnitten wollen wir uns anschauen, wie wir bei Blueshoe diese Herausforderungen meistern. Wir setzen dabei auf <strong><em>k3d</em></strong> als <strong>lokalen Kubernetes-Cluster</strong> sowie <em><strong>PyCharm</strong></em> als unsere <strong>Entwicklungsumgebung</strong>. Weiterhin nutzen wir <strong><em>Helm</em></strong> für das <strong>Management des Clusters</strong> sowie <strong><em>Telepresence</em></strong> um <strong>live Code-Reloading</strong> zu bewerkstelligen. Die folgenden Installationsbeispiele wurden alle auf einem aktuellen Ubuntu-System durchgeführt.
:::</p>
<p>:::globalTitle{:size="md" .mb-5}
<strong>K3D/K3S - “LIGHTWEIGHT KUBERNETES IN DOCKER”</strong>
:::
:::globalParagraph
k3d lässt sich sehr einfach installieren, Rancher stellt ein Installations-Script zur Verfügung:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">wget -q -O - https://raw.githubusercontent.com/rancher/k3d/master/install.sh | bash
</code></pre>
<p>:::</p>
<p>:::globalParagraph
Die Installation von k3s ist ebenso simpel:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">curl -sfL https://get.k3s.io | sh -
</code></pre>
<p>:::</p>
<p>:::globalParagraph
Ein neuer Cluster lässt sich mit folgendem Befehl anlegen:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">k3d create --api-port 6550 --publish 8080:80 --workers 2 --name buzzword-counter --enable-registry
</code></pre>
<p>:::
:::globalParagraph
Wir haben hier einen Cluster namens <strong>buzzword-counter</strong> erzeugt und u.a. den lokalen Port 8080 auf den Cluster-internen Port 80 gemappt, sodass wir unseren Cluster im Webbrowser unter dem Port 8080 erreichen können. Zusätzlich ermöglichen wir mit der Flag <em><strong>--enable-registry</strong></em>, dass lokale Docker-Images im Cluster deployed werden können. Die <em><strong>lokale Registry</strong></em>  ist ein gewöhnlicher Docker-Container, der sich z.B. nach einem Neustart des Rechners mit <em><strong>"docker restart &#x3C;>"</strong></em> wieder starten lässt. Dafür benötigen wir zusätzlich einen entsprechenden Eintrag in unserer <em><strong>/etc/hosts</strong></em> Datei:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">[...]
127.0.0.1     registry.local
[...]
</code></pre>
<p>:::
:::globalParagraph
Damit wir mittels kubectl mit unserem Cluster interagieren können, können wir entweder die <strong>KUBECONFIG-Umgebungsvariable</strong> exportieren oder den Inhalt der entsprechenden Datei in <em><strong>~/.kube/config</strong></em> integrieren:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">export KUBECONFIG="$(k3d get-kubeconfig --name='buzzword-counter')"
</code></pre>
<p>:::</p>
<p>:::globalTitle{:size="md" .mb-5}
<strong>HELM - “KUBERNETES PACKAGE MANAGER”</strong>
:::
:::globalParagraph
Wir benutzen oftmals Helm, um unsere Kubernetes Cluster zu managen. Helm beschreibt sich selbst als Package Manager für Kubernetes und ermöglicht auch die <strong>Abbildung komplexer Kubernetes Anwendungen in Templates.</strong> Das Buzzword lautet hier <strong>“Infrastructure as Code”.</strong> Durch die Templates kann unsere Anwendung jederzeit ohne größeren Aufwand in einen neuen Kubernetes Cluster deployed werden. Um Helm zu installieren, kann man einfach eine Binary-Datei herunterladen: Zum Download.
:::</p>
<p>:::globalTitle{:size="sm" :tag="h4" .mb-5}
<strong>BEISPIEL-DEPLOYMENT BUZZWORD-COUNTER</strong>
:::
:::globalParagraph
Um ein praktisches Beispiel zeigen zu können, haben wir ein simples Deployment für diesen Blog-Post erstellt und auf Github bereitgestellt:
:::
:::globalParagraph
BUZZWORD COUNTER
:::
:::globalParagraph
BUZZWORD CHARTS
:::
:::globalParagraph
Dieses Deployment enthält eine einfache Django-Anwendung, eine <em><strong>Celery Distributed Task Queue</strong></em> mit <em><strong>rabbitmq</strong></em> als Message Broker, <strong>um asynchrone Tasks abzuarbeiten</strong>, sowie eine <strong>PostgreSQL Datenbank</strong>. Mittels unserer Anwendung können wir Buzzwords zählen und auch neue Buzzwords hinzufügen. Letzteres ist als Celery-Task implementiert, was in diesem Beispielszenario zwar recht unnötig ist, aber die Funktionsweise unserer Celery Distributed Task Queue bestens demonstriert.
:::
:::globalParagraph
Der erste Schritt des Deployments ist das Bereitstellen der Anwendung als Docker-Image. Dazu müssen wir zunächst das <strong>Docker-Image</strong> builden (aus dem Verzeichnis der Django Anwendung) und in unsere lokale Registry pushen:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">docker build -t registry.local:5000/buzzword-counter:0.1.0 .
docker push registry.local:5000/buzzword-counter:0.1.0
</code></pre>
<p>:::
:::globalParagraph
Mit folgenden Befehlen (aus dem Verzeichnis der Helm Charts) wird die Anwendung mit der PostgreSQL- und der rabbitmq-Dependency im Kubernetes-Cluster installiert und konfiguriert:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">helm repo add bitnami https://charts.bitnami.com/bitnami
helm dep build buzzword-counter
helm install buzzword-counter buzzword-counter/
</code></pre>
<p>:::
:::globalParagraph
Mittels kubectl können wir uns z. B. anschauen, ob die Pods verfügbar sind, oder auch den Log-Output anzeigen lassen und verifizieren, dass einerseits der Runserver auf dem Web-Pod sowie der Celery-Worker auf dem Worker-Pod gestartet wurde:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-bash">kubectl get deployments
kubectl get pods
kubectl logs -f buzzword-counter-web-XXXXX-XXXXXXXX
kubectl logs -f buzzword-counter-worker-XXXXX-XXXXXXXX
</code></pre>
<p>:::
:::globalParagraph
Nun haben wir also einen lokalen Kubernetes-Cluster, in dem unsere Anwendung installiert und konfiguriert ist. Damit wir unsere Seite lokal erreichen können, müssen wir noch folgende drei Befehle ausführen, die uns der Output von helm install ausgegeben hat:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-bash">export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=buzzword-counter,app.kubernetes.io/instance=buzzword-counter" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace default port-forward $POD_NAME 8080:80
</code></pre>
<p>:::
:::globalParagraph
Daraufhin können wir unseren Service auf der angegebenen URL erreichen. Starten wir einen Task, können wir uns dessen Output mittels kubectl im Log des Worker-Pods ansehen:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-bash">kubectl logs -f buzzword-counter-worker-XXXXX-XXXXXXXX
</code></pre>
<p>:::</p>
<p><img src="/img/blogs/pasted_image.jpg" alt="pasted_image">{.object-cover .w-full .mb-5}</p>
<p>:::globalTitle{:size="md" .mb-5}
<strong>TELEPRESENCE - “FAST, LOCAL DEVELOPMENT FOR KUBERNETES”</strong>
:::
:::globalParagraph
Um <strong>live Code-Reloading</strong> zu erreichen, d.h. um Code-Änderungen, die wir in PyCharm tätigen, sofort im Cluster verfügbar zu haben, verwenden wir Telepresence. Telepresence ist ein sogenanntes <strong>Sandbox-Projekt der CNCF</strong>, der <strong>Cloud Native Computing Foundation</strong>. Ohne live Code-Reloading müssten wir nach jeder Änderung ein neues Docker-Image builden und im Cluster deployen, was umständlich und auf Dauer zeitintensiv ist.
:::
:::globalParagraph
Mittels <em>Telepresence</em> kannst du nun ein Docker-Image, das du lokal gebaut hast, in einem Cluster ausführen, indem ein <strong>Deployment “geswapt”</strong> wird. Technisch ist das durchaus spektakulär, für diesen Blog-Post genügt es jedoch, dass wir mit einem Command unser <strong>Buzzword-Counter-Web-Deployment</strong> unseres Kubernetes-Clusters austauschen können und stattdessen das angegebene Docker-Image ausgeführt wird. Zuvor müssen wir noch das Docker-Image bauen. Für beide Befehle müssen wir im <strong>Verzeichnis des Source Codes unserer Django-Anwendung</strong> sein:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">docker build -t buzzword-counter:local .
telepresence --swap-deployment buzzword-counter-web --expose 8080 --docker-run --rm -it -v $(pwd):/code buzzword-counter:local python manage.py runserver 0.0.0.0:8080
</code></pre>
<p>:::</p>
<p>:::globalParagraph
Mit der Flag <em><strong>“-v $(pwd):/code”</strong></em> haben wir zusätzlich das aktuelle Verzeichnis in den <strong>Docker-Container gemounted</strong>, sodass die Code-Änderungen in <em>PyCharm</em> auch im Kubernetes-Cluster verfügbar sind. Da wir den Django-Runserver benutzen, funktioniert das live Reloading allerdings nur, wenn <em><strong>DEBUG=True</strong></em> gesetzt ist. Das können wir entweder <strong>über die Helm-Charts deployen oder einfach in unserem geswappten Deployment exportieren.</strong> Danach führen wir das <em>run-Script</em> aus:
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">export DJANGO_DEBUG=True
/usr/src/run_app.sh
</code></pre>
<p>:::
:::globalParagraph
Wenn wir den Container swappen, müssen wir wieder die drei Befehle von oben für den <em>port-forward</em> des Pods ausführen. Daraufhin können wir Code in <em>PyCharm</em> ändern und einerseits im Log oder durch das Aufrufen der Seite im Browser verifizieren, dass der Runserver neu gestartet wurde:
:::</p>
<p><img src="/img/blogs/pasted_image_0_1.jpg" alt="pasted_image">{.object-cover .w-full .mb-5}</p>
<p>:::globalParagraph
Wer genauer hinschaut, wird feststellen, dass <strong>Telepresence</strong> nicht auf einen lokalen Cluster beschränkt ist. Es lassen sich auch <strong>Deployments von remote Clustern</strong> swappen, sofern der Zugriff mittels <em>kubectl</em> möglich ist. Das kann z.B. zum Debuggen und Nachvollziehen von Bugs auf Test-Systemen sehr nützlich sein. Allerdings ist hier Vorsicht geboten, denn jeder <strong>Traffic des Deployments</strong> wird nach dem Swappen auf den lokalen Laptop geleitet. Das heißt dieses Vorgehen bietet sich maximal für <strong>Test-Systeme</strong> an und sollte bei den <strong>allermeisten Produktiv-Systemen</strong> tunlichst gemieden werden.
:::</p>
<p>:::globalTitle{:size="md" .mb-5}
<strong>PYTHON REMOTE DEBUG IN PYCHARM</strong>
:::</p>
<p>:::globalParagraph
Mittlerweile können wir also unsere Anwendung im lokalen Kubernetes-Cluster mit live Code-Reloading deployen. Wir haben unsere gebuzzwordete Mission erfüllt, die <strong>Production-Kubernetes-Umgebung ist lokal nachgebildet</strong> und wir können <em><strong>cloud native</strong></em> an unserem Service entwickeln. Das i-Tüpfelchen ist nun noch den <em>PyCharm Debugger</em> so zu konfigurieren, dass wir unsere Anwendung auch direkt in <em>PyCharm</em> debuggen können. Hierzu müssen wir zuerst <em><strong>Python Remote Debug</strong></em> in <em>PyCharm</em> konfigurieren:
:::</p>
<p><img src="/img/blogs/pasted_image_0_2.jpg" alt="pasted_image">{.object-cover .w-full .mb-5}</p>
<p>:::globalParagraph
Zu beachten ist das Pfad-Mapping, denn hier muss unbedingt ein <strong>absoluter Pfad</strong> angegeben werden (der ~-Shortcut für das Home-Verzeichnis funktioniert nicht). Wie oben im Bild zu sehen ist, benötigt die Konfiguration außerdem eine spezifische Version des Python-Packages <em><strong>pydevd-pycharm</strong></em>.
:::
:::globalParagraph
Um zu verhindern, dass dieses Paket unnötig in unserem Production-Deployment ist, erzeugen wir ein zweites Dockerfile, das erweiterte <em><strong>pip-Requirements</strong></em> installiert. Weiterhin haben wir einen simplen View zu unserer Anwendung hinzugefügt (in <em>urls.py</em>), damit wir bequem per URL die Verbindung zwischen unserem Cluster und dem <em>PyCharm Debugger</em> herstellen können. Hier ist insbesondere darauf zu achten, dass die IP-Adresse und der Port mit der Konfiguration in <em>PyCharm</em> übereinstimmen.
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">docker build -f Dockerfile.dev -t buzzword-counter:debug .
telepresence --swap-deployment buzzword-counter-web --expose 8080 --docker-run --rm -it -v $(pwd):/code buzzword-counter:debug bash
</code></pre>
<p>:::
:::globalParagraph
Daraufhin browsen wir die <em>Debug-URL</em> an. Auch hier müssen wir wieder daran denken, dass <em>DEBUG=True</em> gesetzt ist und wir den port-forward durchgeführt haben. Nun können wir auch schon einen <strong>Breakpoint</strong> in <em>PyCharm</em> setzen. Browsen wir den entsprechenden View an, wird die Ausführung durch den Debugger angehalten und wir können genauer untersuchen, warum ein Reduzieren des Counters diesen entweder auf 0 zurücksetzt oder sogar ein <em><strong>IntegrityError</strong></em> kommt:
:::</p>
<p><img src="/img/blogs/debug_kubernetes.jpg" alt="pasted_image">{.object-cover .w-full .mb-5}</p>
<p>:::globalTitle{:size="lg" .mb-5}
<strong>FAZIT</strong>
:::
:::globalParagraph
Mittels der <strong>Tools</strong> <em><strong>k3d/k3s, Helm, Telepresence und Python Remote Debug</strong></em> haben wir also das Brett namens <strong>“Cloud Native k8s Development”</strong> komplett durchbohrt. Unsere Entwickler können nun alle munter in ihrem <strong>eigenen lokalen Kubernetes-Cluster entwickeln.</strong> Vor allem <em>Telepresence</em> in Kombination mit <em>Python Remote Debug</em> ist eine sehr praktische Lösung.
:::
:::globalParagraph
Dennoch muss man festhalten, dass die <strong>Handhabung der Tools</strong> nicht ganz simpel ist bzw. eine <strong>gewisse Einarbeitungszeit</strong> benötigt. Vor allem im Vergleich zu <em>docker-compose</em> ist die Hürde wesentlich höher. Hier fehlt noch etwas, das die genannten Tools kombiniert und sich dennoch <strong>ohne große Einstiegshürde</strong> bedienen lässt. Bleibt gespannt, wir bei Blueshoe haben in letzter Zeit an einer <strong>Lösung</strong> gebastelt, die sich intern bereits bewährt hat. Demnächst werdet ihr auch in unserem Blog mehr davon lesen!
:::
:::globalParagraph
Abschließend natürlich noch der <strong>Buzzword-Counter</strong>: Ich bin insgesamt auf <strong>23 unique Buzzwords</strong> gekommen. Hast du mitgezählt und bist auf einen anderen Wert gekommen? Dann lass es uns doch einfach in einem Kommentar wissen.
:::</p>]]></content:encoded>
            <category>Kubernetes</category>
            <category>Docker</category>
            <category>Entwicklung</category>
            <enclosure url="https://blueshoe.de/img/blogs/joseph-barrientos.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Kubernetes für Anfänger: Der Cluster]]></title>
            <link>https://blueshoe.de/blog/kubernetes-fuer-anfaenger</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/kubernetes-fuer-anfaenger</guid>
            <pubDate>Thu, 24 Nov 2022 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Kubernetes ist derzeit "das ganz heiße Zeug". Auch für Entwickler ist es manchmal schwierig, Zugang zu dieser Technologie zu bekommen. Schwieriger ist es aber für Nicht-Entwickler. Was kann Kubernetes? Was unterscheidet die Anbieter? Welche Vorteile hat es?</p>
<p>Wir betrachten diese Fragen und geben einen Überblick über Kubernetes und verwandte Themen. Nicht bis ins kleinste Detail, aber so, dass auch Nicht-Entwickler verstehen, was Kubernetes ist.</p>
<p><img src="/img/blogs/header_unikube-kubernetes.jpg" alt="header_unikube-kubernetes">{.object-cover .max-w-full .mb-5}</p>
<h2>Was ist Kubernetes</h2>
<p>Kubernetes selbst ist keine Dienstleistung, die einzelne Anbieter anbieten. Vielmehr ist Kubernetes eine open-source Technologie, um Applikationen, die in Containern verpackt sind, zu managen und zu orchestrieren.</p>
<p>Theoretisch kann Kubernetes kostenlos auf GitHub heruntergeladen werden und wird dann auf lokalen Servern oder öffentlich (z.B. für einen Kunden) bereitgestellt.</p>
<p>Zusätzlich gibt es kostenpflichtige Angebote, die Kubernetes als open-source Technologie nutzen, aber darüber hinaus noch weitere Services zur Vereinfachung und Erweiterung von Kubernetes bereitstellen. Die bekanntesten Beispiele dafür sind Azure Kubernetes Service (AKS), Google Kubernetes Engine (GKE) oder Amazon Elastic Kubernetes Service (EKS). Die Nutzung dieser Services wird wahrscheinlich von den meisten Entwicklern genutzt werden.</p>
<p>Kubernetes ist in all diesen Angeboten weiterhin kostenlos, nicht aber die Cloud-Ressourcen oder Oberflächen, die die Anbieter bereitstellen. Die Kosten für die Verwaltung von EKS, AKS oder GKE sind oft gering, die Rechen- und Speicherkosten für Ressourcen, die die Dienste für Cloud-Ressourcen oder Oberflächen erheben, können sich aber schnell summieren.</p>
<h3>Back to the roots: Monolithen vs. Microservices</h3>
<p>Sehr vereinfacht gesagt gibt es in der Software-Entwicklung 2 Ansätze, um Software zu entwickeln: den monolithischen Ansatz und den Bau von Microservices.</p>
<p>Bei Monolithen sind alle relevanten Bestandteile in einer Applikation enthalten. Bei Microservices gibt es für jeden Bestandteil dagegen jeweils eine eigenständige Applikation, die nur einen eng umrissenen Aufgabenteil bearbeitet. Benötigt ein Microservice zur Erledigung seiner Aufgaben Input eines anderen Microservice, kommunizieren die einzelnen Microservices via Schnittstellen untereinander. Ein Vorteil von Microservices gegenüber einer monolithischen Architektur ist, dass, sollte ein Microservice ausfallen, nicht zwangsläufig das ganze System nicht mehr einsatzbereit ist. Es gibt aber nicht das überlegene Verfahren. Blueshoe nutzt bei seiner Arbeit meist Microservices, da für unsere Zwecke hier die Vorteile überwiegen.</p>
<p>Zur Nutzung von Kubernetes ist es nicht notwendig, dass Microservices genutzt werden. Kubernetes kann auch zum Betreiben von Monolithen herangezogen werden.</p>
<p>Für die Nutzung von Kubernetes ist es aber unabdingbar, dass die Softwareanwendung in einen Container verpackt ist - und das geht mit Monolithen und Microservices.</p>
<p><img src="/img/blogs/kubernetes-explained-for-non-developers-1.jpg" alt="monolith vs. microservices">{.object-cover .max-w-full .mb-5}</p>
<h3>Back to the roots: Container</h3>
<p>Um auch Nicht-Entwicklern eine kurzen Einblick zu geben, warum Container genutzt werden, wollen wir ganz kurz und vereinfacht darauf eingehen.</p>
<p>Bei Software-Containern handelt es sich auch buchstäblich um Container. Sie bilden eine vordefinierte Umwelt, in der Code ausgeführt werden kann. Ein Container enthält also nicht nur Software, sondern bietet auch die Möglichkeit, die Umgebung, in der die Software ausgeführt wird (also den Container), vorzukonfigurieren. Bevor die Praxis der containerisierten Software aufgekommen ist, musste Software immer in unterschiedlichen Umgebungen ausgeführt werden, z.B. auf verschiedenen Computern. Es bestand also nicht nur die Herausforderung, dass die Software an sich fehlerfrei sein musste, sondern jede Umgebung musste auch gleich konfiguriert sein. Mit der Containerisierung von Software stellt der Container selbst die Umgebung dar, in der die Software ausgeführt wird. Der Container kann also auf unterschiedlichen Servern betrieben werden, ohne dass die einzelnen Server jeweils einzeln konfiguriert werden müssen.</p>
<p>Der bekannteste Anbieter, der das Verpacken von Software in Containern erlaubt, ist Docker. Daraus ergeben sich also "Docker Container." "Container" wird also oft mit "Docker-Container" synonym verwendet. Für die Nutzung von Kubernetes ist es notwendig, dass Software in Container verpackt ist. Mit welcher Technik das herbeigeführt wird, ist nicht relevant.</p>
<p>Wie groß die einzelnen, in Container verpackten Applikationen dabei sind (siehe oben: monolithische Architektur vs. Microservices) ist ebenfalls nicht relevant - Kubernetes kann für beide Ansätze genutzt werden.</p>
<p>Die Software, die in einen Container verpackt wurde, wird an einem definierten Ort abgelegt. Das wird als Docker-Image bezeichnet. Wird die Software ausgeführt, wird immer auf dieses Image referenziert. Die Software kann also in mehreren Instanzen ausgeführt werden, wenn auf dasselbe Image referenziert wird.</p>
<h2>Was Kubernetes genau macht</h2>
<p>Die open-source Technologie Kubernetes erlaubt es, Container in einem definierten Umfeld zu verwalten. Dieses Umfeld muss sich von seiner Umgebung klar abgrenzen, also einen so genannten Cluster bilden und auf einem lokalen System oder z.B. einer öffentlichen Cloud (die für Kunden zugangsbeschränkt werden kann) hergestellt werden.</p>
<p>Ein Cluster ist ein Zusammenschluss von verschiedenen Teilelementen, die zur Ausführung der Kubernetes Technologie benötigt werden, zum Beispiel Nodes, Pods, etc. Eine Node entspricht dabei einem Server, der eine Softwareapplikation ausführt. Damit eine Node eine Softwareapplikation ausführen kann, muss die Software in einem Container verpackt sein und in der Node bereitgestellt werden (mehr dazu später).</p>
<h3>"Server vs. Node" oder: "pet vs. cattle"</h3>
<p>Ohne die Nutzung von Kubernetes wird Software direkt auf einem Server ausgeführt. Die Software ist dabei nur auf diesem einen Server verfügbar. Ist der Server nicht mehr erreichbar, kann auch die Software nicht mehr ausgeführt werden.</p>
<p>Neben der Pflege der eigentlichen Software ist bei dieser Lösung die Serverpflege essentiell. Der Server ist damit wie ein pet - ein Haustier. Er wird gehegt und gepflegt, es soll ihm gut gehen, er soll lange leben und nie krank werden. Alles, damit der Server die Software stabil ausliefern kann.</p>
<p>Demgegenüber ist eine Node ein anonymes Arbeitstier oder eine Drohne - also ein cattle. Nur eine Nummer, ohne Namen, ohne Gesicht. Stirbt ein Arbeitstier, wird es, ohne Aufsehen zu erregen, direkt durch ein anderes ausgetauscht. Viele Arbeitstiere bilden eine Herde anonymer Individuen. Analog dazu bilden mehrere Nodes einen Cluster.</p>
<p>Wird Software auf einem individuellen Server ausgeführt und der Server ist nicht mehr erreichbar, kann die Software auch nicht mehr ausgeführt werden, der Service ist für die User nicht mehr nutzbar. Ist die Node, auf der eine in Containern verpackte Applikation ausgeführt wird, nicht mehr erreichbar, kann der Container sehr schnell auf eine andere Node transferiert und damit die Software weiter ausgeführt werden.</p>
<p>Für die Verschiebung eines Containers in eine andere Node sorgt dabei Kubernetes "ganz von selbst". Kubernetes ist im Beispiel also wie ein Aufseher, der dafür sorgt, dass immer ein Arbeitstier zur Verfügung steht, das die Applikation trägt. Kubernetes wird auch oft als ein Dienst bezeichnet, der Container orchestriert, indem er die Container wie ein Dirigent verfügbaren Nodes zuordnet.</p>
<h3>Alternativen zu Kubernetes</h3>
<p>Kubernetes ist nicht der einzige Dienst, um die Ausführung von Software in Container auf virtuellen Maschinen (den Nodes) zu orchestrieren. Neben Kubernetes gibt es noch eine ganze Reihe anderer Anbieter: Docker Swarm, Nomad oder Kontena seien hier als Alternativen genannt. Hier gibt es einen Überblick über die <a href="https://www.linux-magazin.de/ausgaben/2018/08/kubernetes-alternativen/">Vor- und Nachteile dieser und weiterer Applikationen im Vergleich zu Kubernetes</a>{target="_blank"}. (Stand: 19.05.2022):</p>
<h3>Warum Kubernetes nutzen?</h3>
<p>Kubernetes bietet unbestreitbar viele Vorteile - aber gleichzeitig auch einige Nachteile. Ob Kubernetes genutzt werden soll oder nicht, will also gut überlegt sein.</p>
<p>Wir haben hier einige Argumente für und gegen Kubernetes zusammengetragen.</p>
<p><img src="/img/blogs/kubernetes-explained-for-non-developers-2.jpg" alt="monolith vs. microservices">{.object-cover .max-w-full .mb-5}</p>
<p>Jedem Vorteil von Kubernetes kann auch ein Nachteil zugeordnet werden. Eine einfache Antwort auf die Frage "Soll ich Kubernetes nutzen?" gibt es also nicht. Jede Antwort ist so individuell wie jede Software oder jeder Kunde von Blueshoe.</p>
<p>Wir bei Blueshoe haben uns aus folgenden Gründen entschieden Kubernetes als Erste-Wahl-Lösung zur Orchestrierung von Applikationen in Container zu nutzen:</p>
<ul>
<li>Die Projekte, die wir betreuen bringen meist so komplexe Applikationen hervor, das sich der Einsatz von Kubernetes lohnt</li>
<li>Unsere Entwickler können nicht alle alternativen Produkte, die es zur Orchestrierung von containerisierter Software gibt, gleich gut beherrschen. Wir haben uns entschlossen, Kubernetes-Experten zu werden. Unser Wissen dabei können wir auf viele Projekte übertragen. Die Ramp-up-Phase für uns war hoch, aber wir profitieren nun davon</li>
<li>Kubernetes wird von (fast) allen Cloud-Anbietern unterstützt - anders als alternative Systeme zur Orchestrierung von containerisierter Software. Damit sind unsere Kunden bei der Anbieterwahl nicht beschränkt.</li>
</ul>
<h3>Soll man Kubernetes selbst hosten oder einen managed Service in Anspruch nehmen?</h3>
<p>Wie oben beschrieben handelt es sich bei Kubernetes um eine open-source Technologie, die Nutzung ist also grundsätzlich (lizenz)kostenfrei und kann vollkommen selbstständig gehostet werden. AKS, GKE, EKS oder andere Anbieter haben dazu gemanaged Services im Angebot, die die Nutzung von Kubernetes erleichtern sollen. Die Nutzung dieser Services ist das, was die Anbieter in Rechnung stellen.</p>
<p>Was also tun? Selbst hosten oder Geld für einen managed Service bezahlen?</p>
<p>Dabei sind zwei Faktoren zu berücksichtigen: Einerseits der Faktor, welche "Hardware" und welche Services die Anbieter bereitstellen. Diesen Aspekt haben wir in unserem Blogpost "<a href="/blog/managed-vs-unmanaged-kubernetes/">Managed vs. Unmanaged Kubernetes</a>" bereits zusammengestellt.</p>
<p>Andererseits darf nicht vergessen werden, dass auch die Personalkosten für die Verwaltung eines vollständig selbst-gehosten Kubernetesclusters nicht zu vernachlässigen sind. <a href="https://www.koyeb.com/blog/the-true-cost-of-kubernetes-people-time-and-productivity">Hier</a> wird anschaulich verdeutlicht, dass wenn ein Cluster 24/7 gewartet werden muss, zumindest 4 Vollzeit-beschäftigte Entwickler vorgehalten werden sollen um auch Urlaubs- und Krankheitszeiten kompensieren zu können. Es wird davon ausgegangen, dass bei der Nutzung eines gemanagten Services, der zwar auch durch einen Entwickler betreut wird, eine Vollzeitstelle genügt.</p>
<p>Zu bedenken ist außerdem, dass die Kostenstruktur für Nodes, Rechenleistungen, etc. bei den einzelnen Anbietern zum Teil sehr undurchsichtig ist. Welche Kosten hier genau auf einen zukommen hängt wesentlich vom Umfang der zu verarbeitenden Informationen und der dafür notwendigen Rechenleistung ab. Die Kosten sind oft so dargestellt, dass es für Personen, die nicht mit der technischen Entwicklung und/oder Umsetzung von Software (z.B. Fachabteilungen) vertraut sind, kaum zu beurteilen ist, wie hoch die notwendigen Kapazitäten sind und welche Kosten anfallen werden. So können die Kosten beispielsweise je nach spontanem Nutzeraufkommen sehr stark variieren, da dadurch wesentlich mehr Rechenkapazität benötigt wird. Eine grobe Kostenschätzung für einen managed Service sollte auch immer mit einem inhaltlich involvierten Entwickler vorgenommen und nicht zu knapp kalkuliert werden.</p>
<h3>Was man zum Bau von Kubernetes braucht</h3>
<p>Im zweiten Teil wollen wir nun zu Kubernetes selbst kommen. Dabei sollen Begrifflichkeiten geklärt und aufgezeigt werden, wie diese im Einzelnen zusammenhängen.</p>
<p>Hier findet sich ein guter erster <a href="https://kubernetes.io/docs/tutorials/kubernetes-basics/">Überblick sowohl für Nicht-Entwickler als auch für Entwickler, die sich zum ersten Mal mit Kubernetes befassen</a>{target="_blank"}. Wir greifen im Folgenden hauptsächlich auf diese Quelle zurück.</p>
<h3>Cluster</h3>
<p>Kubernetes kann nur in einem definierten Umfeld verwendet werden. Der Cluster selbst kann <em>nicht</em> mit Kubernetes gleichgestellt werden. Kubernetes bietet als Technologie aber die Möglichkeit, einen Cluster zu erstellen, der verschiedene Bestandteile gegen seine Umgebung abgrenzt.</p>
<p>Im Kontext von Kubernetes ist ein Cluster also ein Zusammenschluss von verschiedenen Bestandteilen, die für die Nutzung von Kubernetes benötigt werden, der klar von der Umwelt abgegrenzt ist. Neue Bestandteile (z.B. neue Nodes) müssen dem Cluster explizit durch einen Entwickler zugeordnet werden und können nicht <em>automatisch</em> in den Cluster gelangen.</p>
<p><img src="/img/blogs/kubernetes-explained-for-non-developers-3.jpg" alt="Cluster">{.object-cover .max-w-full .mb-5}</p>
<h3>Node</h3>
<p>Eine Node ist eine virtuelle Maschine oder ein physischer Computer. Eine Node ist der Bestandteil des Clusters, der die in Container verpackte Software ausführt. Die containerisierte Software selbst wird über Pods auf die Node gebracht.</p>
<p>Die Node selbst besitzt kleinere Applikationen um diese Arbeit auch durchführen zu können:</p>
<ul>
<li><strong>Kubelet</strong>: Jede Node besitzt ein sogenanntes Kubelet, der die Node selbst managt und via <a href="/loesungen/api-entwicklung/">API</a> mit dem Control Plane kommuniziert</li>
<li><strong>Tools um die Container innerhalb der Node zu betreiben:</strong> Die Node stellt Platz zur Verfügung, um die containerisierte Software zu beherbergen. Allerdings kann es notwendig werden, dass Arbeiten an der containerisierten Software durchgeführt werden. Um die Software auf der Node bearbeiten zu können (z.B. zu starten) verfügt die Node über Tools, um die Software, die im Container auf der Node liegt, ansprechen zu können. In der Darstellung werden Docker-Container genutzt. Aus diesem Grund handelt es sich um ein spezielles Tool, um Docker-Container zu verwalten.</li>
</ul>
<p>Zusätzlich können Nodes Pods umfassen, die wiederum containerisierte Software beinhalten. Dazu aber später mehr.</p>
<h3>Control Plane</h3>
<p>Der Control Plane ist das Herzstück von Kubernetes, die ausführende Kubernetes-Instanz (sozusagen die Schaltzentrale), die alle Aktivitäten im Cluster koordiniert. Ein Control Plane ist ebenfalls eine Node, allerdings mit der speziellen Aufgabe, den Cluster zu koordinieren.</p>
<p>Der Control Plane stellt eine <a href="/loesungen/api-entwicklung/">API</a> zur Verfügung, um mit den anderen Clusterbestandteilen zu kommunizieren.</p>
<h3>Pods</h3>
<p>Der Begriff "Pod" gehört bestimmt zu denjenigen, die im Kubernetes-Umfeld am meisten Verwendung finden. Pods haben nichts mit Star Wars zu tun, sondern sind die kleinsten Einheiten im Kubernetes-Universum.</p>
<p>Pods als kleinste eigenständige Einheiten im Cluster fassen mehrere Elemente sinnhaft zusammen, die im Deployment-Prozess in den Cluster gebracht werden. Pods sind also "Hülsen" und können beispielsweise folgende Elemente umschließen:</p>
<ul>
<li>geteilte Speichereinheiten, z.B. Volumes</li>
<li>cluster-spezifische IP-Adressen</li>
<li>Informationen darüber, wie Container betrieben werden, z.B. Image-Versionen der Applikation, Informationen zu Ports, etc.</li>
</ul>
<p>Jeder Pod verfügt dabei über eine eigene IP-Adresse, die nur innerhalb des Clusters bekannt ist. Das heißt, die einzelnen Pods können nur im Cluster angesprochen und nicht von außen gesteuert werden.</p>
<p>Eine Node kann mehrere Pods beherbergen. Jeder Pod ist immer nur einer Node zugeordnet und verbleibt bei dieser Node bis sie gelöscht wird oder aufgrund anderer Fehler "stirbt". Passiert das,"stirbt" auch der Pod. Wenn das passiert, kann durch den Deployment-Prozess (siehe unten) der Pod auf einer anderen Node aber neu erzeugt werden und somit "ewig leben".</p>
<p><img src="/img/blogs/kubernetes-explained-for-non-developers-41.jpg" alt="Cluster">{.object-cover .max-w-full .mb-5}</p>
<p><img src="/img/blogs/kubernetes-explained-for-non-developers-4.jpg" alt="node.js">{.object-cover .max-w-full .mb-5}</p>
<h3>Replica-Sets und der Deployment-Prozess</h3>
<p>Mit Node, Control Plane und Pods haben wir jetzt die einzelnen Bestandteile des Cluster besprochen. Aber wie agieren die einzelnen Bestandteile miteinander und wie tragen sie dazu bei, dass von Entwicklern geschriebener Code korrekt ausgeführt wird?</p>
<p>Dafür ist der Deployment-Prozess zuständig. Erst mit dem Deployment bringen Entwickler den Code in den Cluster. Im Deployment-Prozess wird beschrieben, wie oft einzelne Softwarebestandteile im Cluster ausgeführt werden sollen.</p>
<p>Hier wird beispielsweise beschrieben, welche Software ausgeführt werden soll. Die Software ist in ein Container-Image verpackt und das Image ist an einer definierten Stelle abgelegt (siehe oben). Wird im Deployment beschrieben, dass die Software einmalig ausgeführt werden soll, ist die Anzahl des Replica-Sets = 1 und es wird dafür ein Pod etabliert. Soll die Software öfters ausgeführt werden, ist die Anzahl des Replica-Sets beispielsweise =3. Im Replica-Set werden nun 3 Pods etabliert, die jeweils auf dasselbe Container-Image referenzieren.</p>
<p>Ist das Replica-Set = 3 und umfasst somit 3 Pods, können diese 3 Pods auf derselben Node ausgeführt werden. Alle drei Pods führen über die Referenz zum Container-Image mit demselben Code dann zwar dieselbe Software aus, es handelt sich aber um unterschiedliche Pods. Somit bleibt die oben beschriebene Prämisse bestehen, dass jeder Pod nur einmalig im Cluster enthalten sein kann und immer nur einer Node zugeordnet ist.</p>
<p>Ein Deployment kann mehrere Replica-Sets umfassen, die auf unterschiedliche oder auch dieselbe Software referenzieren.</p>
<p>Wie zuvor beschrieben bietet der Deployment-Prozess den Vorteil, dass, sollte eine Node z.B. gelöscht werden oder fehlerhaft sein, der Pod mit der beinhalteten Software auf einer anderen Node erneut erstellt werden kann. Die Information, wie ein Pod im Cluster ausgeführt werden soll, ist im Deployment beschrieben. Durchgeführt wird der Schritt, den Pod einer Node zuzuordnen durch einen Service (siehe unten).</p>
<p>Um das Deployment zu starten und zu managen, wird bei Kubernetes das Command-Line-Interface genutzt - das Kubectl. Kubectl ist für Nicht-Entwickler aber weniger relevant, die Bezeichnung sollte aber an dieser Stelle einmal erwähnt werden.</p>
<p><img src="/img/blogs/kubernetes-explained-for-non-developers-5.jpg" alt="ReplicaSets">{.object-cover .max-w-full .mb-5}
<img src="/img/blogs/kubernetes-explained-for-non-developers-6.jpg" alt="ReplicaSets">{.object-cover .max-w-full .mb-5}</p>
<h3>Service, Label und Selector</h3>
<p><img src="/img/blogs/kubernetes-explained-for-non-developers-7.jpg" alt="ReplicaSets">{.object-cover .max-w-full .mb-5}</p>
<p>Ein Service in Kubernetes hat nichts mit einem "managed Kubernetes-Service" (siehe oben) zu tun, sondern ist ein Bestandteil im Cluster. Ein Service kann als eine Abstraktion verstanden werden, die Pods logisch zusammenfasst und definiert, wie die Pods miteinander interagieren können. Dabei interagieren die Pods nicht direkt miteinander. Vielmehr können den Pods Labels zugeordnet werden. Der Service verfügt dabei über den komplementären Selector. Über die Labels am Pod und die Selektoren am Service können Pods miteinander interagieren.</p>
<h2>Der Cluster im Überblick</h2>
<p>Zusammengefasst besteht ein Cluster aus mehreren Nodes, wovon eine Node den Control Plane darstellt.</p>
<p>Die Software, die im Cluster ausgeführt werden soll, ist in einen Container verpackt und als Container-Image an einem definierten Ort festgelegt. Im Cluster selbst wird also nicht die Software selbst abgelegt, sondern immer nur auf das Container-Image referenziert.</p>
<p>Im Deployment-Prozess wird beschrieben, welche Software in welcher Form (z.B. wie oft) ausgeführt werden soll. Dafür werden ein oder mehrere Replica-Sets angelegt. Der Deployment-Prozess ist auf dem Control Plane abgelegt.</p>
<p>Nach dem Deployment werden die im Deployment-Prozess definierten Pods im Cluster angelegt und auf die verschiedenen Nodes verteilt.</p>
<p>Services im Cluster verfügen über Selektoren, Pods können über die komplementären Labels zu den Selektoren verfügen. Durch dieses Schlüssel-/Schloss-Prinzip ist es möglich, dass die Pods miteinander in Interaktion stehen. Nur wenn ein Pod über ein Label verfügt, kann er mit anderen Pods interagieren, ansonsten steht der Pod für sich alleine.</p>
<p><img src="/img/blogs/kubernetes-explained-for-non-developers-8.jpg" alt="Cluster">{.object-cover .max-w-full .mb-5}</p>
<h3>Clustergröße</h3>
<p>Der Vorteil von Kubernetes ist, dass das System erkennen kann, wann eine Node (= virtuelle Maschine) nicht mehr betriebsbereit ist. Anstatt dass die Software dann nicht mehr ausgeführt werden könnte (wie das der Fall wäre, würde die Software auf einem einzelnen Server ausgeführt werden), kann Kubernetes die Software im Container automatisiert einer anderen funktionsfähigen Node zuordnen. Aus diesem Grund sollte ein Cluster im Produktivsystem aus mind. 3 Nodes bestehen: Davon ist eine Node der notwendige Control plane, die anderen beiden Nodes beherbergen die auszuführende Software in Pods. Ein Pod läuft dabei nur auf einer Node, die verbleibende Node steht dann "nur" für den Fall bereit, dass die andere ausfällt.</p>
<h2>Zusammenfassung</h2>
<p>Kubernetes selbst ist eine open-source-Software, die von jedem kostenfrei genutzt werden kann. Es handelt sich dabei um einen Dienst, der Software ausführt und gegenüber dem Betrieb von eigenständigen Servern eine ganze Reihe Vorteile bietet.</p>
<p>Anbieter wie AKS, GKE oder EKS stellen darüber hinaus weitere Services in Zusammenhang mit Kubernetes bereit, die die Administration erleichtern sollen. Für diese Services fallen aber eine ganze Reihe von Kosten an, die nicht leicht zu überblicken sind.</p>
<p>Kubernetes bezeichnet eine Technologie, die eine Reihe einzelner Komponenten umfasst. Erst im Zusammenspiel von Nodes, Pods und dem Control Plane kann ein Cluster erstellt werden, der zum Betrieb von Kubernetes notwendig ist.</p>
<p>Kubernetes ist kein Allheilmittel, das für jede Software gleichermaßen geeignet ist. Ob Kubernetes für den Betrieb einer speziellen Software oder für eine Organisation als ganze geeignet ist und ob die Services von AKS, GKE, EKS oder anderen hinzugekauft werden sollen, muss im Einzelfall beurteilt werden.</p>
<p>Wir hoffen, wir konnten euch einen guten Überblick geben, was Kubernetes ist, wie es sich von anderen Technologien zum Ausführen von Software unterscheidet und dass ein Pod in der Kubernetes-Welt nichts mit Podrennen in Star Wars Filmen zu tun hat.</p>]]></content:encoded>
            <category>Kubernetes</category>
            <category>Entwicklung</category>
            <category>Projekt Management</category>
            <enclosure url="https://blueshoe.de/img/blogs/header_unikube-kubernetes.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Kubernetes Logging mit Promtail, Loki und Grafana]]></title>
            <link>https://blueshoe.de/blog/kubernetes-logging-mit-promtail-loki-und-grafana</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/kubernetes-logging-mit-promtail-loki-und-grafana</guid>
            <pubDate>Wed, 24 Aug 2022 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Das Lesen von Protokollen aus mehreren Kubernetes-Pods mit kubectl kann schnell umständlich werden. Was wäre, wenn es eine Möglichkeit gäbe, Protokolle aus dem gesamten Cluster an einem einzigen Ort zu sammeln und sie einfach zu filtern, abzufragen und zu analysieren? Hier kommen Promtail, Loki und Grafana ins Spiel.</p>
<p><img src="/img/blogs/kubernetes-logging-with-promtail-loki-and-grafana.jpg" alt="my image">{.object-cover .max-w-full .mb-5}</p>
<h2>Einführung (Was ist das, warum brauchen wir es)</h2>
<p>Standardmäßig halten Logs in Kubernetes nur während der Lebensdauer eines Pods. Um Logs länger als die Lebensdauer eines einzelnen Pods aufzubewahren, verwenden wir Log-Aggregation. Das bedeutet, dass wir Logs aus verschiedenen Quellen an einem einzigen Ort speichern, um sie auch nach einem Fehler leicht analysieren zu können. Während der ELK-Stack (kurz für Elasticsearch, Logstash, Kibana) eine beliebte Lösung für die Log-Aggregation ist, haben wir uns für etwas leichtgewichtigeres entschieden: Loki.</p>
<p>Entwickelt von Grafana Labs, ist "Loki ein horizontal skalierbares, hochverfügbares, mandantenfähiges Log-Aggregationssystem, das von Prometheus inspiriert ist". Loki ermöglicht eine einfache Sammlung von Logs aus verschiedenen Quellen mit unterschiedlichen Formaten, skalierbare Persistenz über Objektspeicher und einige weitere coole Funktionen, die wir später im Detail erklären werden. Werfen wir jetzt einen Blick auf das von uns erstellte Setup.</p>
<h2>Deck</h2>
<p>Wenn du nur einen schnellen Blick darauf werfen möchtest, kannst du Deck verwenden, um diesen Stack mit einem Befehl auf deinem Computer einzurichten. Nach der <a href="https://getdeck.dev/docs/deck/installation">Installation von Deck</a>{target="_blank"} kannst du Folgendes ausführen:</p>
<pre><code class="language-docker">$ deck get https://raw.githubusercontent.com/Getdeck/wharf/main/loki/deck.yaml
</code></pre>
<p>Folge den Anweisungen, die nach Abschluss des Installationsprozesses angezeigt werden, um sich bei Grafana anzumelden und mit der Erkundung zu beginnen.</p>
<h2>Setup</h2>
<p>In diesem Artikel konzentrieren wir uns auf die Helm-Installation. Grafana Labs bietet eine Reihe von <a href="https://grafana.com/docs/loki/latest/installation/?pg=get&#x26;plcmt=selfmanaged-box2-cta1">anderen Installationsmethoden</a>{target="_blank"} an.</p>
<p>Im <a href="https://github.com/grafana/helm-charts">Helm-Chart-Repository von Grafana</a>{target="_blank"} findest du 5 Charts, die mit Loki zusammenhängen. <em>Loki-canary</em> ermöglicht dir die Installation von Canary-Builds von Loki in deinem Cluster. <em>Loki-distributed</em> installiert die relevanten Komponenten als <a href="/loesungen/microservice-architektur-beratung/">Microservices</a> und bietet dir die üblichen Vorteile von Microservices wie Skalierbarkeit, Ausfallsicherheit usw., während du sie unabhängig voneinander konfigurieren kannst. <em>Loki-simple-scalable</em> ist ähnlich, jedoch sind einige der Komponenten immer aktiviert, was einige der Konfigurationsmöglichkeiten einschränkt. Das Chart mit dem Namen <em>Loki</em> wird einen einzelnen StatefulSet in deinem Cluster bereitstellen, der alles enthält, was du zum Ausführen von Loki benötigst. Das letzte in der Gruppe ist <em>loki-stack</em>, das zusätzlich zum Loki-Chart dasselbe StatefulSet bereitstellt, auch Promtail, Grafana und einige andere. Für unseren Anwendungsfall haben wir uns für das Loki-Chart entschieden. Neben Loki selbst werden in unserem Cluster auch Promtail und Grafana ausgeführt. Im folgenden Abschnitt zeigen wir dir, wie du diesen Log-Aggregations-Stack in deinem Cluster installieren kannst!</p>
<h2>Voraussetzungen</h2>
<p>Um mitzumachen, benötigst du einen Kubernetes-Cluster, auf den du über kubectl zugreifen kannst, und Helm muss auf deinem Computer eingerichtet sein.</p>
<p>Zunächst müssen wir das Chart-Repository von Grafana zu unserer lokalen Helm-Installation hinzufügen und die neuesten Charts abrufen, wie folgt:</p>
<pre><code class="language-docker">$ helm repo add grafana https://grafana.github.io/helm-charts
$ helm repo update
</code></pre>
<p>Sobald das erledigt ist, können wir mit dem eigentlichen Installationsprozess beginnen.</p>
<h2>Loki Installation</h2>
<p>Lass uns damit beginnen, Loki in unserem Cluster zum Laufen zu bringen. Um deine Installation zu konfigurieren, wirf einen Blick auf die Werte, die das Loki-Chart über den Befehl 'helm show values' akzeptiert, und speichere diese in einer Datei.</p>
<pre><code class="language-docker">$ helm show values grafana/loki > loki-values.yaml
</code></pre>
<p>Wir werden die Einstellungen nicht im Detail besprechen, da die meisten Werte auf ihren Standardwerten belassen werden können. Du solltest jedoch einen Blick auf den persistence-Schlüssel werfen, um Loki so zu konfigurieren, dass deine Protokolle tatsächlich in einem PersistentVolume gespeichert werden.</p>
<pre><code class="language-json">persistence:
    enabled: true
    accessModes:
    - ReadWriteOnce
    size: 10Gi
    annotations: {}
</code></pre>
<p>Sobald du die Werte an deine Vorlieben angepasst hast, installiere Loki in deinem Cluster mit dem folgenden Befehl:</p>
<pre><code class="language-docker">$ helm upgrade --install loki grafana/loki -n loki -f loki-values.yaml
</code></pre>
<p>Nachdem das erledigt ist, kannst du überprüfen, ob alles funktioniert hat, indem du kubectl verwendest:</p>
<pre><code class="language-docker">$ kubectl get pods -n loki
NAME                            READY   STATUS    RESTARTS   AGE
loki-0                          1/1     Running   0          1h
</code></pre>
<p>Wenn die Ausgabe ähnlich aussieht, herzlichen Glückwunsch! Das ist einer von drei Komponenten, die erfolgreich installiert wurden.</p>
<h2>Promtail Installation</h2>
<p>Als nächstes schauen wir uns Promtail an. Promtail hat 3 Hauptfunktionen, die für unsere Einrichtung wichtig sind:</p>
<ol>
<li>Es erkennt Ziele (Pods, die in unserem Cluster ausgeführt werden)</li>
<li>Es beschriftet Logstreams (hängt Metadaten wie Pod/Dateinamen usw. an, um sie später einfacher identifizieren zu können)</li>
<li>Es versendet sie an unsere Loki-Instanz</li>
</ol>
<p>Um es zu installieren, benötigen wir zuerst eine Werte-Datei, genau wie wir es für Loki getan haben:</p>
<pre><code class="language-docker">$ helm show values grafana/promtail > promtail-values.yaml
</code></pre>
<p>Wie bei Loki können die meisten Werte auf ihren Standardwerten belassen werden, um Promtail zum Laufen zu bringen. Wir müssen jedoch Promtail mitteilen, wohin es die gesammelten Protokolle senden soll, indem wir Folgendes tun:</p>
<pre><code class="language-docker">$ kubectl get svc -n loki
NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
loki            ClusterIP   10.101.163.181   &#x3C;none>        3100/TCP   1h
</code></pre>
<p>Wir fragen kubectl nach den Diensten im Loki-Namespace und es wird uns mitgeteilt, dass es einen Dienst namens Loki gibt, der den Port 3100 freigibt. Um Promtail dazu zu bringen, unsere Protokolle an das richtige Ziel zu senden, zeigen wir es über den 'config'-Schlüssel in unserer Werte-Datei auf den Loki-Dienst.</p>
<pre><code class="language-json">config:
 logLevel: info
 serverPort: 3101
 lokiAddress: http://loki:3100/loki/api/v1/push
</code></pre>
<p>Unter 'lokiAddress' geben wir an, dass Promtail Protokolle an <a href="http://loki:3100/loki/api/v1/push">http://loki:3100/loki/api/v1/push</a>{target="_blank"} senden soll. Beachte, dass du, wenn Loki nicht im selben Namespace wie Promtail läuft, die vollständige Service-Adressnotation verwenden musst, z. B. '..svc.cluster.local:'. Promtail läuft als DaemonSet und hat die folgenden Tolerations, um auf Master- und Worker-Knoten ausgeführt zu werden.</p>
<pre><code class="language-json">tolerations:
    - key: node-role.kubernetes.io/master
    operator: Exists
    effect: NoSchedule
    - key: node-role.kubernetes.io/control-plane
    operator: Exists
    effect: NoSchedule
</code></pre>
<p>Wenn du nicht möchtest, dass Promtail auf deinen Master-/Control-Plane-Knoten ausgeführt wird, kannst du dies hier ändern.</p>
<p>Jetzt, da wir die wichtigsten Werte festgelegt haben, lass uns das installieren!</p>
<pre><code class="language-bash">$ helm upgrade --install promtail grafana/promtail --namespace=loki -f promtail-values.yaml
</code></pre>
<p>Überprüfe, ob alles wie erwartet funktioniert:</p>
<pre><code class="language-bash">$ kubectl get ds -n loki
NAME       DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
promtail   8         8         8       8            8           &#x3C;none>          1h
</code></pre>
<p>Du kannst auch mit dem Flag '-o wide' einen Blick auf die Pods werfen, um zu sehen, auf welchem Knoten sie ausgeführt werden:</p>
<pre><code class="language-bash">$ kubectl get pods -n loki -o wide
NAME                            READY   STATUS    RESTARTS   AGE    IP         NODE                     NOMINATED NODE   READINESS GATES
promtail-2j9dj                  1/1     Running   0          1h    1.1.1.1     control-plane-3          &#x3C;none>           &#x3C;none>
promtail-5wjxl                  1/1     Running   0          1h    1.1.1.1     control-plane-1          &#x3C;none>           &#x3C;none>
promtail-9nvps                  1/1     Running   0          1h    1.1.1.1     worker-1                 &#x3C;none>           &#x3C;none>
promtail-brgj2                  1/1     Running   0          1h    1.1.1.1     worker-2                 &#x3C;none>           &#x3C;none>
promtail-cfnff                  1/1     Running   0          1h    1.1.1.1     control-plane-2          &#x3C;none>           &#x3C;none>
promtail-gtt6m                  1/1     Running   0          1h    1.1.1.1     worker-3                 &#x3C;none>           &#x3C;none>
promtail-hnh4z                  1/1     Running   0          1h    1.1.1.1     worker-4                 &#x3C;none>           &#x3C;none>
promtail-r4xsz                  1/1     Running   0          1h    1.1.1.1     worker-5                 &#x3C;none>           &#x3C;none>
</code></pre>
<h2>Grafana Installation</h2>
<p>Zu guter Letzt, lass uns eine Instanz von Grafana in unserem Cluster starten.</p>
<p>Die folgenden Werte ermöglichen die Persistenz. Wenn du möchtest, dass deine Grafana-Instanz E-Mails senden kann, kannst du SMTP wie unten gezeigt konfigurieren. Füge einfach deinen SMTP-Host und 'from_address' hinzu, um ein Secret mit deinen Anmeldeinformationen zu erstellen.</p>
<pre><code class="language-json">persistence:
 type: pvc
 enabled: true
 # storageClassName: default
 accessModes:
   - ReadWriteOnce
 size: 10Gi
grafana.ini:
 smtp:
   enabled: true
   host: smtp.smtpserver.io:465
   from_address: grafana@collectallthelogs.io
   skip_verify: true
smtp:
 # `existingSecret` is a reference to an existing secret containing the smtp configuration
 # for Grafana.
 existingSecret: "grafana-smtp"
 userKey: "user"
 passwordKey: "password"
</code></pre>
<p>Sobald du deine Werte konfiguriert hast, kannst du Grafana wie folgt in deinem Cluster installieren:</p>
<pre><code class="language-bash">helm upgrade --install loki-grafana grafana/grafana --namespace=loki -f grafana-values.yaml
</code></pre>
<p>Überprüfe, ob alles reibungslos verlaufen ist:</p>
<pre><code class="language-bash">$ kubectl get pods -n loki
NAME                            READY   STATUS    RESTARTS   AGE
loki-grafana-64b4b79494-8bt7c   1/1     Running   0          1h
</code></pre>
<p>Alle drei Komponenten sind einsatzbereit, super! Jetzt, da alles eingerichtet ist, schauen wir uns an, wie wir dies tatsächlich nutzen können.</p>
<p>:::GlobalPodcastSection{:videoId="tyvE9VlSWkE" :videoPosition="left" .mb-6}
::::GlobalPreTitle{:color="text-bs-green" .mb-3}
UNSER KUBERNETES-PODCAST
::::
::::GlobalTitle{:tag="h3" .mb-6}
Werkzeuge für das Handwerk: Die Navigation im Kubernetes-Ökosystem
::::
::::globalParagraph{:font-size="lg" .mb-4}
Michael und Robert sprechen ausführlich über die Feinheiten der lokalen Kubernetes-Entwicklung und geben auch einige echte Codierungsbeispiele.
::::
::::globalParagraph{:font-size="lg" }
Weitere Ausgaben unseres Podcasts findest du hier:
::::
::::GlobalButton{:url="/kubernetes-podcast/" :label="Mehr anzeigen" :color="green"}
::::
:::</p>
<h2>Verwendung</h2>
<p>Verbinde deine neu erstellte Loki-Instanz einfach mit Grafana. Alles, was du tun musst, ist eine Datenquelle in Grafana zu erstellen. Unter Konfiguration → Datenquellen klicke auf 'Datenquelle hinzufügen' und wähle Loki aus der Liste aus. Dir wird dieses Einstellungsfenster angezeigt, in dem du nur die URL deiner Loki-Instanz konfigurieren musst, um deine Protokolle mit Grafana zu analysieren. Da Grafana im selben Namespace wie Loki läuft, reicht es aus, <a href="http://loki:3001">http://loki:3001</a>{target="_blank"} anzugeben.</p>
<p><img src="/img/blogs/kubernetes-logging-with-promtail-loki-and-grafana-1.jpg" alt="Verwendung">{.object-cover .max-w-full .mb-5}</p>
<p>Wenn du fertig bist, klicke auf 'Speichern &#x26; Testen' und voilà, du bist bereit, Abfragen gegen Loki auszuführen.</p>
<h2>LogQL</h2>
<p>'LogQL ist die von Grafana Loki inspirierte Abfragesprache, ähnlich wie PromQL. Abfragen fungieren wie ein verteilter grep, um Log-Quellen zu aggregieren. LogQL verwendet Labels und Operatoren zur Filterung.'</p>
<p>Mit LogQL kannst du einfach Abfragen gegen deine Protokolle ausführen. Du kannst entweder Protokollabfragen ausführen, um den Inhalt tatsächlicher Protokollzeilen zu erhalten, oder Metrikabfragen verwenden, um Werte basierend auf den Ergebnissen zu berechnen.</p>
<p>LogQL ist <a href="https://grafana.com/docs/loki/latest/logql/">gut dokumentiert</a>{target="_blank"}, daher gehen wir nicht auf jedes Feature im Detail ein, sondern geben dir stattdessen einige Abfragen, die du sofort gegen deine Protokolle ausführen kannst, um loszulegen. Gehe zum Explore-Panel in Grafana (${grafanaUrl}/explore), wähle deine Loki-Datenquelle im Dropdown-Menü aus und schau dir an, was Loki bisher für dich gesammelt hat.</p>
<h3>Simple Log Query</h3>
<p>Wenn du nur die Protokolle eines einzelnen Pods haben möchtest, ist es so einfach, eine Abfrage wie diese auszuführen:</p>
<pre><code>{pod="loki-0"}
</code></pre>
<p>Grafana wählt automatisch das richtige Panel für dich aus und zeigt an, was dein Loki Pod protokolliert.</p>
<h3>Fehler in einem Namespace</h3>
<p>Diese Abfrage filtert Protokolle aus einem bestimmten Namespace, die das Wort "Fehler" enthalten. Sie zählt sie über den im Dashboard ausgewählten Bereich und gibt die Summe zurück, um dir einen einfachen Überblick darüber zu geben, was in deinem Cluster passiert.</p>
<pre><code>sum(count_over_time({namespace="loki"} |= "error" [$__range]))
</code></pre>
<h3>Durchschnittliche Antwortzeit in einem Namespace, nach Pfad und Anwendung</h3>
<p>Diese Abfrage ist so komplex wie es in diesem Artikel wird. Sie sammelt Protokolle aus einem Namespace und wendet dann mehrere nützliche Funktionen von LogQL an, wie zum Beispiel Musterabgleich, reguläre Ausdrücke, Zeilenformatierung und Filterung. Am Ende erhältst du die durchschnittliche Antwortzeit von Anwendungen, die im angegebenen Namespace innerhalb des ausgewählten Intervalls ausgeführt werden. Sie filtern effektiv die Protokollzeilen heraus, die von Kubernetes Liveness- und Readiness-Probes generiert werden, gruppiert nach App-Label und Pfad. Hinweis: Diese genaue Abfrage funktioniert für das Protokollformat von Django Hurricane, aber du kannst sie anpassen, indem du das Muster an dein Protokollformat anpasst.</p>
<pre><code>avg_over_time({namespace="application"} | pattern "&#x3C;date> &#x3C;time> &#x3C;access> &#x3C;level>     &#x3C;code> &#x3C;method> &#x3C;path> &#x3C;ip> &#x3C;latency>" | regexp "(?P&#x3C;latencyDecimal>[0-9]+\\.[0-9]+ms)" | line_format "{{.latencyDecimal}}" | regexp "(?P&#x3C;latencyClean>[0-9]+\\.[0-9])" | unwrap latencyClean | __error__="" | path!="/alive" | path!="/ready" [$__interval]) by (path, app)
</code></pre>
<h2>Sonstige Merkmale/Weitere Lektüre/Heiligtümer</h2>
<p>Wenn du deine Logs nicht in deinem Cluster speichern möchtest, kannst du die gesammelten Daten an S3-kompatible Speicherlösungen wie Amazon S3 oder MinIO senden. Der Prozess der Log-Analyse/-Ansicht bleibt derselbe.</p>
<p>Die Dateispeicherung funktioniert nicht, wenn du das verteilte Chart verwendest, da mehrere Pods Lese-/Schreibvorgänge auf demselben PV durchführen müssten. Dies ist im <a href="https://github.com/kubernetes-sigs/kind">Chart-Repository</a>{target="_blank"} dokumentiert, wird jedoch leider nicht in der offiziellen Dokumentation von Loki erwähnt.</p>
<p><a href="https://grafana.com/docs/loki/latest/tools/logcli/">LogCLI</a>{target="_blank"} ist das CLI-Tool von Loki, mit dem du deine Protokolle bequem von der Kommandozeile aus durchsuchen kannst. Hierfür musst du deine Loki-Instanz über HTTP freigeben oder Port-Forwarding von deinem Cluster zu deinem Computer verwenden.</p>]]></content:encoded>
            <category>Kubernetes</category>
            <category>Betrieb</category>
            <enclosure url="https://blueshoe.de/img/blogs/kubernetes-logging-with-promtail-loki-and-grafana.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Kubernetes Skalierung in der Google Cloud]]></title>
            <link>https://blueshoe.de/blog/kubernetes-skalierung-gke</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/kubernetes-skalierung-gke</guid>
            <pubDate>Wed, 20 Nov 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>In diesem Artikel geht es um die Möglichkeit, Kubernetes Workloads in der Google Cloud einfach und nachhaltig zu skalieren.</p>
<p><img src="/img/blogs/gke-hpa.svg" alt="Blueshoe und FastAPI: Dokumentation mit Programmierbeispielen">{.object-cover .max-w-full .mb-5}</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Skalierung der API - zu viel und zu wenig
:::
:::GlobalParagraph
Kubernetes erlaubt es bereits auf einfache Art und Weise, Workloads zu skalieren. Für diesen Artikel wird eine zustandslose (stateless) Application angenommen - eine simple REST API.
:::
:::GlobalParagraph
Diese ist in Form eines Deployments mit 4 Replicas (je 125 mCPU und 250 Mi Memory) im GKE Autopilot Cluster vorhanden.
:::
:::GlobalParagraph
<strong>Problemstellung:</strong> Nachts laufen diese 4 Replicas nahezu ohne Last. Am Tag reichen diese teilweise nicht aus.
:::
:::GlobalParagraph
<strong>Lösung:</strong> Automatische Skalierung der Services basierend auf deren Auslastung.
:::
:::GlobalParagraph
<em>Im schlechtesten Falle werden die Applikationen während hoher Last noch OOM Killed - weil sie zu viel Speicher verbrauchen.</em>
:::</p>
<p>:::GlobalButton{:url="/loesungen/cloud-infrastruktur-beratung/" :label="Erfahre mehr über unsere Infrastrukur-Entwicklungsdienste" :color="blue" .mb-6}
:::</p>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Wie viel CPU oder Arbeitsspeicher brauchen meine Pods?
:::</p>
<p>:::GlobalParagraph
Bevor die Skalierung konfiguriert wird, ist es wichtig herauszufinden, - was eigentlich
typische Metriken für das Verhalten der zu skalierenden Applikation sind . Verbraucht diese viel CPU oder viel Arbeitsspeicher? Gibt es eine andere Metrik, welche mir Aufschluss über die Auslastung gibt (z.B. <em>Request Queue</em>-Länge)?
:::
<img src="/img/blogs/gke-hpa-1.png" alt="GKE Metrics">{.mx-auto .w-1/2}</p>
<p>:::GlobalParagraph{.mb-4}
Ein einfacher Blick in die Google Cloud Dashboards unserer REST API zeigt, dass der Arbeitsspeicher schwankt und die CPU ab einem bestimmten Zeitpunkt eine recht konstante Auslastung hat. Die blaue Linie zeigt die tatsächliche Auslastung, die rote Linie die Limits.
Der Arbeitsspeicher scheint hier deutlich volatiler und näher an seinen Limits - für die Skalierung unserer Applikation in Kubernetes wird der Memory als Grundlage genutzt.
:::
:::GlobalParagraph
Nun ist zwar klar, welche Metrik hergenommen wird, allerdings noch nicht, was die notwendigen Parameter sind. Die Memory Auslastung fällt selten unter 250 MB, was bedeutet, dass mindestens 2 Pods ständig verfügbar sein sollten.
Wir laufen selten, aber zuverlässig an die Kapazitätsgrenze der aktuell 4 verfügbaren Replicas. Also nehmen wir mit etwas Puffer maximal 6 Replicas als höchste Auslastung an.
:::</p>
<p>:::GlobalParagraph
<em>Anmerkung: Stark fluktuierende Auslastung des Arbeitsspeichers deutet auf Probleme in der Applikation hin. In diesem Falle ein Memory-Leak einer Abhängigkeit, welche nicht verändert werden kann.</em>
:::</p>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Skalierung in Kubernetes: der Horizontal Pod Autoscaler
:::</p>
<p>:::GlobalParagraph
Die Google Cloud erlaubt sowohl vertikale als auch horizontale Skalierung von Workloads. Vertikale Skalierung bedeutet, dass die verfügbaren Ressourcen (CPU, Memory) von Pods skaliert werden. Horizontale Skalierung erzeugt und entfernt ganze Pods desselben Deployments.
:::</p>
<p><img src="/img/blogs/gke-hpa-2.png" alt="Google Cloud Menu">{.mx-auto .w-1/2}</p>
<p>:::GlobalParagraph
Die grundlegenden Parameter sind schnell erstellt - das Minimum und Maximum der Skalierung der API ist mit den folgenden 2 Feldern erledigt:
:::</p>
<p><img src="/img/blogs/gke-hpa-3.png" alt="HPA Configure 1">{.mx-auto .w-1/2}</p>
<p>::GlobalCallAnExpert{:bg="bg-bs-blue"}
#buttonBlockTitle
Wir können auch Deine Apps dynamisch an den Bedarf anpassen.
::</p>
<p>:::GlobalParagraph
Doch was ist nun die Baseline für die Skalierung selbst? Es wird einfach - für unser Beispiel - eine gewünschte “Idealauslastung” pro Pod festgelegt. Das Limit eines jeden Pods liegt bei 250 MB. Mit einer Auslastung von 80% oder 200 MB kommen wir (mit etwas Puffer) an die Belastungsgrenze des Services und brauchen eine neue Instanz.
:::</p>
<p><img src="/img/blogs/gke-hpa-4.png" alt="HPA Configure Metrics">{.mx-auto .w-1/2}</p>
<p>:::GlobalParagraph{.mb-4}
Da das definierte Minimum 2 Pods sind - wird, sobald die Memory Auslastung im Schnitt 400 MB überschreitet, ein weiterer Pod hinzu skaliert. Wird diese dann wieder unterschritten, so entfernt der Horizontal Pod Autoscaler (HPA) diesen auch wieder.
:::</p>
<p>:::GlobalParagraph
Für alle Kubernetes Experten - natürlich lässt sich der HPA auch über Kubernetes Ressourcen definieren und somit als Konfiguration im Cluster hinterlegen - das typische DevOps Vorgehen.
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-yaml">apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: store-autoscale
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: store-autoscale
  minReplicas: 2
  maxReplicas: 6
  metrics:
    - resource:
        name: memory
        target:
          averageValue: 200
          type: AverageValue
</code></pre>
<p>:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Fazit
:::
:::GlobalParagraph{.mb-8}
Mit ein paar Klicks - oder auch einer einfachen Kubernetes Ressourcen - lassen sich Kosten sparen und die Lastspitzen der REST API einfach abfangen.
:::</p>]]></content:encoded>
            <category>Kubernetes</category>
            <category>Entwicklung</category>
            <category>Betrieb</category>
            <enclosure url="https://blueshoe.de/img/blogs/gke-hpa.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Künstliche Intelligenz bedient Datenbank und API]]></title>
            <link>https://blueshoe.de/blog/kuenstliche-intelligenz-datenbank-api</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/kuenstliche-intelligenz-datenbank-api</guid>
            <pubDate>Wed, 04 Dec 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Überall wird über Künstliche Intelligenz gesprochen - auch wir bei Blueshoe kommen nicht an dem Thema vorbei. In diesem Blogpost bauen wir einen simpleren und doch beeindruckenden Case mit Google’s Vertex AI und dem Python Paket SQLModel. Wir demonstrieren, wie Datenbanken und APIs in die KI-Modelle eingewoben werden können. Das Szenario soll es ermöglichen, einfach Anfragen in natürlicher Sprache an unsere Datenbank zu stellen und hier Antworten zu erhalten.</p>
<p><img src="/img/blogs/vertex-ai.svg" alt="Blueshoe und FastAPI: Dokumentation mit Programmierbeispielen">{.object-cover .max-w-full .mb-5}</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Vertex AI und SQLModel
:::
:::GlobalParagraph
Die Plattform Vertex AI ist ein Produkt der Google Cloud und bietet Entwicklern die Möglichkeit, generative AI in ihre Applikationen einzubinden. Die Möglichkeiten sind zahlreich: Input und Output können via Text, Audio und Video erfolgen und es stehen verschiedene AI Modelle zur Auswahl. Für diesen Blogpost beschränken wir uns auf Text als Eingabe- und Ausgabemedium. Vertex AI Tutorials wie dieses zeigen, wie einfach die Integration in Projekte gelingt.
:::
:::GlobalParagraph
SQLModel ist ein Python Paket, welches auf Pydantic (einer Validierungsbiibliothek) aufsetzt und es ermöglicht, die Python-Datenmodelle in Datenbanken zu speichern oder aus diesen zu lesen. Praktisch ein ORM (=Object–relational mapping) auf Pydantic Basis, das sich hervorragend für eine KI Datenbank eignet. Künstliche Intelligenz mit Python zu kombinieren, wird damit besonders einfach.
:::</p>
<p>:::GlobalButton{:url="/loesungen/api-entwicklung/" :label="Erfahre mehr über unsere API-Entwicklungsdienste" :color="blue" .mb-6}
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Datenmodell und Generierung von Daten
:::</p>
<p>:::GlobalParagraph
Folgendes Szenario nehmen wir an:
Wir sind ein Buchladen und führen eine Datenbank mit Büchern. Es ist bekannt, wie viele Exemplare pro Buch im Lager vorhanden sind. Zu jedem Buch ist der Autor bekannt.
:::
:::GlobalParagraph
Das Datenmodell sieht folgendermaßen aus:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-python">class Author(SQLModel, table=True):
   id: Optional[int] = Field(default=None, primary_key=True)
   first_name: str
   last_name: str
   birthday: date

class Book(SQLModel, table=True):
   id: Optional[int] = Field(default=None, primary_key=True)
   name: str
   author_id: int = Field(default=None, foreign_key="author.id")
   num_in_stock: int = 0
</code></pre>
<p>:::</p>
<p>:::GlobalParagraph{.mb-4}
Bücher und Autoren sind über eine klassische Foreign-Key-Beziehung miteinander verbunden. Es lassen sich mit SQLModel nun ganz einfach Queries an die Datenbank formulieren, um eine KI API aufzubauen - z.B. die Selektion aller Autoren mit einem bestimmten Vor- und Nachnamen:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-python"># select Author with matching first_name and last_name
statement = select(Author).where(Author.first_name == first_name, Author.last_name == last_name)
</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Soweit so gut. Via Polyfactory generieren wir uns noch ein paar Testdaten:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-python">class AuthorFactory(ModelFactory[Author]):
   __model__ = Author
   __faker__ = Faker(locale="de_DE")
   id = Use(lambda: None)

   @classmethod
   def first_name(cls) -> str:
       return cls.__faker__.first_name()

   @classmethod
   def last_name(cls) -> str:
       return cls.__faker__.last_name()

class BookFactory(ModelFactory[Book]):
   __model__ = Book
   __faker__ = Faker(locale="de_DE")
   id = Use(lambda: None)

   @classmethod
   def name(cls) -> str:
       return cls.__faker__.catch_phrase()

def create_authors():
   for i in range(0, 1000):
       author = AuthorFactory.build()
       with Session(engine) as session:
           session.add(author)
           session.commit()
           for i in range(1, randrange(1, 10)):
               create_book(author_id=author.id)

def create_book(author_id):
   book = BookFactory.build(author_id=author_id)
   with Session(engine) as session:
       session.add(book)
       session.commit()

# init sqlite
engine = create_engine("sqlite:///database.db")
SQLModel.metadata.create_all(engine)
# call create fcnt
create_authors()
</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Und schon haben wir 1000 verschiedene Autoren mit zwischen 1 und 10 Büchern. Die Bücher bekommen zufällige Verfügbarkeiten (Anzahl im Lager) zugeordnet.
:::</p>
<p>::GlobalCallAnExpert{:bg="bg-bs-blue"}
#buttonBlockTitle
Wir können auch für Dich KI-Lösungen bauen.
::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Verbindung mit Vertex AI
:::</p>
<p>:::GlobalParagraph
Zuallererst wäre zu definieren, auf welche Funktionalität unser Agent Zugriff haben soll. Wir nehmen an, dass der Agent Bücher für einen bestimmten Autor ermitteln können soll und auch die Anzahl von Exemplaren auf dem Lager für ein bestimmtes Buch kennen muss.
:::
:::GlobalParagraph
Dafür werden folgende Hilfsfunktionen angelegt:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-python">from utils import (
   get_book_id_by_title,
   get_books_for_author,
   get_num_in_stock_for_book,
)
</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Vertex AI braucht nun Informationen über die Funktionen, was diese tun und welche Eingabeparameter zu erwarten sind. Dies wird mithilfe von FunctionDeclarations gemacht:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-python">get_authors_for_book_func = FunctionDeclaration(
   name=GET_BOOK_BY_AUTHOR,
   description="Get a list of book names for an author.",
   parameters={
       "type": "object",
       "properties": {
           "first_name": {
               "type": "string",
               "description": "The first name of the author",
           },
           "last_name": {
               "type": "string",
               "description": "The last name of the author",
           },
       },
   },
)

</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Die Funktion selbst sieht folgendermaßen aus:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-python">def get_books_for_author(first_name: str, last_name: str):
   with Session(engine) as session:
       statement = select(Author).where(Author.first_name == first_name, Author.last_name == last_name)
       author = session.exec(statement).first()
       statement = select(Book).where(Book.author_id == author.id)
       books = session.exec(statement=statement).all()
       return [book.name for book in books]
</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Die Funktionen werden in ein "Tool" zusammengefasst und dem Modell zur Verfügung gestellt:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-python">tools = Tool(
   function_declarations=[
       get_authors_for_book_func,
       get_book_id_by_title_func,
       get_num_in_stock_for_book_func,
   ],
)
model = GenerativeModel(
   model_name="gemini-1.5-pro-002",
   generation_config=GenerationConfig(temperature=0),
   tools=[tools],
)

</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Das Modell kann den Input eines Nutzers nun klassifizieren und eine Einschätzung geben, welche der Funktionen verwendet werden soll. Diese kann dann aufgegriffen werden:
:::</p>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-python">if function_calls:
   api_responses = []
   for func in function_calls:
       if func.name == GET_BOOK_BY_AUTHOR:
           api_responses.append(
               {
                   "name": func.name,
                   "content": get_books_for_author(
                       first_name=func.args["first_name"],
                       last_name=func.args["last_name"],
                   ),
               }
           )
       elif func.name == GET_BOOK_ID_BY_TITLE:
           api_responses.append(
               {
                   "name": func.name,
                   "content": get_book_id_by_title(
                       title=func.args["title"],
                   ),
               }
           )
       elif func.name == GET_NUM_IN_STOCK_FOR_BOOK:
           api_responses.append(
               {
                   "name": func.name,
                   "content": get_num_in_stock_for_book(
                       title=func.args["title"],
                   ),
               }
           )

   # Return the API response to Gemini
   for api_response in api_responses:
       name = api_response.pop("name")
       response = chat_session.send_message(
           [
               Part.from_function_response(
                   name=name,
                   response=api_response,
               )
           ]
       )
       print(response.text)

</code></pre>
<p>:::</p>
<p>:::GlobalParagraph
Das Modell extrahiert also die Parameter aus der Eingabe des Nutzers. Diese kann dann im Funktionsaufruf verwendet werden.
:::
:::GlobalParagraph
Nun kann der Chat starten!
:::</p>
<p>:::GlobalParagraph{.italic .mb-[-20px]}
<strong>Which books are written by Guenther Hendriks?</strong>
:::
:::GlobalParagraph{.italic .mb-2}
Guenther Hendriks has written the following books: Digitized optimal circuit, Future-proofed content-based groupware, Universal clear-thinking Local Area Network, Digitized scalable service-desk, and Innovative full-range protocol.
:::</p>
<p>:::GlobalParagraph{.italic .mb-[-20px]}
<strong>How many copies of the first of those books are available?</strong>
:::
:::GlobalParagraph{.italic .mb-8}
There are 2089 copies of "Digitized optimal circuit" in stock.
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Fazit
:::
:::GlobalParagraph
Die API von Vertex AI funktioniert generell gut. Aktuell dauert die Rückmeldung immer ein paar Sekunden, was je nach Use-Case eventuell zu viel sein könnte. Das Interface ist intuitiv und einfach zu bedienen. Für manche Anfragen kommt gar keine Rückmeldung von Vertex AI, was dann abgefangen werden muss.
:::
:::GlobalParagraph
Vertex AI macht es möglich, bestehende APIs und Datenbanken über natürliche Sprache zugänglich zu machen, was cool und schnell funktioniert, allerdings noch etwas ausreifen muss. Es erlaubt eine effiziente Nutzung der KI Datenbank und der Einbindung in verschiedene Systeme. Besonders für Entwickler, die KI programmieren mit Python wollen, ist dies eine spannende Lösung.
:::</p>]]></content:encoded>
            <category>API</category>
            <category>KI</category>
            <enclosure url="https://blueshoe.de/img/blogs/vertex-ai.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Links und Linkstruktur in django CMS]]></title>
            <link>https://blueshoe.de/blog/links-django-cms</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/links-django-cms</guid>
            <pubDate>Wed, 17 May 2017 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Links sind ein essentieller Bestandteil von Webseiten und machen das Internet prinzipiell zum dem was es ist. Hier zeigen wir, wie wir Links in Django CMS verwenden, worauf wir dabei achten und wie wir eine konsistente Linkstruktur garantieren.</p>
<p><img src="/img/blogs/matthew-lancaster-MthZSf.jpg" alt="matthew-lancaster">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
<a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} bringt von Haus aus eine ganze Reihe Plugins mit. Darunter das TextPlugin. Es erlaubt uns einfach Texte in unser CMS einzufügen und diese zu bearbeiten.
:::</p>
<p><img src="/img/blogs/text_edit_django_cms.png" alt="text_edit_django_cms">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Weiterhin erlaubt uns <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} innerhalb des Plugins weitere Plugins zu verschachteln. Das sieht dann folgendermaßen aus:
:::</p>
<p><img src="/img/blogs/django_cms_structure_mode.jpg" alt="django_cms_structure_mode">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Dies ergibt dann folgendes Ergebnis auf unsere Webseite:
:::</p>
<p><img src="/img/blogs/django_cms_content_mode.jpg" alt="django_cms_content_mode">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Hier ist nun ein LinkPlugin in einem TextPlugin verschachtelt.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
DJANGOCMSPLUGIN_FILER_LINK – DAS DJANGO CMS LINK PLUGIN
:::
:::globalParagraph
Einfach, oder? Da der Link innerhalb des Textes steht, ist er dem TextPlugin untergeordnet. Das LinkPlugin erlaubt es uns einfach interne und externe Inhalte zu verlinken. Es folgen Beispiele:
:::
:::globalParagraph
Interne Verlinkung:
:::</p>
<p><img src="/img/blogs/django_cms_link_internal.jpg" alt="django_cms_link_internal">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Externe Verlinkung:
:::</p>
<p><img src="/img/blogs/django_cms_link_external.jpg" alt="django_cms_link_external">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
E-Mail-Adresse:
:::</p>
<p><img src="/img/blogs/django_cms_link_mail.jpg" alt="django_cms_link_mail">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Datei:
:::</p>
<p><img src="/img/blogs/django_cms_link_file.jpg" alt="django_cms_link_file">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Weiterhin ist es möglich Vorlagen für den Link zu definieren, sodass seine Erscheinung einfach durch den Nutzer umgestellt werden kann:
:::
:::globalParagraph
Vorher
:::</p>
<p><img src="/img/blogs/link_style_before.jpg" alt="link_style_before">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Nachher
:::</p>
<p><img src="/img/blogs/link_style_after.jpg" alt="link_style_after">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Falls sich der Link in einem neuen Fenster öffnen soll ist dies einfach über eine Checkbox einzustellen.
:::
:::globalParagraph
Für die fortgeschrittenen Nutzer ist es möglich weitere Attribute an den Link zu hängen.
:::
:::globalParagraph
Möchte man beispielsweise einen „title“ zu dem Link hinzufügen wäre das hierüber problemlos möglich.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
BLUESHOE’S ANPASSUNGEN FÜR DAS PLUGIN
:::
:::globalParagraph
Das Link Plugin bringt alle Basisfunktionalitäten mit, welche wir hierfür benötigen. An dieser Stelle haben wir noch etwas weitergedacht: Wie können wir das Plugin benutzerfreundlicher machen? Was passiert mit toten Links?
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
VERBESSERTE ÜBERSICHT
:::
:::globalParagraph
Um eine bessere Übersicht zu erlangen haben wir die Oberfläche des Plugins für Autoren und Editoren überarbeitet. Es werden nun nicht mehr alle Möglichkeiten interne, externe Verlinkungen sowie Mail- und Dateiverlinkungen untereinander angezeigt, sondern als Tableiste in der Administrationsoberfläche hinterlegt.
:::</p>
<p><img src="/img/blogs/tabs.jpg" alt="tabs">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Der Tab, für den eine Verlinkung hinterlegt ist, ist automatisch aktiv. Von nun an ist es nicht mehr möglich mehrere Verlinkungen zu hinterlegen, diese sind nun komplett exklusiv. Dadurch erlangt der Nutzer schneller eine Übersicht und die Oberfläche wirkt etwas schlanker (vergleiche obere Screenshots).
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
404 UND 500 FEHLER VERMEIDEN
:::
:::globalParagraph
404 Fehler treten auf, wenn die angeforderte Seite nicht auf dem Server gefunden werden kann. Das ist absolut normal, denn Webserver können natürlich nicht jede URL bedienen, welche bei ihnen angefragt wird. Dennoch möchte man natürlich sehen, ob 4xx Fehler verursacht werden, um beispielsweise Tippfehler in der URL durch eine Weiterleitung auszugleichen. Ebenso sollte man darauf achten, dass alle verlinkten Seiten funktionstüchtig sind und nicht mit einem 5xx Fehler antworten.
:::
:::globalParagraph
Dafür haben wir den LinkHealthState eingeführt und verwenden das Plugin regelmäßig bei unserer Softwareentwicklung. Über ein Dashboard im <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} Administrator kann man alle Links, sowohl intern als auch extern, auf der Webseite analysieren und den Status der verlinkten Seiten einsehen. Dabei wird gelistet, ob der Link auf eine Weiterleitung (3xx), eine nicht erreichbare Seite (4xx) oder eine fehlerhafte Seite führt (5xx). Ist mit dem Link alles in Ordnung, so wird dieser nicht gelistet. Ein Button erlaubt es die Analyse neu auszuführen.
:::</p>
<p><img src="/img/blogs/link_health_state_django_cms.jpg" alt="link_health_state_django_cms">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Weiterhin werden fehlerhafte Links im Frontend gekennzeichnet, sodass Editoren schnell den entsprechenden Link auf einer Seite finden können.
:::</p>
<p><img src="/img/blogs/django_cms_link_error.jpg" alt="django_cms_link_error">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Somit ist das LinkPlugin nicht mehr nur ein herkömmliches Plugin, sondern erlaubt auch eine schnelle Analyse der Webseite um Linkkonsistenz gewährleisten zu können. Der aktuelle Entwicklungsstand ist unter <a href="https://github.com/Blueshoe/djangocms-link2">https://github.com/Blueshoe/djangocms-link2</a>{.bs-link-blue :target="_blank"} einsehbar. Feedback und Kritik zum Django-CMS Link Plugin von Blueshoe sind herzlich willkommen.
:::</p>]]></content:encoded>
            <category>Django CMS</category>
            <category>Django</category>
            <enclosure url="https://blueshoe.de/img/blogs/matthew-lancaster-MthZSf.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Managed vs. Unmanaged Kubernetes]]></title>
            <link>https://blueshoe.de/blog/managed-vs-unmanaged-kubernetes</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/managed-vs-unmanaged-kubernetes</guid>
            <pubDate>Mon, 17 May 2021 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Wir Blueshoes merken in Gesprächen mit Techies und vor allem mit unseren Kunden, dass immer mehr Interesse am Thema Kubernetes (K8s) besteht. In diesem Blogpost widmen wir uns deshalb der Frage "Was ist eigentlich managed und was is unmanaged Kubernetes?"</p>
<p><img src="/img/blogs/Bildschirmfoto_4.jpg" alt="Bildschirmfoto">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Beim Thema <a href="/technologien/docker-kubernetes/">Kubernetes (K8s)</a>{.bs-link-blue} stellen wir fest, dass bei einer der grundsätzlichen Entscheidungen und zwar, ob man managed oder unmanaged Kubernetes verwenden möchte, oftmals am wenigsten Wissen vorhanden ist. Das wollen wir ändern!
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
<strong>Kubernetes-Überblick</strong>
:::
:::globalParagraph
Wir setzen für diesen Blogpost ein gewisses <a href="/blog/kubernetes-development/">Grundverständnis von Kubernetes</a>{.bs-link-blue} (K8s) voraus. Dennoch wollen wir uns nochmal ganz kurz anschauen wie Kubernetes eigentlich funktioniert und vor allem wie damit interagiert wird:
:::
:::globalParagraph
Ganz unten an der Basis hat sich im Grunde nichts verändert, es werden “wie früher” immer noch virtuelle Server benutzt, um den Code auszuführen. <strong>Kubernetes fungiert als eine Art Abstraktions-Schicht über den virtuellen Servern, die dort Nodes genannt werden und zu einem Cluster zusammengefasst werden</strong> (siehe Schaubild unten von Kubernetes.io). Ich als Entwickler oder Operations-Spezialist muss mir also keine konkreten Gedanken machen, auf welcher Node meine Anwendung nun läuft. Ich muss Kubernetes nur in Form von Manifest-Dateien die Rahmenbedingungen meiner Anwendung beibringen, K8s kümmert sich dann darum, dass eine passende Node verwendet wird.
:::</p>
<p><img src="/img/blogs/bildschirmfoto_1.jpg" alt="bildschirmfoto">{.object-cover .w-full .mb-5}</p>
<p>:::globalParagraph
Die direkte <strong>Interaktion mit den virtuellen Maschinen wird mir somit durch Kubernetes abgenommen</strong>. Im K8s-Manifest sind einige Informationen angegeben, darunter das <a href="/blog/strategien-fur-schlanke-docker-images/">Container-Image</a>{.bs-link-blue} und dessen Version sowie die Spezifikation dazu, welcher Befehl ausgeführt wird. Hier findet man aber auch Dinge wie die Resource Requests und Limits – also Mindestanforderungen, die die Anwendung an CPU und Arbeitsspeicher stellt, bzw. welche Werte die Anwendung maximal beanspruchen darf.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
<strong>Unmanaged Kubernetes</strong>
:::
:::globalParagraph
Das “unmanaged” im Begriff <strong>unmanaged Kubernetes bezieht sich darauf, dass ich mich selbst um die Kubernetes Installation und Instandhaltung kümmern muss</strong>, also selbst Kubernetes managen muss. Das ist vor allem nötig, wenn aus welchen Gründen auch immer ein Hosting bei einem Cloud-Anbieter nicht möglich oder nicht erwünscht ist.
:::
:::globalParagraph
Im Folgenden beschreiben wir einen üblichen, wenn auch gekürzten <strong>exemplarischen Ablauf für die manuelle Installation</strong>:
:::
:::globalParagraph
<strong>Zunächst müssen die K8s-Nodes, also virtuelle Maschinen erstellt werden</strong>. In diesem Beispiel gehen wir von 3 virtuellen Servern aus, einen für die sogenannte Master-Node und zwei weitere für sogenannte Worker-Nodes. Die Master-Node kontrolliert dabei die Worker-Nodes, d. h. die K8s-Software, die auf dem Master installiert ist, entscheidet letztendlich, auf welcher Worker-Node eine Anwendung ausgeführt wird.
:::
:::globalParagraph
<strong>Als nächstes müssen die benötigten Software-Pakete installiert und konfiguriert werden</strong>. Unter anderem wird <em>kubeadm</em> benötigt, um einerseits die Master-Node zu initialisieren und um andererseits die Worker-Nodes dem Master zuzuweisen. Durch diesen Schritt ist der Kubernetes-Cluster geschaffen und im Grunde genommen betriebsbereit.
:::
:::globalParagraph
<strong>Als nächstes können die K8s-Manifeste direkt, oder auch durch Anwendungen z. B. mittels <em>helm</em> installiert werden.</strong>
:::
:::globalParagraph
<strong>Alternativ zur Installation aller benötigten einzelnen Komponenten wäre es z. B. auch möglich <em>microk8s</em> von Canonical zu verwenden</strong>. Dieses wird oftmals benutzt, um für die lokale Entwicklung Kubernetes zu simulieren. Aber seit ein paar Jahren bezeichnet Canonical selbst microk8s als production-ready. Der Vorteil zur vorigen Methode ist, dass man sich hier auf die Installation von einem Paket beschränken kann.
:::
:::globalParagraph
<strong>Zu beachten ist, dass sich “unmanaged” natürlich auch auf die Instandhaltung bezieht</strong>. Alle Maintenance-Aufgaben, sei es z. B. die Bereitstellung und Konfiguration weiterer Worker-Nodes, weil die zur Verfügung stehenden Ressourcen nicht mehr ausreichen, oder auch nur regelmäßige Updates der Kubernetes-Version müssen komplett händisch geschehen.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
<strong>Managed Kubernetes</strong>
:::
:::globalParagraph
Managed Kubernetes bezeichnet im Unterschied dazu nun <strong>eine Kubernetes-Installation, die von einem Provider zur Verfügung gestellt wird, bzw. bei einem Provider konfiguriert werden kann</strong>. Provider bzw. deren Services sind z. B. die Google Kubernetes Engine (GKE), Amazons AWS Elastic Kubernetes Service (EKS) oder auch managed Kubernetes bei IONOS.
:::
:::globalParagraph
<strong>Der Grad des “managed” ist dabei variabel</strong>. Oftmals wird “nur” die Möglichkeit bereitgestellt eine Kubernetes-Infrastruktur zu provisionieren, d. h. einen Cluster zu erstellen, sowie die Anzahl und Spezifikation der Worker-Nodes zu konfigurieren. Das ist bereits wesentlich komfortabler als unmanaged Kubernetes. Üblicherweise wird ein Web-Interface oder auch ein Command-Line-Interface zur Interaktion zur Verfügung gestellt. Darüber hinaus können z. B. zusätzliche Worker-Nodes hinzugefügt werden oder auch Updates auf eine neue Kubernetes-Version bequem eingespielt werden.
:::
:::globalParagraph
Ganz oben auf der “managed”-Skala steht z. B. das managed Kubernetes Angebot von Canonical. Dabei wird die komplette Kubernetes-Infrastruktur durch den Provider installiert und gewartet. Mit so einem Angebot schrumpft der Operations-Anteil auf das Erstellen der Kubernetes-Manifeste bzw. Helm-Charts.
:::
:::globalParagraph
Google hat am oberen Ende der “managed”-Skala den  <strong>GKE Autopilot</strong> <strong>im Angebot</strong>. Bei diesem werden die Nodes automatisch passend zu den durch die Anwendung angeforderten Ressourcen skaliert. Als Entwickler bzw. Operations-Spezialist kann ich mich auch dort auf das Erstellen der Kubernetes-Manifeste bzw. Helm-Charts beschränken. Den GKE Autopilot werden wir uns in einem künftigen Blogpost nochmal etwas genauer anschauen.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
<strong>Zusammenfassung</strong>
:::
:::globalParagraph
<strong>Unserer Erfahrung nach sind die meisten Projekte und Unternehmen mit managed Kubernetes besser beraten.</strong> Durch die verschiedenen Anbieter lassen sich viele Anforderungen hinsichtlich des Hostings abdecken. Je nach managed-Grad profitiert man von unterschiedlichem Komfort. Einer der größten Vorteile ist sicherlich, dass man sich nicht direkt um die Wartung des Clusters kümmern muss, sondern diese z. T. automatisiert erfolgt oder bequem per Web-Interface oder CLI zu erledigen ist.
:::
:::globalParagraph
<strong>Bestehen explizite Anforderungen von on-premise Hosting,</strong> oder aber triftige Gründe, Kubernetes z. B. in einem eigenen Rechenzentrum zu betreiben, dann <strong>führt kein Weg an unmanaged Kubernetes vorbei.</strong>
:::
:::globalParagraph
<strong>Gut zu wissen:</strong> Im Zusammenhang mit unserer neuen <a href="/loesungen/unikube/">Kubernetes-Service-Plattform Unikube</a>{.bs-link-blue} muss man sich die Frage managed vs. unmanaged Kubernetes gar nicht stellen. Für Unikube spielt es keine Rolle wie der K8s-Cluster installiert wurde, ob vollständig oder nur teilweise managed, unmanaged oder sogar nur lokal simuliert.
:::</p>]]></content:encoded>
            <category>Kubernetes</category>
            <category>Entwicklung</category>
            <enclosure url="https://blueshoe.de/img/blogs/Bildschirmfoto_4.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Migration nach Cloud Native]]></title>
            <link>https://blueshoe.de/blog/migration-nach-cloud-native</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/migration-nach-cloud-native</guid>
            <pubDate>Mon, 06 Sep 2021 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Der "Cloud Native"-Entwicklungsprozess hat in immer mehr unserer Projekte Einzug gehalten. Insbesondere bei bereits bestehenden Projekten kann es eine Herausforderung sein, diese in einen Cloud Native Workflow zu migrieren. In diesem Blogpost zeigen wir euch die notwendigen Schritte für eine Migration nach Cloud Native anhand dreier Beispiele.</p>
<p><img src="/img/blogs/animal_migration_2.jpg" alt="animal_migration">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Die ersten Hürden sind gemeistert. Wir wissen, warum wir Cloud Native entwickeln wollen, unsere Entwickler sind für den Prozess geschult und neue Projekte werden von Anfang an Cloud Native gestaltet. Dennoch kann die Migration zu Cloud Native eine echte Herausforderung darstellen. Deshalb erfahrt ihr am Ende dieses Artikels, wie Unikube bei der Migration unterstützen kann.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
<strong>Art der Migration</strong>
:::
:::globalParagraph
Bei der Migration wollen wir letztlich, dass unsere Produktiv- und Staging-/Testing-Systeme mittels Kubernetes (K8s) gehostet werden und auch unserer Entwickler mit persönlichen, bzw. individuellen Kubernetes Clustern entwickeln. Wie das lokale Entwickeln mit Kubernetes aussehen kann, haben wir schon einmal in diesem <a href="/blog/kubernetes-development/">Blog-Post</a>{.bs-link-blue} erläutert.
:::
:::globalParagraph
Je nach Entwicklungsprozess bringt ein bestehendes Projekt verschiedene Elemente mit, auf denen man bei der Migration aufbauen kann oder die erst noch vorbereitet werden müssen. Einer der größeren Punkte ist, ob das Projekt bereits Container-Images verwendet oder nicht. Wir schauen uns drei Beispielprojekte an, die sich im Entwicklungsvorgehen und Hosting unterscheiden:
:::
:::globalParagraph
Zum einen ein Projekt das lokal mit Vagrant entwickelt wurde und mittels einem für Django-Projekte recht üblichen Tech-Stack von uwsgi und nginx gehostet ist. Die anderen beiden Projekte verwenden bereits Docker-Images. Für die lokale Entwicklung wird bei beiden Docker-Compose verwendet, eines wird auch mit Docker-Compose gehostet, das andere mit Kubernetes.
:::
:::globalParagraph
In der folgenden Grafik, die die zeitliche Entwicklung von Entwicklungs- und Hosting-Systemen veranschaulicht, können wir unsere Beispielprojekte eindeutig zuordnen. Für diesen Blogbeitrag ignorieren wir die erste Stufe, ‘Bare Metal’. Im Bereich ‘Development Platform’ finden wir unsere drei Projekte unter den Stufen 'Virtualized' und 'Containerized'. In der ‘Operating Platform’ können wir den Pfeilen folgen und finden unsere Projekte unter ‘Virtual Machines’, ‘Container Runtimes &#x26; PaaS’ sowie ‘Container-Orchestration’. Ziel ist es, dass alle drei Projekte sowohl in der Entwicklung als auch im Betrieb unter der Stufe ‘Container-Orchestrated’ erscheinen.
:::</p>
<p><img src="/img/blogs/migration-to-cloud-native-1.jpg" alt="Migration to cloud-native">{.object-cover .w-full .mb-5}</p>
<p>:::globalTitle{:size="md" :tag="h3" .mb-5}
Beispiel 1: Lokale Entwicklung mit Vagrant
:::</p>
<p>:::globalParagraph
Vagrant wurde entwickelt, um die Entwicklungsumgebung in einer komplett virtuellen Maschine (VM) abzubilden, um dort so gut wie irgend möglich die Produktivumgebung nachzustellen. Da im Cloud Native-Prozess keine kompletten VMs mehr verwendet, sondern die Anwendungen mit ihren Umgebungsvariablen in Container gepackt werden, ist der erste Schritt eine Migration hin zu Docker.
:::
:::globalParagraph
Dazu muss ein Dockerfile geschrieben werden, welches die Anwendung umfasst. An dieser Stelle kann es auch Sinn machen, zu überlegen, welche Teile der Anwendung in einzelne Services aufgeteilt werden könnten. Zuvor gab es ja nur eine Vagrant-VM, die Anwendung könnte im schlimmsten Fall ein riesiger Monolith sein. Zumindest Systeme, wie eine Datenbank oder ein Cache sollten nicht im Dockerfile landen, sondern als eigene Services konfiguriert werden.
:::
:::globalParagraph
Der zweite Schritt ist, die Kubernetes-Manifeste zu erstellen, z. B. mittels Helm. Das bedeutet, es müssen Helm-Charts geschrieben werden, die für alle benötigten Services die entsprechenden K8s-Ressourcen erzeugen.
:::
:::globalParagraph
Als letztes muss “nur” noch der Entwicklungsprozess auf Kubernetes umgestellt werden. Das heißt, unseren Entwicklern stehen persönliche, bzw. individuelle Cluster zur Verfügung. Das könnten zum einen lokale Cluster sein, welche z.B. mittels k3d, microk8s, oder minikube simuliert werden. Zum anderen könnten diese individuellen “Entwickler-Cluster” aber auch remote existieren. Sozusagen ein realer K8s-Cluster, der aber nur von einem Entwickler genutzt wird.
:::</p>
<p>:::globalTitle{:size="md" :tag="h3" .mb-5}
Beispiel 2: Lokale Entwicklung und Hosting mit Docker-Compose
:::
:::globalParagraph
Unser zweites Projekt verwendet Docker-Compose sowohl in der Entwicklung als auch beim Hosting des Produktivsystems, d. h. wir verfügen über ein oder mehrere Dockerfiles und haben uns bereits zu Beginn des Projekts grundlegend Gedanken dazu gemacht, welche verschiedenen Services die Anwendung benötigt. Diese sind im <em>Docker-Compose.yaml</em> abgebildet.
:::
:::globalParagraph
Der Hauptteil der Migration besteht darin die Kubernetes-Manifeste zu erstellen. Dies kann wie beim vorigen Projekt auch mittels Helm-Charts geschehen. Durch Helm-Charts sind Kubernetes-Manifeste einfacher zu pflegen. Das fällt insbesondere bei größeren Projekten ins Gewicht. Wenn z. B. Deployments recht ähnlich aufgebaut sind, lässt sich das in Helm-Templates geschickter abbilden (Stichwort DRY). Sollte das keine Anforderung sein, könnte man die Manifest-Dateien auch direkt aus dem <em>Docker-Compose.yaml</em> erzeugen. Kubernetes stellt dafür den Command <em>kompose</em> zur Verfügung. Die Verwendung ist simpel, ein einfaches <em>kompose</em> convert genügt, um die Dateien zu erzeugen.
:::
:::globalParagraph
Auch hier muss daraufhin natürlich der lokale Entwicklungsprozess auf Kubernetes umgestellt werden.
:::</p>
<p>:::globalTitle{:size="md" :tag="h3" .mb-5}
Beispiel 3: Lokale Entwicklung mit Docker-Compose, Hosting mit Kubernetes
:::</p>
<p>:::globalParagraph
Bei diesem Projekt haben wir unsere Anwendung bereits in Services unterteilt und wir haben ein oder mehrere Dockerfiles. Bei der lokalen Entwicklung kommt Docker-Compose zum Einsatz, gehostet wird mittels Kubernetes. Dementsprechend besteht eigentlich der einzige Schritt der Migration darin, den lokalen Entwicklungsprozess auf Kubernetes umzustellen. Ansonsten muss die Produktivumgebung für die lokale Entwicklung immer im <em>Docker-Compose.yaml</em> nachgestellt werden. Das ist zum einen ein gewisser zusätzlicher Aufwand, zum anderen hat man das Problem, dass die lokale Umgebung nicht exakt der Produktivumgebung entspricht und es somit beim Deployment zu unvorhergesehenen Problemen oder Nebenwirkungen kommen kann.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
<strong>Herausforderungen bei der Migration</strong>
:::
:::globalParagraph
Bei der Migration unserer drei Beispielprojekte gilt es ein paar Herausforderungen zu meistern. Diese beziehen sich einerseits auf die Migration an und für sich. Andererseits auch darauf, wie die Umstellung des lokalen Entwicklungsprozesses von den Entwicklern aufgenommen wird und sie z. B. erst noch eine handvoll Tools lernen müssen. Wir treffen hier außerdem die Annahme, dass ein Operations-Spezialist mit Kubernetes-Expertise mit im Team ist, um die Helm Charts zu entwickeln.
:::
:::globalParagraph
Nun stellt sich die Frage, welche Hürden es aus Entwicklersicht noch gibt, um alle Projekte nach Cloud Native zu migrieren:
:::
:::GlobalBlock{.ol-decimal .mb-5}</p>
<ol>
<li>Umgang mit <em>kubectl</em> erlernen, um direkt mit dem Cluster interagieren zu können.</li>
<li>Live-reloading des Codes: Wenn der Code geändert wird, sollten die Änderungen möglichst schnell testbar sein, ohne zuerst ein neues Docker-Image builden und im lokalen Cluster deployen zu müssen. Dies ist z. B. mit Telepresence möglich, ein weiteres Tool das unsere Entwickler lernen müssen.</li>
<li>Der Debugger ist sicherlich für die meisten Entwickler sehr wichtig für einen optimalen Entwicklungsprozess. Dieser muss bei einem lokalen Kubernetes-Cluster nochmal explizit konfiguriert werden. Im Python-Umfeld z. B. mit <em>python remote debug</em>.
:::
:::globalParagraph
Unsere Entwickler müssen also drei weitere Tools zumindest in den Grundzügen lernen, um über den Funktionsumfang zu verfügen, den das Docker-Compose-Setup zur Verfügung gestellt hat. Das ist natürlich keine unmögliche Hürde, bedeutet aber zusätzlichen Aufwand.
:::</li>
</ol>
<p>:::globalTitle{:size="lg" .mb-5}
Wie unterstützt Unikube bei der Migration?
:::
:::globalParagraph
Zum Abschluss betrachten wir noch kurz, wie <a href="/tools">Unikube</a>{.bs-link-blue} bei der Migration nach Cloud Native ins Spiel kommt. Unikube fungiert praktisch als eine Art “Wrapper” für verschiedene Tools bzw. Funktionalitäten. Das bedeutet, dass ein Entwickler, der mit Unikube arbeitet, nur noch ein Tool und nicht mehr mehrere erlernen muss. Er muss sich somit außerdem kein detailliertes Kubernetes-Wissen mehr aneignen und auch nicht direkt mit dem Kubernetes-Cluster interagieren.
:::
:::globalParagraph
Unikube wurde unter dem Aspekt entwickelt, möglichst einfach und bequem per Command Line Interface zu benutzen zu sein. Der Anspruch ist hier, auf eine ähnliche Convenience zu kommen, wie man sie von Docker-Compose gewohnt ist. Und dabei wird auch noch ganz nebenbei die Kubernetes-Komplexität aufgelöst!
:::
:::globalParagraph
Seid gespannt, wie sich Unikube mit neuen Features weiterentwickelt und gebt uns gerne Input, z. B. auf GitHub.
:::</p>]]></content:encoded>
            <category>Entwicklung</category>
            <category>Betrieb</category>
            <category>Sicherheit</category>
            <category>Dokumentation</category>
            <enclosure url="https://blueshoe.de/img/blogs/animal_migration_2.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Minikube vs k3d vs kind vs Getdeck]]></title>
            <link>https://blueshoe.de/blog/minikube-vs-k3d-vs-kind-vs-getdeck-beiboot</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/minikube-vs-k3d-vs-kind-vs-getdeck-beiboot</guid>
            <pubDate>Mon, 27 Feb 2023 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Was ist das beste Kubernetes-Tool für die Entwicklung im Jahr 2023? Dieser Artikel vergleicht drei der beliebtesten Lösungen. Getdeck, entwickelt von Blueshoe, ist eine neue Alternative zur lokalen Kubernetes-Entwicklung, die auf den Markt kommt.</p>
<p><img src="/img/blogs/minikube-vs-k3d-vs-kind-vs-getdeck-beiboot.jpg" alt="minikube vs k3d vs kind vs getdeck">{.object-cover .max-w-full .mb-5}</p>
<h2>Einführung</h2>
<p>In diesem Artikel vergleichen wir drei beliebte lokale Kubernetes-Entwicklungstools. Zusätzlich wird Getdeck Beiboot als remote Kubernetes-basierte Entwicklungsumgebung in den Vergleich aufgenommen.</p>
<p>Der Schwerpunkt dieses Blogbeitrags liegt auf der Bewertung der DX ("Developer Experience") in tatsächlichen Entwicklungsszenarien. Dies ist besonders wichtig, da du diese Tools möglicherweise auch für Produktbereitstellungen verwenden kannst. Die wichtigen Dimensionen für die Bewertung dieser Tools unterscheiden sich jedoch sehr zwischen Entwicklung und Produktionshosting.</p>
<p>Die folgenden Aspekte sind für Softwareentwicklungsfälle relevant:</p>
<ul>
<li>Einfache Installation</li>
<li>Einfache Bedienung, Komplexität</li>
<li>Funktionsvollständigkeit (insbesondere für die Entwicklung und Produktionsparität)</li>
<li>Ressourcenverbrauch</li>
<li>Gesamte Benutzerfreundlichkeit (die sogenannte Entwicklererfahrung, DX)</li>
</ul>
<p>Diese Liste der Bewertungskriterien ist nicht abschließend. Es gibt auch einige Aspekte, die die Arbeit mit diesen Tools attraktiv machen, wie persönliche Vorlieben. Ich werde jedoch nicht auf alle diese Kriterien in diesem Artikel eingehen.</p>
<p>Alle Tools sind in der Lage, dem Entwickler eine dedizierte Kubernetes-Umgebung zum Erlernen von Kubernetes, zum Herumspielen oder zur Lösung von Entwicklungsaufgaben bereitzustellen.</p>
<h2>Minikube vs. k3d</h2>
<h2>Minikube</h2>
<p><a href="https://minikube.sigs.k8s.io/">minikube</a>{target="_blank"} ist eine der ausgereiftesten Lösungen auf dem Markt. Als unser Team bei Blueshoe im Jahr 2017 begann, <a href="/blog/kubernetes-development/">Kubernetes</a> zu übernehmen, war minikube bereits verfügbar. Die erste Version 0.1.0 wurde am 30. Mai 2016 veröffentlicht, kurz nach dem <a href="https://github.com/kubernetes/minikube">ersten Commit auf Github</a>{target="_blank"} am 16. April 2016.</p>
<p><img src="/img/blogs/minikube-vs-k3d-vs-kind-vs-getdeck-beiboot-1.jpg" alt="minikube">{.object-cover .max-w-full .mb-5}</p>
<p>minikube wurde von einer Kubernetes SIG, einer Interessengruppe, gestartet, die den Bedarf an lokalen Kubernetes-Umgebungen erkannte. Heute ist die <a href="https://minikube.sigs.k8s.io/community/">SIG eng mit dem Kubernetes-Entwicklungsteam verbunden</a>{target="_blank"} und daher auf dem neuesten Stand des offiziellen Kubernetes-Codebasis.</p>
<p>:::GlobalPodcastSection{:videoId="tyvE9VlSWkE" :videoPosition="left" .mb-6}
::::GlobalPreTitle{:color="text-bs-green" .mb-3}
TOOLS FOR THE CRAFT
::::</p>
<p>::::GlobalTitle{:tag="h3" .mb-6}
E1: Kubernetes-Entwicklungsumgebungen
::::</p>
<p>::::GlobalParagraph{:font-size="lg" .mb-4}
Michael und Robert sprechen darüber, wie wir von Docker Compose zu einer echten Entwicklung mit Kubernetes gekommen sind. Sie diskutieren die verschiedenen Herausforderungen und welche Tools helfen können, Entwicklungsumgebungen näher an die Produktionsumgebung heranzuführen.
::::
:::</p>
<h3>Alleskönner für verschiedene Plattformen</h3>
<p>Ein sehr wichtiger Unterschied zwischen Minikube und allen anderen Teilnehmern ist, dass es Kubernetes-Cluster <a href="https://minikube.sigs.k8s.io/docs/drivers/">mit einem der verschiedenen Treiber</a>{target="_blank"} bereitstellen kann. Diese Treiber implementieren die Art und Weise, wie du den Kubernetes-Cluster auf einer Entwicklungs-Maschine ausführst: entweder in einer virtuellen Maschine (z.B. Hyper-V, KVM2, QEMU oder andere) oder in einer Container-Laufzeitumgebung (z.B. mit <a href="/blog/docker-vs-podman/">Docker oder Podman</a>). Wenn man sich Minikube mit den oben genannten Bewertungsaspekten ansieht, kann man Unterschiede in den Details zwischen diesen Treibern feststellen. Aber im Allgemeinen abstrahiert Minikube die Implementierung des Treibers für den Entwickler.</p>
<p>Daher ist es sehr wahrscheinlich, dass Minikube Kubernetes für praktisch jede Plattform ausführen kann, an der ein Entwickler arbeitet. Mit einer einheitlichen Benutzeroberfläche ist Minikube eine sehr plattformunabhängige Lösung. Wenn dein Team mit Windows, macOS, Linux oder sogar exotischeren Plattformen arbeitet, ist es ein großer Vorteil, wenn alle Mitglieder das gleiche Tool verwenden. Du kannst Wissen leichter teilen, Skripte für die Automatisierung bereitstellen und Dokumentation erstellen, die alle Plattformen gleichermaßen abdeckt.</p>
<p>Ein großer Pluspunkt für Minikube ist seine umfassende Dokumentation. Sie enthält nicht nur technische Referenzen, sondern auch eine lange Liste von Tutorials für viele spezifische Anwendungsfälle und Bereitstellungsszenarien.</p>
<h3>Verwende alle K8s-Funktionen mit Minikube</h3>
<p>Mit Minikube kann ein Entwickler praktisch jede erforderliche Kubernetes-Funktion verwenden. Einige von ihnen müssen mit dem Flag <em>–feature-gates</em> aktiviert werden. Dies ist eine Reihe von Schlüssel-Wert-Paaren, die Feature-Gates für experimentelle Funktionen beschreiben. Andere Funktionen werden vom Addons-System von Minikube gesteuert. <em>Addons</em> können von Drittanbietern integriert werden. Hier ist eine Liste von Addons von meinem System.</p>
<p>Diese Addons werden aktiviert mit...</p>
<pre><code class="language-bash">minikube addons enable [...]
</code></pre>
<p>...und ermöglichen es einem Minikube-Cluster, diese bestimmte Funktion im lokalen Entwicklungsscluster bereitzustellen. Wenn du beispielsweise Volumesnapshots benötigst, wie wir es beim Aufbau des Getdeck Beiboot-Regalfeatures getan haben, führe einfach Folgendes aus:</p>
<pre><code class="language-bash">minikube addons enable volumesnapshots
</code></pre>
<p>Das macht es sehr bequem, eine solche Funktion zu verwenden, ohne jede Entwicklungsumgebung von Anfang an aufzublähen. Außerdem stehen dem Team dieselben Addons zur Verfügung, vorausgesetzt, sie verwenden alle dieselbe Version von Minikube.</p>
<h3>Minikube-Profile: mehrere logische Cluster auf einer Entwicklungs-Maschine</h3>
<p>Als wir begannen, <a href="/blog/kubernetes-development/">Kubernetes</a> zu übernehmen, suchten wir nach einer Lösung, die es uns ermöglichte, mehrere logische Cluster auf einer Entwicklungs-Maschine zu verwalten. In den Jahren 2016/2017 legte Minikube nicht viel Wert auf dieses spezielle Feature. Es war nur möglich, einen Cluster pro Maschine zu starten, und es gab nur eine Single-Node-Cluster-Konfiguration. Aus diesem Grund haben wir uns bei Blueshoe entschieden, mit k3d zu arbeiten. Minikube hat jedoch dieses wichtige Entwickler-Feature aufgeholt und unterstützt jetzt mehrere sogenannte Minikube-Profile.</p>
<p>Minikube-Profile sind logische Cluster, die separat voneinander gestartet und gestoppt werden können. Es ermöglicht einem Entwickler, mehr als eine Kubernetes-basierte Entwicklungsumgebung zu haben. Denke nur an mehrere getrennte Projekte, die unterschiedliche Kubernetes-<a href="/loesungen/api-entwicklung/">API</a>-Versionen, Funktionen oder einfach unterschiedliche Workloads erfordern, die in ihnen ausgeführt werden. <a href="https://minikube.sigs.k8s.io/docs/commands/start/">Du kannst...</a>{target="_blank"}:</p>
<pre><code class="language-bash">minikube start -p myprofile1
</code></pre>
<p>...ausführen und du erhältst einen leeren neuen Cluster mit einem frischen Profil, das neben anderen Profilen existieren kann.</p>
<h2>k3d</h2>
<p>k3d ist in Bezug auf die Bereitstellung auf einer Entwicklungs-Maschine eingeschränkter. Von Anfang an unterstützte k3d nur eine lokale Container-Runtime für die Ausführung des Kubernetes-Clusters. Wie ich zuvor erwähnt habe, war es jedoch immer möglich, mehrere separate Cluster für die Entwicklung auf einem Host zu verwalten. Das war ein echtes Killer-Feature, insbesondere für Blueshoe, da wir mehrere verschiedene Kubernetes-Projekte für verschiedene Kunden betreiben. Gerade bei unserer Wartungsarbeit ist es ein Muss, gleichzeitig eine topaktuelle (keine Sorge, ich habe diesen Begriff erfunden) Entwicklungsumgebung sowie eine stabile, produktionsnahe Umgebung zu haben. Als Entwickler muss ich Fehlerkorrekturen in kürzester Zeit bereitstellen und die Entwicklung neuer Funktionen vorantreiben.</p>
<p><img src="/img/blogs/minikube-vs-k3d-vs-kind-vs-getdeck-beiboot-2.jpg" alt="k3d">{.object-cover .max-w-full .mb-5}</p>
<p>k3d basiert auf k3s, einer schlanken Kubernetes-Lösung, die von Rancher entwickelt wird. K3d ist jedoch nicht eng mit k3s verbunden und wird von einer <a href="https://k3d.io/v5.4.7/#what-is-k3d">Entwicklergemeinschaft</a>{target="_blank"} vorangetrieben.</p>
<p>Das Besondere an k3s ist, dass es einige der standardmäßigen Kubernetes-Komponenten wie <a href="https://etcd.io/">etcd</a>{target="_blank"} durch weniger skalierbare und ressourcenintensive Alternativen (z. B. SQLite) ersetzt. Darüber hinaus wird das gesamte System in eine sehr kleine ausführbare Binärdatei kompiliert (weniger als 40 MiB), was auch den Speicherplatzbedarf sehr gering hält. Das Basis-Kubernetes-System k3s wurde ursprünglich für IoT- und Edge-Computing-Umgebungen entwickelt. Ich würde sagen, dass es auch für Entwicklungsumgebungen perfekt ist, da diese geringen Ressourcenanforderungen perfekt passen. Den Vergleich des Ressourcenverbrauchs werden wir später in diesem Artikel sehen.</p>
<p>Da k3d nur ein Wrapper für k3s ist, kann es sich auf die Entwicklererfahrung konzentrieren. Es wird mit <a href="https://k3d.io/v5.4.7/usage/commands/k3d/">sehr guter Dokumentation</a>{target="_blank"} geliefert, genau wie bei Minikube, die auch Tutorials für bestimmte Anwendungsszenarien enthält. Zum Beispiel findest du ein Beispiel für einen Entwicklungsworkflow mit <a href="https://tilt.dev/">Tilt</a>{target="_blank"} und einen Build-Push-Test-Zyklus mit k3d's <a href="https://k3d.io/v5.4.7/usage/registries/#using-a-local-registry">Container-Image-Sharing</a>{target="_blank"}-Funktion.</p>
<h3>Gut für Teams: Teilen von k3d-Konfigurationen</h3>
<p>Ein großer Vorteil, den k3d bietet (den Minikube zu diesem Zeitpunkt vermisst), ist, dass k3d eine <a href="https://k3d.io/v5.4.7/usage/configfile/#using-config-files">Cluster-Konfigurationsdatei</a>{target="_blank"} bereitstellt (ab Version 4.0.0). Dadurch können Entwicklungsteams die Konfiguration eines k3d-Clusters in einer lokalen YAML-Datei speichern, die im Team geteilt werden kann. Diese Datei enthält die Konfiguration für fast alle Parameter, die einen Cluster ausmachen, z. B. die Anzahl der Clusterknoten, die Kubernetes-Version, die lokal zugeordneten Ports, Registrierungen, Funktionen und vieles mehr. Mit dieser Datei kannst du die gleiche Cluster-Konfiguration im Team problemlos bereitstellen, ohne dass der Entwickler einer Anleitung oder einem Skript folgen muss, um seinen lokalen Kubernetes-Cluster einzurichten. Du kannst k3d cluster create --config mycluster1.yaml ausführen und alles wird wie angegeben bereitgestellt. Meiner Meinung nach ist das viel einfacher als das, was du derzeit mit Minikube tun kannst.</p>
<h3>Keine Sorge um kubectl</h3>
<p>Mit beiden Lösungen, Minikube und k3d, wird der kubectl-Kontext des Entwicklers automatisch auf den neu erstellten Cluster gesetzt. Beide Alternativen benennen ihren kube-Kontext nach dem Cluster-Namen/Profil-Namen, der beim Erstellen des Clusters angegeben wurde. Auf diese Weise kann der Entwickler problemlos mit der Arbeit beginnen, ohne sich um die kubectl-Konfiguration kümmern zu müssen.</p>
<h3>Weniger Komplexität, weniger CLI-Befehle</h3>
<p>Da k3d nicht die Komplexität von Minikube bietet, ist die CLI viel weniger umfassend, aber dennoch einfach. Ich würde sagen, für Entwickler, die mit der CLI arbeiten, ist das ein Pluspunkt. Insbesondere bei Verwendung der k3d-Konfigurationsdatei kann ich die meisten Eingaben auf der Befehlszeilenschnittstelle einsparen und die Oberfläche der CLI auf die wenigen erforderlichen Befehle reduzieren: Starten, Stoppen und Löschen eines Clusters.</p>
<p>Ich vermute, dass in k3d nur wenige Funktionen fehlen, da sie in k3s nicht unterstützt werden, aber für 95% der Entwicklungsarbeit sollte es völlig ausreichend sein. Sogar der <a href="https://github.com/k3s-io/k3s/pull/6459">Snapshot-Controller</a>{target="_blank"} wurde kürzlich zu k3s hinzugefügt.</p>
<h2>Minikube vs. kind</h2>
<p>Kind ist ein weiteres Projekt, das von einer <a href="https://kind.sigs.k8s.io/">Kubernetes SIG</a>{target="_blank"} vorangetrieben wird. An diesem Punkt konnte ich nicht herausfinden, warum es noch gepflegt wird (ich habe einen Grund gefunden, aber lies weiter). Kind ist ein Akronym für "Kubernetes in Docker" und entstand aus der Idee, Kubernetes auf einer Container-Runtime (anstelle einer virtuellen Maschine) auszuführen. Heutzutage bevorzugt minikube jedoch ebenfalls Docker als Bereitstellungsoption, sodass es in diesem wichtigen Punkt keinen Unterschied mehr zwischen minikube und kind gibt. Sie haben jedoch eine schöne Seite in ihrer Dokumentation, auf der die <a href="https://kind.sigs.k8s.io/docs/design/principles/">Prinzipien und Zielanwendungsfälle von Kind</a>{target="_blank"} erklärt werden. Ich würde sagen, es läuft alles auf Automatisierung hinaus.</p>
<p><img src="/img/blogs/minikube-vs-k3d-vs-kind-vs-getdeck-beiboot-3.jpg" alt="minikube vs. kind">{.object-cover .max-w-full .mb-5}</p>
<h3>Konfigurationsdateien und K8s-Funktionen</h3>
<p>Ähnlich wie k3d bietet auch Kind die Möglichkeit, <a href="https://kind.sigs.k8s.io/docs/user/configuration/#a-note-on-cli-parameters-and-configuration-files">Konfigurationsdateien</a>{target="_blank"} zu verwenden. Ähnlich wie bei k3d kannst du...</p>
<pre><code class="language-bash">kind create cluster --config mycluster1.yaml
</code></pre>
<p>... ausführen, um einen lokalen Kubernetes-Cluster basierend auf der angegebenen Konfiguration zu erstellen.</p>
<p>Kind bietet auch Feature Gates, um experimentelle Kubernetes-Funktionen und viele andere Konfigurationsoptionen zu aktivieren.</p>
<h3>Podman? Rootless? Kind!</h3>
<p>Im Vergleich zu Minikube, wo die Verwendung von <a href="/blog/docker-vs-podman/">Podman</a> als experimentell betrachtet wird, bietet Kind solide Unterstützung für Podman. Das Projektteam hat sogar erhebliche Anstrengungen unternommen, um Kind auch im <a href="https://kind.sigs.k8s.io/docs/user/rootless/">rootless</a>{target="_blank"}-Modus auszuführen. Für diejenigen, für die dies wichtig ist, ist Kind derzeit die einzige Lösung. Natürlich bringt es jedoch mehrere Einschränkungen mit sich.</p>
<p>Kind verfügt über eine weniger komplexe CLI im Vergleich zu Minikube. Sie verzichtet auch auf Emojis, was ein Vorteil sein kann. Aber das ist Geschmackssache.</p>
<p>Wenn man die Startseite beider Produkte vergleicht, behauptet Minikube, sich "[...] <a href="https://minikube.sigs.k8s.io/docs/">auf die Unterstützung von Anwendungsentwicklern und neuen Kubernetes-Benutzern zu konzentrieren</a>{target="_blank"}.", während Kind "ursprünglich für das Testen von Kubernetes selbst entwickelt wurde, aber auch für die lokale Entwicklung oder CI verwendet werden kann". Ich denke, das gibt eine gewisse Vorahnung davon, worum es geht.</p>
<h2>Vergleich von Minikube, k3d und Kind im Jahr 2023</h2>
<p>Lass uns nun einen direkten Vergleich dieser drei Alternativen für die lokale Kubernetes-Entwicklung im Jahr 2023 durchführen.</p>
<h2>Beliebtheit</h2>
<p>Beliebtheit ist ein Indikator dafür, wie gesichert die fortlaufende Wartung eines Produkts ist. Eine gute Währung, um dies zu messen, ist die Anzahl der GitHub-Stargazers:</p>
<ol>
<li>Minikube: >25,8k Sterne auf GitHub</li>
<li>Kind: >11,1k Sterne auf GitHub</li>
<li>k3d: >4,1k Sterne auf GitHub</li>
</ol>
<p>Wie du sehen kannst, haben alle drei Kandidaten bereits eine erhebliche Beliebtheit auf GitHub. Minikube ist jedoch bei weitem die beliebteste Option. Ich würde sagen, dass alle drei Lösungen aufgrund ihrer lebendigen Community dauerhaft gewartet werden.</p>
<h2>Leistungsbewertung</h2>
<p>Die folgende Leistungsbewertung wurde auf einem Ubuntu-System durchgeführt, das auf einem Intel Core i7 (8. Generation) mit 16 GiB RAM läuft. Obwohl ich auf Linux arbeite, habe ich Docker Desktop auf meinem Rechner verwendet und hoffe, vergleichbare Ergebnisse mit anderen Plattformen zu sammeln.</p>
<p>Hinweis: Docker Desktop führt auf Linux ebenfalls eine QEMU-basierte virtuelle Maschine aus, genau wie auf Windows und macOS. Das Kubernetes in Docker Desktop wurde deaktiviert.</p>
<p>Ich habe die folgenden Versionen der Tools verwendet:</p>
<ul>
<li>Minikube: Minikube-Version: v1.26.1</li>
<li>Kind: Kind v0.17.0 go1.19.2 linux/amd64</li>
<li>k3d: k3d-Version v5.4.1</li>
</ul>
<h3>Cluster-Startzeit</h3>
<p>In diesem Testfall messe ich die Zeit vom Anfordern eines neuen lokalen Clusters bis zu dessen Start. Ich habe keine speziellen Konfigurationen angegeben, sondern die Standardwerte verwendet.</p>
<p>Ich habe diesen Test fünfmal durchgeführt und das beste Ergebnis aller Messungen genommen, damit keine Container-Images während der gemessenen Zeit heruntergeladen werden.</p>
<p>Die Startzeiten sind wie folgt:</p>
<ul>
<li>Minikube (Docker): 29,448s</li>
<li>k3d: 15,576 s</li>
<li>Kind: 19,691 s</li>
</ul>
<p>Die Startzeiten aller Kandidaten sind ziemlich nah beieinander. Wenn du beispielsweise zuerst die erforderlichen Container-Images herunterladen musst, wird dies wahrscheinlich den Gesamtprozess mehr beeinflussen als der zugrunde liegende Bootstrapping-Prozess. Mit Ausnahme des kvm2-Treibers von Minikube. Dieser Prozess ist viel aufwändiger und beinhaltet das Hochfahren einer gesamten virtuellen Maschine. Ich gehe davon aus, dass VM-basierte Treiber sowieso nicht die erste Wahl für die Mehrheit der Entwickler sind.</p>
<h3>Cluster-Abschaltzeit</h3>
<p>Ich habe die Zeiten für das Stoppen und Löschen eines Clusters gemessen. Ich habe diesen Test mehrmals durchgeführt und das beste Ergebnis aller Messungen genommen:</p>
<ul>
<li>Minikube (Docker): 2,616 s</li>
<li>k3d: 0,700 s</li>
<li>Kind: 0,805 s</li>
</ul>
<p>Alle Tools stoppen und löschen ihre Cluster sehr schnell. Kein großer Unterschied zwischen ihnen.</p>
<h3>Cluster-Ressourcenverbrauch</h3>
<p>Ich habe einen lokalen Kubernetes-Cluster gestartet und etwa 120 s nach dem abgeschlossenen Start die Ressourcenverbräuche für einen im Leerlauf befindlichen Einzelknoten-Cluster überprüft. Dafür habe ich den Befehl "docker stats" verwendet.</p>
<p>Bitte beachte, dass ich traefik bei k3d deaktiviert habe, um eine vergleichbare Konfiguration zu erhalten. Da k3d mindestens zwei Container ausführt, habe ich die Verbräuche aggregiert.</p>
<p>Hier sind die Ergebnisse:</p>
<ul>
<li>Minikube mit Docker (CPUs=8, Memory=15681 MiB):
CPU: ~20% Speicherauslastung: ~680,8 MiB</li>
<li>K3d (CPUs=8, Memory=15681 MiB):
CPU: ~20% Speicherauslastung: ~502 MiB</li>
<li>Kind (CPUs=8, Memory=15681 MiB):
CPU: ~20% Speicherauslastung: ~581 MiB</li>
</ul>
<p>Bei Betrachtung der Ergebnisse kann man einige Unterschiede zwischen Minikube und k3d oder Kind erkennen. Für einen leeren und im Leerlauf befindlichen Cluster benötigt Minikube etwa 35% mehr Speicher als k3d und 17% mehr Speicher als Kind. Ich vermute, dass mit zunehmender Anzahl von Workloads der Ressourcenverbrauch von Minikube sehr schnell an die Grenze der Entwicklungsmaschine stoßen wird.</p>
<p>In jedem Fall war ich sehr überrascht von der CPU-Auslastung, die von 10% auf 50% stieg, obwohl in diesen Clustern nichts los war. Dieses Muster trat bei allen Kubernetes-Anbietern auf.</p>
<h2>Benutzerfreundlichkeit und Entwicklererfahrung (DX)</h2>
<p>Benutzerfreundlichkeit und DX sind sehr komplexe Themen und es ist schwierig, quantitative Metriken zu finden. Dennoch möchte ich einige meiner Erkenntnisse hervorheben, die mir an den Tools gefallen oder nicht gefallen.</p>
<p>Alle Tools sind derzeit nur als CLI (Befehlszeilenschnittstelle) verfügbar. Das ist für mich in Ordnung und wahrscheinlich für einen Großteil der Entwickler auf Linux und macOS. Soweit ich weiß, arbeiten nur wenige Entwickler unter Windows gerne mit einem Terminal. Aus ihrer Sicht bietet eine CLI wahrscheinlich nicht die bestmögliche DX. Es gibt jedoch einige GUIs (grafische Benutzeroberflächen). Meine Ergebnisse dazu habe ich im nächsten Kapitel hinzugefügt.</p>
<h3>Minikube</h3>
<p>Minikube wird mit einer CLI geliefert, die viele Emojis verwendet. Das ist eine sehr individuelle Vorliebe, aber ich finde sie ein wenig nervig. Allerdings können <a href="https://github.com/kubernetes/minikube/issues/5024">sie deaktiviert werden</a>{target="_blank"}.</p>
<p>Die Installation ist sehr einfach. Du kannst es über Brew, ein Skript oder als Binärdatei herunterladen und manuell in deinen Pfad legen.</p>
<p>Minikube macht es sehr einfach und schnell, einen neuen Cluster zu erstellen. Es ist nur ein Befehl mit zwei Wörtern: <em>minikube start</em>. So einfach ist das. Wie gibt man Konfigurationsoptionen an? Genau! Direkt als Argument für den Startvorgang. Eine sehr wichtige Konfiguration ist die Kubernetes-API-Version. Es spielt keine Rolle, welche Version von Minikube du auf deinem lokalen Rechner installiert hast. Du kannst <a href="https://kubernetes.io/releases/">immer eine andere Kubernetes-API-Version</a>{target="_blank"} als die Standardversion auswählen. Das ist sehr einfach und intuitiv. Dein Produktionscluster läuft auf Version 1.25.5, dann möchtest du Folgendes ausführen:</p>
<pre><code class="language-bash">minikube start --kubernetes-version=1.25.5
</code></pre>
<p>...und du erhältst die richtige <a href="/loesungen/api-entwicklung/">API-</a> Version.</p>
<p>Andere grundlegende Clusteroperationen sind ähnlich: Das Anhalten, Stoppen oder Löschen des Clusters erfolgt immer mit nur einem Befehl.</p>
<p><strong>Saubere CLI, schnelles Kubernetes Dashboard</strong></p>
<p>Die Befehlspalette der minikube CLI ist sauber und übersichtlich. Wenn du mit mehreren Clustern parallel arbeitest, die entweder gestartet sind oder sich im Ruhezustand befinden, kannst du immer das Argument <em>-p/--profile</em> zu den meisten Aktionen hinzufügen und die gewünschte Aktion auf dem angegebenen Cluster ausführen.</p>
<p>Wie listet man alle vorhandenen Cluster auf der Maschine auf? Das ist eine</p>
<pre><code class="language-bash">minikube profile list
</code></pre>
<p>...und dir wird eine Liste der erstellten Cluster angezeigt.</p>
<p>Wenn du einen laufenden Cluster hast, kannst du immer das offizielle Kubernetes-Dashboard mit <em>minikube dashboard</em> öffnen (für das Standardprofil). Natürlich kannst du das Kubernetes-Dashboard immer auf jedem Cluster installieren, aber dieser Befehl ist wirklich eine Abkürzung, um nach wenigen Sekunden eine visuelle Benutzeroberfläche für diesen Cluster zu erhalten.</p>
<p><img src="/img/blogs/minikube-vs-k3d-vs-kind-vs-getdeck-beiboot-4.jpg" alt="minikube dashboard">{.object-cover .w-full .mb-5}</p>
<p><strong>Ingress hinzufügen</strong></p>
<p>Wenn du eine Kubernetes-Bereitstellung oder einen Dienst auf deiner lokalen Entwicklungsmaschine freigeben musst, verwende einfach die Netzwerk- und Verbindungskommandos:</p>
<ul>
<li><em>minikube service</em>: gibt eine URL zurück, um eine Verbindung zu einem Service herzustellen</li>
<li><em>minikube tunnel</em>: Verbindung zu LoadBalancer-Services herstellen</li>
</ul>
<p>Ein häufiges Komponente, die über Addons aktiviert werden muss, ist der Ingress-Controller. Normalerweise ist dies die bevorzugte Methode, um eine Anwendung freizugeben. Mit Minikube hast du standardmäßig keinen Ingress-Controller zur Verfügung, stattdessen musst du ihn manuell bereitstellen. Glücklicherweise gibt es ein Addon mit dem bekannten und weit verbreiteten "nginx-ingress". Führe einfach aus:</p>
<pre><code class="language-bash">minikube addons enable ingress
</code></pre>
<p>und du kannst Ingress-Objekte erstellen, die unter http://192.168.49.2 bereitgestellt werden. Bitte beachte, dass die IP-Adresse deines Clusters eine andere sein kann. Du kannst sie herausfinden mit</p>
<pre><code class="language-bash">minikube ip
</code></pre>
<p><strong>Kritik</strong></p>
<p>Es gibt nur eine Kritik, die ich an Minikube habe: die schlechten Automatisierungsoptionen. Es gibt keine Konfigurationsdatei, die ich einfach in den Befehl eingeben kann, um einen gesamten Cluster gemäß den Vorgaben einzurichten. Stattdessen muss ich all diese Befehle sequentiell ausführen. Das ist schade und kann in Zukunft verbessert werden.</p>
<p>Ein Befehl zum Generieren des Tab-Completion-Skripts ist auch für viele Terminals verfügbar.</p>
<h3>k3d</h3>
<p>Die Installation der k3d CLI ist sehr einfach. Du kannst sie über Brew, ein Skript oder als Binärdatei herunterladen und manuell in deinen Pfad legen. Die CLI benötigt jedoch etwas Zeit, um sich daran zu gewöhnen. Im Vergleich zu Minikube bietet k3d nicht so viele Funktionen auf der Befehlszeile, aber du kannst mit k3d fast alle erforderlichen Setups genauso gut realisieren.</p>
<h4>Weniger CLI-Optionen, aber Ingress out of the box</h4>
<p>Ein Entwickler wird die meisten praktischen Funktionen vermissen, die die Minikube CLI bietet, aber bei der k3d-CLI fehlen. Das ist jedoch kein großes Problem. Wenn du ein erfahrenerer Entwickler bist, arbeitest du wahrscheinlich sehr effizient mit kubectl und kennst andere Tools aus dem Ökosystem wie Helm oder Kustomize. Wenn du beispielsweise das Kubernetes-Dashboard benötigst, musst du es über Helm installieren (oder eine andere Installationsmethode). Das ist kein großes Problem, aber es ist nicht so bequem wie bei Minikube. Sobald du einen Cluster erstellt hast, wird dein globaler kubeconfig-Kontext so eingestellt, dass er auf den neuen Cluster zeigt.</p>
<p>k3d wird mit Traefik als Ingress-Controller geliefert. Es ist immer installiert, es sei denn du deaktivierst es explizit mit einem Konfigurationsflag. Bei Blueshoe fanden wir es sehr hilfreich, es immer verfügbar zu haben, da wir dieses wichtige Feature während der Entwicklungseinrichtung nicht extra handhaben mussten.</p>
<h4>Port-Mapping</h4>
<p>Das Festlegen des Port-Mappings auf deinem lokalen Rechner kann etwas umständlich sein. Wenn du beispielsweise eine Anwendung über Ingress auf Port 8080 auf deiner Entwicklungsmaschine freigeben möchtest, musst du dies während der Clustererstellung angeben. Und die Notation ist für Entwickler nicht super intuitiv. Schaue dir die Dokumentation an. Erstelle einen Cluster mit einer festen Reihe von Port-Mappings wie folgt:</p>
<pre><code class="language-bash">k3d cluster create -p "8080:80@loadbalancer" -p "8443:443@loadbalancer" …
</code></pre>
<p>Es sind auch andere Portkonfigurationen möglich, aber aus Sicht der Entwicklererfahrung ist es nicht sehr praktisch, den gesamten Cluster neu zu erstellen, nur weil man vergessen hat, die Ports zuzuordnen.</p>
<p>Wenn du nur als Entwickler in einem Team arbeiten möchtest, erhältst du wahrscheinlich sowieso eine Cluster-Konfigurationsdatei. Mit dieser Datei und den richtigen Spezifikationen wird es dir leichtfallen, alles einzurichten. Du musst nur noch starten:</p>
<pre><code class="language-bash">k3d cluster create --config myconfig.yaml
</code></pre>
<p>...und innerhalb weniger Sekunden bist du startklar. Das ist schnell und sehr bequem. Ein großes Plus für die Entwicklererfahrung mit k3d.</p>
<p>Es gibt auch einen Befehl, um das Tab-Completion-Skript für viele Terminals zu generieren.</p>
<h4>kind</h4>
<p>kind ist in den meisten Aspekten sehr ähnlich zu k3d. Genau wie k3d und minikube kannst du es mit beliebten Paketmanagern, Skripten und als einzelne ausführbare Datei installieren.</p>
<p>Wenn du bereits mit der k3d CLO vertraut bist, wirst du dich wahrscheinlich schnell an die kind CLI gewöhnen. Die Optionen sind fast identisch, und auch die Einschränkungen sind ähnlich.</p>
<p>Es gibt in diesem Abschnitt nicht viel hinzuzufügen.</p>
<h2>Entwicklungsoptionen</h2>
<p>Es kann herausfordernd und umständlich sein, deinen eigenen Code in einem der Kubernetes-Tools auszuführen. Zunächst musst du Container-Images erstellen, da Kubernetes nur das Ausführen von Container-Instanzen zulässt. Normalerweise bezieht Kubernetes diese Images aus einem externen Container-Register (wie Dockerhub, quay.io oder einem selbst gehosteten Register). Wenn ein Entwickler eigenen Code ausführen möchte, erfordert dies eine Arbeitslastspezifikation und ein Register, das das Container-Image bereitstellt. Dies kann zu einem enormen Effizienzverlust führen.</p>
<p>Glücklicherweise bieten alle Tools einige Workarounds oder Abkürzungen, um diese Hürde zu überwinden (zumindest teilweise).</p>
<h5>Lokalen Code einbinden</h5>
<p>minikube und k3d bieten die Möglichkeit, Code direkt von der Entwicklermaschine in den laufenden Kubernetes-Knoten einzubinden.</p>
<p>Mit k3d ist dies mit dem <a href="https://docs.k3s.io/storage">lokalen Pfadbereitsteller</a>{target="_blank"} von k3s möglich. Ein Entwickler kann einen <a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/">PersistentVolumeClaim</a>{target="_blank"} erstellen, der auf einen Pfad im Host-System verweist. Anschließend kann dieser PVC in eine Container-Instanz eingebunden und im Container-Prozess verwendet werden. Dadurch kannst du entweder einen Container-Prozess mit dem aktuellen Code ausführen (den Container neu starten, sobald der Code geändert wurde) oder den Container-Prozess mit <em>Hot-Reloading-Funktionen</em> starten. Natürlich ist dies stark abhängig von einem Framework oder Prozess, der ausgeführt wird, und hat nichts mit Kubernetes zu tun. Dies funktioniert jedoch nur bei der Erstellung des Clusters wie folgt:</p>
<pre><code class="language-bash">k3d cluster create my-cluster --volume /my/home/go/src/github.com/nginx:/data
</code></pre>
<p>Das Hinzufügen von Volumes nachdem der Cluster erstellt und ausgeführt wird, ist <a href="https://github.com/k3d-io/k3d/issues/566">noch ein offenes Problem</a>{target="_blank"}.</p>
<p>Mit dem <a href="https://minikube.sigs.k8s.io/docs/handbook/mount/">minikube mount</a>{target="_blank"}-Befehl ist dasselbe möglich. Du kannst sogar Speichervolumes nach der Erstellung des Clusters einbinden. Anstatt einen Kubernetes PVC zu verwenden, kannst du den Code mit der <em>hostPath</em>-Eigenschaft eines Pods einbinden, was es etwas bequemer macht.</p>
<h5>Lokales Container-Image laden</h5>
<p>Ein praktischerer und weniger invasiver Ansatz, um lokalen Code in minikube, k3d und kind auszuführen, ist die Load-Image-Funktion. Warum weniger invasiv? - Als Entwickler musst du die Kubernetes-Objekte (Pods, PVCs usw.) für deine lokale Umgebung nicht anpassen, basierend auf den Pfaden, die möglicherweise einzigartig für dein System sind (z. B. das Einbinden von Home-Verzeichnissen unterscheidet sich normalerweise zwischen Entwicklern). Stattdessen stellst du ein Container-Image für deinen lokalen Cluster zur Verfügung, ohne dass ein dediziertes Container-Register erforderlich ist. Das bedeutet, du erstellst ein lokales Container-Image basierend auf deinem aktuellen Code (z. B. <em>docker build . -t myimage</em>) und überträgst es direkt in dein lokales Kubernetes, um es auszuführen.</p>
<p>Dieser Ansatz wird von fast allen Kubernetes-Entwicklungstools wie <a href="https://tilt.dev/">tilt.dev</a>{target="_blank"}, <a href="https://www.devspace.sh/">devspace</a>{target="_blank"} und anderen genutzt. Diese Arten von Entwicklungstools führen automatisch einen Build-Load-Execute-Zyklus durch, während sie auf Codeänderungen achten. Dieser Ansatz ist langsamer als das Einbinden von lokalem Code mit einem angepassten Container-Prozess, aber zumindest modifiziert er (normalerweise) nicht die Kubernetes-Objekte.</p>
<p>Um dies mit minikube zu tun, führst du aus...</p>
<pre><code class="language-bash">minikube image load &#x3C;name>
</code></pre>
<p>Bei k3d lade ein Image mit...</p>
<pre><code class="language-bash">k3d image import &#x3C;name>
</code></pre>
<p>und bei kind ist es...</p>
<pre><code class="language-bash">kind load docker-image &#x3C;name>
</code></pre>
<p>...von deiner Konsole aus.</p>
<p>Es gibt noch einige andere verfügbare Tools wie ksync, das Code in Container kopiert, die in Kubernetes laufen, aber mit einem allgemeineren technischen Ansatz. Eine großartige Option für Entwickler, die mit jeder Art von Kubernetes-Umgebung, sei es lokal oder remote, arbeiten, wird im nächsten Abschnitt vorgestellt.</p>
<h5>Die beste Alternative für die lokale Kubernetes-Entwicklung</h5>
<p>Die oben genannten Optionen machen nicht alle erforderlichen Entwicklungsfunktionen leicht zugänglich. Zum Beispiel ist das Überschreiben von Umgebungsvariablen nicht sehr einfach, da sie aus verschiedenen Kubernetes-Objekten stammen können: ConfigMaps, Secrets, Pod-Spezifikationen, Downward <a href="/loesungen/api-entwicklung/">API</a> und andere. Ein Entwickler, der nicht daran gewöhnt ist, mit Kubernetes zu arbeiten, könnte Schwierigkeiten haben, mit Umgebungsvariablen herumzuspielen.</p>
<p>Der allmächtige Debugger, der nicht einfach an einen in Kubernetes ausgeführten Prozess angehängt werden kann, ist mit den oben genannten Optionen nicht sehr praktisch. Die oben genannten Optionen haben auch einige andere Nachteile.</p>
<p>An diesem Punkt hat Blueshoe beschlossen, ein anspruchsvolleres Entwicklungstool zu entwickeln, das Entwickler von der Zeit im Build-Load-Execute-Zyklus oder vom Ausführen lokaler Verzeichnisse in Kubernetes entlastet: <a href="/technologien/#tools">Gefyra</a>.</p>
<p>Gefyra verbindet sich nicht nur mit lokalen Kubernetes-Clustern, die auf minikube, k3d oder kind basieren. Es verbindet sich mit praktisch jedem Kubernetes-Cluster, der irgendwo läuft. Dies ermöglicht Gefyra-Benutzern, dedizierte Entwicklungsumgebungen in der Cloud zu erstellen und gleichzeitig lokales Codieren für die Entwickler bereitzustellen.</p>
<p>Gefyra führt den Code auf einer lokalen Docker-Laufzeitumgebung aus (ganz ohne Kubernetes), erledigt jedoch einige Netzwerk- und Prozessmagie, um die lokale Container-Instanz mit einem Kubernetes-Cluster zu verbinden. Der Prozess auf einer Entwicklermaschine fühlt sich an, als würde er direkt in einem Kubernetes-Namespace ausgeführt (einschließlich Netzwerkfunktionen) und bietet den Vorteil, dass alle gängigen Entwicklungstools lokal verfügbar sind. Dies kann die Entwicklungsgeschwindigkeit drastisch verbessern und gleichzeitig eine sehr gute Übereinstimmung zwischen Entwicklung und Produktion gewährleisten.</p>
<p>Wenn du eine Meinung zu Gefyra hast, fehlende Funktionen oder einen Fehler melden möchtest, kannst du gerne ein Issue oder eine Diskussion auf <a href="https://github.com/gefyrahq/gefyra">GitHub</a>{target="_blank"} eröffnen.</p>
<h2>Grafische Benutzeroberflächen und Docker Desktop</h2>
<p>Wenn du nach einer grafischen Benutzeroberfläche für deinen lokalen Kubernetes-Cluster suchst, wirf einen Blick auf <a href="https://github.com/inercia/k3x">K3x</a>{target="_blank"} und <a href="https://github.com/kubernetes-sigs/minikube-gui">minikube GUI</a>{target="_blank"}. Beide Projekte befinden sich zum Zeitpunkt des Verfassens dieses Artikels noch in einem sehr frühen Entwicklungsstadium.</p>
<p>Die Hauptziele dieser Projekte sind es, dem Benutzer zu ermöglichen, Kubernetes-Cluster mit einem Mausklick zu erstellen, zu starten und zu beenden. Darüber hinaus ermöglichen sie Entwicklern, die wichtigsten Operationen mit Tastenkombinationen zu verwalten und die Lernkurve bei der Verwendung von Kubernetes zu reduzieren.</p>
<p>Und dann gibt es Docker Desktop, das eine eigene Kubernetes-Lösung mitbringt. Allerdings bietet Kubernetes in Docker Desktop nicht wirklich die Funktionen, die minikube, k3d oder kind bieten. Du kannst lediglich einen Kubernetes-Cluster mit einer grafischen Benutzeroberfläche starten und stoppen.</p>
<h2>Eine cloud-basierte Kubernetes-Entwicklungsumgebung mit Getdeck</h2>
<p>Bei Blueshoe haben wir festgestellt, dass lokale Kubernetes-Cluster mit wachsenden Arbeitslasten eine Herausforderung darstellen. Insbesondere unter Windows und macOS verwandelt selbst eine geringe Anzahl von Entwicklungsaufgaben in Kubernetes die Entwicklungsmaschine in einen langsamen Zombie. Das war sehr unpraktisch, daher haben wir uns entschieden, nach anderen Lösungen für unsere Entwicklungsteams zu suchen. Für die komplexe Kubernetes-native Softwarearchitektur, an der wir arbeiten, war es nicht möglich, eine gemeinsame Cluster-Konfiguration zu erstellen. Das Aufteilen eines physischen Clusters mit Namespaces ist etwas, das viele Entwicklungsteams derzeit tun. Stattdessen wollten wir unseren Entwicklern dedizierte, vollwertige, bedarfsgesteuerte Kubernetes-Cluster zur Verfügung stellen. Aber mit allen Funktionen, die eine reife Entwicklungsorganisation benötigt, wie z.B. Lebenszyklusmanagement, Ressourcenbeschränkungen usw.</p>
<p>Dafür haben wir Getdeck entwickelt.</p>
<p>Mit Getdeck Beiboot muss ein Team von Entwicklern nur einen physischen Kubernetes-Cluster betreiben. Der Beiboot-Operator erstellt "virtuelle" Kubernetes-Cluster innerhalb des Host-Clusters und verwaltet deren Lebenszyklen. Die Erstellung einer ad-hoc Kubernetes-Umgebung dauert etwa 20 Sekunden und beansprucht keine Ressourcen auf einer Entwicklungsmaschine.</p>
<p>Darüber hinaus ermöglicht die Beiboot Shelf-Funktion Entwicklern, vorkonfigurierte Kubernetes-Cluster von der Stange zu erstellen. Das bedeutet, es dauert nur wenige Sekunden länger und Entwickler haben einen dedizierten Kubernetes-Cluster, der alle erforderlichen Arbeitslasten für ihre Aufgaben enthält und alle Daten enthält, die erforderlich sind, um die Produktionsinfrastruktur abzubilden. Dies ist nicht nur bequem für Entwicklungszwecke, sondern auch für automatisierte Aufgaben in CI/CD-Szenarien.</p>
<p>Und das Beste daran: Diese Cluster werden zum lokalen Rechner getunnelt, so dass es sich so anfühlt, als würden sie auf dem lokalen Host ausgeführt werden. Das ist sehr praktisch.</p>
<p>Getdeck kommt jetzt auch mit einer grafischen Benutzeroberfläche: <a href="https://github.com/Getdeck/Getdeck-Desktop">Getdeck Desktop</a>{target="_blank"}.</p>
<p>Damit können Entwickler Beiboot-Cluster in kürzester Zeit verwalten. Sie können eine Verbindung dazu herstellen und damit arbeiten, als würde es auf ihrem lokalen Rechner laufen, aber ohne dass der Computer abstürzt.</p>
<p>Du kannst dies mit unserem kostenlosen Getdeck-as-a-Service-Angebot einfach testen. Lade einfach die Desktop-App herunter, gib einige Ports ein, erstelle einen Cluster und beginne mit der Entwicklung in einem virtuellen Kubernetes-Cluster, der von uns gehostet und bezahlt wird. Der Cluster unterliegt folgenden Einschränkungen:</p>
<ul>
<li>maximale Lebensdauer des Clusters von 4 Stunden</li>
<li>keine Sitzungszeitüberschreitung</li>
<li>maximal 3 Knoten (maximal 2 Kerne, 6 GB RAM, 50 GB Speicher)</li>
<li>maximal 1 Cluster gleichzeitig</li>
</ul>
<h2>Abschließende Bemerkungen</h2>
<p>Es ist sehr schwierig, in diesem Vergleich einen Gewinner zu ermitteln. Alle drei etablierten Lösungen, minikube, k3d und kind, sind sehr ähnlich zueinander. Es gibt einige Vor- und Nachteile für jede Lösung, aber nichts, was wirklich herausragt. Das ist gut, denn es ist auch nicht wirklich möglich, das falsche Tool auszuwählen. Mir gefällt die allgemeine Benutzerfreundlichkeit aller dieser Tools, da sie eine professionelle Arbeitsumgebung ansprechen. Alle sind schnell, einfach zu installieren und ziemlich einfach zu bedienen.</p>
<p>Ich habe das Gefühl, dass minikube etwas voraus ist und der offiziellen Kubernetes-Entwicklungs-Roadmap am nächsten kommt. Insbesondere für einen einzelnen (potenziell unerfahrenen) Entwickler scheint die Einstiegshürde ziemlich niedrig zu sein. Es ist jedoch die Option mit dem höchsten Ressourcenbedarf. Ich würde Kubernetes-Einsteigern minikube empfehlen.</p>
<p>Bei Blueshoe waren wir in der Vergangenheit sehr zufrieden mit k3d. Besonders wenn du viele verschiedene Kubernetes-Cluster ausführst, wirst du über den geringeren Ressourcenverbrauch im Vergleich zu minikube erfreut sein. Wenn du in einem Team arbeitest, werden die mit k3d oder kind gelieferten Konfigurationsdateien für alle von großem Nutzen sein.</p>
<p>Für einige unserer automatisierten Testfälle sind wir auf minikube umgestiegen, aufgrund des <em>--kubernetes-version</em> Arguments. Es ist sehr einfach, die gewünschte Kubernetes-Version festzulegen und voilà, es läuft. Bei k3d musst du dir das entsprechende k3s Docker-Image ansehen, das verwendet werden soll.</p>
<p>Langfristig betrachten wir die lokale Kubernetes-Entwicklung tatsächlich nicht als nachhaltige Option. Remote-Entwicklungsumgebungen sind die Zukunft! Getdeck Beiboot wird alle auf Kubernetes basierenden Ressourcen ausführen, und mit <a href="/technologien/#tools">Tools</a> wie Gefyra ermöglichen wir Entwicklern, in einer echten Kubernetes-basierten Entwicklungsumgebung zu arbeiten.</p>
<p>Wenn du mehr über die Kubernetes-basierte Entwicklung erfahren möchtest, kannst du mir auf LinkedIn <a href="http://www.linkedin.com/in/michael-schilonka">folgen</a>{target="_blank"}, unserem Discord <a href="https://discord.gg/7A8mnvQjRp">beitreten</a>{target="_blank"} oder uns bei <a href="/kontakt/">Blueshoe kontaktieren</a>.</p>]]></content:encoded>
            <category>Kubernetes</category>
            <category>Gefyra</category>
            <category>Entwicklung</category>
            <enclosure url="https://blueshoe.de/img/blogs/minikube-vs-k3d-vs-kind-vs-getdeck-beiboot.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Model Context Protocol trifft auf OpenAPI]]></title>
            <link>https://blueshoe.de/blog/model-context-protocol-mit-openapi</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/model-context-protocol-mit-openapi</guid>
            <pubDate>Thu, 12 Jun 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Künstliche Intelligenz (KI) - insbesondere Large-Language Models (LLMs) - sind in aller Munde. Nun sollen diese nicht nur generativ
Text, Bilder, Video und Audio erzeugen, sondern auch die Software bedienen, mit der wir als Menschen arbeiten. Um
Effizienzen zu heben und Mensch-Maschine Schnittstellen natürlicher zu gestalten. In diesem Blogpost bieten wir einen
simplen Einstieg in das Model Context Protocol (MCP) und zeigen unsere ersten Erfahrungen, welche wir mit diesem gemacht
haben. Außerdem schauen wir uns die Anbindung von APIs via dem OpenAPI Standard durch das MCP an LLMs an.</p>
<p><img src="/img/blogs/llm-openapi.svg" alt="MCP meets OpenAPI - ein Erfahrungsbericht">{.object-cover .max-w-full .mb-5}</p>
<h2>Model Context Protocol</h2>
<p>Das Model Context Protocol (MCP) ist eine Definition zur Bereitstellung von Kontext aus Anwendungen für LLMs. Vereinfacht  gesagt beinhaltet das Protokoll Informationen, wie ein LLM eine Software (oftmals Schnittstellen) bedienen kann.</p>
<p>Dabei liegt es nahe, dass zur Definition eines MCP einer Anwendung bereits auf bestehende Strukturen, wie der OpenAPI  Standard zurückgegriffen wird. Bevor wir MCP und OpenAPI jedoch zusammenbringen schauen wir als erstes auf ein paar  Details des MCP.</p>
<h3>Kommunikationsmodell des MCP</h3>
<p>Die Kommunikation im Rahmen des MCP läuft als Client-Server Kommunikation ab. Der Client ist in diesem Fall das LLM,  der Server ist eine Abstraktionsschicht der Anwendung, die bedient werden soll. Diese Schicht gibt an, wie der Client die dahinterliegende Anwendung bedienen kann.</p>
<p><img src="/img/blogs/mcp_fastapi/architecture.png" alt="Genereller Aufbau der MCP Architektur" title="Quelle: https://modelcontextprotocol.io/introduction (07.06.2025)"></p>
<p>Dabei kann die dahinterliegende Anwendung alles mögliche sein - eine Datenbank / Datenquelle, eine komplexe Anwendung, oder eine Schnittstelle. Als MCP Host wird hierbei das Programm bezeichnet, welches den MCP Client ausführt (z.B. Claude Desktop, ChatGPT).</p>
<p><img src="/img/blogs/mcp_fastapi/communication.png" alt="Kommunikationsablauf des MCP" title="Quelle: https://modelcontextprotocol.io/docs/concepts/architecture (07.06.2025)"></p>
<p><strong>Wichtig</strong> - die Grafik zeigt eine bidirektionale Kommunikation zwischen Client und Server. Das bedeutet, dass unser MCP Server aus dem Internet erreichbar sein muss. Lokal laufende MCP Server benötigen ein entsprechendes Setup, welche sie für die zumeist im Internet laufenden MCP Clients verfügbar macht (z.B. Claude Desktop oder Ngrok).</p>
<h3>Resourcen und Tools im MCP</h3>
<p>Ich möchte nicht zu tief in die Begrifflichkeiten des MCP einsteigen - alle Details können in der <a href="https://modelcontextprotocol.io/">offiziellen Dokumentation</a>{target="_blank"} gelesen werden. Wichtig für den Kontext des Blogposts sind vor allem
<strong>Resources</strong> und <strong>Tools</strong>.</p>
<p><a href="https://modelcontextprotocol.io/docs/concepts/resources">Resources</a>{target="_blank"} sind vereinfacht gesprochen Datenquellen. Sie beschreiben wie der Client (das LLM) Daten von dem Server abrufen kann. Alle Formen von Daten sind denkbar: Text,
Binärdaten, Bilder, Ton, Videos, strukturiert oder unstrukturiert. Für den Zugriff auf konkrete Resourcen werden i.d.R. <em>Resource Templates</em> verwendet. Diese erlauben dann Zugriff auf bestimmte Resourcen - beispielsweise "Gib mir die Bestellung mit der Nummer <em>X341</em>.".</p>
<p><a href="https://modelcontextprotocol.io/docs/concepts/tools">Tools</a>{target="_blank"} erlauben die Ausführung von Aktionen. Hierdurch wird den LLMs die Möglichkeit gegeben Aktionen mit laufenden Diensten durchzuführen. Das Anlegen oder Manipulieren eines Datensatzes, die Berechnung einer Aufgabe, das Auslösen einer Bestellung oder die Manipulation eines Bildes. Alles ist denkbar.</p>
<h3>Verknüpfung eines LLM und einer Anwendung via MCP</h3>
<p>Mit der Beschreibung von Resourcen und Tools verspricht das MCP nun, dass LLM in sinnvoller Art und Weise Anwendungen bedienen können. Sie heben Effizienzen und schaffen defacto eine neue Schnittstelle sowohl in der Maschine-Maschine als auch Mensch-Maschine Kommunikation. Schaut man in die Details zur Beschreibung Resourcen und Tools fällt auf, dass viele Informationen, welche für das MCP gebraucht werden, bereits in bestehenden Standard wie OpenAPI vorhanden sind. Die Beschreibung der Strukturen ist in beiden Fällen JSON basiert. Natürlich unterscheidet sich der Aufbau - hier kommt fastmcp ins Spiel.</p>
<p>Im Folgenden Teil haben wir das Python Paket <a href="https://gofastmcp.com/getting-started/welcome">fastmcp</a>{target="_blank"} erkundet und Teilen unsere Erkenntnisse.</p>
<h2>fastmcp - Das Pythonic MCP Paket</h2>
<p><code>fastmcp</code> ist eine Framework zur Implementierung des MCP Protokoll, geschrieben in Python. Version 1 des Paketes wurde in das offizielle Python SDK aufgenommen. Die, zum Zeitpunkt des Blogposts, aktuelle Version 2 konzentriert sich nun hauptsächlich auf gute Handhabbarkeit und einem möglichst kompletten Feature-Set.</p>
<p>Beim durchschauen der Dokumentation fällt direkt auf, dass es eine dedizierte Seite für die Integration von <a href="https://gofastmcp.com/servers/openapi">OpenAPI</a>{target="_blank"} gibt. Das initiale Beispiel erscheint denkbar einfach:</p>
<pre><code class="language-python">import httpx
from fastmcp import FastMCP

# Create an HTTP client for your API
client = httpx.AsyncClient(base_url="https://blueshoe.youtrack.cloud/api/", headers={
    "Authorization": "###############",
    "Accept": "application/json",
})

# Load your OpenAPI spec 
openapi_spec = httpx.get("https://blueshoe.youtrack.cloud/api/openapi.json").json()

# Create the MCP server
mcp = FastMCP.from_openapi(
    openapi_spec=openapi_spec,
    client=client,
    name="blueshoe-youtrack",
)

if __name__ == "__main__":
    mcp.run()
</code></pre>
<p>Der Server bekommt eine URL mit dem OpenAPI JSON und eine Client Objekt zur Bedienung der API.</p>
<p><strong>Meine Idee:</strong> ich möchte unser Ticket-System YouTrack, welches wir für das Projektmanagement bei Blueshoe einsetzen via LLM bedienbar machen. Los geht's.</p>
<p>Das funktioniert problemlos - nun wie können LLMs mit meinem Server kommunizieren? Die Dokumentation gibt hier 2 Möglichkeiten - Claude Desktop oder Ngrok. Wo liegen die Unterschiede?</p>
<h3>MCP Lokal oder Remote?</h3>
<p><code>fastmcp</code> erlaubt es den geschriebenen Server einfach in das lokal installierte Programm Claude Desktop zu installieren. Die Kommunikation läuft dann per Stdio. Alternativ kann der Server an einen Port gebunden werden und die Kommunikation läuft dann per SSE (<a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events">Server-Sent Events</a>{target="_blank"}). Bei der Alternative kommt <a href="https://ngrok.com/">Ngrok</a>{target="_blank"} ins Spiel. Ngrok bietet einfache Möglichkeiten lokale Programme ins Internet zu exponieren. Meine ersten Versuche startete ich mit Claude Desktop.</p>
<h2>MCP via Claude Desktop</h2>
<p><code>fastmcp</code> kommt mit einem Kommando zum Installieren des MCP Servers in Claude Desktop. Hierbei befolgenden wie hauptsächlich die <a href="https://gofastmcp.com/integrations/claude-desktop">Dokumentation</a>{target="_blank"} zur Anbindung an Claude Desktop. Also los:</p>
<pre><code class="language-shell">fastmcp install server.py
</code></pre>
<p>Beim Start von Claude Desktop erschien dann folgende Meldung:</p>
<p><img src="/img/blogs/mcp_fastapi/claude_1.png" alt="Claude Desktop Error"></p>
<p>Erm, okay? Was ist das Problem? Ich starte den Server manuell. Funktioniert. Nach kurzer Untersuchung stelle ich fest: Das Installationskommando von fastmcp ist nicht sonderlich smart:</p>
<p><img src="/img/blogs/mcp_fastapi/claude_2.png" alt="Claude Desktop fastmcp Installation"></p>
<p>Leider wird durch das Installationskommando nicht der richtige Python-Interpreter ausgewählt. Dies führt dazu, dass das Skript nicht ausgeführt werden kann.</p>
<p>Hinweis: Ich habe hier ein kleines Stück des Pfades ausgeschnitten.</p>
<p>Okay, dies lässt sich einfach beheben. Im Verzeichnis meines Projektes führe ich folgendes Kommando aus:</p>
<pre><code class="language-shell">which python3
</code></pre>
<p>Diesen Interpreter hinterlege ich dann anstelle von <a href="https://github.com/astral-sh/uv">uv</a>{target="_blank"} in den Konfigurationsdatei von Claude Desktop. Als Parameter wird dann nur der Pfad zur <code>server.py</code> eingetragen.</p>
<p>Problem gelöst! Claude Desktop startet ohne Probleme. Ich stelle eine Frage - welches Tickets wurden heute bearbeitet?</p>
<p><img src="/img/blogs/mcp_fastapi/claude_3.png" alt="Claude Desktop fastmcp Installation"></p>
<p>Ahja. Mist! Es stellt sich heraus, dass so eine, doch recht umfangreiche, OpenAPI Spezifikation direkt mal die "Längenlimits" sprengt. Umfangreich deutet ja erstmal auf eine recht gute Beschreibung hin. Ich halte viel von Jetbrains und ihren Produkten, entsprechend war meine Hoffnung hier, dass der Input des YouTrack OpenAPI sich hervorragend für mein Experiment eignet.</p>
<p>Ich habe noch weitere Modelle getestet, inkl. bezahlter Varianten. Leider laufe ich häufig in Limitierung von Token oder auch Ratelimits. Mein persönlicher Eindruck an dieser Stelle war von Ohnmacht geprägt. Alles easy zusammengesteckt, aber wie kann ich den Problemen auf den Grund gehen? Debugging habe ich dann in meiner Server-Skript via <code>print</code> gemacht, keine besonders nette Erfahrung.</p>
<h2>MCP via Client Skript &#x26; Ngrok</h2>
<p>Okay, Schluss mit den UIs - dann skripten wir unseren Client eben.</p>
<pre><code class="language-python">import anthropic
from rich import print

# Your server URL (replace with your actual URL)
url = 'https:/#####################.ngrok-free.app'

client = anthropic.Anthropic()

response = client.beta.messages.create(
    model="claude-3-5-haiku-20241022",
    max_tokens=1000,
    messages=[{"role": "user", "content": "What issues have been edited on Blueshoe YouTrack today?"}],
    mcp_servers=[
        {
            "type": "url",
            "url": f"{url}/sse",
            "name": "youtrack",
        }
    ],
    extra_headers={
        "anthropic-beta": "mcp-client-2025-04-04"
    }
)

print(response.content)
</code></pre>
<p>Mit ngrok machen wir unseren Server ins Internet verfügbar:</p>
<pre><code class="language-shell">ngrok http 8000  
</code></pre>
<p>Das Client Skript bekommt die entsprechende Adresse und unsere Nachricht. Was wurde denn heute so in unserem YouTrack bearbeitet?</p>
<p>Ein paar interessante Ausgaben finden sich im Log:</p>
<pre><code class="language-python">BetaMCPToolUseBlock(
      id='mcptoolu_01JqQroumE3gHkVnYFZzRc7C',
      input={'query': 'updated: today'},
      name='POST_searchassist',
      server_name='youtrack',
      type='mcp_tool_use'
  )
</code></pre>
<p>Ohlala - eine Query <code>updated: today</code> - das sieht doch gut aus, oder?</p>
<p>Leider nicht - die schlussendliche Antwort sieht ungefähr so aus:</p>
<blockquote>
<p>I apologize for the persistent errors. It seems there might be an issue with the YouTrack API tools at the moment.
Without being able to directly query the system, I can provide some general advice: To find issues edited today in
YouTrack, you would typically use a search query like "updated: today" in the YouTrack interface.</p>
</blockquote>
<p>Benutz einfach das YouTrack UI! Leider kam das LLM auf keinen grünen Zweig bei der Benutzung der YouTrack API. Nachdem ich anfangs in Authentifizierungsprobleme gelaufen bin, welche ich alle beheben konnte, hatte das LLM eigentlich freie Bahn. Leider war es nicht im Stande eine gescheite API Abfrage zu formulieren. Auch vermeintlich simple Abfragen wie - <code>Wieviele Tickets wurden heute bearbeitet?</code> (wir wollen also "nur" eine Zahl) - haben nicht funktioniert.</p>
<h2>Fazit zu MCP mit OpenAPI</h2>
<p>Das Model Context Protocol ist eine gute Systematisierung zur Kommunikation von LLMs mit Bestandssystemen. Es bedeutet allerdings nicht, dass die Anbindung von Datenbanken oder Awendungen mit einem Fingerschnipsen erledigt sind. Selbst Schnittstellen mit hervorragenden OpenAPI Schemas können nicht ohne Weiteres integriert werden - es braucht hier entsprechendes Finetuning, sodass ein LLM gute Ergebnisse liefern kann.</p>
<p><a href="https://modelcontextprotocol.io/docs/concepts/tools#best-practices">Best Practices</a>{target="_blank"} zur Erstellung von MCP Strukturen sind bereits vorhanden. Ein OpenAPI Schema kann die Grundlage für eine Integration bilden - allerdings ersetzt diese nicht die Implementierung.</p>
<p>Das Thema MCP ist <strong>nicht</strong> Plug'n Play. Es braucht auch hier gutes Engineering und klare Ziele, welche mit einer Integration verfolgt werden. Ich werde das Thema jedoch weiter verfolgen, vermutlich auch in einem weiteren Blogpost.</p>
<p>Eine Frage, welche ich mir als Autor von Software-Projekten stelle - wie testet man diese Integrationen eigentlich zuverlässig? Die Dokumentation von fastmcp erscheint mir im Bereich <a href="https://gofastmcp.com/patterns/testing">Testing</a>{target="_blank"} etwas kurz. Wenn ihr da etwas wisst oder kennt, teilt das gerne in den Kommentaren.</p>]]></content:encoded>
            <category>FastAPI</category>
            <category>API</category>
            <category>Entwicklung</category>
            <category>KI</category>
            <enclosure url="https://blueshoe.de/img/blogs/llm-openapi.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Coole neue Features für Django-Hurricane]]></title>
            <link>https://blueshoe.de/blog/neue-features-fuer-django-hurricane</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/neue-features-fuer-django-hurricane</guid>
            <pubDate>Tue, 13 Apr 2021 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Mit dem Open-Source-Projekt Django-Hurricane wollen wir bei Blueshoe die Entwicklung in dem Bereich django und Kubernetes vorantreiben. Heute möchten wir dir einige neue Features vorstellen.</p>
<p><img src="/img/blogs/whats_new2.jpg" alt="whats_new2">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Ende 2020 haben wir dir unser neues Open-Source-Projekt  Django-Hurricane präsentiert. Den Blogpost zur Vorstellung von Django-Hurricane kannst du <a href="/blog/django-fuer-kubernetes/">hier lesen</a>{.bs-link-blue}. Mit diesem Projekt wollen wir bei Blueshoe die Entwicklung in dem Bereich <strong>Django und Kubernetes</strong> vorantreiben und viele Routineaufgaben, die beim Aufsetzen des Projektes entstehen, von den Schultern der Entwickler nehmen und in ein robustes Framework verlagern.
:::
:::globalParagraph
Wir haben außerdem an der To-do-Liste im GitHub Repository gearbeitet und können jetzt hier einige <strong>neue Features von Django-Hurricane</strong> präsentieren.
:::</p>
<p><img src="/img/blogs/hurricane_logo_text.jpg" alt="hurricane">{.object-cover .max-w-[350px] .mb-5}</p>
<p>:::globalTitle{:size="lg" .mb-5}
Dokumentation
:::
:::globalParagraph
Wir haben intensiv an der Dokumentation gearbeitet. Neben dem <strong>User’s Guide</strong>, gibt es auch eine Low-Level API Dokumentation. Der User’s Guide gibt hilfreiche Informationen für die Nutzung von <strong>Django-Hurricane</strong> z. B. die verfügbaren Command-Optionen, nützliche Tipps für die Konfiguration und die allgemeine Aufklärung über die einzelnen Komponenten von Django-Hurricane. In der API-Dokumentation kann man die Funktionalitäten einzelner Komponente besser nachschauen.
:::</p>
<p><img src="/img/blogs/christina-wocintechchat.jpg" alt="christina-wocintechchat">{.object-cover .mb-5}</p>
<p>:::globalTitle{:size="lg" .mb-5}
<strong>Management-Commands-Ausführung</strong>
:::
:::globalParagraph
Außerdem haben wir ein Feature implementiert, welches es ermöglicht, die <strong>Management-Commands</strong> direkt in dem „serve“-Command zu spezifizieren und auszuführen. Die <strong>neuen Command-Optionen</strong> sind:
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>no-metrics: Das Metriksammeln deaktivieren.</li>
<li>command: Der wiederholbare Befehl, der die Management-Commands definiert. Die Commands werden vor dem Starten des HTTP-Servers ausgeführt. Da der Befehl wiederholbar ist, kann es mehrmals mit den unterschiedlichen Management-Commands definiert werden, die auch weitere Optionen haben können. In diesem Fall muss man die Optionen zusammen mit dem Namen des Commands in einem String angeben.
:::
:::globalParagraph
Ein Command mit den Management-Commands könnte wie folgt aussehen:
:::</li>
</ul>
<p>:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">python manage.py serve --command makemigrations --command “compilemessages --locale =de_DE”
</code></pre>
<p>:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
<strong>Probe-Endpoints</strong>
:::
:::globalParagraph
Probe-Endpoints können jetzt separat mit dem „serve“-Command definiert werden. Die Optionen dafür sind:
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>startup-probe: Der Pfad für Startup-Endpoint (default: /startup).</li>
<li>readiness-probe: Der Pfad für Readiness-Endpoint (default: /ready).</li>
<li>liveness-probe: Der Pfad für Liveness-Endpoint (default: /alive).</li>
<li>req-queue-len: Der Schwellenwert für die Request-Warteschlange. Wenn dieser Wert überschritten wird, liefert der Readiness-Probe einen Request mit dem Status-Code 400.
:::</li>
</ul>
<p>:::globalTitle{:size="lg" .mb-5}
<strong>Webhooks für Probe-Events</strong>
:::
:::globalParagraph
Darüber hinaus haben wir noch ein Feature für Django-Hurricane implementiert: Die Möglichkeit die <strong>Webhooks</strong> auf eine bestimmte Adresse zu verschicken. Es gibt mittlerweile drei Webhooks, die den drei Probes entsprechen. Der erste Webhook ist der Startup-Webhook. Er wird nach dem Start des HTTP-Servers ausgeführt und an die angegebene Adresse verschickt. Im Falle des Fehlstarts der Applikation wird ein Startup-Webhook mit dem Status <em>„failed“</em> initiiert. Anschließend wird die Applikation gestoppt.
:::
:::globalParagraph
Die Liveness- und Readiness-Webhooks sind nach den entsprechenden <strong>Probe-Anfragen</strong> initiiert. Die Webhooks werden nur nach einer State-Änderung ausgeführt, d. h. entweder bei der ersten Probe-Anfrage, wobei der State von „None“ auf <em>„Healthy/Unhealthy“</em> geändert wird. Ansonsten wird der Webhook bei der Änderung der States von <em>„Healthy“</em> zu <em>„Unhealthy“</em> oder von <em>„Unhealthy“</em> zu <em>„Healthy“</em> initiiert. Für die Webhooks mit dem Status <em>„failed“</em> wird entsprechend auch der Fehler-Traceback mitgeschickt.
:::
:::globalParagraph
Damit die Webhooks überhaupt geschickt werden können, muss der <strong>Webhook-Command</strong> mit der URL-Adresse mitgegeben werden. Die Command-Option heißt <em>„webhook-url”</em> und wird als URL-Adresse für Webhooks mitgegeben. Der gesamte Befehl für die Ausführung der Applikation mit Webhooks würde wie folgt aussehen.
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">python manage.py serve --webhook-url „http://&#x3C;Adresse>“
</code></pre>
<p>:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
Dabei enthält jedes Webhook einige Daten:
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>Status: <em>„failed“</em> oder <em>„succeeded“</em>, hängt davon ab, ob der Probe erfolgreich ausgeführt wird oder fehlt.</li>
<li>Typ: <em>„startup“</em>, <em>„readiness“</em> oder <em>„liveness“</em>.</li>
<li>Timestamp: Zeitpunkt, wenn der Webhook initiiert wurde.</li>
<li>Hostname: die Rechner- bzw. Serverbezeichnung.</li>
<li>Version: Die Hurricane-Version.</li>
<li>Error trace: Wenn der Webhook den Status <em>„failed“</em> hat, schickt der Server auch die Fehlermeldung mit dem Fehlerpfad.
:::</li>
</ul>
<p><img src="/img/blogs/cyclone.jpg" alt="cyclone">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Trotz unserer fleißigen Arbeit an der <strong>Django-Hurricane-</strong>To-do-Liste in den letzten Monaten, gibt es noch mehrere offene Checkboxen. Wir wären daher sehr dankbar für jegliche Hilfe bei der Weiterentwicklung von Django-Hurricane sowie bei allen unseren anderen <strong>Open-Source-Projekten.</strong> Wir freuen uns schon jetzt auf die neuen Challenges in der Weiterentwicklung und das Abhaken weiterer Checkboxen auf unserer To-do-Liste im GitHub Repository.
:::</p>]]></content:encoded>
            <category>Django</category>
            <category>Kubernetes</category>
            <enclosure url="https://blueshoe.de/img/blogs/whats_new2.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Nuxt 4 ist hier: Ein Deep Dive in die neuen Features und was sie für Entwickler bedeuten]]></title>
            <link>https://blueshoe.de/blog/nuxt-4-neue-features</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/nuxt-4-neue-features</guid>
            <pubDate>Mon, 22 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Nach monatelangen Spekulationen und intensiven Beta-Tests hat das Nuxt-Team Nuxt 4 offiziell veröffentlicht.</p>
<p><img src="/img/blog/nuxt4.svg" alt="Nuxt 4 ist da, was gibt es neues?">{.object-cover .max-w-full .mb-5}
Anders als beim Sprung von Nuxt 2 zu 3 setzt das Team diesmal auf Evolution statt Revolution – ein Ansatz, der Entwicklern deutlich entgegenkommt.<br>
In diesem Artikel werfen wir einen detaillierten Blick auf die wichtigsten Neuerungen und zeigen, wie Nuxt 4 den Entwickleralltag spürbar verbessert. Von der neuen Verzeichnisstruktur bis hin zu intelligenteren Data Fetching-Features – hier erfährst du alles, was du über die Framework-Evolution wissen musst.</p>
<h2>Nuxt 4 – Evolution statt Revolution</h2>
<h3>Der Erfolg von Nuxt 3</h3>
<p>Nuxt 3 hat die Vue-Entwicklung grundlegend verändert. Während frühere Ansätze oft mit komplexen Build-Konfigurationen und mühsamen SSR-Setups zu kämpfen hatten, brachte Nuxt 3 mit dem Nitro-Server, automatischen Imports und nahtloser TypeScript-Integration eine neue Leichtigkeit in die Entwicklung.</p>
<h3>Die neue Strategie: Kontinuierliche Verbesserung</h3>
<p>Das Nuxt-Team hat aus den Erfahrungen der Vergangenheit gelernt. Statt eines weiteren großen Rewrites setzen sie auf kontinuierliche Verbesserung bestehender Konzepte. Dieser Ansatz reduziert den Migrationsaufwand erheblich und ermöglicht es Entwicklern, schrittweise von den neuen Features zu profitieren.</p>
<h3>Praktische Auswirkungen</h3>
<p>Das Ergebnis ist ein Framework-Update, das bestehende Nuxt 3-Projekte ohne größere Umbrüche upgraden lässt. Die Verbesserungen sind sofort spürbar, während der Migrationsaufwand überschaubar bleibt – ein willkommener Kontrast zu früheren Major-Updates.</p>
<h2>Die Top-Neuerungen in Nuxt 4 im Detail</h2>
<h3>Neue Projektstruktur: Bessere Organisation durch das app-Verzeichnis</h3>
<p>Eine der auffälligsten Neuerungen in Nuxt 4 ist die optionale <code>app/</code> Verzeichnisstruktur. Diese löst ein bekanntes Problem: In größeren Projekten sammeln sich schnell zahlreiche Ordner im Root-Verzeichnis an, was die Übersichtlichkeit beeinträchtigt.</p>
<p>Die neue Struktur trennt klar zwischen App-spezifischem Code und Konfigurationsdateien. Alle Anwendungsdateien werden unter <code>app/</code> organisiert, während Konfigurationsdateien im Root-Verzeichnis verbleiben.</p>
<p><strong>Vorteile der neuen Struktur:</strong></p>
<ul>
<li><strong>Klarere Trennung</strong> zwischen App-Code und Konfiguration</li>
<li><strong>Bessere Übersichtlichkeit</strong> in größeren Projekten</li>
<li><strong>Konsistenz</strong> mit anderen modernen Frameworks wie Next.js</li>
<li><strong>Verbesserte IDE-Unterstützung</strong> durch klarere Verzeichnisstruktur</li>
</ul>
<p><strong>Vergleich der Strukturen:</strong></p>
<pre><code class="language-bash"># Alte Struktur (Nuxt 3)
/
├── components/
├── pages/
├── composables/
├── layouts/
├── middleware/
├── plugins/
├── assets/
├── public/
├── server/
├── nuxt.config.ts
└── package.json

# Neue Struktur (Nuxt 4)
/
├── app/
│   ├── components/
│   ├── pages/
│   ├── composables/
│   ├── layouts/
│   ├── middleware/
│   ├── plugins/
│   ├── assets/
│   ├── utils/
│   ├── app.vue
│   └── app.config.ts
├── content/
├── layers/
├── modules/
├── public/
├── server/
├── shared/
├── nuxt.config.ts
└── package.json
</code></pre>
<p>Die neue Struktur macht es deutlich einfacher, zwischen App-spezifischem Code und Konfigurationsdateien zu unterscheiden. Besonders in größeren Teams und Enterprise-Projekten führt dies zu einer erheblich besseren Code-Organisation und reduziert die Zeit, die für die Suche nach spezifischen Dateien benötigt wird.</p>
<h3>Singleton Data Fetching Layer: Revolutionäres Caching-System</h3>
<p>Das Data Fetching in Nuxt 4 wurde grundlegend überarbeitet und führt das "Singleton Data Fetching Layer" ein. Diese Neuerung bringt erhebliche Verbesserungen in Performance und Konsistenz mit sich.</p>
<p><strong>Was sich geändert hat:</strong></p>
<p><strong>1. Geteilte Refs für denselben Key:</strong>
Alle Aufrufe von <code>useAsyncData</code> oder <code>useFetch</code> mit demselben Key teilen sich jetzt dieselben <code>data</code>, <code>error</code> und <code>status</code> Refs. Das bedeutet, dass alle Aufrufe mit einem expliziten Key keine widersprüchlichen <code>deep</code>, <code>transform</code>, <code>pick</code>, <code>getCachedData</code> oder <code>default</code> Optionen haben dürfen.</p>
<p><strong>2. Erweiterte <code>getCachedData</code> Kontrolle:</strong>
Die <code>getCachedData</code> Funktion wird jetzt bei jedem Datenabruf aufgerufen, auch wenn dies durch einen Watcher oder <code>refreshNuxtData</code> verursacht wird. Die Funktion erhält ein Kontext-Objekt mit der Ursache der Anfrage, was mehr Kontrolle über die Verwendung von gecachten Daten ermöglicht.</p>
<p><strong>3. Reaktive Key-Unterstützung:</strong>
Du kannst jetzt computed refs, plain refs oder Getter-Funktionen als Keys verwenden. Dies ermöglicht automatisches Data Refetching und speichert Daten separat.</p>
<p><strong>4. Automatische Datenbereinigung:</strong>
Wenn die letzte Komponente, die Daten mit <code>useAsyncData</code> abruft, unmounted wird, bereinigt Nuxt automatisch die entsprechenden Daten aus dem Cache.</p>
<p><strong>Praktisches Beispiel: E-Commerce Produktseite</strong></p>
<p>Stell dir vor, du baust eine E-Commerce-Seite mit Produktdetails und verwandten Produkten:</p>
<pre><code class="language-javascript">// Produktseite - lädt Produktdaten
const { data: product } = await useFetch(`/api/products/${productId}`, {
  key: `product-${productId}`
})

// Verwandte Produkte Komponente - teilt dieselben Daten
const { data: relatedProducts } = await useFetch(`/api/products/${productId}/related`, {
  key: `related-${productId}`
})

// Warenkorb Komponente - lädt Produktdaten erneut
const { data: cartProduct } = await useFetch(`/api/products/${productId}`, {
  key: `product-${productId}` // Gleicher Key = gleiche Daten!
})
// → Kein zusätzlicher API-Call, nutzt gecachte Daten
</code></pre>
<p><strong>Was passiert hier:</strong></p>
<ul>
<li><strong>Ein API-Call</strong> für Produktdaten, aber <strong>drei Komponenten</strong> nutzen die Daten</li>
<li><strong>Automatisches Caching</strong> verhindert doppelte Anfragen</li>
<li><strong>Geteilte Refs</strong> sorgen dafür, dass alle Komponenten synchron bleiben</li>
</ul>
<p><strong>Reales Szenario: Blog mit Kommentaren</strong></p>
<pre><code class="language-javascript">// Blog-Post Seite
const { data: post } = await useFetch(`/api/posts/${postId}`, {
  key: `post-${postId}`
})

// Kommentare Komponente (gleiche Seite)
const { data: comments } = await useFetch(`/api/posts/${postId}/comments`, {
  key: `comments-${postId}`
})

// User wechselt zu anderer Seite → Komponenten werden unmounted
// → Cache wird automatisch bereinigt (keine Memory Leaks!)

// User kehrt zurück → Daten werden neu geladen
// → Frische Daten, optimaler Speicherverbrauch
</code></pre>
<p><strong>Vorteile in der Praxis:</strong></p>
<ul>
<li><strong>Weniger API-Calls</strong> = schnellere Ladezeiten</li>
<li><strong>Synchronisierte Daten</strong> = keine Inkonsistenzen zwischen Komponenten</li>
<li><strong>Automatisches Cleanup</strong> = keine Memory Leaks bei Navigation</li>
<li><strong>Intelligentes Caching</strong> = bessere User Experience</li>
</ul>
<p><strong>Pro-Tipp: Composable für wiederverwendbare Data Fetching Logic</strong></p>
<p>Da alle Komponenten mit demselben Key automatisch die gleichen Daten teilen, macht es Sinn, die <code>useAsyncData</code>/<code>useFetch</code> Aufrufe in eigene Composables auszulagern:</p>
<pre><code class="language-javascript">// composables/useProduct.js
export function useProduct(productId: string) {
  return useAsyncData(
    `product-${productId}`,
    () => $fetch(`/api/products/${productId}`),
    { 
      deep: true,
      transform: (product) => ({ 
        ...product, 
        formattedPrice: `€${product.price.toFixed(2)}`,
        lastViewed: new Date()
      })
    }
  )
}

// In verschiedenen Komponenten
const { data: product } = await useProduct(productId)
// → Alle nutzen dieselben Daten und Transformations-Logic
</code></pre>
<p><strong>Vorteile:</strong></p>
<ul>
<li><strong>Konsistente Keys</strong> über alle Komponenten hinweg</li>
<li><strong>Zentrale Transformations-Logic</strong> (Preis-Formatierung, etc.)</li>
<li><strong>Einfache Wartung</strong> und Updates</li>
<li><strong>TypeScript-Support</strong> durch zentrale Typen</li>
</ul>
<p><strong>Wichtige Hinweise:</strong></p>
<ul>
<li><strong>Konsistente Optionen:</strong> Alle Aufrufe mit demselben Key müssen identische <code>deep</code>, <code>transform</code>, <code>pick</code>, <code>getCachedData</code> oder <code>default</code> Optionen haben</li>
<li><strong>Reaktive Keys:</strong> Computed refs, plain refs oder Getter-Funktionen als Keys ermöglichen automatisches Refetching</li>
<li><strong>Memory Management:</strong> Automatische Bereinigung verhindert Memory Leaks bei großen Anwendungen</li>
</ul>
<p><strong>Konfiguration: Cache-Verhalten anpassen</strong></p>
<p>Falls du das neue Cache-Verhalten deaktivieren möchtest, kannst du das in der <code>nuxt.config.ts</code> konfigurieren:</p>
<pre><code class="language-javascript">export default defineNuxtConfig({
  experimental: {
    granularCachedData: false,  // Deaktiviert das granulare Caching
    purgeCachedData: false      // Deaktiviert die automatische Bereinigung
  }
})
</code></pre>
<p><strong>Wann das sinnvoll ist:</strong></p>
<ul>
<li><strong>Legacy-Projekte</strong> mit komplexen Caching-Anforderungen</li>
<li><strong>Schrittweise Migration</strong> von bestehenden Caching-Strategien</li>
<li><strong>Debugging</strong> von Cache-Problemen während der Entwicklung</li>
</ul>
<p>Diese Verbesserungen sorgen dafür, dass unnötige API-Calls vermieden werden und die Anwendung deutlich performanter wird. Besonders bei komplexen Anwendungen mit vielen Datenquellen macht sich der Unterschied in der Praxis erheblich bemerkbar.</p>
<h3>Developer Experience: Verbesserte Produktivität und Effizienz</h3>
<p>Die Developer Experience wurde in Nuxt 4 erheblich verbessert, was sich direkt auf die Produktivität von Entwicklern auswirkt. Die Optimierungen konzentrieren sich auf schnellere Workflows und bessere Entwicklungstools.</p>
<p><strong>Performance-Verbesserungen:</strong></p>
<ul>
<li><strong>Deutlich schnellere Startzeiten</strong> des Entwicklungsservers</li>
<li><strong>Verbesserte HMR-Zuverlässigkeit</strong> (Hot Module Replacement)</li>
<li><strong>Optimierte TypeScript-Integration</strong> für bessere Typsicherheit und Autovervollständigung</li>
<li><strong>Intelligente Build-Optimierungen</strong> für schnellere Compile-Zeiten</li>
</ul>
<p><strong>Praktische Verbesserungen:</strong></p>
<ul>
<li>Nahtlose Integration mit modernen IDEs</li>
<li>Bessere Fehlermeldungen und Debugging-Informationen</li>
<li>Optimierte Bundle-Größe durch intelligente Tree-Shaking</li>
<li>Verbesserte Source Maps für effizienteres Debugging</li>
</ul>
<p>Diese Verbesserungen sorgen dafür, dass Entwickler weniger Zeit mit Warten und Debugging verbringen und sich mehr auf die eigentliche Entwicklung konzentrieren können.</p>
<h2>Der Upgrade-Prozess: So gelingt der Umstieg von Nuxt 3 auf 4</h2>
<p>Der Umstieg von Nuxt 3 auf Nuxt 4 wurde bewusst unkompliziert gestaltet. Das Nuxt-Team hat großen Wert darauf gelegt, dass bestehende Projekte ohne größere Änderungen migriert werden können. Nach dem Upgrade sind die meisten Nuxt 4 Verhaltensweisen bereits Standard, aber einige Features können noch konfiguriert werden, um Rückwärtskompatibilität während der Migration zu gewährleisten.</p>
<h3>Schritt 1: Nuxt auf Version 4 aktualisieren</h3>
<p>Der erste Schritt ist die Aktualisierung des Nuxt-Pakets auf Version 4:</p>
<pre><code class="language-bash"># Mit npm
npm install nuxt@^4.0.0

# Mit yarn
yarn add nuxt@^4.0.0

# Mit pnpm
pnpm add nuxt@^4.0.0

# Mit bun
bun add nuxt@^4.0.0
</code></pre>
<p><strong>Was passiert hier?</strong></p>
<ul>
<li>Das <code>nuxt</code> Paket wird auf Version 4 aktualisiert</li>
<li>Alle Nuxt 4 Verhaltensweisen werden aktiviert</li>
<li>Die meisten bestehenden Konfigurationen bleiben funktional</li>
</ul>
<h3>Schritt 2: Migration durchführen</h3>
<p>Du hast zwei Optionen für die Migration:</p>
<h4>Option A: Automatische Migration mit Codemods (empfohlen)</h4>
<p>Das Nuxt-Team hat mit dem Codemod-Team zusammengearbeitet, um viele Migrationsschritte zu automatisieren:</p>
<pre><code class="language-bash"># Alle Migration-Codemods ausführen

# Mit npm
npx codemod@latest nuxt/4/migration-recipe

# Mit yarn
yarn dlx codemod@latest nuxt/4/migration-recipe

# Mit pnpm
pnpm dlx codemod@latest nuxt/4/migration-recipe

# Mit bun
bun x codemod@latest nuxt/4/migration-recipe
</code></pre>
<p><strong>Was machen die Codemods?</strong></p>
<ul>
<li>Automatische Anpassung der Verzeichnisstruktur</li>
<li>Migration veralteter Konfigurationsoptionen</li>
<li>Anpassung von TypeScript-Konfigurationen</li>
<li>Update von Import-Pfaden und Aliases</li>
</ul>
<h4>Option B: Manuelle Migration</h4>
<p>Falls du die Migration manuell durchführen möchtest oder die Codemods nicht alle Aspekte abdecken:</p>
<p><strong>2.1 Neue Verzeichnisstruktur erstellen</strong></p>
<p>Die neue Verzeichnisstruktur bietet bessere Performance und IDE-Unterstützung:</p>
<pre><code class="language-bash"># Neues app/ Verzeichnis erstellen
mkdir app

# Bestehende Verzeichnisse in app/ verschieben
mv assets app/
mv components app/
mv composables app/
mv layouts app/
mv middleware app/
mv pages app/
mv plugins app/
mv utils app/

# App-spezifische Dateien verschieben
mv app.vue app/
mv error.vue app/
mv app.config.ts app/
</code></pre>
<p><strong>2.2 Root-Verzeichnis bereinigen</strong></p>
<p>Stelle sicher, dass diese Verzeichnisse im Root bleiben:</p>
<ul>
<li><code>nuxt.config.ts</code></li>
<li><code>content/</code></li>
<li><code>layers/</code></li>
<li><code>modules/</code></li>
<li><code>public/</code></li>
<li><code>server/</code></li>
</ul>
<p><strong>2.3 TypeScript-Konfiguration anpassen</strong></p>
<p>Nuxt 4 verwendet neue TypeScript-Projekt-Referenzen für bessere Typsicherheit:</p>
<pre><code class="language-json">// tsconfig.json
{
  "files": [],
  "references": [
    { "path": "./.nuxt/tsconfig.app.json" },
    { "path": "./.nuxt/tsconfig.server.json" },
    { "path": "./.nuxt/tsconfig.shared.json" },
    { "path": "./.nuxt/tsconfig.node.json" }
  ]
}
</code></pre>
<pre><code class="language-json">// package.json - Type-Checking Scripts aktualisieren
{
  "scripts": {
    "typecheck": "nuxt prepare &#x26;&#x26; vue-tsc -b --noEmit"
  }
}
</code></pre>
<p><strong>Type-Augmentierungen verschieben:</strong></p>
<ul>
<li><strong>App-Kontext:</strong> Verschiebe Dateien nach <code>app/</code></li>
<li><strong>Server-Kontext:</strong> Verschiebe Dateien nach <code>server/</code></li>
<li><strong>Geteilt:</strong> Verschiebe Dateien nach <code>shared/</code></li>
</ul>
<p><strong>2.4 Konfiguration anpassen</strong></p>
<p><strong>Veraltete generate-Konfiguration migrieren:</strong></p>
<pre><code class="language-typescript">// Alte Konfiguration (Nuxt 3)
export default defineNuxtConfig({
  generate: {
    exclude: ['/admin', '/private'],
    routes: ['/sitemap.xml', '/robots.txt']
  }
})

// Neue Konfiguration (Nuxt 4)
export default defineNuxtConfig({
  nitro: {
    prerender: {
      ignore: ['/admin', '/private'],
      routes: ['/sitemap.xml', '/robots.txt']
    }
  }
})
</code></pre>
<p><strong>Experimentelle Features entfernen:</strong></p>
<p>Diese Features sind in Nuxt 4 nicht mehr konfigurierbar:</p>
<ul>
<li><code>experimental.treeshakeClientOnly</code> (immer <code>true</code>)</li>
<li><code>experimental.configSchema</code> (immer <code>true</code>)</li>
<li><code>experimental.polyfillVueUseHead</code> (immer <code>false</code>)</li>
<li><code>experimental.respectNoSSRHeader</code> (immer <code>false</code>)</li>
</ul>
<h3>Schritt 3: Testing und Validierung</h3>
<pre><code class="language-bash"># Entwicklungsserver starten
npm run dev

# Type-Checking ausführen
npm run typecheck

# Build testen
npm run build

# Alle Tests ausführen
npm run test
</code></pre>
<p><strong>Was zu testen ist:</strong></p>
<ul>
<li>Alle Seiten laden korrekt</li>
<li>Data Fetching funktioniert</li>
<li>TypeScript-Fehler sind behoben</li>
<li>Performance-Verbesserungen sind sichtbar</li>
<li>Alle Module und Plugins funktionieren</li>
</ul>
<h3>Schritt 4: Rückwärtskompatibilität (falls nötig)</h3>
<p>Falls du die alte Verzeichnisstruktur beibehalten möchtest:</p>
<pre><code class="language-typescript">// nuxt.config.ts
export default defineNuxtConfig({
  // V3-Struktur beibehalten
  srcDir: '.',
  dir: {
    app: 'app'
  }
})
</code></pre>
<h3>Wichtige Breaking Changes</h3>
<p>Nuxt 4 bringt einige signifikante Änderungen mit sich, die bei der Migration beachtet werden müssen:</p>
<p><strong>1. Neue Verzeichnisstruktur:</strong></p>
<ul>
<li>Standard <code>srcDir</code> ist jetzt <code>app/</code> statt Root-Verzeichnis</li>
<li><code>serverDir</code> ist jetzt <code>&#x3C;rootDir>/server</code> statt <code>&#x3C;srcDir>/server</code></li>
<li><code>layers/</code>, <code>modules/</code> und <code>public/</code> werden relativ zu <code>&#x3C;rootDir></code> aufgelöst</li>
</ul>
<p><strong>2. Singleton Data Fetching Layer:</strong></p>
<ul>
<li>Geteilte Refs für denselben Key in <code>useAsyncData</code> und <code>useFetch</code></li>
<li>Erweiterte <code>getCachedData</code> Kontrolle mit Kontext-Objekt</li>
<li>Reaktive Key-Unterstützung für automatisches Refetching</li>
<li>Automatische Datenbereinigung beim Unmounten</li>
</ul>
<p><strong>3. TypeScript-Konfiguration:</strong></p>
<ul>
<li>Neue Projekt-Referenzen für bessere Typsicherheit</li>
<li>Getrennte Konfigurationen für App, Server und Build-Zeit</li>
<li>Type-Augmentierungen müssen in entsprechende Verzeichnisse (<code>app/</code>, <code>server/</code>, <code>shared/</code>)</li>
</ul>
<p><strong>Migration Guide:</strong> Für detaillierte Informationen empfehlen wir den <a href="https://nuxt.com/docs/4.x/getting-started/upgrade">offiziellen Nuxt 4 Upgrade Guide</a> zu konsultieren.</p>
<h2>Was bedeutet Nuxt 4 für die Zukunft? Ein Blick nach vorn</h2>
<h3>Die Roadmap: Was kommt nach Nuxt 4?</h3>
<p>Das Nuxt-Team arbeitet bereits an <strong>Nuxt 5</strong> und <strong>Nitro v3</strong>, die weitere revolutionäre Features bringen werden. Nuxt 4 bildet dabei das solide Fundament für diese zukünftigen Entwicklungen.</p>
<p><strong>Geplante Features für die Zukunft:</strong></p>
<ul>
<li>Erweiterte Server-Side Rendering Optimierungen</li>
<li>Verbesserte Edge-Computing Unterstützung</li>
<li>Erweiterte TypeScript-Integration</li>
<li>Neue Performance-Metriken und Monitoring-Tools</li>
</ul>
<h3>Die Bedeutung für das Ökosystem</h3>
<p><strong>Module-Entwickler:</strong> Können ihre Module schrittweise an die neuen Features anpassen und von der verbesserten API profitieren.</p>
<p><strong>Community:</strong> Profitieren von der verbesserten Developer Experience und den neuen Möglichkeiten für Performance-Optimierungen.</p>
<p><strong>Enterprise:</strong> Haben eine stabile Basis für langfristige Projekte mit vorhersagbaren Upgrade-Pfaden.</p>
<h3>Einordnung: Wie Nuxt seine Position als führendes Vue-Framework weiter ausbaut</h3>
<p>Nuxt 4 festigt die Position als eines der modernsten und entwicklerfreundlichsten Vue-Frameworks und setzt neue Standards in der Web-Entwicklung. Die Fokussierung auf Stabilität und kontinuierliche Verbesserung macht es zur idealen Wahl für professionelle Projekte.</p>
<p><strong>Wettbewerbsvorteile:</strong></p>
<ul>
<li>Überlegene Developer Experience im Vergleich zu anderen Vue-Frameworks</li>
<li>Bessere Performance durch intelligente Optimierungen</li>
<li>Starke Community und umfangreiches Ökosystem</li>
<li>Enterprise-ready mit langfristiger Unterstützung</li>
</ul>
<h2>Fazit: Warum sich das Upgrade auf Nuxt 4 jetzt lohnt</h2>
<h3>Zusammenfassung der wichtigsten Vorteile:</h3>
<p><strong>Performance:</strong> Deutlich schnellere Startzeiten und bessere HMR-Zuverlässigkeit sorgen für eine flüssigere Entwicklung.</p>
<p><strong>Code-Qualität:</strong> Bessere Projektstruktur durch die neue <code>src</code>-Verzeichnisorganisation und optimierte TypeScript-Integration.</p>
<p><strong>Produktivität:</strong> Intelligenteres Data Fetching und verbesserte Developer Experience führen zu weniger Wartezeiten und effizienterer Entwicklung.</p>
<p><strong>Zukunftssicherheit:</strong> Stabile Basis für langfristige Projekte mit vorhersagbaren Upgrade-Pfaden.</p>
<h3>Klare Handlungsempfehlung</h3>
<p>Das Upgrade auf Nuxt 4 ist für alle Entwickler und Teams empfehlenswert, die bereits mit Nuxt 3 arbeiten. Die Verbesserungen sind sofort spürbar, während der Migrationsaufwand minimal bleibt.</p>
<p><strong>Für Teams:</strong> Die verbesserte Developer Experience und Performance-Optimierungen rechtfertigen das Upgrade bereits nach kurzer Zeit.</p>
<p><strong>Für Enterprise:</strong> Die Fokussierung auf Stabilität und langfristige Unterstützung macht Nuxt 4 zur idealen Wahl für professionelle Projekte.</p>
<p>Jetzt die <a href="https://nuxt.com">offizielle Nuxt 4 Dokumentation</a> ansehen und dein erstes Projekt mit Nuxt 4 starten!</p>
<hr>
<p><em>Hast du bereits Erfahrungen mit Nuxt 4 gemacht? Teile deine Erkenntnisse in den Kommentaren und lass uns wissen, welche Features dich am meisten begeistern!</em></p>
<hr>
<h2>FAQ – Häufige Fragen zu Nuxt 4</h2>
<h3>1. Was sind die wichtigsten Neuerungen in Nuxt 4?</h3>
<p>Die wichtigsten Neuerungen in Nuxt 4 sind die optionale <code>src</code>-Verzeichnisstruktur für bessere Projektorganisation, intelligenteres Data Fetching mit automatischem Caching und De-Duplizierung, sowie verbesserte Developer Experience mit schnellerem CLI und optimierter TypeScript-Integration. Der Fokus liegt auf Stabilität und einem reibungslosen Upgrade-Pfad von Nuxt 3.</p>
<h3>2. Wie unterscheidet sich Nuxt 4 von Nuxt 3?</h3>
<p>Nuxt 4 baut auf Nuxt 3 auf und setzt auf Evolution statt Revolution. Während Nuxt 3 einen kompletten Rewrite war, konzentriert sich Nuxt 4 auf die Verfeinerung bestehender Konzepte. Die neue <code>src</code>-Verzeichnisstruktur bietet bessere Organisation, das Data Fetching wurde intelligenter und die Developer Experience wurde durch schnellere Startzeiten und bessere HMR-Zuverlässigkeit verbessert.</p>
<h3>3. Ist das Upgrade von Nuxt 3 auf Nuxt 4 kompliziert?</h3>
<p>Nein, das Upgrade von Nuxt 3 auf Nuxt 4 wurde bewusst unkompliziert gestaltet. Das Nuxt-Team hat großen Wert darauf gelegt, dass bestehende Projekte ohne größere Änderungen migriert werden können. Die Breaking Changes sind minimal und die meisten bestehenden Projekte sollten ohne größere Anpassungen funktionieren.</p>
<h3>4. Welche Vorteile bietet die neue src-Verzeichnisstruktur in Nuxt 4?</h3>
<p>Die neue optionale <code>src</code>-Verzeichnisstruktur bietet eine klarere Trennung zwischen Konfigurationsdateien und dem eigentlichen App-Code. Dies führt zu besserer Übersicht in großen Projekten, konsistenter Struktur mit anderen modernen Frameworks und einer saubereren Projektorganisation. Alle App-spezifischen Dateien werden unter <code>src/</code> organisiert, während Konfigurationsdateien im Root-Verzeichnis bleiben.</p>
<h3>5. Wie verbessert Nuxt 4 das Data Fetching?</h3>
<p>Nuxt 4 bringt intelligenteres Data Fetching mit automatischem Caching und De-Duplizierung von Anfragen mit demselben Key. Zusätzlich erfolgt eine automatische Bereinigung beim Unmounten von Komponenten, was Memory Leaks verhindert. Die verbesserte Fehlerbehandlung und Retry-Mechanismen sorgen für robustere Anwendungen.</p>
<h3>6. Welche Performance-Verbesserungen bietet Nuxt 4?</h3>
<p>Nuxt 4 bietet deutlich schnellere Startzeiten des Entwicklungsservers, verbesserte HMR-Zuverlässigkeit und optimierte TypeScript-Integration. Zusätzlich sorgen intelligente Build-Optimierungen für schnellere Compile-Zeiten und bessere Bundle-Größen durch verbessertes Tree-Shaking.</p>
<h3>7. Kann ich Nuxt 4 Features bereits in Nuxt 3 testen?</h3>
<p>Ja, du kannst Features aus Nuxt 4 bereits in Nuxt 3 testen, indem du die neuesten Beta-Versionen verwendest. Dies ermöglicht eine schrittweise Migration und frühzeitige Erkennung von Kompatibilitätsproblemen. Der offizielle Migration Guide bietet detaillierte Anweisungen für den Übergang.</p>
<h3>8. Welche Breaking Changes gibt es in Nuxt 4?</h3>
<p>Da Nuxt 4 auf Stabilität setzt, sind die Breaking Changes minimal. Die meisten bestehenden Projekte sollten ohne größere Anpassungen funktionieren. Wichtige Änderungen betreffen hauptsächlich die optionale <code>src</code>-Struktur und einige veraltete API-Funktionen, die durch modernere Alternativen ersetzt wurden.</p>]]></content:encoded>
            <category>Nuxt</category>
            <category>Vue.js</category>
            <category>Entwicklung</category>
            <category>Performance</category>
            <enclosure url="https://blueshoe.de/img/blog/nuxt4.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Nuxt in Produktion: Statisch vs. Dynamisch]]></title>
            <link>https://blueshoe.de/blog/nuxt-generate-vs-server</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/nuxt-generate-vs-server</guid>
            <pubDate>Sun, 12 Jan 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Nuxt ist ein modernes Framework, das speziell für die Entwicklung von SEO-optimierten und hochperformanten Webanwendungen entwickelt wurde. In diesem Artikel erfährst du, wie du Nuxt optimal in Produktionsumgebungen einsetzen kannst. Wir beleuchten die verschiedenen Rendering-Methoden – statische Generierung und Server-Rendering – und geben Tipps, wie du das Beste aus beiden Welten herausholen kannst.</p>
<p><img src="/img/blogs/vuejsnuxt.svg" alt="Blueshoe und FastAPI in Produktion">{.object-cover .max-w-full .mb-5}</p>
<p>::GlobalTitle{:size="lg" .mb-4}
Einleitung
::</p>
<p>::GlobalParagraph
Nuxt ist ein vielseitiges Framework, das Entwicklern hilft, moderne Webanwendungen effizient zu erstellen. Ein entscheidender Aspekt bei der Entwicklung mit Nuxt ist die Wahl der richtigen Rendering-Methode. Die zwei Hauptoptionen sind:
::</p>
<p>::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li><strong>Statische Generierung (Nuxt Generate)</strong></li>
<li><strong>Server-Rendering (Nuxt Server)</strong>
::</li>
</ul>
<p>::GlobalParagraph
Doch wann solltest du Nuxt Generate oder Nuxt Server verwenden? Dieser Artikel gibt dir eine klare Übersicht über die Nuxt Render-Strategien im Vergleich, zeigt ihre Vor- und Nachteile auf und stellt hybride Ansätze vor.
::</p>
<p>:::GlobalButton{:url="/technologien/vuejs-nuxt/" :label="Erfahre mehr über unsere Nuxt-Entwicklungsdienste" :color="blue" .mb-6}
:::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Statische Generierung (Nuxt Generate)
::</p>
<p>::GlobalParagraph
Die statische Generierung ist ein Ansatz, bei dem während des Build-Prozesses HTML-Dateien erstellt werden, die dann direkt von einem Webserver oder CDN ausgeliefert werden.
Das führt zu:
::</p>
<p>::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li><strong>Extrem schnellen Ladezeiten</strong></li>
<li><strong>Weniger Abhängigkeit von Backend-Systemen</strong>
::</li>
</ul>
<p>::GlobalParagraph
Alle Seiten der Website werden während des Build-Prozesses generiert und als statische HTML-Dateien bereitgestellt.
::</p>
<p>::GlobalParagraph
Ein häufiges Beispiel ist eine Blog-Website. Hier können alle Artikel während des Builds generiert werden. Zusätzlich kann ein Crawler genutzt werden, um alle internen Links automatisch zu erfassen und sicherzustellen, dass alle Inhalte basierend auf dem Index generiert werden:
::</p>
<p>::BlogCode</p>
<pre><code class="language-typescript">// nuxt.config.ts

export default defineNuxtConfig({
  nitro: {
    prerender: {
      crawlLinks: true,
      routes: ['/'],
    },
  }
})
</code></pre>
<p>::</p>
<p>::GlobalParagraph
Mit dieser Konfiguration werden alle angegebenen Routen während des Build-Prozesses generiert und als statische HTML-Dateien bereitgestellt. Ein integrierter Crawler sorgt dafür, dass auch alle internen Links automatisch erfasst und generiert werden. Das entlastet den Webserver erheblich und ermöglicht blitzschnelle Ladezeiten. Die <code>routes</code>-Option in der Datei <code>nuxt.config.ts</code> gibt dabei genau an, welche Pfade während des Builds erstellt werden sollen.
::</p>
<p>::GlobalParagraph
Um den statischen Generierungsmodus zu nutzen, führe folgendes npm-Kommando aus:
::</p>
<p>::BlogCode</p>
<pre><code class="language-bash">npm run generate
</code></pre>
<p>::</p>
<p>::GlobalParagraph
Nach dem Ausführen des Befehls <code>npm run generate</code> werden die statischen HTML-Dateien im Verzeichnis <code>dist</code> gespeichert. Um die Seite online zugänglich zu machen, musst du diese Dateien auf einen Webserver oder ein CDN hochladen.
::</p>
<p>::GlobalParagraph
Zusammenfassung:</p>
<p>:::GlobalBlock{.ul-disk .my-4}</p>
<ul>
<li><strong>Performance:</strong> Extrem schnelle Ladezeiten durch vorab generierte HTML-Dateien.</li>
<li><strong>Kosten:</strong> Reduziert die Abhängigkeit von serverseitigen Prozessen und verringert Hosting-Kosten.</li>
<li><strong>SEO-Optimierung:</strong> Bessere Indexierung durch vorgerenderte Inhalte.</li>
<li><strong>Einschränkungen:</strong> Keine Echtzeit-Inhalte, lange Build-Zeiten bei umfangreichen Websites.
:::
::</li>
</ul>
<p>::GlobalParagraph
Wie du Nuxt effizient nutzt, erfährst du in unserem <a href="/blog/webentwicklung-mit-vuejs-und-nuxt/">Blog zur Webentwicklung mit Vue.js &#x26; Nuxt</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}
.
::</p>
<p>::GlobalParagraph
Weitere Informationen zur Konfiguration findest du in der <a href="https://nuxt.com/docs/getting-started/configuration">offiziellen Nuxt-Dokumentation</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}.
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Server-Rendering (Nuxt Server)
::</p>
<p>::GlobalParagraph
Beim Server-Rendering wird die HTML-Ausgabe dynamisch auf einem Server generiert, sobald eine Anfrage gestellt wird. Dieser Ansatz eignet sich besonders für Anwendungen wie Dashboards, die personalisierte Inhalte oder häufig aktualisierte Daten bereitstellen:
::</p>
<p>::BlogCode</p>
<pre><code class="language-vue">&#x3C;!-- pages/dashboard.vue -->

&#x3C;script setup lang="ts">
interface User {
  name: string
  lastLogin: string
}

const user = ref&#x3C;User | null>(null)

const { data } = await useFetch&#x3C;User>('/api/user')
user.value = data.value
&#x3C;/script>

&#x3C;template>
  &#x3C;div v-if="user">
    &#x3C;h1>Willkommen, {{ user.name }}&#x3C;/h1>
    &#x3C;p>Letzte Anmeldung: {{ user.lastLogin }}&#x3C;/p>
  &#x3C;/div>
&#x3C;/template>
</code></pre>
<p>::</p>
<p>::GlobalParagraph
In diesem Beispiel wird die <code>useFetch</code>-Methode genutzt, um während des Server-Renderings stets aktuelle Nutzerdaten zu laden und an die Komponente zu übergeben. Dies stellt sicher, dass die Inhalte immer aktuell sind. Dank der Auto-Import-Funktionalität von Nuxt müssen Systemfunktionen wie <code>ref</code> und <code>useFetch</code> nicht separat importiert werden.
::</p>
<p>::GlobalParagraph
Um den Server-Rendering-Modus zu nutzen, kannst du das folgende npm-Kommando ausführen:
::</p>
<p>::BlogCode</p>
<pre><code class="language-bash">npm run build
npm run start
</code></pre>
<p>::</p>
<p>::GlobalParagraph
Zusammenfassung:</p>
<p>:::GlobalBlock{.ul-disk .my-4}</p>
<ul>
<li><strong>Flexibilität:</strong> Inhalte können in Echtzeit angepasst werden</li>
<li><strong>SEO-Vorteile:</strong> Immer aktuelle Inhalte für Suchmaschinen.</li>
<li><strong>Herausforderungen:</strong> Höherer Bedarf an Serverressourcen und komplexere Infrastruktur
:::
::</li>
</ul>
<p>::GlobalParagraph
Weitere Details findest du in der <a href="https://nuxt.com/docs/getting-started/deployment#server-hosting">offiziellen Nuxt-Dokumentation zum Server-Rendering</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}.
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Hybride Ansätze mit Nuxt
::</p>
<p>::GlobalParagraph
Ein Hybridansatz kombiniert die Vorteile von statischer Generierung und Server-Rendering. Dabei kannst du ausgewählte Seiten statisch generieren, während andere dynamisch gerendert werden. Dies eignet sich besonders für Anwendungen mit gemischten Anforderungen, wie eine E-Commerce-Website mit statisch generierten Produktseiten und einer dynamischen Warenkorb-Funktion.
::</p>
<p>::BlogCode</p>
<pre><code class="language-vue">&#x3C;!-- pages/products/_id.vue -->

&#x3C;script setup lang="ts">
interface Product {
  id: number
  name: string
  description: string
}

const route = useRoute()
const product = ref&#x3C;Product | null>(null)

const { data } = await useFetch&#x3C;Product>(`/api/products/${route.params.id}`)
product.value = data.value

function addToCart(productId: number) {
  // Dynamische Funktion zum Hinzufügen des Produkts in den Warenkorb
  // ...
}
&#x3C;/script>

&#x3C;template>
  &#x3C;div v-if="product">
    &#x3C;h1>{{ product.name }}&#x3C;/h1>
    &#x3C;p>{{ product.description }}&#x3C;/p>
    &#x3C;button @click="addToCart(product.id)">
      In den Warenkorb
    &#x3C;/button>
  &#x3C;/div>
&#x3C;/template>
</code></pre>
<p>::</p>
<p>::GlobalParagraph
In diesem Beispiel werden die Produktinformationen bereits beim Build-Prozess als statische HTML-Dateien generiert. Die Warenkorb-Funktion hingegen wird dynamisch gerendert, um Nutzern eine Interaktion in Echtzeit zu ermöglichen.
::</p>
<p>::GlobalCallAnExpert{:bg="bg-bs-blue"}
#buttonBlockTitle
Wir können auch Deine Apps mit Nuxt realisieren.
::</p>
<p>::GlobalParagraph
Um eine Nuxt-Anwendung im Hybridmodus zu starten, kannst du die <code>routeRules</code>-Option in der Datei <code>nuxt.config.ts</code> verwenden. Hier ist ein Beispiel für einen Online-Shop:
::</p>
<p>::BlogCode</p>
<pre><code class="language-typescript">// nuxt.config.ts

export default defineNuxtConfig({
  routeRules: {
    '/': { prerender: true },
    '/products/**': { swr: 3600 },
    '/admin/**': { ssr: false },
    '/api/**': { cors: true },
    '/old-page': { redirect: '/new-page' }
  }
})
</code></pre>
<p>::</p>
<p>::GlobalParagraph
Weitere Informationen zu den <code>routeRules</code> findest du in der <a href="https://nuxt.com/docs/guide/concepts/rendering#route-rules">offiziellen Nuxt-Dokumentation</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}.
::</p>
<p>::GlobalParagraph
Mit dieser Konfiguration werden die Produktseiten sowie die Startseite statisch generiert, während der Warenkorb und die Benutzerprofile dynamisch generiert werden. Beachte, dass Hybrid-Rendering nicht verfügbar ist, wenn <code>nuxt generate</code> verwendet wird. Um die Anwendung im Hybridmodus zu starten, führe die folgenden npm-Befehle aus:
::</p>
<p>::BlogCode</p>
<pre><code class="language-bash">npm run build
npm run start
</code></pre>
<p>::</p>
<p>::GlobalParagraph
Der Befehl <code>npm run build</code> erstellt die statischen HTML-Dateien und das Server-Bundle, und <code>npm run start</code> startet den Server, der sowohl die statischen Dateien als auch die dynamischen Inhalte ausliefert. Dies ermöglicht es dir, die Anwendung so zu testen, wie sie in der Produktion laufen würde.
::</p>
<p>::div{.mb-8 .mt-8 .relative }
:::GlobalSliderSection{:numberCards=2 data-title="Successful projects" :bg="bg-bs-gray"}
:::GlobalTitle{.mb-6 :size="lg" :tag="h3"}
Beispielprojekte, die mit Nuxt und dem Hybridansatz realisiert wurden
:::</p>
<p>:::GlobalParagraph
Auch in komplexen Projekten können wir mit Nuxt dafür sorgen, dass die Seiten-Geschwindigkeit über dem Industrie-Standard liegt.
:::</p>
<p>#card1
:GlobalPartial{content=slides/luma-3-blog}</p>
<p>#card2
:GlobalPartial{content=slides/wuc-1-blog}</p>
<p>:::
::</p>
<p>::GlobalParagraph
Zusammenfassung:</p>
<p>:::GlobalBlock{.ul-disk .my-4}</p>
<ul>
<li><strong>Kombination:</strong> Nutzt die Vorteile von statischer Generierung für schnelle Auslieferung und dynamisches Rendering für Echtzeit-Inhalte.</li>
<li><strong>Flexibilität:</strong> Perfekt für Anwendungen mit gemischten Anforderungen, z. B. E-Commerce oder Blogs mit dynamischen Kommentaren.</li>
<li><strong>Komplexität:</strong> Erfordert sorgfältige Planung und die richtige Konfiguration von Routen.
:::
::</li>
</ul>
<p>::GlobalParagraph
Wenn du mehr über Headless E-Commerce-Lösungen erfahren möchtest, besuche unsere <a href="/loesungen/headless-e-commerce/">Seite zu Headless E-Commerce</a>{.bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}.
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Fazit: Optimale Nutzung von Nuxt in der Produktion
::</p>
<p>::GlobalParagraph
Die Wahl zwischen <strong>Nuxt Generate</strong> und <strong>Nuxt Server</strong> hängt von den Anforderungen deiner Anwendung ab:
::</p>
<p>::GlobalBlock{.ul-disk .my-4}</p>
<ul>
<li><strong>Statische Generierung</strong> ist ideal für Websites mit gleichbleibendem Inhalt wie Blogs oder Marketingseiten.</li>
<li><strong>Server-Rendering</strong> bietet Flexibilität für dynamische oder personalisierte Inhalte.</li>
<li><strong>Hybride Ansätze</strong> vereinen Performance und Echtzeit-Interaktivität für komplexere Projekte.
::</li>
</ul>
<p>::GlobalParagraph
Hast du Fragen oder möchtest du Unterstützung bei der Umsetzung? <a href="https://calendar.google.com/calendar/u/0/appointments/schedules/AcZssZ1o_3_fkKq3NG9abaGK4vqs8nMSti3YkLdy5R12O5-L_0Rxi2RQy-lYliH-sybedsEMPW-bb5E2">Kontaktiere uns</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} – wir helfen dir, deine Nuxt-Projekte erfolgreich umzusetzen!
::</p>
<p>::GlobalParagraph{.mb-4}
Weitere Informationen zu den Rendering-Konzepten findest du in der <a href="https://nuxt.com/docs/guide/concepts/rendering">offiziellen Nuxt-Dokumentation</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}.
::</p>
<p>:::GlobalTitle{:size="lg" .mb-5 .mt-8}
Häufige Fragen
:::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
1. Was ist der Unterschied zwischen Nuxt Generate und Nuxt Server?
::</p>
<p>::GlobalParagraph
Nuxt Generate erstellt während des Build-Prozesses statische HTML-Dateien, die von einem Server oder CDN ausgeliefert werden. Nuxt Server hingegen rendert die Seiten dynamisch, wenn ein Nutzer sie anfragt. Beide Methoden haben unterschiedliche Anwendungsfälle und Vorteile.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
2. Wann sollte ich Nuxt Generate oder Nuxt Server verwenden?
::</p>
<p>::GlobalParagraph
Nutze Nuxt Generate für Websites mit gleichbleibendem Inhalt wie Blogs oder Marketingseiten. Nuxt Server ist besser geeignet für Anwendungen mit personalisierten oder häufig aktualisierten Inhalten, wie Dashboards oder E-Commerce-Seiten.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
3. Welche Vorteile bietet statische Generierung für SEO und Performance?
::</p>
<p>::GlobalParagraph
Mit Nuxt Generate sind die Ladezeiten extrem schnell, da HTML-Dateien vorgerendert werden. Das verbessert die SEO durch eine einfache Indexierung und reduziert Hosting-Kosten. Allerdings sind keine Echtzeit-Inhalte möglich, was je nach Anwendungsfall eine Einschränkung darstellen kann.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
4. Kann ich statische und dynamische Inhalte in Nuxt kombinieren?
::</p>
<p>::GlobalParagraph
Ja, hybride Ansätze sind möglich. Du kannst zum Beispiel Blogartikel statisch generieren und dynamische Elemente wie Kommentare client-seitig rendern lassen. Dies vereint schnelle Performance mit Flexibilität und passt sich perfekt an unterschiedliche Anforderungen an.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
5. Wie richte ich den richtigen Nuxt Production Mode ein?
::</p>
<p>::GlobalParagraph{.mb-4}
Für eine optimale Produktion nutzt du statische Generierung, wenn der Inhalt konstant ist, oder setzt auf Server-Rendering für dynamische Inhalte. Mit hybriden Ansätzen kannst du beides kombinieren. Eine saubere Konfiguration in der <code>nuxt.config.ts</code> ist entscheidend, um die bestmögliche Performance und Skalierbarkeit zu erreichen.
::</p>]]></content:encoded>
            <category>Nuxt</category>
            <category>Vue.js</category>
            <category>Entwicklung</category>
            <category>Betrieb</category>
            <category>Performance</category>
            <category>Sicherheit</category>
            <enclosure url="https://blueshoe.de/img/blogs/vuejsnuxt.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Nuxt & Keycloak: Anleitung zur einfachen Integration für SSR]]></title>
            <link>https://blueshoe.de/blog/nuxt-keycloak-integration</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/nuxt-keycloak-integration</guid>
            <pubDate>Fri, 10 Oct 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Die Integration von Keycloak in eine Nuxt-Anwendung für eine robuste Authentifizierung kann eine Herausforderung sein, besonders im Hinblick auf Server-Side Rendering (SSR). In diesem Artikel vergleichen wir zwei führende Module, nuxt-auth-utils und @sidebase/nuxt-auth, und bieten eine schrittweise Anleitung, um Ihnen bei der Auswahl der richtigen Lösung zu helfen.</p>
<p><img src="/img/blogs/nuxtkeycloak.svg" alt="Nuxt und Keycloak">{.object-cover .max-w-full .mb-5}</p>
<p>::GlobalBlogLevelInfo</p>
<ul>
<li><a href="https://www.keycloak.org/">Keycloak</a>{target="_blank"} und <a href="https://en.wikipedia.org/wiki/OAuth">OAuth2</a>{target="_blank"}</li>
<li><a href="https://nuxt.com/docs/4.x/guide/concepts/rendering">Nuxt Rendering Modes</a>{target="_blank"}</li>
</ul>
<p>Solltest du Fragen haben, oder dir etwas unklar sein, kannst du die Kommentarfunktion unter dem Artikel nutzen.
::</p>
<h2>SSR, Hybrid, Generate - Authentifizierung je Modus</h2>
<p>Als erstes ist es wichtig zu identifizieren, in welchem Modus die Nuxt Applikation betrieben wird. Hieraus ergeben sich unterschiedliche Anforderungen an die Authentifizierung. Einen Blogartikel zu den unterschiedlichen <a href="/blog/nuxt-generate-vs-server/">Modi gibt's hier</a>{.bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}.</p>
<p><strong>Server-Side Rendering -</strong> Werden während des Server-Side Renderings Nutzer-Informationen verarbeitet wie Berechtigungen, Namen o.ä. - so benötigt der Nitro Server Zugriff auf diese. Entsprechend muss der Nitro/Nuxt Server validieren können ob eine Session authentifiziert bzw. autorisiert ist.</p>
<p><strong>Client-Side Rendering</strong> - Es existiert keine Server-Logik. Alles wird auf der Client-Seite (dem Browser) verarbeitet - entsprechend läuft auch das Handling der Nutzer-Informationen/Session lediglich client-seitig.</p>
<p><strong>Hybrid-Rendering</strong> - Je nach dem, ob die SSR Routen Nutzer-Session-Informationen benötigen oder nicht, muss die Keycloak Integration mitgedacht werden.</p>
<h2>Option 1: Integration mit nuxt-auth-utils - New and improved</h2>
<p>Die Integration von Keycloak und Nuxt durch <a href="https://github.com/atinux/nuxt-auth-utils"><code>nuxt-auth-utils</code></a>{target="_blank"} könnte kaum einfacher sein. Ein wenig Konfiguration in der <code>nuxt.config.js</code> und schon steht das Grundgerüst:</p>
<pre><code class="language-json">runtimeConfig: {
    oauth: {
      keycloak: {
        serverUrl: 'https://keycloak.blueshoe.de',
        realm: 'Blueshoe',
        clientId: 'blueshoe-website',
        // clientSecret: '',
        redirectURL: 'https://blueshoe.de/auth/keycloak',
      },
    },
  },
  modules: [
    'nuxt-auth-utils',
  ]
</code></pre>
<p>Nun stehen komfortable <a href="https://github.com/atinux/nuxt-auth-utils/?tab=readme-ov-file#user-session">Composables</a>{target="_blank"} zur Verfügung:</p>
<pre><code class="language-javascript">const { loggedIn, user, session, fetch, clear, openInPopup } = useUserSession()
</code></pre>
<p>Mit den Informationen lassen sich schnell und einfach nutzerspezifische Informationen rendern:</p>
<pre><code class="language-vue">&#x3C;template>
  &#x3C;span v-if="loggedIn">Hallo, {{ user.firstName }} {{ user.lastName }}&#x3C;/span>
  &#x3C;span v-else>Hallo Gast&#x3C;/span>
&#x3C;/template>
</code></pre>
<p>Ebenso stehen auf SSR Seite <a href="https://github.com/atinux/nuxt-auth-utils/?tab=readme-ov-file#session-management">nützliche Utils zur Verfügung</a>{target="_blank"}. Benötigt eine Route zwingend eine gültige User-Session lässt sich dies einfach mit dem folgenden Composable realisieren:</p>
<pre><code class="language-javascript">const session = await requireUserSession(event)
</code></pre>
<p>Soweit so gut - doch wie funktioniert der Login?</p>
<p>Durch die Umgebungsvariablen hat das Modul nuxt-auth-utils alle Informationen zur Verfügung um die Login Weiterleitung zu Erzeugen und im Redirect (zurück) auf die Nuxt Applikation die Session zu verwenden.</p>
<blockquote>
<p>💡 <strong>Hinweis:</strong> Die <code>runtimeConfig</code> ist selbst keine Umgebungsvariable, sondern die Art, wie Nuxt den Zugang zu Umgebungsvariablen ermöglicht. Mehr dazu in der <a href="https://nuxt.com/docs/4.x/guide/going-further/runtime-config#environment-variables">Nuxt Dokumentation</a>{target="_blank"}.</p>
</blockquote>
<p>Hierfür wird einfach eine serverseitige Route erzeugt - unter <code>server/auth/keycloak.get.ts</code> :</p>
<pre><code class="language-javascript">export default defineOAuthKeycloakEventHandler({
  async onSuccess(event, { user }) {
    await setUserSession(event, {
      user: {
        keycloak: user.preferred_username,
      },
      loggedInAt: Date.now(),
    })

    return sendRedirect(event, '/')
  },
})
</code></pre>
<p>Ruft man nun /auth/keycloak/ auf - so wird man automatisch auf die laufende Keycloak Instanz weitergeleitet und erhält nach erfolgreichem Login eine Session.</p>
<p>Das Modul ist von <a href="https://github.com/atinux">Sébastien Chopin</a>{target="_blank"} - dem Autor von Nuxt persönlich und hat mit (Stand Sep 2025) 95.000 Downloads pro Monat, regelmäßigen Updates sowie 1.400 Stargazern ein solides Standing und kann guten Gewissens empfohlen werden.</p>
<h2>Option 2: Integration mit @sidebase/nuxt-auth - Battle-tested und einsatzbereit</h2>
<p>Eine weitere Möglichkeit Nuxt und Keycloak zusammenzubringen ist das Paket <code>@sidebase/nuxt-auth</code>. Mit ein paar Anpassungen in der <code>nuxt.config.js</code> lässt sich das Modul einfach konfigurieren:</p>
<pre><code class="language-json">runtimeConfig: {
    public: {
      authOrigin: 'http://localhost:3000',
    },
  },

  modules: [
    '@sidebase/nuxt-auth',
  ],

  auth: {
    isEnabled: true,
    disableServerSideAuth: false,
    provider: {
      type: 'authjs',
      trustHost: false,
      defaultProvider: 'keycloak',
      addDefaultCallbackUrl: true,
    },
    sessionRefresh: {
      enablePeriodically: true,
      enableOnWindowFocus: true,
    },
  },
</code></pre>
<p>Wie die Konfiguration schon zeigt kümmert sich <code>@sidebase/nuxt-auth</code> selbstständig darum die Session zu aktualisieren. Keycloak existiert als vorgefertigter Provider. Wir legen folgende Datei an <code>server/api/auth/[...].ts</code> und konfigurieren den Provider:</p>
<pre><code class="language-javascript">import KeycloakProvider from 'next-auth/providers/keycloak'
import { NuxtAuthHandler } from '#auth'

export default NuxtAuthHandler({
  secret: 'your-secret-here',
  providers: [
    KeycloakProvider.default({
      clientId: process.env.KEYCLOAK_ID,
      clientSecret: process.env.KEYCLOAK_SECRET,
      issuer: process.env.KEYCLOAK_ISSUER,
    })
  ]
})
</code></pre>
<p>Hier gilt es zu beachten, dass als Issuer die URL zum Keycloak Realm angegeben werden muss: https://my-keycloak-domain.com/realms/My_Realm.</p>
<p>Zusätzlich lassen sich Callbacks definieren, um auf verschiedene Events zu reagieren:</p>
<pre><code class="language-javascript">export default NuxtAuthHandler({
	...
	callbacks: {
	    /* on before signin */
	    async signIn({ user, account, profile, email, credentials }) {
	      return true
	    },
	    /* on redirect to another url */
	    async redirect({ url, baseUrl }) {
	      return baseUrl
	    },
	    /* on session retrieval */
	    async session({ session, user, token }) {
	      return session
	    },
	    /* on JWT token creation or mutation */
	    async jwt({ token, user, account, profile, isNewUser }) {
	      return token
	    }
	  }
})
</code></pre>
<p><code>@sidebase/nuxt-auth</code>  erzeugt selbstständig eine Seite, die den Login zur Verfügung stellt und mit den gegebenen Providern integriert.</p>
<p>Die Daten des Nutzers stehen dann wie folgt in der Applikation bereit:</p>
<pre><code class="language-vue">&#x3C;script setup>
const {
  status,
  data,
  lastRefreshedAt,
  getCsrfToken,
  getProviders,
  getSession,
  signIn,
  signOut
} = useAuth()
&#x3C;/script>
</code></pre>
<p>Der Status gibt an, ob der Nutzer authentifiziert ist, oder nicht. <code>data</code> beinhaltet nutzerspezifische Daten.</p>
<p>Das Modul ist SSR kompatibel und erlaubt es basierend auf der Session unterschiedliche Informationen zu rendern.</p>
<p><code>@sidebase/nuxt-auth</code> wird von der <a href="https://sidebase.io/">sidebase</a>{target="_blank"} entwickelt und hat sich als zuverlässige Lösung für Authentifizierung in Nuxt-Anwendungen etabliert.
Mit einer gut situierten Firma im Rücken und regelmäßigen Updates bietet es eine solide Grundlage für produktive Anwendungen und kann guten Gewissens empfohlen werden.</p>
<h2>Fazit: nuxt-auth-utils oder @sidebase/nuxt</h2>
<p>Wie so oft steckt der Teufel im Anwendungsfall. 😉 Beide Module haben einen guten Track-Record bezüglich Weiterentwicklung, die Integration ist einfach und die Composables sind sehr gut.</p>
<p>Möchte man die typischen Authentifizierungsseiten selbst überarbeiten, eignet sich <code>@sidebase/nuxt-auth</code> besser. Auch kommt das Modul mit einer konfigurierbaren einfachen Refresh-Logik für die User Session.
<code>nuxt-auth-utils</code> hingegen "fühlt" sich etwas leichtgewichtiger an. Eine <a href="https://github.com/atinux/nuxt-auth-utils/issues/356">Anfrage</a>{target="_blank"} für automatischen Session-Refresh steht aktuell noch aus. Diese lässt sich i.d.R. aber einfach selbständig nachrüsten.</p>
<p>Wir können beide Module wärmstens empfehlen und freuen uns auf Feedback zu den Erfahrungen mit Nuxt und Keycloak in den Kommentaren!</p>
<p>Wie habt ihr Nuxt und Keycloak "zusammengesteckt"? Gibt es bessere Wege? Was sind eure Erfahrungen mit den Modulen?</p>]]></content:encoded>
            <category>Nuxt</category>
            <category>Vue.js</category>
            <category>Keycloak</category>
            <enclosure url="https://blueshoe.de/img/blogs/nuxtkeycloak.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Die Lösung für SSR Slider mit Nuxt]]></title>
            <link>https://blueshoe.de/blog/nuxt-ssr-slider-top-performance</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/nuxt-ssr-slider-top-performance</guid>
            <pubDate>Tue, 19 Aug 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Lange haben wir nach einer sinnvollen Lösung zur Umsetzung von Slidern mit Nuxt gesucht. Viele Entwickler kämpfen mit Performance-Problemen, Hydration-Mismatches und schlechter SEO-Performance bei herkömmlichen Slider-Bibliotheken. In diesem umfassenden Guide zeigen wir, wie wir Embla Carousel in Nuxt einsetzen und damit Top SSR Performance erreichen.</p>
<p>In diesem Artikel erklären wir, warum herkömmliche Slider-Bibliotheken in Nuxt-Projekten oft versagen, was Embla Carousel so besonders macht und wie du damit performante, SEO-freundliche Karussells implementierst. Von der Problemanalyse über die Lösung bis hin zu Best Practices für Produktionsumgebungen – alles was du für Top-Performance Slider in Nuxt brauchst.</p>
<p><img src="/img/blogs/nuxt-embla-slider.svg" alt="Nuxt SSR Slider mit Top Performance"></p>
<h2>Einleitung: Das Problem mit Slidern in Nuxt</h2>
<h3>Warum sind Slider in Nuxt eine Herausforderung?</h3>
<p>Slider und Karussells gehören zu den häufigsten UI-Komponenten im Web. Sie werden für Produktgalerien, Testimonials, Blog-Posts und vieles mehr verwendet. Doch in Nuxt-Projekten mit Server-Side Rendering (SSR) können sie schnell zu Performance-Problemen führen und die User Experience negativ beeinflussen.</p>
<p>Das liegt daran, dass viele herkömmliche Slider-Bibliotheken wie Swiper.js, Slick Carousel oder Owl Carousel nicht für moderne SSR-Frameworks entwickelt wurden. Sie verursachen typischerweise folgende Probleme:</p>
<ul>
<li><strong>Keine SSR-Unterstützung</strong>: Viele Slider funktionieren nur client-seitig und erzeugen Hydration-Mismatches</li>
<li><strong>Hydration-Mismatches</strong>: Unterschiede zwischen Server- und Client-Rendering führen zu JavaScript-Fehlern</li>
<li><strong>Große JavaScript-Bundles</strong>: 50-100KB zusätzlicher Code verlangsamen die Ladezeit</li>
<li><strong>Layout-Shifts</strong>: Unvorhersehbare Größenänderungen verschlechtern Core Web Vitals</li>
<li><strong>Schlechte SEO-Performance</strong>: Suchmaschinen können dynamische Inhalte nicht richtig indexieren</li>
<li><strong>Accessibility-Probleme</strong>: Viele Slider sind nicht barrierefrei und verstoßen gegen WCAG-Richtlinien</li>
</ul>
<h3>Die Lösung: Embla Carousel</h3>
<p>Nach umfangreichen Tests verschiedener Slider-Bibliotheken in realen Nuxt-Projekten haben wir <strong>Embla Carousel</strong> als die beste Lösung für moderne Web-Anwendungen identifiziert. Embla wurde speziell für Frameworks wie Nuxt, Next.js und SvelteKit entwickelt und löst die typischen Probleme herkömmlicher Slider-Bibliotheken.</p>
<p><strong>Was macht Embla Carousel so besonders:</strong></p>
<ul>
<li><strong>Native SSR-Unterstützung</strong>: Perfekte Integration mit Nuxt's Server-Side Rendering</li>
<li><strong>Minimale Bundle-Größe</strong>: Nur ~7KB gzipped vs. 50-100KB bei anderen Bibliotheken</li>
<li><strong>Hervorragende Performance</strong>: Optimierte Rendering-Engine ohne Virtual DOM Overhead</li>
<li><strong>Touch-Gesten und Accessibility</strong>: Vollständige WCAG-Konformität und mobile Optimierung</li>
<li><strong>TypeScript-Unterstützung</strong>: Vollständig typisiert für sichere Entwicklung</li>
<li><strong>Flexible Konfiguration</strong>: Anpassbar an jedes Design-System und Anforderung</li>
</ul>
<p>:GlobalButton{:url="/technologien/vuejs-nuxt/" :label="Du willst Vue.js richtig einsetzen – Wir helfen dir dabei!" :target="_blank" :color="blue" .mb-6}</p>
<h2>Warum herkömmliche Slider-Bibliotheken versagen</h2>
<h3>Swiper.js: Der Klassiker mit Problemen</h3>
<p><a href="https://swiperjs.com/">Swiper.js</a>{target="_blank"} ist zweifellos eine der beliebtesten Slider-Bibliotheken. Mit über 35.000 GitHub-Stars und einer großen Community scheint es die perfekte Wahl zu sein. Doch in Nuxt-Projekten stößt Swiper schnell an seine Grenzen.</p>
<p>Die Probleme mit Swiper.js sind vielfältig und reichen von der enormen Bundle-Größe von über 45KB gzipped für die Basis-Version bis hin zu grundlegenden SSR-Problemen. Swiper bietet keine native SSR-Unterstützung und erfordert Client-only-Wrapper, was zu häufigen Hydration-Issues führt. Diese Mismatches zwischen Server- und Client-Rendering können die User Experience erheblich beeinträchtigen. Zusätzlich leidet die Performance unter Virtual DOM Overhead und unnötigen Re-Renders, während grundlegende Accessibility-Features oft fehlen.</p>
<h3>Slick Carousel: Der Legacy-Klassiker</h3>
<p><a href="https://kenwheeler.github.io/slick/">Slick Carousel</a>{target="_blank"} war lange Zeit der Standard für jQuery-basierte Slider. Doch in modernen Vue.js/Nuxt-Anwendungen ist es nicht mehr zeitgemäß.</p>
<p>Die jQuery-Abhängigkeit stellt eine unnötige Belastung in modernen Frameworks dar, während die fehlende SSR-Unterstützung bedeutet, dass Slick nur client-seitig funktioniert. Mit einer Bundle-Größe von über 30KB zusätzlich zu jQuery und einer veralteten Architektur, die nicht für moderne Web-Standards entwickelt wurde, ist Slick Carousel heute keine praktikable Option mehr.</p>
<h3>Owl Carousel: Der vergessene Klassiker</h3>
<p><a href="https://owlcarousel2.github.io/OwlCarousel2/">Owl Carousel</a>{target="_blank"} war einst beliebt, hat aber mit modernen Frameworks erhebliche Probleme.</p>
<p>Seit Jahren gibt es keine aktiven Updates, was zu SSR-Inkompatibilität führt. Das Framework funktioniert nicht mit Server-Side Rendering und leidet unter Performance-Problemen durch ineffiziente DOM-Manipulation. Zusätzlich fehlen Accessibility-Features und WCAG-Konformität, was es für moderne Web-Anwendungen ungeeignet macht.</p>
<h2>Embla Carousel: Die moderne Alternative</h2>
<h3>Was ist Embla Carousel?</h3>
<p><a href="https://www.embla-carousel.com/">Embla Carousel</a>{target="_blank"} ist eine moderne, leichte und performante Slider-Bibliothek, die speziell für moderne Web-Frameworks entwickelt wurde. Im Gegensatz zu herkömmlichen Slider-Bibliotheken wurde Embla von Grund auf für SSR, Performance und Accessibility konzipiert.</p>
<p>Die Entwicklungsphilosophie von Embla Carousel basiert auf vier fundamentalen Prinzipien: Framework-Agnostizismus ermöglicht die Verwendung mit Vue, React, Svelte und Vanilla JavaScript. Der Performance-first-Ansatz garantiert minimale Bundle-Größe und eine optimierte Rendering-Engine. Accessibility-by-default bedeutet, dass alle WCAG-Richtlinien von Anfang an beachtet werden. Und schließlich bietet Embla native Unterstützung für Server-Side Rendering, was es zur idealen Wahl für Nuxt-Projekte macht.</p>
<h3>Technische Vorteile von Embla Carousel</h3>
<p><strong>1. Minimale Bundle-Größe</strong>
Embla Carousel ist mit nur ~7KB gzipped extrem kompakt. Im Vergleich dazu benötigen Swiper.js über 45KB gzipped, Slick Carousel über 30KB gzipped plus jQuery, und Owl Carousel etwa 25KB gzipped. Diese deutliche Reduktion der Bundle-Größe führt zu schnelleren Ladezeiten und besserer Performance.</p>
<p><strong>2. Optimierte Performance</strong>
Embla Carousel verzichtet bewusst auf Virtual DOM und nutzt direkte DOM-Manipulation ohne Overhead. Intelligentes Caching sorgt für effiziente Speichernutzung, während Lazy Loading Bilder und Inhalte bedarfsgerecht lädt. Die Touch-Optimierung bietet native Touch-Gesten ohne zusätzliche Bibliotheken.</p>
<p><strong>3. Native SSR-Unterstützung</strong>
Embla Carousel ist hydration-frei und verursacht keine Mismatches zwischen Server und Client. Die SEO-freundliche Struktur ermöglicht es Suchmaschinen, alle Inhalte zu indexieren. Progressive Enhancement bedeutet, dass der Slider auch ohne JavaScript funktioniert.</p>
<p><strong>4. Accessibility-Features</strong>
Vollständige Tastatursteuerung ermöglicht die Navigation über Keyboard, während Screen Reader Support durch ARIA-Labels und semantische Struktur gewährleistet wird. Intelligente Fokus-Verwaltung und WCAG 2.1 AA Konformität erfüllen alle wichtigen Accessibility-Standards.</p>
<p>Für detaillierte Performance-Benchmarks und Vergleiche empfehlen wir die <a href="https://www.embla-carousel.com/api/options/">offizielle Embla Carousel API-Dokumentation</a>.</p>
<h2>Installation und Setup</h2>
<h3>1. Embla Carousel installieren</h3>
<pre><code class="language-bash">npm install embla-carousel-vue
# oder
yarn add embla-carousel-vue
</code></pre>
<h3>2. Grundlegende Komponenten-Struktur</h3>
<p>Die <code>emblaCarouselVue</code> Funktion bietet eine nahtlose Integration mit Vue. Ein minimales Setup benötigt einen <strong>overflow wrapper</strong> und einen <strong>scroll container</strong>. Hier ist die grundlegende Struktur:</p>
<pre><code class="language-vue">&#x3C;script setup>
import emblaCarouselVue from 'embla-carousel-vue'

const [emblaRef] = emblaCarouselVue()
&#x3C;/script>

&#x3C;template>
  &#x3C;div class="embla" ref="emblaRef">
    &#x3C;div class="embla__container">
      &#x3C;div class="embla__slide">Slide 1&#x3C;/div>
      &#x3C;div class="embla__slide">Slide 2&#x3C;/div>
      &#x3C;div class="embla__slide">Slide 3&#x3C;/div>
    &#x3C;/div>
  &#x3C;/div>
&#x3C;/template>
</code></pre>
<h3>3. Styling des Carousels</h3>
<p>Die <code>emblaCarouselVue</code> Funktion gibt uns eine <strong>emblaRef</strong>, die wir an unser Wrapper-Element mit der Klasse <code>embla</code> anhängen, um den Scroll-Overflow zu verstecken. Das Element mit der <code>container</code> Klasse ist der Scroll-Body, der die Slides scrollt. Hier ist das benötigte <strong>CSS</strong>:</p>
<pre><code class="language-vue">&#x3C;style scoped>
.embla {
  overflow: hidden;
}

.embla__container {
  display: flex;
}

.embla__slide {
  flex: 0 0 100%;
  min-width: 0;
}
&#x3C;/style>
</code></pre>
<h3>4. Zugriff auf die Carousel API</h3>
<p>Die <code>emblaCarouselVue</code> Funktion nimmt die Embla Carousel Optionen als ersten Parameter. Zusätzlich kannst du mit <code>onMounted</code> auf die API zugreifen:</p>
<pre><code class="language-vue">&#x3C;script setup>
import { onMounted } from 'vue'
import emblaCarouselVue from 'embla-carousel-vue'

const [emblaRef, emblaApi] = emblaCarouselVue({ loop: false })

onMounted(() => {
  if (emblaApi.value) {
    console.log(emblaApi.value.slideNodes()) // API-Zugriff
  }
})
&#x3C;/script>

&#x3C;template>
  &#x3C;div class="embla" ref="emblaRef">
    &#x3C;div class="embla__container">
      &#x3C;div class="embla__slide">Slide 1&#x3C;/div>
      &#x3C;div class="embla__slide">Slide 2&#x3C;/div>
      &#x3C;div class="embla__slide">Slide 3&#x3C;/div>
    &#x3C;/div>
  &#x3C;/div>
&#x3C;/template>
</code></pre>
<h3>5. Plugins hinzufügen (optional)</h3>
<p>Falls du Plugins verwenden möchtest, installiere zuerst das gewünschte Plugin. Zum Beispiel das Autoplay-Plugin:</p>
<pre><code class="language-bash">npm install embla-carousel-autoplay
# oder
yarn add embla-carousel-autoplay
</code></pre>
<p>Die <code>emblaCarouselVue</code> Funktion akzeptiert Plugins als zweiten Parameter. Plugins müssen in einem <strong>Array</strong> übergeben werden:</p>
<pre><code class="language-vue">&#x3C;script setup>
import emblaCarouselVue from 'embla-carousel-vue'
import Autoplay from 'embla-carousel-autoplay'

const [emblaRef] = emblaCarouselVue({ loop: false }, [Autoplay()])
&#x3C;/script>

&#x3C;template>
  &#x3C;div class="embla" ref="emblaRef">
    &#x3C;div class="embla__container">
      &#x3C;div class="embla__slide">Slide 1&#x3C;/div>
      &#x3C;div class="embla__slide">Slide 2&#x3C;/div>
      &#x3C;div class="embla__slide">Slide 3&#x3C;/div>
      &#x3C;div class="embla__slide">Slide 4&#x3C;/div>
    &#x3C;/div>
  &#x3C;/div>
&#x3C;/template>
</code></pre>
<h2>Anwendungsfälle und Use Cases</h2>
<h3>Wann solltest du Embla Carousel verwenden?</h3>
<p>Embla Carousel eignet sich besonders gut für verschiedene Anwendungsfälle, die wir im Detail betrachten wollen.</p>
<p><strong>1. Produktgalerien</strong>
Für E-Commerce-Websites sind hochperformante Produktgalerien essentiell. Embla Carousel bietet touch-optimierte Navigation für mobile Nutzer, Lazy Loading für schnelle Ladezeiten, Zoom-Funktionalität für detaillierte Produktansichten und eine SEO-freundliche Struktur für bessere Suchmaschinen-Rankings. Diese Kombination macht Embla zur idealen Wahl für Online-Shops, die auf Conversion und Performance setzen.</p>
<p><strong>2. Testimonials und Reviews</strong>
Kundenbewertungen sind entscheidend für die Conversion-Rate. Embla Carousel unterstützt diese Anforderung durch Autoplay-Funktionalität für automatische Rotation, Pause bei Hover für bessere Nutzerkontrolle, umfassende Accessibility-Features für alle Nutzer und responsives Design für alle Geräte. Diese Features sorgen dafür, dass Kundenbewertungen optimal präsentiert werden.</p>
<p><strong>3. Blog-Post Slider</strong>
Für Content-Marketing und Blog-Websites bietet Embla Carousel Infinite Loop für endlose Navigation, Keyboard Navigation für Power-User, Screen Reader Support für barrierefreien Zugang und Social Media Integration für Sharing-Funktionen. Diese Kombination macht den Slider ideal für Content-vermittelnde Websites.</p>
<p><strong>4. Team-Mitglieder Präsentation</strong>
Für Unternehmenswebsites und Portfolios bietet Embla Carousel Grid-Layout für mehrere Slides gleichzeitig, Hover-Effekte für interaktive Elemente, Smooth Transitions für professionelle Optik und Mobile-First Design für optimale Nutzung. Diese Features sorgen für eine professionelle Präsentation von Team-Mitgliedern.</p>
<h3>Performance-Vorteile in der Praxis</h3>
<p><strong>SEO-Verbesserungen</strong>
Durch die native SSR-Unterstützung kannst du die SEO-Performance deutlich verbessern. Die optimierten Core Web Vitals durch schnelles Rendering führen zu hohen Lighthouse Scores und guten Google PageSpeed Bewertungen für mobile Geräte. Die verbesserte Indexierung von Bildinhalten in der Search Console rundet die SEO-Vorteile ab.</p>
<h2>Grundlegende Implementierung</h2>
<h3>Einfacher Slider</h3>
<pre><code class="language-vue">&#x3C;template>
  &#x3C;div class="embla" ref="emblaRef">
    &#x3C;div class="embla__container">
      &#x3C;div class="embla__slide" v-for="(slide, index) in slides" :key="index">
        &#x3C;img :src="slide.image" :alt="slide.title" class="w-full h-64 object-cover" />
        &#x3C;h3 class="text-xl font-bold mt-4">{{ slide.title }}&#x3C;/h3>
        &#x3C;p class="text-gray-600">{{ slide.description }}&#x3C;/p>
      &#x3C;/div>
    &#x3C;/div>
  &#x3C;/div>
&#x3C;/template>

&#x3C;script setup>
import { ref, onMounted } from 'vue'
import emblaCarouselVue from 'embla-carousel-vue'

const [emblaRef, emblaApi] = emblaCarouselVue()

const slides = ref([
  {
    image: '/img/blog/team/blue_shoes-47.jpg',
    title: 'Cloud Native Entwicklung',
    description: 'Moderne Anwendungen mit Docker, Kubernetes und Microservices',
    link: '/leistungen/cloud-native-entwicklung'
  },
  {
    image: '/img/blog/team/blue_shoes-3.jpg',
    title: 'Vue.js &#x26; Nuxt.js Expertise',
    description: 'Performante Frontend-Lösungen mit modernen Frameworks',
    link: '/technologien/vuejs-nuxt'
  },
  {
    image: '/img/blog/team/blue_shoes-28.jpg',
    title: 'DevOps &#x26; CI/CD',
    description: 'Automatisierte Deployment-Pipelines für maximale Effizienz',
    link: '/leistungen/devops-consulting'
  },
  {
    image: '/img/blog/team/blue_shoes-61.jpg',
    title: 'API-Entwicklung',
    description: 'RESTful APIs und GraphQL mit Python, FastAPI und Django',
    link: '/leistungen/api-entwicklung'
  }
])

onMounted(() => {
  if (emblaApi.value) {
    emblaApi.value.on('select', (event) => {
      const currentSlide = slides.value[event.selectedScrollSnap()]
      console.log(`Aktueller Slide: ${currentSlide.title}`)
    })
  }
})
&#x3C;/script>

&#x3C;style scoped>
.embla {
  overflow: hidden;
}

.embla__container {
  display: flex;
  gap: 1rem;
}

.embla__slide {
  flex: 0 0 100%;
  min-width: 0;
}
&#x3C;/style>
</code></pre>
<h2>Erweiterte Features und Konfiguration</h2>
<h3>Responsive Design und Breakpoints</h3>
<p>Embla Carousel bietet hervorragende Unterstützung für responsive Design. Mit den integrierten Breakpoints kannst du das Verhalten des Sliders an verschiedene Bildschirmgrößen anpassen.</p>
<p>Die Vorteile der responsiven Konfiguration umfassen einen Mobile-First Approach, der für Touch-Geräte optimiert ist, Tablet-Optimierung mit angepasster Navigation für mittlere Bildschirme, Desktop-Enhancement mit erweiterten Features für große Bildschirme und Performance-Optimierung, bei der nur notwendige Features geladen werden.</p>
<h3>Autoplay und Interaktivität</h3>
<p>Die Autoplay-Funktionalität von Embla Carousel ist besonders benutzerfreundlich gestaltet.</p>
<p>Intelligente Autoplay-Features umfassen Pause bei Hover für automatisches Pausieren bei Nutzerinteraktion, Touch-Pause, das bei Touch-Gesten auf mobilen Geräten stoppt, Keyboard-Pause bei Tastatureingaben und Visibility API, das pausiert, wenn der Slider nicht sichtbar ist.</p>
<h3>Touch-Gesten und Mobile Optimierung</h3>
<p>Embla Carousel bietet native Touch-Unterstützung ohne zusätzliche Bibliotheken.</p>
<p>Die Touch-Features umfassen Swipe-Gesten für natürliche Swipe-Navigation, Momentum Scrolling mit Physik-basierten Animationen, Touch Resistance, die versehentliche Navigation verhindert, und Multi-Touch Support für Pinch-to-Zoom und andere Gesten.</p>
<h2>Performance-Optimierungen</h2>
<h3>Lazy Loading für Bilder</h3>
<p>Embla Carousel unterstützt nativ Lazy Loading für optimale Performance. Hier ist ein Beispiel für die Implementierung:</p>
<pre><code class="language-vue">&#x3C;template>
  &#x3C;div class="embla" ref="emblaRef">
    &#x3C;div class="embla__container">
      &#x3C;div class="embla__slide" v-for="(slide, index) in slides" :key="index">
        &#x3C;img 
          :src="slide.image" 
          :alt="slide.title" 
          loading="lazy"
          class="w-full h-64 object-cover"
        />
        &#x3C;h3 class="text-xl font-bold mt-4">{{ slide.title }}&#x3C;/h3>
        &#x3C;p class="text-gray-600">{{ slide.description }}&#x3C;/p>
      &#x3C;/div>
    &#x3C;/div>
  &#x3C;/div>
&#x3C;/template>

&#x3C;script setup>
import emblaCarouselVue from 'embla-carousel-vue'

const [emblaRef] = emblaCarouselVue()
&#x3C;/script>
</code></pre>
<h3>Intersection Observer für Performance</h3>
<p>Für zusätzliche Performance-Optimierung kannst du Intersection Observer verwenden, um den Slider nur zu initialisieren, wenn er sichtbar ist:</p>
<pre><code class="language-vue">&#x3C;script setup>
import { ref, onMounted } from 'vue'
import emblaCarouselVue from 'embla-carousel-vue'

const isVisible = ref(false)
const sliderRef = ref(null)
const [emblaRef, emblaApi] = emblaCarouselVue()

onMounted(() => {
  const observer = new IntersectionObserver(
    ([entry]) => {
      isVisible.value = entry.isIntersecting
    },
    { threshold: 0.1 }
  )
  
  if (sliderRef.value) {
    observer.observe(sliderRef.value)
  }
})
&#x3C;/script>

&#x3C;template>
  &#x3C;div ref="sliderRef" class="embla" ref="emblaRef">
    &#x3C;div class="embla__container">
      &#x3C;div class="embla__slide" v-for="(slide, index) in slides" :key="index">
        &#x3C;img :src="slide.image" :alt="slide.title" class="w-full h-64 object-cover" />
      &#x3C;/div>
    &#x3C;/div>
  &#x3C;/div>
&#x3C;/template>
</code></pre>
<h3>Optimierte Bundle-Größe</h3>
<p>Für Nuxt-Projekte kannst du die Bundle-Größe durch optimierte Konfiguration weiter reduzieren:</p>
<pre><code class="language-typescript">// nuxt.config.ts
export default defineNuxtConfig({
  build: {
    transpile: ['embla-carousel-vue']
  },
  vite: {
    optimizeDeps: {
      include: ['embla-carousel-vue']
    }
  }
})
</code></pre>
<h2>Accessibility und SEO-Optimierung</h2>
<h3>Warum Accessibility für Slider wichtig ist</h3>
<p>Slider sind oft kritische UI-Komponenten, die von allen Nutzern bedient werden können müssen. Embla Carousel wurde von Grund auf mit Accessibility im Fokus entwickelt und bietet native Unterstützung für Screen Reader, Keyboard-Navigation und WCAG 2.1 AA Konformität.</p>
<p>Die Vorteile für Slider-spezifische Accessibility reichen von vollständiger Tastatursteuerung für Power-User bis hin zu Screen Reader Support, der es blinden Nutzern ermöglicht, durch alle Slides zu navigieren. Embla's native ARIA-Labels und semantische Struktur sorgen dafür, dass jeder Slide korrekt beschrieben wird.</p>
<h3>Embla Carousel Accessibility-Features</h3>
<p>Embla Carousel bietet umfassende Accessibility-Features, die speziell für Slider-Implementierungen entwickelt wurden:</p>
<p><strong>1. Keyboard Navigation für Slider</strong></p>
<pre><code class="language-vue">&#x3C;template>
  &#x3C;div 
    class="embla" 
    ref="emblaRef"
    @keydown="handleKeydown"
    tabindex="0"
    role="region"
    aria-label="Bildergalerie"
  >
    &#x3C;div class="embla__container">
      &#x3C;div class="embla__slide" v-for="(slide, index) in slides" :key="index">
        &#x3C;img :src="slide.image" :alt="slide.title" class="w-full h-64 object-cover" />
      &#x3C;/div>
    &#x3C;/div>
  &#x3C;/div>
&#x3C;/template>

&#x3C;script setup>
import emblaCarouselVue from 'embla-carousel-vue'

const [emblaRef, emblaApi] = emblaCarouselVue()

const handleKeydown = (event) => {
  switch (event.key) {
    case 'ArrowLeft':
      emblaApi.value?.scrollPrev()
      break
    case 'ArrowRight':
      emblaApi.value?.scrollNext()
      break
    case 'Home':
      emblaApi.value?.scrollTo(0)
      break
    case 'End':
      emblaApi.value?.scrollTo(slides.value.length - 1)
      break
  }
}
&#x3C;/script>
</code></pre>
<p><strong>2. Screen Reader Support für Slider-Inhalte</strong></p>
<pre><code class="language-vue">&#x3C;template>
  &#x3C;div class="embla" ref="emblaRef">
    &#x3C;div class="embla__container">
      &#x3C;div 
        class="embla__slide" 
        v-for="(slide, index) in slides" 
        :key="index"
        :aria-label="`Slide ${index + 1} von ${slides.length}: ${slide.title}`"
        :aria-hidden="emblaApi?.selectedScrollSnap() !== index"
      >
        &#x3C;img 
          :src="slide.image" 
          :alt="slide.title" 
          class="w-full h-64 object-cover"
        />
      &#x3C;/div>
    &#x3C;/div>
    
    &#x3C;!-- Navigation mit ARIA-Labels -->
    &#x3C;button 
      class="embla__prev" 
      @click="emblaApi?.scrollPrev()"
      aria-label="Vorheriger Slide"
      :aria-disabled="emblaApi?.canScrollPrev() === false"
    >
      ←
    &#x3C;/button>
    &#x3C;button 
      class="embla__next" 
      @click="emblaApi?.scrollNext()"
      aria-label="Nächster Slide"
      :aria-disabled="emblaApi?.canScrollNext() === false"
    >
      →
    &#x3C;/button>
  &#x3C;/div>
&#x3C;/template>

&#x3C;script setup>
import emblaCarouselVue from 'embla-carousel-vue'

const [emblaRef, emblaApi] = emblaCarouselVue()
&#x3C;/script>
</code></pre>
<h3>SEO-Optimierung für Slider-Inhalte</h3>
<p>Slider stellen eine besondere Herausforderung für SEO dar, da dynamische Inhalte oft von Suchmaschinen übersehen werden. Embla Carousel löst dieses Problem durch native SSR-Unterstützung und SEO-freundliche Strukturen.</p>
<p><strong>Warum SEO für Slider kritisch ist:</strong></p>
<ul>
<li><strong>Bildindexierung</strong>: Suchmaschinen können alle Bilder in Slidern indexieren, nicht nur das erste</li>
<li><strong>Content-Accessibility</strong>: Alle Slide-Inhalte sind für Crawler verfügbar, nicht nur sichtbare</li>
<li><strong>Strukturierte Daten</strong>: Schema.org Markup für Karussell-Inhalte verbessert Snippets</li>
<li><strong>Core Web Vitals</strong>: Optimierte Performance verbessert Rankings für Seiten mit Slidern</li>
</ul>
<p><strong>SEO-Best Practices für Embla Slider:</strong></p>
<ul>
<li><strong>Semantische HTML-Struktur</strong>: Verwendung von korrekten HTML-Tags für Slider-Inhalte</li>
<li><strong>Aussagekräftige Alt-Texte</strong>: Jedes Bild im Slider benötigt beschreibende Alt-Texte</li>
<li><strong>Strukturierte Daten</strong>: Schema.org Markup für Karussell-Inhalte implementieren</li>
<li><strong>Meta-Beschreibungen</strong>: Optimierte Meta-Tags für jeden Slide-Inhalt</li>
</ul>
<h2>Best Practices für Slider-Implementierungen</h2>
<h3>Warum Slider-spezifische Optimierung wichtig ist</h3>
<p>Slider sind oft die ersten Elemente, die Nutzer auf einer Seite sehen. Eine schlecht optimierte Slider-Implementierung kann die gesamte User Experience negativ beeinflussen. Embla Carousel bietet spezielle Features für Produktionsumgebungen.</p>
<p><strong>Slider-spezifische Probleme:</strong></p>
<ul>
<li><strong>Langsame Ladezeiten</strong>: Große Bilder in Slidern verlangsamen die Initial Page Load</li>
<li><strong>Layout-Shifts</strong>: Unvorhersehbare Größenänderungen verschlechtern Core Web Vitals</li>
<li><strong>Touch-Probleme</strong>: Schlechte Touch-Optimierung auf mobilen Geräten</li>
<li><strong>Accessibility-Mängel</strong>: Viele Slider sind nicht barrierefrei</li>
</ul>
<h3>Error Handling für Slider</h3>
<p>Robustes Error Handling ist besonders wichtig für Slider, da sie oft kritische Inhalte präsentieren:</p>
<p><strong>1. Graceful Degradation für Slider</strong></p>
<pre><code class="language-vue">&#x3C;script setup>
import { ref, onMounted } from 'vue'
import emblaCarouselVue from 'embla-carousel-vue'

const hasError = ref(false)
const errorMessage = ref('')
const isLoading = ref(true)

const [emblaRef, emblaApi] = emblaCarouselVue()

onMounted(() => {
  try {
    if (emblaApi.value) {
      emblaApi.value.on('error', (error) => {
        console.error('Slider error:', error)
        hasError.value = true
        errorMessage.value = 'Bildergalerie konnte nicht geladen werden'
      })
      
      emblaApi.value.on('init', () => {
        isLoading.value = false
        console.log('Slider erfolgreich initialisiert')
      })
    }
  } catch (error) {
    hasError.value = true
    errorMessage.value = `Fehler beim Initialisieren der Bildergalerie: ${error.message}`
    isLoading.value = false
    console.error('Slider initialization failed:', error)
  }
})
&#x3C;/script>
</code></pre>
<h3>Progressive Enhancement für Slider</h3>
<p>Slider müssen auch ohne JavaScript funktionieren, um SEO und Accessibility zu gewährleisten:</p>
<p><strong>1. Progressive Enhancement für Slider</strong></p>
<pre><code class="language-vue">&#x3C;template>
  &#x3C;div class="embla">
    &#x3C;!-- Fallback für SEO und No-JS -->
    &#x3C;div class="embla__fallback" v-if="!isHydrated">
      &#x3C;div v-for="(slide, index) in slides" :key="index" class="slide-fallback mb-8">
        &#x3C;img 
          :src="slide.image" 
          :alt="slide.title" 
          class="w-full h-64 object-cover rounded-lg"
        />
        &#x3C;h3 class="text-xl font-bold mt-4 text-gray-800">{{ slide.title }}&#x3C;/h3>
        &#x3C;p class="text-gray-600 mt-2">{{ slide.description }}&#x3C;/p>
        &#x3C;a 
          :href="slide.link" 
          class="inline-block mt-4 px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
        >
          Mehr erfahren
        &#x3C;/a>
      &#x3C;/div>
    &#x3C;/div>
    
    &#x3C;!-- Hydrated Slider mit voller Funktionalität -->
    &#x3C;div v-else class="embla" ref="emblaRef">
      &#x3C;div class="embla__container">
        &#x3C;div 
          class="embla__slide" 
          v-for="(slide, index) in slides" 
          :key="index"
        >
          &#x3C;div class="relative group">
            &#x3C;img 
              :src="slide.image" 
              :alt="slide.title" 
              class="w-full h-64 object-cover rounded-lg transition-transform group-hover:scale-105"
            />
            &#x3C;div class="absolute inset-0 bg-black bg-opacity-40 rounded-lg opacity-0 group-hover:opacity-100 transition-opacity">
              &#x3C;div class="absolute bottom-4 left-4 text-white">
                &#x3C;h3 class="text-xl font-bold">{{ slide.title }}&#x3C;/h3>
                &#x3C;p class="text-sm">{{ slide.description }}&#x3C;/p>
              &#x3C;/div>
            &#x3C;/div>
          &#x3C;/div>
        &#x3C;/div>
      &#x3C;/div>
    &#x3C;/div>
  &#x3C;/div>
&#x3C;/template>

&#x3C;script setup>
import { ref } from 'vue'

const isHydrated = ref(false)

onMounted(() => {
  isHydrated.value = true
})
&#x3C;/script>
</code></pre>
<h3>Performance-Metriken für Slider</h3>
<p>Für Slider-spezifische Performance-Optimierung sind bestimmte Metriken besonders wichtig:</p>
<p><strong>Slider-spezifische Metriken:</strong></p>
<ul>
<li><strong>Ladezeit</strong>: Slider sollten unter 1 Sekunde laden, da sie oft Above-the-Fold sind</li>
<li><strong>Interaktionsverzögerung</strong>: Slide-Wechsel sollten unter 100ms reagieren</li>
<li><strong>Memory Usage</strong>: Überwachung von Speicherlecks bei langen Slider-Sessions</li>
<li><strong>Touch-Responsiveness</strong>: Touch-Gesten sollten sofort reagieren</li>
<li><strong>Slide-Transition-Performance</strong>: Flüssige Übergänge zwischen Slides</li>
</ul>
<h2>Fazit: Warum Embla Carousel die beste Slider-Lösung ist</h2>
<p>Nach jahrelanger Erfahrung mit verschiedenen Slider-Bibliotheken in Nuxt-Projekten können wir mit Sicherheit sagen: <strong>Embla Carousel ist die beste Lösung für moderne Slider-Implementierungen</strong>.</p>
<h3>Zusammenfassung der Slider-Vorteile</h3>
<p><strong>Performance-Vorteile für Slider:</strong>
Embla Carousel bietet eine kleine Bundle-Größe von nur ~7KB gzipped, was besonders wichtig ist, da Slider oft Above-the-Fold geladen werden. Hohe FPS und flüssige Animationen sorgen für professionelle Slide-Übergänge, während schnelle Ladezeiten durch optimierte Architektur die User Experience verbessern. Keine Hydration-Mismatches dank nativer SSR-Unterstützung machen Embla zur idealen Wahl für Nuxt-Projekte.</p>
<p><strong>SEO- und Accessibility-Vorteile für Slider:</strong>
Vollständige WCAG 2.1 AA Konformität ist besonders wichtig für Slider, da sie oft kritische Inhalte präsentieren. Bessere Google-Rankings durch optimierte Core Web Vitals, Screen Reader Support für barrierefreien Zugang zu allen Slides und Keyboard Navigation für Power-User machen Embla zur idealen Wahl für professionelle Slider-Implementierungen.</p>
<p><strong>Entwickler-Erfahrung für Slider:</strong>
TypeScript-Unterstützung für sichere Slider-Entwicklung, flexible Konfiguration für alle Slider-Anwendungsfälle, aktive Community und regelmäßige Updates sowie umfassende Dokumentation und Beispiele sorgen für eine hervorragende Entwicklererfahrung bei der Implementierung von Slidern.</p>
<h3>Wann du Embla Carousel für Slider verwenden solltest</h3>
<p><strong>Perfekt geeignet für Slider in:</strong>
Embla Carousel ist ideal für E-Commerce-Websites mit Produktgalerien, Corporate Websites mit Team-Präsentationen, Blog-Websites mit Content-Slidern, Portfolio-Websites mit Projekt-Galerien und Marketing-Websites mit Testimonial-Slidern.</p>
<p><strong>Nicht geeignet für:</strong>
Sehr einfache Bildergalerien ohne Interaktivität, Legacy-Projekte mit jQuery-Abhängigkeiten oder Projekte mit sehr spezifischen Anforderungen an andere Slider-Bibliotheken sind weniger geeignet für Embla Carousel.</p>
<h3>Business-Impact für Slider-Implementierungen</h3>
<p>Die Implementierung von Embla Carousel für Slider kann erhebliche Vorteile für dein Business bringen:</p>
<p><strong>Performance-Impact für Slider:</strong>
Schnellere Ladezeiten verbessern die User Experience bei Slidern, während bessere Core Web Vitals zu höheren Google-Rankings führen. Reduzierte Bounce-Rate durch schnellere Slider-Interaktionen rundet die Performance-Vorteile ab.</p>
<p><strong>SEO-Impact für Slider:</strong>
Verbesserte Bildindexierung durch SEO-freundliche Slider-Struktur, bessere Accessibility-Scores in Lighthouse und höhere Conversion-Rates durch optimierte Slider-User Experience sind die wichtigsten SEO-Vorteile.</p>
<p><strong>Entwicklungs-Impact für Slider:</strong>
Schnellere Entwicklung durch einfache Slider-Integration, weniger Bugs durch TypeScript und robuste Slider-Architektur sowie einfachere Wartung durch klare API und Dokumentation machen Embla Carousel zur idealen Wahl für Slider-Entwicklerteams.</p>
<hr>
<h2>FAQ – Häufige Fragen zu Embla Carousel in Nuxt</h2>
<h3>1. Warum ist Embla Carousel die beste Wahl für Nuxt SSR?</h3>
<p>Embla Carousel bietet native SSR-Unterstützung, minimale Bundle-Größe und hervorragende Performance. Es ist speziell für moderne Web-Frameworks entwickelt und funktioniert nahtlos mit Nuxt's Server-Side Rendering. Im Gegensatz zu anderen Slider-Bibliotheken verursacht Embla keine Hydration-Mismatches und bietet optimale Performance für Nuxt-Projekte.</p>
<h3>2. Wie implementiere ich Embla Carousel in einem Nuxt-Projekt?</h3>
<p>Die Implementierung ist denkbar einfach: Installieren Sie das Embla Carousel Vue-Plugin mit <code>npm install embla-carousel-vue</code> und verwenden Sie die <code>emblaCarouselVue</code> Funktion in Ihren Komponenten. Erstellen Sie eine Client-only-Komponente für die Carousel-Funktionalität und nutzen Sie Nuxt's SSR-Features für optimale Performance. Die detaillierte Implementierung haben wir in diesem Artikel Schritt für Schritt erklärt.</p>
<h3>3. Welche Performance-Optimierungen bietet Embla Carousel?</h3>
<p>Embla Carousel bietet Lazy Loading, Touch-Gesten, Keyboard-Navigation, automatische Größenanpassung und minimale JavaScript-Ausführung. Diese Features sorgen für flüssige Animationen und optimale Nutzererfahrung. Zusätzlich unterstützt Embla Intersection Observer für bedarfsgerechte Initialisierung und bietet native Touch-Optimierung ohne zusätzliche Bibliotheken.</p>
<h3>4. Kann ich Embla Carousel mit TypeScript in Nuxt verwenden?</h3>
<p>Ja, Embla Carousel bietet vollständige TypeScript-Unterstützung. Die Typen sind bereits im Paket enthalten und bieten hervorragende IDE-Unterstützung für eine sichere Entwicklung. Die <code>emblaCarouselVue</code> Funktion ist vollständig typisiert und bietet IntelliSense für alle Optionen und Methoden.</p>
<h3>5. Wie handle ich Bilder und Medien in Embla Carousel?</h3>
<p>Embla Carousel unterstützt Lazy Loading für Bilder und bietet optimierte Medien-Handling-Features. Kombinieren Sie es mit Nuxt Image für zusätzliche Performance-Optimierungen. Das <code>loading="lazy"</code> Attribut wird nativ unterstützt, und Sie können Intersection Observer für zusätzliche Performance-Optimierungen verwenden.</p>
<h3>6. Welche Alternativen zu Embla Carousel gibt es für Nuxt?</h3>
<p>Alternativen sind Swiper.js, Splide.js oder Slick Carousel. Embla Carousel zeichnet sich jedoch durch bessere SSR-Unterstützung, kleinere Bundle-Größe und modernere Architektur aus. Wie wir in diesem Artikel gezeigt haben, haben herkömmliche Slider-Bibliotheken erhebliche Nachteile in Nuxt-Projekten, während Embla Carousel speziell für moderne Web-Frameworks entwickelt wurde.</p>
<p>:GlobalButton{:url="/kontakt/" :label="Brauchst du Hilfe bei der Slider-Implementierung? Kontaktiere uns!" :target="_blank" :color="green" .mt-8}</p>]]></content:encoded>
            <category>Nuxt</category>
            <category>Vue.js</category>
            <category>Entwicklung</category>
            <category>Performance</category>
            <enclosure url="https://blueshoe.de/img/blogs/nuxt-embla-slider.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Optimale Nutzung von TailwindCSS in Nuxt 3]]></title>
            <link>https://blueshoe.de/blog/nuxt3-tailwindcss-best-practices</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/nuxt3-tailwindcss-best-practices</guid>
            <pubDate>Mon, 14 Jul 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>TailwindCSS ist eine leistungsstarke Lösung für das Styling in Nuxt 3-Projekten. Erfahre, wie du es optimal integrierst, Design-Systeme erstellst und die Performance deiner Nuxt-App maximierst. Wir zeigen dir Best Practices für eine effiziente Nutzung.</p>
<p>In diesem Artikel erklären wir, wie du TailwindCSS optimal in dein Nuxt 3-Projekt integrierst, welche Performance-Optimierungen du nutzen kannst und wie du ein skalierbares Design-System aufbaust.</p>
<p><img src="/img/blog/tailwind-nuxt3.svg" alt="Optimale Nutzung von TailwindCSS in Nuxt 3"></p>
<p>::GlobalBlogLevelInfo</p>
<ul>
<li><a href="https://tailwindcss.com/">TailwindCSS</a>{target="_blank"} und <a href="https://tailwindcss.nuxtjs.org/">Tailwind CSS für Nuxt</a>{target="_blank"}</li>
<li><a href="https://nuxt.com/">Nuxt in Version 3</a>{target="_blank"}</li>
<li>Saubere Vue.js Komponenten</li>
</ul>
<p>Solltest du Fragen haben, oder dir etwas unklar sein, kannst du die Kommentarfunktion unter dem Artikel nutzen.
::</p>
<h2>Einleitung: TailwindCSS meets Nuxt 3</h2>
<h3>Was ist TailwindCSS?</h3>
<p>TailwindCSS ist ein Utility-First CSS-Framework, das einen völlig anderen Ansatz als traditionelle CSS-Frameworks verfolgt. Statt vordefinierter Komponenten bietet es eine umfangreiche Sammlung von Utility-Klassen, die direkt im HTML verwendet werden können. Diese Klassen ermöglichen es, Designs direkt im Markup zu erstellen, ohne zwischen HTML und CSS-Dateien wechseln zu müssen.</p>
<h3>Warum TailwindCSS mit Nuxt 3?</h3>
<p>Die Kombination aus Nuxt 3 und TailwindCSS bietet eine leistungsstarke Grundlage für moderne Webanwendungen. Nuxt 3's Server-Side Rendering (SSR) und Static Site Generation (SSG) Fähigkeiten harmonieren perfekt mit TailwindCSS's Utility-First-Ansatz.</p>
<h4>Vorteile der Kombination:</h4>
<ol>
<li>
<p><strong>Schnelle Entwicklung</strong></p>
<ul>
<li>Direktes Styling im Template</li>
<li>Kein Kontextwechsel zwischen Dateien</li>
<li>Konsistente Design-Sprache</li>
</ul>
</li>
<li>
<p><strong>Optimierte Performance</strong></p>
<ul>
<li>Automatische PurgeCSS-Integration</li>
<li>Minimale Bundle-Größe</li>
<li>Effizientes Caching</li>
</ul>
</li>
<li>
<p><strong>Flexibilität</strong></p>
<ul>
<li>Einfache Anpassung an Design-Systeme</li>
<li>Responsive Design ohne Media Queries</li>
<li>Dark Mode Support</li>
</ul>
</li>
<li>
<p><strong>Developer Experience</strong></p>
<ul>
<li>Intuitive API</li>
<li>Gute IDE-Unterstützung</li>
<li>Umfangreiche Dokumentation</li>
</ul>
</li>
</ol>
<h4>Nachteile und Herausforderungen:</h4>
<ol>
<li>
<p><strong>Lernkurve</strong></p>
<ul>
<li>Neue Denkweise beim Styling</li>
<li>Viele Utility-Klassen zu lernen</li>
<li>Komplexere Templates</li>
</ul>
</li>
<li>
<p><strong>Team-Koordination</strong></p>
<ul>
<li>Konsistente Namenskonventionen nötig</li>
<li>Code-Reviews erfordern Tailwind-Kenntnisse</li>
<li>Potenzielle Template-Komplexität</li>
</ul>
</li>
<li>
<p><strong>Build-Zeit</strong></p>
<ul>
<li>Längere Build-Zeiten bei großen Projekten</li>
<li>Höherer Speicherverbrauch während der Entwicklung</li>
<li>Komplexere PostCSS-Konfiguration</li>
</ul>
</li>
</ol>
<p>::GlobalCallAnExpert{:bg="bg-bs-blue"}
#buttonBlockTitle
Wir können auch Deine Nuxt und Tailwind Projekte optimieren.
::</p>
<h3>Entwicklungsumgebung einrichten</h3>
<p>Bevor wir mit der Installation beginnen, sollten wir die Entwicklungsumgebung optimal einrichten. Hier sind die wichtigsten Tools und Einstellungen für VSCode:</p>
<h4>1. VSCode Extensions</h4>
<p>Installiere folgende Extensions für die beste Developer Experience:</p>
<ul>
<li><a href="https://marketplace.cursorapi.com/items?itemName=bradlc.vscode-tailwindcss"><strong>Tailwind CSS IntelliSense</strong></a>{target="_blank"}
<ul>
<li>Autocomplete für Tailwind-Klassen</li>
<li>Hover-Vorschau</li>
<li>Syntax-Highlighting</li>
</ul>
</li>
</ul>
<h4>2. VSCode Einstellungen</h4>
<p>Füge diese Einstellungen zu deiner <code>settings.json</code> hinzu:</p>
<pre><code class="language-json">{
  "editor.quickSuggestions": {
    "strings": true
  },
  "files.associations": {
    "*.css": "tailwindcss"
  },
}
</code></pre>
<h4>3. Debug-Tools</h4>
<p>Für die Entwicklung empfehlen wir folgende Tools:</p>
<ul>
<li>
<p><strong>Tailwind CSS Debug Tools</strong></p>
<ul>
<li>Browser-DevTools für Tailwind</li>
<li>Komponenten-Inspektion</li>
<li>Responsive Design Testing</li>
</ul>
</li>
<li>
<p><strong>Nuxt DevTools</strong></p>
<ul>
<li>Performance-Monitoring</li>
<li>Komponenten-Hierarchie</li>
<li>State-Management</li>
</ul>
</li>
</ul>
<h3>Installation und Grundkonfiguration</h3>
<p>Die Integration von TailwindCSS in Nuxt 3 ist dank des offiziellen Moduls @nuxtjs/tailwindcss sehr einfach. Das Modul kümmert sich automatisch um die grundlegende Konfiguration und optimiert TailwindCSS für die Nutzung mit Nuxt 3.</p>
<h4>1. Voraussetzungen</h4>
<p>Stelle sicher, dass du ein funktionierendes <a href="https://nuxt.com/docs/getting-started/installation">Nuxt 3-Projekt</a>{target="_blank"} hast:</p>
<pre><code class="language-bash"># Neues Nuxt 3 Projekt erstellen
npm create nuxt &#x3C;project-name>
cd &#x3C;project-name>

# Dependencies installieren
npm install
</code></pre>
<h4>2. Installation</h4>
<p>Die Installation erfolgt mit einem einzigen Befehl:</p>
<pre><code class="language-bash">npx nuxi@latest module add tailwindcss
</code></pre>
<p>Alternativ kannst du auch npm oder yarn verwenden:</p>
<pre><code class="language-bash"># Mit npm
npm install -D @nuxtjs/tailwindcss

# Mit yarn
yarn add -D @nuxtjs/tailwindcss
</code></pre>
<h4>3. Nuxt-Konfiguration</h4>
<p>Nach der Installation wird das Modul automatisch in deiner <code>nuxt.config.ts</code> aktiviert. Du kannst die Konfiguration bei Bedarf anpassen:</p>
<pre><code class="language-typescript">// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxtjs/tailwindcss']
})
</code></pre>
<h4>4. Tailwind-Konfiguration</h4>
<p>Das Modul sucht automatisch nach folgenden Dateien:</p>
<ul>
<li><code>./assets/css/tailwind.css</code></li>
<li><code>./tailwind.config.{js,cjs,mjs,ts}</code></li>
</ul>
<p>Falls diese Dateien nicht existieren, werden sie automatisch mit einer Basis-Konfiguration erstellt. Du kannst die Konfiguration auch manuell anpassen:</p>
<pre><code class="language-typescript">// tailwind.config.ts
import type { Config } from 'tailwindcss'

export default {
  content: [
    './components/**/*.{js,vue,ts}',
    './layouts/**/*.vue',
    './pages/**/*.vue',
    './plugins/**/*.{js,ts}',
    './app.vue',
  ],
  theme: {
    extend: {
      colors: {
        'primary': {
          50: '#f0f9ff',
          100: '#e0f2fe',
          // ... weitere Abstufungen
          900: '#0c4a6e',
        }
      },
      fontFamily: {
        sans: ['Inter var', 'sans-serif'],
      }
    }
  },
  plugins: [
    require('@tailwindcss/typography'),
  ],
} satisfies Config
</code></pre>
<h4>5. CSS-Direktiven</h4>
<p>In deiner <code>assets/css/tailwind.css</code> Datei sollten die folgenden Direktiven enthalten sein:</p>
<pre><code class="language-css">@tailwind base;
@tailwind components;
@tailwind utilities;

</code></pre>
<h4>6. Entwicklung starten</h4>
<p>Starte den Entwicklungsserver und überprüfe die Installation:</p>
<pre><code class="language-bash">npm run dev
# oder
yarn dev
</code></pre>
<p>Öffne die Tailwind-Viewer unter <code>http://localhost:3000/_tailwind/</code> um deine Konfiguration zu überprüfen.</p>
<h2>Vom Utility-Chaos zum skalierbaren Design-System</h2>
<p>Die Sorge ist verständlich: Führt ein Utility-First-Ansatz nicht zwangsläufig zu überladenen, unleserlichen HTML-Templates? Die Antwort ist ein klares Jein. Ohne Struktur kann das passieren. Mit der richtigen Strategie wird TailwindCSS jedoch zur Grundlage für ein robustes und <em>skalierbares Design-System</em>.</p>
<p>Ein solches System ist der Schlüssel für nachhaltige Projekte. Es sorgt dafür, dass das Design konsistent bleibt, auch wenn das Projekt wächst und neue Entwickler an Bord kommen. Änderungen sind zentral an einer Stelle möglich, statt an hunderten von Stellen im Code.</p>
<p>Lass uns ansehen, wie du das mit TailwindCSS in Nuxt 3 praktisch umsetzt. Die Strategie ruht auf zwei Säulen.</p>
<h3>1. Die <code>tailwind.config.ts</code> als dein Fundament</h3>
<p>Betrachte die <code>tailwind.config.js</code> als die "Single Source of Truth" für dein Design. Hier definierst du alle grundlegenden visuellen Aspekte deiner Anwendung – die sogenannten Design-Tokens.</p>
<p>Anstatt hartkodierte Werte wie <code>#3b82f6</code> im Code zu verstreuen, gibst du ihnen einen semantischen Namen. Das macht deine Absicht klarer und die Wartung zum Kinderspiel.</p>
<p>Schau dir diese erweiterte Konfiguration an:</p>
<pre><code class="language-typescript">// tailwind.config.ts
import type { Config } from 'tailwindcss'
import defaultTheme from 'tailwindcss/defaultTheme'

export default {
  content: [
    // ... deine Content-Pfade
  ],
  theme: {
    extend: {
      colors: {
        'primary': {
          '50': '#eff6ff',
          '100': '#dbeafe',
          '200': '#bfdbfe',
          '300': '#93c5fd',
          '400': '#60a5fa',
          '500': '#3b82f6', // Brand-Blau
          '600': '#2563eb',
          '700': '#1d4ed8',
          '800': '#1e40af',
          '900': '#1e3a8a',
          '950': '#172554',
        },
        'accent': {
            '500': '#f59e0b' // Ein Akzent-Gelb
        }
      },
      fontFamily: {
        sans: ['Inter var', ...defaultTheme.fontFamily.sans],
      },
      spacing: {
        '128': '32rem', // Eigene Abstands-Einheit
      }
    }
  },
  plugins: [
    require('@tailwindcss/typography'),
  ],
} satisfies Config
</code></pre>
<p>Jetzt verwendest du im Code nicht mehr <code>text-blue-500</code>, sondern <code>text-primary-500</code>. Wenn sich deine Markenfarbe ändert, musst du sie nur an <em>einer einzigen Stelle</em> in der Konfiguration anpassen. Alle Vorkommen werden automatisch aktualisiert.</p>
<h3>2. Vue-Komponenten als wiederverwendbare Bausteine</h3>
<p>Nachdem du die Design-Tokens als Fundament gelegt hast, kommt die zweite Säule: Abstraktion durch Komponenten. Statt lange und sich wiederholende Klassenlisten im Markup zu verwenden, kapselst du sie in wiederverwendbaren Vue-Komponenten.</p>
<p>Nehmen wir das Beispiel eines einfachen Buttons. Ohne Komponente könnte dein Code so aussehen:</p>
<pre><code class="language-html">&#x3C;!-- Überall in deiner Anwendung verstreut -->
&#x3C;button class="inline-flex items-center justify-center rounded-md px-4 py-2 text-base font-medium text-white transition-colors bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2">
  Aktion ausführen
&#x3C;/button>
</code></pre>
<p>Das ist nicht nur unübersichtlich, sondern auch ein Albtraum bei Änderungen.</p>
<p>Die Lösung ist, eine <code>Button.vue</code>-Komponente zu erstellen, wie sie im nächsten Abschnitt detailliert beschrieben wird. Die Verwendung reduziert sich dann auf eine saubere, lesbare Zeile:</p>
<pre><code class="language-html">&#x3C;Button variant="primary">Aktion ausführen&#x3C;/Button>
</code></pre>
<p>Der Mehrwert ist enorm:</p>
<ul>
<li><strong>Lesbarkeit:</strong> Der Code drückt die <em>Absicht</em> aus, nicht die Implementierungsdetails.</li>
<li><strong>Wartbarkeit:</strong> Das Button-Styling wird nur noch in der <code>Button.vue</code> geändert.</li>
<li><strong>Konsistenz:</strong> Alle Buttons sehen garantiert gleich aus.</li>
</ul>
<p>Wenn du diese beiden Prinzipien – eine zentrale Konfiguration für die Design-Tokens und die Kapselung von UI-Logik in Komponenten – konsequent anwendest, baust du ein System, das mit deinem Projekt wachsen kann. Es bleibt wartbar, konsistent und macht die Einarbeitung für neue Teammitglieder deutlich einfacher.</p>
<h2>Best Practices für Komponenten</h2>
<h3>1. Komponenten-Struktur</h3>
<p>Hier ein Beispiel für eine optimierte Button-Komponente mit TailwindCSS:</p>
<pre><code class="language-vue">&#x3C;!-- components/Button.vue -->
&#x3C;script setup lang="ts">
interface Props {
  variant?: 'primary' | 'secondary' | 'outline'
  size?: 'sm' | 'md' | 'lg'
}

const props = withDefaults(defineProps&#x3C;Props>(), {
  variant: 'primary',
  size: 'md'
})

const buttonClasses = computed(() => {
  const baseClasses = 'inline-flex items-center justify-center rounded-md font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2'
  
  const variants = {
    primary: 'bg-primary-600 text-white hover:bg-primary-700 focus:ring-primary-500',
    secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200 focus:ring-gray-500',
    outline: 'border border-gray-300 bg-white text-gray-700 hover:bg-gray-50 focus:ring-primary-500'
  }
  
  const sizes = {
    sm: 'px-3 py-1.5 text-sm',
    md: 'px-4 py-2 text-base',
    lg: 'px-6 py-3 text-lg'
  }
  
  return `${baseClasses} ${variants[props.variant]} ${sizes[props.size]}`
})
&#x3C;/script>

&#x3C;template>
  &#x3C;button :class="buttonClasses">
    &#x3C;slot />
  &#x3C;/button>
&#x3C;/template>
</code></pre>
<h3>2. Dark Mode Support</h3>
<p>TailwindCSS bietet eine elegante Lösung für die Implementierung eines Dark Modes in Nuxt 3-Anwendungen. Der Dark Mode kann entweder systemabhängig oder manuell gesteuert werden. Hier erklären wir beide Ansätze:</p>
<h4>Verwendung in Komponenten</h4>
<p>Um den Dark Mode in deinen Komponenten zu nutzen, kannst du die <code>dark:</code> Variante von Tailwind-Klassen verwenden. Hier ein Beispiel für eine Karte-Komponente:</p>
<pre><code class="language-vue">&#x3C;template>
  &#x3C;div class="bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 p-4 rounded-lg shadow">
    &#x3C;h2 class="text-xl font-bold mb-2">Titel&#x3C;/h2>
    &#x3C;p class="text-gray-600 dark:text-gray-300">
      Dieser Text passt sich automatisch dem Dark Mode an.
    &#x3C;/p>
  &#x3C;/div>
&#x3C;/template>
</code></pre>
<h4>Best Practices für Dark Mode</h4>
<ol>
<li>
<p><strong>Konsistente Farbpalette</strong></p>
<ul>
<li>Definiere eine klare Farbpalette für beide Modi</li>
<li>Nutze semantische Farbnamen (z.B. <code>primary</code>, <code>secondary</code>)</li>
<li>Berücksichtige Kontrastverhältnisse</li>
</ul>
</li>
<li>
<p><strong>Performance</strong></p>
<ul>
<li>Vermeide unnötige Reflows beim Theme-Wechsel</li>
<li>Nutze CSS-Variablen für dynamische Werte</li>
<li>Implementiere Smooth Transitions</li>
</ul>
</li>
<li>
<p><strong>Zugänglichkeit</strong></p>
<ul>
<li>Stelle sicher, dass der Kontrast in beiden Modi ausreichend ist</li>
<li>Teste die Lesbarkeit aller Texte</li>
<li>Berücksichtige Farbenblindheit</li>
</ul>
</li>
<li>
<p><strong>Persistenz</strong></p>
<ul>
<li>Speichere die Benutzerpräferenz</li>
<li>Respektiere System-Einstellungen als Fallback</li>
<li>Implementiere einen sanften Übergang</li>
</ul>
</li>
</ol>
<p>In diesem Fall wird der Dark Mode automatisch aktiviert, wenn das Betriebssystem im Dark Mode ist.</p>
<h2>Performance-Optimierungen für TailwindCSS in Nuxt 3</h2>
<p>TailwindCSS ist nicht nur flexibel, sondern auch leistungsstark – aber ohne Optimierungen kann es deine Bundle-Größe aufblasen. Nuxt 3 und Tailwind bieten hier smarte Features, die du nutzen solltest, um Ladezeiten zu minimieren und die App flüssig zu halten.</p>
<h3>1. PurgeCSS und Just-in-Time (JIT) Mode</h3>
<p>Der JIT-Compiler von Tailwind generiert CSS nur für die Klassen, die du tatsächlich verwendest. In Nuxt 3 ist das standardmäßig aktiviert. Um es zu optimieren, stelle sicher, dass deine <code>tailwind.config.ts</code> alle relevanten Dateien im <code>content</code>-Array abdeckt:</p>
<pre><code class="language-typescript">// tailwind.config.ts
export default {
  content: [
    './components/**/*.{vue,js,ts}',
    './layouts/**/*.vue',
    './pages/**/*.vue',
    './plugins/**/*.{js,ts}',
    './nuxt.config.{js,ts}',
    './app.vue',
  ],
  // ...
}
</code></pre>
<p>Das reduziert ungenutztes CSS automatisch beim Build. Optional, für manuelle Tests oder Debugging:</p>
<pre><code class="language-bash">npx tailwindcss -i assets/css/tailwind.css -o .output/public/_nuxt/tailwind.css --minify
</code></pre>
<h3>2. Caching und Lazy Loading</h3>
<p>In großen Projekten kann der Build-Prozess langsamer werden. Aktiviere Caching in Nuxt, indem du in <code>nuxt.config.ts</code> folgendes hinzufügst:</p>
<pre><code class="language-typescript">export default defineNuxtConfig({
  // ...
  nitro: {
    compressPublicAssets: true,
  },
})
</code></pre>
<p>Für dynamische Komponenten lade Tailwind-Styles lazy, z.B. mit Nuxt's <code>&#x3C;NuxtLazyHydrate></code>.</p>
<h3>3. Bundle-Analyse</h3>
<p>Verwende Tools wie</p>
<pre><code class="language-bash">nuxt analyze
</code></pre>
<p>oder den Webpack Bundle Analyzer, um zu sehen, wie viel Tailwind zu deiner Bundle-Größe beiträgt. Ziel: Halte das finale CSS unter 10-20 KB.</p>
<p>Durch diese Maßnahmen sparst du Bandbreite und verbesserst die Core Web Vitals deiner App.</p>
<h2>Häufige Fehler und wie du sie vermeidest</h2>
<p>Selbst erfahrene Entwickler stolpern manchmal über Tailwind in Nuxt. Hier sind gängige Fallstricke und wie du sie umgehst:</p>
<h3>1. Überladene Templates</h3>
<p><strong>Fehler:</strong> Zu viele Utility-Klassen direkt im HTML machen den Code unlesbar.</p>
<p><strong>Lösung:</strong> Wie im Abschnitt zu Komponenten beschrieben, kapsle sie in Vue-Komponenten. Ergänze Props für Flexibilität, ohne den Markup zu komplizieren.</p>
<h3>2. Inkonsistente Konfiguration</h3>
<p><strong>Fehler:</strong> Vergessene Pfade im <code>content</code>-Array führen dazu, dass Klassen nicht generiert werden.</p>
<p><strong>Lösung:</strong> Überprüfe mit dem Tailwind-Viewer (<code>/_tailwind/</code>) und erweitere das Array bei Bedarf.</p>
<h3>3. Performance-Probleme im Development</h3>
<p><strong>Fehler:</strong> Langsame Builds durch JIT in großen Projekten.</p>
<p><strong>Lösung:</strong> Setze <code>mode: 'jit'</code> nur für Production und nutze <code>tailwindcss --watch</code> für schnelle Entwicklung.</p>
<h3>4. Zugänglichkeitsfallen</h3>
<p><strong>Fehler:</strong> Vergessene Fokus-Styles oder unzureichender Kontrast.</p>
<p><strong>Lösung:</strong> Integriere immer <code>focus:</code>-Varianten und teste mit Tools wie Lighthouse.</p>
<p>Indem du diese Fehler vermeidest, sparst du Zeit und Frust im Projektverlauf.</p>
<h2>FAQ</h2>
<h3>Wie integriere ich TailwindCSS in ein bestehendes Nuxt 3-Projekt?</h3>
<p>Die Integration von TailwindCSS in ein bestehendes Nuxt 3-Projekt ist einfach. Installieren Sie das offizielle Nuxt-Modul und konfigurieren Sie es in Ihrer nuxt.config.ts. Detaillierte Anweisungen finden Sie in der <a href="https://tailwindcss.nuxtjs.org/getting-started/installation">offiziellen Dokumentation</a>.</p>
<h3>Welche Performance-Optimierungen bietet TailwindCSS in Nuxt 3?</h3>
<p>TailwindCSS in Nuxt 3 bietet Optimierungen wie PurgeCSS-Integration, JIT-Compiler, Caching und Bundle-Analyse. Diese Features reduzieren die Bundle-Größe und verbessern Ladezeiten. Mehr Details finden Sie im Abschnitt zu Performance-Optimierungen.</p>
<h3>Wie erstelle ich ein skalierbares Design-System mit TailwindCSS in Nuxt 3?</h3>
<p>Durch zentrale Konfiguration in tailwind.config.ts und wiederverwendbare Vue-Komponenten bauen Sie ein skalierbares System auf. Definieren Sie Design-Tokens und kapseln Sie Styles in Komponenten für Konsistenz und Wartbarkeit.</p>
<h3>Welche Vorteile bietet die Kombination von TailwindCSS mit Nuxt 3?</h3>
<p>Die Kombination bietet schnelle Entwicklung, optimierte Performance, konsistente Designs und hohe Wartbarkeit. Sie ermöglicht moderne, performante Webanwendungen mit Utility-First-Styling.</p>
<h3>Wie implementiere ich Dark Mode in TailwindCSS mit Nuxt 3?</h3>
<p>Aktivieren Sie Dark Mode in der tailwind.config.ts mit darkMode: 'class'. Verwenden Sie 'dark:'-Präfixe in Klassen und berücksichtigen Sie Best Practices für Konsistenz und Zugänglichkeit.</p>
<h3>Welche häufigen Fehler sollte ich bei TailwindCSS in Nuxt 3 vermeiden?</h3>
<p>Vermeiden Sie überladene Templates, inkonsistente Konfigurationen, Performance-Probleme und Zugänglichkeitsfallen. Kapseln Sie Styles in Komponenten und testen Sie regelmäßig.</p>]]></content:encoded>
            <category>Nuxt</category>
            <category>Tailwind CSS</category>
            <category>Vue.js</category>
            <category>Entwicklung</category>
            <enclosure url="https://blueshoe.de/img/blog/tailwind-nuxt3.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Leistungsvergleich: GKE vs. EKS]]></title>
            <link>https://blueshoe.de/blog/performance-vergleich-gke-vs-eks</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/performance-vergleich-gke-vs-eks</guid>
            <pubDate>Thu, 09 Feb 2023 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Die solide Leistung von verwalteten Kubernetes-Plattformen wird im Allgemeinen als selbstverständlich angesehen und wird kaum jemals in Frage gestellt. Allerdings könnte es Unterschiede in der Performance von Containern auf verschiedenen beliebten verwalteten Kubernetes-Plattformen geben. Ich wollte einen genaueren Blick darauf werfen und habe die beiden beliebtesten Kubernetes-Dienste ausgewählt, die wir bei Blueshoe für unsere Kunden nutzen: Amazon Elastic Kubernetes Service (EKS) und Google Kubernetes Engine (GKE).</p>
<p><img src="/img/blogs/performance-comparison-gke-vs-eks.jpg" alt="Leistungsvergleich: GKE vs. EKS">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalTitle{:size="lg" .mb-5}
EKS vs. GKE – und warum ist das wichtig?
:::
:::globalParagraph
Laut dieser Statistik von Februar 2020 haben 540 Befragte auf die Frage "Welche der folgenden Container-Orchestratoren verwenden Sie?" geantwortet:
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>37% aller Befragten verwenden EKS</li>
<li>21% der Befragten verwenden GKE
:::
:::globalParagraph
Bitte beachte, dass die Auswahl mehrerer Antworten möglich war, daher sind die Gruppen nicht exklusiv. Die Zahlen haben sich wahrscheinlich seitdem etwas geändert, aber es ist offensichtlich, dass diese beiden sehr beliebte Optionen in der Welt des verwalteten Kubernetes sind. Die Zahlen entsprechen auch der Verteilung der von Blueshoe verwalteten Kubernetes-Plattformen bis heute.
:::
:::globalParagraph
Natürlich sollten wir die Analyse der Container-Runtime-Leistung mit diesen beiden Lösungen beginnen.
:::</li>
</ul>
<p>:::globalTitle{:size="lg" .mb-5}
Aber warum?
:::
:::globalParagraph
Einerseits ist es einfach interessant festzustellen, wie diese beiden großen Player im Vergleich zueinander abschneiden. Auf der einen Seite hast du Amazon Web Services - den Riesen auf dem Markt der Hyperscaler. Und auf der anderen Seite gibt es Google - den Technologieriesen und Pionier von <a href="/blog/kubernetes-development">Kubernetes</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}
.
:::
:::globalParagraph
Aber noch wichtiger ist, dass es immer auf die Kosten ankommt. Wenn du bei vergleichbarem Preis 10% mehr Leistung bekommen kannst, möchten einige vielleicht die Portabilität von Kubernetes nutzen. Dabei geht es nicht um das Ökosystem oder potenziell angehängte Dienste (wie verwaltete Datenbanken oder Speicher), sondern um die reine Container-Runtime-Leistung. Ich wollte die Frage beantworten: "Mit welcher Geschwindigkeit läuft mein Code in einem ganz normalen Kubernetes-Cluster?". Und das ist, was ich gefunden habe:
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
Das Benchmark-Setup
:::
:::globalParagraph
Auf EKS habe ich einen Kubernetes-Cluster mit folgenden Spezifikationen erstellt:
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>Instanztyp: t3.xlarge</li>
<li>Region: eu-west-1</li>
<li>K8s-Version: 1.23</li>
<li>Betriebssystem: Amazon Linux 2</li>
<li>Container-Runtime: Docker</li>
<li>Node-VM-Preis: 0,1824 USD pro Stunde
:::
:::globalParagraph
Um diese Parameter so genau wie möglich anzupassen, habe ich einen Google Kubernetes Engine-Cluster mit folgenden Spezifikationen erstellt:
:::
:::GlobalBlock{.ul-disk .mb-5}</li>
<li>Instanztyp: e2-standard-4</li>
<li>Region: europe-north1-a</li>
<li>K8s-Version: 1.23.14-gke.401</li>
<li>Betriebssystem: Container-Optimized OS mit containerd (cos_containerd)</li>
<li>Container-Runtime: containerd</li>
<li>Node-VM-Preis: 0,147552 USD pro Stunde
:::
:::globalParagraph
Beide Maschinentypen umfassen eine 4 vCPU-Maschine mit 16 GB RAM, basierend auf einem Intel-Prozessor. Der Kubernetes-Knoten, auf dem der Test ausgeführt wurde, war ausschließlich dem Test-Pod gewidmet und nur mit anderen "Standard"-Pods dieses verwalteten Kubernetes-Angebots gefüllt. Ich habe keine speziellen Konfigurationen verwendet, sondern einfach einen Cluster mit den voreingestellten Werten bestellt.
:::</li>
</ul>
<p>:::globalTitle{:size="lg" .mb-5}
Wie man die Container-Runtime benchmarkt
:::
:::globalParagraph
Eines der Hauptziele bei der Durchführung einer Leistungsanalyse besteht darin, eine sehr einfache Reproduktion zu ermöglichen. Glücklicherweise sprechen wir über Kubernetes, was bedeutet, dass es nur eine Frage des Schreibens von Kubernetes-Konfigurationen und deren Anwendung auf den Cluster ist. Dennoch gibt es einige wichtige Dinge zu beachten:
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>Wähle vergleichbare Kubernetes-Knoteninstanztypen, um den Vergleich so fair wie möglich zu gestalten</li>
<li>Deploye die Benchmark-Workload nicht neben anderen Containern</li>
<li>Verwende die gleiche Kubernetes-Version</li>
<li>Notiere wichtige Unterschiede zwischen den Teilnehmern
:::
:::globalParagraph
Leider war es nicht gerade einfach, ein gutes Benchmark-Tool zu finden, das den Anforderungen entspricht, um die folgenden Teile zu benchmarken:
:::
:::GlobalBlock{.ul-disk .mb-5}</li>
<li>die CPU</li>
<li>den Arbeitsspeicher (RAM)</li>
<li>das Container-Dateisystem (nicht die angehängten Volumes, es geht um das native Dateisystem)
:::</li>
</ul>
<p>:::globalParagraph
Ein recht häufig verwendetes Tool mit nur wenigen bekannten Schwächen ist sysbench. Mit etwa 5.000 Sternen auf GitHub und einer recht großen und aktiven Community könnte es für meine Anforderungen geeignet sein. Ein großer Pluspunkt ist die Erweiterbarkeit und die vielen integrierten komplexen Benchmark-Typen, wie z.B. Datenbank-Benchmarks usw.
:::
:::globalParagraph
Glücklicherweise hat jemand bei Severalnines bereits ein Container-Image für sysbench erstellt und es öffentlich zugänglich gemacht. Das Benchmark-Tool ist also einsatzbereit.
:::
:::globalParagraph
Um diesen Prozess zu vereinfachen und reproduzierbar zu machen, habe ich einen kleinen Test-Runner für sysbench gestartet. Dieses Tool plant das Benchmark im Cluster (mit einem Node-Selector), wartet auf den Abschluss des Jobs, analysiert das Ergebnis und erstellt eine Datei mit den Testergebnissen.
:::
:::globalParagraph
Ich habe den Code hier öffentlich gemacht. Er basiert auf Python und Poetry. Wenn du Poetry installiert hast, kannst du einfach <em>'poetry run benchmark'</em>  ausführen und es wird die Kapazität der CPU, des Arbeitsspeichers und des Dateisystems benchmarken.
:::</p>
<p>:::GlobalPodcastSection{:videoId="tyvE9VlSWkE" :videoPosition="left" .mb-6}
::::GlobalPreTitle{:color="text-bs-green" .mb-3}
UNSER KUBERNETES-PODCAST
::::
::::GlobalTitle{:tag="h3" .mb-6}
TftC E1: Kubernetes-Entwicklungsumgebungen
::::
::::globalParagraph{:font-size="lg" .mb-4}
Michael und Robert sprechen ausführlich über die Vor- und Nachteile der lokalen Kubernetes-Entwicklung und geben auch einige echte Codebeispiele.
::::
::::globalParagraph{:font-size="lg" }
Weitere Ausgaben unseres Podcasts findest du hier:
::::
::::GlobalButton{:url="/kubernetes-podcast/" :label="Mehr anzeigen" :color="green"}
::::
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
Die Ergebnisse
:::
:::globalParagraph
Zusammenfassend lässt sich sagen, dass EKS in allen Metriken eine höhere Leistung bietet. Insbesondere die Datei-E/A-Leistung bei GKE ist ehrlich gesagt schlecht. Wir sprechen über 10% weniger Leistung bei der CPU, 9% weniger beim Arbeitsspeicher und eine große Lücke bei den Dateioperationen für einen Standard-Kubernetes-Cluster. Schauen wir uns die Ergebnisse genauer an.
:::</p>
<p>:::globalTitle{:size="md" .mb-5}
CPU-Leistung
:::
:::globalParagraph
Der sysbench-Befehl zum Ausführen des CPU-Tests lautet: sysbench --test=cpu --time=60 run
:::
:::globalParagraph
Dieser Befehl führt den CPU-Benchmark eine Minute lang aus.
:::</p>
<p>:::globalTitle{:size="md" .mb-5}
1) GKE vs. EKS: CPU-Ereignisse pro Sekunde
:::
:::globalParagraph
Sysbench erfasst die ausgeführten Schleifen (auch Ereignisse genannt) und berechnet alle Primzahlen bis zu einem bestimmten Parameter in einem bestimmten Zeitrahmen. Es zeigt an, wie viel CPU-Zeit dem Prozess gewährt wurde und wie schnell die Berechnung im Allgemeinen war.
:::
<img src="/img/blogs/performance-comparison-gke-vs-eks-1.jpg" alt="kubernetes">{.object-cover .w-max-full .mb-5}
:::globalParagraph
Das Ergebnis zeigt einen schockierenden Unterschied von etwa 11% mehr Ereignissen auf EKS im Vergleich zu GKE. Da du für die Zeit deines Kubernetes-Knotens bezahlst, ist es entscheidend, dass in dieser Zeit möglichst viele Berechnungen durchgeführt werden.
:::</p>
<p>:::globalTitle{:size="md" .mb-5}
2) GKE vs. EKS: CPU-Latenz
:::
:::globalParagraph
Sysbench erfasst die CPU-Latenz für ein angefordertes Ereignis. Es aggregiert die Ergebnisse und gibt die minimalen, maximalen, durchschnittlichen und 95. Perzentil-Werte zurück.
:::
<img src="/img/blogs/performance-comparison-gke-vs-eks-2.jpg" alt="kubernetes">{.object-cover .w-max-full .mb-5}</p>
<p>:::globalParagraph
Wie du sehen kannst, ist die Leistung des containerd-basierten Runtimes von GKE nicht signifikant langsamer als der docker-basierte Runtime von EKS. Dennoch beträgt der Unterschied im 95. Perzentil etwa 2%. Dies kann auf die relativ kurze Laufzeit des Benchmarks und andere Faktoren zurückzuführen sein.
:::</p>
<p>:::globalTitle{:size="md" .mb-5}
Speicherleistung
:::
:::globalParagraph
Der sysbench-Befehl zum Ausführen des Speichertests (RAM) lautet: sysbench --test=memory --memory-total-size=500G run
:::
:::globalParagraph
Dieser Befehl schreibt 500 Gigabyte in den Hauptspeicher und erfasst die Schreibgeschwindigkeit.
:::
<img src="/img/blogs/performance-comparison-gke-vs-eks-3.jpg" alt="kubernetes">{.object-cover .w-max-full .mb-5}</p>
<p>:::globalParagraph
Auch hier ist GKE etwa 9% langsamer als der Container-Runtime von EKS, wenn es darum geht, viel in den Hauptspeicher zu schreiben. Auf einem EKS-Cluster kann dein Code potenziell mit 4,25 Gigabyte pro Sekunde in den RAM schreiben, während auf GKE dein Container nur mit 3,87 Gigabyte pro Sekunde schaufeln kann. Im Vergleich dazu läuft mein Laptop mit etwa 6,36 Gigabyte pro Sekunde. Daher ist keines der Ergebnisse überwältigend.
:::</p>
<p>:::globalTitle{:size="md" .mb-5}
GKE vs EKS: Datei-E/A-Leistung
:::
:::globalParagraph
Die Ergebnisse zur Dateisystemleistung zeichnen ein besonders dramatisches Bild. Der sysbench-Befehl zum Ausführen des Dateitests lautet:
:::</p>
<p>:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>sysbench --test=fileio --file-num=5 --file-total-size=5G prepare</li>
<li>sysbench --test=fileio --file-total-size=5G --file-num=5 --file-test-mode=rndrw --time=100 --max-requests=0 run
:::</li>
</ul>
<p>:::globalTitle{:size="md" .mb-5}
1) GKE vs EKS: Dateidurchsatz
:::
:::globalParagraph
Der Dateidurchsatz-Benchmark schreibt einfach eine Datei in das Dateisystem und liest eine künstliche Datei aus dem Dateisystem.
:::
<img src="/img/blogs/performance-comparison-gke-vs-eks-4.jpg" alt="kubernetes">{.object-cover .w-max-full .mb-5}</p>
<p>:::globalParagraph
Die Schreib- und Leseleistung eines Containers, der auf EKS läuft, ist bei Lesevorgängen etwa 95% besser und bei Schreibvorgängen etwa 94% besser. Diese Metrik könnte relevant werden, wenn eine Anwendung Dateien aus dem temporären Speicher im Container schreibt und liest.
:::</p>
<p>:::globalTitle{:size="md" .mb-5}
2) Datei-E/A-Latenz
:::
<img src="/img/blogs/performance-comparison-gke-vs-eks-5.jpg" alt="kubernetes">{.object-cover .w-max-full .mb-5}</p>
<p>:::globalParagraph
Die Dateilatenz ist für beide Plattformen nahezu gleich. Persönlich würde ich der maximalen Latenz nicht allzu viel Bedeutung beimessen (diese kann von Lauf zu Lauf stark variieren), sondern eher das 95. Perzentil betrachten. Mit dieser Metrik übertrifft EKS GKE um eine Größenordnung.
:::</p>
<p>:::globalTitle{:size="md" .mb-5}
3) Dateioperationen pro Sekunde
:::
<img src="/img/blogs/performance-comparison-gke-vs-eks-6.jpg" alt="kubernetes">{.object-cover .w-max-full .mb-5}</p>
<p>:::globalParagraph
Die geringe Anzahl von Dateioperationen pro Sekunde auf GKE ist nur eine Folge der vorherigen Ergebnisse. Bitte beachte, dass diese Bewertungen zur Dateisystemleistung auf dem nativen Dateisystem des Containers ausgeführt werden. Es ist keine zusätzliche Storage Class an den Pod angehängt, auf dem der Benchmark läuft.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
Abschließende Bemerkungen
:::</p>
<p>:::globalParagraph
Ich war etwas schockiert über die Ergebnisse nach dem Vergleich dieser beiden verwalteten Kubernetes-Plattformen und ihrer Container-Runtime-Leistung. Wie du sehen kannst, ist der Preis des GKE-Knotens in diesen Regionen etwa 22% niedriger als der entsprechende Preis bei AWS EKS. Das gleicht zumindest ein wenig den Unterschied in der Leistung aus, aber diese Fakten können die Entscheidung beeinflussen, wo eine containerisierte Workload in Zukunft platziert werden soll.
:::
:::globalParagraph
Beim Versuch, die Ergebnisse zu verstehen, stieß ich auf das Nitro-System von Amazon, eine Hardware-Technologie, die Amazon Web Service für ihre eigene Cloud-Computing-Plattform entwickelt hat. Sind diese Ergebnisse ein Beweis für die versprochenen Leistungsgewinne? Spielt der docker-basierte Container-Runtime von AWS dabei eine Rolle?
:::
:::globalParagraph
Bei Blueshoe arbeiten wir gerne mit der Google Cloud Platform, da wir sie im Allgemeinen als benutzerfreundlicher und übersichtlicher im Vergleich zur AWS-Konsole betrachten. Leistungsüberlegungen sind in der Tat sehr wichtig, aber es gibt auch andere wesentliche Kriterien bei der Auswahl eines verwalteten Kubernetes-Angebots. Bitte nimm diesen Benchmark auch mit einer Prise Salz, da es viele Konfigurationen gibt, die einen großen Einfluss auf die Gesamtsystemleistung haben können.
:::
:::globalParagraph
Fühle dich frei, folge mir auf LinkedIn oder trete unserem Discord bei.
:::</p>]]></content:encoded>
            <category>Kubernetes</category>
            <category>Betrieb</category>
            <enclosure url="https://blueshoe.de/img/blogs/performance-comparison-gke-vs-eks.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[PHP oder Python? Wir vergleichen!]]></title>
            <link>https://blueshoe.de/blog/php-oder-python</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/php-oder-python</guid>
            <pubDate>Mon, 30 Jan 2017 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Warum setzt Blueshoe auf Django und Python, wenn doch PHP viel verbreiteter ist? Wir haben unsere Gründe und halten diese auch sicher nicht geheim. In diesem Artikel vergleichen wir Python und Django mit PHP.</p>
<p><img src="/img/blogs/luca-bravo-XJXWbfSo2f0.jpg" alt="luca-bravo">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalTitle{:size="lg" .mb-5}
WARUM SETZT BLUESHOE AUF DJANGO?
:::
:::globalParagraph
In vielen unserer Projekte setzen wir Django ein. Insbesondere, wenn wir uns einem neuen, potentiellen Kunden vorstellen, fragt dieser: „Warum setzt <a href="/team/">Blueshoe</a>{.bs-link-blue} auf Django? Ihre Mitbewerber setzen vorrangig auf PHP Systeme wie Wordpress, Drupal bzw. Magento, Shopware oder WooCommerce.“
:::
:::globalParagraph
An dieser Stelle muss ich sagen, dass der Vergleich zwischen PHP-Systemen, wie den oben genannten, und Django nicht ganz fair ist.
:::
:::globalParagraph
PHP ist eine Programmiersprache, Django ein Web Framework, Wordpress und Drupal sind CMS Systeme, Magento und WooCommerce sind E-Commerce-Systeme.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
PHP IST EINE KRUX
:::
:::globalParagraph
PHP ist eine Programmiersprache mit weiter Verbreitung. Es gibt viele Quellen und viele Projekte. Jedoch entfernt sich die moderne Welt der Webentwicklung langsam aber sicher von PHP:
:::
:::globalParagraph
Suchanfragen für PHP:
:::</p>
<p><img src="/img/blogs/php-blog-graph.jpg" alt="php_blog_graph">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Suchanfragen für das weit verbreitete Zend Framework (PHP basiert):
:::</p>
<p><img src="/img/blogs/zend.jpg" alt="zend">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Jeff Atwood beschreibt PHP in seinem Artikel „The PHP Singularity“[1]: „PHP isn't so much a language as a random collection of arbitrary stuff, a virtual explosion at the keyword and function factory.“
:::
:::globalParagraph
Folgt man Jeff Atwood’s Artikel, wird schnell klar: PHP ist eine schrecklich schlecht designte Programmiersprache. Warum gibt es dann so viele Projekte, welche auf PHP basieren? Dafür könnte es folgende Gründe geben:
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>PHP war frühzeitig (PHP 1 erschien 1995)[2] eine Möglichkeit serverseitige Logik zu implementieren.</li>
<li>PHP ist billig. Nahezu jeder Webspace-Anbieter erlaubt es PHP auszuführen. Das Deployment ist simpel, simpler geht es nicht. Einfach die Dateien auf den Server laden und schon läuft’s.</li>
<li>Aufgrund der früh wachsenden Nachfrage sind natürlich viele Jobs im PHP Umfeld entstanden, was zur Folge hat, dass auch mehr Projekte in PHP umgesetzt werden
:::
:::globalParagraph
Systeme, die auf dieser Basis aufsetzen, sind auf den ersten Blick billig und einfach zu handhaben. Genauso einfach läuft aber auch das Boot aus dem Ruder: Sicherheitslücken, schlechte Wartbarkeit, schlechte Code Qualität[3]. Schlechte Wartbarkeit ist bekanntlich teuer, da die Einarbeitungszeit in vorhandenen Code für Entwickler in der Regel höher ist oder weil der Entwickler eine so spezielle Kenntnis von dem System hat, dass er mit der Zeit immer mehr Geld verlangen kann. Eine Abhängigkeit des Auftraggebers, ein „Vendor lock-in“, entsteht.
:::</li>
</ul>
<p>:::globalTitle{:size="lg" .mb-5}
PYTHON ALS FUNDAMENT
:::
:::globalParagraph
Saubere Strukturen, eine gute Code-Qualität sowie eine entsprechend leichtgewichtige Wartbarkeit liegen in der Natur von Python. Im Gegensatz zu anderen Programmiersprachen bildet Python seine Struktur durch Einrückung ab. Dies zwingt den Entwickler nicht nur, eine konstante Struktur bei der Einrückung und Strukturierung seines Codes zu verwenden, sondern macht es wesentlich einfacher für andere Entwickler, sich in ein Projekt einzulesen.
:::
:::globalParagraph
Ein kurzes Beispiel:
:::
:::globalTitle{:size="md" .mb-5}
PHP
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-php">for ($i = 1; $i &#x3C; 10; $i++) {
  if $($i % 2 == 0) {
    echo $I;
  }
}
</code></pre>
<p>:::
:::globalTitle{:size="md" .mb-5}
Python
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-py">for i in range(0, 10, 2):
   print i
</code></pre>
<p>:::
:::globalParagraph
Natürlich repräsentiert dieses Beispiel keinen umfassenden Vergleich beider Sprachen. Es soll lediglich eine grobe Vorstellung über die Lesbarkeit beider Sprachen vermitteln.
:::
:::globalParagraph
Weiterhin gibt es mit PEP8[4] eine klare Richtlinie, wie Python Code zu formatieren ist. Unsere Entwicklungsumgebungen bei Blueshoe sind so konfiguriert, dass diese auf Konformität zu PEP8 prüfen.
:::
:::globalParagraph
Schön und gut, wir haben nun gezeigt, dass Python seinen Entwickler zu einer gewissen Strukturierung „erzieht“. Aber können professionelle PHP Entwickler dies nicht auch?
:::
:::globalParagraph
Natürlich gibt es auch PHP Entwickler, welche es schaffen, ihren Code sauber zu strukturieren und lesbar zu halten. In der Praxis sieht man heute leider noch viel zu oft sogenannten Spaghetti Code, wenn man durch PHP Projekte schaut. Python sollte somit auf langfristige Sicht nicht teurer als PHP sein, da die Wartungskosten in der Regel gering gehalten werden können.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
DJANGO – DAS GERÜST UNSERER WEBANWENDUNG
:::
:::globalParagraph
Warum genau setzt Blueshoe nun auf Django und was genau ist Django eigentlich?
:::
:::globalParagraph
Django ist ein Framework zur Entwicklung von Webanwendungen. Damit gibt Django einen gewissen Workflow vor – eine Struktur in der Entwicklung von Webanwendungen. Hält man diese ein, fällt es wiederum neuen Entwicklern leicht, sich in den Code eines neuen Projektes einzulesen. Dies hat nicht nur zur Folge, dass das Projekt besser strukturiert und organisiert ist, sondern auch Zeit eingespart wird, was sich sich wiederum in den Kosten widerspiegelt.
:::
:::globalParagraph
Django bringt von Haus aus verschiedene Sicherheitsmechanismen mit, welche standardmäßig Pflicht bei der Implementierung sind. Beispielsweise Cross-Site Request Forgery (CSRF) wird durch den Einsatz von CSRF-Token unterbunden. Cross-Site Scripting wird ebenfalls von Haus aus von der Django-Template Engine unterbunden. Ein Entwickler muss sich aktiv gegen diesen Schutz für eine Variable entscheiden, um diesen Schutz zu deaktivieren. Unter anderen Angriffsvektoren, welche Django so klein wie nur möglich hält, ist auch SQL Injection. Das Open Web Application Security Project (OWASP) kategorisierte SQL Injection 2013 als Nummer 1 Schwachstelle oder auch den potenziell größten Angriffsvektor.[5]
:::
:::globalParagraph
Django bezeichnet sich selbst als „The web framework for perfectionists with deadlines“[6]. Das Entwicklungs-Team um Django hat sich grundlegende Philosophien für die Entwicklung des Frameworks festgelegt[7]. Diese sind natürlich keine Neuerfindung in der IT-Welt, sondern gelten auch für andere Projekte und Softwaresysteme. Es ist jedoch bemerkenswert, wie strikt diese im Django-Projekt verfolgt und eingehalten werden. Dies wird von der Community sehr gut angenommen und liegt uns bei Blueshoe sehr am Herzen. Die Folgen sind der Traum eines jeden Auftraggebers (von Software):
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>Wiederverwendbare Komponenten = weniger Zeit, geringere Kosten</li>
<li>Einfache Erweiterbarkeit des Systems = Projekt als Grundlage für Weiterentwicklung</li>
<li>Einfachheit, Lesbarkeit = Keine Abhängigkeit vom Auftragnehmer</li>
<li>Up to date Sicherheitsmechanismen (für Webanwendungen)
:::
:::globalParagraph
Gut, Django ist sicher, es ist sehr strukturiert, hat eine sehr gute Wartbarkeit und erlaubt es, in kurzer Zeit viel Funktionalität umzusetzen. Aber wer setzt auf Django? Gibt es Beispiele bekannter Webseiten, welche auf Python/Django setzen?
:::</li>
</ul>
<p>:::globalParagraph
Wer setzt sonst noch auf Python/Django?
:::
:::globalParagraph
Es gibt zahlreiche, darunter sehr bekannte, Webseiten die Python/Django einsetzen. Ein paar hier:
:::</p>
<p><img src="/img/blogs/platforms.jpg" alt="platforms">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalTitle{:size="md" :tag="h3" .mb-5}
Dropbox:
:::
:::globalParagraph
Viele Instanzen in der Dropbox-Infrastruktur führen Python-Code aus. Der hauptsächliche Grund für Dropbox war die schnelle Entwicklung ihrer Features, welche Python ermöglichte.
:::</p>
<p>:::globalTitle{:size="md" :tag="h3" .mb-5}
YouTube[8]:
:::
:::globalParagraph
2009 sprengte YouTube die Grenze von einer Milliarde Views pro Tag. YouTube setzt auf einen Application Server, welcher in Python geschrieben wurde. Um verschiedenen Auslastungen gerecht zu werden, kann YouTube einfach Maschinen hinzu- bzw. abschalten. Python ist bei hoher Auslastung der YouTube-Infrastruktur in der Regel nicht das Bottleneck. YouTube setzt Python außerdem vor allem wegen seiner Möglichkeiten zur schnellen und flexiblen Entwicklung ein.
:::</p>
<p>:::globalTitle{:size="md" :tag="h3" .mb-5}
Pinterest[9]:
:::
:::globalParagraph
Pinterest zahlt in Hochzeiten 52$ pro Stunde für ihre Server. Der meiste Traffic kommt dabei nachmittags und abends. Über Nacht kann die Gesamtanzahl der Instanzen auf 40% reduziert werden, was in einer Kostenreduktion bis auf 15$ pro Stunde resultieren kann. Hauptsächlich werden zum Ausliefern der Inhalte Django und Tornado (ein Python-Web-Framework und Asynchronous Network Library)[10] eingesetzt.
:::</p>
<p>:::globalTitle{:size="md" :tag="h3" .mb-5}
Spotify[11]:
:::
:::globalParagraph
Spotifys Backend besteht aus vielen unterschiedlichen Services. 80% dieser Dienste sind in Python geschrieben. Einer der wesentlichen Gründe für Spotify ist die schnelle Entwicklung, die Python mit sich bringt. Für die Abarbeitung asynchroner Tasks sowie (ca. 90% der) Map-Reduce Tasks in Spotify’s Hadoop Cluster wird Python hergenommen.
:::</p>
<p>:::globalTitle{:size="md" :tag="h3" .mb-5}
Instagram[12]:
:::
:::globalParagraph
Mit mehreren Millionen Requests pro Sekunde zeigt Instagram erfolgreich, wie perfekt Django zum Bauen von Web-Applications ist. Die Gesamtarchitektur ist natürlich komplex, Kern der Instagram Server ist jedoch Django als Application Server.
:::</p>
<p>:::globalTitle{:size="md" :tag="h3" .mb-5}
Disqus[13]:
:::
:::globalParagraph
Disqus wird auf zahlreichen Webseiten als Kommentar-Plugin verwendet. Mit (Stand 2013) 45.000 Anfragen pro Sekunde ist Disqus eine Webanwendung, welche extrem skaliert. Django wird fast ausschließlich für die Bearbeitung aller Anfragen auf Disqus verwendet. Natürlich kommen hier Technologien zum Einsatz, welche „Django das Leben erleichtern“ (z.B. Caches). Trotzdem gilt auch hier: Ein solides Fundament (wie beispielsweise Django) wird gebraucht, um stabile, sichere und skalierbare Webanwendungen zu entwickeln.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
BLUESHOE &#x3C;3 PYTHON/DJANGO
:::
:::globalParagraph
Python und Django haben aktive Communities, sie werden ständig weiterentwickelt und erlauben es uns, in kurzer Zeit hochqualitative Software zu schreiben. Deshalb lieben wir Python und Django. Kürzere Umsetzungszeiten bei Change Requests , hohe Sicherheitsstandards und geringe Kosten bei der Wartung/Pflege, diese Dinge lieben unsere Kunden. Es ist uns bei Blueshoe wichtig, dass unsere Kunden und Partner verstehen, warum wir diese Technologie als Grundlage vieler Projekte für uns gewählt haben.
:::
:::globalParagraph
Lange Rede, kurzer Sinn: Python/Django sind grandiose Fundamente für unterschiedlichste Projekte. Wir setzen darauf und unsere Erfahrungen sprechen für sich. Täglich erwarten uns neue Herausforderungen und wir wurden niemals von Django enttäuscht. Es trifft einfach den Kern unserer Mentalität: Auch wir sind Perfektionisten mit Deadlines.
:::
:::globalParagraph
Es gibt übrigens auch ein wundervolles Content Management System, dass auf Django basiert:
:::</p>
<p>:::globalParagraph
[1]<a href="http://blog.codinghorror.com/the-php-singularity/">http://blog.codinghorror.com/the-php-singularity/</a>{.bs-link-blue :target="_blank"}</p>
<p>[2]<a href="http://php.net/manual/de/history.php.php">http://php.net/manual/de/history.php.php</a>{.bs-link-blue :target="_blank"}</p>
<p>[3]<a href="https://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/#stance">https://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/#stance</a>{.bs-link-blue :target="_blank"}</p>
<p>[4]<a href="https://www.python.org/dev/peps/pep-0008">https://www.python.org/dev/peps/pep-0008</a>{.bs-link-blue :target="_blank"}</p>
<p>[5]<a href="https://www.owasp.org/index.php/Top_10_2013-Top_10">https://www.owasp.org/index.php/Top_10_2013-Top_10</a>{.bs-link-blue :target="_blank"}</p>
<p>[6]<a href="https://www.djangoproject.com/">https://www.djangoproject.com/</a>{.bs-link-blue :target="_blank"}</p>
<p>[7]<a href="https://docs.djangoproject.com/en/stable/misc/design-philosophies/">https://docs.djangoproject.com/en/stable/misc/design-philosophies/</a>{.bs-link-blue :target="_blank"}</p>
<p>[8]<a href="http://highscalability.com/youtube-architecture">http://highscalability.com/youtube-architecture</a>{.bs-link-blue :target="_blank"}</p>
<p>[9]<a href="http://highscalability.com/blog/2012/5/21/pinterest-architecture-update-18-million-visitors-10x-growth.html">http://highscalability.com/blog/2012/5/21/pinterest-architecture-update-18-million-visitors-10x-growth.html</a>{.bs-link-blue :target="_blank"}</p>
<p>[10]<a href="http://www.tornadoweb.org/en/stable/">http://www.tornadoweb.org/en/stable/</a>{.bs-link-blue :target="_blank"}</p>
<p>[11]<a href="https://labs.spotify.com/2013/03/20/how-we-use-python-at-spotify/">https://labs.spotify.com/2013/03/20/how-we-use-python-at-spotify/</a>{.bs-link-blue :target="_blank"}</p>
<p>[12]<a href="http://instagram-engineering.tumblr.com/post/13649370142/what-powers-instagram-hundreds-of-instances">http://instagram-engineering.tumblr.com/post/13649370142/what-powers-instagram-hundreds-of-instances</a>{.bs-link-blue :target="_blank"}</p>
<p>[13]<a href="http://blog.disqus.com/post/62187806135/scaling-django-to-8-billion-page-views">http://blog.disqus.com/post/62187806135/scaling-django-to-8-billion-page-views</a>{.bs-link-blue :target="_blank"}
:::</p>]]></content:encoded>
            <category>Python</category>
            <enclosure url="https://blueshoe.de/img/blogs/luca-bravo-XJXWbfSo2f0.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Zeitschätzungen bei Blueshoe: So planen wir deine Projekte realistisch]]></title>
            <link>https://blueshoe.de/blog/projektmanagement-zeitschaetzung-bei-blueshoe</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/projektmanagement-zeitschaetzung-bei-blueshoe</guid>
            <pubDate>Sat, 09 Aug 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Zeitschätzungen sind für jedes Projekt ein absolutes Muss. Sie helfen uns dabei, den Aufwand für einzelne Aufgaben oder das ganze Projekt realistisch einzuschätzen – und sorgen dafür, dass wir Ressourcen und Termine vernünftig planen können. Nur mit einer guten Zeitschätzung lassen sich Erwartungen bei Kunden und im Team klar kommunizieren und unangenehme Überraschungen vermeiden.</p>
<p><img src="/img/blogs/agile-backlog.svg" alt="Blueshoe Zeitschatzung">{.object-cover .max-w-full .mb-5}</p>
<p>Natürlich sind Zeitschätzungen nie 100% exakt. Projekte bringen oft unerwartete Herausforderungen mit sich: technische Hürden, fehlende Infos oder sich ändernde Anforderungen. Deshalb planen wir immer einen Puffer mit ein. Dieser Puffer gibt uns den nötigen Spielraum, um flexibel auf solche Situationen zu reagieren, ohne den gesamten Zeitplan durcheinanderzubringen.</p>
<p>Wichtig ist, dass der Puffer weder zu knapp noch zu großzügig bemessen ist. Er basiert auf Erfahrung und der Komplexität der jeweiligen Aufgabe. Und genauso wichtig: Wir kommunizieren transparent, wie sich die Zeitschätzung zusammensetzt – inklusive Puffer. So wissen alle Beteiligten jederzeit, woran sie sind und können besser planen.</p>
<p>Bei Blueshoe setzen wir deshalb auf einen klaren, strukturierten Workflow, mit dem wir Zeitschätzungen direkt im Ticketsystem erfassen und nachvollziehbar machen. In diesem Artikel geben wir einen Einblick, wie wir das genau machen und warum das für uns so gut funktioniert.</p>
<p><img src="/img/blogs/blueshoe-81.webp" alt="Blueshoe">{.object-cover .max-w-full .mb-5}</p>
<h2>Unser Zeitschätzungsprozess im Detail</h2>
<p>Wenn ein Kunde mit einer neuen Anforderung oder einem Problem auf uns zukommt, das eine Zeitschätzung braucht, starten wir einen klar strukturierten Prozess. Unser Projektmanager legt dafür ein eigenes Zeitschätzungsticket als Unterticket zum Hauptticket an und weist es einem Entwickler zu. Im Team klären wir dann intern, welche Aufgaben anstehen, welche Voraussetzungen erfüllt sein müssen und ob es Abhängigkeiten gibt. Falls Fragen auftauchen, beziehen wir den Kunden natürlich direkt mit ein – so vermeiden wir Missverständnisse von Anfang an.</p>
<p>Der Entwickler, der das Ticket bekommt, ist verantwortlich für eine realistische und sorgfältige Einschätzung. Wenn nötig, holt er sich Unterstützung von Kollegen, damit die Schätzung so genau wie möglich wird. Damit wir immer auf Nummer sicher gehen, prüfen wir jede Zeitschätzung zusätzlich durch einen zweiten Entwickler.</p>
<p><img src="/img/blog/timeestimate.webp" alt="estimate ticket">{.object-cover .max-w-full .mb-5}</p>
<p>Am Ende bekommt der Kunde von uns eine transparente Übersicht mit der fertigen Zeitschätzung und der Aufteilung der einzelnen Aufgaben. So kann er ganz entspannt entscheiden, ob er die Schätzung freigibt oder ob noch offene Fragen geklärt werden müssen.</p>
<h3>Wichtige Rahmenbedingungen für Zeitschätzungen</h3>
<ul>
<li><strong>Maximaler Zeitaufwand</strong>: Wir halten Zeitschätzungen möglichst schlank: Ideal ist, dass sie nicht mehr als 1–2 Stunden Aufwand bedeuten.</li>
<li><strong>Analysebudget</strong>: Sollte sich abzeichnen, dass eine Aufgabe komplexer ist und die Zeitschätzung mehr Zeit braucht, sprechen wir das frühzeitig mit dem Kunden ab und beantragen ein Analysebudget.</li>
<li><strong>Zweite Meinung</strong>: Außerdem setzen wir auf den Blick von zwei Entwicklern: Jede Zeitschätzung wird von einem zweiten Entwickler geprüft und bestätigt. So stellen wir sicher, dass unsere Einschätzungen realistisch und qualitativ hochwertig sind.</li>
</ul>
<h2>Struktur eines Zeitschätzungstickets</h2>
<p>Das Zeitschätzungsticket folgt einer klaren Struktur:</p>
<ul>
<li><strong>Aufgabenpakete und Schritte</strong><br>
Eine Auflistung der Tasks 1-X, mit optionalen Notizen und detaillierten Beschreibungen.</li>
<li><strong>Annahmen und Voraussetzungen</strong><br>
Welche Abhängigkeiten bestehen? Gibt es Voraussetzungen, die vom Kunden oder anderen Teams erfüllt sein müssen?</li>
<li><strong>Technische Limitierungen &#x26; Risiken</strong><br>
Mögliche technische Herausforderungen oder Risiken, die den Aufwand beeinflussen können.</li>
<li><strong>Testaufwand</strong><br>
Einschätzung des notwendigen Testaufwands.</li>
<li><strong>Puffer</strong><br>
Der Projektmanager ergänzt den Zeitpuffer für Unvorhergesehenes.</li>
<li><strong>Confidence-Level</strong><br>
Falls die Schätzung weniger als 100% sicher ist, werden die Unbekannten klar aufgelistet.</li>
</ul>
<h2>Fazit</h2>
<p>Unser klar strukturierter Zeitschätzungs-Workflow sorgt dafür, dass alle Beteiligten jederzeit den Überblick behalten. Transparenz, realistische Planung und offene Kommunikation zwischen Kunden, Projektmanagement und Entwicklung sind für uns selbstverständlich. So können wir neue Anforderungen schnell und effizient einschätzen und umsetzen – zum Vorteil für alle.</p>
<p>Wenn du mehr über unsere Arbeitsweise wissen möchtest oder Unterstützung bei deinem Projekt brauchen, melde dich einfach bei uns! Wir freuen uns auf den Austausch.</p>]]></content:encoded>
            <category>Projekt Management</category>
            <category>Dokumentation</category>
            <category>Team Blueshoe</category>
            <enclosure url="https://blueshoe.de/img/blogs/agile-backlog.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Analyse der Import-Zeit von Python Apps]]></title>
            <link>https://blueshoe.de/blog/python-django-schnelle-startup-zeit</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/python-django-schnelle-startup-zeit</guid>
            <pubDate>Tue, 28 Jan 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Der schnelle Start von Python-Applikationen in Cloud-Umgebungen kann essentiell für dynamische Skalierung sein, wie beispielsweise das horizontale Pod-Autoscaling. In diesem Blogpost teilen wir unsere Methode, um die Importzeiten – und damit die Startzeit von Python-Apps – zu analysieren und zu optimieren.</p>
<p><img src="/img/blogs/python-import.webp" alt="Analyse der Import-Zeit von Python Apps">{.object-cover .max-w-full .mb-5}</p>
<p>::GlobalTitle{:size="lg" .mb-4}
Schneller Start von Python-Apps
::</p>
<p>::GlobalParagraph
Warum benötigen wir überhaupt Applikationen, die schnell starten? Blueshoe ist hauptsächlich im Cloud-Entwicklungsbereich tätig. Unsere Applikationen tragen unterschiedliche Lasten zu unterschiedlichen Zeiten. Lastspitzen müssen schnell abgefangen werden, Services müssen skalieren. Wenn ein Service jedoch 10, 20 oder gar 30 Sekunden zum Starten braucht, kann dies fatal für Lastspitzen sein, da die Skalierung zu langsam geschieht. Dies gilt sowohl für klassische <a href="/blog/kubernetes-autoscaling-keda/">Kubernetes-Workloads</a>{:target="_blank" .text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} als auch für den Einsatz von <a href="/blog/function-as-a-service-faas-vs-kubernetes/">FaaS (Function-as-a-Service)</a>{:target="_blank" .text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}.
::</p>
<p>::GlobalParagraph
Ein weiterer wichtiger Aspekt ist die reine Entwicklungsarbeit. In der Regel starten Services in Debug-Setups oder Entwicklungsumgebungen noch langsamer als in Produktion. Wenn ich also meinen Service immer wieder neu starte und dabei warten muss, unterbrechen diese Wartezeiten den Arbeitsfluss und stören den Entwickler.
::</p>
<p>::GlobalParagraph
Ebenso können lange Startzeiten ein Symptom von weiteren, zugrunde liegenden Problemen sein – beispielsweise <em>Memory Leaks</em>.
::</p>
<p>:::GlobalButton{:url="/technologien/python-django-agentur/" :label="Erfahre mehr über unsere Django-Entwicklungsdienste" :color="blue" .mb-6}
:::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Tools zur Analyse der Python-App Importzeit
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Pythons Bordmittel: -X importtime
::</p>
<p>::GlobalParagraph
Tatsächlich bringt Python selbst schon ein wichtiges Mittel zur Analyse der Importzeiten mit: die Flag <code>-X importtime</code>. Startet man den Python-Interpreter seiner Applikation mit dieser Flag, wird eine Aufstellung über die verschiedenen Importzeiten der importierten Module ausgegeben:
::</p>
<p>::BlogCode{.mb-4}</p>
<pre><code class="language-text">...
import time:      1110 |       1569 | django.contrib.messages.storage.base
import time:       705 |        705 | django.contrib.admin.decorators
import time:      2103 |       2103 |       django.contrib.admin.utils
import time:      3080 |       5182 |     django.contrib.admin.helpers
import time:      3219 |       3219 |     django.contrib.admin.widgets
import time:      1862 |       1862 |     django.contrib.admin.checks
import time:       649 |        649 |     django.contrib.admin.exceptions
import time:       716 |        716 |       django.contrib.admin.templatetags
import time:      1328 |       2044 |     django.contrib.admin.templatetags.admin_urls
import time:       843 |        843 |     django.views.decorators.csrf
import time:      3891 |      17687 |   django.contrib.admin.options
import time:      1793 |      19479 | django.contrib.admin.filters
...

</code></pre>
<p>::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Visuelle Analyse
::</p>
<p>::GlobalParagraph
Soweit so gut! Um das Ganze besser analysieren zu können, wird dieser Output in <a href="https://kmichel.github.io/python-importtime-graph/">kmichel’s Importtime Graph</a>{:target="_blank" .text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} eingefügt:
::</p>
<p><img src="/img/blogs/python-import-chart.png" alt="Analyse der Import-Zeit">{.object-cover .max-w-full .mb-5 .mx-auto}</p>
<p>::GlobalParagraph
So erhält man eine visuelle Repräsentation über die Dauer der Imports verschiedener Module. Jede Fläche steht für ein Modul, welches unter Umständen Subflächen enthält. Je größer eine Fläche, desto länger die Importzeit.
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Weitere Tools zur Analyse von Importzeiten
::</p>
<p>::GlobalParagraph
Neben der <code>-X importtime</code>-Flag gibt es weitere nützliche Tools, die bei der Analyse und Optimierung der Startzeit helfen können:
::</p>
<p>::GlobalBlock{.ol-decimal .my-4}</p>
<ol>
<li><a href="https://github.com/benfred/py-spy"><strong>Py-Spy:</strong></a>{:target="_blank" .text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} Ein leistungsstarker Profiler, der ohne Instrumentierung arbeitet. Py-Spy kann genutzt werden, um genau zu verstehen, wie viel Zeit während der Initialisierungsphase aufgewendet wird.</li>
<li><a href="https://github.com/plasma-umass/scalene"><strong>Scalene:</strong></a>{:target="_blank" .text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} Ein hochpräziser Profiler, der CPU- und Speicherverbrauch analysiert. Er eignet sich hervorragend, um teure Initialisierungen zu identifizieren.</li>
<li><a href="https://docs.python.org/3/library/profile.html"><strong>cProfile:</strong></a>{:target="_blank" .text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} Ein in Python integrierter Profiler, der nicht nur während der Laufzeit, sondern auch während der Startzeit Einblicke liefert. Kombiniert mit <a href="https://docs.python.org/3/library/profile.html">pstats</a>{:target="_blank" .text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} oder Tools wie <a href="https://jiffyclub.github.io/snakeviz/">snakeviz</a>{:target="_blank" .text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} kann der Output visuell analysiert werden.</li>
<li><a href="https://modulegraph.readthedocs.io/en/latest/"><strong>Modulegraph:</strong></a>{:target="_blank" .text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} Dieses Tool kann Abhängigkeiten zwischen Modulen visualisieren, um unnötige oder doppelte Imports leichter aufzuspüren.
::</li>
</ol>
<p>::GlobalCallAnExpert{:bg="bg-bs-blue"}
#buttonBlockTitle
Wir können auch deine Python oder Django Apps optimieren.
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Tipps zur Optimierung der Importzeiten
::
::GlobalParagraph
Nachdem nun klar ist, welche Python-Module genauer anzusehen sind, stellt sich die Frage: Was ist zu tun? Wie verkürze ich die Zeiten? Hierbei gehen wir bei Blueshoe wie folgt vor
::</p>
<p>::GlobalBlock{.ol-decimal .my-4}</p>
<ol>
<li><strong>Dead-Code:</strong> Wird der Code noch gebraucht? Falls nein – raus damit. Nicht verwendete Imports oder Module tragen unnötig zur Startzeit bei.</li>
<li><strong>Externe Abhängigkeit überprüfen:</strong> Handelt es sich um eine externe Abhängigkeit? Falls ja, sollte geprüft werden, ob es eine neuere Version gibt, die optimiert ist. Gerade bei umfangreichen Bibliotheken wie Django oder NumPy werden regelmäßig Performance-Verbesserungen eingebaut.</li>
<li><strong>Initialisierung überprüfen:</strong> Führt ein Modul bei der Initialisierung Code aus, der nicht zwingend notwendig ist? Dies könnte z. B. eine komplexe Konfigurationsprüfung oder initiale Datenbankabfragen umfassen. Solche Prozesse sollten möglichst spät in der Laufzeit erfolgen.</li>
<li><strong>Import-Struktur verbessern:</strong> Gruppiere Imports logisch und überprüfe, ob sich einige Module zusammenfassen lassen, um redundante Abhängigkeiten zu reduzieren. Ein gutes Beispiel sind Utility-Module, die oft nur selektiv genutzt werden.
::</li>
</ol>
<p>::GlobalParagraph
Deine Python-App startet zu langsam? Reduziere die Startzeit mit Blueshoe: <a href="/technologien/python-django-agentur/">Zur Optimierung!</a>{:target="_blank" .text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Fazit
::</p>
<p>::GlobalParagraph
Eine kurze Startzeit von Python-Applikationen ist essenziell – sei es für die reibungslose Skalierung in Cloud-Umgebungen oder für eine angenehme Entwicklererfahrung. Mit einfachen Mitteln wie <code>-X importtime</code>, weiteren Tools wie z. B. <em>Py-Spy</em> oder <em>Scalene</em> und einer strukturierten Herangehensweise lassen sich Importzeiten analysieren und optimieren. So sorgt man dafür, dass Applikationen schneller starten, effizienter arbeiten und insgesamt stabiler sind.
::</p>
<p>::GlobalParagraph
Wie optimierst du die Startzeit deiner Python-Apps? Teile deine Erfahrungen und Tipps gerne in den Kommentaren!
::</p>
<p>:::GlobalTitle{:size="lg" .mb-5 .mt-8}
Häufige Fragen
:::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
1. Wie kann ich die Startzeit meiner Python App analysieren?
::</p>
<p>::GlobalParagraph
Verwende das <code>-X importtime</code>-Flag von Python, um die Importzeiten deiner Module zu messen. Mit Tools wie kmichel’s Importtime Graph oder Py-Spy kannst du diese Daten visualisieren und Engpässe identifizieren.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
2. Welche Tools helfen, die Django App Startzeit zu optimieren?
::</p>
<p>::GlobalParagraph
Einige der besten Tools sind:
::</p>
<p>::GlobalBlock{.ul-disk}</p>
<ul>
<li>Py-Spy für schnelle Analysen ohne Instrumentierung</li>
<li>Scalene für detaillierte CPU- und Speicherprofile</li>
<li>cProfile, das in Python integriert ist, für umfassende Laufzeitanalysen</li>
<li>Modulegraph zur Visualisierung von Abhängigkeiten
::</li>
</ul>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
3. Was sind die häufigsten Ursachen für lange Startzeiten bei Python Apps?
::</p>
<p>::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>Unnötige oder doppelte Imports</li>
<li>Komplexe Initialisierungsvorgänge (z. B. Datenbankabfragen beim Start)</li>
<li>Nicht genutzte Module oder Dead-Code</li>
<li>Schlechte Organisation der Importstruktur
::</li>
</ul>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
4.\ Wie kann ich die Startzeit meiner Django App in Kubernetes-Containern reduzieren?
::</p>
<p>::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>Optimiere die Importstruktur, um Ladezeiten zu minimieren.</li>
<li>Nutze Lazy Initialization, um teure Prozesse erst bei Bedarf zu starten.</li>
<li>Aktualisiere externe Bibliotheken auf schlankere Versionen.
::</li>
</ul>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
5. Warum ist eine kurze Startzeit für Python-Apps in der Cloud wichtig?
::</p>
<p>::GlobalParagraph{.mb-4}
Schnelle Startzeiten ermöglichen eine effiziente Skalierung bei Lastspitzen, insbesondere in Kubernetes-Setups. Lange Startzeiten können die Skalierung verzögern und die Performance deiner Anwendungen negativ beeinflussen.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
6. Welche Rolle spielt die Startzeit von Python Apps beim horizontalen Pod-Autoscaling in Kubernetes?
::</p>
<p>::GlobalParagraph{.mb-4}
Beim horizontalen <a href="/blog/kubernetes-skalierung-gke/"><em>Pod-Autoscaling (HPA)</em> in Kubernetes</a>{:target="_blank" .text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} werden zusätzliche Pods basierend auf der Last automatisch gestartet. Wenn die Python App Startzeit zu lang ist, können die neuen Pods die steigende Last nicht rechtzeitig bewältigen, was zu Verzögerungen und potenziellen Ausfällen führt. Optimierte Startzeiten sorgen für eine reibungslose Skalierung und bessere Verfügbarkeit.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
7. Kann die Optimierung der Python Startzeit die Entwicklungszeit reduzieren?
::</p>
<p>::GlobalParagraph{.mb-4}
Ja, definitiv! In Entwicklungs- und Debug-Umgebungen sind Neustarts von Services häufig erforderlich. Kurze Python App Startzeiten bedeuten weniger Wartezeit für Entwickler, was den Workflow erheblich verbessert und zu einer produktiveren Entwicklungsumgebung führt.
::</p>]]></content:encoded>
            <category>Python</category>
            <category>Django</category>
            <category>Entwicklung</category>
            <category>Performance</category>
            <category>Betrieb</category>
            <enclosure url="https://blueshoe.de/img/blogs/python-import.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[Python Fuzzy Search mit Rust & PyO3]]></title>
            <link>https://blueshoe.de/blog/python-rust-pyo3</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/python-rust-pyo3</guid>
            <pubDate>Tue, 04 Feb 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Fuzzy Search findet unscharfe Übereinstimmungen in Strings, stößt in Python aber bei großen Datenmengen an Grenzen. Rust löst dieses Problem effizient. Mit PyO3 lässt sich Rust als Python-Erweiterung nutzen, um rechenintensive Prozesse zu beschleunigen. Dieser Artikel zeigt, wie Fuzzy Search in Python mit Rust umgesetzt wird und welche Werkzeuge – insbesondere PyO3, Maturin und die Nucleo-Bibliothek – dabei helfen.</p>
<p><img src="/img/blogs/python-rust-pyo3.svg" alt="Effizient Python erweitern: PyO3 und Rust im Einsatz">{.object-cover .max-w-full .mb-5}</p>
<p>::GlobalBlogLevelInfo</p>
<ul>
<li><a href="https://rust-lang.org/">Rust Programmiersprache</a>{target="_blank"}</li>
<li>Optional <a href="https://github.com/PyO3/pyo3">PyO3</a>{:target="_blank"}</li>
<li>Optional <a href="https://github.com/astral-sh/uv">uv</a>{:target="_blank"}</li>
</ul>
<p>Solltest du Fragen haben, oder dir etwas unklar sein, kannst du die Kommentarfunktion unter dem Artikel nutzen.
::</p>
<p>::GlobalTitle{:size="lg" .mb-4}
Pfuzzer: Ein Python Fuzzy Searcher mit Rust
::</p>
<p>::GlobalParagraph
Die Kombination von <strong>Rust</strong> und <strong>Python</strong> wird immer beliebter, insbesondere für performance-sensitive Anwendungen. Mit <a href="https://github.com/PyO3/pyo3"><strong>PyO3</strong></a>{:target="_blank" .text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} lassen sich Rust-Module als Python-Erweiterung nutzen, wodurch sich die Geschwindigkeit und Effizienz von Rust mit der Flexibilität von Python verbinden lassen.
::</p>
<p>::GlobalParagraph
Ein herausragendes Beispiel für diese Integration ist <strong>Pfuzzer</strong> – eine Python-Bibliothek für Fuzzy Search, die auf der leistungsstarken Rust-Bibliothek <a href="https://github.com/helix-editor/nucleo"><strong>Nucleo</strong></a>{:target="_blank" .text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} basiert. Doch Pfuzzer ist mehr als nur eine praktische Lösung für unscharfe Suchanfragen: Es zeigt, wie <strong>PyO3</strong> genutzt werden kann, um Rust-Code nahtlos in Python einzubinden und rechenintensive Prozesse zu optimieren.
::</p>
<p>::GlobalParagraph
In diesem Artikel erfährst du, wie Pfuzzer funktioniert, wie PyO3 die Entwicklung von Rust-Erweiterungen für Python erleichtert und wie du eine Fuzzy Search in Python mit Rust umsetzen kannst.
::</p>
<p>:::GlobalButton{:url="/technologien/python-django-agentur/" :label="Erfahre mehr über unsere Python-Entwicklungsdienste" :color="blue" .mb-6}
:::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Was ist PyO3?
::</p>
<p>::GlobalParagraph
PyO3 ist ein Framework, das Rust-Code als native Python-Module verfügbar macht. Damit lassen sich Rust-Programme in Python integrieren, ohne auf Leistung oder Speichersicherheit verzichten zu müssen. Mit PyO3 kannst du:
::</p>
<p>::GlobalBlock{.ul-disk .my-4}</p>
<ul>
<li>Python-APIs mit Rust entwickeln</li>
<li>Bestehende Rust-Bibliotheken in Python einbinden</li>
<li>Python-Funktionen aus Rust heraus aufrufen
::</li>
</ul>
<p>::GlobalParagraph
Das Framework übernimmt technische Details wie Speicherverwaltung, Datenstrukturen und die Interaktion zwischen Python- und Rust-Laufzeit.
::</p>
<p>::GlobalParagraph
Dank der hohen Performance von Rust und der Flexibilität von Python ist PyO3 ideal für rechenintensive Anwendungen, darunter Datenverarbeitung, maschinelles Lernen und performante Fuzzy-Suchfunktionen – wie es Pfuzzer demonstriert.
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Pfuzzer: Ein Python-Modul für Fuzzy Search mit Rust
::</p>
<p>::GlobalParagraph
Pfuzzer ist eine Python-Bibliothek für Fuzzy Search, die auf der Rust-Bibliothek Nucleo basiert. Sie ermöglicht eine schnelle und effiziente unscharfe Suche – ideal für Anwendungen mit großen Datenmengen.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Warum Rust für Fuzzy Search?
::</p>
<p>::GlobalBlock{.ul-disk .my-4}</p>
<ul>
<li><strong>Performance</strong>: Rust ist deutlich schneller als reines Python</li>
<li><strong>Speichersicherheit</strong>: Kein Garbage Collector, kein Memory Leak</li>
<li><strong>Einfache Integration</strong>: Mit PyO3 als Python-Erweiterung in Rust nutzbar
::</li>
</ul>
<p>::GlobalTitle{:size="lg" .mb-5}
Pfuzzer installieren und einrichten
::
::GlobalParagraph
Um ein Rust-Projekt mit PyO3 in Python zu nutzen, eignet sich <a href="https://github.com/PyO3/maturin"><strong>Maturin</strong></a>{:target="_blank" .text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}. Maturin erleichtert den Build-Prozess, indem es Rust-Code als Python-Wheel kompiliert. So kann das Modul wie jede andere Python-Bibliothek installiert und verwendet werden.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Einrichtung eines PyO3-Projekts
::</p>
<p>::GlobalTitle{:font="font-oswald" :size="s" :tag="h4" .mb-2}
1. Projekt erstellen
::
::GlobalParagraph
Für die Projektstruktur nutzen wir den uv Package Manager::
::</p>
<p>::BlogCode{.mb-4}</p>
<pre><code class="language-bash">$ mkdir pfuzzer
$ cd pfuzzer
$ uv venv
$ uv add maturin
$ . .env/bin/activate

</code></pre>
<p>::</p>
<p>::GlobalTitle{:font="font-oswald" :size="s" :tag="h4" .mb-2}
2. Maturin initialisieren
::
::GlobalParagraph
Führe Maturin aus und lasse dir alle benötigten Dateien erstellen:
::</p>
<p>::BlogCode{.mb-4}</p>
<pre><code class="language-bash">$ maturin init
✔  🤷 What kind of bindings to use? · pyo3
   ✨ Done! New project created pfuzzer

</code></pre>
<p>::</p>
<p>::GlobalParagraph
Dieser Befehl erzeugt die Dateien <code>Cargo.toml</code> (Rust-Dependency-Management) und <code>lib.rs</code> (Rust-Code, der aus Python aufgerufen wird).
::</p>
<p>::GlobalParagraph
Der Befehl <code>maturin develop</code> installiert dann dein frisch erstelltes Rust Package in dem vorher erstellten virtualenv. Da wir uv benutzen muss die <code>--uv</code> flag ergänzt werden. Mit <code>maturin build –release</code> kann das Package für den Roll-Out gebuildet werden.
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Pfuzzer: Implementierung und Funktionsweise
::
::GlobalParagraph
Das Pfuzzer Python Package ist ein Wrapper für die Rust Library nucleo. Wie bereits gezeigt ist ein Wrapper einer bestehenden Library leicht mit PyO3 umzusetzen. Trotzdem will ich kurz auf die Implementierung eingehen und die Funktionsweise erklären. Der <a href="https://github.com/Blueshoe/pfuzzer">gesamte Code kann hier eingesehen</a>{:target="_blank" .text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} werden.
::
::GlobalParagraph
Dank PyO3 ist die Implementierung recht einfach. So stellt es mehrere Rust Attributes bereit, um Rust Code in Python aufrufbar zu machen. Hier z. B. die Implementierung des grundsätzlichen Python Moduls:
::</p>
<p>::BlogCode{.mb-4}</p>
<pre><code class="language-rust">mod python_classes;
use pyo3::prelude::*;
/// A Python fuzzy searcher module implemented in Rust.
#[pymodule]
fn pfuzzer(m: &#x26;Bound&#x3C;'_, PyModule>) -> PyResult&#x3C;()> {
    m.add_class::&#x3C;python_classes::pfuzzer::Pfuzzer>()?;
    Ok(())
}


</code></pre>
<p>::</p>
<p>::GlobalParagraph
Das ist so wenig Code, man könnte fast meinen, es ist bereits in Python geschrieben. Aber kurz die wichtigsten Abschnitte erklärt:
::</p>
<p>::GlobalBlock{.ol-decimal .my-4}</p>
<ol>
<li><code>#[pymodule]</code> --> teilt dem compiler mit, dass es sich hier um ein Python Modul handelt. Dadurch wird in die Funktion PyModule als Variable injected, welche dann als Eingangspunkt für unser Python Modul benutzt werden kann.</li>
<li><code>m.add_class::&#x3C;...></code> --> weist unserem Modul eine Klasse zu, in diesem Fall die noch nicht spezifizierte Pfuzzer Klasse.
::</li>
</ol>
<p>::GlobalCallAnExpert{:bg="bg-bs-blue"}
#buttonBlockTitle
Wir können auch deine Python Anwendung mit Rust und PyO3 beschleunigen.
::</p>
<p>::GlobalParagraph
Es gibt noch andere Möglichkeiten Rust Code dem Modul zuzuweisen. Darunter <code>m.add_function</code>. Anders als bei <code>add_class</code> wird hier allerdings nicht die Turbofish syntax verwendet sondern man muss die Rust Function mit einem Makro aufrufen. Mehr dazu in <a href="https://pyo3.rs">den PyO3 docs</a>{:target="_blank" .text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}.
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Pfuzzer-Klasse in Rust umsetzen
::</p>
<p>::GlobalParagraph
Da Rust keine Klassen kennt, verwenden wir stattdessen Structs, um das Verhalten der Pfuzzer-Klasse zu definieren:
::
::BlogCode{.mb-4}</p>
<pre><code class="language-rust">#[pyclass]
pub struct Pfuzzer {
    pub matcher: Matcher,
}

</code></pre>
<p>::</p>
<p>::GlobalParagraph
Als Attribut wird hier <code>pyclass</code> vergeben. Dadurch wird das Struct als Python Klasse markiert und kann dann, wie vorher gezeigt, dem Modul zugewiesen werden.
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Konstruktor für Pfuzzer
::</p>
<p>::GlobalParagraph
Jede gute Klasse braucht natürlich auch einen Konstruktor:
::</p>
<p>::BlogCode{.mb-4}</p>
<pre><code class="language-rust">use nucleo::{Config, Matcher, Utf32Str};

#[pymethods]
impl Pfuzzer {
    #[new]
    pub fn new() -> PyResult&#x3C;Self> {
        Ok(Pfuzzer {
            matcher: Matcher::new(Config::DEFAULT),
        })
    }

...

</code></pre>
<p>::</p>
<p>::GlobalParagraph
Durch <code>#[pymethods]</code> deklarieren wir den gesamten Implementation Block als Python Methoden der <code>Pfuzzer</code> Klasse. Der Konstruktor selbst wird mit <code>#[new]</code> markiert. Aktuell unterstützt PyO3 nämlich nur die <code>__new__</code> Magic Method und nicht <code>__init__</code>.
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Fuzzy Search in Rust: <code>compare_strings</code> Methode
::</p>
<p>::GlobalParagraph
Zu guter Letzt noch die eigentliche “Logik” unseres Wrappers. Ich darf präsentieren, die <code>compare_strings</code> Methode:
::</p>
<p>::BlogCode{.mb-4}</p>
<pre><code class="language-rust">pub fn compare_strings(&#x26;self, targets: Vec&#x3C;String>, query: String) -> Vec&#x3C;Option&#x3C;u16>> {
        let mut res = Vec::&#x3C;Option&#x3C;u16>>::new();
        for target in targets {
            res.push(self.matcher.to_owned().fuzzy_match(
                Utf32Str::Ascii(target.as_bytes()),
                Utf32Str::Ascii(query.as_bytes()),
            ))
        }
        return res;
    }

</code></pre>
<p>::</p>
<p>::GlobalParagraph
Wie man schnell erkennen kann, benutzt sie den nucleo Matcher und führt für jeden Ziel String das fuzzy matching anhand des angegebenen Query Strings aus.<br>
Ergebnis der Methode ist dann ein optionaler Integer pro Ziel String, welcher aussagt, wie gut der Query String mit dem Ziel String übereinstimmt. Je höher das Ergebnis, desto besser die Übereinstimmung. Ein <em>Null</em> / <em>None</em> Wert sagt aus, dass Ziel und Query überhaupt nicht (oder zumindest nicht messbar) übereinstimmen.
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Pfuzzer in Python verwenden
::</p>
<p>::GlobalParagraph
Und wie sieht das Ganze nun in Python aus? Bitteschön:
::</p>
<p>::BlogCode{.mb-4}</p>
<pre><code class="language-python">from pfuzzer import Pfuzzer

pf = Pfuzzer()

print(pf.compare_strings(["hello world", "hello blueshoe"], "helo world"))

>>> [257, None]
</code></pre>
<p>::</p>
<p>::GlobalParagraph
Laut Ergebnis Matrix, ist also der erste String der String, welcher am besten mit der Query übereinstimmt.
::</p>
<p>::GlobalParagraph
Wenn du noch Ideen für neue Features für Pfuzzer, oder gar Optimierungsgedanken, hast dann lass gerne einen Kommentar da. Oder mach ein <a href="https://github.com/Blueshoe/pfuzzer/issues">Issue auf Github</a>{:target="_blank" .text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} auf!
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Fazit: Rust und Python effizient kombiniert
::
::GlobalParagraph
In diesem Artikel haben wir gezeigt, wie Rust und Python mit PyO3 zusammenarbeiten, um performante Lösungen für Fuzzy Search zu ermöglichen. Pfuzzer ist ein Beispiel dafür, wie sich die Geschwindigkeit und Sicherheit von Rust mit der Flexibilität von Python verbinden lassen. Die nahtlose Integration über PyO3 und Maturin eröffnet neue Möglichkeiten – besonders für rechenintensive Anwendungen wie Datenverarbeitung und maschinelles Lernen.
::
::GlobalParagraph
Wenn auch du Lust auf richtig schnelles Python hast, dann schreib uns gerne in den Kommentaren und wir erörtern die Einsatzmöglichkeiten von Rust in deinem Python Projekt! Auch über Kommentare zu deinem Einsatz von PyO3 freuen wir uns!
::</p>
<p>:::GlobalTitle{:size="lg" .mb-5 .mt-8}
Häufige Fragen
:::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
1. Wie kann ich ein Python-Modul mit Rust und PyO3 erstellen?
::</p>
<p>::GlobalParagraph
Um ein Rust-Modul für Python zu schreiben, brauchst du PyO3 und Maturin. Die grundlegenden Schritte:
::
::GlobalBlock{.ol-decimal .my-4}</p>
<ol>
<li><strong>Rust-Umgebung einrichten:</strong> Folgende Kommandos: <code>cargo new --lib my_project</code>, <code>cd my_project</code>.</li>
<li><strong>PyO3 hinzufügen:</strong> Füge in der <code>Cargo.toml</code> die Dependency <code>pyo3 = { version = "0.18", features = ["extension-module"] }</code> hinzu.</li>
<li><strong>Modul implementieren:</strong> <code>use pyo3::prelude::*;</code> und <code>#[pymodule]</code></li>
<li><strong>Mit Maturin bauen:</strong> <code>maturin build</code>
::</li>
</ol>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
2. Wie funktioniert Fuzzy Search in Rust mit der Nucleo-Bibliothek?
::</p>
<p>::GlobalParagraph
Die Nucleo-Bibliothek bietet einen leistungsstarken Fuzzy Matching Algorithmus, um die Ähnlichkeit zwischen Strings zu berechnen. <em>Pfuzzer</em> nutzt diese Bibliothek für schnelle unscharfe Suchen in Python.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
3. Kann ich Rust-Module in bestehenden Python-Projekten verwenden?
::</p>
<p>::GlobalParagraph
Ja! Rust-Module lassen sich problemlos in bestehende Python-Projekte integrieren. Dank <strong>PyO3</strong> kannst du Rust-Funktionen direkt als Python-Module importieren und nutzen.
::</p>]]></content:encoded>
            <category>Python</category>
            <category>Rust</category>
            <category>Entwicklung</category>
            <category>Performance</category>
            <category>Sicherheit</category>
            <enclosure url="https://blueshoe.de/img/blogs/python-rust-pyo3.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Grundlagen der API-Strategie: Wie du die richtige Technologie für deine Anwendung auswählst]]></title>
            <link>https://blueshoe.de/blog/richtige-api-technologie-auswaehlen</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/richtige-api-technologie-auswaehlen</guid>
            <pubDate>Wed, 15 May 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Kannst du dir eine Welt ohne APIs vorstellen? Eine Welt ohne die Leichtigkeit und Zuverlässigkeit eines OpenAPI-Schemas? Ja, ich auch nicht - und doch mussten Entwickler noch vor wenigen Jahren geniale Wege finden, um Systeme miteinander kommunizieren zu lassen.</p>
<p><img src="/img/blogs/api-technologies-rest-graphql.svg" alt="Grundlagen der API-Strategie: Wie du die richtige Technologie für deine Anwendung auswählst">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalTitle{:size="lg" .mb-5}
Die Bedeutung von <a href="/loesungen/api-entwicklung/">APIs</a> in der modernen Softwareentwicklung
:::
:::globalParagraph
Es gibt einen sehr guten Artikel von Darius Kazemi mit dem Titel "The land before modern APIs" über die Anfänge von ARPANET im Jahr 1970 und wie diese Entscheidungen die heutige Softwareentwicklung immer noch beeinflussen. Eine dieser Entscheidungen war die Spezifikation von Fehler- und Antwortcodes für das "RJE-Protokoll", was zu den HTTP-Statuscodes führte, die wir alle kennen und "lieben".
:::
:::globalParagraph
Aber warum sind diese frühen Entscheidungen so wichtig und das moderne API-Design so bedeutsam für unsere tägliche Arbeit?
:::globalParagraph
Nun, der von Entwicklern im Jahr 1970 geebnete Weg führte zur Entwicklung von Web-APIs in den frühen 2000er Jahren. Der Artikel von Kin Lane mit dem Titel "Intro to APIs: History of APIs" fasst die Geschichte von APIs sehr gut zusammen. Kurz gesagt, APIs gingen von einem kommerziellen Anwendungsfall über die Sozialisierung des Internets bis hin zur Unterstützung von Geräten der nächsten Generation. Es gibt sogar einen "API-Hub" für APIs! Das alles zeigt, dass APIs eine große Rolle in der modernen Softwareentwicklung spielen.
:::
:::globalParagraph
Obwohl wir unsere APIs lieben, gibt es immer noch eine laufende Debatte darüber, wie eine API unter der Haube funktionieren sollte. Vor 2015 entschied man sich in der Regel für ein REST-basiertes API-Design. Aber dann hat Facebook die GraphQL-Sprache für die API-Entwicklung als Open Source veröffentlicht und uns eine neue Möglichkeit gegeben, mit unseren Daten zu interagieren.
:::
:::globalParagraph
In diesem Beitrag möchte ich einen kleinen Überblick über REST und GraphQL geben, diese beiden vergleichen und schließlich einige andere Ansätze für das API-Design erwähnen.
:::
:::globalParagraph{.mb-5}
Also, wollen wir anfangen?
:::</p>
<p>:::GlobalButton{:url="/loesungen/api-entwicklung/" :label="Erfahre mehr über unsere API-Entwicklungsdienste" :color="blue" .mb-6}
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
Die Grundlagen und Stärken von REST-APIs verstehen
:::
:::globalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Eigenschaften von REST-APIs
:::
:::globalParagraph
REST <a href="/loesungen/api-entwicklung/">APIs</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} weisen zwei grundlegende Eigenschaften auf, die ihrem Design zugrunde liegen: Zustandslosigkeit und die Verwendung von HTTP-Methoden. Erstens implementieren REST-APIs das zustandslose Paradigma, was bedeutet, dass jede Anfrage eines Clients an den Server alle erforderlichen Informationen enthalten muss, um sie zu verstehen und zu erfüllen, ohne auf vorherige Interaktionen angewiesen zu sein. Dies vereinfacht die Serverimplementierung und Skalierbarkeit, da keine Sitzungsdaten auf dem Server gespeichert werden müssen. Zweitens nutzen REST-APIs die vielseitigen Methoden des HTTP-Protokolls, wie z.B. GET, POST, PUT, DELETE, um verschiedene Aktionen auf Ressourcen auszuführen. Diese Methoden ermöglichen es Entwicklern, APIs zu erstellen, die eng mit den Prinzipien von CRUD (Create, Read, Update, Delete) übereinstimmen und die Klarheit, Vorhersagbarkeit und Zugänglichkeit ihrer API-Endpunkte verbessern.
:::
:::globalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Anwendungsfälle von REST
:::
:::globalParagraph
Wenn du eine <a href="/loesungen/api-entwicklung/">API</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} mit einem klar definierten Client-Server-Vertrag planst (das bedeutet unter anderem weniger Flexibilität bei den Serverantworten), ist REST eine sehr gute Option für dich. REST ist nicht nur effizient und einfach zu implementieren, sondern wird auch von allen Programmiersprachen weitgehend unterstützt, die mit HTTP-Methoden arbeiten können.
:::
:::globalParagraph
Obwohl REST vordefinierte Anfragen erwartet und nur festgelegte Antworten liefert, bietet es dennoch eine hohe Flexibilität für die Erstellung einer API. Mit REST kannst du alles aufbauen!
:::
:::globalParagraph
Ein einfaches Beispiel könnte eine einfache API sein, die die Bilder von http.cat abruft und an den Benutzer zurückgibt:
:::
::BlogCode{.mb-5}</p>
<pre><code class="language-python">from fastapi import FastAPI
from fastapi.responses import Response
import requests

app = FastAPI()

@app.get("/{status_code}", description="Get cat status code", response_class=Response)
async def get_cat_status_code(status_code: int):
    response = requests.get(f"https://http.cat/{status_code}")
    return Response(response.content, media_type="image/jpeg")
</code></pre>
<p>::
:::globalParagraph
Bitte beachte, dass http.cat bereits eine API ist, die ein Bild zurückgibt (es ist nicht ungewöhnlich, andere APIs in deiner API aufzurufen). Aber dieses Beispiel soll nur zeigen, was mit einer einfachen REST-API möglich ist und wie sie dem HTTP-Methodenmodell entspricht. Lass uns das genauer betrachten!
:::
:::globalParagraph
In diesem Beispiel habe ich das beliebte FastAPI-Paket für Python verwendet. Es ermöglicht die einfache Erstellung von REST-APIs.
:::
:::globalParagraph
<code>@app.get</code> definiert eine HTTP-GET-Methode auf dem Stammverzeichnis mit dem Pfadparameter <code>status_code</code>. In der Methode <code>get_cat_status_code</code> mache ich dann eine GET-Anfrage an die http.cat-API (unter Verwendung des bereitgestellten status_code) und gebe schließlich die Bildbytes als FastAPI Response-Objekt zurück.
:::
:::globalParagraph{.mb-6}
Damit ist dieses kurze REST-Beispiel abgeschlossen.
:::</p>
<p>::GlobalPartial{content=catcher-1}</p>
<p>:::globalTitle{:size="lg" .mb-5}
GraphQL: Ein "Neuling" unter den APIs
:::
:::globalParagraph
Obwohl GraphQL seit 2015 existiert, ist es im Vergleich zu REST oder sogar älteren Technologien wie SOAP-basierten APIs immer noch recht neu in der API-Welt.
:::
:::
:::globalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Eigenschaften von GraphQL
:::
:::globalParagraph{.mb-5}
GraphQL <a href="/loesungen/api-entwicklung/">APIs</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} besitzen charakteristische Merkmale, die sie von traditionellen REST-APIs unterscheiden und einen flexibleren und effizienteren Ansatz für die Datenabfrage bieten. Eine bemerkenswerte Funktion von GraphQL ist seine Fähigkeit, Abfragen zu optimieren und den Datentransfer zu minimieren. Im Gegensatz zu REST-APIs, bei denen Endpunkte festgelegt sind und vordefinierte Datenstrukturen zurückgeben, ermöglicht GraphQL den Clients, genau anzugeben, welche Daten sie mit einer einzigen Abfrage benötigen. Diese Fähigkeit beseitigt Probleme mit Über- und Unterabfrage, die bei REST-APIs häufig auftreten, bei denen Clients möglicherweise mehr oder weniger Daten erhalten als erforderlich. Darüber hinaus ermöglicht das Typsystem von GraphQL Clients, verschachtelte oder verwandte Daten in einer einzigen Abfrage anzufordern, wodurch die Notwendigkeit für mehrere Anfragen an den Server reduziert wird. Durch Bereitstellung einer präzisen und deklarativen Syntax für die Datenabfrage ermöglicht GraphQL den Clients, nur die erforderlichen Daten abzurufen, was zu einer effizienteren Netzwerknutzung und einer verbesserten Leistung führt. Darüber hinaus unterstützt GraphQL Funktionen wie Query Batching und Caching, wodurch der Datentransfer optimiert und die Latenz für Client-Anwendungen reduziert wird. Insgesamt verbessern die Abfrageoptimierungsfunktionen von GraphQL die Effizienz und Reaktionsfähigkeit von API-Interaktionen und machen es zu einer hervorragenden Wahl für die moderne Anwendungsentwicklung. resulting in more efficient network usage and improved performance.
:::
:::globalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Anwendungsfälle von GraphQL
:::
:::globalParagraph
Der Anwendungsfall für GraphQL-APIs unterscheidet sich etwas von REST. GraphQL sollte in datenreichen Umgebungen mit komplexen Anforderungen verwendet werden. Es ist auch sinnvoll, wenn du mehrere Clients mit unterschiedlichen Datenanforderungen hast.
:::
:::globalParagraph
Lass uns das gleiche Beispiel wie bei der REST-API einrichten und mit GraphQL einige http.cat-Bilder abrufen!
:::
:::globalParagraph
Hier ist der Hauptcode für die API mit FastAPI und seiner GraphQL-Integration mit strawberry:
:::
::BlogCode{.mb-5}</p>
<pre><code class="language-python">import strawberry
from fastapi import FastAPI
from strawberry.asgi import GraphQL

@strawberry.type
class Cat:
    url: str

@strawberry.type
class Query:
    @strawberry.field
    def cat(self, status_code: int) -> Cat:
        return Cat(url=f"https://http.cat/{status_code}")

schema = strawberry.Schema(query=Query)

graphql_app = GraphQL(schema)

app = FastAPI()
app.add_route("/graphql", graphql_app)
app.add_websocket_route("/graphql", graphql_app)
</code></pre>
<p>::
:::globalParagraph
Zuerst definieren wir einen strawberry-Typ namens <em>Cat</em>, dann einen weiteren namens <em>Query</em>. Query hat ein Feld namens <em>cat</em>, das ein implizites Feld <em>url</em> hat. Das Erstellen eines GraphQL-Schemas kann mit strawberry-Schema erfolgen, indem es der <em>Query class</em> übergeben wird.
:::
:::globalParagraph
Wie du sehen kannst, ist GraphQL nicht in der Lage, die Bildbytes direkt zurückzugeben, was bedeutet, dass du solche Dinge selbst erledigen musst. Es benötigt auch einen Client auf der anderen Seite, der eine Abfrage gegen unsere API ausführen kann. Glücklicherweise hat strawberry einen integrierten Server, der einen solchen Client bereitstellt:
<img src="/img/blogs/GraphQL-strawberry.png" alt="Anwendungsfälle von GraphQL">{.object-cover .max-w-full .mb-5}
:::
:::globalParagraph
Auf der linken Seite siehst du die von mir zusammengestellte Abfrage und auf der rechten Seite die JSON-Antwort von der GraphQL-API.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
Vergleich von REST und GraphQL
:::
:::globalParagraph
Im Internet liest man oft über REST vs GraphQL. Meiner Meinung nach kann man diese beiden Technologien nicht gegeneinander antreten lassen. Es handelt sich um zwei sehr unterschiedliche Ansätze zur Interaktion mit Daten, auf die wir zugreifen möchten. Daher ist ein Vergleich zwischen diesen beiden nicht fair und man kann nicht generell sagen, dass ein Ansatz besser ist als der andere. Ich würde eher vorschlagen, wann man welchen Ansatz verwenden sollte.
:::
:::globalParagraph
Wenn du dich in einer Umgebung befindest, in der du die Anforderungen mehrerer Clients erfüllen musst, die unterschiedlich geformte Daten benötigen, solltest du wahrscheinlich GraphQL verwenden. Mit seiner einfachen Abfragesprache kannst du schnelle, effiziente Antworten für deine Clients und ihre unterschiedlichen Anforderungen erzielen.
:::
:::globalParagraph
Auf der anderen Seite, wenn du etwas strenger in Bezug auf Serverantworten sein kannst und gern eine gute Zwischenspeicherung von Anfragen hättest, könnte eine REST-basierte API das Richtige für dich sein. Aufgrund ihrer Standardisierung und der Verwendung von HTTP-Methoden ist REST weit verbreitet und verwendbar. Mit ihrem Aufruf zur klaren Trennung von Zuständigkeiten und der architektonischen Kommunikation ohne Zustand ist sie auch eine sehr skalierbare Lösung.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
Übersicht über alternative API-Technologien
:::
:::globalParagraph
Zum Schluss möchte ich zwei alternative Ansätze zur <a href="/loesungen/api-entwicklung/">API-Entwicklung</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} erwähnen, die wir bei Blueshoe ebenfalls verwenden und anbieten.
:::
:::globalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
gRPC und seine Rolle in Microservices
:::
:::globalParagraph
gRPC (das g steht nicht für Google) ist ein Open-Source-RPC-Framework, das ursprünglich von Google entwickelt wurde. Es basiert auf HTTP/2, Protocol Buffers (protobuf) und anderen modernen Technologien und bietet eine robuste und effiziente Möglichkeit, Dienste in verschiedenen Umgebungen zu verbinden.
:::
:::globalParagraph
gRPC bietet viele Vorteile in <a href="/loesungen/microservice-architektur-beratung/">Microservice-Architekturen</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}. Einige davon sind:
:::
::GlobalBlock{:font-size="lg" :color="text-bs-text" .ul-disk .mb-5}</p>
<ul>
<li><strong>Effiziente Serialisierung mit Protocol Buffers</strong>: gRPC verwendet Protocol Buffers als seine Interface Definition Language (IDL) zur Definition von Serviceverträgen und zur Serialisierung von Daten. Protocol Buffers bieten ein kompaktes Binärformat und effiziente Serialisierungs- und Deserialisierungsprozesse, was zu kleineren Nachrichtengrößen und schnellerer Datenübertragung führt. Diese Effizienz ist insbesondere in <a href="/loesungen/microservice-architektur-beratung/">Microservices</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}-Umgebungen mit hohem Datendurchsatz von Vorteil.</li>
<li><strong>Starke Typisierung und Codegenerierung</strong>: Die gleiche Technologie von Protocol Buffers wird auch für die Typisierung und Codegenerierung verwendet. Dadurch kann die Entwicklung beschleunigt und menschliche Fehler minimiert werden.</li>
<li><strong>Bidirektionales Streaming und Flusssteuerung</strong>: gRPC unterstützt verschiedene Kommunikationsmuster, einschließlich unärer, serverseitiger Streaming, clientseitiger Streaming und bidirektionaler Streaming. Diese Flexibilität ermöglicht es Diensten, Daten in Echtzeit effizient auszutauschen und unterstützt Anwendungsfälle wie Chat-Anwendungen, Echtzeitanalyse und ereignisgesteuerte Architekturen. Darüber hinaus verhindern die integrierten Flusssteuerungsmechanismen von gRPC Probleme wie Rückstau und gewährleisten eine optimale Ressourcennutzung und Systemstabilität.
::</li>
</ul>
<p>:::globalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
SOAP als Unternehmenslösung
:::
:::globalParagraph
SOAP (Simple Object Access Protocol), ein Protokoll zum Austausch strukturierter Informationen bei der Implementierung von Webdiensten, ist seit Jahrzehnten Teil von Unternehmensanwendungen. Es bietet eine standardisierte Möglichkeit für Anwendungen, über Netzwerke zu kommunizieren und ermöglicht die Interoperabilität zwischen verschiedenen Systemen und Plattformen. Trotz Kritik an seiner Verbosity und Komplexität im Vergleich zu neueren Protokollen wie REST ist SOAP aufgrund seiner Zuverlässigkeit, Erweiterbarkeit und umfassenden Sicherheitsfunktionen in Unternehmensumgebungen weiterhin beliebt.
:::</p>
<p>:::globalTitle{:color="text-bs-green" :font="font-oswald" :size="sm" :tag="h4" .mb-5}
Sicherheitsfunktionen von SOAP
:::
::GlobalBlock{:font-size="lg" :color="text-bs-text" .ul-disk .mb-5}</p>
<ul>
<li><strong>Sicherheit auf Nachrichtenebene</strong>: SOAP unterstützt Sicherheit auf Nachrichtenebene, indem einzelne Nachrichten zwischen Client und Server verschlüsselt und signiert werden können. Dadurch wird die Vertraulichkeit, Integrität und Authentizität von Daten gewährleistet und das Risiko von Abhören, Manipulation und unbefugtem Zugriff auf sensible Informationen verringert. Durch die Verschlüsselung von Payloads und das Anhängen digitaler Signaturen an Nachrichten hilft SOAP Unternehmen, die Vertraulichkeit und Integrität ihrer Daten auch während der Übertragung über nicht vertrauenswürdige Netzwerke aufrechtzuerhalten.</li>
<li><strong>WS-Security-Standard</strong>: SOAP integriert sich nahtlos in den WS-Security-Standard, eine weit verbreitete Spezifikation zur Sicherung von Webdiensten. WS-Security bietet einen Rahmen für die Integration verschiedener Sicherheitsmechanismen wie Verschlüsselung, digitale Signaturen und Authentifizierungstoken in SOAP-Nachrichten. Diese Standardisierung gewährleistet die Interoperabilität und Kompatibilität zwischen verschiedenen Implementierungen und ermöglicht Unternehmen, auf ein breites Ökosystem von Sicherheitstools und -lösungen zurückzugreifen, um ihre auf SOAP basierenden Dienste zu schützen.
::</li>
</ul>
<p>:::globalTitle{:size="md" .mb-5}
Zusammenfassung
:::
:::globalParagraph
Wie du siehst, gibt es viele Möglichkeiten, eine <a href="/loesungen/api-entwicklung/">API</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} zu implementieren, und die Wahl der richtigen Technologie kann manchmal schwierig sein. Ich hoffe, mit diesem kurzen Überblick einen Hinweis darauf geben zu können, wann man einen bestimmten Ansatz verwenden sollte, um deine API zu erstellen. Und wenn du jemals eine maßgeschneiderte Lösung brauchst, <a href="/kontakt">melde dich bei uns</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}!
:::</p>
<p>:::GlobalPodcastSection{:videoId="tyvE9VlSWkE" :videoPosition="left" .mb-6}
::::GlobalPreTitle{:color="text-bs-green" .mb-3}
Unser Kubernetes-Podcast
::::
::::GlobalTitle{:tag="h3" .mb-6}
Tools for the Craft: Navigating the Kubernetes ecosystem
::::
::::globalParagraph{:font-size="lg" .mb-4}
Michael und Robert sprechen ausführlich über die Vor- und Nachteile der lokalen Kubernetes-Entwicklung und geben auch einige echte Codierungsbeispiele.
::::
::::globalParagraph{:font-size="lg" }
Weitere Ausgaben unseres Podcasts findest du hier:
::::
::::GlobalButton{:url="/kubernetes-podcast/" :label="Mehr anzeigen" :color="green"}
::::
:::</p>]]></content:encoded>
            <category>Kubernetes</category>
            <category>API</category>
            <category>Python</category>
            <category>Entwicklung</category>
            <enclosure url="https://blueshoe.de/img/blogs/api-technologies-rest-graphql.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Saisonale SEO-Maßnahmen - 3 Tipps für deinen Online-Shop]]></title>
            <link>https://blueshoe.de/blog/saisonale-seo-mabnahmen-3-tipps</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/saisonale-seo-mabnahmen-3-tipps</guid>
            <pubDate>Fri, 09 Feb 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Suchmaschinenoptimierung spielt nicht nur für Dienstleister eine wichtige Rolle, auch für Händler und im Bereich E-Commerce ist das Thema wichtig. Mit einer idealen Sichtbarkeit bei Google kann man vielfach die Absatz- und Umsatzzahlen steigern.</p>
<p><img src="/img/blogs/stephen-phillips-hostreviewsl.jpg" alt="stephen-phillips-hostreviewsl">{.object-cover .max-w-full .mb-5}</p>
<p>Das Thema saisonales SEO ist sehr umfangreich und bietet vielerlei Optionen, Wege und Möglichkeiten. Grund genug, sich damit einmal auseinanderzusetzen und abzuschätzen, ob es sich lohnt, bestimmte (saisonale) SEO-Maßnahmen umzusetzen – insbesondere dann, wenn einzelne Produkte nicht immer angeboten werden (können).</p>
<p>Einzelne Aspekte und Optionen sind dabei von Bedeutung, die im folgenden Text eine entsprechende Wichtigkeit haben. So findet man einfach heraus, wie man im E-Commerce durch Suchmaschinenoptimierung größere Erfolge erzielen kann, wenn Produkte nicht immer griffbereit sind.</p>
<p>Es folgen drei Tipps mit ersten Anregungen:</p>
<h2>TIPP 1: SO KANN MAN MIT ALTEN PRODUKTSEITEN IM ONLINE SHOP UMGEHEN</h2>
<p>Jedes Produkt in einem E-Commerce-Shop hat im Idealfall eine eigene Produktseite. Nun kann es allerdings vorkommen, dass ein Artikel nicht mehr verkauft wird, da die Produktion eingestellt wurde oder er ausverkauft ist.</p>
<p><img src="/img/blogs/alte_produkte_im_e-commerce.jpg" alt="alte_produkte_im_e-commerce">{.object-cover .max-w-full .mb-5}</p>
<p>Wie geht man in einem solchen Fall mit der Produktseite um, die an sich nicht mehr gebraucht wird? Es gibt hierfür zwei Ansätze, die in Frage kommen können:</p>
<p>Man leitet die Seite auf ein <strong>ähnliches Produkt</strong> weiter</p>
<ul>
<li>Man nutzt eine Weiterleitung auf die <strong>Kategorie des Artikels</strong>, sodass der Kunde aus einer anderen Auswahl wählen kann.</li>
</ul>
<h2>TIPP 2: DAS KANN MAN MIT SAISONALEN ARTIKELN MACHEN</h2>
<p>In vielen Online-Shops wird zudem mit saisonalen Artikeln gearbeitet, die jeweils nur für einen begrenzten Zeitraum gefragt sind oder angeboten werden. Im Zusammenhang mit SEO-Maßnahmen sollte man hier bedenken, wie man mit Produktseiten für saisonale Waren umgehen kann. Immerhin ranken auch derartige Seiten und bringen somit potentielle Kunden zum Online-Shop.</p>
<p><img src="/img/blogs/saisonale_produkte_e-commerce.jpg" alt="saisonale_produkte_e-commerce">{.object-cover .max-w-full .mb-5}</p>
<p>Wenn dann allerdings kein Artikel angeboten wird, kann dies ein Nachteil sein. Um auch dann eine Möglichkeit zu finden, einen Verkauf zu erzielen, sind unterschiedliche Wege möglich. Keinesfalls sollte man die Seite so lassen, wie sie ist <strong>– das kann zu unzufriedenen Kunden und einem schnellen Absprung führen</strong>.</p>
<p>Stattdessen sind zum Beispiel diese Lösungen vielversprechender:</p>
<ul>
<li>Den Nutzer auf alternative (eventuell auch saisonale) Produkte aufmerksam machen</li>
<li>Den Nutzer direkt auf alternative Artikel weiterleiten</li>
<li>Einen Hinweis einbringen, dass der Artikel nur in einem bestimmten Zeitraum (mit entsprechender Angabe dessen) erhältlich ist und gleichzeitig auf das allgemeine Angebot des Online Shops verweisen</li>
</ul>
<h2>TIPP 3: SO GEHT MAN MIT AKTUELL VERGRIFFENEN ARTIKELN UM</h2>
<p>Es kann vorkommen, dass ein Artikel in einem Online-Shop kurzzeitig nicht erhältlich ist. Eine größere Nachfrage kann dazu führen, ebenso aber auch ein zu geringer Lagerbestand. Dennoch werden sich Kunden natürlich weiterhin für den Artikel interessieren und ihn kaufen wollen.</p>
<p><img src="/img/blogs/seo_tipps_e-commerce_vergriffene_produkte.jpg" alt="seo_tipps_e-commerce_vergriffene_produkte">{.object-cover .max-w-full .mb-5}</p>
<p>Je nachdem, wie lange der Artikel nicht griffbereit ist, sollte man <strong>SEO-technische Maßnahmen ergreifen</strong>. In Frage können dabei zum Beispiel diese Optionen kommen:</p>
<ul>
<li>Man bietet dem Kunden per Redirect Alternativen an, zum Beispiel gleichwertige Artikel anderer Marken</li>
<li>Eine Info, wann der Artikel wieder verfügbar ist, sollte auf jeden Fall vorhanden sein</li>
<li>Man bietet dem Kunden an, sich informieren zu lassen, wenn der Artikel wieder vorhanden ist, sodass dann ein Kauf möglich ist und kann somit gleichzeitig ggf. schon E-Mail-Adressen von potentiellen Kunden einsammeln</li>
<li>Generell ist es ratsam, auf der Produktseite dann auch alternative Artikel zu verlinken, die für den Kunden in Frage kommen können</li>
</ul>
<p>Grundsätzlich sind SEO-Maßnahmen nur ein Bestandteil einer ausgereiften Inbound-Marketing-Strategie. Nur eine ganzheitliche Betrachtung deines Angebots kann zu nachhaltigen Erfolgen führen. Wir helfen dir gern!</p>]]></content:encoded>
            <category>SEO</category>
            <category>Digitalisierung</category>
            <enclosure url="https://blueshoe.de/img/blogs/stephen-phillips-hostreviewsl.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Strategien für schlanke Docker Images]]></title>
            <link>https://blueshoe.de/blog/strategien-fur-schlanke-docker-images</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/strategien-fur-schlanke-docker-images</guid>
            <pubDate>Thu, 30 Jul 2020 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Docker ist in den letzten Jahren immer populärer geworden und hat sich mittlerweile praktisch zum Industriestandard für Containerisierung gemausert, sei es nun mittels docker-compose oder auch mit Kubernetes. Oftmals wird Docker auch gleich als Synonym für Container verwendet. Dabei gibt es einige Aspekte, auf die man beim Erstellen von Dockerfiles achten sollte. In diesem Blogpost zeigen wir euch ein paar Strategien, um möglichst schlanke Docker Images zu erzeugen.</p>
<p><img src="/img/blogs/pexels-chanaka.jpg" alt="pexels-chanaka">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalTitle{:size="lg" .mb-5}
<strong>WARUM BESCHÄFTIGEN WIR UNS MIT DOCKER IMAGES?</strong>
:::
:::globalParagraph
Docker Images haben sich auch bei Blueshoe als präferierte Technologie herausgestellt, um einerseits Anwendungen lokal zu entwickeln, und, um andererseits die Anwendungen in einem Testing-/Staging- oder Produktiv-System zu betreiben (“Deployment”). Neben der ungewohnten Situation als Python-Entwickler plötzlich wieder eine gewisse Build-Wartezeit zu haben, sind uns mit als erstes die zum Teil großen resultierenden Docker Images aufgefallen.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
<strong>GRÖSSE DER DOCKER IMAGES MINIMIEREN</strong>
:::
:::globalParagraph
Gerade das Minimieren der Imagegröße spielt für uns eine zentrale Rolle für die Erstellung unserer Dockerfiles. Das Docker Image sollte natürlich über alles Benötigte verfügen, um die Anwendung ausführen zu können - aber eben auch nicht mehr. Überflüssiges sind z. B. Software-Pakete und Libraries, die nur zum Kompilieren oder für das Ausführen automatisierter Tests benötigt werden.
:::
:::globalParagraph
Man kann gleich mehrere Gründe finden, wieso in einem Docker Image nur das absolut Nötigste vorhanden sein sollte:
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>Zum einen wird die Sicherheit des Images erhöht. Um eine simple Django-Anwendung auszuführen, benötige ich sicherlich kein ausgewachsenes Debian- oder Ubuntu-Image. Es genügt ein einfaches Python-Image als Basis, das praktisch kaum mehr können muss als mittels eines Python-Interpreters bzw. Application Server die Django-Anwendung auszuführen. Warum wird dadurch nun die Sicherheit erhöht? Ganz einfach, weniger Libraries reduzieren die Anzahl der Angriffsflächen. Der hier verlinkte Artikel ist zwar bereits ein Jahr alt, erläutert aber aufschlussreich die zugrunde liegende Problematik und stellt dar, wie man mit kleineren Versionen des Basis-Images bekannte CVEs (Common Vulnerabilities and Exposures) reduzieren kann.</li>
<li>Ein weiterer Aspekt ist die Geschwindigkeit. Im Betrieb kann es oberflächlich betrachtet eigentlich egal sein, ob das Docker Image nun zwei Gigabyte oder nur 200 Megabyte groß ist. Das Deployment ist oftmals automatisiert und ob es nun ein paar Minuten länger dauert, bis der (neue) Code bereitsteht, spielt i. d. R. auch keine große Rolle. Aber hier gilt natürlich: Was wir an unnötigem Datenverbrauch und überflüssigen Datenübertragungen einsparen können, sollten wir einsparen.</li>
<li>Als Entwickler kann ich hauptsächlich von kleinen Docker Images profitieren, wenn ich keine automatisierte Build-Pipeline habe, sondern die Images selbst baue und z. B. in einer Container Registry (der zentrale Speicher für Images) bereitstelle. Habe ich nun ein Dockerfile erzeugt, das zwei Gigabyte groß ist, und sitze Corona-bedingt im Home Office bei nicht ganz so optimaler Internetverbindung, kann der Upload durchaus eine gewisse Zeit dauern. Ärgerlich ist es allemal, wenn der Entwicklungsprozess unnötig in die Länge gezogen wird und sei es auch nur für eine halbe Minute bei jedem Build.</li>
<li>Der nächste Aspekt ist der Ressourcenverbrauch. Nicht nur auf meinem Laptop, der mit der Zeit durch immer mehr Docker Images zugemüllt wird, die ich regelmäßig aufräumen muss, sondern auch in der Container Registry. Diese könnte z. B. von Gitlab gehostet sein. Klar ist Speicherplatz i. d. R. kein sonderlich großer Kostenfaktor, wenn allerdings jedes Docker Image ein bis zwei Gigabyte groß wird, und Woche für Woche dutzende Images in die Registry gepusht werden, kann sich der angesammelte Speicherplatz durchaus auf eine respektable Menge summieren. Wenn wir es schaffen, die Docker Images auf die Hälfte oder ein Viertel ihrer Größe zu reduzieren, haben wir schon einiges gewonnen.</li>
<li>Zu guter Letzt ist natürlich auch die Umwelt zu nennen. Jedes Docker Image wird zunächst mal in eine Registry gepusht und von dort wieder heruntergeladen. Sei es nun auf den Laptop eines Entwicklers, oder von einem Produktiv-System. Wenn meine Docker Images unnötigerweise viermal so groß sind, wie sie sein müssten, sorge ich dafür, dass permanent viermal so viel Traffic durch die Verwendung meiner Images entsteht oder zusätzliche Speichersysteme betrieben werden müssen.
:::</li>
</ul>
<p><img src="/img/blogs/keppel.jpg" alt="Strategies for slim Docker images">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalTitle{:size="md" .mb-5}
<strong>WELCHE ANFORDERUNGEN BESTEHEN FÜR EIN DOCKER IMAGE?</strong>
:::
:::globalParagraph
Trotz aller Minimierung der Imagegröße müssen natürlich auch einige Anforderungen bedacht werden. Wenn ich meine Docker Images bis aufs letzte Megabyte optimiere, danach aber nicht mehr vernünftig damit entwickeln kann, oder die Anwendung nicht robust läuft, ist nicht viel gewonnen. Im Endeffekt kann man die Überlegungen auf die drei Ebenen Entwicklung, Testing/Staging und Production herunterbrechen.
:::
:::globalParagraph
Als Entwickler möchte ich möglichst wenig in meinem Arbeitsfluss gestört werden, mein Komfort steht hier definitiv im Vordergrund. Das heißt, ich benötige ein Docker Image, das im Idealfall eine möglichst geringe Build-Zeit hat und das z. B. live code-reloading unterstützt. Außerdem möchte ich vielleicht noch weitere Tools wie den Python Remote Debugger oder auch Telepresence einsetzen. Evtl. benötige ich dafür weitere Dependencies, die z. B. in einem Produktiv-System nicht relevant sind.
:::
:::globalParagraph
Für Testing-, bzw. Staging- und für Produktiv-Systeme sehen die Anforderungen sehr ähnlich aus. Hier steht ganz klar die Sicherheit an erster Stelle. Ich möchte möglichst wenig Libraries und Packages auf dem System haben, die für die Ausführung der Anwendung gar nicht gebraucht werden. Eigentlich sollte ich als Entwickler gar keinen Bedarf mehr haben, mit dem laufenden Container zu interagieren, wodurch hier sämtliche Bedenken bzgl. Komfort vernachlässigt werden können.
:::
:::globalParagraph
Aber gerade auf einem Testing-/Staging-System kann es dennoch sehr praktisch sein, wenn ich dort zu Debugging-Zwecken gewisse Packages verfügbar habe. Das ist allerdings nicht unbedingt ein Argument dafür, diese Packages bereits im Docker Image für den Fall der Fälle bereitzuhalten. Mittels Telepresence lässt sich ja z. B. in einer Kubernetes-Umgebung ein Deployment austauschen. Das bedeutet, ich kann mir lokal ein Docker Image bauen, das all meine benötigten Dependencies bereitstellt, und dieses in meinem Testing-/Staging-Cluster ausführen. Wie man das bewerkstelligen kann, haben wir in einem unserer letzten Blogposts - <a href="/loesungen/cloud-native-development/">Cloud Native Kubernetes development</a>{.bs-link-blue} - dargestellt.
:::
:::globalParagraph
Gerade zu Beginn der Entwicklungsphase eines Projekts kann der oben beschriebene Use-Case recht häufig auftreten. Für ein Produktiv-System sollte das aber eigentlich keine Rolle mehr spielen. Dort möchte ich vielleicht noch Logs betrachten, was einerseits mittels kubectl bewerkstelligt werden kann oder evtl. auch mit einer Log-Collector-Lösung. Im Endeffekt möchte ich, dass das Testing-/Staging-System mit dem identischen Docker Image ausgeführt wird, wie das Produktiv-System. Ansonsten besteht z. B. die Gefahr, dass im Produktiv-System ein Fehlverhalten auftritt, welches im Testing-/Staging-System aufgrund der unterschiedlichen Umgebung nicht aufgetreten ist.
:::
:::globalParagraph
Auch von Operations-Seite können Anforderungen kommen, z. B. in Form eines Vulnerability-Checks, um zu gewährleisten, dass bekannte Vulnerabilities, wo es möglich ist, gar nicht erst im Image vorhanden sind, bzw. damit behebbare Vulnerabilities auch behoben sind. Weiterhin kann auch eine Company-Policy Einfluss auf das Dockerfile haben, entweder die der eigenen Firma oder die des Auftraggebers. Ein Szenario wäre z. B. der Ausschluss bestimmter Basis Images oder das Gewährleisten der Verfügbarkeit gewisser Packages oder Libraries.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
<strong>EVOLUTION EINES DOCKER IMAGES</strong>
:::
:::globalParagraph
In den folgenden Abschnitten wollen wir nun betrachten, welche Schritte man konkret vornehmen kann, um das eigene Dockerfile zu optimieren. Zunächst schauen wir uns Voraussetzungen an, die das resultierende Docker Image beeinflussen können. Außerdem werfen wir einen Blick auf die Strategien und Patterns für Dockerfiles, bevor wir uns in mehreren Iterationen die Auswirkungen der Optimierungen anschauen.
:::</p>
<p>:::globalTitle{:size="md" .mb-5}
<strong>VORAUSSETZUNGEN</strong>
:::
:::globalParagraph
An erster Stelle steht die Auswahl des Basis Images. Dieses muss auch im Dockerfile an erster Stelle definiert werden. Für eine Django-Anwendung genügt uns ein Python Base Image. Wir könnten zwar natürlich auch einfach ein Ubuntu Image wählen, aber wir wollen die Image-Größe ja möglichst klein halten und redundante Packages gar nicht erst im Image haben. Der Docker Hub stellt viele vorgefertigte Images bereit. Auch für Python gibt es verschiedene Images, die anhand der Python Versionsnummer oder auch mit den Begriffen slim und alpine unterschieden werden.
:::
:::globalParagraph
Das “Standard” Python Base Image basiert auf Debian Buster und stellt somit die größte der drei Varianten dar. Die slim Variante basiert ebenfalls auf Debian Buster, allerdings mit herunter getrimmten Packages. Das resultierende Image ist somit natürlich kleiner. Die dritte Variante ist alpine und basiert, wie der Name vermuten lässt, auf Alpine Linux. Das entsprechende Python Base Image hat die geringste Größe, allerdings kann es gut sein, dass man benötigte Packages im Dockerfile nachinstallieren muss.
:::
:::globalParagraph
Einen u. U. großen Einfluss auf das Dockerfile haben auch die System Runtime und Build Dependencies. Gerade beim Base Image gilt zu beachten, dass ein auf Alpine basierendes Image mit musl libc ausgeliefert wird und nicht mit glibc, wie auf Debian-basierten Base Images. Selbiges gilt für gcc, der GNU Compiler Collection, die nicht standardmäßig auf Alpine verfügbar ist.
:::
:::globalParagraph
Als Django-Entwicklung haben Anwendungen natürlich einige pip-Requirements. Je nach gewähltem Basis-Image muss das Dockerfile also sicherstellen, dass alle für die pip-Requirements benötigten System-Packages und Libraries installiert sind. Aber auch die pip-Requirements selbst können Auswirkungen auf das Dockerfile haben, wenn ich zusätzliche Packages für die Entwicklung habe, die jedoch im Produktiv-Betrieb nicht benötigt werden und dort auch nicht vorhanden sein sollen. Ein Beispiel dafür ist das pydevd-pycharm Package, welches wir lediglich für den Python Remote Debugger in PyCharm benötigen.
:::</p>
<p>:::globalTitle{:size="md" .mb-5}
<strong>STRATEGIEN UND PATTERNS FÜR DOCKERFILES</strong>
:::
:::globalParagraph
Mit der Zeit haben sich verschiedene Strategien und Patterns entwickelt, um Dockerfiles zu gestalten und zu optimieren. Die Herausforderung, eine kleine Image-Größe zu erhalten, knüpft sich stark daran, wie aus dem Dockerfile ein Image wird. Jede Instruktion fügt einen neuen Layer hinzu, wobei jeder Layer auf dem vorherigen aufbaut. Mit diesem Wissen ist es natürlich naheliegend zu versuchen, die Anzahl der Layer und die Größe der verschiedenen Layer möglichst gering zu halten. Man kann z. B. nicht mehr benötigte Artefakte innerhalb eines Layers löschen, oder mit verschiedenen Shell Tricks und anderen Logiken verschiedene Instruktionen in einem Layer zusammenfassen.
:::
:::globalParagraph
Ein mittlerweile veraltetes Pattern ist das sogenannte Builder Pattern. Hierfür erstellt man ein Dockerfile für die Entwicklung, das Builder-Dockerfile. Dieses enthält alles benötigte, um die Applikation zu bauen. Für die Testing-/Staging- und Produktiv-Umgebungen erstellt man ein zweites, herunter getrimmtes Dockerfile. Dieses enthält die Anwendung an sich und ansonsten nur das Nötigste, um die Software auszuführen. Dieser Ansatz ist zwar machbar, hat allerdings zwei große Nachteile: Zum einen ist es definitiv nicht ideal zwei verschiedene Dockerfiles warten zu müssen, zum anderen entsteht dadurch ein komplizierter Workflow:
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>Builder Image kompilieren</li>
<li>Container anhand Builder Image erstellen</li>
<li>benötigte Artefakte aus Container kopieren bzw. extrahieren</li>
<li>Container löschen</li>
<li>Production Image anhand der extrahierten Artefakte builden
:::</li>
</ul>
<p>:::globalParagraph
Dieser Ablauf lässt sich natürlich durch Scripts automatisieren, ist aber dennoch nicht ideal.
:::
:::globalParagraph
Als bessere Lösung finden mittlerweile sogenannte <strong>Multi-Stage Dockerfiles</strong> immer mehr Verbreitung. Diese werden von Docker selbst empfohlen. Ein Multi-Stage Dockerfile folgt einer eigentlich recht simplen Struktur:
:::
:::globalParagraph
Die verschiedenen Stages werden durch FROM-Statements voneinander getrennt. Dabei kann eine “Stage” auch immer mit einem Namen versehen werden, was es einfacher macht, diese Stage zu referenzieren. Da jede Stage mit einem FROM-Statement beginnt, wird auch in jeder Stage ein neues Base-Image verwendet. Der Vorteil mehrere Stages ist nun, dass sich einzelne Artefakte aus einer Stage selektiv in eine nächste kopieren lassen. Es ist auch möglich an einer gewissen Stage zu stoppen, um z. B. Debugging-Funktionalitäten bereitzustellen, also um verschiedene Stages für Development/Debug und Staging/Production zu unterstützen.
:::
:::globalParagraph
Wird während des Build-Prozesses keine Stage angegeben, an der gestoppt werden soll, wird das komplette Dockerfile durchlaufen, was im Docker Image für das Produktiv-System resultieren sollte. Im Vergleich zum Builder Pattern wird dabei nur ein Dockerfile benötigt und es ist kein Build-Script nötig, um den Workflow abzubilden.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
<strong>EVALUATION VERSCHIEDENER DOCKERFILES</strong>
:::
:::globalParagraph
Mit all diesem Wissen machen wir uns nun an die Evaluation verschiedener Dockerfiles. Wir haben sechs verschiedene Dockerfiles geschrieben, die wir anhand der Größe evaluiert haben. Zunächst schauen wir uns nun der Reihe nach die Dockerfiles mit den gewählten Optimierungen an. Allen Dockerfiles ist gemeinsam, dass postgresql-client bzw. postgresql-dev installiert wird, das Kopieren und Installieren von pip-Requirements sowie das Kopieren der Anwendung.
:::</p>
<p>:::globalTitle{:size="sm" .mb-5}
<strong>DOCKERFILE 1 - NAIVE</strong>
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">FROM python:3.8

RUN apt-get update
RUN apt-get install -y --no-install-recommends postgresql-client

COPY requirements.txt /requirements.txt
RUN pip install -r /requirements.txt

COPY src /app

WORKDIR /app
</code></pre>
<p>:::
:::globalParagraph
Das erste Dockerfile basiert auf dem python:3.8 Basis Image. Wir bekommen also alle Debian “Nuts and Bolts” mitgeliefert, um innerhalb des Containers praktisch ohne Einschränkungen arbeiten zu können.
:::</p>
<p>:::globalTitle{:size="sm" .mb-5}
<strong>DOCKERFILE 2 - NAIVE, LÖSCHEN DER APT-LISTS</strong>
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">FROM python:3.8

RUN apt-get update \
    &#x26;&#x26; apt-get install -y --no-install-recommends postgresql-client \
    &#x26;&#x26; rm -rf /var/lib/apt/lists/*

COPY requirements.txt /requirements.txt
RUN pip install -r /requirements.txt

COPY src /app

WORKDIR /app
</code></pre>
<p>:::
:::globalParagraph
Dieses Dockerfile ist identisch zum ersten, mit der Ausnahme, dass nach der Installation der benötigten zusätzlichen Packages der Inhalt des Verzeichnisses /var/lib/apt/lists/ gelöscht wird. In diesem Verzeichnis werden nach einem apt update Paketlisten gespeichert, die für unser Docker Image nicht mehr relevant sind.
:::</p>
<p>:::globalTitle{:size="sm" .mb-5}
<strong>DOCKERFILE 3 - ALPINE NAIVE</strong>
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">FROM python:3.8-alpine

RUN apk update &#x26;&#x26; apk --no-cache add libpq gcc python3-dev musl-dev linux-headers postgresql-dev

COPY requirements.txt /requirements.txt
RUN pip install -r /requirements.txt

COPY src /app

WORKDIR /app
</code></pre>
<p>:::
:::globalParagraph
Unser drittes Dockerfile basiert ganz simpel auf Alpine Linux. Da in Alpine einige Packages für den Betrieb der Anwendung fehlen, müssen diese zunächst noch installiert werden.
:::</p>
<p>:::globalTitle{:size="sm" .mb-5}
<strong>DOCKERFILE 4 - ALPINE LINUX, LÖSCHEN VON BUILD DEPENDENCIES</strong>
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">FROM python:3.8-alpine

COPY requirements.txt /requirements.txt

RUN apk update &#x26;&#x26; apk add --no-cache --virtual .build-deps gcc python3-dev musl-dev linux-headers postgresql-dev &#x26;&#x26; \
 apk --no-cache add libpq &#x26;&#x26; \
 pip install -r /requirements.txt &#x26;&#x26; \
 apk del .build-deps

COPY src /app
WORKDIR /app
</code></pre>
<p>:::
:::globalParagraph
Das nächste auf Alpine Linux basierende Dockerfile fügt im Grunde auch nur ein Löschen von Build Dependencies hinzu, also von zusätzlich installierten Packages, die nur für das Builden des Images benötigt werden, nicht aber für das Ausführen der Anwendung. Damit das in nur einer Anweisung funktioniert, muss die COPY-Anweisung für die pip-Requirements nach oben geschoben werden, um das Installieren der pip-Requirements zwischen dem Installieren und Löschen der Build Dependencies durchführen zu können.
:::</p>
<p>:::globalTitle{:size="sm" .mb-5}
<strong>DOCKERFILE 5 - ALPINE LINUX, MULTI-STAGE</strong>
:::
:::BlogCode{.mb-5}</p>
<pre><code class="language-docker">FROM python:3.8-alpine as base

FROM base as builder

RUN apk update &#x26;&#x26; apk add gcc python3-dev musl-dev linux-headers postgresql-dev
RUN mkdir /install
WORKDIR /install
COPY requirements.txt /requirements.txt
RUN pip install --prefix=/install -r /requirements.txt

FROM base

COPY --from=builder /install /usr/local
COPY src /app
RUN apk --no-cache add libpq
WORKDIR /app
</code></pre>
<p>:::
:::globalParagraph
Das letzte Dockerfile nutzt das Multi-Stage Pattern. Beide Stages nutzen das Alpine Linux Python Base Image. In der ersten Stage, builder, werden die benötigten Build Dependencies installiert, das Verzeichnis /install als WORKDIR genutzt sowie anschließend die pip-Requirements kopiert und installiert. Die zweite Stage kopiert nun den Inhalt aus dem /install-Verzeichnis der ersten Stage, kopiert den Code der Anwendung und installiert mit libpq noch ein Package, das für die Ausführung der Anwendung benötigt wird.
:::</p>
<p>:::globalTitle{:size="md" .mb-5}
<strong>AUSWERTUNG DER RESULTIERENDEN GRÖSSEN</strong>
:::
:::globalParagraph
Folgende Tabelle zeigt die resultierende Größe der Docker Images für unsere fünf Dockerfiles:
:::</p>
<p><img src="/img/blogs/bildschirmfoto_2.jpg" alt="bildschirmfoto_2">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Den quantitativ deutlichsten Sprung kann man bei der Nutzung des Alpine-basierten Base Images beobachten. Sind die beiden Debian-basierten Docker Images noch jeweils über ein Gigabyte groß, spart die Nutzung von Alpine Linux über die Hälfte ein. Das daraus resultierende Image ist nur noch knapp unter 400 Megabyte groß. Durch geschickt geschriebene Anweisungen und somit optimiertere Dockerfiles können wir die Größe sogar mehr als halbieren und sind letztendlich bei 176 Megabyte.
:::
:::globalParagraph
Das Multi-Stage Dockerfile liegt bei knapp 155 Megabyte. Im Vergleich zum zuvor optimierten Dockerfile haben wir hier nicht allzu viel eingespart. Das Dockerfile ist durch die verschiedenen Stages zwar etwas umfangreicher, aber auch wesentlich aufgeräumter und wie weiter oben erläutert, mit den verschiedenen Stages deutlich flexibler. Mit diesem Image sind wir bei einer Größe von gerade mal 15% des ersten naiven Debian-basierten Images angelangt. Auch im Vergleich zum naiven Alpine-basierten Image haben wir mehr als 60% eingespart.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
<strong>UNSERE EMPFEHLUNG: MULTI-STAGE DOCKERFILES</strong>
:::
:::globalParagraph
Unsere ganz klare Empfehlung ist die Verwendung von Multi-Stage Dockerfiles. Wie wir bei der Evaluation beeindruckend sehen können, lässt sich die resultierende Image-Größe dadurch deutlich reduzieren. Sofern es die Gegebenheiten und die Anwendung zulassen, sollte hinsichtlich der Image-Größe auch ein Alpine-basiertes Base Image verwendet werden.
:::
:::globalParagraph
Aber nicht nur aufgrund der resultierenden Image-Größe können wir Multi-Stage Builds empfehlen. Vor allem die Flexibilität durch die verschiedenen Stages ist aus unserer Sicht ein großer Pluspunkt. Damit können wir unseren Entwicklungsprozess bis hin zum Production-Deployment mit nur einem Dockerfile unterstützen und müssen nicht mehrere Dockerfiles maintainen.
:::</p>]]></content:encoded>
            <category>Docker</category>
            <category>Entwicklung</category>
            <category>Sicherheit</category>
            <enclosure url="https://blueshoe.de/img/blogs/pexels-chanaka.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Varnish - der geheime Held der Website Geschwindigkeit mit Kubernetes]]></title>
            <link>https://blueshoe.de/blog/varnish-website-geschwindigkeit-mit-kubernetes</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/varnish-website-geschwindigkeit-mit-kubernetes</guid>
            <pubDate>Mon, 28 Jul 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Varnish ist ein spezieller HTTP-Cache. Er sitzt vor deiner Webanwendung und speichert fertige Seiten, damit Anfragen blitzschnell direkt aus dem Speicher beantwortet werden können. So wird dein Backend entlastet und deine Seite spürbar schneller, gerade in Kubernetes-Setups ein großer Vorteil.</p>
<p><img src="/img/blog/varnishk8s.svg" alt="Varnish Kubernetes"></p>
<p>::GlobalBlogLevelInfo</p>
<ul>
<li>Kubernetes Basics</li>
<li>Docker Basics</li>
<li><a href="https://de.wikipedia.org/wiki/HTTP_Caching">HTTP Caching</a>{target="_blank"} Basics</li>
<li><a href="https://varnish-cache.org/">Varnish HTTP Cache</a>{target="_blank"}</li>
</ul>
<p>Solltest du Fragen haben, oder dir etwas unklar sein, kannst du die Kommentarfunktion unter dem Artikel nutzen.
::</p>
<h2>Warum Varnish deine Website transformiert</h2>
<p>Performance ist alles im Web. Wenn deine Nutzer warten müssen, springen sie ab. Varnish löst dieses Problem elegant: Als HTTP-Cache sitzt er vor deiner Anwendung und liefert Inhalte blitzschnell aus dem Speicher aus. Das Ergebnis? Bis zu 90% weniger Last auf dein Backend und deutlich schnellere Ladezeiten.</p>
<h2>Lass uns starten: Das brauchst du</h2>
<p>Bevor wir loslegen, hier kurz die vier Dateien, die du für ein einfaches Setup benötigst:</p>
<ul>
<li><code>default.vcl</code>: deine Varnish-Konfiguration</li>
<li><code>Dockerfile</code>: damit baust du dein Varnish-Image inklusive VCL</li>
<li><code>varnish.yaml</code>: Deployment und Service für Kubernetes</li>
<li><code>Ingress.yaml</code>: optional, damit deine Website über einen Domainnamen erreichbar wird</li>
</ul>
<h3>Die VCL Datei verstehen und anpassen</h3>
<p>Schauen wir uns erstmal die einzelnen Teile der <code>default.vcl</code> genauer an:</p>
<pre><code class="language-vcl">backend default {
    .host = "website-svc";
    .port = "3001";
    .first_byte_timeout = 300s;
}
</code></pre>
<p>Der erste Block legt dein Backend fest. <code>website-svc</code> ist dabei der Name des Kubernetes-Services, auf den Varnish zugreift. Der Port <code>3001</code> ist der Port deiner Anwendung (zum Beispiel Django), der innerhalb des Clusters erreichbar ist. Mit <code>first_byte_timeout</code> stellst du ein, wie lange Varnish auf die erste Antwort vom Backend wartet.</p>
<pre><code class="language-vcl">sub vcl_recv {
    if (req.method == "GET" &#x26;&#x26; req.http.Cookie !~ "sessionid") {
        unset req.http.Cookie;
    }
}
</code></pre>
<p><code>vcl_recv</code> wird bei jeder eingehenden Anfrage aufgerufen. Hier wird entschieden, ob Cookies entfernt werden. In diesem Fall entfernt Varnish alle Cookies von GET-Requests, die nicht <code>sessionid</code> enthalten. Das macht Caching effektiver, weil verschiedene Cookie-Werte sonst unterschiedliche Cache-Objekte erzeugen würden.</p>
<pre><code class="language-vcl">sub vcl_backend_response {
    if (bereq.url ~ "^/de-de/aktuelles/.*") {
        set beresp.ttl = 2h;
    } else {
        set beresp.ttl = 12h;
    }
}
</code></pre>
<p><code>vcl_backend_response</code> wird aufgerufen, wenn Varnish das Backend befragt hat und die Antwort verarbeitet. Hier legst du fest, wie lange Varnish den Inhalt cachen soll. Nachrichten-Seiten bekommen hier zum Beispiel nur 2 Stunden, alles andere bleibt 12 Stunden im Cache.</p>
<h2>Fortgeschrittene VCL-Techniken für die Praxis</h2>
<p><strong>Grace Mode &#x26; Saint Mode (Backend-Ausfall überbrücken):</strong><br>
Mit Grace und Saint Mode kannst du veraltete Inhalte ausliefern, wenn das Backend nicht erreichbar ist. Das erhöht die Ausfallsicherheit enorm.</p>
<pre><code class="language-vcl">sub vcl_backend_fetch {
    if (beresp.status == 500 || beresp.status == 503) {
        set beresp.saintmode = 5m; // Markiere Backend für 5 Min. als "krank"
        return (abandon);
    }
    set beresp.grace = 2h; // Erlaube, diesen Inhalt 2h über seine TTL hinaus zu servieren
}
</code></pre>
<p><strong>Cache-Keys anpassen:</strong><br>
Manipuliere den Cache-Key, um z.B. unwichtige Query-Parameter zu ignorieren und so die Hit-Rate zu erhöhen.</p>
<pre><code class="language-vcl">sub vcl_hash {
    // Nur 'id' und 'lang' für den Cache-Key berücksichtigen
    if (req.url ~ "\?") {
        set req.hash += regsuball(req.url, "^.*\?((?:id|lang)=[^&#x26;]+).*$", "\1");
    }
    // ...
}
</code></pre>
<p><strong>Sicheres Purging via API:</strong><br>
Richte einen sicheren HTTP-Endpoint zum gezielten Leeren des Caches ein.</p>
<pre><code class="language-vcl">acl purge {
    "localhost";
    "192.168.1.0"/24; // Erlaube Purging nur aus diesem Netz
}

sub vcl_recv {
    if (req.method == "PURGE") {
        if (!client.ip ~ purge) {
            return (synth(405, "Not allowed."));
        }
        return (hash);
    }
}
</code></pre>
<h3>Dockerfile: VCL ins Image legen</h3>
<pre><code class="language-dockerfile">FROM varnish:stable
COPY default.vcl /etc/varnish/
</code></pre>
<p>So stellst du sicher, dass deine VCL immer direkt im Container liegt. Dein Image baust du anschließend mit:</p>
<pre><code class="language-bash">docker build -t varnish:latest .
</code></pre>
<h3>Deployment und Service definieren</h3>
<p>Das Deployment sorgt dafür, dass dein Varnish-Pod dauerhaft läuft und Kubernetes ihn automatisch neu startet, wenn er abstürzt. In unserem Beispiel läuft nur eine Instanz (replicas: 1), für Produktionsumgebungen kannst du hier aber auch mehrere Replikas eintragen.</p>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
  name: varnish
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: varnish
    spec:
      containers:
      - name: varnish
        image: varnish:latest
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: varnish
spec:
  selector:
    app: varnish
  ports:
    - port: 80
      targetPort: 80
</code></pre>
<p>Der zugehörige Service macht deinen Pod im Cluster erreichbar. Er verbindet den Ingress oder andere Pods mit deinem Varnish-Container. Wichtig ist dabei, dass targetPort und containerPort zusammenpassen.</p>
<p><code>Super!</code> Damit ist nun dein Pod im Cluster erreichbar.</p>
<h2>Ingress-Konfiguration für Domain-Routing</h2>
<p>Der Ingress ist das letzte Glied in der Kette. Er sorgt dafür, dass Anfragen aus dem Internet in deinen Cluster gelangen und dort an den richtigen Service, also deinen Varnish, weitergeleitet werden. Dadurch kannst du Domains und TLS-Zertifikate zentral verwalten.</p>
<p>Ein einfaches Beispiel sieht so aus:</p>
<pre><code class="language-yaml">apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: varnish-ingress
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  ingressClassName: nginx
  rules:
  - host: "example.com"
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: varnish
            port:
              number: 80
</code></pre>
<p>Achte darauf, dass der Name des Services und der Port exakt zu deinem vorher definierten Service passen. So wird der komplette Webtraffic durch Varnish geschleust und deine Anwendung profitiert vom Cache.</p>
<h2>Monitoring und Debugging</h2>
<p>Hier noch ein paar praktische Befehle, um deinen Cache und den Varnish-Server zu überwachen und besser zu steuern:</p>
<pre><code class="language-bash">varnishstat
</code></pre>
<p>Zeigt live Cache-Statistiken, zum Beispiel Hits und Misses.</p>
<pre><code class="language-bash">varnishlog
</code></pre>
<p>Zeigt genau, warum Requests gecacht wurden oder nicht.</p>
<pre><code class="language-bash">varnishadm ban "req.url ~ .*"
</code></pre>
<p>Damit leerst du gezielt den Cache und erzwingst neue Inhalte.</p>
<pre><code class="language-bash">varnishadm ping
</code></pre>
<p>Prüft, ob dein Varnish-Admin erreichbar ist.</p>
<pre><code class="language-bash">varnishadm status
</code></pre>
<p>Zeigt den aktuellen Status deines Varnish-Prozesses.</p>
<h2>Best Practices für optimales Caching</h2>
<h3>1. Cache-Strategien definieren</h3>
<ul>
<li>Statische Inhalte länger cachen (CSS, JS, Bilder)</li>
<li>Dynamische Inhalte kürzer cachen</li>
<li>Session-basierte Inhalte individuell behandeln</li>
</ul>
<h3>2. Performance-Monitoring einrichten</h3>
<ul>
<li>Regelmäßig Cache-Hit-Rate überprüfen</li>
<li>Speicherauslastung im Blick behalten</li>
<li>Backend-Gesundheit überwachen</li>
</ul>
<h3>3. Cache-Invalidierung planen</h3>
<ul>
<li>Automatisierte Prozesse für Content-Updates</li>
<li>Gezielte Invalidierung statt komplettes Leeren</li>
<li>Health-Checks für Backend-Server</li>
</ul>
<h2>Varnish im Vergleich: Die richtige Caching-Strategie wählen</h2>
<p>Varnish ist extrem mächtig, aber nicht immer die einzige Lösung. Wie schlägt es sich im Vergleich zu anderen Caching-Mechanismen?</p>
<p>| Caching-Lösung      | Stärken                                                                 | Schwächen                                                        | Optimal für...                                                                                   |
| :------------------ | :--------------------------------------------------------------------- | :--------------------------------------------------------------- | :---------------------------------------------------------------------------------------------- |
| <strong>Varnish Cache</strong>   | <strong>Maximale Flexibilität</strong> durch VCL; Caching von ganzen HTTP-Objekten; Features wie Grace/Saint Mode. | <strong>Höhere Komplexität</strong> als einfache Caches; kein nativer TLS-Support (benötigt einen Proxy davor). | ...komplexe Websites (z.B. E-Commerce, News-Portale) mit dynamischen Inhalten, die eine feingranulare Cache-Steuerung benötigen. |
| <strong>Nginx Caching</strong>   | <strong>Einfache Konfiguration</strong>; direkt im Webserver integriert; sehr performant; kann TLS terminieren. | <strong>Weniger flexible</strong> Cache-Logik als Varnish; primär für statische Assets und einfache Responses. | ...einfachere Websites und Anwendungen, bei denen ein unkomplizierter Cache für statische Dateien und API-Antworten ausreicht. |
| <strong>CDN (z.B. Cloudflare)</strong> | <strong>Global verteilt</strong> (geringe Latenz weltweit); Schutz vor DDoS-Angriffen; einfaches Setup. | <strong>Kostenintensiver</strong>; weniger Kontrolle über die Cache-Invalidierung; "Blackbox". | ...global agierende Websites, die von einem verteilten Cache und zusätzlichen Sicherheitsfeatures profitieren wollen. |</p>
<h2>Häufig gestellte Fragen (FAQ)</h2>
<h3>Wie kann ich überprüfen, ob eine Seite aus dem Varnish-Cache geladen wird?</h3>
<p>Überprüfe die HTTP-Header der Antwort in den Entwicklertools deines Browsers. Varnish fügt typischerweise Header wie <code>X-Varnish</code> (interne Transaktions-ID) und <code>Age</code> (wie lange das Objekt im Cache liegt) hinzu. Ein <code>Age</code>-Wert größer als 0 ist ein sicheres Zeichen für einen Cache-Hit.</p>
<h3>Warum ist meine Cache-Hit-Rate niedrig?</h3>
<p>Die häufigsten Gründe sind:</p>
<ol>
<li><strong>Cookies:</strong> Varnish cacht standardmäßig keine Anfragen mit Cookies. Passe deine <code>vcl_recv</code> an, um unnötige Cookies zu entfernen.</li>
<li><strong><code>Set-Cookie</code>-Header vom Backend:</strong> Wenn dein Backend einen <code>Set-Cookie</code>-Header sendet, wird die Antwort nicht gecacht.</li>
<li><strong><code>Cache-Control</code>-Header:</strong> Achte auf <code>Cache-Control: private</code> oder <code>max-age=0</code> vom Backend.</li>
</ol>
<h3>Wie schütze ich den Varnish-Admin-Port in Kubernetes?</h3>
<p>Der Admin-Port sollte niemals extern erreichbar sein. Stelle sicher, dass dein Kubernetes-Service für Varnish nur den HTTP-Port (z.B. 80) freigibt. Der Zugriff auf <code>varnishadm</code> sollte nur über <code>kubectl exec</code> in den Pod erfolgen.</p>
<h3>Wie viel RAM bzw. Speicher brauche ich für Varnish?</h3>
<p>Das hängt stark von deinem Usage‑Profil ab. Empfohlen wird eine RAM‑Konfiguration zwischen 1 GB und 16 GB, kombiniert mit SSD‑Speicher.</p>
<h3>Kann Varnish auch ohne Query‑Parameter cachen?</h3>
<p>Ja, du kannst Query‑Parameter entfernen (z. B. per RegEx in VCL), um nur ein Objekt zu cachen:</p>
<pre><code class="language-vcl">sub vcl_recv {
  set req.url = regsub(req.url, "\?.*", "");
}
</code></pre>
<h3>Wie lassen sich benutzerdefinierte Fehlerseiten konfigurieren?</h3>
<p>Über <code>vcl_error</code> kannst du eigene HTML‑Seiten mit einer synthetischen Antwort erstellen. Beispiel:</p>
<pre><code class="language-vcl">sub vcl_error {
  set obj.http.Content-Type = "text/html; charset=utf-8";
  synthetic {"&#x3C;html>…&#x3C;/html>"};
  deliver;
}
</code></pre>
<h2>Fazit</h2>
<p>Damit bist du jetzt komplett durch. Dein Setup von <code>default.vcl</code> bis zum Ingress steht und Varnish sorgt jetzt dafür, dass deine Seite deutlich schneller lädt. Mit der richtigen Konfiguration wird Varnish zum geheimen Helden deiner Website-Performance.</p>
<p>:GlobalButton{:url="https://varnish-cache.org" :label="Mehr über Varnish erfahren" :target="_blank" :color="blue" .mb-6}</p>]]></content:encoded>
            <category>Kubernetes</category>
            <category>Varnish</category>
            <category>Entwicklung</category>
            <category>Performance</category>
            <enclosure url="https://blueshoe.de/img/blog/varnishk8s.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Visual Regression mit Lost Pixel und Gitlab]]></title>
            <link>https://blueshoe.de/blog/visuelle-regression-in-gitlab-mit-lost-pixel</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/visuelle-regression-in-gitlab-mit-lost-pixel</guid>
            <pubDate>Fri, 07 Mar 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Bei Überarbeitung von CSS Regeln gibt es oftmals Seiteneffekte die zu spät bemerkt werden. Um diese zu finden eigenen sich Visual Regression Tests. Lost Pixel ist ein beliebtes, einfaches und sehr gutes Tool um diese Art von Tests auszuführen. Lost Pixel kommt von Haus aus mit Github Actions Support - die Integration in Gitlab ist also nicht ganz offensichtlich. Wir zeigen wie man Lost Pixel und Gitlab CI/CD zusammen verwendet.</p>
<p><img src="/img/blog/lost_pixel.svg" alt="Visual Regression mit Lost Pixel und Gitlab">{.object-cover .max-w-full .mb-5}</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Was ist Visual Regression Testing?
::
::GlobalParagraph
Visual Regression Testing ermöglicht die Identifizierung von optischen Veränderungen basierend auf einer vorher festgelegten Basis. Diese Veränderungen beinhalten unter Umständen Regressionen, welche somit schnell und kostengünstig erkannt werden. Moderne Anwendungen beinhalten oftmals eine Menge JavaScript und CSS. Änderungen an dem Quellcode, oder auch einfache Updates können zu Seiteneffekten führen. Um diese aufzudecken, eignet sich Visual Regression Testing sehr gut.
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Wie funktioniert Visual Regression Testing?
::
::GlobalParagraph
Das Vorgehen für VRT-Tools ist immer ähnlich:
::
::GlobalBlock{.ol-decimal .mb-4}</p>
<ol>
<li>Es wird eine Menge von Komponenten oder Seiten definiert, welche zu prüfen sind.</li>
<li>Basierend darauf wird eine Baseline generiert.</li>
<li>Bei Code Änderungen und Updates werden Screenshots gemacht und diese mit der Baseline verglichen.
::</li>
</ol>
<p>::GlobalParagraph
Oftmals arbeiten Tools im Bereich des Testing für visuelle Regressionen mit einem Threshold. Das bedeutet, die Abweichung von der Baseline hat eine Toleranz - absolut oder relativ. Beispielsweise könnte man festlegen, dass 10 Pixel oder 1% Abweichung von der Baseline akzeptiert werden.
::
::GlobalParagraph
Warum ist das so? - Das Rendering von Websites unterscheidet sich teilweise von Browser zu Browser sowie Betriebssystem zu Betriebssystem. Hier ist eine gewisse Toleranz ausgesprochen hilfreich.
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Lost Pixel in der Praxis
::
::GlobalParagraph
<a href="https://www.lost-pixel.com/">Lost Pixel</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} ist ein Tool der Firma nineLemon. Es ist cloud-basiert. Die einfachste Integration in eine CI Pipeline lässt sich mit Github Actions realisieren. Dabei übernimmt die Lost Pixel Cloud Applikation die Verwaltung der Baseline. Die Screenshots der Baseline müssen nicht in das Repository eingecheckt werden.
::</p>
<p><img src="/img/blog/lost-pixel-1.png" alt="Lost Pixel front page">{.mx-auto .max-w-full}</p>
<p>::GlobalParagraph{.italic .mb-4 .text-center}
(Quelle: <a href="https://lost-pixel.com">https://lost-pixel.com</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid})
::</p>
<p>::GlobalParagraph
Mit der Einbindung der bereitgestellten Github Action werden automatisch Screenshots der festgelegten Seiten/Komponenten erstellt und mit der Baseline verglichen. Die Lost Pixel Cloud Applikation bietet hier hervorragende Ansichten für den Vorher-Nachher Vergleich.
::</p>
<p><img src="/img/blog/lost-pixel-2.png" alt="Lost Pixel front page 2">{.mx-auto .max-w-full}</p>
<p>::GlobalParagraph{.italic .mb-4 .text-center}
(Quelle: <a href="https://lost-pixel.com">https://lost-pixel.com</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} links nachher, rechts vorher)
::</p>
<p>::GlobalParagraph
Über die simple Benutzeroberfläche lassen sich Änderungen an der Baseline akzeptieren oder ablehnen. Bei Ablehnung wird der entsprechende Pull Request auf Github als “fehlgeschlagen” markiert.
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Lost Pixel mit Gitlab
::
::GlobalParagraph
Github Actions ist de facto die “first class citizen” Implementierung von Lost Pixel in CI Pipelines. In diesem Abschnitt möchten wir zeigen, wie wir Lost Pixel mit der Software Gitlab verwenden.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Erstellung der Baseline für Visual Regression Testing
::
::GlobalParagraph
Zuerst wird die Basis erzeugt. Hierfür muss die <code>lostpixel.config.js</code> angelegt werden:
::</p>
<p>::BlogCode{.mb-4}</p>
<pre><code class="language-js">module.exports = {
  pageShots: {
    pages: [
      { path: '/pattern-library/render-pattern/cms/blocks/patterns/text/text.html', name: 'text', threshold: 0.01 },
    ],
    breakpoints: [320, 640, 1024, 1200, 1920],
    baseUrl: 'http://localhost:8000',
  },
  waitBeforeScreenshot: 2000,
  waitForLastRequest: 5000,
  failOnDifference: true,
  shotConcurrency: 1,
  generateOnly: true,
}
</code></pre>
<p>::
::GlobalParagraph
Diese Konfiguration nutzt "Page Shots". Damit werden lediglich ganzseitige Screenshots von den entsprechenden URLs gemacht. Eine detaillierte Aufstellung der Einstellungen <a href="https://docs.lost-pixel.com/user-docs/setup/project-configuration">ist hier zu finden</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}. Der Parameter <code>breakpoints</code> ermöglicht es, unterschiedliche Bildschirmauflösungen gleichzeitig zu prüfen.
::
::GlobalParagraph
Zur Erzeugung und zum Vergleich der Baseline nutzen wir Docker Images, um Unterschiede durch Betriebssystem oder Browser zu minimieren. Hierfür wird die Baseline folgendermaßen erzeugt:
::</p>
<p>::BlogCode{.mb-4}</p>
<pre><code class="language-bash">docker run --rm -v $PWD:$PWD -e WORKSPACE=$PWD -e DOCKER=1 -e LOST_PIXEL_DISABLE_TELEMETRY=0 -e LOST_PIXEL_MODE=update --network="host" lostpixel/lost-pixel:v3.22.0
</code></pre>
<p>::
::GlobalParagraph
Damit wird die Baseline neu erzeugt und lokal abgelegt. Da die Lost Pixel Cloud Plattform nicht mit Gitlab nutzbar ist, müssen diese Dateien ins Git Repository eingecheckt werden.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Installation von Lost Pixel auf dem Gitlab Runner
::
::GlobalParagraph
Zuallererst muss sichergestellt werden, dass Lost Pixel CLI in der Pipeline verfügbar ist:
::</p>
<p>::BlogCode{.mb-4}</p>
<pre><code class="language-bash">apt-get update
apt-get install -y nodejs npm curl
npm i -g lost-pixel
npx playwright@1.47.2 install --with-deps chromium
</code></pre>
<p>::
::GlobalParagraph
Playwright wird in genau der Version installiert, welche für Lost Pixel benötigt wird.
::
::GlobalParagraph
Playwright ist ein modernes, von Microsoft entwickeltes End-to-End-Testframework, das für das Testen von Webanwendungen genutzt wird. Es ermöglicht automatisierte UI-Tests in mehreren Browsern.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Visual Regression Testing in der CI Pipeline
::
::GlobalParagraph
Die Ausführung des Vergleichs ist nun denkbar einfach. Wir starten unsere Applikation (in unserem Fall eine Django App) und führen das Kommando zum Vergleich aus:
::</p>
<p>::BlogCode{.mb-4}</p>
<pre><code class="language-bash">python /app/die/manage.py serve --static &#x26;
npx lost-pixel local
</code></pre>
<p>::
::GlobalParagraph
Wir stellen sicher, dass die Screenshots temporär verfügbar sind um Unterschiede einfach untersuchen zu können. So können fehlgeschlagene Jobs einfach untersucht werden:
::
::BlogCode{.mb-4}</p>
<pre><code class="language-yaml"># gitlab-ci.yaml
artifacts:
  paths:
    - .lostpixel/difference/*
    - .lostpixel/current/*
  expire_in: 1 week
  when: always
</code></pre>
<p>::
::GlobalParagraph
Anbei ein Auszug aus eine möglichen <code>gitlab-ci.yaml</code>. In diesem Fall führen wir eine Django Applikationen mit einer temporär verfügbaren, lokalen Postgres Datenbank aus:
::
::BlogCode{.mb-4}</p>
<pre><code class="language-yaml"># gitlab-ci.yaml

stages:
  - lint
  - build
  - test
  - release
  - deploy

# [...]

visual_regression:
  stage: test
  image:
    name: ${TEST_IMAGE_NAME}
    docker:
      user: root
  services:
    - postgres:17-alpine
  before_script:
    - apt-get update
    - apt-get install -y nodejs npm curl
    - python die/manage.py migrate &#x26;&#x26; npm i -g lost-pixel
    - npx playwright@1.47.2 install --with-deps chromium
  script:
    - python /app/die/manage.py serve --static &#x26;
    - npx lost-pixel local
  variables:
  # [...]
  artifacts:
    paths:
      - .lostpixel/difference/*
      - .lostpixel/current/*
    expire_in: 1 week
    when: always
</code></pre>
<p>::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Auswertung des Visual Regression Tests
::
::GlobalParagraph
Sollte der Job fehlschlagen lassen sich nun die Ergebnisse in der Seitenleiste von Gitlab einfach aufrufen:
::</p>
<p><img src="/img/blog/lost-pixel-3.png" alt="Lost Pixel results">{.mx-auto}</p>
<p>::GlobalParagraph
Mit einem Klick auf <em>Durchsuchen</em> kann die Ordnerstruktur der Artefakte durchsucht werden:
::</p>
<p><img src="/img/blog/lost-pixel-4.png" alt="Lost Pixel folders">{.mx-auto}</p>
<p>::GlobalCallAnExpert{:bg="bg-bs-blue"}
#buttonBlockTitle
Wir können visuelle Regression Tests auch für deine App einrichten.
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Welche alternativen Tools gibt es für Visual Regression Testing?
::
::GlobalParagraph
Neben Lost Pixel, dem Tools welches wir bei <a href="/loesungen/automatisches-frontend-testing/">Blueshoe vornehmlich für Visual Regression Testing</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} verwenden, gibt es auch einige Alternativen:
::
::GlobalParagraph
<strong>Cloud-basierte Tools</strong>
::
::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li><a href="https://applitools.com/platform/eyes/">Applitools Eyes</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} – KI-gestützte visuelle Tests mit Integration in viele Testframeworks</li>
<li><a href="https://www.browserstack.com/percy">Percy (by BrowserStack)</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} – Automatisierte UI-Screenshots mit GitHub-Integration</li>
<li><a href="https://www.lambdatest.com/visual-regression-testing">Visual AI by LambdaTest</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} – Cloud-basierte visuelle Regressionstests</li>
<li><a href="https://www.chromatic.com/">Chromatic</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} – Speziell für Storybook-basierte UI-Tests
::</li>
</ul>
<p>::GlobalParagraph
<strong>Open-Source &#x26; Selbst-gehostete Tools</strong>
::
::GlobalBlock{.ul-disk .mb-8}</p>
<ul>
<li><a href="https://github.com/garris/BackstopJS">BackstopJS</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} – Headless Browser-basierte visuelle Regressionstests</li>
<li><a href="https://github.com/bbc/wraith">Wraith</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} – Von BBC entwickeltes Open-Source-Tool für Screenshot-Vergleiche</li>
<li><a href="https://rsmbl.github.io/Resemble.js/">Resemble.js</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} – JavaScript-Bibliothek für pixelgenaue Bildvergleiche</li>
<li><a href="https://github.com/mapbox/pixelmatch">Pixelmatch</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} – Leichtgewichtige Bildvergleichsbibliothek für visuelle Regressio
::</li>
</ul>
<p>::GlobalTitle{:size="lg" .mb-5}
Fazit
::
::GlobalParagraph{.mb-4}
In der heutigen agilen Entwicklungswelt ist Visual Regression Testing ein entscheidender Bestandteil der Qualitätssicherung. Es hilft, unbeabsichtigte UI-Änderungen frühzeitig zu erkennen und stellt sicher, dass neue Features oder Bugfixes keine bestehenden Designelemente zerstören.
::
::GlobalParagraph{.mb-4}
Durch den Einsatz moderner Tools wie Lost Pixel können Teams visuelle Tests effizient in ihre CI/CD-Pipelines integrieren und so die Konsistenz und Benutzerfreundlichkeit ihrer Anwendungen bewahren. Besonders im Zeitalter von komplexen Webanwendungen und responsivem Design ist ein zuverlässiges visuelles Testing ein echter Gamechanger.
::
::GlobalParagraph
Letztendlich spart Visual Regression Testing nicht nur Zeit und Kosten für manuelle UI-Überprüfungen, sondern trägt auch dazu bei, ein perfektes Nutzererlebnis zu gewährleisten – und das auf allen Geräten und Browsern. 🚀
::</p>
<p>:::GlobalTitle{:size="lg" .mb-5 .mt-8}
Häufige Fragen
:::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
1. Was ist Lost Pixel und warum sollte ich es in GitLab nutzen?
::
::GlobalParagraph
Lost Pixel ist ein Open-Source-Tool für visuelle Regressionstests. In GitLab-Pipelines hilft es, visuelle Unterschiede in der UI frühzeitig zu erkennen und Builds zu stoppen, wenn unerwartete Änderungen auftreten.
::
::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
2. Wie kann ich verhindern, dass nicht-relevante Änderungen Tests fehlschlagen lassen?
::
::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li>Nutze <code>--diff-threshold</code>, um geringfügige Abweichungen zu ignorieren</li>
<li>Verwende <code>excludeSelectors</code>, um dynamische Elemente (z. B. Datumsangaben) auszuschließen</li>
<li>Stelle sicher, dass Screenshots unter identischen Bedingungen (Viewport, Theme) erstellt werden
::</li>
</ul>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
3. Wie kann ich Lost Pixel mit GitLab Merge Requests kombinieren?
::
::GlobalParagraph
Lost Pixel kann so konfiguriert werden, dass es bei Merge Requests ein visuelles Diff generiert und als Kommentar im MR anzeigt. Nutze dazu eine GitLab CI/CD-Integration oder externe Bots.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
4. Wie kann ich Lost Pixel so einrichten, dass nur manuell genehmigte Änderungen als neue Referenz gelten?
::
::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li>Erstelle eine dedizierte <code>baseline</code>-Branch für Referenz-Screenshots</li>
<li>Nutze einen CI/CD-Job, der nur nach Review geänderte Screenshots in die <code>baseline</code>-Branch merged
::</li>
</ul>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
5. Warum schlägt Lost Pixel in GitLab zufällig fehl?
::</p>
<p>::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li>Unstabile Screenshots entstehen oft durch zufällige UI-Elemente oder Animationen.</li>
<li>Nutze den <code>--wait</code>-Parameter, um sicherzustellen, dass alle UI-Elemente gerendert wurden.
::</li>
</ul>]]></content:encoded>
            <category>Entwicklung</category>
            <category>Betrieb</category>
            <category>Sicherheit</category>
            <category>Dokumentation</category>
            <enclosure url="https://blueshoe.de/img/blog/lost_pixel.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Mehr Freiheit: Open Source für Unternehmen]]></title>
            <link>https://blueshoe.de/blog/vorteile-open-source-software-unternehmen</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/vorteile-open-source-software-unternehmen</guid>
            <pubDate>Thu, 15 May 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Open-Source-Software bietet Unternehmen Flexibilität, Kosteneffizienz und Unabhängigkeit von proprietären Anbietern. Doch welche Vorteile bringt sie wirklich, und gibt es auch Herausforderungen? In diesem Artikel zeigen wir, warum sich der Einsatz von Open Source lohnt – und worauf Unternehmen achten sollten.</p>
<p><img src="/img/blog/opensourcebusiness.svg" alt="Mehr Freiheit: Open Source für Unternehmen"></p>
<h2>Was ist Open Source – und warum ist es für Unternehmen so wichtig?</h2>
<p>Open Source bedeutet, dass der Quellcode einer Software frei zugänglich ist. Jeder darf sie nutzen, verändern und weiterverbreiten – ganz legal. Das klingt nicht nur gut, sondern <strong>revolutioniert die Art, wie Unternehmen Software einsetzen</strong>.</p>
<p>Immer mehr Firmen setzen auf Open-Source-Software, um Kosten zu sparen, flexibler zu arbeiten und nicht mehr von proprietären Anbietern abhängig zu sein. Doch was genau macht Open Source so attraktiv – und welche Herausforderungen gibt’s?</p>
<h2>Die Vorteile von Open-Source-Software für Unternehmen</h2>
<h3>Kosteneffizienz durch Lizenzfreiheit</h3>
<p>Einer der größten Pluspunkte: <strong>Keine Lizenzkosten</strong>. Proprietäre Software frisst oft einen großen Teil des IT-Budgets. Mit Open Source sparst du dir nicht nur die Lizenz, sondern auch teure Upgrades und versteckte Gebühren.</p>
<h3>Flexibilität &#x26; Anpassbarkeit</h3>
<p>Du hast die Kontrolle:</p>
<ul>
<li>Der Quellcode gehört dir. Du kannst Funktionen anpassen, erweitern oder ganz neu entwickeln.</li>
<li>Statt dich an Anbieter-Roadmaps zu halten, baust du deine Software so, wie sie zu deinem Geschäftsmodell passt.</li>
</ul>
<h3>Vendor Lock-in? Nicht mit Open Source</h3>
<p>Proprietäre Software macht’s schwer, das System zu wechseln. Bei Open Source ist das anders: Du kannst selbst entscheiden, ob du Cloud, On-Premise oder Hybrid willst – ohne dich an einen Anbieter wie AWS oder Azure zu binden.
<strong>Beispiel</strong>: Tools wie <strong>Kubernetes</strong> oder <strong>OpenStack</strong> ermöglichen genau das.
Bei Blueshoe setzen wir gezielt auf <strong>Docker</strong> und <strong>Kubernetes</strong>, um skalierbare, containerisierte Lösungen zu bauen – flexibel, effizient, zukunftssicher.</p>
<h3>Schnellere Innovation dank Open-Source-Community</h3>
<p>Die Community schläft nie. Weltweit arbeiten Entwickler*innen an neuen Features, Bugfixes und Sicherheitsupdates – oft <strong>schneller als bei kommerziellen Anbietern</strong>. Tools wie <strong>Kubernetes, Django, Vue.js</strong> oder <strong>Next.js</strong> bieten dir modernste Entwicklungsmöglichkeiten – ohne Lizenzbindung.</p>
<p>Bei Blueshoe kombinieren wir <strong>Vue.js / Nuxt.js</strong> im Frontend mit <strong>Django / Wagtail</strong> im Backend. So bauen wir flexible Webanwendungen mit Open-Source-Stack, die du jederzeit erweitern kannst.</p>
<h3>Open Source heißt auch: Cloud-Native &#x26; skalierbar</h3>
<p>Skalierbarkeit ist heute Pflicht. Und genau dafür ist Open-Source-Software gemacht:
Technologien wie <strong>Docker</strong> und <strong>Kubernetes</strong> sind <strong>Cloud-native von Anfang an</strong> und machen dich unabhängig von starren Plattformen.</p>
<p>Ob du in kleinen Schritten wächst oder riesige Plattformen betreibst – mit Open Source kannst du deine IT-Infrastruktur flexibel skalieren.</p>
<hr>
<h2>Herausforderungen – und wie du sie löst</h2>
<h3>Support &#x26; Wartung</h3>
<p>Nicht jede Open-Source-Software bringt professionellen Support mit. Aber keine Panik:
<strong>Lösung</strong>: Setze auf einen erfahrenen Partner wie Blueshoe. Wir übernehmen Support, Wartung und Weiterentwicklung – ganz ohne Lizenzvertrag.</p>
<h3>Updates &#x26; Sicherheit</h3>
<p>Sicherheitslücken? Die gibt’s überall. Aber bei Open Source trägst du die Verantwortung für regelmäßige Updates und Patches.</p>
<p><strong>Unsere Lösung</strong>: Mit <strong>automatisierter Wartung und Sicherheitsprozessen</strong> sorgen wir bei Blueshoe dafür, dass deine Software sicher bleibt – dauerhaft.</p>
<p>::GlobalCallAnExpert{:bg="bg-bs-blue"}
#buttonBlockTitle
Wenn Du mehr über Open-Source Software erfahren willst ...
::</p>
<h2>Open-Source-Software-Beispiel: So gelingt der Umstieg</h2>
<p>Du willst wissen, wie ein Wechsel zu Open Source in der Praxis aussieht? Hier ein typisches Beispiel:</p>
<h3>Best Practices für Open-Source-Integration in Unternehmen</h3>
<p>Damit Open Source richtig fliegt, braucht’s eine klare Strategie. Hier unsere Tipps:</p>
<ol>
<li>Schrittweise einführen: Pilotprojekte starten und Erfahrungen sammeln.</li>
<li>Internes Know-how aufbauen: Entwicklerteams mit Open-Source-Skills ausstatten.</li>
<li>API-first denken: Integration in bestehende Systeme clever planen.</li>
<li>Security ernst nehmen: Regelmäßige Updates, Backups &#x26; Security-Scans.</li>
<li>Experten ins Boot holen: Blueshoe begleitet dich mit Beratung &#x26; Support.</li>
</ol>
<h2>Fazit: Open Source = Unabhängigkeit + Effizienz</h2>
<p>Open-Source-Software bietet Unternehmen zahlreiche Vorteile – von Kosteneinsparungen über mehr Flexibilität bis hin zu erhöhter Sicherheit. Mit der richtigen Strategie lassen sich mögliche Herausforderungen problemlos bewältigen. Wer Open Source gezielt einsetzt, kann sich unabhängiger aufstellen und langfristig effizienter arbeiten. Unternehmen, die auf Open Source setzen, gewinnen nicht nur technologische Freiheit, sondern auch einen nachhaltigen Wettbewerbsvorteil.</p>
<p>Bei Blueshoe begleiten wir Unternehmen dabei, Open-Source-Technologien erfolgreich in ihre Prozesse zu integrieren – von der Entwicklung skalierbarer Webanwendungen mit <strong>Wagtail CMS</strong> bis hin zur Umsetzung moderner <strong>Cloud-Architekturen mit Docker und Kubernetes</strong>. Sprich uns an und erfahre, wie wir Open Source für dein Unternehmen optimal nutzen können!</p>
<hr>
<h2>FAQ – Häufige Fragen zu Open-Source Sofware</h2>
<h3>1. Was ist Open Source?</h3>
<p>Open Source bedeutet, dass der Quellcode einer Software <strong>öffentlich zugänglich</strong> ist. Unternehmen können ihn einsehen, anpassen und weiterentwickeln – ohne teure Lizenzgebühren. Das fördert <strong>Innovation, Transparenz und Sicherheit</strong>.</p>
<h3>2. Was ist Open-Source-Software?</h3>
<p>Open-Source-Software (OSS) ist Software, die von einer Community entwickelt wird und frei genutzt werden kann. Beispiele sind <strong>Linux, Kubernetes, Nextcloud</strong> oder <strong>Odoo</strong>.</p>
<h3>3. Was ist die Bedeutung von Open Source für Unternehmen?</h3>
<p>Für Unternehmen bedeutet Open Source mehr Freiheit, geringere Kosten und keine Abhängigkeit von einem Anbieter (Vendor Lock-in). Zudem ermöglicht Open Source eine flexible IT-Strategie und bessere Sicherheitskontrolle.</p>
<h3>4. Was sind Beispiele für Open-Source-Software in Unternehmen?</h3>
<p>Hier sind einige Open-Source-Software-Beispiele für Unternehmen:</p>
<ul>
<li>ERP &#x26; CRM: Odoo, ERPNext, SuiteCRM</li>
<li>Cloud &#x26; Hosting: OpenStack, Kubernetes</li>
<li>Sicherheit: WireGuard (VPN), OpenSSL, Vault</li>
<li>Webentwicklung: Django, Vue, FastAPI, Nuxt</li>
</ul>
<h3>5. Welche Best Practices gibt es für die Open-Source-Integration in Unternehmen?</h3>
<p>✔ Schrittweise Einführung: Erst einzelne Open-Source-Lösungen testen, dann erweitern.<br>
✔ Interne Expertise aufbauen: IT-Teams schulen, Open-Source-Strategie definieren.<br>
✔ APIs &#x26; Middleware nutzen: Open Source nahtlos mit bestehenden Systemen verbinden.<br>
✔ Sicherheitsupdates priorisieren: Automatische Updates &#x26; Security-Checks einrichten.<br>
✔ Mit Open-Source-Partnern zusammenarbeiten: Experten für Support &#x26; Wartung einbinden (z. B. Blueshoe 😉).</p>
<h3>6. Ist Open-Source-Software wirklich sicher?</h3>
<p>Ja, Open Source kann sogar sicherer als proprietäre Software sein!<br>
✅ Transparenter Code → Sicherheitslücken können geprüft &#x26; schneller geschlossen werden.<br>
✅ Regelmäßige Updates → Community &#x26; Unternehmen verbessern die Software kontinuierlich.<br>
✅ Keine versteckten Hintertüren → Proprietäre Software kann versteckte Schwachstellen haben.</p>]]></content:encoded>
            <category>Projekt Management</category>
            <category>Digitalisierung</category>
            <category>Team Blueshoe</category>
            <enclosure url="https://blueshoe.de/img/blog/opensourcebusiness.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Strongswan-VPN in Kubernetes: Externe Dienste sicher einbinden]]></title>
            <link>https://blueshoe.de/blog/vpn-kubernetes-strongswan</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/vpn-kubernetes-strongswan</guid>
            <pubDate>Tue, 06 May 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>In manchen Fällen benötigt es eine VPN-Anbindung zu einem externen Service - was mit Kubernetes tricky sein kann. In diesem Beitrag zeigen wir, wie man mit StrongSwan einen IPsec-Tunnel aus Kubernetes heraus zu einem externen Dienst aufbaut und dabei Nginx als Reverse-Proxy nutzt. Das Setup ist klar strukturiert, gut wartbar und unterscheidet dynamisch zwischen Staging und Production.</p>
<p><img src="/img/blog/kubernetes-strongswan.svg" alt="IPsec-VPN-Tunnel aus Kubernetes heraus sicher aufbauen"></p>
<p>::GlobalBlogLevelInfo</p>
<ul>
<li><a href="https://de.wikipedia.org/wiki/IPsec">IPSec</a>{:target="_blank"} und VPN</li>
<li><a href="https://strongswan.org/">Strongswan</a>{:target="_blank"}</li>
<li><a href="https://de.wikipedia.org/wiki/IPsec#Automatische_Schl%C3%BCsselverwaltung">Internet Key Exchange (IKE)</a>{:target="_blank"}</li>
</ul>
<p>Solltest du Fragen haben, oder dir etwas unklar sein, kannst du die Kommentarfunktion unter dem Artikel nutzen.
::</p>
<h2>1. Problemstellung</h2>
<p>Manchmal steht eine wichtige Datenbank nicht im eigenen Cluster, sondern hinter einer Firewall auf einem externen Server. Der Zugriff erfolgt nur über ein VPN – zum Beispiel via IPSec mit einer WatchGuard-Appliance. Die Herausforderung: Wie erreichen Anwendungen im Cluster diesen Service zuverlässig und sicher?</p>
<h2>2. Zielbild</h2>
<p>Ziel war es, eine schlanke und wartbare Lösung zu schaffen, die:</p>
<ul>
<li>von innerhalb des Clusters auf fest definierte externe Services via VPN zugreifen kann,</li>
<li>klar zwischen Staging- und Produktionszugängen unterscheidet,</li>
<li>flexibel konfigurierbar bleibt,</li>
<li>mit Kubernetes-Standards arbeitet (Probes, Services, ConfigMaps etc.).</li>
</ul>
<p><img src="/img/blog/strongswan-k8s.png" alt="Das Strongswan Kubernetes Setup"></p>
<h2>3. Lösungsidee</h2>
<p>Wir haben einen dedizierten Pod mit Strongswan und Nginx aufgesetzt:</p>
<ul>
<li>Strongswan übernimmt den Aufbau des IPSec-Tunnels zum externen VPN-Endpunkt.</li>
<li>Nginx fungiert als TCP-Proxy und leitet Anfragen aus dem Cluster an den externen Service weiter.</li>
<li>Je nach Umgebung (Staging oder Production) wird der Nginx entsprechend konfiguriert.</li>
</ul>
<p>Innerhalb des Clusters können andere Services einfach via <code>ClusterIP</code> auf den Nginx zugreifen, der dann die Verbindung über das VPN übernimmt.</p>
<h2>4. Umsetzung in Kubernetes</h2>
<h3>a) Dockerfile</h3>
<p>Im Container laufen Nginx und Strongswan nebeneinander. Ein Startscript entscheidet je nach <code>ENVIRONMENT</code>-Variable, welche Konfigurationsdateien verwendet werden.</p>
<pre><code class="language-dockerfile">FROM nginx:1.27.3-alpine
RUN apk add --no-cache strongswan netcat-openbsd
...
CMD ["/scripts/startup.sh"]
</code></pre>
<h3>b) Nginx-Konfiguration</h3>
<p>Für Production gibt es eine einzige TCP-Weiterleitung (z. B. auf Port 8080), im Staging sind es zwei (8080 + 8081 für Prod/Staging).</p>
<pre><code class="language-nginx">stream {
  upstream ext-db-production {
    server NGINX_EXTDB_IP_PRODUCTION:NGINX_EXTDB_PORT_PRODUCTION;
  }
  server {
    listen 8080;
    proxy_pass ext-db-production;
  }
  ...
}
</code></pre>
<p>::GlobalCallAnExpert{:bg="bg-bs-blue"}
#buttonBlockTitle
Wir können auch Deine Kubernetes Apps per VPN verbinden.
::</p>
<h3>c) VPN-Setup mit Strongswan</h3>
<p>Der Tunnel wird über <code>swanctl.conf</code> und Umgebungsvariablen dynamisch konfiguriert. So können IP-Adressen, PSK und Subnetze per Helm Template eingespielt werden.</p>
<pre><code class="language-bash">connections {
   k8s-ext-db {
      remote_addrs = SWANCTL_CONF_REMOTE_ADDRS

      local {
         auth = psk
         id = SWANCTL_CONF_LOCAL_ID
      }
      remote {
         auth = psk
      }
      children {
         net-net {
            local_ts  = SWANCTL_CONF_LOCAL_TS
            remote_ts = SWANCTL_CONF_REMOTE_TS
            esp_proposals = SWANCTL_CONF_ESP_PROPOSALS
            ...
         }
      }
      version = 2
      proposals = SWANCTL_CONF_PROPOSALS
      ...
   }
}

secrets {
   ike-k8s-nors {
      id-k8s = SWANCTL_CONF_LOCAL_ID
      secret = SWANCTL_CONF_SECRET_PSK_TOKEN
   }
}
</code></pre>
<p>Die großgeschriebenen Variablen in allen Config-Dateien werden vom Startup Script mit Werten aus den Umgebungsvariablen ersetzt. Dadurch kann der Pod für verschiedene Umgebungen dynamisch konfiguriert werden. Die Werte könnten natürlich auch direkt angegeben werden.</p>
<p>Das Startup Script führt dann nur noch folgende Commands aus um die VPN Verbindung aufzubauen:</p>
<pre><code class="language-bash">swanctl --load-all
ipsec up k8s-ext-db
</code></pre>
<h3>d) Health-Probes</h3>
<p>Ein Shell-Skript prüft regelmäßig:</p>
<ul>
<li>ob die VPN-Ziel-IP erreichbar ist: <code>nc -z -w 1 $PROBE_STRONGSWAN_IP $PROBE_STRONGSWAN_PORT</code></li>
<li>ob der interne Nginx eine <code>/healthz</code>-Route bedient: <code>curl -f -LI $PROBE_NGINX_IP:$PROBE_NGINX_PORT/healthz</code></li>
</ul>
<p>Nur wenn beide Bedingungen erfüllt sind, wird der Exit-Code 0 zurückgeben. Damit lassen sich die Pods gut in Kubernetes überwachen und restarten, falls etwas hängt.</p>
<h2>5. Besonderheiten</h2>
<ul>
<li>Der Pod benötigt die <code>NET_ADMIN</code>-Capability, damit Strongswan Routing-Tables setzen darf.</li>
<li>Production und Staging unterscheiden sich nicht nur in den Zielsystemen, sondern auch in der Menge der angebundenen externen Dienste.</li>
<li>Der gesamte Tunnel läuft in einem dedizierten Service – andere Pods im Cluster brauchen davon nichts zu wissen.</li>
</ul>
<h2>6. Pod Manifest</h2>
<p>Hier ein Beispiel für das Pod-Manifest:</p>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
  name: vpn-client
  namespace: vpn
spec:
  containers:
  - name: vpn
    image: your-vpn-image:1.0.0
    imagePullPolicy: Always
    envFrom:
    - configMapRef:
        name: vpn-client-configmap
    ports:
    - containerPort: 8080
      name: ext-db-prod
      protocol: TCP
    livenessProbe:
      exec:
        command:
        - /scripts/probe.sh
      failureThreshold: 3
      periodSeconds: 10
      successThreshold: 1
      timeoutSeconds: 2
    readinessProbe:
      exec:
        command:
        - /scripts/probe.sh
      failureThreshold: 1
      periodSeconds: 10
      successThreshold: 1
      timeoutSeconds: 2
    startupProbe:
      exec:
        command:
        - /scripts/probe.sh
      failureThreshold: 40
      periodSeconds: 5
      successThreshold: 1
      timeoutSeconds: 2
    securityContext:
      capabilities:
        add:
        - NET_ADMIN
</code></pre>
<h2>7. Fazit</h2>
<p>Die Kombination aus Strongswan und Nginx ist eine robuste, leichtgewichtige Möglichkeit, externe Dienste über VPN in Kubernetes anzubinden. Die strikte Trennung nach Umgebungen, flexible Helm-Templates und Kubernetes-Probes machen die Lösung production-ready – ohne Overhead durch zusätzliche Tools.</p>
<h2>8. Häufige Fragen</h2>
<h3>1. Warum brauche ich ein VPN in Kubernetes?</h3>
<p>Ein VPN ermöglicht sichere Verbindungen zwischen deinem Cluster und externen Systemen, etwa Datenbanken oder Legacy-Systemen, die nicht öffentlich erreichbar sind. Es ist besonders sinnvoll, wenn sensible Daten übertragen werden oder direkte IP-Kommunikation erforderlich ist.</p>
<h3>2. Was ist StrongSwan und warum eignet es sich für Kubernetes?</h3>
<p>StrongSwan ist eine etablierte Open-Source-VPN-Lösung für IPsec-basierte Tunnel. Sie ist leichtgewichtig, zuverlässig und lässt sich durch ihre Modularität gut in containerisierte Umgebungen wie Kubernetes integrieren.</p>
<h3>3. Wie integriere ich StrongSwan in Kubernetes?</h3>
<p>StrongSwan kann als dediziertes Deployment in einem Pod betrieben werden. Über eine angepasste Netzwerkkonfiguration und iptables-Routing wird der Traffic durch das VPN geleitet. NGINX oder andere Proxies übernehmen dabei das Routing zum Zielsystem über das VPN.</p>
<h3>4. Gibt es Helm-Charts für StrongSwan in Kubernetes?</h3>
<p>Nein, es gibt keine offiziellen Helm-Charts. Die meisten Setups nutzen eigene Deployments mit Docker-Images, Konfigurationsdateien und init-Skripten. Das kann zwar mühselig sein, ermöglicht aber maximale Flexibilität bei der VPN-Einrichtung.</p>
<h3>5. Welche Alternativen zu StrongSwan gibt es?</h3>
<p>Je nach Anforderungen kannst du auch WireGuard (modern, schnell), OpenVPN (bewährt, aber komplexer) oder VPN-Services von Cloud-Anbietern wie AWS Site-to-Site VPN oder Azure VPN Gateway einsetzen.</p>]]></content:encoded>
            <category>Kubernetes</category>
            <category>Entwicklung</category>
            <category>Betrieb</category>
            <enclosure url="https://blueshoe.de/img/blog/kubernetes-strongswan.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Vue Vapor: Bye Virtual DOM, Hallo Performance!]]></title>
            <link>https://blueshoe.de/blog/vue-vapor-performance-ohne-virtual-dom</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/vue-vapor-performance-ohne-virtual-dom</guid>
            <pubDate>Fri, 09 May 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Ladezeiten, komplexe Komponentenbäume und unnötiger Overhead – der Virtual DOM stößt an seine Grenzen. Mit Vue Vapor kommt die Antwort: maximale Performance, minimaler JavaScript-Ballast – ob klassisches SPA oder Server-Side Rendering (SSR).</p>
<p>In diesem Artikel erklären wir, wie Vue Vapor funktioniert, welche Vorteile es gegenüber dem bisherigen Vue.js 3 Virtual DOM bringt, und worauf du als Entwickler achten solltest, um das volle Performance-Potenzial zu entfalten. Außerdem vergleichen wir Vue Vapor vs. Vue 3, und werfen einen Blick auf moderne Konzepte wie Partial Hydration und server-driven UI.</p>
<p><img src="/img/blog/vuevapor.svg" alt="Vue Vapor: Bye Virtual DOM, Hallo Performance!"></p>
<h2>Einleitung: Eine neue Ära für Vue</h2>
<p>Mit <strong>Vue Vapor</strong> läutet das Vue-Core-Team ein neues Kapitel in der Geschichte des beliebten Frameworks ein. Statt sich wie bisher auf den Virtual DOM zu verlassen, geht Vue Vapor einen radikal anderen Weg: Es setzt auf <strong>Compile-Time Optimierungen</strong> und ein vollständig <strong>DOM-basiertes Rendering</strong> – ganz ohne diffing, patching oder VDOM-Abgleich.</p>
<p>Das Ziel? <strong>Maximale Performance</strong> bei minimalem Overhead.</p>
<p>:GlobalButton{:url="/technologien/vuejs-nuxt/" :label="Du willst Vue.js richtig einsetzen – Wir helfen dir dabei!" :target="_blank" :color="blue" .mb-6}</p>
<h2>Warum Vue den Virtual DOM über Bord wirft</h2>
<p>Der Virtual DOM war lange Zeit ein cleverer Kompromiss: Er ermöglichte eine deklarative Programmierweise, ohne direkt mit dem echten DOM zu arbeiten. Doch dieser Ansatz bringt auch Nachteile mit sich:</p>
<ul>
<li><strong>Performance-Kosten</strong> beim Vergleichen von VDOM und realem DOM (Diffing)</li>
<li><strong>Zusätzlicher Speicherverbrauch</strong> durch Virtual Node-Strukturen</li>
<li><strong>Komplexität</strong> bei größeren Komponentenbäumen</li>
</ul>
<p>Mit zunehmenden Anforderungen an Ladezeit, Energieeffizienz (z. B. auf mobilen Geräten) und Interaktivität (z. B. in E-Commerce oder SaaS) wurde klar: Der VDOM ist nicht mehr der schnellste Weg zum Ziel.</p>
<h2>Was ist Vue Vapor?</h2>
<p>Vue Vapor ist eine neue <strong>Rendering Engine</strong> für Vue.js, die vollständig <strong>VDOM-frei</strong> arbeitet. Sie basiert auf einem intelligenten Compile-Time-Ansatz, der den Code beim Build analysiert und exakt den minimal notwendigen DOM-Code generiert. Statt allgemeiner "diffbarer" Komponenten entsteht präziser, spezialisierter Output.</p>
<p><strong>Kernmerkmale von Vue Vapor:</strong></p>
<ul>
<li>Kein Virtual DOM → keine Diffing-Operationen zur Laufzeit</li>
<li>Statisch analysierter &#x26; kompilierter DOM-Code</li>
<li>Minimaler JavaScript-Overhead</li>
<li>Automatische Optimierungen für SSR, CSR &#x26; Hydration</li>
<li>Integration mit modernen Patterns wie <strong>Partial Hydration</strong> &#x26; <strong>Server-driven UI</strong></li>
</ul>
<h2>Wie funktioniert das in der Praxis?</h2>
<p>Statt eine generische Render-Funktion zu erzeugen, analysiert der Vue-Vapor-Compiler die Komponente bereits <strong>beim Build</strong>. Dabei werden Reaktivität, bedingte Renderpfade, Loops und sogar Event-Handler optimiert und "hart verdrahtet" in DOM-Manipulationen umgewandelt.</p>
<h3>Beispiel: Button-Komponente mit Vue Vapor</h3>
<pre><code class="language-vue">&#x3C;!-- herkömmliche Vue-Komponente -->
&#x3C;script setup lang="ts">
const count = ref(0)
&#x3C;/script>

&#x3C;template>
  &#x3C;button @click="count++">
    Clicked {{ count }} times
  &#x3C;/button>
&#x3C;/template>
</code></pre>
<p>Ergibt mit Vue 3 eine render-Funktion mit VDOM-Diffing.
Mit <strong>Vue Vapor</strong> hingegen wird direkt generierter DOM-Code erzeugt, der weiß: Nur <code>textContent</code> dieses einen DOM-Nodes muss bei Änderung aktualisiert werden – nichts weiter.</p>
<h2>Performance-Vergleich: Vue 3 vs. Vue Vapor</h2>
<p>Statt auf diffbasierte Updates zur Laufzeit zu setzen, erzeugt Vue Vapor bereits beim Build optimierten DOM-Code. Das reduziert JavaScript-Overhead und macht das Rendering besonders bei komplexen oder dynamischen Komponenten spürbar effizienter.</p>
<p>Während Vue 3 auf den bewährten Virtual DOM setzt, verfolgt Vue Vapor einen radikal anderen Ansatz – mit klaren Vorteilen bei initialem Rendern, Hydration und Speicherverbrauch.</p>
<blockquote>
<p>Erste Tests und Erfahrungsberichte aus der Community deuten auf eine deutlich bessere Performance hin, insbesondere im SSR-Kontext und bei mobilen Anwendungen. Offizielle, reproduzierbare Benchmarks des Vue-Core-Teams stehen aktuell jedoch noch aus.</p>
</blockquote>
<h2>Was Entwickler jetzt beachten müssen</h2>
<p>Vue Vapor ist <strong>opt-in</strong>. Du entscheidest, ob du es aktivierst – aktuell noch experimentell, aber bereits stabil genug für Tests und neue Projekte. Voraussetzungen:</p>
<ul>
<li>Vue ≥ 3.5 (Vapor Preview)</li>
<li><code>@vue/compiler-vapor</code> als zusätzliche Dependency</li>
<li>Komponenten müssen <strong>Single File Components (SFCs)</strong> sein</li>
<li>Keine dynamischen Komponenten oder <code>$refs</code> auf Root-Ebene</li>
<li>SSR/CSR-Hybrid-Projekte sollten auf Partial Hydration achten</li>
</ul>
<p>👉 Wichtig: Einige Features wie <code>v-html</code>, dynamische Komponenten oder nicht-deterministisches Verhalten (z. B. <code>Math.random()</code>) sind mit Vapor nicht kompatibel – zumindest nicht ohne Workarounds.</p>
<h2>Moderne Konzepte: Partial Hydration &#x26; Server-driven UI</h2>
<p>::GlobalCallAnExpert{:bg="bg-bs-blue"}
#buttonBlockTitle
Wir können auch Deine Vue und Nuxt Apps beschleunigen
::</p>
<h3>Partial Hydration</h3>
<p>Vue Vapor spielt perfekt mit <strong>Partial Hydration</strong> zusammen. Hierbei wird nur der interaktive Teil einer Seite hydratisiert – der Rest bleibt statisch. Das spart Ladezeit und vermeidet unnötige JavaScript-Ausführung.</p>
<h3>Server-driven UI</h3>
<p>Mit serverseitiger Steuerung von UI-Elementen (z. B. über JSON-Payloads oder ViewModel-Strukturen) wird Vue Vapor zur idealen Grundlage für headless-optimierte Frontends. Du renderst, was gebraucht wird – und sonst nichts.</p>
<hr>
<h2>Fazit: Warum Vue Vapor ein echter Gamechanger ist</h2>
<p>Vue Vapor zeigt, wie modernes Frontend ohne VDOM funktionieren kann – schneller, schlanker, intelligenter. Für Performance-kritische Anwendungen (z. B. E-Commerce, Dashboards, PWAs) ist es eine <strong>vielversprechende Zukunftstechnologie</strong>.</p>
<p><strong>Unser Tipp:</strong> Jetzt testen, lernen und den Kopf auf die Post-VDOM-Zukunft ausrichten. Der Unterschied ist nicht nur messbar – er ist spürbar.</p>
<hr>
<h2>FAQ – Häufige Fragen zu Vue Vapor</h2>
<h3>Was ist Vue Vapor?</h3>
<p><strong>Vue Vapor</strong> ist eine neuartige <strong>Rendering-Engine</strong> aus dem Vue-Ökosystem, die vollständig auf den <strong>Virtual DOM</strong> verzichtet. Stattdessen setzt sie auf einen <strong>Compiler-first-Ansatz</strong>, der direkt optimierten DOM-Code erzeugt – für maximale <strong>Performance</strong> und minimalen Overhead.</p>
<h3>Warum verzichtet Vue Vapor auf den Virtual DOM?</h3>
<p>Der klassische <strong>Virtual DOM</strong> erzeugt zusätzlichen Rechenaufwand beim <strong>DOM-Diffing</strong>. <strong>Vue Vapor</strong> eliminiert diesen Schritt, indem es schon zur Build-Zeit den effizientesten DOM-Code generiert. Das macht das Rendering deutlich schneller, besonders bei großen oder interaktiven Anwendungen.</p>
<h3>Wie unterscheidet sich Vue Vapor von Vue 3?</h3>
<p><strong>Vue 3</strong> verwendet weiterhin den Virtual DOM. <strong>Vue Vapor</strong> hingegen ersetzt diesen durch eine <strong>statisch analysierte Rendering-Strategie</strong>, die keinen diffing-basierten Vergleich mehr benötigt. Das Ergebnis: <strong>Weniger Speicherverbrauch</strong> und ein spürbarer <strong>Performance-Boost</strong> bei typischen Vue-Anwendungen.</p>
<h3>Ist Vue Vapor bereits für Produktionsprojekte geeignet?</h3>
<p>Aktuell befindet sich <strong>Vue Vapor</strong> noch im experimentellen Stadium. Es richtet sich primär an Entwickler, die <strong>Cutting-Edge-Technologien</strong> evaluieren möchten. Für stabile Produktivumgebungen bleibt <strong>Vue 3</strong> derzeit der empfohlene Standard.</p>
<h3>Kann ich Vue Vapor mit Nuxt verwenden?</h3>
<p><strong>Noch nicht.</strong> Derzeit basiert <strong>Nuxt</strong> vollständig auf <strong>Vue 3</strong>. Eine Integration von Vue Vapor ist mittelfristig denkbar – vor allem, wenn es eine stabile Version und offizielle Unterstützung durch das Nuxt-Team gibt.</p>
<h3>Welche Alternativen gibt es zu Vue Vapor?</h3>
<p>Ähnliche Ansätze verfolgen z. B. <strong>React Server Components</strong>, <strong>SvelteKit</strong> oder <strong>Qwik</strong>. Alle zielen auf bessere <strong>Frontend-Performance</strong> durch reduzierten Overhead und intelligentere <strong>Hydration-Strategien</strong> – jedoch mit unterschiedlichen Architekturen und Philosophien.</p>]]></content:encoded>
            <category>Vue.js</category>
            <category>Nuxt</category>
            <category>Entwicklung</category>
            <category>Performance</category>
            <enclosure url="https://blueshoe.de/img/blog/vuevapor.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Prop Drilling in Vue.js vermeiden – So klappt's]]></title>
            <link>https://blueshoe.de/blog/vuejs-prop-drilling-vermeiden</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/vuejs-prop-drilling-vermeiden</guid>
            <pubDate>Tue, 15 Apr 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Daten zwischen Komponenten zu teilen ist in Vue.js Alltag – aber was einfach klingt, wird schnell messy. Der Grund: Prop Drilling. Dabei reichst du Props durch mehrere Komponenten durch, auch wenn sie nur am Ende gebraucht werden. Das funktioniert zwar – ist aber alles andere als elegant.
Wenn du prop drilling in Vue vermeiden willst, bist du hier richtig. Wir zeigen dir Best Practices und Tools wie <code>provide/inject</code> und State Management mit Pinia, um deine App sauber, wartbar und skalierbar zu halten.</p>
<p><img src="/img/blog/propsdrilling_pinia.svg" alt="Prop Drilling in Vue: So vermeidest du Chaos im Code"></p>
<h2>Was ist Prop Drilling in Vue.js?</h2>
<p>Prop Drilling bedeutet, dass Daten von einer übergeordneten Komponente (Parent) durch mehrere Zwischen Komponenten hindurch an eine tiefer liegende Komponente (Child) weitergereicht werden.</p>
<h3>Beispiel für Prop Drilling</h3>
<p>Angenommen, eine tief liegende Komponente benötigt eine Information von einer übergeordneten Komponente:</p>
<pre><code class="language-vue">&#x3C;!-- App.vue -->
&#x3C;script setup>
import { ref } from 'vue';
import ParentComponent from './ParentComponent.vue';

const username = ref('Max');
&#x3C;/script>
&#x3C;template>
  &#x3C;ParentComponent :username="username" />
&#x3C;/template>
</code></pre>
<pre><code class="language-vue">&#x3C;!-- ParentComponent.vue -->
&#x3C;script setup>
import ChildComponent from './ChildComponent.vue';

defineProps(['username']);
&#x3C;/script>

&#x3C;template>
  &#x3C;ChildComponent :username="username" />
&#x3C;/template>
</code></pre>
<pre><code class="language-vue">&#x3C;!-- ChildComponent.vue -->
&#x3C;script setup>
defineProps(['username']);
&#x3C;/script>

&#x3C;template>
  &#x3C;p>Hallo, {{ username }}!&#x3C;/p>
&#x3C;/template>
</code></pre>
<p>Hier wird <code>username</code> von <code>App.vue</code> über <code>ParentComponent.vue</code> an <code>ChildComponent.vue</code> weitergereicht – selbst wenn <code>ParentComponent</code> den Wert gar nicht benötigt.</p>
<hr>
<h2>Warum solltest du Prop Drilling in Vue vermeiden?</h2>
<p>In kleinen Projekten geht’s vielleicht noch – aber spätestens, wenn dein Code wächst, wirst du es bereuen. Hier die größten Pain Points beim Prop Drilling in Vue.js:</p>
<p>❌ <strong>Unübersichtlicher Code</strong>: Wenn eine Prop durch viele Komponenten weitergereicht wird, kann es schwierig werden, den Datenfluss nachzuvollziehen.<br>
❌ <strong>Hoher Wartungsaufwand</strong>: Falls sich eine Prop ändert, müssen alle beteiligten Komponenten angepasst werden.<br>
❌ <strong>Geringe Wiederverwendbarkeit</strong>: Komponenten werden unnötig voneinander abhängig, da sie Daten weitergeben müssen, die sie gar nicht selbst nutzen.</p>
<p>Wenn du <strong>Vue.js Best Practices</strong> suchst, führt kein Weg daran vorbei: <strong>Prop Drilling vermeiden</strong>, wo immer es geht.</p>
<p>:GlobalButton{:url="/technologien/vuejs-nuxt/" :label="Du willst Vue.js richtig einsetzen – Wir helfen dir dabei!" :target="_blank" :color="blue" .mb-6}</p>
<hr>
<h2>Vue.js Datenfluss optimieren – Alternativen zu Prop Drilling</h2>
<p>Zum Glück bietet Vue verschiedene Lösungen, um Daten effizient weiterzugeben, ohne sie durch jede Zwischenkomponente zu schleifen.</p>
<h3>1. <code>provide</code> und <code>inject</code> in Vue.js - Die eingebaute Lösung</h3>
<p>Vue bietet mit <code>provide</code> und <code>inject</code> eine native Möglichkeit, Daten an tiefer liegende Komponenten weiterzugeben.</p>
<p><strong>Beispiel mit <code>provide</code> und <code>inject</code></strong></p>
<pre><code class="language-vue">&#x3C;!-- App.vue -->
&#x3C;script setup>
import { provide, ref } from 'vue';
import ChildComponent from './ChildComponent.vue';

const username = ref('Max');
provide('username', username);
&#x3C;/script>

&#x3C;template>
  &#x3C;ChildComponent />
&#x3C;/template>
</code></pre>
<pre><code class="language-vue">&#x3C;!-- ChildComponent.vue -->
&#x3C;script setup>
import { inject } from 'vue';

const username = inject('username');
&#x3C;/script>

&#x3C;template>
  &#x3C;p>Hallo, {{ username }}!&#x3C;/p>
&#x3C;/template>
</code></pre>
<p><strong>Vorteile von mit <code>provide</code> und <code>inject</code>:</strong></p>
<p>✅ Keine unnötigen Props in Zwischenkomponenten<br>
✅ Bessere Struktur und Lesbarkeit<br>
✅ Ideal für Themes, Lokalisierung, Form Handling</p>
<p><strong>Nachteile:</strong></p>
<p>⚠️ Funktioniert nur innerhalb eines Komponentenbaums<br>
⚠️ Datenfluss ist weniger offensichtlich</p>
<p>Trotzdem: Für kleinere bis mittlere Projekte eine super Lösung, um Prop Drilling in Vue zu vermeiden.</p>
<h3>2. State Management mit Pinia</h3>
<p>Für größere Anwendungen ist ein <strong>State-Management-Tool</strong> wie <a href="https://pinia.vuejs.org/">Pinia</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}) eine noch bessere Alternative.</p>
<p><strong>Beispiel mit Pinia</strong></p>
<p>Zunächst muss Pinia installiert werden:</p>
<pre><code class="language-bash">yarn add pinia
# or
npm install pinia
</code></pre>
<p>Dann wird ein Store erstellt:</p>
<pre><code class="language-typescript">// stores/user.ts
import { defineStore } from 'pinia';
import { ref } from 'vue';

export const useUserStore = defineStore('user', () => {
  const username = ref&#x3C;string>('Max');

  return {
    username,
  };
});
</code></pre>
<p>Nun kann der Store in jeder Komponente genutzt werden:</p>
<pre><code class="language-vue">&#x3C;!-- ChildComponent.vue -->
&#x3C;script setup>
import { useUserStore } from '@/stores/user';

const userStore = useUserStore();
&#x3C;/script>

&#x3C;template>
  &#x3C;p>Hallo, {{ userStore.username }}!&#x3C;/p>
&#x3C;/template>
</code></pre>
<p><strong>Vorteile von Pinia:</strong></p>
<p>✅ Zentrale Verwaltung aller Zustände<br>
✅ Kein Prop Drilling<br>
✅ Perfekt für große, komplexe Vue-Projekte</p>
<p>Mit Pinia kannst du deinen Vue.js Datenfluss optimieren und den Code endlich entkoppeln und skalieren.</p>
<hr>
<h2>Fazit: So vermeidest du Prop Drilling in Vue.js richtig</h2>
<p>Prop Drilling kann zu unübersichtlichem Code und hohem Wartungsaufwand führen. Glücklicherweise bietet Vue mit <code>provide/inject</code> und Pinia bessere Alternativen für saubere und wartbare Datenflüsse.</p>
<ul>
<li>Für kleinere Projekte ist <code>provide/inject</code> oft schon genug.</li>
<li>Bei größeren Projekten kommst du an einem sauberen State Management mit <em>Pinia</em> nicht vorbei.</li>
</ul>
<p>Mit der richtigen Methode lässt sich Prop Drilling vermeiden, wodurch eine bessere Code-Struktur und Wartbarkeit sichergestellt wird.</p>
<p>Beide Lösungen helfen dir, vue.js best practices umzusetzen und deinen Komponentenbaum zu entwirren.</p>
<hr>
<h2>FAQ – Häufige Fragen zu Prop Drilling in Vue</h2>
<h3>1. Wann ist Prop Drilling okay?</h3>
<p>Prop Drilling ist in Ordnung, wenn eine Prop nur durch eine oder zwei Komponenten weitergereicht wird und keine komplexen Datenstrukturen involviert sind. In kleinen Projekten kann es eine einfache Lösung sein, ohne zusätzlichen Code für State-Management oder <code>provide/inject</code>.</p>
<h3>2. Wann sollte <code>provide</code> und <code>inject</code> anstelle von Props verwendet werden?</h3>
<p>Diese Methode eignet sich besonders für global relevante Daten wie Theme-Einstellungen, Übersetzungen oder übergeordnete Form-Validierungen. Sie ist ideal, wenn Daten an viele untergeordnete Komponenten weitergegeben werden sollen, ohne unnötige Prop-Ketten zu erzeugen.</p>
<h3>3. Wann ist Pinia die bessere Lösung?</h3>
<p>Pinia sollte verwendet werden, wenn mehrere Komponenten unabhängig voneinander auf denselben Zustand zugreifen müssen. Es ist besonders nützlich für Benutzerverwaltung, Warenkörbe oder größere Anwendungen, in denen Daten aus verschiedenen Bereichen der Anwendung benötigt werden.</p>
<h3>4. Kann <code>provide</code> und <code>inject</code> mit Pinia kombiniert werden?</h3>
<p>Ja! Beispielsweise kann <code>provide</code> genutzt werden, um eine Instanz eines Pinia-Stores nur innerhalb eines bestimmten Komponentenbaums zu überschreiben, ohne den globalen Zustand zu verändern.</p>
<h3>5. Gibt es noch andere Alternativen zur Datenweitergabe?</h3>
<p>Ja. Je nach Anwendungsfall können Composables genutzt werden, um wiederverwendbare Logik und State zu kapseln. In seltenen Fällen kann auch ein Event Bus sinnvoll sein, insbesondere wenn Ereignisse zwischen nicht direkt verwandten Komponenten ausgetauscht werden müssen.</p>]]></content:encoded>
            <category>Vue.js</category>
            <category>Nuxt</category>
            <category>Entwicklung</category>
            <enclosure url="https://blueshoe.de/img/blog/propsdrilling_pinia.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Wagtail Localize: Mehrsprachige Django-Webseiten]]></title>
            <link>https://blueshoe.de/blog/wagtail-localize-tutorial-mehrsprachige-webseiten-django</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/wagtail-localize-tutorial-mehrsprachige-webseiten-django</guid>
            <pubDate>Wed, 09 Apr 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Mehrsprachige Webseiten mit Django Wagtail professionell umsetzen – mit Wagtail Localize lassen sich Inhalte effizient übersetzen und verwalten. In diesem Guide erfährst du, wie du Wagtail Localize installierst, konfigurierst und erfolgreich nutzt.</p>
<p><img src="/img/blog/wagtail-django.svg" alt="Wagtail Localize: Mehrsprachige Django-Webseiten">{.object-cover .max-w-full .mb-5}</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Was ist Wagtail Localize?
::
::GlobalParagraph
<a href="/technologien/wagtail-cms/">Wagtail</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} ist ein leistungsfähiges Django CMS zur Verwaltung von Webinhalten. Wenn du eine mehrsprachige Webseite betreiben willst, hilft dir <a href="https://wagtail-localize.org/stable/">Wagtail Localize</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}, Inhalte einfach zu übersetzen und zu verwalten.
::</p>
<p>::GlobalParagraph
Mit Wagtail Localize kannst du:
::</p>
<p>::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li>Mehrsprachige Inhalte direkt in der Wagtail-Admin-Oberfläche verwalten</li>
<li>Automatische oder manuelle Übersetzungen nutzen</li>
<li>Übersetzungsworkflows und lokale Anpassungen (z. B. für SEO) steuern</li>
<li>Mehrsprachige Webseiten SEO-optimiert und effizient aufbauen
::</li>
</ul>
<p>:::GlobalButton{:url="/technologien/wagtail-cms/" :label="Du möchtest dein Wagtail-Projekt auf den Weg bringen? Erfahre, wie Wagtail deine Webprojekte schneller, flexibler und skalierbar macht." :target="_blank" :color="blue" .mb-6}
:::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Installation und Erste Schritte
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
1. Wagtail Localize installieren
::</p>
<p>::GlobalParagraph
Installiere das Paket mit pip:
::</p>
<p>::BlogCode{.mb-5}</p>
<pre><code class="language-python">pip install wagtail-localize
</code></pre>
<p>::</p>
<p>::GlobalParagraph
Füge wagtail_localize in die INSTALLED_APPS deiner settings.py ein:
::</p>
<p>::BlogCode{.mb-5}</p>
<pre><code class="language-python">INSTALLED_APPS = [
    "wagtail_localize",
    "wagtail_localize.locales",  # Ermöglicht die Verwaltung mehrsprachiger Inhalte
    "wagtail.contrib.modeladmin",
    *INSTALLED_APPS,
]
</code></pre>
<p>::
::GlobalParagraph
Führe die Migrationen aus:
::</p>
<p>::BlogCode{.mb-5}</p>
<pre><code class="language-python">python manage.py migrate wagtail_localize
</code></pre>
<p>::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
2. Sprachen konfigurieren
::</p>
<p>::GlobalParagraph
In settings.py definierst du die unterstützten Sprachen:
::
::BlogCode{.mb-5}</p>
<pre><code class="language-python">from django.utils.translation import gettext_lazy as _

LANGUAGES = [
    ("en", _("English")),
    ("de", _("Deutsch")),
    ("fr", _("Français")),
]

WAGTAIL_CONTENT_LANGUAGES = LANGUAGES
</code></pre>
<p>::</p>
<p>::GlobalParagraph
Aktiviere die Mehrsprachigkeitsoption in Wagtail:
::</p>
<p>::BlogCode{.mb-5}</p>
<pre><code class="language-python">WAGTAIL_I18N_ENABLED = True
</code></pre>
<p>::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Mehrsprachige Seiten in Wagtail verwalten
::</p>
<p>::GlobalParagraph
Wagtail verwaltet mehrsprachige Seiten automatisch über Locales. Dafür erstellt Wagtail für jede Sprache eine separate Version der Seite, die du erstellt hast. Die Sprachen und Seiten können über die Admin-Oberfläche verwaltet werden. Die Sprachverwaltung kannst du an folgenden Screenshot sehen:
::</p>
<p><img src="/img/blog/mehrsprachige-Seiten-verwalten-in-Wagtail.png" alt="Mehrsprachige Seiten in Wagtail verwalten">{.mx-auto .max-w-full}</p>
<p>::GlobalParagraph
Um jetzt in Wagtail Custom-Seiten anzulegen, genügt es, das normale Page-Modell zu benutzen.
::</p>
<p>::GlobalParagraph
Beispiel einer mehrsprachigen Seite:
::</p>
<p>::BlogCode{.mb-5}</p>
<pre><code class="language-python">from wagtail.models import Page
class CustomPage(Page):
    pass
</code></pre>
<p>::</p>
<p>::GlobalParagraph
Diese Klasse wird dann automatisch von Wagtail Localize verwaltet und kann in verschiedenen Sprachen im Seitenmenü angelegt werden.
::</p>
<p><img src="/img/blog/wagtail-localize-seitenmenue.png" alt="Wagtail Localize Seitenmenü">{.mx-auto .max-w-full}</p>
<p>::GlobalParagraph
Jetzt hast du bereits alles, um erste Seiten in mehreren Sprachen zu verwalten! Es gibt aber noch mehr.
::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Wann brauchst du TranslatableMixin?
::
::GlobalParagraph
Falls du Snippets oder andere Datenmodelle mehrsprachig verwalten willst, nutze <a href="https://wagtail-localize.org/stable/tutorial/3-content/">TranslatableMixin</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}.
::
::GlobalParagraph
Beispiel für ein übersetzbares Snippet (z. B. eine Personen-Datenbank):
::</p>
<p>::BlogCode{.mb-5}</p>
<pre><code class="language-python">
from wagtail.snippets.models import register_snippet
from wagtail_localize.models import TranslatableMixin
from django.db import models
from wagtail.admin.panels import FieldPanel
from django.utils.translation import gettext_lazy as _

@register_snippet
class Person(TranslatableMixin, models.Model):
    first_name = models.CharField(max_length=200)
    last_name = models.CharField(max_length=200)
    email = models.EmailField(blank=True, null=True)
    job_function = models.CharField(_("Function"), max_length=400, blank=True, null=True)
    job_function_international = models.CharField(_("Function international"), max_length=400, blank=True, null=True)
    image = models.ForeignKey("wagtailimages.Image", null=True, blank=True, on_delete=models.SET_NULL, related_name="+")
    phone = models.CharField(_("Phone"), max_length=200, blank=True, null=True)
    mobile = models.CharField(_("Mobile"), max_length=200, blank=True, null=True)
    country = models.CharField(_("Country"), max_length=200, blank=True, null=True)

    panels = [
        FieldPanel("first_name"),
        FieldPanel("last_name"),
        FieldPanel("email"),
        FieldPanel("job_function"),
        FieldPanel("job_function_international"),
        FieldPanel("image"),
        FieldPanel("phone"),
        FieldPanel("mobile"),
        FieldPanel("country"),
    ]

    def __str__(self):
        return f"{self.first_name} {self.last_name}"
</code></pre>
<p>::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Typische Anwendungsfälle für TranslatableMixin:
::
::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li>Übersetzbare Team-Mitglieder-Profile</li>
<li>Mehrsprachige Produktbeschreibungen in einer Datenbank</li>
<li>Globale Einstellungen, die sprachspezifisch angepasst werden sollen
::</li>
</ul>
<p>::GlobalTitle{:size="lg" .mb-5}
Best Practices für Wagtail Localize
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
1. Einheitliche Sprachverwaltung
::
::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li>Stelle sicher, dass alle Sprachversionen einer Seite die gleiche Grundstruktur haben.</li>
<li>Übersetze nicht nur den Inhalt, sondern passe auch SEO-relevante Daten (Titel, Meta-Beschreibungen) an.
::</li>
</ul>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
2. SEO für Mehrsprachigkeit optimieren
::
::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li>Nutze hreflang-Tags, um Suchmaschinen zu zeigen, welche Version für welche Sprache gedacht ist.</li>
<li>Stelle sicher, dass URLs sprachspezifisch sind, z. B. /de/about/ statt /about-de/.
::</li>
</ul>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
3. Performance beachten
::
::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li>Caching aktivieren, um häufig genutzte Übersetzungen schneller auszuliefern.
::</li>
</ul>
<p>:::GlobalButton{:url="/technologien/wagtail-cms/" :label="Nutze Wagtail für leistungsstarke, mehrsprachige Websites mit einfacher Verwaltung. Jetzt mehr erfahren!" :target="_blank" :color="blue" .mb-6}
:::</p>
<p>::GlobalTitle{:size="lg" .mb-5}
Dein Einstieg in Wagtail Localize
::</p>
<p>::GlobalParagraph
Mit Wagtail Localize erstellst du mehrsprachige Django-Webseiten schnell, effizient und SEO-optimiert. Dank einfacher Konfiguration und Übersetzungs-Features ist es ein starkes Tool für internationale Webprojekte.
::
::GlobalParagraph
Nutze die Best Practices, um eine nachhaltige und gut strukturierte mehrsprachige Webseite aufzubauen!
::</p>
<p>:::GlobalTitle{:size="lg" .mb-5 .mt-8}
Häufige Fragen
:::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
1. Was ist Wagtail Localize und warum sollte ich es nutzen?
::
::GlobalParagraph
Wagtail Localize ist ein leistungsstarkes Übersetzungs-Tool für das Wagtail CMS. Es ermöglicht dir, mehrsprachige Webseiten effizient zu verwalten, Inhalte direkt in der Admin-Oberfläche zu übersetzen und Übersetzungs-Workflows flexibel zu steuern.
::
::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
2. Wie installiere ich Wagtail Localize in meinem Django-Projekt?
::
::GlobalParagraph
Die Installation erfolgt in drei einfachen Schritten:
::
::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li>Paket installieren: pip install wagtail-localize</li>
<li>In INSTALLED_APPS aufnehmen</li>
<li>Migrationen ausführen mit python manage.py migrate wagtail_localize
::</li>
</ul>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
3. Unterstützt Wagtail Localize automatische Übersetzungen?
::
::GlobalParagraph
Ja! Du kannst Inhalte manuell übersetzen oder eine automatische Übersetzung (z. B. via DeepL oder Google Translate API) in den Workflow integrieren.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
4. Wie funktioniert die Sprachverwaltung in Wagtail?
::
::GlobalParagraph
Wagtail nutzt das Locale-System, um Sprachversionen einer Seite zu verwalten. Jede Sprache erhält eine eigene Seitenversion, die du im Admin-Bereich anpassen kannst.
::</p>
<p>::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
5. Ist Wagtail Localize gut für SEO?
::
::GlobalParagraph
Ja! Wagtail Localize unterstützt SEO-Best Practices für mehrsprachige Webseiten, darunter:
::
::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li>hreflang-Tags zur Kennzeichnung von Sprachversionen</li>
<li>Sprachspezifische URLs (/de/ statt /about-de/)</li>
<li>Individuelle SEO-Texte pro Sprache
::</li>
</ul>]]></content:encoded>
            <category>Django</category>
            <category>Wagtail</category>
            <category>Entwicklung</category>
            <category>Betrieb</category>
            <category>Digitalisierung</category>
            <category>SEO</category>
            <enclosure url="https://blueshoe.de/img/blog/wagtail-django.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Warum unsere Wartungsreports ein Schlüssel zur erfolgreichen Zusammenarbeit sind]]></title>
            <link>https://blueshoe.de/blog/wartung-quartalsreports</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/wartung-quartalsreports</guid>
            <pubDate>Mon, 17 Nov 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Ein Wartungsreport ist für uns mehr als eine Zahlentabelle: Er ist ein Werkzeug für Austausch, Planung und Vertrauen. Er zeigt, was wir getan haben, wo Potenziale liegen und welche Themen im nächsten Quartal anstehen - damit unsere Kunden wissen, dass ihre Systeme in guten Händen sind.</p>
<p><img src="/img/blogs/agile-backlog.svg" alt="Blueshoe Wartung Quartalsreport">{.object-cover .max-w-full .mb-5}</p>
<h2>1. Warum wir auf quartalsweise Wartungsreports setzen</h2>
<p>Würde Heraklit noch heute leben, würde er wohl sagen: "Das einzige Konstante in Software ist Veränderung". So stammt dieses Zitat eben von Jeff Atwood.
Software ist nie wirklich „fertig“. Sobald sie produktiv im Einsatz ist, muss sie gewartet werden. Dafür werden wir <a href="/leistungen/wartung-support/">von unseren Kunden mit Wartungsvereinbarungen beauftragt</a>{target="_blank"}, sodass wir regelmäßig einen abgesprochenen Zeitaufwand in die Pflege, Anpassung und Weiterentwicklung der Projekte stecken.
Die meisten unserer Kunden möchten oder können dabei inhaltlich nicht im Detail mitgestalten. Genau deshalb übernehmen wir als Dienstleister die verlässliche Einplanung und Durchführung der Wartungsaufgaben.</p>
<p>Damit unsere Kunden abseits des Ticketsystems einen Überblick in vergangene und künftige Wartungsaufgaben bekommen, erstellen wir quartalsweise Wartungsreports.
Unsere Kunden haben die Möglichkeit, die geleisteten Tätigkeiten auf den Prüfstand zu stellen und auf künftige Tätigkeiten einzuwirken.
Die Reports schaffen Transparenz über den Zustand komplexer Systeme. Schließlich sind sie ein wichtiges Werkzeug für langfristige Zusammenarbeit.</p>
<h2>2. Was unsere Reports enthalten</h2>
<p>Unsere Reports folgen einer klaren Struktur.
Neben den klassischen Zahlen zu Supportzeiten enthalten sie technische Bewertungen, z.B. zur Stabilität der eingesetzten Softwarekomponenten, Backup-Mechanismen und Sicherheitsaspekte.
Die Übersicht der eingesetzten Softwarekomponenten sieht z.B. folgendermaßen aus:
<img src="/img/blog/lts-versions-overview.png" alt="Stabilität der eingesetzten Softwarekomponenten"></p>
<p>Ergänzt wird das durch Kennzahlen aus Monitoring-Systemen, wie z.B. ein <a href="/blog/fehler-tracking-django-sentry/">Überblick der Fehlerauswertung mittels Sentry</a>{target="_blank"}:
<img src="/img/blog/sentry-overview.png" alt="Überblick der Fehlerauswertung">
Weiterhin gibt es einen Überblick zur Testabdeckung:
<img src="/img/blog/test-coverage-overview.png" alt="Überblick Testabdeckung"></p>
<p>Eine Einschätzung zu Risiken, Herausforderungen und geplanten Themen für das kommende Quartal runden den Wartungsreport ab.</p>
<h2>3. Wie wir unsere Reports vorbereiten</h2>
<p>Die Reports entstehen quartalsweise als Teil der Wartungsroutine. Ein Report wird dabei von mindestens zwei Personen erstellt.
Sofern der Kunde keine expliziten Wünsche hat, setzen wir thematische Schwerpunkte, die wir für das Quartal etwas genauer beleuchten.
Wir prüfen Daten aus verschiedenen Systemen, konsolidieren sie und bewerten Trends.
Jede Empfehlung wird von unserem Team besprochen, bevor sie in den Report fließt.</p>
<p>Im Laufe der Zeit hat sich ein schöner Nebeneffekt ergeben: Der Termin, in dem unser Team den Report für einen Kunden vorbereitet, ist eine ideale Gelegenheit, um Ideen und Entwicklungsmöglichkeit für die Projekte des Kunden zu brainstormen und zu besprechen.
Man beschäftigt sich intensiv mit dem Projekt eines Kunden, ist aber nicht in detaillierte Arbeit verstrickt, wodurch ein übergreifender Blick möglich ist.</p>
<h2>4. Der Austausch mit unseren Kunden</h2>
<p>Der Report wird in einem gemeinsamen Call besprochen. Für unsere Kunden ist das die ideale Gelegenheit, um Fragen zu stellen, Prioritäten zu setzen oder Themen anzustoßen.
Für uns ist der Report ein fester Touchpoint, um Feedback zu erhalten und neue Ideen zu entwickeln. Unsere Kunden bekommen die Reports als PDFs zur Verfügung gestellt.
Auch weniger technisch versierte Kunden verstehen durch die strukturierte Darstellung, wo ihr System steht. Hier haben wir schon häufiger gehört, dass die Reports sehr hilfreich sind, um innerhalb des Unternehmens oder gegenüber Vorgesetzten Transparenz für das Thema Wartung zu schaffen.</p>
<h2>5. Warum Transparenz Vertrauen schafft</h2>
<p>Regelmäßige, nachvollziehbare Reports schaffen Sicherheit. Sie geben einen Überblick über den Zustand komplexer Systeme und ihrer Zukunftssicherheit.
Durch die Regelmäßigkeit können Kunden Entwicklungen über Zeiträume hinweg nachvollziehen und auch selbst steuernd eingreifen, ohne sich mit den sehr technischen Details der Wartungstätigkeiten auseinandersetzen zu müssen.</p>
<h2>6. Fazit: Gemeinsam an stabilen und zukunftssicheren Systemen arbeiten</h2>
<p>Unsere Wartungsreports sind kein Pflichtdokument, sondern ein Spiegel unserer Zusammenarbeit.
Sie zeigen, was erreicht wurde, wo wir stehen und welche Chancen und Herausforderungen im nächsten Quartal warten.
So sorgen wir gemeinsam dafür, dass <a href="/blog/vorteile-open-source-software-unternehmen/">Systeme langfristig stabil, sicher und entwicklungsfähig</a>{target="_blank"} bleiben.</p>]]></content:encoded>
            <category>Projekt Management</category>
            <category>Dokumentation</category>
            <category>Sicherheit</category>
            <category>Betrieb</category>
            <enclosure url="https://blueshoe.de/img/blogs/agile-backlog.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Was steckt hinter Googles AMP?]]></title>
            <link>https://blueshoe.de/blog/was-ist-amp</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/was-ist-amp</guid>
            <pubDate>Fri, 17 Mar 2017 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Im Netz tauchen immer mehr Artikel zu Googles AMP Technologie auf. Nur ein kurzer Hype, oder ein Trend, welcher zukünftig interessant für Webseiten-Betreiber wird? Wir wollen unsere Meinung dazu hier teilen und diskutieren.</p>
<p><img src="/img/blogs/magnet-me-Ayx2M0iiVFQl.jpg" alt="magnet-me">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Seit ca. einem Jahr taucht ein kleiner Blitz mit der Kennung „AMP“ in den mobilen SERPs (Search Engine Result Pages) auf. Dahinter versteckt sich ein Projekt, initiiert von Google: AMP.
:::</p>
<p><img src="/img/blogs/google_result.jpg" alt="google_result">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Quelle: https://moz.com/blog/google-amp-search-results
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
WOFÜR STEHT AMP?
:::
:::globalParagraph
AMP steht für Accelerated Mobile Pages. Es geht also um die Beschleunigung von Mobilen Webseiten, richtig? Ungefähr. Mehr oder weniger. Das große Ziel von AMP ist es die User Experience unserer Nutzer auf Webseiten zu verbessern. Dies realisiert AMP hauptsächlich durch die Verbesserung der Geschwindigkeit der jeweiligen Page. Klickt man auf einen Link mit der oben dargestellten Kennung, so öffnet sich die Seite blitzschnell. Der Mehrwert wird schnell ersichtlich, schaut man auf den Impact, welchen die Geschwindigkeit einer Webseite auf Besucherzahlen, Verweildauer, CTR und Conversion-Rates hat.
:::
:::globalParagraph
Die Geschwindigkeit / Ladezeit von Webseiten hat eine signifikante Relevanz für Besucherzahlen und Conversion-Rates gewonnen. Der Artikel „Find Out How You Stack Up to New Industry Benchmarks for Mobile Page Speed“[1] befasst sich genau damit. Im Januar analysierte ein Team bei Google 900.000 „mobile ad landing pages“.
:::
:::globalParagraph
<strong>Ergebnis:</strong>
:::
:::globalParagraph
Egal in welche Branche wir schauen, mobile Webseiten sind absolut überladen.
:::
:::globalParagraph
Je mehr Elemente, seien es Bilder, Skripte oder Stylings, auf einer Webseite verwendet werden, desto schlechter wird die Performance.
:::
:::globalParagraph
Die Bounce Rate, zu Deutsch „Absprungrate“, sprich die Wahrscheinlichkeit das der Nutzer nur eine Seite anschaut, steigt enorm mit der Ladezeit einer Seite an.
:::</p>
<p><img src="/img/blogs/avarage_load_time-amp.jpg" alt="avarage_load_time-amp">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Die durchschnittliche Zeit bis zum ersten Byte der Antwort (Time to first Byte oder auch TTFB) sollte im besten Falle unter 1.3s liegen. Das klingt simpel, scheint es aber nicht zu sein. Schauen wir uns die durchschnittliche TTFB in Deutschland an, schafft es niemand annähernd an diese Zeit.
:::</p>
<p><img src="/img/blogs/avarage_ttfb.jpg" alt="avarage_ttfb">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Woran liegt das? Dies kann verschiedene Ursachen haben. Ein langsamer Server, ein schlecht konfigurierter oder fehlender Cache oder einfach die falsche Wahl des Hosting Anbieters (was somit eine der vorherigen Ursachen begünstigt) können dazu führen, dass eine Webseite erstmal 2-3 Sekunden braucht um überhaupt zu antworten. Das bedeutet, dass in dieser Zeit noch keine Inhalte ausgeliefert wurden, sondern lediglich die Kommunikation zwischen Browser und Webserver initiiert ist.
:::
:::globalParagraph
Eine Seite sollte im Idealfall weniger als 500kB „wiegen“. Das klingt logisch, je weniger zu übertragen ist, desto schneller ist die Übertragung abgeschlossen. Gerade für Nutzer welche auf mobilen Endgeräte surfen, wenn das Netz mal wieder etwas schlechter ist, sind schlanke Webseiten eine Freude. Wie schaut es denn da so im Schnitt bei den deutschen Webseiten aus?
:::</p>
<p><img src="/img/blogs/avarage_weight.jpg" alt="avarage_weight">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Interessanterweise schafft es auch hier niemand annähernd an die Empfehlung von Google. Dabei müsste es doch so leicht sein, oder? Verzichtet man einfach auf ein wenig „Schnick-Schnack“, ein paar Bildchen weniger und schon geschafft? Dies wird wohl kaum die Lösung sein. Es gibt fast immer gute Gründe entsprechende Inhalte auf den Seiten zu platzieren. Entsprechend ist die Kürzung von Inhalten und Medien der letzte Schritt, welchen wir bei Blueshoe für die Optimierung einer Seite gehen würden. Vorher schauen wir auf andere Faktoren wie z.B. die Komprimierung von Bildern, Skripten und Styles oder das nachladen von Inhalten. Werden z.B. Bilder erst nachgeladen, sobald sie durch Scrollen in den Fokus des Nutzers kommen, wird der initiale Seitenaufruf wesentlich schneller.
:::
:::globalParagraph
Dies sind lediglich 2 Faktoren aus dem Bericht „Find Out How You Stack Up to New Industry Benchmarks for Mobile Page Speed“. Es gibt noch weitere Merkmale, nach denen man seine Seite optimieren kann und sollte. Google hilft bereits dabei die eigene Webseite zu untersuchen: https://testmysite.thinkwithgoogle.com - das Tool basiert auf den PageSpeed Insights[2] von Google.
:::
:::globalParagraph
Nun haben wir gelernt, dass noch vieles falsch läuft bezüglich der Gestaltung und Auslieferung von mobilen Webseiten. Aber wer kann nun eigentlich von AMP profitieren?
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
AMP - NUR FÜR PUBLISHER INTERESSANT?
:::
:::globalParagraph
Schaut man sich um, wer AMP bereits massiv einsetzt, stößt man vorrangig auf Publisher wie Wired, The Washington Post oder The New York Times[3]. Zufall? Nein, das Team hinter AMP ist früh eine Partnerschaft mit den Verlagen eingegangen, da diese in erster Linie von den schnellen Ladezeiten profitieren können. Hat es Ihnen etwas gebracht auf AMP zu setzen? Definitiv, ja. Schaut man auf die Case Studies erscheint der Mehrwert von AMP offensichtlich: 23% Anstieg von wiederkehrenden Besuchern für die Washington Post[4] und 25% Anstieg in der CTR der Suchergebnisse für Wired[5].
:::
:::globalParagraph
Für Webseiten, welche Inhalte veröffentlichen und bereitstellen macht es also anscheinend Sinn. Je nach Aufwand sicher auch für Blogger und Hobbyautoren. Aber was ist mit den anderen Seiten?
:::
:::globalParagraph
WeGo – eine der größten Suchmaschinen für Flüge und Hotels in Indien, haben ihre Landingpages für Flüge mit AMP und PWA (Progressive Web Apps) gestaltet. Das bedeutet konkret: Wenn ein Nutzer Flug Singapur – Bangalore bei Google eingibt, so erscheint die Landingpage für diesen Flug in den Suchergebnissen. Klickt der Nutzer auf jenes Suchergebnis, so öffnet sich die Seite mit einem bereits vorausgefüllten Formular. Im Hintergrund werden bereits die nächsten Schritte geladen (Eingabe von Hin- und Rückflugdatum usw.) Für den Nutzer erscheint der Prozess hierdurch ohne Ladezeit. Dadurch haben sich erhebliche Verbesserung in der Conversion bei WeGo eingestellt[6].
:::
:::globalParagraph
Ebay hat begonnen AMP für ihre Seite auszurollen und wird dies weiterhin ausbauen[7].
:::
:::globalParagraph
Macht AMP also für E-Commerce Seiten Sinn? Auf jeden Fall. Das AMP Team hat bereits angekündigt, die Möglichkeiten für den Bau von Webseiten mit AMP im Sinne von E-Commerce zu erweitern. Einige der dazu benötigten Komponenten wurden vor kurzem auf der AMP Conference vorgestellt. Dieses Beispiel zeigt einen einfachen Konfigurator für ein Produkt auf einer Seite. Es ist (fast) alles da, was unser E-Commerce Herz begehrt: Galerie-Slider, Video, Auswahl von Farben und der „Warenkorb“-Button. Lediglich das Eingabefeld für die Menge ist noch ein Auswahlfeld. Somit kann die Menge vom Nutzer nicht frei bestimmt werden. Aber keine Sorge, Input Felder sind bereits verfügbar[8].
:::
:::globalParagraph
AMP Seiten werden also auch im E-Commerce relevant. Vorstellbar sind Produkt Landingpages, welche mit AMP ausgestattet sind oder einfach die Produktseiten der E-Commerce Webseite/des Shops. Die Landingpages könnten den Nutzer weiter zum Produkt führen oder sogar direkt in den Warenkorb legen lassen.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
WELCHEN EINFLUSS HAT AMP AUF SUCHMASCHINEN-RANKINGS?
:::
:::globalParagraph
Schön und gut, Seiten werden also schneller, aber wie schaut es denn mit SEO aus? Verbessern sich meine Rankings? Google belohnt mich doch sicher, wenn ich AMP in meine Seite integriere!
:::
:::globalParagraph
Die Integration von AMP in eine Webseite bringt keinen Boost im Ranking[9]. Jedoch kann AMP indirekt Auswirkungen auf das Ranking haben: durch den PageSpeed, durch bessere Nutzerzahlen und daraus resultierenden Aktionen wie z.B. das Teilen von Links. Google hat bis jetzt noch kein Statement abgegeben, ob AMP zukünftig ein Ranking-Faktor wird, oder nicht. Es gibt viele Spekulationen, „googelt“ man nach „AMP ranking factor“. Dies könnte zum absoluten Durchbruch von AMP führen.
:::
:::globalParagraph
Nichtsdestotrotz ist AMP eine sehr gute Strategie Teile der eignen Webseite zu verbessern, die User Experience zu erhöhen und somit die eigene Präsenz im Web aufzuwerten. Verbessert man die eigene Webseite, so konsumieren Nutzer mehr Inhalte – was zu höheren Verkaufszahlen, einer stärkeren Bindung oder einfach nur zufriedeneren Lesern führt.
:::</p>
<p>:::globalTitle{:size="lg" .mb-5}
WIE GEHT ES WEITER MIT AMP?
:::
:::globalParagraph
AMP wird von Google professionell vorangetrieben und natürlich gibt es auch eine Roadmap[10]. Wie sieht es also in naher Zukunft aus, was für Möglichkeiten wird uns AMP bieten?
:::
:::globalParagraph
Die Möglichkeiten komplexe Webseiten sowie bestehende Konventionen in AMP umzusetzen werden in Zukunft stetig wachsen. Features wie Autocomplete, Smart Buttons, Tabs, Parallax Scrolling, Hero Video Player, Lightbox sind bereits in Planung oder sogar schon umgesetzt. Entwickler werden sich zwar auf AMP Komponenten beschränken, aber dank der hervorragenden Community (307 Contributors und 9777 Stars auf Github)[11] werden diese ständig weiterentwickelt, verbessert oder gar ganz neue Komponenten entwickelt.
:::
:::globalParagraph
Im nächsten Artikel wird es wieder etwas technischer: Wie AMP aus technischer Sicht funktioniert und auf wie man AMP in Kombination mit Django einsetzen kann werden wir in den nächsten Wochen hier zeigen. Stay tuned!
:::
:::globalParagraph
Als Webagentur für Softwareentwicklung haben wir bei Blueshoe bereits unsere Erfahrungen mit AMP gesammelt und in der Verbindung mit Django-CMS umgesetzt. Bei Fragen stehen wir gerne beratend zur Seite! Hier gibt es aber schon mal ein paar mehr Infos zu Django-CMS:
:::
:::globalParagraph
<a href="/projekte/">Webseiten mit Django-CMS</a>{.bs-link-blue}
:::
:::globalParagraph
Hier noch einer kleiner Talk von der PyConWeb in München, den Robert kürzlich über das Thema Google AMP in Django hielt:
:::</p>
<p>:::GlobalPodcastSection{:videoId="NkrucRmfPrM" :videoPosition="right" :bg="bg-white"}</p>
<p>:::</p>
<p>:::globalParagraph
[1]<a href="https://www.thinkwithgoogle.com/articles/mobile-page-speed-new-industry-benchmarks.html">https://www.thinkwithgoogle.com/articles/mobile-page-speed-new-industry-benchmarks.html</a>{.bs-link-blue :target="_blank"}</p>
<p>[2]<a href="https://developers.google.com/speed/pagespeed/insights/?hl=de">https://developers.google.com/speed/pagespeed/insights/?hl=de</a>{.bs-link-blue :target="_blank"}</p>
<p>[3]<a href="https://www.ampproject.org/learn/who-uses-amp/publishers/">https://www.ampproject.org/learn/who-uses-amp/publishers</a>{.bs-link-blue :target="_blank"}</p>
<p>[4]<a href="https://www.ampproject.org/case-studies/washingtonpost/">https://www.ampproject.org/case-studies/washingtonpost</a>{.bs-link-blue :target="_blank"}</p>
<p>[5]<a href="https://www.ampproject.org/case-studies/wired/">https://www.ampproject.org/case-studies/wired</a>{.bs-link-blue :target="_blank"}</p>
<p>[6]<a href="https://youtu.be/lX8szUWpfjk?t=3h14m28s">https://youtu.be/lX8szUWpfjk?t=3h14m28s</a>{.bs-link-blue :target="_blank"}</p>
<p>[7]<a href="https://youtu.be/lX8szUWpfjk?t=2h20m55s">https://youtu.be/lX8szUWpfjk?t=2h20m55s</a>{.bs-link-blue :target="_blank"}</p>
<p>[8]<a href="https://www.ampproject.org/docs/reference/components/dynamic/amp-form">https://www.ampproject.org/docs/reference/components/dynamic/amp-form</a>{.bs-link-blue :target="_blank"}</p>
<p>[9]<a href="https://moz.com/blog/google-amp-search-results">https://moz.com/blog/google-amp-search-results</a>{.bs-link-blue :target="_blank"}</p>
<p>[10]<a href="https://www.ampproject.org/roadmap/">https://www.ampproject.org/roadmap</a>{.bs-link-blue :target="_blank"}</p>
<p>[11] Stand 12.12 Uhr 17. März 2017
:::</p>]]></content:encoded>
            <category>Performance</category>
            <category>SEO</category>
            <enclosure url="https://blueshoe.de/img/blogs/magnet-me-Ayx2M0iiVFQl.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Was ist Django CMS und warum mögen wir es so?]]></title>
            <link>https://blueshoe.de/blog/was-ist-django-cms</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/was-ist-django-cms</guid>
            <pubDate>Wed, 31 Jan 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Django CMS ist ein quell-offenes Enterprise Content Management System basierend auf dem ausgereiften, schnellen und sicheren Web Framework Django (welches in Python geschrieben und ebenfalls quell-offen ist).</p>
<p><img src="/img/blogs/preview-2_subsampling.jpg" alt="preview-2_subsampling">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
EIN CMS FÜR DIE ZUKUNFT
:::
:::globalParagraph
Django-CMS ist aktuell in Version 3.4 LTS. Die LTS (Long-term support) Version wird für mindestens die nächsten 2 Jahre mit sicherheits-relevanten Updates versorgt. Darüber hinaus wird aber für viele Jahre hinaus die Kompatibilität aller darauf aufbauenden Module sichergestellt. Daher bietet django-CMS 3.4 LTS die geeignete Grundlage, um auch langfristig angelegte <a href="/">Softwareentwicklungs-Projekte</a>{.bs-link-blue} darauf zu realisieren.
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
WER ENTWICKELT DJANGO CMS?
:::
:::globalParagraph
Der Kern von <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} wird maßgeblich von der Schweizer Unternehmung Divio AG entwickelt und öffentlich zugänglich gemacht. Da <a href="/team/">Blueshoe</a>{.bs-link-blue} ein Partner von Divio ist, sind wir auch in der Lage, Einfluss auf die Roadmap und Entwicklung zu nehmen. <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} ist mit seiner einfachen und intuitiven Bedienoberfläche nicht nur kompatibel zu allen aktuellen Web-Browsern, sondern kann auch hervorragend mit mobilen Geräten wie Tablets und Smartphones, gesteuert werden. Deshalb mögen wir es so gerne!
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
EINFACHES BEARBEITEN VON INHALTEN
:::
:::globalParagraph
Wir schwören auf <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} und haben keinen Kunden, der nicht von der simplen Art und Weise Inhalte zu editieren vollkommen überzeugt ist. Redakteure müssen sich nicht durch aufwändige und komplexe Backend-Ansichten navigieren – ein Doppelklick auf den Inhalt reicht, um diesen zu verändern.
:::
:::globalParagraph
Inhalte werden in „Plugins“ dargestellt. Diese können nach dem Drag-and-Drop-Prinzip an einer beliebigen Stelle auf der Seite platziert werden. Damit hat der Administrator die Möglichkeit Struktur und Inhalt der Seite zu bearbeiten, ohne dabei mit HTML-Code in Berührung zu kommen. Die Trennung von Code und Inhalt, aber dennoch eine große Flexibilität in der Darstellung, ist eine weitere Stärke von Django-CMS. Plugins können auch ohne Weiteres verschachtelte Inhaltsstrukturen abbilden. Der Fantasie sind damit in der Strukturierung von Seiten keine Grenzen gesetzt.
:::</p>
<p><img src="/img/blogs/seitenstruktur_mit_plugins.jpg" alt="seitenstruktur_mit_plugins">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Automatisch generierte Seiten werden über sogenannte „Apps“ erstellt. Diese können auf Bestände in der Datenbank zurückgreifen und Ansichten mit Listen, Tabellen oder anderen Darstellungsformen generieren. Diese müssen dann nicht explizit durch einen Administrator angelegt werden. Über Apps lassen sich in <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} die komplexesten Herausforderungen meistern.
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
TAXONOMIE IN DJANGO CMS
:::
:::globalParagraph
Seiten werden in einem Seitenbaum organisiert. Ganz im Stile von <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} kann der Seitenbaum durch einfache "drag'n'drop" Bedienung beliebig und in wenigen Sekunden angepasst werden. Eine Seite vom der zweiten Hierarchie-Ebene auf die Erste holen? Kein Problem, drag'n'drop. Reihenfolge in der Baumstruktur ändern? Kein Problem, drag'n'drop. Das schöne dabei ist, dass es somit kein Problem ist im Verlauf der Erstellung einer Seite auch mal seine Meinung zu ändern. Nichts ist in Stein gemeißelt und Änderungen in Sekundenschnelle umgesetzt.
:::</p>
<p><img src="/img/blogs/seitenbaum_django_cms.jpg" alt="seitenbaum_django_cms">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Für jede Seite kann eine weitere Sprache angelegt werden. Jedoch ist es unproblematisch, wenn eine Seite mal nicht in jeder Sprache verfügbar sein soll. Doch nicht nur das Frontend ist mehrsprachig, auch im Backend werden unterschiedliche Sprachen unterstützt. Bei <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} wurde akribisch darauf geachtet, das multilinguale Webseiten möglichst einfach erstellt und gepflegt werden können.
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
STATE-OF-THE-ART FEATURES
:::
:::globalParagraph
Aus technischer Sicht bietet <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} alle state-of-the-art Funktionalitäten, die man erwartet: von LDAP Authentifizierung bis hin zu einer Versionsverwaltung mit Vergleichsansicht und Audit-Trail. Darüber hinaus bietet <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} ein umfassendes Rechtemanagement, welches nach Belieben erweitert und angepasst werden kann. Dieses findet Anwendung sowohl bei der Rollenverteilung für Administratoren als auch für das „Absichern“ von zugangsgeschützten Seiten.
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
VIELE WEBSITES ABER NUR EIN BACKEND
:::
:::globalParagraph
Genau wie bei der Multilingualität war es Kern der Entwicklung von <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} den Betrieb multipler Webseiten, auch auf völlig unterschiedlichen Domains, so einfach wie möglich zu gestalten. Eine einzige Installation des CMS genügt um alle ihre Seiten unter einen Hut zu bringen. Entwickelte Features, wie bspw. spezielle Plugins können dann bspw. über alle Seiten hinweg deployed und genutzt werden. Aber auch die Einschränkung von Features oder Styles auf nur eine einzelne Instanz der Installation ist ohne weiteres möglich. Plugins lassen sich z. B. auch Seitenübergreifend kopieren und in die Inhaltsstruktur einer anderen Seite einfach einfügen. Das erspart oft viel Arbeit und erleichtert die Verwaltung multipler Seiten ungemein.
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
SKALIERBARKEIT, BACK-UPS UND UPDATES
:::
:::globalParagraph
Die Architektur von <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} erlaubt das einfache Betreiben derselben Anwendungen auf unterschiedlichen Server-Instanzen. Dies ist besonders wichtig, wenn durch sehr hohes Besucheraufkommen eine horizontale Skalierung angestrebt werden soll.
:::
:::globalParagraph
Als Backup-Strategie setzten wir typischerweise inkrementelle Datenbank-Snapshots ein. Diese können bei Bedarf problemlos zurückgespielt werden. Updates werden über den Paketmanager „Pip“ abgebildet. Diese werden zunächst umfänglich auf dem Staging-System getestet, bevor sie in das Live-System eingespielt werden.
:::</p>
<p>:::globalTitle{:size="lg" :tag="h2" .mb-5}
BEISPIELE FÜR <a href="/technologien/django-cms/">Django CMS</a>{.text-bs-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} WEBSEITEN
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>Ofa Bamberg</li>
<li>Campbell Hörmann Steuerkanzlei</li>
<li>CreditGate24.com</li>
<li>Samberger24
:::</li>
</ul>]]></content:encoded>
            <category>Django CMS</category>
            <category>Team Blueshoe</category>
            <enclosure url="https://blueshoe.de/img/blogs/preview-2_subsampling.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Effiziente Webentwicklung mit Vue.js & Nuxt.]]></title>
            <link>https://blueshoe.de/blog/webentwicklung-mit-vuejs-und-nuxt</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/webentwicklung-mit-vuejs-und-nuxt</guid>
            <pubDate>Mon, 07 Oct 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Zusammen bilden Vue.js und Nuxt ein leistungsstarkes Duo für die moderne Webentwicklung. Sie ermöglichen es Entwicklern, schnell skalierbare und wartbare Anwendungen zu erstellen. Erfahre in diese Blogpost warum.</p>
<p><img src="/img/blogs/vuejsnuxt.svg" alt="Vue.js und Nuxt bei Blueshoe.">{.object-cover .max-w-full .mb-5}</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Was ist Vue.js?
:::
:::GlobalParagraph
Die erste Version von Vue.js ist im Jahr 2014 erschienen. Ähnlich wie ReactJS fokussiert Vue.js sich darauf, ein Entwicklungsgerüst (Framework) für reaktive Anwendungen zur Verfügung zu stellen. Vue.js befindet sich mittlerweile in Version 3, es hat stark an Reife gewonnen und wird von Unternehmen wie Google, Netflix, Facebook und Adobe eingesetzt.
:::
:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Schlüsselfunktionen von Vue.js:
:::
:::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li><strong>Reaktives Databinding:</strong> eines der Hauptmerkmale ist die Verwendung von Referenzen in der Verarbeitung von Daten. So lassen sich Variablen an mehreren Stellen gleichzeitig aktualisieren</li>
<li><strong>Computed Properties:</strong> diese berechnen Werte basierend auf Referenzen. Aktualisiert sich eine Referenz, so wird auch die computed Property aktualisiert.</li>
<li><strong>Komponenten:</strong> Vue.js erlaubt die Erstellung von Komponenten als SFC - Single-File Components. Diese kapseln alle relevanten Informationen - DOM-Struktur, JS Logik und CSS-Styles in einer Datei.</li>
<li><strong>Progressive:</strong> Vue.js ist in verschiedene Teilprojekte untergliedert, sodass entsprechend der Anforderung an eine Software nur die notwendigen Abhängigkeiten installiert werden müssen
:::</li>
</ul>
<p><img src="/img/blogs/Vue.js_Logo_2.svg" alt="Vue.js logo">{.mx-auto .h-48 .max-w-full .mt-4}
:::GlobalParagraph{.text-center}
Das Vue.js Logo
:::</p>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Beliebtheit und Community:
:::
:::GlobalParagraph{.mb-4}
Das Vue.js Projekt zählt mittlerweile über 16.000 Follower wohingegen der alte Platzhirsch React mit nur knapp 5.000 Followern abgeschlagen wirkt. Vue. Im Gegensatz zu den Frameworks Angular (Microsoft) und React (Meta) wird Vue.js von einer unabhängigen Community von freien Entwicklern vorangetrieben.
:::</p>
<p>:::GlobalButton{:url="/technologien/vuejs-nuxt/" :label="Erfahre mehr über unsere Vue.js-Entwicklungsdienste" :color="blue" .mb-6}
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Einführung in Nuxt
:::
:::GlobalParagraph
Nuxt ist ein SSR-Framework (Serverseitiges Rendern), welches auf Vue.js aufbaut. Vue.js selbst ist dafür konzipiert, im Browser Anwendungen zu entwickeln. Nuxt nimmt Vue.js als Grundlage und ermöglicht die Generierung HTML und Vue.js Strukturen auf der Serverseite.
:::
:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Was Serverseitiges Rendering (SSR)?
:::
:::GlobalParagraph
Mithilfe des SSR wird der gesamte DOM - sprich die Struktur des HTML - bereits auf dem Webserver erzeugt und direkt an den Browser ausgeliefert. Dieser spart somit zum einen Rechenleistung, zum anderen sind alle Informationen, welche für Suchmaschinen relevant sind (SEO) auch bereits im HTML enthalten. Während in einer reinen Vue.js Anwendung relevante Daten asynchron, bzw. nachträglich geladen werden können, sorgt Nuxt dafür, dass diese Daten zur Zeit des Abrufs der Seite bereits zur Verfügung stehen.
:::</p>
<p><img src="/img/blogs/Nuxt_logo.svg" alt="Nuxt logo">{.mx-auto .h-48 .max-w-full .mt-4}
:::GlobalParagraph{.text-center}
Das Nuxt Logo
:::</p>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Weitere wichtige Features:
:::
:::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li>
<p>Nuxt wird üblicherweise in einem von zwei Modi betrieben:</p>
<ul>
<li><em>Static:</em> Alle verfügbaren Seiten werden generiert und auf eine Festplatte geschrieben. Die Dateien werden dann über einen Webserver zugänglich gemacht.</li>
<li><em>Server-Side-Rendering:</em> Die Seiten werden generiert, wenn der Besucher diese anfragt, und u.U. zwischengespeichert.</li>
</ul>
</li>
<li>
<p>Automatische Routing-Konfiguration: Nuxt erzeugt automatische Routen, Sitemaps und validiert diese.</p>
</li>
<li>
<p>Module - eine einfache Möglichkeit Nuxt Applikationen zu erweitern sind Module wie <a href="https://image.nuxt.com/">@nuxt/image</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}. Dieses Modul ermöglicht denkbar einfach das Resizing und Ausliefern von optimierten Bildern über Content-Delivery-Networks. Nuxt bietet eine reiche Auswahl an <a href="https://nuxt.com/modules">nützlichen Modulen</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}.
:::</p>
</li>
</ul>
<p>:::GlobalButton{:url="/technologien/vuejs-nuxt/" :label="Erfahre mehr über unsere Nuxt-Entwicklungsdienste" :color="blue" .mb-6}
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Vorteile der Verwendung von Vue.js &#x26; Nuxt
:::
:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Performance und Skalierbarkeit
:::
:::GlobalParagraph
Mit der Verwendung von Vue.js sowie Nuxt ist es einfach möglich, die physische (Datei)Größe von Webseiten zu verringern, bzw. diese zu modularisieren und smart zu laden. Dadurch ergeben sich maßgebliche Performance-Potentiale. Ebenfalls eine Skalierung, beispielsweise über Kubernetes, ist einfach möglich, da die Applikationen sich einfach in Container packen lassen.
:::
:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Flexibilität und Anpassbarkeit
:::
:::GlobalParagraph
Durch den progressiven Aufbau beider Frameworks kann zur Entwicklungszeit mit einem minimalen Set an Basisfunktionen gestartet werden. Kommen neue Anforderungen hinzu, lassen sich einfach neue Module einfügen und somit die Funktionalität problemlos erweitern.
:::
:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
SEO-Optimierung
:::
:::GlobalParagraph
Mithilfe von Nuxt Server-Side-Rendering lassen sich reaktive, benutzerfreundliche Applikationen mit einem starken SEO-Fokus erstellen. Extrem gute <a href="https://web.dev/articles/vitals?hl=de">Web-Vitals</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}, hervorragende Crawlbarkeit für Suchmaschinen, kurze Ladezeiten und smarte Logiken zum Laden von Inhalten sind nur die Spitze des Eisberges der SEO-Vorteile für Applikationen, welche auf Nuxt basieren.
:::
:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Entwicklungsfreundlichkeit
:::
:::GlobalParagraph
Vue.js sowie auch Nuxt haben eine sehr steile Lernkurve. Die Dokumentationen sind hervorragend und lassen so auch fremde Entwickler schnell einsteigen. Beide Projekte kommen mit eigenen Dev-Tools Erweiterungen, welche es Entwicklern ermöglichen, schnell tiefgreifende Einblicke in die Applikationen zu gewinnen.
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Praxisbeispiele und erfolgreiche Projekte
:::
:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Projektbeispiele
:::
:::GlobalParagraph
Wenn es darum geht, benutzerfreundliche, interaktive Frontends zu bauen, setzen wir bei Blueshoe i.d.R. auf Vue.js und Nuxt. Ob im Onlineshop von <a href="https://luma-delikatessen.ch/de/">Luma Delikatessen</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}, der Unternehmenswebsite von <a href="https://www.winter-company.com/de/">Winter &#x26; Company</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} oder dem <a href="https://digitallearninglab.de/">digital.learning.lab</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} - die beiden Frameworks haben sich immer als hervorragende Wahl herausgestellt.
:::
:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Fallstudien
:::
:::GlobalParagraph{.mb-4}
Insbesondere im Webshop des schweizer Online-Fleischhändlers Luma Delikatessen hat sich Nuxt bewährt. Hervorragende Crawlbarkeit, eine starke Interaktivität, schnelle Antwortzeiten in Kombination mit modernem Design machen das Einkaufserlebnis auf luma-delikatessen.com zu einer puren Freude.
Mithilfe von Nuxt hat Blueshoe interaktive Produktansichten mit intuitiver Bedienung geschaffen, Produktinformationen geschickt angeordnet und einfache Indizierbarkeit für alle Suchmaschinen sichergestellt.
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5 .mt-5}
Wie sieht eine Implementierungsstrategie aus?
:::</p>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Best Practices
:::
:::GlobalParagraph
Um ein Projekt mit Vue.js oder in Kombination mit Nuxt aufzusetzen, empfiehlt es sich, die Command-Line-Interfaces (CLIs) der beiden Frameworks zu verwenden. Diese bringen bereits einfach konfigurierbare Projekt-Templates mit. Wir empfehlen TypeScript als Sprache, da eine starke und konsistente Typisierung langfristig simple Bugs verhindert und Kosten spart. Des Weiteren empfiehlt sich die Konfiguration von <a href="https://eslint.org/">eslint</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} als Linter und <a href="https://prettier.io/">Prettier</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid} zur Formatierung des Projektes.
:::</p>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Schritt-für-Schritt-Anleitung
:::
:::GlobalBlock{.ul-disk .mb-4}</p>
<ul>
<li>Ein Projekt mit Nuxt zu starten ist denkbar simpel: 
<code>npx nuxi@latest init &#x3C;projekt_name></code>
Schon steht die grundsätzliche Projektstruktur.</li>
<li>Der Entwicklungs-Server kann einfach via <code>npm run dev -- -o</code> gestartet werden.</li>
<li>Die grundlegende Struktur von Nuxt Projekten ist hier detailliert erläutert.
:::</li>
</ul>
<p>:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Häufige Herausforderungen
:::
:::GlobalParagraph
Eine häufige Herausforderung in der Arbeit mit Nuxt ist die Unterscheidung zwischen client- und serverseitiger Ausführung von Code. Der Entwickler muss sich stets bewusst sein, in welchem Kontext der Code ausgeführt wird. So gibt es auf der Serverseite beispielsweise kein <code>window</code> oder <code>document</code> Objekt - da diese lediglich im Browser existieren. Ebenso lässt sich die Ausführung bestimmter Codestellen einfach auf Server- oder Clientseite begrenzen, beispielsweise durch die <a href="https://nuxt.com/docs/api/components/client-only">ClientOnly Komponente</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}.
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Warum Blueshoe als Partner für Vue.js &#x26; Nuxt?
:::
:::GlobalTitle{:color="text-bs-blue" :font="font-oswald" :size="md" :tag="h3" .mb-5}
Unsere Expertise
:::
:::GlobalParagraph
Nach der Umsetzung von zahlreichen Projekten mit Vue.js 2 Nuxt 1 sowie dem Übergang unserer Bestands Projekte auf Vue.js 3 sowie Nuxt 2 ist Blueshoe der Ansprechpartner für die Entwicklung von Frontends mit den genannten Frameworks. Wir kennen die Strukturen der Technologien wie unsere Westentasche, sind mit den Modulen und zusätzlichen Pakete äußerst vertraut und können uns schnell in bestehenden Projekten zurechtfinden.
:::</p>
<p>:::GlobalButton{:url="/technologien/vuejs-nuxt/" :label="Hier erfährst du, warum Blueshoe deine Agentur der Wahl für Projekte mit Vue.js und Nuxt ist" :color="blue" .mb-6}
:::</p>
<p>:::GlobalTitle{:size="lg" .mb-5}
Fazit
:::
:::GlobalParagraph
Mit der stark-steilen Lernkurve von Vue.js und Nuxt, dem progressiven Ansatz beider Frameworks und der Vielseitigkeit in deren Einsatz lassen sich sowohl einfache als auch komplexe moderne Web-Anwendungen bauen. Ob SEO, Performance, UX - alle Anforderungen an moderne Webprojekte lassen sich mit den beiden Technologien bewerkstelligen.
:::
:::GlobalParagraph{.mb-4}
Können wir bei deinem Projekt mit unserer Expertise in Frontend-Technologien behilflich sein? Kontaktiere uns einfach unter <a href="mailto:frontend@blueshoe.de">frontend@blueshoe.de</a>{:target="_blank" .bs-link-blue .hover:underline .hover:decoration-bs-blue .hover:decoration-solid}.
:::</p>]]></content:encoded>
            <category>TypeScript</category>
            <category>Vue.js</category>
            <category>Nuxt</category>
            <category>Entwicklung</category>
            <enclosure url="https://blueshoe.de/img/blogs/vuejsnuxt.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Wie gestaltet man URLs?]]></title>
            <link>https://blueshoe.de/blog/wie-gestaltet-man-urls</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/wie-gestaltet-man-urls</guid>
            <pubDate>Thu, 12 Apr 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Rund um die Gestaltung von URLs gibt es zahlreiche Mythen, diverse Tipps und eine Vielzahl an Dingen, die in dieser Hinsicht zu vermeiden sind – wie es so häufig heißt. In der Serie SEO-Snippets, die bei YouTube zur Verfügung steht, gibt es jetzt direkt von Google Hinweise und Tipps rund um die Gestaltung von URLs.</p>
<p><img src="/img/blogs/design_ohne_titel.jpg" alt="design_ohne_titel">{.object-cover .max-w-full .mb-5}</p>
<h2>URL-GESTALTUNG: GOOGLE GIBT TIPPS UND HINWEISE</h2>
<p>Rund um die Gestaltung von URLs gibt es zahlreiche Mythen, diverse Tipps und eine Vielzahl an Dingen, die in dieser Hinsicht zu vermeiden sind – wie es so häufig heißt. In der <a href="https://www.youtube.com/watch?v=p74HC4x5AUE&#x26;list=PLKoqnv2vTMUPhLQ054sMg3vgzy9md9tWg">Serie SEO-Snippets</a>{target="_blank"}, die bei YouTube zur Verfügung steht, gibt es jetzt direkt von Google Hinweise und Tipps rund um die Gestaltung von URLs.</p>
<h2>FÜR GOOGLE HABEN URLS EINE SEHR GROSSE RELEVANZ</h2>
<p>Eine URL hat für Google in jedem Fall eine ganz große Bedeutung. Mit dieser kann der Suchgigant neue Inhalte im Web erfassen und auch zum Crawlen von Webseiten sind URLs entsprechend wichtig. Als User sollte man aus diesem Grund stets darauf achten, eine URL möglichst so zu gestalten, dass diese für Google – aber natürlich auch für Besucher der Webseite – gut lesbar sind. Ebenso sollte eine URL stets auch</p>
<ul>
<li>einzigartig sein sowie</li>
<li>klar von anderen URLs zu unterscheiden sein.</li>
</ul>
<h2>DIESE ASPEKTE SIND LAUT GOOGLE FÜR URLS ENTSCHEIDEND</h2>
<p>Das aktuelle Google-Video aus der Serie „SEO-Snippets" widmet sich zudem dem Thema URLs und gibt klare Tipps und Hinweise zu diesem Thema. Bei der Gestaltung einer URL sollte man somit auf jeden Fall folgende Aspekte berücksichtigen und beachten:</p>
<ul>
<li>Die URL muss stets valide sein</li>
<li>Einzigartigkeit ist für URLs sehr wichtig</li>
<li>Eine Kodierung mittels UTF-8 ist möglich – Escape-Version und Unicode-Version werden von Google identisch behandelt</li>
<li>In Domainname und Top Level Domain können nicht-lateinische Zeichen durch Unicode dargestellt werden</li>
<li>Eine einfache Verlinkung sollte immer möglich sein. Leerzeichen, Sonderzeichen und Kommata sollen vermieden werden, auch wenn Google damit zurechtkommt.</li>
<li>Bindestriche zum Trennen sind gegenüber Unterstrichen zu bevorzugen</li>
<li>URLs für mehrsprachige Seiten sollten stets auch in der entsprechenden Sprache gestaltet sein</li>
</ul>
<p>Damit gibt Google für URLs klare Hinweise und Vorgaben, die man aus SEO-Sicht sicherlich berücksichtigen sollte. Generell sind „sprechende" URLs allerdings eh zu bevorzugen, da diese oftmals auch für den User an sich hilfreich sein können.</p>]]></content:encoded>
            <category>SEO</category>
            <enclosure url="https://blueshoe.de/img/blogs/design_ohne_titel.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Verständlich gemacht: eigene Konfigurationen für ingress-nginx mit kustomize]]></title>
            <link>https://blueshoe.de/blog/wie-kustomize-ingress-nginx</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/wie-kustomize-ingress-nginx</guid>
            <pubDate>Tue, 01 Jul 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Ingress-nginx ist einer der beliebtesten ingress-controller für Kubernetes. In diesem Blogpost zeigen wir, wie man die <em>ingress-nginx</em> K8s-Ressourcen anpassen und mittels kustomize einfach persistieren kann. Dadurch werden Installation und Updates einfacher und weniger fehleranfällig.</p>
<p><img src="/img/blog/kustomize-ingress-nginx.svg" alt="Eigene Konfigurationen für ingress-nginx mit kustomize">{.object-cover .max-w-full .mb-5}</p>
<p>::GlobalBlogLevelInfo</p>
<ul>
<li>Kubernetes Basics, <a href="https://kubernetes.io/docs/concepts/services-networking/ingress/">Ingress</a>{target="_blank"}</li>
<li><a href="https://github.com/kubernetes/ingress-nginx">Ingress-Nginx</a>{target="_blank"}</li>
<li>kustomize</li>
</ul>
<p>Solltest du Fragen haben, oder dir etwas unklar sein, kannst du die Kommentarfunktion unter dem Artikel nutzen.
::</p>
<h2>1. Problemstellung</h2>
<p>Viele <a href="https://github.com/kubernetes/ingress-nginx">ingress-nginx</a>{target="_blank"} Installationen benötigen Anpassungen. Sei es ein eigenes Fehler-Backend, wodurch die Command-Args und die <em>ConfigMap</em> angepasst werden müssen. Oder z.B. weil zusätzliche Ports benötigt werden, oder der K8s-Service <em>Annotations</em> benötigt, um mit einem <em>Load Balancer</em> zu matchen. Diese Anpassungen können zwar manuell vorgenommen werden. Das erhöht allerdings sowohl bei der Installation, als auch bei Updates das Risiko, etwas zu übersehen oder falsch zu konfigurieren. Zudem ist jedes Update mühselig und erfordert ein zusätzliches Dokumentieren der Anpassungen, damit man nichts vergisst.</p>
<p>Da stellt sich doch die Frage: wie kann man diese Anpassungen so persistieren, dass sie automatisch aufgegriffen werden und bei Updates bestenfalls nur noch die Versionsnummer angepasst werden muss?</p>
<h2>2. Lösungsansatz</h2>
<p><em>kustomize</em> to the rescue! Die Anforderungen sind klar, Anpassungen an den K8s-Ressourcen sollen in Code abgelegt werden und bei der Installation und bei Updates automatisch ausgerollt werden. Kustomize ermöglicht es, die K8s yaml-Manifeste von <em>ingress-nginx</em> als Ressource zu spezifizieren und mit Patches die benötigten Anpassungen vorzunehmen. Folgende <code>kustomization.yaml</code> haben wir in <code>./base/ingress-nginx</code> abgelegt:</p>
<pre><code class="language-yaml">apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - github.com/kubernetes/ingress-nginx/deploy/static/provider/cloud?ref=controller-v1.12.3

patches:
  - path: patch-configmap.yaml
  - path: patch-deployment.yaml
  - path: patch-service.yaml
</code></pre>
<p>Als <code>resources</code> werden die K8s-Manifeste in Version 1.12.3 spezifiziert. Zusätzlich haben wir drei Patches, für die <em>Configmap</em>, für das Deployment und für den Service. Diese schauen wir uns in den nächsten Absätzen genauer an.</p>
<h2>3. Configmap anpassen</h2>
<p>Der Patch für die <em>ConfigMap</em> muss im selben Verzeichnis liegen wie die <code>kustomization.yaml</code>, so wie es dort spezifiziert ist. Hier ist der Inhalt von <code>patch-configmap.yaml</code>:</p>
<pre><code class="language-yaml">apiVersion: v1
kind: ConfigMap
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
data:
  proxy-buffer-number: "4"
  proxy-buffer-size: 128k
  proxy-busy-buffers-size: 256k
  custom-http-errors: 404,503,502,504
</code></pre>
<p>In der <em>ConfigMap</em> werden ein paar <em>proxy</em>-Werte gesetzt, sowie die HTTP-Statuscodes spezifiziert, für die eine Response vom <em>Custom Errors Backend</em> gerendert werden soll.</p>
<h2>4. Command anpassen</h2>
<p>Um den Command anzupassen, muss das Deployment gepatcht werden. <code>patch-deployment.yaml</code> sieht folgendermaßen aus:</p>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
spec:
  template:
    spec:
      containers:
        - name: controller
          args:
          - /nginx-ingress-controller
          - --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller
          - --election-id=ingress-nginx-leader
          - --controller-class=k8s.io/ingress-nginx
          - --ingress-class=nginx
          - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
          - --validating-webhook=:8443
          - --validating-webhook-certificate=/usr/local/certificates/cert
          - --validating-webhook-key=/usr/local/certificates/key
          - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
          - --default-backend-service=default/nginx-errors
          ports:
            - containerPort: 21
              name: ftp
              protocol: TCP
</code></pre>
<p>In diesem Beispiel werden die <code>args</code> überschrieben, um <code>--default-backend-service</code> auf <code>default/nginx-errors</code> zu setzen. Zusätzlich wird noch der Port 21 spezifiziert, um FTP-Requests zu ermöglichen.</p>
<h2>5. Service anpassen</h2>
<p>Der Service enthält üblicherweise Annotations, die für den Load Balancer relevant sind. <em>Ingress-nginx</em> bietet auch K8s Manifeste, die bereits für AWS, GKE, oder Azure optimiert sind. Falls man diese ergänzen möchte, oder einen anderen Cloud-Anbieter wählt, kann man auch das über einen Patch lösen. Hier ist das entsprechende <code>patch-service.yaml</code>:</p>
<pre><code class="language-yaml">apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
  annotations:
    load-balancer.hetzner.cloud/location: fsn1
    load-balancer.hetzner.cloud/name: # …
spec:
  ports:
    - name: ftp
      port: 21
      protocol: TCP
      targetPort: ftp
</code></pre>
<p>Es sind zwei exemplarische Annotations enthalten, die bei Hetzner relevant sind. Eine, um die Location des Load Balancer zu spezifizieren und die andere für den Namen des Load Balancers. Weiterhin ist am Service auch der Port 21 spezifiziert, damit die FTP-Requests zum Deployment gelangen.</p>
<p>::GlobalCallAnExpert{:bg="bg-bs-blue"}
#buttonBlockTitle
Wir können auch Deine Kubernetes Umgebungen optimieren und verwalten.
::</p>
<h2>6. Deployment und Updates</h2>
<p>Die yaml-Beispiele aus den vorigen Abschnitten sind bereits alles, was für das Deployment und für Updates benötigt wird. Wenn man manuell mit <code>kubectl</code> deployt, genügt folgender Command: <code>kubectl apply -k ./base/ingress-nginx</code>.</p>
<p>Für Updates muss lediglich die Version in <code>./base/ingress-nginx/kustomization.yaml</code> angepasst werden und der <code>kubectl</code> Command von oben angewendet werden. Vor jedem Deployment oder Update bietet es sich an, die Ressourcen mittels <code>kubectl kustomize ./base/ingress-nginx</code> rendern zu lassen, damit man überprüfen kann, ob alles korrekt ist.</p>
<h2>7. Fazit</h2>
<p>Wie ihr hoffentlich gesehen habt, ist es ein einfaches, <em>ingress-nginx</em> mittels <em>kustomize</em> anzupassen. Die Codebeispiele dieses Blogposts sind bereits alles, was man benötigt. Der Inhalt ist natürlich von Fall zu Fall anzupassen. Genauso kann es sein, dass man mehrere Overlays benötigt, weil z.B. im Staging und im Production unterschiedliche Annotations benötigt werden. Aber auch das macht es nicht komplexer. Die Änderungen sind übersichtlich und sauber im Repository abgelegt und damit auch gleich dokumentiert. Bei Updates muss lediglich die Version geändert werden, einfacher geht es nicht.</p>
<p>Aus unserer Sicht gibt es keinen Grund, warum man ingress-nginx Anpassungen nicht mittels <em>kustomize</em> erledigen sollte. Hast du ein Gegenargument? Dann lass es uns gerne in den Kommentaren wissen.</p>
<h2>Häufige Fragen</h2>
<h3>1. Warum sollte ich ingress-nginx überhaupt anpassen?</h3>
<p>Viele Setups benötigen Custom-Configs – etwa ein eigenes Fehler-Backend, zusätzliche Ports oder spezielle Load-Balancer-Anbindungen. Ohne Anpassung fehlen oft wichtige Features.</p>
<h3>2. Was bringt mir kustomize gegenüber einem Helm-Chart?</h3>
<p>Kustomize arbeitet deklarativ und direkt auf Kubernetes-YAMLs. Du brauchst kein Template-Rendering und kannst gezielt einzelne Ressourcen patchen - einfach, lesbar und Git-friendly.</p>
<h3>3. Kann ich kustomize auch mit Helm kombinieren?</h3>
<p>Ja, aber dieser Blogpost nutzt bewusst nur kustomize, um die <em>ingress-nginx</em>-Standardressourcen direkt zu patchen. Wer Helm einsetzt, kann helm template und kustomize kombinieren.</p>
<h3>4. Welche Risiken gibt es bei Updates?</h3>
<p>Wenn Upstream-Ressourcen sich stark ändern, können Patches fehlschlagen. Deshalb: Vor jedem Update <code>kubectl kustomize</code> ausführen und prüfen, ob alles sauber rendert.</p>
<h3>5. Was, wenn ich mehrere Umgebungen wie Staging und Production habe?</h3>
<p>Kustomize unterstützt Overlays. Du kannst ein gemeinsames Base-Verzeichnis nutzen und spezifische Patches pro Umgebung in separaten Overlays definieren.</p>
<h3>6. Muss ich bei jeder neuen ingress-nginx-Version alle Patches anpassen?</h3>
<p>Nicht unbedingt. Solange sich Namen, Strukturen und Pfade der Ressourcen nicht ändern, bleiben Patches oft stabil. Trotzdem solltest du sie beim Versionswechsel kurz überprüfen.</p>]]></content:encoded>
            <category>Kubernetes</category>
            <category>Betrieb</category>
            <category>Sicherheit</category>
            <category>Dokumentation</category>
            <enclosure url="https://blueshoe.de/img/blog/kustomize-ingress-nginx.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Unser Fahrplan für deine Software: Wie wir Updates planen, entwickeln und bereitstellen]]></title>
            <link>https://blueshoe.de/blog/wie-planen-wir-softwareupdates</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/wie-planen-wir-softwareupdates</guid>
            <pubDate>Fri, 24 Oct 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Software-Updates sind entscheidend für die Sicherheit, Effizienz und den Funktionsumfang unserer Produkte. Doch wie stellen wir sicher, dass jedes Update ein echter Fortschritt für unsere Kunden ist?</p>
<p><img src="/img/blogs/agile-backlog.svg" alt="Blueshoe Update Planung">{.object-cover .max-w-full .mb-5}</p>
<p>In diesem Beitrag öffnen wir die Türen zu unserem Planungsprozess und zeigen dir, wie wir von der ersten Idee bis zur finalen Bereitstellung sicherstellen, dass unsere Software-Updates genau auf die Bedürfnisse unserer Kunden zugeschnitten sind. Erfahre, wie das Feedback unserer Kunden die Entwicklung steuert und wie wir einen reibungslosen Übergang bei jeder Aktualisierung gewährleisten.</p>
<h2>1. Warum transparente Update-Planung entscheidend ist</h2>
<p>Software ist nie fertig. Sicherheitslücken wollen geschlossen, neue Anforderungen erfüllt und bestehende Funktionen verbessert werden. Regelmäßige Updates sind daher kein "Nice-to-have", sondern essenziell für Stabilität, Performance und Sicherheit.
Unser Anspruch: Transparenz. Wir möchten, dass unsere Kunden nicht nur wissen, dass ein Update kommt, sondern auch, warum, wann und wie es entsteht.
In diesem Beitrag geben wir dir einen Blick hinter die Kulissen – von der ersten Idee über die interne Roadmap bis zur finalen Veröffentlichung. Dazu gehören nicht nur neue Features und Bugfixes, sondern auch regelmäßige Updates von Third-Party-Packages, die für Sicherheit und Stabilität unverzichtbar sind.</p>
<h2>2. Unsere Philosophie: Kundenfeedback als Herzstück</h2>
<p>Viele neue Funktionen beginnen mit einer Beobachtung oder einem Wunsch aus dem Alltag unserer Kunden.</p>
<ul>
<li><strong>Die Stimme unserer Kunden zählt</strong>: Wir sammeln Feedback über Tickets, direkte Gespräche oder auch den quartalsweisen Reports zu den Wartungstätigkeiten.</li>
<li><strong>Von der Idee zur Funktion</strong>: Aus diesen Rückmeldungen entstehen konkrete Verbesserungsvorschläge, die wir im Produktteam diskutieren.</li>
<li><strong>Priorisierung</strong>: Nicht jede Idee kann sofort umgesetzt werden. Wir bewerten Dringlichkeit, Kundennutzen, strategische Relevanz und technischen Aufwand – so stellen wir sicher, dass die wichtigsten Verbesserungen zuerst realisiert werden.</li>
</ul>
<h2>3. Der Update-Fahrplan: Ein Blick in unsere Roadmap</h2>
<h3>3.1 Update-Kategorien</h3>
<p>Unsere interne Roadmap sorgt dafür, dass wir kurzfristige Verbesserungen und langfristige Visionen in Einklang bringen.
Wir halten uns dabei an die drei Arten von Updates, die das <a href="https://semver.org/">Semantic Versioning</a>{:target="_blank"} definiert:</p>
<ol>
<li><strong>Major-Updates</strong>: Große Neuerungen, neue Kernfunktionen oder eine Änderung bestehender Funktionalität.</li>
<li><strong>Minor-Updates</strong>: Optimierungen bestehender Workflows, kleinere neue Features. Die bestehende Funktionalität kann wie bisher genutzt werden.</li>
<li><strong>Patches &#x26; Bugfixes</strong>: Schnelle Fehlerbehebungen und Sicherheitsupdates.</li>
</ol>
<p>Diese Kategorien verwenden wir sowohl bei internen Weiterentwicklungen, als auch bei regelmäßigen Updates der verwendeten externen Pakete.</p>
<h3>3.2 Externe Pakete im Fokus</h3>
<p>Unsere Software basiert auf einer Vielzahl bewährter Open-Source-Bibliotheken und externer Pakete.
Damit diese Bausteine sicher, performant und kompatibel bleiben, prüfen wir regelmäßig neue Versionen und aktualisieren sie zeitnah.
Für unsere Kunden bedeutet das:</p>
<ul>
<li><strong>Sicherheitsvorteil</strong>: Kritische Sicherheitslücken in externen Abhängigkeiten schließen wir ohne Verzögerung.</li>
<li><strong>Weniger Abstimmungsaufwand</strong>: Minor- und Patch-Updates solcher Pakete setzen wir meist eigenständig im Rahmen der Wartungsvereinbarung um – inklusive Tests und Deployment.</li>
<li><strong>Planbare Major-Updates</strong>: Größere Versionssprünge, die mögliche Anpassungen erfordern, stimmen wir vorab gemeinsam mit unseren Kunden ab und begleiten mit Staging-Tests.</li>
</ul>
<p>So stellen wir sicher, dass auch die "unsichtbaren" Teile unserer Software – die externen Pakete – stets auf einem aktuellen und sicheren Stand bleiben, ohne dass unsere Kunden sich darum kümmern müssen.</p>
<h3>3.3 Priorisierung &#x26; Timing</h3>
<p>Updates aus Kategorie 2 (Minor) und 3 (Patches) haben oftmals einen kleineren Entwicklungsumfang; bei externen Paketen sind häufig gar keine Anpassungen notwendig. Diese Updates setzen wir deshalb kurzfristig um und deployen sie direkt.
Major-Updates hingegen erfordern mehr Abstimmung. Oft wollen unsere Kunden nach solchen Updates zunächst ausführliche Tests im Staging-System durchführen, bevor das Update im Produktiv-System deployt wird.</p>
<h2>4. Der Entwicklungsprozess: Von der Planung zur Qualitätssicherung</h2>
<p>Unsere Teams arbeiten nach agilen Prinzipien. In kurzen Sprints entwickeln wir neue Features, testen sie und passen sie flexibel an neue Anforderungen an.
Mehrstufige Tests – automatisiert, intern, sowie von unseren Kunden im Staging System – stellen sicher, dass neue Versionen stabil und sicher sind.
Parallel dokumentieren wir jede Änderung sorgfältig, damit Funktionen verständlich erklärt und in unserer Wissensdatenbank verfügbar sind.</p>
<p>Es gilt allerdings anzumerken, dass es nicht jede Idee sofort in die Umsetzung schafft – Ressourcen, technische Abhängigkeiten, strategische Ausrichtung und das zur Verfügung stehende Budget unserer Kunden spielen dabei eine wichtige Rolle.
Im - hoffentlich langen - Leben eines Softwareprodukts ist es völlig normal, dass Funktionen oder potenziellen Bugfixes nicht umgesetzt werden, etwa weil die Umsetzung sich zu geringer Relevanz erweist.</p>
<h2>5. Kommunikation ist alles: Updates transparent ankündigen</h2>
<p>Wir möchten, dass unsere Kunden nie überrascht werden. Besonders Major Updates haben einen größeren Abstimmungsaufwand. Sowohl von unserer Seite, um die Entwicklerkapazitäten planen und vorhalten zu können. Aber natürlich auch von Kundenseite, um Test-Kapazitäten bereitzuhalten und einen geeigneten Deploymenttermin zu finden.
Auch kleinere Entwicklungen werden eng mit unseren Kunden abgestimmt.</p>
<p>Lediglich Minor- und Patch-Updates der externen Pakete werden eigenständig von uns im Rahmen der Wartungsvereinbarung geplant, durchgeführt, getestet und deployt.</p>
<p>Zu jedem Update gibt es ein Ticket in unserem Ticketsystem. Dieses wird nach dem Deployment aktualisiert, sodass unsere Kunden jederzeit Einblick über erfolgte Deployments haben.</p>
<h2>6. Fazit: Gemeinsam die Zukunft der Projekte gestalten</h2>
<p>Unsere Update-Strategie basiert auf Transparenz, Feedback und Qualität.
Der Input unserer Kunden bestimmt, wohin sich die Projekte entwickelt – und wir sorgen dafür, dass jedes Update reibungslos und gut dokumentiert bei allen Kunden ankommt.</p>]]></content:encoded>
            <category>Projekt Management</category>
            <category>Sicherheit</category>
            <category>Betrieb</category>
            <enclosure url="https://blueshoe.de/img/blogs/agile-backlog.svg" length="0" type="image/svg"/>
        </item>
        <item>
            <title><![CDATA[Wie wichtig ist User Experience (UX) 2019?]]></title>
            <link>https://blueshoe.de/blog/wie-wichtig-ist-user-experience-ux-2019</link>
            <guid isPermaLink="false">https://blueshoe.de/blog/wie-wichtig-ist-user-experience-ux-2019</guid>
            <pubDate>Mon, 29 Apr 2019 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Nutzerzentriertes Website-Design wird 2019 noch wichtiger. John Müller, Webmaster Trends Analyst bei Google, hat bereits Anfang des Jahres darauf hingewiesen, dass man sich bei der Optimierung von Websites stärker auf das Nutzungserlebnis der User fokussieren soll. Optimierungen, die nur auf ein besseres Google-Ranking ausgerichtet sind, gehören laut Müller der Vergangenheit an.</p>
<p><img src="/img/blogs/pexels-pixabay-147413.jpg" alt="pexels-pixabay-147413">{.object-cover .max-w-full .mb-5}</p>
<p>:::globalParagraph
Der Begriff “User Experience” ist nicht zu verwechseln mit dem der “Usability”. Während User Experience das <strong>Nutzungserlebnis</strong> meint, steht Usability für <strong>Benutzerfreundlichkeit bzw. Gebrauchstauglichkeit</strong>. User Experience geht also einen Schritt weiter und schließt Usability dabei mit ein. Definiert wird User Experience offiziell wie folgt:
:::
:::globalParagraph
<em>"Wahrnehmungen und Reaktionen einer Person, die aus der tatsächlichen und/oder der erwarteten Benutzung eines Produkts, eines Systems oder einer Dienstleistung resultieren. [...] Dies umfasst alle Emotionen, Vorstellungen, Vorlieben, Wahrnehmungen, physiologischen und psychologischen Reaktionen, Verhaltensweisen und Leistungen, die sich vor, während und nach der Nutzung ergeben. (DIN ISO 9241-210)"</em>
:::
:::globalParagraph
Zur User Experience zählen: Nutzerspaß (Joy of Use), Nutzbarkeit (Usability), Nutzwert (Utility) und <a href="/blog/barrierefreie-webseite-erstellen/">barrierefreier Zugang</a>{.bs-link-blue} (Accessibility). All diese Faktoren tragen dazu bei, das Nutzer sich auf der Seite “wohlfühlen” und dementsprechend auch wieder dorthin zurückkehren. Das könnte man als digitale Kundenbindung bezeichnen. Schön und gut, aber hilft dieses positive Nutzungserlebnis auch dabei, ein besseres Ranking bei Google zu erzielen?
:::</p>
<p>:::globalTitle{:size="md" :tag="h4" .mb-5}
Verbessert eine gute UX mein Ranking bei Google?
:::
:::globalParagraph
Die Antwort auf diese Frage lautet: indirekt. Denn wenn die Seite den Nutzern gefällt, kommen sie wieder und hinterlassen im besten Fall positive <strong>Usersignale</strong>. Zu diesen Signalen bzw. messbaren Kennzahlen zählen u. a.:
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>neue Verlinkungen</li>
<li>hohe Klickrate</li>
<li>hohe Verweildauer</li>
<li>wenig Short Clicks (Klicks, die zurück auf die Suchergebnisseite und direkt zum nächsten Ergebnisses führen, was auf ein schlechtes erstes Ergebnis schließen lässt.)</li>
<li>geringe Absprungrate</li>
<li>hohe Wiederkehrrate
:::
:::globalParagraph
Natürlich bleibt dabei zu beachten, dass man diesen Kennzahlen nicht immer blind vertrauen darf. Denn eine kurze Verweildauer kann beispielsweise auch bedeuten, dass der Nutzer die von ihm gesuchte Information einfach sehr schnell gefunden hat, und sich deshalb nicht besonders lange auf der Website aufhält.
:::</li>
</ul>
<p>:::globalParagraph
Außerdem darf man bei all dem Fokus auf das Verhalten der User eines nicht vergessen: gute Inhalte. Denn ohne die, werden die User keinen langfristigen Nutzen von der Webseite haben und auch nicht wiederkehren. Optimiertes UX-Design für eine inhaltlich schlechte Webseite kann man sich schlichtweg sparen.
:::</p>
<p>:::globalTitle{:size="md" :tag="h4" .mb-5}
Fünf Top-User-Experience-Trends für 2019
:::
:::globalParagraph
Um eine Website im UX-Wettbewerb nach vorne zu bringen, muss man nicht nur den Nutzer in den Mittelpunkt stellen, sondern sich gleichzeitig auch dem digitalen Wandel stellen. Denn wer sein <strong>digitales Nutzungserlebnis</strong> fortwährend verbessern will, darf den Trend nicht verschlafen.
:::
:::globalParagraph
Die <strong>fünf wichtigsten Trends</strong> in diesem Jahr sind unserer Meinung nach:
:::
:::GlobalBlock{.ol-decimal .mb-5}</p>
<ol>
<li>Gutes UX-Design beinhaltet Sprachsteuerung.</li>
<li>Gutes UX-Design nutzt Augmented Reality.</li>
<li>Gutes UX-Design fokussiert sich auf Personalisierungen.</li>
<li>Gutes UX-Design arbeitet mit Chatbots.</li>
<li>Gutes UX-Design wirkt geräteübergreifend.
:::</li>
</ol>
<p>:::globalParagraph
All diesen Trends ist eines gemeinsam: Sie erleichtern dem User die Nutzung und sparen ihm im Idealfall Zeit, Geld und Nerven. Hier ein paar <strong>konkrete Beispiele</strong>:
:::
:::GlobalBlock{.ol-decimal .mb-5}</p>
<ol>
<li><strong>Sprachsteuerung:</strong> Wenn man gerade keine Hand frei hat, um eine Suchanfrage einzutippen, spricht man sie einfach auf.</li>
<li><strong>Augmented Reality:</strong> Wenn man wissen will, ob einem das neue Brillenmodell steht, lädt man einfach ein Foto von sich hoch und lässt mittels Augmented Reality ein Bild von sich selbst mit eben erwähnter Brille erstellen.</li>
<li><strong>Personalisierungen:</strong> Wenn man nach einem neuen Sommerkleid sucht, ist man über Vorschläge froh, die den eigenen Kleidungsstil widerspiegeln. Stichwort: Algorithmus.</li>
<li><strong>Chatbots:</strong> Wenn man den Kundenservice-Chat nutzen kann, um ein Problem zu lösen, zieht man das vermutlich der Warteschleife am Telefon vor.</li>
<li><strong>Geräteübergreifend:</strong> Wenn man ein Flugticket online gekauft hat, checkt man am Schalter mit dem Smartphone ein oder lässt sich von der Smartwatch über Verspätungen informieren.
:::</li>
</ol>
<p>:::globalTitle{:size="md" :tag="h4" .mb-5}
Die Vorteile von gutem User Experience-Design
:::
:::globalParagraph
Mit gutem UX-Design kann man viel erreichen. Die folgenden Punkte sind Beispiele, die man durch eine Verbesserung der User Experience erreichen kann. Natürlich geht das nicht von heute auf morgen. Aber die Investition in ein emotionales, positives Nutzungserlebnis lohnt sich und verspricht:
:::
:::GlobalBlock{.ul-disk .mb-5}</p>
<ul>
<li>Aufbau von Vertrauen zur Marke bzw. zum Produkt</li>
<li>Höhere Besucherzahlen (Page Impressions)</li>
<li>Verminderung der Absprungrate</li>
<li>Höhere Conversion Rate</li>
<li>Verminderung der Kosten im First-Level-Support
:::</li>
</ul>
<p>:::globalTitle{:size="md" :tag="h4" .mb-5}
Fazit
:::
:::globalParagraph
Wer in gutes User Experience-Design investiert, erntet zwar nicht automatisch eine bessere Ranking-Position bei Google, aber er befindet sich auf dem besten Weg dahin. Schon lange wird von Google gepredigt, den <strong>Nutzer in den Mittelpunkt zu stellen</strong>. Und da ist es nur konsequent, wenn man das Nutzungserlebnis für potenzielle Kunden vor, während und nach dem Besuch einer Webseite so gut wie möglich optimiert.
:::
:::globalParagraph
Ein Nutzer, der intuitiv und mühelos alles (und mehr) findet, was er sucht, ist ein zufriedener Nutzer. Wenn dann noch ein <strong>emotionaler Mehrwert</strong> - der <strong>Joy of Use</strong> - hinzu kommt, ist die User Experience perfekt. Im Idealfall kehrt der Nutzer aufgrund seiner positiven Erfahrungen immer wieder zurück, empfiehlt das Produkt bzw. die Marke weiter und sendet somit positive Usersignale, die zu einer Verbesserung im Google-Ranking führen können.
:::</p>
<p>:::globalParagraph
Bildnachweis: Rob Hampson / Unsplash
:::</p>]]></content:encoded>
            <category>Digitalisierung</category>
            <category>SEO</category>
            <enclosure url="https://blueshoe.de/img/blogs/pexels-pixabay-147413.jpg" length="0" type="image/jpg"/>
        </item>
    </channel>
</rss>