
Hari ini kami membagikan kepada Anda terjemahan artikel oleh insinyur IBM DevOps tentang mengotomatiskan pembuatan gambar Docker yang dirakit dengan cepat dan mudah di-debug untuk proyek Python menggunakan Makefile. Proyek ini tidak hanya membuat proses debug di Docker menjadi lebih mudah, tetapi juga menjaga kualitas kode proyek Anda. Detail, seperti biasa, di bawah potongan.
Setiap proyek - apakah Anda bekerja pada aplikasi web, dengan Ilmu Data atau kecerdasan buatan, dapat memanfaatkan CI / CD yang disetel dengan baik, image Docker yang secara bersamaan di-debug selama pengembangan dan dioptimalkan untuk lingkungan produksi, atau alat jaminan kualitas kode seperti CodeClimate atau SonarCloud . Semua hal ini tercakup dalam artikel ini dan diperlihatkan bagaimana mereka ditambahkan ke proyek Python.
Container yang dapat di-debug untuk pengembangan
Beberapa orang tidak menyukai Docker karena container sulit di-debug, atau karena pembuatan image membutuhkan waktu lama. Jadi, mari kita mulai dengan membuat image yang ideal untuk pengembangan - cepat dibuat dan mudah di-debug. Untuk membuat gambar mudah di-debug, Anda memerlukan gambar dasar yang menyertakan semua alat yang mungkin perlu Anda debug. Ini adalah bash, vim, netcat, wget, cat, find, grep, dan lainnya.
Gambar Python: 3.8.1-bustersepertinya kandidat yang sempurna untuk tugas ini. Ini mencakup banyak alat di luar kotak, mudah untuk menginstal alat yang hilang. Gambarnya besar, tapi tidak masalah di sini: itu hanya akan digunakan dalam pengembangan. Seperti yang mungkin Anda perhatikan, pencitraannya sangat spesifik. Mengunci versi Python dan Debian disengaja: Anda ingin meminimalkan risiko kerusakan yang disebabkan oleh versi baru Python atau Debian yang mungkin tidak kompatibel . Gambar berbasis Alpine dimungkinkan sebagai alternatif , tetapi dapat menyebabkan beberapa masalah: di dalamnya menggunakan musl lib dan bukan glibcyang diandalkan Python. Ingatlah hal ini jika Anda memutuskan untuk memilih Alpine. Dalam hal kecepatan, kami akan menggunakan build multi-tahap untuk menyimpan cache sebanyak mungkin. Jadi dependensi dan alat seperti gcc , serta semua dependensi yang dibutuhkan oleh aplikasi, tidak dimuat dari Requirement.txt setiap saat. Untuk lebih mempercepat, gambar dasar khusus dibuat dari python yang disebutkan sebelumnya : 3.8.1-buster , yang memiliki semua yang kami butuhkan, karena kami tidak dapat menyimpan langkah-langkah yang diperlukan untuk mengunduh dan menginstal alat ini ke dalam gambar akhir
runner. Tapi berhenti bicara, mari kita lihat Dockerfile:
# 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}
Di atas Anda dapat melihat bahwa kode
runnerakan melewati 3 gambar perantara sebelum membuat gambar akhir . Yang pertama adalah pembangun . Itu mengunduh semua pustaka yang diperlukan untuk membangun aplikasi, termasuk gcc dan lingkungan virtual Python. Setelah instalasi, lingkungan virtual nyata dibuat dan digunakan oleh gambar berikut. Berikutnya adalah builder-vv , yang menyalin daftar dependensi (requirement.txt) ke dalam image dan kemudian menginstalnya. Gambar perantara ini diperlukan untuk cache: Anda hanya ingin menginstal pustaka jika requirement.txt berubah, jika tidak, kami hanya menggunakan cache. Mari kita uji aplikasinya sebelum membuat gambar akhir.
Sebelum kita membuat gambar akhir kita, pertama-tama mari kita jalankan tes aplikasi kita. Salin kode sumber dan jalankan pengujian. Saat tes berhasil, buka gambar runner . Ini menggunakan gambar khusus dengan beberapa alat tambahan yang tidak ditemukan di gambar Debian biasa: vim dan netcat. Gambar ini ada di Docker Hub , dan Anda juga dapat melihat Dockerfile yang sangat sederhana di base.Dockerfile . Jadi apa yang kami lakukan pada gambar akhir ini: pertama kami menyalin lingkungan virtual tempat semua dependensi yang kami instal dari gambar penguji disimpan, lalu salin aplikasi yang diuji. Sekarang semua sumber ada dalam gambar, pindah ke direktori tempat aplikasi berada dan instal ENTRYPOINT sehingga saat gambar diluncurkan, aplikasi diluncurkan. Untuk alasan keamanan, USER disetel ke 1001 : praktik terbaik merekomendasikan jangan pernah menjalankan container sebagai root. 2 baris terakhir mengatur label gambar. Mereka akan diganti saat membangun melalui target
make, yang akan kita lihat nanti.
Kontainer yang dioptimalkan untuk lingkungan produksi
Terkait tampilan kelas produksi, Anda ingin memastikan tampilannya kecil, aman, dan cepat. Favorit pribadi saya dalam pengertian ini adalah gambar Python dari proyek Distroless . Tapi apa itu "Distroless"? Mari kita begini: di dunia yang ideal, setiap orang akan membangun gambar mereka sendiri menggunakan FROM scratch sebagai basis mereka (yaitu gambar kosong). Tetapi bukan itu yang diinginkan sebagian besar dari kita, karena memerlukan biner yang menghubungkan secara statis, dll. Di situlah Distroless berperan : ini adalah FROM scratch untuk semua orang. Dan sekarang saya akan benar-benar memberi tahu Anda apa itu "Distroless". Ini adalah set yang dibuat oleh Googlegambar yang berisi jumlah minimum mutlak yang dibutuhkan oleh aplikasi. Ini berarti tidak ada pembungkus, pengelola paket, atau alat lain yang membengkak gambar dan menghasilkan gangguan sinyal untuk pemindai keamanan (seperti CVE ), sehingga sulit untuk menetapkan kepatuhan. Sekarang kita tahu apa yang kita hadapi, mari kita lihat produksi Dockerfile. Faktanya, Anda tidak perlu banyak mengubah kodenya, Anda hanya perlu mengubah 2 baris:
# 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
Yang perlu kami ubah hanyalah gambar dasar kami untuk membuat dan menjalankan aplikasi! Tetapi perbedaannya cukup besar - gambar pengembangan memiliki berat 1,03 GB, dan yang ini hanya 103 MB, yang merupakan perbedaan besar! Dan saya sudah dapat mendengar Anda: "Alpina bahkan bisa lebih ringan!" ... Ya, tapi ukuran tidak terlalu menjadi masalah. Anda hanya akan melihat ukuran gambar saat memuat / membongkar, itu tidak sering terjadi. Saat gambar berfungsi, ukuran tidak menjadi masalah. Yang lebih penting dari ukuran adalah keamanan, dan dalam hal ini Distroless jelas lebih unggul daripada Alpine: Alpine memiliki banyak paket tambahan untuk meningkatkan permukaan serangan. Hal terakhir yang perlu disebutkan ketika berbicara tentang Distroless adalah debugging gambar. Mengingat bahwaDistroless tidak mengandung shell (bahkan tidak "sh"), debugging dan riset menjadi cukup sulit. Untuk ini, ada versi "debug" dari semua gambar Distroless . Dengan begitu, saat terjadi masalah, Anda dapat membuat gambar yang berfungsi menggunakan tag
debugdan menerapkannya bersama dengan gambar biasa, melakukan yang diperlukan dalam gambar debug dan melakukan, misalnya, pembuangan aliran. Dimungkinkan untuk menggunakan versi debug dari gambar python3 seperti ini:
docker run --entrypoint=sh -ti gcr.io/distroless/python3-debian10:debug
Satu tim untuk segalanya
Dengan semua Dockerfile siap, Anda dapat mengotomatiskan seluruh mimpi buruk ini dengan Makefile! Hal pertama yang ingin kami lakukan adalah membangun aplikasi menggunakan Docker. Oleh karena itu, untuk membangun image pengembangan, kita akan menulis
make build-devyang mengeksekusi kode berikut:
# 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- .
Target ini membangun gambar dengan terlebih dahulu mengganti label di bagian bawah dengan
dev.Dockerfilenama gambar dan tag yang dibuat saat peluncuran git describe, lalu diluncurkan docker build. Selanjutnya, buat untuk lingkungan produksi menggunakan 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- .
Target ini sangat mirip dengan yang sebelumnya, tetapi alih-alih menggunakan git tag sebagai versinya, versi yang diteruskan sebagai argumen digunakan, dalam contoh di atas adalah 1.0.0. Saat semuanya berjalan di Docker , pada titik tertentu Anda juga perlu men-debug semua yang ada di Docker . Ada tujuan untuk ini:
# 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)
Pada kode di atas, Anda dapat melihat bahwa titik masuk diganti oleh bash, dan perintah container diganti dengan argumen di CMD. Jadi, kita bisa masuk ke wadah dan mencari-cari, atau menjalankan semacam perintah, seperti pada contoh di atas. Setelah kami selesai memprogram dan mendorong gambar ke registri Docker, kami dapat menggunakan
make push VERSION=0.0.2. Mari kita lihat apa tujuan ini:
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)
Ini pertama kali meluncurkan target yang dibahas sebelumnya
build-prod, dan kemudian secara sederhana docker push. Ini mengasumsikan Anda masuk ke registri Docker, jadi target ini perlu dieksekusi sebelum dijalankan docker login. Tujuan akhirnya adalah untuk membersihkan artefak Docker. Ini menggunakan tag nama, yang telah diganti di dalam file build image Docker, untuk memfilter dan menemukan artefak yang perlu dihapus:
docker-clean:
@docker system prune -f --filter "label=name=$(MODULE)"
Semua kode Makefile ada di repositori .
CI / CD dengan Tindakan GitHub
Proyek ini menggunakan make, Github Actions, dan registri paket Github untuk membangun pipeline (tugas) dan menyimpan image kita untuk mengonfigurasi CI / CD. Tapi apa itu?
- Tindakan GitHub adalah tugas / pipeline yang membantu mengotomatiskan alur kerja pengembangan. Dimungkinkan untuk menggunakannya untuk membuat tugas terpisah lalu menggabungkannya ke dalam alur kerja kustom yang dijalankan, misalnya, setiap kali Anda mengirimkan data ke repositori atau saat membuat rilis.
- Github Package Registry adalah layanan hosting paket yang terintegrasi penuh dengan GitHub. Ini memungkinkan Anda untuk menyimpan berbagai jenis paket, seperti paket permata Ruby atau npm . Proyek menggunakannya untuk menyimpan gambar Docker. Pelajari lebih lanjut tentang registri paket Github bisa di sini .
Untuk menggunakan GitHub Actions , alur kerja dibuat dalam proyek berdasarkan pemicu yang dipilih (contoh pemicu dikirimkan ke repositori). Alur kerja ini adalah file YAML di direktori
.github/workflows:
.github
βββ workflows
βββ build-test.yml
βββ push.yml
File build-test.yml berisi 2 tugas yang dijalankan setiap kali kode dikirim ke repositori, mereka ditampilkan di bawah ini:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Run Makefile build for Development
run: make build-dev
Tugas pertama, yang disebut build, memverifikasi bahwa aplikasi dapat dibangun dengan menjalankan target
make build-dev. Namun, sebelum memulai, ia memeriksa repositori dengan mengeksekusinya checkoutyang dipublikasikan ke GitHub.
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
Tugas kedua sedikit lebih sulit. Ini menjalankan pengujian di samping aplikasi, serta 3 linter kontrol kualitas kode (pengontrol kualitas kode). Seperti pada tugas sebelumnya, tindakan digunakan untuk mendapatkan kode sumber
checkout@v1. Setelah itu, tindakan lain yang diterbitkan yang disebut setup-python@v1menyiapkan lingkungan python diluncurkan (lebih lanjut tentang ini di sini ). Sekarang kita memiliki lingkungan Python, kita membutuhkan dependensi aplikasi requirements.txtyang diinstal menggunakan pip. Pada titik ini make test, mari mulai menjalankan target , ini menjalankan rangkaian pengujian Pytest . Jika tes kit lulus, lanjutkan dengan memasang linter yang disebutkan sebelumnya - pylint , flake8 dan bandit . Akhirnya, kami meluncurkan targetmake lintyang pada gilirannya meluncurkan masing-masing linter ini. Ini semua tentang pekerjaan build / test, tapi bagaimana dengan mengirimkan kode? Mari kita bicarakan tentang dia:
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 }}
4 baris pertama menentukan kapan pekerjaan dimulai. Kami menunjukkan bahwa pekerjaan ini hanya boleh dipicu ketika tag dipindahkan ke repositori (* menunjukkan pola nama, di sini semuanya adalah tag ). Ini dilakukan agar kita tidak memasukkan image Docker ke dalam registry paket GitHub setiap kali kita memasukkan data ke repositori, tetapi hanya jika tag yang menunjukkan versi baru aplikasi kita diupload. Sekarang untuk isi tugas ini - dimulai dengan memeriksa kode sumber dan menyetel nilai variabel lingkungan RELEASE_VERSION sama dengan tag yang diupload git. Ini dilakukan menggunakan fungsi GitHub Actions :: setenv (detail selengkapnya di sini). Tugas kemudian memasuki registri Docker dengan REGISTRY_TOKEN rahasia yang disimpan di repositori dan login pengguna yang memulai alur kerja (github.actor). Terakhir, baris terakhir menjalankan target push, yang membangun image produksi dan memasukkannya ke dalam registry dengan tag git yang diposting sebelumnya sebagai tag image. Lihat semua kode di file repositori saya .
Pemeriksaan kualitas kode dengan CodeClimate
Terakhir, mari tambahkan pemeriksaan kualitas kode menggunakan CodeClimate dan SonarCloud . Mereka akan bekerja sama dengan tugas tes yang ditunjukkan di atas. Tambahkan beberapa baris kode:
# 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 }}
Dimulai dengan CodeClimate : mengekspor variabel yang
GIT_BRANCHdiambil menggunakan variabel lingkungan GITHUB_REF. Kemudian kami mengunduh alat laporan pengujian CodeClimate dan membuatnya dapat dieksekusi. Kemudian kami akan menggunakannya untuk memformat laporan cakupan rangkaian pengujian. Di baris terakhir, kami mengirimkannya ke CodeClimate dengan ID alat untuk laporan pengujian, yang disimpan dalam rahasia repositori. Untuk SonarCloud , Anda perlu membuat file sonar-project.properties. Nilai untuk file ini dapat ditemukan di dashboard SonarCloud di pojok kanan bawah, dan file ini terlihat seperti ini:
sonar.organization=martinheinz-github
sonar.projectKey=MartinHeinz_python-project-blueprint
sonar.sources=blueprint
Selain itu, dimungkinkan untuk hanya menggunakan orang yang melakukan pekerjaan untuk kita
sonarcloud-github-action. Yang harus kita lakukan adalah menyediakan dua token: untuk GitHub, yang ada di repositori default, dan untuk SonarCloud , yang kita dapatkan dari situs web SonarCloud . Catatan: Langkah-langkah untuk mendapatkan dan menginstal semua token dan rahasia yang disebutkan dijelaskan di README repositori .
Kesimpulan
Itu saja! Dengan alat, konfigurasi, dan kode, Anda siap untuk menyesuaikan dan mengotomatiskan setiap aspek dari proyek Python Anda berikutnya! Jika Anda membutuhkan informasi lebih lanjut tentang topik yang ditampilkan atau didiskusikan dalam artikel ini, lihat dokumentasi dan kode di repositori saya , dan jika Anda memiliki saran atau masalah, silakan kirim permintaan ke repositori, atau cukup bintangi proyek kecil ini jika Anda membutuhkannya Suka.

Dan dengan kode promo HABR , Anda bisa mendapatkan tambahan diskon 10% yang tertera di banner.
- Mengajar profesi Ilmu Data dari awal
- Bootcamp online untuk Ilmu Data
- Melatih profesi Analis Data dari awal
- Bootcamp Online Analisis Data
- Python untuk Kursus Pengembangan Web
Lebih banyak kursus