משימה 4: ה‑container image
במשימה הזאת ה‑placeholders הראשונים נעשים אמיתיים: ה‑jobs image-build ו‑image-push.
- ה‑image נבנה על GitHub runner, מכונה חדשה לכל job, לא על המחשב שלכם.
- רק ה‑workflow של ה‑merge דוחף אותו ל‑Docker Hub. pull request בונה את ה‑image וזורק אותו; topic branch לא בונה בכלל.
- ה‑push צריך login ל‑Docker Hub, עם token ששמור ב‑GitHub: הצעדים נמצאים ב‑ Pipeline Identities, Part 2 (באנגלית).
כתבו את ה‑Dockerfile
- base image רזה של Python, ה‑requirements, האפליקציה, ו‑user שאינו root.
- השרת: TODO: gunicorn ל‑Flask, uvicorn ל‑FastAPI.
- בנו והריצו אותו על המחשב שלכם; קראו ל‑
/healthz.
למה הבחירות האלה
- base image רזה: פחות packages, ולכן הורדה קטנה יותר ופחות ממצאים לסורק של משימה 9.
- ה‑requirements לפני האפליקציה: Docker משתמש שוב ב‑layer שהקלט שלו לא השתנה, כך ששינוי בקוד
בונה מחדש רק את ה‑layers האחרונים, ולא את ה‑
pip install. - user שאינו root: תהליך שפורץ מהאפליקציה אינו root בתוך ה‑container.
- שרת production: שרתי הפיתוח של Flask ושל FastAPI מיועדים רק למחשב שלכם.
איפה ה‑image נבנה: ה‑runner
כל docker build ב‑pipeline רץ על GitHub‑hosted runner: מכונה וירטואלית ש‑GitHub מפעיל ל‑job אחד
ומוחק כשה‑job מסתיים.
- ה‑job עושה checkout ל‑repository על ה‑runner, ובונה שם, עם ה‑Docker שמותקן על ה‑runner.
- ה‑image קיים רק על המכונה הזאת: כשה‑job מסתיים הוא נעלם, אלא אם ה‑job דחף אותו.
- לכן job שצריך את ה‑image או בונה אותו בעצמו, או קורא אותו מה‑registry.
מה נובע מהבנייה על ה‑runner
- שום דבר לא נשאר מה‑run הקודם. כל job מתחיל על מכונה נקייה:
- אין layers של image ב‑cache מאתמול, ולכן כל build הוא build מלא;
- TODO: build cache ש‑GitHub שומר (
docker/build-push-actionעםcache-from: type=gha), או לא.
- ה‑runner בונה למעבד שלו:
ubuntu-latestהוא x86-64 (amd64), כמו ה‑nodes של EKS ושל EC2 במשימה 5.- image שנבנה על Mac עם Apple silicon הוא
arm64, ולא עולה על ה‑nodes האלה. - זו עוד סיבה שה‑image שעולה ל‑deployment מגיע מה‑runner, ולא ממחשב נייד.
- image שנבנה על Mac עם Apple silicon הוא
- ה‑build רואה רק את ה‑checkout: הקבצים שעברו commit ל‑repository. קובץ על המחשב שלכם שלא עשיתם לו commit לא נמצא ב‑image, וכך ה‑pipeline תופס אותו.
- מה שמריצים מקומית ומה שה‑pipeline מריץ הם אותו Dockerfile, כך ש‑build שעובד על המחשב שלכם עובד גם שם, חוץ מהמעבד.
אילו workflows בונים, ואילו דוחפים
| Workflow | בונה את ה‑image | דוחף אותו |
|---|---|---|
| Branch | לא | לא |
| Pull request | כן, כדי לבדוק את ה‑Dockerfile | לא |
| Merge | כן | כן, מתויג ב‑SHA של ה‑merge commit |
- זה המקום הראשון שבו שלושת ה‑workflows נבדלים במה שהם עושים, ולא רק בכמה.
- ה‑registry מחזיק רק images שנבנו מ‑
main: כל tag הוא commit שעבר את השער ועלה ל‑deployment.
למה רק ה‑workflow של ה‑merge דוחף: החלטת design
- כל image ב‑registry עלה ל‑deployment, או עומד לעלות. אין בו שום branch חצי גמור.
- tag נותן שם ל‑commit על
main, ולכן חזרה לגרסה קודמת היא deploy של tag קודם. - ה‑token של Docker Hub מגיע רק ל‑workflow של ה‑merge. ה‑runs של pull request ושל branch לעולם לא עושים
login, כך ששינוי שנמצא ב‑review לא יכול להשתמש ב‑token או להדפיס אותו.
- pull request מ‑fork לא מקבל secrets בכלל, כך שהוא לא היה יכול לדחוף גם אילו ניסה.
- המחיר: ה‑image נבנה פעמיים, פעם ב‑pull request ופעם אחרי ה‑merge.
- ה‑design האחר: צוותים שמריצים כל pull request ב‑test environment משלו דוחפים גם את ה‑image של ה‑pull request, ל‑repository נפרד שמנוקה באופן קבוע. לפרויקט הזה יש environment אחד, ולכן הוא לא עושה זאת.
בנו את ה‑image ב‑pull request
- החליפו את ה‑placeholder של
image-buildב‑pr.yml: checkout ל‑repository, ואזdocker build, בלי login ובלי push. - ה‑job נכשל כשה‑Dockerfile נכשל, כך ש‑image שבור לעולם לא מגיע ל‑
main.
עשו login ל‑Docker Hub
עקבו אחרי Pipeline Identities, Part 2, עם השמות של הפרויקט הזה:
- צרו את ה‑repository ב‑Docker Hub:
devops-project. TODO: ציבורי או פרטי. - צרו את ה‑access token: Repo Read & Write, 30 יום.
- שמרו אותו ב‑GitHub: ה‑secret
DOCKERHUB_TOKEN, וה‑variable DOCKERHUB_USERNAME. - הוסיפו את צעד ה‑login (
docker/login-action) ל‑job image-pushב‑merge.yml, ורק שם.
repository ציבורי או פרטי ב‑Docker Hub
- ציבורי: כל אחד יכול לעשות pull ל‑image, בלי credential.
- ה‑cluster במשימה 6 מושך אותו בלי שום הגדרה.
- כמו ה‑repository ב‑GitHub, זה משהו להראות למעסיק.
- פרטי: רק מי שמחזיק credential יכול למשוך אותו.
- ה‑cluster צריך credential משלו למשיכה: Pipeline Identities, Part 6 מראה אחד.
- לחשבון Docker Hub חינמי יש מספר מוגבל של repositories פרטיים.
דחפו את ה‑image מה‑workflow של ה‑merge
- החליפו את ה‑placeholder של
image-pushב‑merge.yml: checkout, login, build, tag ב‑SHA של ה‑commit ($GITHUB_SHA), push. - TODO: גם tag בשם
latest, או רק ה‑SHA. - TODO: להעביר את שם ה‑image ל‑job
deploy, כ‑job output.
עשו merge, ומצאו את ה‑image
- פתחו pull request:
image-buildרץ, ושום דבר חדש לא מופיע ב‑Docker Hub. - עשו merge:
image-pushרץ, וה‑image מופיע ב‑Docker Hub תחת ה‑SHA של ה‑merge commit.