<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>Amirs Blog</title><link>https://amir.goodarzi.net/de/</link><description>Amir Goodarzi blog</description><generator>Hugo -- gohugo.io</generator><language>de</language><copyright>Diese Arbeit ist lizenziert unter einer Creative Commons Namensnennung-NichtKommerziell 4.0 International Lizenz.</copyright><lastBuildDate>Wed, 16 Oct 2024 13:25:00 +0200</lastBuildDate><atom:link href="https://amir.goodarzi.net/de/index.xml" rel="self" type="application/rss+xml"/><item><title>Heute gelernt: git restore zum Rückgängigmachen von Dateiänderungen und Synchronisierung mit einem anderen Branch</title><link>https://amir.goodarzi.net/de/til-16-10-2024-git-restore/</link><pubDate>Wed, 16 Oct 2024 13:25:00 +0200</pubDate><author>Amir</author><guid>https://amir.goodarzi.net/de/til-16-10-2024-git-restore/</guid><description><![CDATA[<blockquote>
<p><strong>Heute habe ich gelernt</strong>, dass man mit dem Befehl <code>git restore</code> eine bestimmte Datei in ihren ursprünglichen Zustand zurückversetzen kann – auch nach mehreren Commits – oder eine Datei mit einem anderen Branch synchronisieren kann.</p>
</blockquote>
<p>Der Befehl <code>git restore</code> ermöglicht es, eine Datei auf einen früheren Zustand zurückzusetzen oder sie mit einem anderen Branch abzugleichen. Das ist besonders nützlich, wenn man Änderungen verwerfen und eine Datei mit der Version aus einem anderen Branch – zum Beispiel <code>main</code> – in Einklang bringen möchte.</p>
<p>In diesem Beispiel möchte ich alle committeten Änderungen im aktuellen Branch für eine bestimmte Datei verwerfen und sie mit der Version aus dem <code>main</code>-Branch angleichen:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="In Zwischenablage kopieren"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git restore --source<span class="o">=</span>main -- main.tf</span></span></code></pre></div></div>
<ul>
<li>git-restore <a href="https://git-scm.com/docs/git-restore" target="_blank" rel="noopener noreffer ">Dokumentation</a></li>
<li>Das Titelbild steht unter der <a href="https://creativecommons.org/licenses/by/3.0/deed.de" target="_blank" rel="noopener noreffer ">Creative Commons Attribution 3.0 Unported</a>-Lizenz; <a href="https://commons.wikimedia.org/wiki/File:Git-logo.svg" target="_blank" rel="noopener noreffer ">Quelle</a>: Wikimedia Commons</li>
</ul>
]]></description></item><item><title>Docker-Images sicher und schnell mit Kaniko erstellen</title><link>https://amir.goodarzi.net/de/kaniko/</link><pubDate>Thu, 10 Feb 2022 03:29:25 +0330</pubDate><author>Amir</author><guid>https://amir.goodarzi.net/de/kaniko/</guid><description><![CDATA[<h2 id="einleitung">Einleitung</h2>
<h3 id="was-ist-kaniko">Was ist Kaniko?</h3>
<p><a href="https://github.com/GoogleContainerTools/kaniko" target="_blank" rel="noopener noreffer ">Kaniko</a> ist im Wesentlichen ein Docker-Image-Builder. Diese Einführung mag unbefriedigend erscheinen, aber genau darum geht es bei Kaniko: Images erstellen und in eine Registry pushen – sei es Dockerhub, ECR, ACR oder eine eigene Container-Image-Registry.</p>
<h3 id="wie-funktioniert-es">Wie funktioniert es?</h3>
<p>Die Nutzung ist denkbar einfach. Zunächst wird das Dateisystem des Basis-Images extrahiert. Anschließend wird jede Anweisung (COPY, ADD, RUN) im Dateisystem des Basis-Images ausgeführt. Dabei wird ein Snapshot des aktuellen Dateisystemzustands erstellt, neu hinzugefügte oder geänderte Dateien werden dem Basis-Image hinzugefügt, und die Image-Metadaten werden aktualisiert. Äußerst nützlich – oder? Nicht, bis man gelernt hat, es zu verwenden.</p>
<h3 id="anwendungsfälle">Anwendungsfälle</h3>
<p>Kaniko erstellt <strong>Docker-Images</strong> innerhalb eines Containers oder Kubernetes-Clusters. Wenn eines der folgenden Probleme bekannt vorkommt, hilft dieser Artikel dabei, es zu lösen:</p>
<ul>
<li>
<p>Beim Erstellen von Images auf einer gemeinsam genutzten Maschine oder einem geteilten Runner lässt sich das Risiko von Sicherheitsverletzungen reduzieren. Es gibt verschiedene Methoden zum Erstellen von Images: Bash-Skripte, Ansible-Playbooks oder Skripte im CI/CD-System, die etwas wie <code>docker build -t beispiel:v0.0.1 -f MeinDockerfile</code> ausführen. Dieser Befehl kann aus der Shell oder einem Container heraus (Docker in Docker) ausgeführt werden. Keine dieser Methoden ist für gemeinsam genutzte Umgebungen geeignet. Stellen Sie sich vor, ein anderer Nutzer, Entwickler oder eine andere Pipeline erhält Zugriff auf den Code, greift auf kritische Datenbankdaten zu oder stiehlt Zugangsdaten oder Schlüssel aus einer anderen Quelle. Dies kann zur Katastrophe führen: Code kann von Unbefugten eingesehen, Datenbankdaten können geleakt, oder Schlüssel können gestohlen werden. Das ist der schlimmste Albtraum für jedes Sicherheits- und Betriebsteam.</p>
</li>
<li>
<p>Kürzere Build-Zeiten sind ein weiterer interessanter Vorteil. Server (Runner) bereiten DevOps-Teams häufig Kopfzerbrechen, wenn Build-Prozesse lange laufen. Ein großes Codebase mit vielen Abhängigkeiten aus dem Internet kann den Tag verderben. Kein DevOps- oder SRE-Team möchte ein weiteres Ticket erhalten mit dem Inhalt: &ldquo;Unsere Build-Pipelines sind langsam. Wir sind in einer Feuerwehrsituation und Hotfixes müssen sofort in die Produktion.&rdquo;</p>
</li>
<li>
<p>Bei begrenztem Infrastrukturbudget wird oft vollständig auf den Kubernetes-Cluster gesetzt – von der Datenbank bis zur Umgebung für das Erstellen von Docker-Images. (Profi-Tipp: Nichts auf Kubernetes deployen, was nicht sein muss. Darüber werde ich in Zukunft schreiben. Datenbanken können auf Kubernetes laufen – aber tun Sie es besser nicht.)</p>
</li>
</ul>
<p><strong>Kaniko</strong> hilft, diese Probleme zu lösen. Kein Superheld – aber es kann uns zum Helden im Unternehmen machen.</p>
<h2 id="das-problem">Das Problem</h2>
<h3 id="was-wir-hatten">Was wir hatten</h3>
<p>Ich nutze diese Gelegenheit, um zu beschreiben, wie unsere CI/CD-Infrastruktur bei <a href="https://www.linkedin.com/company/alibaba-travels/" target="_blank" rel="noopener noreffer ">Alibaba Travels</a> aufgebaut war und was wir getan haben, um sie zuverlässiger zu machen. Es handelte sich um eine veraltete Infrastruktur, die erneuert oder umstrukturiert werden musste. Wir haben bereits mehrere Projekte in diese neue Struktur migriert – in Staging-, Entwicklungs- und anderen Nicht-Produktionsumgebungen – aber es bleibt noch viel zu tun.</p>
<p>Zurück zum eigentlichen Thema: Wir haben Projekte aus verschiedenen Tech-Stacks. Wir nutzen alles von Dotnet Core über Nodejs bis Python. Zuvor nutzten alle Projekte einen gemeinsamen Runner, der auf GitLab Continuous Integration lief. Auf diesem leistungsstarken Server waren mehrere Shell- und Docker-Runner installiert (sie sind zur Risikominimierung noch verfügbar, aber die Migration ist geplant). Projekte, die ihren Build-Prozess in Docker ausführen, haben Zugriff auf den Docker-Socket, und alle Prozesse innerhalb dieses Runners können sich gegenseitig sehen – und sogar Shell-Zugriff auf andere Container gewähren.</p>
<h3 id="die-gefahrenzone">Die Gefahrenzone</h3>
<p>Dies kann dazu führen, dass Daten und möglicherweise auch Code ausgelesen werden. Shell-Runner sind ebenfalls verfügbar und bieten Shell-Zugriff auf den Runner – mit allen Rechten des <code>gitlab-runner</code>-Benutzers. Mehrere Gründe haben uns dazu bewogen, unsere Runner auf Kubernetes zu migrieren: bessere Ressourcenkontrolle pro Projekt, bessere Netzwerkkontrolle, Isolation zwischen Containern und mehr.</p>
<h3 id="einen-schritt-weiter">Einen Schritt weiter</h3>
<p>Nach dem Einrichten mehrerer Kubernetes-Runner lief alles reibungslos – aber ein neues Problem entstand. Es ist nicht möglich, den Docker-Socket des Hosts in den Container zu mounten. Das Risiko ist zu groß: Der Container würde Zugriff auf alle Container des Hosts erhalten. Dedizierte Maschinen wären eine Lösung, aber kostspielig. Kann man einen einzigen Docker-Socket in mehrere Container einbinden? Außerdem muss der Security Context berücksichtigt werden, was weitere Sicherheitslücken schafft.</p>
<p><strong>Das ist die Katastrophe.</strong></p>
<h2 id="die-lösung">Die Lösung</h2>
<h3 id="was-wir-brauchen">Was wir brauchen</h3>
<p>Kaniko hilft uns, diese Lücken zu schließen. Nach der Implementierung von Kaniko dauerte das Erstellen eines Docker-Images aus einer DotNet-Anwendung 21 Minuten. Ohne den Kubernetes-Runner dauerte der Prozess dreieinhalb Minuten. Für eine Node.js-Anwendung stieg die Zeit auf 45 Minuten. Auf unseren gemeinsamen Runnern dauerte es 11 Minuten.</p>
<h3 id="was-wir-gemacht-haben">Was wir gemacht haben</h3>
<p>Wir wollten es zuverlässiger machen, aber unsere Lösung brauchte deutlich mehr Zeit.</p>
<h4 id="caching">Caching</h4>
<p>Caching hat das Problem für mich gelöst. Zunächst muss Caching aktiviert werden (Flag <code>--cache=true</code>). Es gibt zwei Caching-Optionen:</p>
<ul>
<li>Cache in einem lokalen Verzeichnis (<code>--cache-dir</code>)</li>
<li>Cache in einem bestimmten Repository (<code>--cache-repo</code>-Flag)<br>
Da wir viele Ressourcen in unserer Registry haben, haben wir uns für die Repository-Option entschieden.</li>
</ul>
<p>Nicht vergessen: COPY- und RUN-Anweisungen können gecacht werden, wenn das Flag <code>--cache-copy-layers=true</code> gesetzt ist.<br>
Hinweis: Beide Caching-Methoden sind nur verfügbar, wenn <code>--cache=true</code> gesetzt ist.</p>
<h4 id="dockerfile">Dockerfile</h4>
<p>Dieses Dockerfile sieht auf den ersten Blick in Ordnung aus – und der Entwickler ist zufrieden damit:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-dockerfile">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="In Zwischenablage kopieren"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">mcr.microsoft.com/dotnet/sdk:6.0</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> apt update <span class="o">&amp;&amp;</span> apt install libgnutls30 <span class="o">&amp;&amp;</span> apt install ca-certificates <span class="o">&amp;&amp;</span> update-ca-certificates<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">WORKDIR</span><span class="w"> </span><span class="s">/app</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">ENV</span> <span class="nv">ASPNETCORE_ENVIRONMENT</span><span class="o">=</span>Production
</span></span><span class="line"><span class="cl"><span class="k">ENV</span> <span class="nv">ASPNETCORE_URLS</span><span class="o">=</span>http://+:80<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> . ./<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> dotnet restore   src/app/app.csproj --configfile src/app/nuget.config<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> dotnet publish src/app/apptifier.csproj -c Release -o /app/out<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">mcr.microsoft.com/dotnet/sdk:6.0</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">WORKDIR</span><span class="w"> </span><span class="s">/app</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> --from<span class="o">=</span>build-env /app/out .<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">EXPOSE</span><span class="w"> </span><span class="s">80</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">ENTRYPOINT</span> <span class="p">[</span><span class="s2">&#34;dotnet&#34;</span><span class="p">,</span><span class="s2">&#34;app.dll&#34;</span><span class="p">]</span></span></span></code></pre></div></div>
<p>Es sieht normal aus – aber ich halte es für eine weitere <strong>Katastrophe</strong>. Erklärung:</p>
<ul>
<li>COPY-Anweisungen sollten in kleinere Teile aufgeteilt werden. In diesem Fall habe ich geändert, dass jedes Modul der Anwendung separat kopiert wird.</li>
<li>Zwei RUN-Anweisungen können zusammengeführt werden.</li>
<li>Der apt-Befehl löscht keine gecachten Pakete. In dieser Situation nicht kritisch, aber wir haben es trotzdem bereinigt.</li>
</ul>
<p>Das Reduzieren der Zeilen im Dockerfile funktioniert für Kaniko nicht gut. Mehr Zeilen zu schreiben ist in Ordnung. Hier ist die überarbeitete Version:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-dockerfile">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="In Zwischenablage kopieren"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">mcr.microsoft.com/dotnet/sdk:6.0</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="s">build-env</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">WORKDIR</span><span class="w"> </span><span class="s">/app</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">ENV</span> <span class="nv">ASPNETCORE_ENVIRONMENT</span><span class="o">=</span>Production
</span></span><span class="line"><span class="cl"><span class="k">ENV</span> <span class="nv">ASPNETCORE_URLS</span><span class="o">=</span>http://+:80<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> src/Module1 ./src/Module1<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> src/Module2 ./src/Module2<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> src/app ./src/app<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> tests/app.Test ./tests/Iapp.Test.Test<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> tests/app.integrity.Test ./tests/app.integrity.Test<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> app.sln ./<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> dotnet publish --configfile src/app/nuget.config src/app/app.csproj -c Release -o /app/out --nologo -verbosity:quiet<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">mcr.microsoft.com/dotnet/sdk:6.0</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">WORKDIR</span><span class="w"> </span><span class="s">/app</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> --from<span class="o">=</span>build-env /app/out .<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">EXPOSE</span><span class="w"> </span><span class="s">80</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">ENTRYPOINT</span> <span class="p">[</span><span class="s2">&#34;dotnet&#34;</span><span class="p">,</span><span class="s2">&#34;app.dll&#34;</span><span class="p">]</span></span></span></code></pre></div></div>
<p>Dieselbe Methode habe ich auch für andere Projekte angewandt.</p>
<h4 id="snapshot">Snapshot</h4>
<p>Zu Beginn dieses Beitrags habe ich erwähnt, dass Snapshots eine Methode zur Speicherung von Image-Schichten und deren Zuständen sind. Mit <code>--single-snapshot=true</code> wird nur ein einziger Snapshot am Ende des Prozesses erstellt – sehr schnell, aber nicht cache-freundlich. Alternativ kann <code>--snapshotMode</code> genutzt werden, um die richtige Snapshot-Methode sicherzustellen. Die folgende Tabelle stammt direkt aus der offiziellen Dokumentation:</p>
<table>
	<thead>
			<tr>
					<th>Modus</th>
					<th>Ergebnis</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>full</td>
					<td>Vollständiger Dateiinhalt und Metadaten werden beim Snapshot berücksichtigt. Die langsamste Option, aber am robustesten.</td>
			</tr>
			<tr>
					<td>redo</td>
					<td>Datei-mtime, Größe, Modus, Besitzer-UID und GID werden beim Snapshot berücksichtigt. Bis zu 50 % schneller als „full&quot;, besonders bei Projekten mit vielen Dateien.</td>
			</tr>
			<tr>
					<td>time</td>
					<td>Nur die Datei-mtime wird beim Snapshot berücksichtigt.</td>
			</tr>
	</tbody>
</table>
<p>Aus bestimmten Gründen habe ich mich für <code>redo</code> entschieden.</p>
<h2 id="ergebnis">Ergebnis</h2>
<p>Zunächst hat es etwas länger gedauert als der ursprüngliche <code>docker build</code>-Befehl – das ist verständlich, da Kaniko versucht, Caches zu erstellen. Nach einigen Änderungen an Modulen und Dateien sind die Ergebnisse jedoch beeindruckend:</p>
<table>
	<thead>
			<tr>
					<th>Projekttyp</th>
					<th>docker build</th>
					<th>Kaniko ohne Cache</th>
					<th>Kaniko (1. Lauf mit Cache)</th>
					<th>Kaniko (2. Lauf mit Cache)</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Dotnet Core</td>
					<td>3:10</td>
					<td>14:25</td>
					<td>3:40</td>
					<td>0:27</td>
			</tr>
			<tr>
					<td>NodeJS</td>
					<td>11:45</td>
					<td>45:20</td>
					<td>13:10</td>
					<td>2:10</td>
			</tr>
	</tbody>
</table>
<h3 id="kaniko-verwenden">Kaniko verwenden</h3>
<p>Vier Dinge werden benötigt:</p>
<ul>
<li>Eine Dockerfile und Quellcode (Build-Kontext) sowie kleine Anpassungen am Dockerfile</li>
<li>Eine Registry, in die gepusht werden soll</li>
<li>Kaniko selbst</li>
<li><a href="https://github.com/GoogleContainerTools/kaniko#using-kaniko" target="_blank" rel="noopener noreffer ">Kaniko-Dokumentation</a></li>
</ul>
<h4 id="meine-methode">Meine Methode</h4>
<p>Hier ist der vollständige Befehl, den ich verwende:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-shell">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="In Zwischenablage kopieren"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">        /kaniko/executor
</span></span><span class="line"><span class="cl">        --context <span class="s2">&#34;projectDirectory&#34;</span>
</span></span><span class="line"><span class="cl">        --dockerfile <span class="s2">&#34;</span><span class="nv">$projectDirectory</span><span class="s2">/Dockerfile&#34;</span>
</span></span><span class="line"><span class="cl">        --destination <span class="s2">&#34;imageTag:version&#34;</span>
</span></span><span class="line"><span class="cl">        --cache-copy-layers<span class="o">=</span><span class="nb">true</span>
</span></span><span class="line"><span class="cl">        --snapshotMode<span class="o">=</span><span class="nb">time</span>
</span></span><span class="line"><span class="cl">        --use-new-run
</span></span><span class="line"><span class="cl">        --cache<span class="o">=</span><span class="nb">true</span>
</span></span><span class="line"><span class="cl">        --cache-repo<span class="o">=</span><span class="s2">&#34;imageTag:cache&#34;</span></span></span></code></pre></div></div>
<h2 id="fazit">Fazit</h2>
<h3 id="hinweise">Hinweise</h3>
<h4 id="hinweise-zur-produktion">Hinweise zur Produktion</h4>
<p>Dies wurde auf Staging-, Entwicklungs- und anderen Nicht-Produktionsumgebungen angewandt. Es kann bis zu sechs Monate dauern, bis diese Methode und die Tools vollständig übernommen wurden. Wer dies implementieren möchte, sollte sicherstellen, dass alles gründlich getestet wurde und keine komplexen Probleme – insbesondere Sicherheitsprobleme – auftreten, die die Produktion gefährden. Es handelt sich um ein stabiles und ausgereiftes Tool. Nutzung auf eigene Gefahr.</p>
<h4 id="weitere-anmerkungen">Weitere Anmerkungen</h4>
<p>Das Ziel dieses Beitrags ist es, die Leistung und Sicherheit von Docker-Build-Pipelines zu verbessern. Kommentare sind jederzeit willkommen. Bei Unstimmigkeiten oder Problemen bitte melden :)</p>
]]></description></item></channel></rss>