
Heute teilen wir Ihnen eine Übersetzung eines Artikels eines IBM DevOps-Ingenieurs zur Automatisierung der Erstellung schnell zusammengestellter und einfach zu debuggender Docker-Images für Python-Projekte mithilfe eines Makefiles mit. Dieses Projekt erleichtert nicht nur das Debuggen in Docker, sondern sorgt auch für die Qualität Ihres Projektcodes. Details wie immer unter dem Schnitt.
Jedes Projekt - ob Sie an einer Webanwendung mit Data Science oder künstlicher Intelligenz arbeiten - kann von gut abgestimmten CI / CDs, Docker-Images, die gleichzeitig während der Entwicklung debuggt und für eine Produktionsumgebung optimiert werden, oder Qualitätssicherungstools profitieren Code wie CodeClimate oder SonarCloud . All diese Dinge werden in diesem Artikel behandelt und gezeigt, wie sie einem Python-Projekt hinzugefügt werden.
Debuggbare Container für die Entwicklung
Einige Leute mögen Docker nicht, weil es schwierig sein kann, Container zu debuggen, oder weil das Erstellen von Images lange dauert. Beginnen wir also mit der Erstellung von Images, die sich ideal für die Entwicklung eignen - schnell zu erstellen und einfach zu debuggen. Um das Debuggen eines Images zu vereinfachen, benötigen Sie ein Basis-Image, das alle Tools enthält, die Sie möglicherweise jemals zum Debuggen benötigen. Dies sind Bash, Vim, Netcat, Wget, Cat, Find, Grep und andere. Python-
Image : 3.8.1-Busterscheint ein perfekter Kandidat für diese Aufgabe zu sein. Es enthält viele sofort einsatzbereite Werkzeuge. Es ist einfach, fehlende Werkzeuge zu installieren. Das Bild ist groß, aber hier spielt es keine Rolle: Es wird nur in der Entwicklung verwendet. Wie Sie wahrscheinlich bemerkt haben, sind die Bilder sehr spezifisch. Das Sperren von Python- und Debian- Versionen ist beabsichtigt: Sie möchten das Risiko eines Bruchs minimieren, der durch neue, möglicherweise inkompatible Versionen von Python oder Debian verursacht wird . Alternativ ist ein alpines Bild möglich , das jedoch einige Probleme verursachen kann: Im Inneren wird musl lib anstelle von glibc verwendetworauf Python sich verlässt. Denken Sie daran, wenn Sie sich für eine alpine entscheiden. In Bezug auf die Geschwindigkeit verwenden wir mehrstufige Builds, um so viele Ebenen wie möglich zwischenzuspeichern. Daher werden Abhängigkeiten und Tools wie gcc sowie alle von der Anwendung benötigten Abhängigkeiten nicht jedes Mal aus der Datei " resources.txt" geladen . Um die Arbeit weiter zu beschleunigen, wird aus dem zuvor erwähnten Python: 3.8.1-Buster ein benutzerdefiniertes Basis-Image erstellt , das alles enthält, was wir benötigen, da wir die zum Herunterladen und Installieren dieser Tools erforderlichen Schritte nicht im endgültigen Image zwischenspeichern können
runner. Aber hör auf zu reden, lass uns einen Blick auf die Docker-Datei werfen:
# dev.Dockerfile
FROM python:3.8.1-buster AS builder
RUN apt-get update && apt-get install -y --no-install-recommends --yes python3-venv gcc libpython3-dev && \
python3 -m venv /venv && \
/venv/bin/pip install --upgrade pip
FROM builder AS builder-venv
COPY requirements.txt /requirements.txt
RUN /venv/bin/pip install -r /requirements.txt
FROM builder-venv AS tester
COPY . /app
WORKDIR /app
RUN /venv/bin/pytest
FROM martinheinz/python-3.8.1-buster-tools:latest AS runner
COPY --from=tester /venv /venv
COPY --from=tester /app /app
WORKDIR /app
ENTRYPOINT ["/venv/bin/python3", "-m", "blueprint"]
USER 1001
LABEL name={NAME}
LABEL version={VERSION}
Oben sehen Sie, dass der Code
runner3 Zwischenbilder durchläuft, bevor das endgültige Bild erstellt wird. Der erste ist Baumeister . Es lädt alle zum Erstellen der Anwendung erforderlichen Bibliotheken herunter, einschließlich gcc und der virtuellen Python-Umgebung. Nach der Installation wird eine reale virtuelle Umgebung erstellt und von den folgenden Abbildungen verwendet. Als nächstes kommt builder-vv , das die Liste der Abhängigkeiten (require.txt) in das Image kopiert und diese dann installiert. Dieses Zwischenabbild ist für das Caching erforderlich: Sie möchten Bibliotheken nur installieren, wenn sich die Datei "require.txt" ändert. Andernfalls verwenden wir nur den Cache. Lassen Sie uns die Anwendung testen, bevor Sie das endgültige Image erstellen.
Bevor wir unser endgültiges Image erstellen, führen wir zunächst die Tests unserer Anwendung aus. Kopieren Sie den Quellcode und führen Sie die Tests aus. Wenn die Tests bestanden sind, gehen Sie zum Läuferbild . Hierbei wird ein benutzerdefiniertes Image mit einigen zusätzlichen Tools verwendet, die im regulären Debian-Image nicht enthalten sind: vim und netcat. Dieses Bild befindet sich auf Docker Hub , und Sie können sich auch eine sehr einfache Docker-Datei in base.Dockerfile ansehen . Was wir also in diesem endgültigen Image tun: Zuerst kopieren wir die virtuelle Umgebung, in der alle Abhängigkeiten gespeichert sind, die wir vom Tester- Image installiert habenKopieren Sie dann die getestete Anwendung. Nachdem sich alle Quellen im Image befinden, wechseln Sie in das Verzeichnis, in dem sich die Anwendung befindet, und installieren Sie ENTRYPOINT, damit die Anwendung beim Starten des Images gestartet wird. Aus Sicherheitsgründen ist USER auf 1001 festgelegt : Best Practice empfiehlt, Container niemals als Root auszuführen. Die letzten 2 Zeilen legen die Bildbeschriftungen fest. Sie werden beim Bauen durch das Ziel ersetzt
make, was wir etwas später sehen werden.
Optimierte Container für die Produktionsumgebung
Wenn es um Looks in Produktionsqualität geht, sollten Sie sicherstellen, dass sie klein, sicher und schnell sind. Mein persönlicher Favorit in diesem Sinne ist das Python- Bild aus dem Distroless- Projekt . Aber was ist "Distroless"? Sagen wir es so: In einer idealen Welt würde jeder sein eigenes Bild mit FROM-Scratch als Basis erstellen (d. H. Ein leeres Bild). Aber das ist nicht das, was die meisten von uns wollen, da es die statische Verknüpfung von Binärdateien usw. erfordert. Hier kommt Distroless ins Spiel : Es ist ein FROM-Scratch für alle. Und jetzt werde ich dir wirklich sagen, was "Distroless" ist. Dies ist ein von Google erstelltes SetBilder, die das von der Anwendung geforderte absolute Minimum enthalten. Dies bedeutet, dass es keine Wrapper, Paketmanager oder andere Tools gibt, die das Bild aufblähen und Signalrauschen für Sicherheitsscanner (wie CVE ) erzeugen , was es schwierig macht, die Konformität festzustellen. Nachdem wir wissen, womit wir es zu tun haben, werfen wir einen Blick auf die Produktions-Docker-Datei. In der Tat müssen Sie den Code nicht viel ändern, Sie müssen nur 2 Zeilen ändern:
# prod.Dockerfile
# 1. Line - Change builder image
FROM debian:buster-slim AS builder
# ...
# 17. Line - Switch to Distroless image
FROM gcr.io/distroless/python3-debian10 AS runner
# ... Rest of the Dockefile
Alles, was wir ändern mussten, waren unsere Basis-Images, um die App zu erstellen und auszuführen! Aber der Unterschied ist ziemlich groß - das Entwicklungsimage wog 1,03 GB, und dieses war nur 103 MB groß, was ein großer Unterschied ist! Und ich kann dich schon hören: "Alpina kann noch weniger wiegen!" ... Ja, aber die Größe spielt keine Rolle. Sie werden die Größe des Bildes nur beim Laden / Entladen bemerken, es kommt nicht sehr oft vor. Wenn das Bild funktioniert, spielt die Größe keine Rolle. Was wichtiger als die Größe ist, ist die Sicherheit, und in dieser Hinsicht ist Distroless Alpine definitiv überlegen: Alpine verfügt über viele zusätzliche Pakete, um die Angriffsfläche zu vergrößern. Das Letzte, was es zu erwähnen gilt, wenn es um Distroless geht, ist das Debuggen von Bildern . Bedenkt, dassDistroless enthält keine Shell (nicht einmal "sh"), das Debuggen und Nachforschen wird ziemlich schwierig. Dafür gibt es "Debug" -Versionen aller Distroless- Images . Auf diese Weise ist es bei Problemen möglich, Ihr Arbeitsimage mithilfe eines Tags zu erstellen
debugund zusammen mit Ihrem üblichen Image bereitzustellen, die im Debug-Image erforderlichen Schritte auszuführen und beispielsweise einen Stream-Dump durchzuführen. Es ist möglich, die Debug-Version des Python3-Images wie folgt zu verwenden:
docker run --entrypoint=sh -ti gcr.io/distroless/python3-debian10:debug
Ein Team für alles
Wenn alle Dockerfiles fertig sind, können Sie diesen ganzen Albtraum mit einem Makefile automatisieren! Als erstes möchten wir die Anwendung mit Docker erstellen. Um ein Entwicklungsimage zu erstellen, schreiben wir daher
make build-devden folgenden Code:
# The binary to build (just the basename).
MODULE := blueprint
# Where to push the docker image.
REGISTRY ?= docker.pkg.github.com/martinheinz/python-project-blueprint
IMAGE := $(REGISTRY)/$(MODULE)
# This version-strategy uses git tags to set the version string
TAG := $(shell git describe --tags --always --dirty)
build-dev:
@echo "\n${BLUE}Building Development image with labels:\n"
@echo "name: $(MODULE)"
@echo "version: $(TAG)${NC}\n"
@sed \
-e 's|{NAME}|$(MODULE)|g' \
-e 's|{VERSION}|$(TAG)|g' \
dev.Dockerfile | docker build -t $(IMAGE):$(TAG) -f- .
Dieses Ziel erstellt ein Bild, indem zuerst die Beschriftungen unten durch den
dev.DockerfileNamen des Bildes und das beim Start erstellte Tag ersetzt git describeund dann gestartet werden docker build. Erstellen Sie als Nächstes für die Produktionsumgebung Folgendes make build-prod VERSION=1.0.0:
build-prod:
@echo "\n${BLUE}Building Production image with labels:\n"
@echo "name: $(MODULE)"
@echo "version: $(VERSION)${NC}\n"
@sed \
-e 's|{NAME}|$(MODULE)|g' \
-e 's|{VERSION}|$(VERSION)|g' \
prod.Dockerfile | docker build -t $(IMAGE):$(VERSION) -f- .
Dieses Ziel ist dem vorherigen sehr ähnlich, aber anstatt das Git-Tag als Version zu verwenden, wird die als Argument übergebene Version verwendet. Im obigen Beispiel ist es 1.0.0. Wenn alles in Docker ausgeführt wird , müssen Sie irgendwann auch alles in Docker debuggen . Dafür gibt es ein Ziel:
# Example: make shell CMD="-c 'date > datefile'"
shell: build-dev
@echo "\n${BLUE}Launching a shell in the containerized build environment...${NC}\n"
@docker run \
-ti \
--rm \
--entrypoint /bin/bash \
-u $$(id -u):$$(id -g) \
$(IMAGE):$(TAG) \
$(CMD)
Im obigen Code können Sie sehen, dass der Einstiegspunkt von bash überschrieben wird und der Containerbefehl von einem Argument in der CMD überschrieben wird. So können wir entweder einfach in den Container gehen und herumwühlen oder eine Art Befehl ausführen, wie im obigen Beispiel. Sobald wir die Programmierung abgeschlossen und das Image in die Docker-Registrierung verschoben haben, können wir es verwenden
make push VERSION=0.0.2. Mal sehen, was dieses Ziel bewirkt:
REGISTRY ?= docker.pkg.github.com/martinheinz/python-project-blueprint
push: build-prod
@echo "\n${BLUE}Pushing image to GitHub Docker Registry...${NC}\n"
@docker push $(IMAGE):$(VERSION)
Es startet zuerst das zuvor diskutierte Ziel
build-prodund dann einfach docker push. Dies setzt voraus, dass Sie in der Docker-Registrierung angemeldet sind. Daher muss dieses Ziel vor der Ausführung ausgeführt werden docker login. Das letzte Ziel ist es, Docker-Artefakte zu bereinigen. Hierbei wird das Namensschild verwendet, das in den Docker-Image-Build-Dateien ersetzt wurde, um Artefakte zu filtern und zu finden, die entfernt werden müssen:
docker-clean:
@docker system prune -f --filter "label=name=$(MODULE)"
Der gesamte Makefile-Code befindet sich im Repository .
CI / CD mit GitHub-Aktionen
Das Projekt verwendet make, Github Actions und die Github-Paketregistrierung, um Pipelines (Aufgaben) zu erstellen und unsere Images zum Konfigurieren von CI / CD zu speichern. Aber was ist es?
- GitHub-Aktionen sind Aufgaben / Pipelines, mit denen Entwicklungsworkflows automatisiert werden können. Sie können sie verwenden, um separate Aufgaben zu erstellen und sie dann zu benutzerdefinierten Workflows zu kombinieren, die beispielsweise jedes Mal ausgeführt werden, wenn Daten an das Repository gesendet werden oder wenn ein Release erstellt wird.
- Die Github Package Registry ist ein Paket- Hosting-Service, der vollständig in GitHub integriert ist. Hier können Sie verschiedene Arten von Paketen speichern, z. B. Ruby Gems oder npm- Pakete . Das Projekt verwendet es zum Speichern von Docker-Bildern. Weitere Informationen zur Github-Paketregistrierung finden Sie hier .
Um GitHub-Aktionen verwenden zu können , werden im Projekt Workflows basierend auf den ausgewählten Triggern erstellt (Beispiel für einen Trigger, der an das Repository gesendet wird). Diese Workflows sind YAML- Dateien im Verzeichnis
.github/workflows:
.github
└── workflows
├── build-test.yml
└── push.yml
Die Datei build-test.yml enthält zwei Jobs, die jedes Mal ausgeführt werden, wenn der Code an das Repository gesendet wird. Diese werden nachfolgend angezeigt:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Run Makefile build for Development
run: make build-dev
Die erste Aufgabe, Build genannt, überprüft, ob die Anwendung durch Ausführen des Ziels erstellt werden kann
make build-dev. Vor dem Start wird jedoch das Repository überprüft, indem checkoutes in GitHub veröffentlicht ausgeführt wird.
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/setup-python@v1
with:
python-version: '3.8'
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run Makefile test
run: make test
- name: Install Linters
run: |
pip install pylint
pip install flake8
pip install bandit
- name: Run Linters
run: make lint
Die zweite Aufgabe ist etwas schwieriger. Es führt Tests neben der Anwendung sowie 3 Code-Qualitätskontroll-Linters (Code-Qualitätskontroller) aus. Wie in der vorherigen Aufgabe wird eine Aktion verwendet, um den Quellcode abzurufen
checkout@v1. Danach wird eine weitere veröffentlichte Aktion namens aufgerufen setup-python@v1, mit der die Python-Umgebung eingerichtet wird (mehr dazu hier ). Jetzt, da wir eine Python-Umgebung haben, benötigen wir Anwendungsabhängigkeiten, von requirements.txtdenen mit pip installiert wird. An diesem Punkt starten wir das Ziel make test, es führt die Pytest - Testsuite aus . Wenn die Kit-Tests bestanden sind, fahren Sie mit der Installation der zuvor genannten Linters fort - Pylint , Flake8 und Bandit . Schließlich starten wir das Zielmake lintwas wiederum jeden dieser Linters startet. Es geht nur um den Build / Test-Job, aber was ist mit dem Senden des Codes? Reden wir über sie:
on:
push:
tags:
- '*'
jobs:
push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set env
run: echo ::set-env name=RELEASE_VERSION::$(echo ${GITHUB_REF:10})
- name: Log into Registry
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login docker.pkg.github.com -u ${{ github.actor }} --password-stdin
- name: Push to GitHub Package Registry
run: make push VERSION=${{ env.RELEASE_VERSION }}
Die ersten 4 Zeilen definieren, wann der Job beginnt. Wir weisen darauf hin, dass dieser Job nur ausgelöst werden soll, wenn Tags in das Repository verschoben werden (* gibt ein Namensmuster an, hier sind es alle Tags ). Dies geschieht, damit das Docker-Image nicht jedes Mal in die GitHub-Paketregistrierung verschoben wird, wenn Daten in das Repository übertragen werden, sondern nur, wenn ein Tag hochgeladen wird, das die neue Version unserer Anwendung angibt. Nun zum Hauptteil dieser Aufgabe: Zunächst wird der Quellcode überprüft und der Wert der Umgebungsvariablen RELEASE_VERSION gleich dem hochgeladenen Git-Tag gesetzt. Dies erfolgt mit der integrierten GitHub Actions- Funktion :: setenv (weitere Details hier). Anschließend wird die Aufgabe mit dem im Repository gespeicherten geheimen REGISTRY_TOKEN und der Anmeldung des Benutzers, der den Workflow initiiert hat (github.actor), in die Docker-Registrierung eingegeben. Schließlich wird in der letzten Zeile das Push-Ziel ausgeführt, das das Produktionsimage erstellt und in die Registrierung mit dem zuvor veröffentlichten Git-Tag als Image-Tag schiebt. Überprüfen Sie den gesamten Code in meinen Repository- Dateien .
Codequalitätsprüfung mit CodeClimate
Zu guter Letzt fügen wir die Codequalitätsprüfung mit CodeClimate und SonarCloud hinzu . Sie arbeiten mit der oben gezeigten Testaufgabe zusammen. Fügen Sie einige Codezeilen hinzu:
# test, lint...
- name: Send report to CodeClimate
run: |
export GIT_BRANCH="${GITHUB_REF/refs\/heads\//}"
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
chmod +x ./cc-test-reporter
./cc-test-reporter format-coverage -t coverage.py coverage.xml
./cc-test-reporter upload-coverage -r "${{ secrets.CC_TEST_REPORTER_ID }}"
- name: SonarCloud scanner
uses: sonarsource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
Beginnend mit CodeClimate : Exportieren einer Variablen, die
GIT_BRANCHmithilfe einer Umgebungsvariablen abgerufen wurde GITHUB_REF. Anschließend laden wir das CodeClimate- Testberichtstool herunter und machen es ausführbar. Anschließend wird der Bericht zur Abdeckung der Testsuite formatiert. In der letzten Zeile senden wir es mit der ID des Tools für den Testbericht an CodeClimate , der in den Geheimnissen des Repositorys gespeichert ist. Für SonarCloud müssen Sie eine erstellen sonar-project.properties. Die Werte für diese Datei finden Sie im SonarCloud-Dashboard in der unteren rechten Ecke. Diese Datei sieht folgendermaßen aus:
sonar.organization=martinheinz-github
sonar.projectKey=MartinHeinz_python-project-blueprint
sonar.sources=blueprint
Es ist auch möglich, einfach denjenigen zu verwenden, der die Arbeit für uns erledigt
sonarcloud-github-action. Wir müssen lediglich zwei Token bereitstellen: für GitHub, das im Standard-Repository, und für SonarCloud , das wir von der SonarCloud- Website erhalten haben . Hinweis: Die Schritte zum Abrufen und Installieren aller genannten Token und Geheimnisse sind in der README-Datei des Repositorys beschrieben .
Fazit
Das ist alles! Mit Tools, Konfigurationen und Code können Sie jeden Aspekt Ihres nächsten Python-Projekts anpassen und automatisieren! Wenn Sie weitere Informationen zu den in diesem Artikel gezeigten oder diskutierten Themen benötigen, lesen Sie die Dokumentation und den Code in meinem Repository . Wenn Sie Vorschläge oder Probleme haben, senden Sie bitte eine Anfrage an das Repository oder starten Sie dieses kleine Projekt, wenn Sie es benötigen gefällt.

Mit dem HABR-Gutscheincode erhalten Sie zusätzlich 10% auf den auf dem Banner angegebenen Rabatt.
- Unterrichten des Data Science-Berufs von Grund auf
- Online-Bootcamp für Data Science
- Schulung des Data Analyst-Berufs von Grund auf
- Data Analytics Online Bootcamp
- Python für Webentwicklungskurs
Weitere Kurse