
نشارككم اليوم ترجمة لمقال من قبل مهندس IBM DevOps حول أتمتة إنشاء صور Docker المجمعة بسرعة وسهولة تصحيح أخطائها لمشاريع Python باستخدام Makefile. لا يجعل هذا المشروع تصحيح الأخطاء في Docker أسهل فحسب ، بل يهتم أيضًا بجودة كود مشروعك. التفاصيل ، كما هو الحال دائمًا ، تحت الخفض.
يمكن لكل مشروع - سواء كنت تعمل على تطبيق ويب ، باستخدام علوم البيانات أو الذكاء الاصطناعي ، الاستفادة من CI / CDs المضبوطة جيدًا ، وصور Docker التي يتم تصحيحها في وقت واحد أثناء التطوير وتحسينها لبيئة الإنتاج ، أو أدوات ضمان الجودة كود مثل CodeClimate أو SonarCloud . تمت تغطية كل هذه الأشياء في هذه المقالة وتوضيح كيفية إضافتها إلى مشروع Python.
حاويات قابلة للفك للتطوير
لا يحب بعض الأشخاص Docker لأنه قد يكون من الصعب تصحيح أخطاء الحاويات أو لأن إنشاء الصور يستغرق وقتًا طويلاً. لذلك لنبدأ ببناء صور مثالية للتطوير - سريعة الإنشاء وسهلة التصحيح. لجعل الصورة سهلة التصحيح ، تحتاج إلى صورة أساسية تتضمن جميع الأدوات التي قد تحتاجها في أي وقت لتصحيح الأخطاء. هذه هي bash و vim و netcat و wget و cat و find و grep وغيرها.
صورة Python: 3.8.1-busterيبدو أنه مرشح مثالي لهذه المهمة. يتضمن العديد من الأدوات خارج الصندوق ، ومن السهل تثبيت الأدوات المفقودة. الصورة كبيرة ، لكن لا يهم هنا: سيتم استخدامها فقط في التطوير. كما لاحظت على الأرجح ، فإن الصور محددة للغاية. يعد قفل إصدارات Python و Debian أمرًا مقصودًا: فأنت تريد تقليل مخاطر الانهيار الناجم عن الإصدارات الجديدة التي قد تكون غير متوافقة من Python أو Debian . يمكن استخدام صورة قائمة على جبال الألب كبديل ، ولكنها قد تسبب بعض المشكلات: داخلها تستخدم musl lib بدلاً من glibcالذي تعتمد عليه بايثون. ضع ذلك في الاعتبار إذا قررت اختيار جبال الألب. فيما يتعلق بالسرعة ، سنستخدم تصميمات متعددة المراحل لتخزين أكبر عدد ممكن من الطبقات. لذلك لا يتم تحميل التبعيات والأدوات مثل مجلس التعاون الخليجي ، بالإضافة إلى جميع التبعيات التي يحتاجها التطبيق ، من requirements.txt في كل مرة. لزيادة تسريع الأمور ، يتم إنشاء صورة أساسية مخصصة من Python المذكورة سابقًا : 3.8.1-buster ، والتي تحتوي على كل ما نحتاجه ، حيث لا يمكننا تخزين الخطوات المطلوبة لتنزيل هذه الأدوات وتثبيتها في الصورة النهائية مؤقتًا
runner. لكن توقف عن الكلام ، دعنا نلقي نظرة على 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}
أعلاه يمكنك أن ترى أن الكود
runnerسوف يمر عبر 3 صور وسيطة قبل إنشاء الصورة النهائية . الأول هو باني . يقوم بتنزيل جميع المكتبات اللازمة لإنشاء التطبيق ، بما في ذلك بيئة gcc و Python الافتراضية. بعد التثبيت ، يتم إنشاء بيئة افتراضية حقيقية واستخدامها بواسطة الصور التالية. يأتي بعد ذلك builder-vv ، الذي ينسخ قائمة التبعيات (requirements.txt) في الصورة ثم تثبيتها. هذه الصورة الوسيطة ضرورية للتخزين المؤقت: فأنت تريد فقط تثبيت المكتبات إذا تغيرت requirements.txt ، وإلا فإننا نستخدم ذاكرة التخزين المؤقت فقط. دعونا نختبر التطبيق قبل إنشاء الصورة النهائية.
قبل أن ننشئ صورتنا النهائية ، دعنا أولاً نجري اختبارات تطبيقنا. انسخ الكود المصدري وقم بإجراء الاختبارات. عندما تجتاز الاختبارات ، انتقل إلى صورة العداء . يستخدم هذا صورة مخصصة مع بعض الأدوات الإضافية غير الموجودة في صورة دبيان العادية: vim و netcat. هذه الصورة موجودة على Docker Hub ، ويمكنك أيضًا إلقاء نظرة على Dockerfile بسيط جدًا في base.Dockerfile . إذن ما نفعله في هذه الصورة النهائية: أولاً نقوم بنسخ البيئة الافتراضية حيث يتم تخزين جميع التبعيات التي قمنا بتثبيتها من صورة الاختبار، ثم انسخ التطبيق الذي تم اختباره. الآن بعد أن أصبحت جميع المصادر في الصورة ، انتقل إلى الدليل حيث يوجد التطبيق وقم بتثبيت ENTRYPOINT بحيث يتم تشغيل التطبيق عند بدء تشغيل الصورة. لأسباب تتعلق بالأمان ، تم تعيين USER على 1001 : توصي أفضل الممارسات بعدم تشغيل الحاويات كجذر. يقوم السطران الأخيران بتعيين تسميات الصورة. سيتم استبدالها عند البناء من خلال الهدف
make، والذي سنراه بعد ذلك بقليل.
حاويات محسّنة لبيئة الإنتاج
عندما يتعلق الأمر بمظهر فئة الإنتاج ، فأنت تريد التأكد من أنها صغيرة وآمنة وسريعة. المفضلة الشخصية بهذا المعنى هي صورة Python من مشروع Distroless . ولكن ما هو "Distroless"؟ دعونا نضع الأمر على هذا النحو: في عالم مثالي ، يمكن للجميع بناء صورتهم الخاصة باستخدام من الصفر كقاعدة (أي صورة فارغة). لكن هذا ليس ما يريده معظمنا ، لأنه يتطلب ربط الثنائيات بشكل ثابت ، وما إلى ذلك. وهنا يأتي دور Distroless : إنه من الصفر للجميع. والآن سأخبرك حقًا ما هو "Distroless". هذه مجموعة تم إنشاؤها بواسطة Googleالصور التي تحتوي على الحد الأدنى المطلق المطلوب من التطبيق. هذا يعني أنه لا توجد أغلفة أو مديرو حزم أو أدوات أخرى تعمل على تضخيم الصورة وتوليد ضوضاء إشارة للماسحات الضوئية الأمنية (مثل CVE ) ، مما يجعل من الصعب إثبات الامتثال. الآن بعد أن عرفنا ما نتعامل معه ، دعنا نلقي نظرة على Dockerfile الإنتاج. في الواقع ، لا تحتاج إلى تغيير الرمز كثيرًا ، ما عليك سوى تغيير سطرين:
# 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
كل ما احتجنا إلى تغييره هو الصور الأساسية لبناء التطبيق وتشغيله! لكن الفارق كبير جدًا - بلغ وزن صورة التطوير 1.03 غيغابايت ، وكانت هذه الصورة 103 ميجابايت فقط ، وهذا فرق كبير! ويمكنني سماعك بالفعل: "ألبينا يمكن أن يكون وزنها أقل!" ... نعم هو كذلك ، لكن الحجم لا يهم كثيرًا. ستلاحظ حجم الصورة فقط عند التحميل / التفريغ ، وهذا لا يحدث كثيرًا. عندما تعمل الصورة ، لا يهم الحجم. ما هو أكثر أهمية من الحجم هو الأمان ، وفي هذا الصدد ، يتفوق Distroless بشكل واضح على Alpine: لدى Alpine العديد من الحزم الإضافية لزيادة سطح الهجوم. آخر شيء يستحق الذكر عند الحديث عن Distroless هو تصحيح أخطاء الصورة. معتبرا أنلا يحتوي Distroless على أي غلاف (ولا حتى "sh") ، وتصحيح الأخطاء والبحث يصبح صعبًا للغاية. لهذا ، هناك إصدارات "تصحيح" لجميع صور Distroless . بهذه الطريقة ، عندما تحدث مشكلة ، من الممكن بناء صورة العمل الخاصة بك باستخدام علامة
debugونشرها مع صورتك المعتادة ، وتنفيذ ما يلزم في صورة تصحيح الأخطاء والقيام ، على سبيل المثال ، بتفريغ التدفق. من الممكن استخدام نسخة التصحيح من صورة python3 مثل هذا:
docker run --entrypoint=sh -ti gcr.io/distroless/python3-debian10:debug
فريق واحد لكل شيء
مع كل Dockerfiles جاهزة ، يمكنك أتمتة هذا الكابوس بأكمله باستخدام Makefile! أول شيء نريد القيام به هو إنشاء التطبيق باستخدام Docker. لذلك ، لبناء صورة تطويرية ،
make build-devسنكتب أن تنفذ الكود التالي:
# 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- .
يبني هذا الهدف صورة عن طريق استبدال الملصقات الموجودة في الأسفل أولاً
dev.Dockerfileباسم الصورة والعلامة التي تم إنشاؤها عن طريق التشغيل git describe، ثم يتم تشغيلها docker build. بعد ذلك ، قم بالبناء من أجل بيئة الإنتاج باستخدام 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- .
هذا الهدف مشابه جدًا للهدف السابق ، ولكن بدلاً من استخدام علامة git كإصدار ، يتم استخدام الإصدار الذي تم تمريره كوسيطة ، في المثال أعلاه هو 1.0.0. عندما يعمل كل شيء في Docker ، في مرحلة ما تحتاج أيضًا إلى تصحيح كل شيء في Docker . هناك هدف لهذا:
# 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)
في الكود أعلاه ، يمكنك أن ترى أن نقطة الإدخال قد تم تجاوزها بواسطة bash ، وأن أمر الحاوية تم تجاوزه بواسطة وسيطة في CMD. وبالتالي ، يمكننا إما إدخال الحاوية والبحث حولها ، أو تنفيذ نوع من الأوامر ، كما في المثال أعلاه. بمجرد الانتهاء من البرمجة ودفع الصورة إلى سجل Docker ، يمكننا استخدام
make push VERSION=0.0.2. دعونا نرى ما يفعله هذا الهدف:
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)
يقوم أولاً بإطلاق الهدف الذي تمت مناقشته مسبقًا
build-prodثم ببساطة docker push. يفترض هذا تسجيل الدخول إلى سجل Docker ، لذلك يجب تنفيذ هذا الهدف قبل التشغيل docker login. الهدف النهائي هو تنظيف آثار Docker. يستخدم هذا علامة الاسم ، التي تم استبدالها داخل ملفات إنشاء صورة Docker ، لتصفية العناصر والعثور عليها التي يجب إزالتها:
docker-clean:
@docker system prune -f --filter "label=name=$(MODULE)"
كل كود Makefile موجود في المستودع .
CI / CD مع إجراءات GitHub
يستخدم المشروع make و Github Actions وسجل حزمة Github لبناء خطوط أنابيب (مهام) وتخزين صورنا لتهيئة CI / CD. ولكن ما هو؟
- إجراءات GitHub هي مهام / خطوط أنابيب تساعد على أتمتة سير عمل التطوير. من الممكن استخدامها لإنشاء مهام منفصلة ، ثم دمجها في مهام سير عمل مخصصة يتم تنفيذها ، على سبيل المثال ، في كل مرة تقوم فيها بإرسال البيانات إلى المستودع أو عند إنشاء إصدار.
- Github Package Registry عبارة عن خدمة استضافة حزمة متكاملة تمامًا مع GitHub. يسمح لك بتخزين أنواع مختلفة من الحزم ، مثل روبي الأحجار الكريمة أو حزم npm . يستخدمه المشروع لتخزين صور Docker. تعرف على المزيد حول Github package Registry يمكن أن يكون هنا .
لاستخدام إجراءات GitHub ، يتم إنشاء مهام سير العمل في المشروع بناءً على المشغلات المحددة (مثال على المشغل يتم الإرسال إلى المستودع). مهام سير العمل هذه هي ملفات YAML في الدليل
.github/workflows:
.github
└── workflows
├── build-test.yml
└── push.yml
يحتوي ملف build-test.yml على وظيفتين يتم تشغيلهما في كل مرة يتم فيها إرسال الرمز إلى المستودع ، وهما موضحان أدناه:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Run Makefile build for Development
run: make build-dev
تتحقق المهمة الأولى ، المسماة build ، من إمكانية إنشاء التطبيق عن طريق تشغيل الهدف
make build-dev. ومع ذلك ، قبل البدء ، يتحقق من المستودع عن طريق تنفيذه checkoutالمنشور على 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
المهمة الثانية أكثر صعوبة بقليل. يقوم بإجراء اختبارات بجانب التطبيق ، بالإضافة إلى 3 وحدات تحكم في جودة الكود (وحدات تحكم جودة الكود). كما في المهمة السابقة ، يتم استخدام إجراء للحصول على شفرة المصدر
checkout@v1. بعد ذلك ، setup-python@v1تم إطلاق إجراء آخر منشور يسمى ، والذي ينشئ بيئة بيثون (المزيد حول ذلك هنا ). الآن بعد أن أصبح لدينا بيئة Python ، نحتاج إلى تبعيات التطبيقات requirements.txtالتي تم تثبيتها باستخدام pip. في هذه المرحلة make test، لنبدأ تشغيل الهدف ، فهو يدير مجموعة اختبار Pytest . إذا نجحت اختبارات المجموعة ، فانتقل إلى تثبيت linters المذكورة سابقًا - pylint و flake8 و bandit . أخيرًا ، أطلقنا الهدفmake lintوالتي بدورها تطلق كل من هذه linters. الأمر كله يتعلق بوظيفة البناء / الاختبار ، ولكن ماذا عن إرسال الكود؟ لنتحدث عنها:
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 }}
تحدد الأسطر الأربعة الأولى موعد بدء المهمة. نشير إلى أن هذه الوظيفة يجب أن يتم تشغيلها فقط عندما يتم نقل العلامات إلى المستودع (* تشير إلى نمط اسم ، وهنا جميع العلامات ). يتم ذلك حتى لا ندفع صورة Docker إلى سجل حزمة GitHub في كل مرة نقوم فيها بدفع البيانات إلى المستودع ، ولكن فقط عندما يتم تحميل علامة تشير إلى الإصدار الجديد من تطبيقنا. الآن بالنسبة إلى نص هذه المهمة - يبدأ بالتحقق من شفرة المصدر وتعيين قيمة متغير البيئة RELEASE_VERSION مساوية لعلامة git التي تم تحميلها. يتم ذلك باستخدام وظيفة GitHub Actions :: setenv (مزيد من التفاصيل هنا). ثم تدخل المهمة في سجل Docker مع REGISTRY_TOKEN السري المخزن في المستودع وتسجيل دخول المستخدم الذي بدأ سير العمل (github.actor). أخيرًا ، يقوم السطر الأخير بتشغيل هدف الدفع ، والذي يبني صورة الإنتاج ويدفعها إلى التسجيل بعلامة git المنشورة مسبقًا كعلامة الصورة. تحقق من جميع الكود في ملفات المستودع الخاصة بي .
فحص جودة الكود باستخدام CodeClimate
أخيرًا وليس آخرًا ، دعنا نضيف فحص جودة الكود باستخدام CodeClimate و SonarCloud . سيعملون مع مهمة الاختبار الموضحة أعلاه. أضف بضعة أسطر من التعليمات البرمجية:
# 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 }}
البدء بـ CodeClimate : تصدير متغير
GIT_BRANCHمسترجع باستخدام متغير البيئة GITHUB_REF. ثم نقوم بتنزيل أداة تقرير اختبار CodeClimate وجعلها قابلة للتنفيذ. ثم سنستخدمه لتنسيق تقرير تغطية مجموعة الاختبار. في السطر الأخير ، نرسله إلى CodeClimate مع معرف الأداة لتقرير الاختبار ، والذي يتم تخزينه في أسرار المستودع. بالنسبة إلى SonarCloud ، تحتاج إلى إنشاء ملف sonar-project.properties. يمكن العثور على قيم هذا الملف في لوحة معلومات SonarCloud في الزاوية اليمنى السفلية ، ويبدو هذا الملف على النحو التالي :
sonar.organization=martinheinz-github
sonar.projectKey=MartinHeinz_python-project-blueprint
sonar.sources=blueprint
علاوة على ذلك ، من الممكن ببساطة استخدام الشخص الذي يقوم بالعمل نيابةً عنا
sonarcloud-github-action. كل ما يتعين علينا القيام به هو توفير رمزين مميزين : بالنسبة إلى GitHub ، الرمز الموجود في المستودع الافتراضي ، وبالنسبة إلى SonarCloud ، الذي حصلنا عليه من موقع SonarCloud . ملاحظة: تم وصف خطوات الحصول على جميع الرموز والأسرار المذكورة وتثبيتها في README الخاص بالمستودع .
خاتمة
هذا كل شئ! باستخدام الأدوات والتكوينات والكود ، أنت جاهز لتخصيص وأتمتة كل جانب من جوانب مشروع Python القادم! إذا كنت بحاجة إلى مزيد من المعلومات حول الموضوعات المعروضة أو التي تمت مناقشتها في هذه المقالة ، فتحقق من الوثائق والكود في المستودع الخاص بي ، وإذا كان لديك أي اقتراحات أو مشكلات ، فيرجى إرسال طلب إلى المستودع ، أو مجرد تمييز هذا المشروع الصغير بتمييز إذا كان يناسبك. مثل.

وباستخدام الرمز الترويجي HABR ، يمكنك الحصول على 10٪ إضافية للخصم الموضح على اللافتة.
- تدريس مهنة علوم البيانات من الصفر
- المعسكر التدريبي عبر الإنترنت لعلوم البيانات
- تدريب مهنة محلل البيانات من الصفر
- المعسكر التدريبي لتحليلات البيانات على الإنترنت
- دورة بايثون لتطوير الويب
المزيد من الدورات