• בלוג
  • בניית אימג'ים עם Dockerfile

בניית אימג'ים עם Dockerfile

08/05/2019

פוסט זה כולל טיפ קצר על Docker. אם אתם רוצים ללמוד יותר לעומק על פיתוח עם Docker, Docker Compose או Kubernetes תשמחו לשמוע שבניתי קורס וידאו מקיף בנושא זה.
למידע נוסף והצטרפות לקורס בקרו בדף קורס Docker כאן באתר.
 

בתור מפתחים הבחירה שלנו להפיץ את הקוד שאנחנו כותבים דרך דוקר אומרת שאנחנו אומנם עושים קצת יותר מאמץ בעת בניית הקוד, אבל מקבלים בתמורה מנגנון ממש פשוט להתקנת היישום שלנו על מכונות חדשות ברגע ובלי קשר לדברים שכבר מותקנים על אותן מכונות.

בואו נראה כמה דוגמאות בהן אנחנו בונים יישומים מסוגים שונים בתור אימג'ים כדי שלאחרים יהיה קל להתקין ולהפעיל את היישומים שלנו.

1. תיקיית בסיס לאימג'

ברוב המקרים שנרצה לבנות אימג' נצטרך להגדיר איזו תיקיית בסיס שדוקר יוכל לקרוא ממנה את הקבצים וכמובן את קובץ ההוראות ליצירת האימג', הוא ה Dockerfile. תיקיית הבסיס נקראת ה Build Context בדוקרית והקבצים שם נשלחים ל Docker Daemon, שזו תוכנה שרצה אצלכם מקומית על המחשב ובונה את האימג'. בגלל שה Docker Daemon רץ כתהליך נפרד ברקע, העברת הקבצים אליו נעשית כבר עם תחילת הבניה בלי קשר לאיזה קבצים תשלבו בפועל באימג', ולכן מומלץ שתיקיית הבניה שלכם תכיל רק את הקבצים שאתם צריכים.

בתוך ה Dockerfile, הפקודה FROM קובעת מאיזה אימג' נתחיל לעבוד, הפקודה COPY מעתיקה קבצים מה Docker Context לתוך האימג' והפקודה ENTRYPOINT קובעת איזה תוכנית נפעיל. רק עם שלושת הפקודות האלה אפשר להתחיל וליצור אימג' שמפעיל תוכנית רובי ומדפיס הודעה קצרה על המסך.

צרו תיקיה חדשה ובתוכה את הקובץ hello.rb עם התוכן הבא:

puts "Hello World"

והקובץ Dockerfile ובו התוכן הבא:

FROM ruby:2.6

COPY hello.rb /opt

ENTRYPOINT ["/usr/local/bin/ruby", "/opt/hello.rb"]

הקובץ Dockerfile מגדיר איך לבנות את האימג', משמעות 3 השורות בו:

  1. השורה הראשונה קובעת מאיזה אימג' בסיס להתחיל.

  2. השורה השניה מבקשת להעתיק את הקובץ hello.rb למקום ידוע על האימג'.

  3. השורה השלישית מגדירה את הפקודה שהאימג' יריץ כשמפעילים אותו.

בשביל למצוא את הנתיב לרובי ולדעת שיש תיקיה opt על האימג' שאפשר לשים בה קבצים אני חייב להכיר את אימג' הבסיס בו השתמשתי. הרבה פעמים בבניית אימג' אנחנו קודם כל ניצור קונטיינר מאימג' הבסיס, ניכנס אליו ונחטט כדי לגלות מה יש שם ואז נכתוב את ה Dockerfile תוך שימוש במה שגילינו.

בשביל לבנות את האימג' עם הדוקרפייל שיצרנו אכתוב מתוך אותה תיקיה:

$ docker build . -t 01-helloruby

הפרמטר ל -t הוא שם האימג'. אם יש לכם חשבון בדוקרהאב סיכוי טוב שתרצו לשים שם גם את שם המשתמש שלכם ואז תוכלו להעלות את האימג' לענן כשתסיימו לבנות אותו. לדוגמא שם המשתמש שלי הוא ynonp אז בשביל לבנות אימג' שאפשר להעלות לענן אני יכול להפעיל:

$ docker build . -t ynonp/01-helloruby

2. הפעלת פקודות בזמן בניית אימג'

ניקח את הסיפור הזה עוד צעד קדימה ונרצה להוסיף לתוכנית שלנו גם שימוש בספריה חיצונית. ברובי אנחנו מתקינים סיפריה חיצונית עם הפקודה gem, כך שאצלי על המחשב אני יכול להפעיל את:

$ gem install cowsay

כדי להתקין סיפריה חיצונית שנקראת cowsay.

מסתבר שהמנגנון ממש דומה גם בתוך האימג'. כל עוד באימג' הבסיס שלכם יש את כל הכלים בשביל להריץ את הפקודה שאתם רוצים, תוכלו להשתמש ב RUN כדי להפעיל את הפקודה.

נעדכן את התוכנית כך שתשתמש ב cowsay:

require 'cowsay'

puts Cowsay.say('Hello World', 'cow')

ונעדכן את ה Dockerfile כך שיתקין את Cowsay:

FROM ruby:2.6

RUN ["/usr/local/bin/gem", "install", "cowsay"]
COPY hello.rb /opt

ENTRYPOINT ["/usr/local/bin/ruby", "/opt/hello.rb"]

ואחרי בניה והפעלה נוכל לראות פרה שאומרת שלום:

$ docker build . -t 02-rubygems
$ docker run 02-rubygems

 _____________ 
| Hello World |
 ------------- 
      \   ^__^
       \  (oo)\_______
          (__)\       )\/\
              ||----w |
              ||     ||

במנגנון הזה נשתמש למשל באימג' עבור Web Application כדי להריץ npm install ו webpack כחלק מהבניה של האימג' במקום לשים את התוצאות של כלים אלה בתוך ה Docker Context.

3. יצירת אימג' עם Volume

הרבה אפליקציות מאפשרות לכל אחד להתאים אישית חלקים מסוימים באפליקציה באמצעות קבצי הגדרות או קבצים מסוגים אחרים שהאפליקציה משתמשת בהם. דוגמא פשוטה היא אפליקציית ווב שבכל התקנה ניתן לשנות קצת את דפי השגיאה (ה-404) שהאפליקציה מציגה.

בשביל זה אנחנו רוצים לאפשר לכל מי שיוצר קונטיינר מהאימג' שלנו לשנות חלקים מסוימים במערכת הקבצים, ועדיין לתת ערכי ברירת מחדל לקבצים אלה. המנגנון של דוקר שעוזר לנו בפיתוח כזה נקרא Volumes. ראינו כבר באחד הפרקים הקודמים בסידרה הזאת איך ליצור Volume ולחבר אותו לקונטיינר, ועכשיו יהיה מעניין לראות איך אנחנו בונים אימג' שמצפה ל Volume ואפילו מאתחל אותו עם קבצי ברירת מחדל.

אני רוצה להמשיך עם הדוגמא של אפליקציית ווב ולכתוב Web Application ב Ruby שמציג בעמוד הראשי את הטקסט Hello World וכל עמוד אחר יציג דף HTML סטטי מקובץ שנקרא 404.html. האימג' יכיל תוכן ברירת מחדל לקובץ זה, וכל מי שירצה להשתמש באימג' יוכל להחליף את הקובץ עם התוכן שלו באמצעות Volume.

תחילה אני יוצר תיקיה חדשה בשם 03-volumes עם:

mkdir 03-volumes
cd 03-volumes

נעבור לאפליקציה. צרו קובץ בשם Gemfile עם התוכן הבא:

source "https://rubygems.org"

gem "sinatra"
gem "sinatra-contrib"

צרו קובץ בשם app.rb עם התוכן הבא:

require "sinatra"

get '/' do
  'Hello world!'
end

not_found do
  send_file(File.join(File.dirname(__FILE__), 'public', '404.html'))
end

צרו תיקיה בשם public ובתוכה קובץ בשם 404.html עם התוכן הבא:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>404</title>
  </head>
  <body>
    <h1>Page Not Found...</h1>
  </body>
</html>

ואנחנו מוכנים לכתוב את הדוקרפייל. צרו קובץ בשם Dockerfile עם התוכן הבא:

FROM ruby:2.6

COPY app.rb Gemfile /opt/
COPY public /opt/public

WORKDIR /opt

RUN ["/usr/local/bin/bundler", "install"]

ENV APP_ENV production
ENTRYPOINT ["/usr/local/bin/ruby", "./app.rb"]

שתי פקודות חדשות יש לנו בקובץ עד עכשיו:

  1. הפקודה ENV מגדירה משתנה סביבה. באפליקציית ווב בספריית סינטרה משתנה הסביבה APP_ENV קובע את סביבת היישום (פיתוח או ייצור).

  2. הפקודה WORKDIR קובעת מאיפה להריץ את הפקודות (בתוך האימג') מכאן והלאה.

עכשיו אפשר לבנות עם:

$ docker build . -t webapp

ולהריץ:

$ docker run webapp -p 4000:4567 webapp

ואם עשיתם הכל כמו שצריך תוכלו לגלוש לפורט 4000 על המחשב המקומי כדי לראות את הטקסט Hello World, ואם תשנו נתיב תגיעו לדף ה 404 שיצרנו עם הטקסט Page Not Found.

אבל אנחנו כאן בשביל לדבר על Volumes. לכן נרצה לאפשר למשתמשים של האימג' לשנות את קובץ ה 404.html שלנו. בשביל זה נגדיר את תיקיית public בתור Volume בדוקרפייל:

FROM ruby:2.6

COPY app.rb Gemfile /opt/
COPY public /opt/public

WORKDIR /opt

RUN ["/usr/local/bin/bundler", "install"]

ENV APP_ENV production
VOLUME ["/opt/public"]
ENTRYPOINT ["/usr/local/bin/ruby", "./app.rb"]

נבנה מחדש ונריץ את האימג':

$ docker build . -t webapp
$ docker run webapp

ועכשיו כשנסתכל ברשימת ה Volumes על המכונה נקבל את הכונן החדש:

$ docker volume ls
DRIVER              VOLUME NAME
local               f5fee487f78072e4556b5fffd1675f0404b9fe483e35f74f82db70a01f3c4ff1

אימג' עם Volume מתנהג לפי הכללים הבאים:

  1. כל פעם שניצור קונטיינר מהאימג' יווצר אוטומטית Volume חדש מהתיקיה שכתבנו ב Dockerfile. המידע מהתיקיה המקורית יועתק ל Volume.

  2. אנחנו יכולים להיכנס ל Volume הזה ממכונה אחרת ולעדכן את הקבצים שבו.

  3. כל פעם שנפעיל מחדש קונטיינר שנוצר מהאימג' עם ה Volume, אותו Volume ימופה (גם אם עשיתם שינויים בתוכן שלו).

זה אומר שעכשיו בשביל לעדכן את קובץ ה 404 שיצרנו ניכנס ל Volume ממכונה אחרת, נעדכן את הקובץ ואז נפעיל מחדש את הקונטיינר שכבר יצרנו. בקוד זה יראה כך:

$ docker run --rm -it --mount src=f5fee487f78072e4556b5fffd1675f0404b9fe483e35f74f82db70a01f3c4ff1,dst=/mnt busybox

# inside the machine, edit the file /mnt/404.html
# then leave the machine

$ docker ps -a
eef4dd103c01        webapp              "/usr/local/bin/ruby…"   4 minutes ago       Exited (0) 4 minutes ago                       naughty_kirch

$ docker start eef4dd103c01

ועכשיו בגלישה ל http://localhost:4000/foo תקבלו את דף ה 404 החדש שיצרתם.

4. סיכום - יצירת אימג'ים

במדריך זה ראינו איך לבנות אימג' מתוך תיקיה באמצעות Dockerfile. בשביל זה שמנו את כל הקבצים הרלוונטים בתיקיה יחד עם קובץ ההוראות והפעלנו docker build. קובץ ההוראות קבע איזה קבצים להעתיק ולאן וגם איזה פקודות להריץ על המכונה.

בעת יצירת אימג'ים כדאי להתאמץ ולבנות אימג'ים שאנשים יוכלו מיד ליצור מהם קונטיינרים ולהתחיל לעבוד. חישבו על השימוש שלכם בדוקר ועל כמה נוח לכם להרים קונטיינר חדש מתוך אימג', ונסו לייצר את אותה חוויה ללקוחות שלכם.

השלב הבא והחשוב בבניית אימג'ים יהיה אופטימיזציה שלהם מבחינת גודל ומבחינת חלוקה לשכבות. היינו רוצים שהאימג'ים שלנו יהיו כמה שיותר קטנים כדי שללקוחות יהיה קל להוריד אותם מהרשת ולהפעיל. על מה שקובע את גודל האימג'ים וטכניקות להקטין אותם נדבר בפרק הבא.