Mengotomatiskan pekerjaan dengan proyek Python



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.



gambar


Dan dengan kode promo HABR , Anda bisa mendapatkan tambahan diskon 10% yang tertera di banner.







Artikel yang direkomendasikan






All Articles