Select Git revision
docker-container.tex 22.32 KiB
% docker-container.tex - Small introduction into docker
% Copyright (C) 2020 Armin Co
%
% This document is free software: you can redistribute it and/or
% modify it either under the terms of the Creative Commons
% Attribution-ShareAlike 3.0 License, or under the terms of the
% GNU General Public License as published by the Free Software
% Foundation, either version 3 of the License, or (at your option)
% any later version.
%
% This document is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this document. If not, see <http://www.gnu.org/licenses/>.
%
% You should have received a copy of the Creative Commons
% Attribution-ShareAlike 3.0 Unported License along with this
% document. If not, see <http://creativecommons.org/licenses/>.
\documentclass[12pt, a4paper]{article}
\usepackage{pgscript}
\setcounter{secnumdepth}{0}
\newcommand{\mylicense}{CC-by-sa (Verision 3.0) oder GNU GPL (Version 3 oder höher)}
\newenvironment{bibcomment}{% found at: https://tex.stackexchange.com/questions/133475/how-to-add-running-text-between-bibliography-items
\item[]\begingroup\par\parshape0\em
}{%
\par\endgroup
}
\begin{document}
\makebox(0,0.005)[tl]{\includegraphics[scale=0.72]{logo-hochschule-bochum-cvh-text-v2.pdf}}\hfill
\makebox(0,0)[tr]{ \includegraphics[scale=0.5]{logo-hochschule-bochum.pdf}}
\par\bigskip\bigskip
\begin{center}
\Large
Einführung
\par\smallskip
\Huge\textbf{Docker \& Container}
\par\medskip
\Large
für Online-Werkzeuge und Entwicklungsumgebungen
\end{center}
Die Begriffe Docker und Container werden häufig verwendet.
Docker ist hierbei wohl der geläufigere Begriff.
Sie werden durchaus auch zusammen oder synonym als Docker-Container verwendet.
Weshalb dies jedoch nicht richtig ist, soll im Folgenden erläutert werden.
Um erklären zu können was Docker ist, muss zunächst ein Verständnis dafür geschaffen werden, was ein Container ist.
\section{Container}
\emph{\glqq A container is a standard unit of software delivery that allows engineering teams to ship software reliably and automatically.\grqq}
\cite{padok: what is a container}
Aus diesem Satz lassen sich mehrere Eigenschaften eines Containers ableiten: \\
Ein Container \dots
\begin{itemize}
\item wird dazu verwendet Software auszuliefern.
\item unterliegt gewissen Standards.
\item erhöht Zuverlässigkeit.
\item ermöglicht Automatisierung.
\end{itemize}
Die einzelnen Punkte lassen sich noch besser verstehen,
wenn man einen Blick auf die technischen Details wirft, die einen Container ausmachen.
Ein Container setzt sich aus drei ihm zugrundeliegenden Technologien zusammen,
den Namespaces\cite{man-pages: namespaces}, Control Groups\cite{kernel: cgroups} und Union-File-Systems\cite{Unionfs: website}. \cite{docker-docs: underlying-technology}
Zusammen ergeben sie einen Container, dessen Format von der OCI\footnote{
\emph{\glqq Die Open-Container-Initiative ist ein Projekt der Linux-Foundation,
um offene Standards für Container zu definieren.\grqq} \cite{wiki: oci}}
definiert wird.
Dies dient dazu, dass verschiedene Werkzeuge und die Container zu einander kompatibel sind.
Namespaces und Control Groups sind beide Feature des Linux-Kernels, wurden jedoch unabhängig voneinander entwickelt und sind nicht nur für Container verwendbar.
Machen diese aber erst möglich.
\begin{description}
\item[Namespaces] \cite{wiki: namespaces}
Namespaces sind ein Feature des Linux-Kernels, welches die Ressourcen des Kernels aufteilt.
\emph{\glqq A namespace wraps a global system resource in an abstraction that
makes it appear to the processes within the namespace that they have
their own isolated instance of the global resource. Changes to the
global resource are visible to other processes that are members of
the namespace, but are invisible to other processes. One use of
namespaces is to implement containers.\grqq} \cite{man-pages: namespaces}
Ein Kernel-Namespace verpackt eine globale Systemressource in eine Abstraktion, diese lässt die Ressource
innerhalb des Namespaces so aussehen, als hätte der Prozess seine eigene isolierte Instanz der globalen Ressource.
Änderungen die an der globalen Ressource vorgenommen werden sind für andere Prozesse sichtbar, die Teil des Namespace sind.
Für andere Prozesse sind diese Änderungen aber nicht sichtbar.
Folgende Arten von Systemressourcen können von Namespaces abstrahiert werden:
\begin{itemize}
\item Cgroup - Cgroup root directory
\item IPC - System V IPC, POSIX message queues
\item Network \cite{man-pages: network-namespaces}- Network devices, stacks, ports, etc.
\item Mount\footnote{\url{https://man7.org/linux/man-pages/man7/mount_namespaces.7.html}} - Mount points
\item PID - Process Identifiers ()
\item Time - Boot and monotonic clocks
\item User - User and group IDs
\item UTS - Hostname and NIS, domain name
\end{itemize}
Der PID-Namespace bietet Isolation für die Allokation von PIDs, Listen von Prozessen und deren Details.
Ein neuer Namespace ist isoliert von anderen \emph{Geschwistern}, jedoch nicht von seinem \glqq Eltern\grqq-Namespace
worin alle Prozesse des \emph{Kind}-namespaces sichtbar sind, jedoch mit anderen PIDs.
Mit dem Network-Namespace werden sowohl physische als auch virtuelle Netwerkgeräte, so wie iptables und firewall routing tables
isoliert. Die Network-Namespaces können untereindander mit virtuellen Ethernet Geräten (veth) verbunden werden.
Namespaces können mit dem Programm \emph{unshare},
dem gleichnamigen syscall oder dem syscall \emph{clone} und der entsprechenden Flag erzeugt werden.
\item[Cgroups]
Control Groups (cgroups) sind ebenfalls ein Kernel-Feature. Es dient dazu die Nutzung von Ressourcen (CPU, RAM, disk I/O, etc.) für eine Gruppe von Prozessen zu limitieren und isolieren. \cite{wiki: cgroups}
Darüber hinaus kännen mit Cgroups Prozesse hierarchisch organisiert werden, sodass eine Baumstruktur an Prozessen ensteht, wobei jeder Prozess nur einer Cgroup angehört. Somit gehören auch alle Threads eines Prozesses zu einer Cgroup. \cite{kernel: cgroups}
Sie erlauben außerdem das Messen der benutzten Ressourcen.
Eine Cgroup wird von einem Verzeichnis repräsentiert, das die Dateien enthält, die die Cgroup beschreiben.
Dazu gehört eine Lists von Tasks (PID), die zu der Cgroup gehören. Sowie Cgroup.procs eine Liste von Thread-Gruppen-IDs.
Weiterführender Artikel auf LWN.net zu \glqq Process containers\grqq \cite{lwn.net: process containers}
\item[Union-File-Systems] \label{ufs}
Ein Union-File-System stellt ein Dateisystem dar, indem es Verzeichnisse und Dateien in Branches gruppiert.
Diese Branches können übereinander gestapelt werden, sodass mehrere Layer entstehen.
Auf diese Art werden Images für Container erstellt.
Teilen Images für Container die selbe Basis (die selben Layer), müssen diese nicht neu angelegt werden,
sondern es reicht diese als Referenz anzugeben.
Als unterster Layer für einen Container dient ein bootfs, das einem typischen Linux boot-Dateisystem ähnelt.
Der darauf folgende Layer ähnelt einem root-Dateisystem.
Für den Einsatz mit Containern wird dabei das Copy-on-Write Prinzip verwendet.
Das bedeutet, dass beim Start eines Containers keine Dateien geladen oder kopiert werden müssen.
Soll eine Datei geändert werden, dann wird diese Datei kopiert und die Änderung in der Kopie vorgenommen.
Hierzu werden leere read-write Layer für jeden Layer des Images angelegt, die Änderungen finden in diesen Layern statt.
Die Kopie verdeckt anschlißend die Referenz in dem Image. \cite{gitbooks: docker}
Bereits exitierende Images, können daher wiederverwendet werden, da sie bei Verwendung nicht verändert werden.
Die Images für Container können sehr klein sein, da sich mehrere Images das selbe Base-Image (z.B. mit einem Debian Betriebsystem) teilen. Nur die Änderungen und Ergenzungen für das neuen Image benötigen zusätzlichen Speicher.\cite{wiki: unionfs}
\end{description}
Werden alle drei Technologien angewendet, erhält man das, was man einen Container nennt.
Kurz zusammgengefasst kann man einen Container als einen Prozess oder eine Gruppe von Prozessen bezeichnen,
die in einer genau definierten Umgebung (Namespaces) ausgeführt werden.
Die Umgebung wird durch das zugrundeligende Image definiert und auf die Art und Weise wie der Container gestartet wird.
\section{Docker}
Docker ist eine OpenSource Plattform, deren Technik auf GitHub\footnote{\url{https://github.com/docker}} unter der Apache-2.0 Lizenz veröffentlicht wird.
Die Technik ermöglicht es Programme zu entwicklen, verbreiten und auszuführen, und gleichzeitig von der Infrastruktur zu separieren.
Das ermöglicht das schnelle Ausliefern von Software.
\cite{docker-docks: overview}
Die Basis für die von Docker eingesetze Technik bieten die Eingangs beschriebenen Container.
Diese bieten Docker im Vergleich zu virtuellen Maschinen die Vorteile, dass kein Hypervisor benötigt wird, keine Virtualisierung stattfindet
sonder direkt der Kernel verwendet wird, es können sogar Container innerhalb von virtuellen Maschinen ausgeführt werden.
Dadurch sind Container deutlich leichtgewichtiger, als virtuelle Maschienen, sodass viele Container auf einem Host parallel ausgeführt werden können.
Ein Container kann, als Distributionseinheit für Tests und Auslieferung dienen.
Hierbei spielt es keine Rolle, ob das Ziel der lokale Desktop zur Entwicklung oder ein Rechenzentrum, um mit der Anwendung
in den produktiv Betrieb zu gehen, ist.
\subsection{Docker Engine}
Auf der untersten Ebene arbeitet der Docker-Daemon (\emph{dockerd}), dies ist ein durchlaufender Prozess (Daemon),
welcher das Erzeugen und Ausführen von Containern übernimmmt.
Dazu gehören des Weiteren das Managen von Images, Netzwerken und Volumen.
Der Docker-Daemon ist außerdem in der Lage mit anderen dockerd Instanzen zu Kommunizieren, um verteilte Docker-Dienste zu managen.
Über eine Schnittstelle, die \emph{dockerd} zur Verfügung stellt, kann ein Docker-Client (z.B. der Befehlt \emph{docker}) mit dem Daemon interagieren.
Die Interaktion kann dabei, sowohl lokal als auch über das Netzwerk stattfinden (UNIX-Sockets oder REST API).
Docker bietet darüber hinaus auch eine Registry, den Docker-Hub, für Images an.
Die ist eine öffentlich zugängliche Plattform, auf der für jeden zugänglich Images bereitgestellt werden können.
Docker ist standardmäßig so eingestellt, dass dort nach Images für Container gesucht wird.
Es gibt aber auch Möglichkeiten eine eigene Registry zu betreiben, falls diese nicht öffentlich sein soll.
Mit dem Befehl \emph{docker pull} werden Images von der Registry abgerufen und heruntergeladen.
Bei der Verwendung von Docker interagiert man hauptsächlich mit zwei Arten von Objekten, den bereits erwähnten Images und Containern.
Ein Image ist ein schreibgeschütztes Template, das die Instruktionen zum Erstellen eines Containers enthält.
Da die Images auf Union-File-Systems basieren, können Images auch aufeinander aufbauen.
Zum Beispiel ist es möglich, als Grundlage ein Ubuntu-Image zu benutzen und darauf einen Apache-Webserver, eine eigene Anwendung und
Konfigurationsdetails, die für die Anwendung notwendig sind, zu installieren.
Es ist sowohl möglich, eigene Images zu definieren und zu verwenden, als auch den bereits erwähnten Docker-Hub zu nutzen.
Um ein eigenes Image zu erstellen, muss ein \emph{Dockerfile} angelegt werden, in dem in einer einfachen Syntax die Schritte
definiert werden, die für das Erzeugen des Images notwendig sind.
\begin{description}
\item[FROM] Hiermit lässt sich ein Image angeben, auf dem aufgebaut werden soll.
\item[COPY] Mit diesem Befehl können Verzeichnisse und Dateien in das zu erzeugende Image kopiert werden.
\item[RUN] Ausführen von Befehlen beim Erstellen des Images, z.B. aufrufen des Paketmanagers zum Installieren von Paketen
oder ausführen von Programmen und etwas innerhalb des Images zu konfigurieren, erzeugen oder kompilieren.
\item[EXPOSE] Öffnet den angegebenen Port, sodass ein Service der innerhalb des Containers auf diesem Port läuft
erreicht werden kann.
\item[CMD] Mit dieser Anweisung kann ein Befehl definiert werden, der wenn das Image als Container ausgeführt wird gestartet werden soll.
\end{description}
Mit jedem dieser Befehle im Dockerfile wird ein neuer Layer des Union-File-System erzeugt.
Der Befehl \emph{docker build} gibt die Anweisung an den Docker-Daemon das Image in dem angegebenen Dockerfile zu bauen.
Nimmt man eine Änderung an dem Dockerfile vor weist \emph{docker} \emph{dockerd} an das Image erneut zu bauen, dann werden nur die Layer des Images neu
gebaut, die sich geändert haben.
Die zweite wichtige Art von Objekt bei der Verwendung von Docker sind die eigentlichen Container.
Ein Container ist eine ausführbare Instanz eines Images.
Da ein Image nur ein Template ist, können mit ein und dem selben Image mehrere Container erstellt werden.
Da ein Image schreibgeschützt ist, sind Änderungen im Container die während er ausgeführt wird nicht permanent.
Wird ein Containern gestoppt, dann sind alle Änderungen aufgehoben.
Sollen die Änderungen nicht verloren gehen, gibt es zwei Möglichkeiten. Zum einen ist es möglich, den Zustand eines laufenden Containers
in einem neuen Image zu speichern, dieses Image ist dann wiederum schreibgeschützt und es können neue Container auf Basis dieses Images
erzeugt werden.
Zum anderen ist es möglich mit Docker Volumes zu erzeugen. Ein Volume kann innerhalb des Container gemountet werden und die Änderungen die dort
vorgenommen werden bleiben in dem Volume erhalten. Über den Docker Client ist es auch möglich den Daemon anzuweiesen, dass ein Volume in
mehreren Containern gemountet werden soll, sodass sich mehrere Container das Volume teilen und es für alle sichtbar ist.
Hierbei kann definiert werden ob das Volume nur lesbar gemountet werden soll oder ob auch Schreibrechte genehmigt werden.
Alternativ ist es möglich ein Verzeichnis des Hostsystems in den Container zu mounten, sodass dort z.B. Konfigurationen abgelegt und geändert werden
können, sodass ein Zugriff sowohl vom Container als auch vom Host-System aus möglich ist.
Standardmäßig ist ein Container gut isoliert, zum Beispiel ist das Dateisystem des Hosts nicht eingebunden und zugänglich.
Auch der Netzwerkzugriff ist abgegränzt zum Hostsystem. Es ist jedoch ähnlich wie beim Dateisystem möglich, die Container über
separat mit dem Docker-Daemon angelegte Netzwerk-Namespaces miteinander zu verbinden, zum Beispiel einen Container in dem eine Anwendung ausgeführt mit
einem weiteren Datenbank Container. Für andere Container die ausgeführt werden aber nicht diesem Netzwerk zugewiesen wurden, ist dann zum Beispiel
der Datenbank Container nicht erreichbar.
\smallskip
\textbf{Docker-Client}
Für die Interaktion mit Containern steht ein Auswahl von Befehlen zur Verfügung. Kleine Übersicht:
\begin{description}
\item[docker build] Build a docker image, specified by a Dockerfile
\item[docker pull] Pull the given image from the DockerHub
\item[docker run] Start a container based on the specified image
\item[docker ps] Show the currently running containers
\item[docker exec] Run a command inside the context/namespace of the container (see also \emph{nsenter})
\end{description}
\subsection{chroot \& mount Namespace}
Um eine Shell innerhalb eines Containers zu öffnen
kann man zum Beispiel den Befehl \emph{docker exec -it container\_name} verwenden.
Schaut man sich dann innerhalb des Containers im Dateisystem um stellt man
fest, dass es sich dabei nicht um das Dateisystem des Hosts handelt.
Jedoch gibt es keine Möglichkeit wieder auf das Dateisystem des Hosts
zuzugreifen, im Gegensatz zu einer reinen chroot Umgebung,
denn der Container ist isoliert.
Dies wird über die Verwendung des Mount-Namepspaces erreicht.
Als erstes wird ein neuer Prozess geklont, welcher als init Prozess verwendet wird.
Darin wird das Dateisystem vorbereitet, zum Beispiel indem mit pivot\_root()\footnote{\url{https://man7.org/linux/man-pages/man2/pivot_root.2.html}} und chroot()\footnote{\url{https://www.man7.org/linux/man-pages/man1/chroot.1.html}}
das Root-Verzeichnis gewechselt wird.
Anschließend können in dem Prozess alle Einhängepunkte ausgehangen werden, die nicht mehr benötigt werden, sodass
nur noch das Root-Verzeichnis des Containers übrig ist.
Die Änderungen haben dabei nur auf den Namespace effekt, in dem der Init-Process ausgeführt wird.
Zum Abschluss wird unshare()\footnote{\url{https://man7.org/linux/man-pages/man2/unshare.2.html}}
mit der Flag für den Mount-Namespace aufgerufen, dadurch wird der Prozess in einen neuen Namespace gebracht.
Dies führt dazu, dass die vorherigen Änderungen nicht wieder rückgängig zu machen sind und der Container nun isoliert ist.
Jetzt kann der eigentliche Prozess gestartet werden, für den der Container gedacht ist, welcher
sich jetzt in den fertig konfigurierten Namespaces befindet.
Zur Konfiguration der Namespaces setzt der Docker-Daemon runc \cite{github-runc} ein.
Dies ist eine Go geschriebene Software die für das Ausführen von Containern konform
zur OCI-Spezifikation zuständig ist.
\section{Beispiele}
Ausbrechen aus einer \lstinline{chroot} Umgebung.
\begin{lstlisting}
#include <unistd.h>
#define DIR "xxx"
int main() {
int i;
mkdir(DIR, 0755);
chroot(DIR);
for (i = 0; i < 1024; i++) chdir("..");
chroot(".");
execl("/bin/sh", "-i", NULL);
}
\end{lstlisting}
Sandboxing durch die Verwendung von \lstinline{chroot} und Namepspaces.
\begin{lstlisting}[style=terminal]
unshare -muinp -f --mount-proc=./wheezy_chroot/proc \
chroot ./wheezy_chroot /bin/bash
\end{lstlisting}
Ein Beispiel in C das veranschaulicht, wie ein Prozess (hier die Funktion child\_fn)
innerhalb eines neuen Netzwerk Namespaces ausgeführt werden kann.
\lstinputlisting{namespace_isolation.c}
\newpage
\begin{thebibliography}{}
\begin{bibcomment}
Die Online Quellen beziehen sich auf den Stand im Mai 2020.
\end{bibcomment}
\bibitem{padok: what is a container}
From Docker to OCI: What is a container?
\emph{Padok - Busser Arthur} \\
\url{https://www.padok.fr/en/blog/container-docker-oci}
\bibitem{wiki: oci}
Wikipedia:
\emph{Open Container Initiative (OCI)}\\
\url{https://en.wikipedia.org/wiki/Open_Container_Initiative}
\bibitem{docker-docs: underlying-technology}
Docker Docs:
\emph{The underlying technology}\\
\url{https://docs.docker.com/get-started/overview/#the-underlying-technology}
\bibitem{man-pages: namespaces}
Linux Programmers's Manual:
\emph{namespaces - overview of Linux namespaces}\\
\url{http://man7.org/linux/man-pages/man7/namespaces.7.html}
\bibitem{man-pages: network-namespaces}
Linux manual page:
\emph{network\_namesapces(7)}\\
\url{https://www.man7.org/linux/man-pages/man7/network_namespaces.7.html}
\bibitem{kernel: cgroups}
Kernel:
\emph{Control Group v2}\\
\url{https://www.kernel.org/doc/Documentation/cgroup-v2.txt}
\bibitem{Unionfs: website}
Unionfs:
\emph{A Stackable Unification File System}\\
\url{https://unionfs.filesystems.org/}
\bibitem{wiki: namespaces}
Wikipedia:
\emph{Linux namespaces}\\
\url{https://en.wikipedia.org/wiki/Linux_namespaces}
\bibitem{wiki: cgroups}
Wikipedia:
\emph{cgroups}\\
\url{https://en.wikipedia.org/wiki/Cgroups}
\bibitem{lwn.net: process containers}
LWN.net:
\emph{Process containers}\\
\url{https://lwn.net/Articles/236038/}
\bibitem{gitbooks: docker}
washraf:
\emph{Union File Systems}\\
\url{https://washraf.gitbooks.io/the-docker-ecosystem/content/Chapter%201/Section%203/union_file_system.html}
\bibitem{wiki: unionfs}
Wikipedia:
\emph{UnionFS}\\
\url{https://en.wikipedia.org/wiki/UnionFS}
\bibitem{docker-docks: overview}
docker docs:
\emph{The Docker platform}\\
\url{https://docs.docker.com/get-started/overview}
\bibitem{github-runc}
GitHub:
\emph{opencontainers/runc}\\
\url{https://github.com/opencontainers/runc}
\end{thebibliography}
Weitere Quellen
\begin{itemize}
\item Introduction into docker images \\
\url{https://jfrog.com/knowledge-base/a-beginners-guide-to-understanding-and-building-docker-images/}
\item Understanding containerization by recreating docker\\
\url{https://itnext.io/linux-container-from-scratch-339c3ba0411d}
\item Playing with namespaces \\
\url{https://pulsesecurity.co.nz/articles/docker-rootkits}
\item Containers namespacs cgroups and some filesystem magic\\
\url{https://www.slideshare.net/jpetazzo/anatomy-of-a-container-namespaces-cgroups-some-filesystem-magic-linuxcon}
\item Deep dive into docker storage drivers \\
\url{http://jpetazzo.github.io/assets/2015-07-01-deep-dive-into-docker-storage-drivers.html#83}
\item Isolation with namespaces \\
\url{https://www.toptal.com/linux/separation-anxiety-isolating-your-system-with-linux-namespaces}
\item Linus Torvalds - chroot() \& pivot\_root() \\
\url{https://yarchive.net/comp/linux/pivot_root.html}
\item Jürgen Brunk, Matthias Albert und Nils Magnus - Docker: die Linux-Basics unter der Container-Haube\\
\url{https://entwickler.de/online/besuch-im-docker-maschinenraum-126456.html}
\end{itemize}
\vfill
\begingroup
\small
\setlength{\leftskip}{3cm}
Copyright \copyright\ 2020\quad Armin Co\\
Lizenz: \mylicense
Sie können diesen Text einschließlich \LaTeX-Quelltext
herunterladen unter:\\
\url{https://gitlab.cvh-server.de/aco/ow}
\endgroup
\end{document}}