זיהוי נפילות באפליקציה בתוך Docker
פוסט זה כולל טיפ קצר על Docker. אם אתם רוצים ללמוד יותר לעומק על פיתוח עם Docker, Docker Compose או Kubernetes תשמחו לשמוע שבניתי קורס וידאו מקיף בנושא זה.
למידע נוסף והצטרפות לקורס בקרו בדף קורס Docker כאן באתר.
כל מערכת שאתם מפעילים עלולה להגיע למצבים מביכים כשהיא פוגשת את העולם האמיתי. לפעמים עומסים, לפעמים באגים ולרוב שילוב של שניהם יגרמו למערכת שלכם להיתקע או להתרסק. לדוקר יש מנגנון אוטומטי שמזהה נפילות מסוג זה וגם יפעיל מחדש את האפליקציה כשזה קורה כדי שאף אחד לא ישים לב לפאדיחה. זה נקרא Health Check ובוא נראה איך זה עובד.
1. המנגנון בקיצור
דוקר יודע להריץ פקודה כל X זמן לפי מה שאתם מבקשים ולפי התוצאה של הפקודה הזאת לגלות האם האפליקציה שלכם עדיין בחיים. אתם קובעים כל כמה זמן להריץ את הפקודה, כמה זמן לחכות עד שתסתיים בהצלחה וכמה כישלונות אתם מוכנים לסבול עד שאפשר לסמן את הקונטיינר בתור unhealthy
. דוקר מוסיף אינדיקציה למצב הבריאות של כל קונטיינר ויישום אחר שנקרא autoheal
יאפשר לנו לאתחל באופן אוטומטי קונטיינרים שאינם בריאים.
הפקודה הרלוונטית ב Dockerfile היא HEALTHCHECK
. יכולה להיות רק אחת כזו בקובץ והפרמטרים הרלוונטים עבורה (עם ערכי ברירות המחדל שלהם) הם:
--interval=DURATION (default: 30s)
--timeout=DURATION (default: 30s)
--start-period=DURATION (default: 0s)
--retries=N (default: 3)
במקרה של שרת ווב נוכל להשתמש למשל בבדיקה הבאה כדי לוודא שהשרת עדיין עונה:
HEALTHCHECK CMD curl --fail http://localhost || exit 1
אבל כמובן זה יעבוד רק אם יש לנו את curl על הקונטיינר. הרבה פעמים נצטרך להיות יותר יצירתיים ואפילו לכתוב בעצמנו סקריפט שבודק אם השרת חי, בגלל שבתוך קונטיינר לא תמיד יהיו לנו את כל הכלים שאנחנו רגילים אליהם.
2. זיהוי תקיעה של שרת ווב
ניקח לדוגמא שרת ווב עם תקלה ששתלתי בו - השרת יענה לבקשות עד שמישהו יפנה ל URL בעייתי מסוים ששובר אותו, ואחרי זה השרת ימשיך לרוץ אבל יחזיר שגיאות לכל בקשה שתגיע. זה קוד השרת ב Flask:
from flask import Flask
import flask
import os
import socket
# Connect to Redis
app = Flask(__name__)
app.isactive = True
@app.route("/")
def hello():
if app.isactive:
return "Hello World"
else:
flask.abort(500)
@app.route("/stop")
def stop():
app.isactive = False
if __name__ == "__main__":
app.run(host='0.0.0.0', port=80)
הכתובת הראשית עונה את התשובה Hello World אבל אם מישהו פונה לנתיב stop
אז החל מאותו רגע הכתובת הראשית כבר תחזיר Server Erorr. נעבור להשתמש ב HEALTHCHECK כדי לזהות את הבעיה הזאת.
תחילה נרצה לכתוב סקריפט קצר שמזהה האם השרת חי או לא ומחזיר למערכת ההפעלה את הסטטוס המתאים. אני כותב את הסקריפט הזה בפייתון כי אני יודע שיש פייתון על הקונטיינר (האפליקציה שלי כתובה בפייתון). במקרים אחרים נצטרך לזהות כלים אחרים שמותקנים על הקונטיינר ולהשתמש בהם, או במקרה הגרוע להתקין כלי מיוחד רק בשביל הבדיקה. בפייתון אפשר לבדוק אם השרת פעיל באמצעות שליחת הודעה לכתובת הראשית שלו ולכן הסקריפט יראה כך:
import urllib2, sys
req = urllib2.Request('http://localhost')
try:
response = urllib2.urlopen(req)
if response.getcode() == 200:
sys.exit(0)
else:
sys.exit(1)
except Exception:
sys.exit(2)
ועכשיו נכתוב Dockerfile שישים את השרת, סקריפט הבדיקה וה HEALTHCHECK:
# Use an official Python runtime as a parent image
FROM python:2.7-slim
# Set the working directory to /app
WORKDIR /app
# Copy the current directory contents into the container at /app
COPY . /app
# Install any needed packages specified in requirements.txt
RUN pip install --trusted-host pypi.python.org -r requirements.txt
# Make port 80 available to the world outside this container
EXPOSE 80
# Define environment variable
ENV NAME World
HEALTHCHECK --interval=20s --start-period=30s CMD ["python", "/app/check.py"]
# Run app.py when the container launches
CMD ["python", "app.py"]
אני מגדיר שהבדיקה תשתמש ב curl כדי לבדוק את הנתיב הראשי, תרוץ כל עשרים שניות, בדקה הראשונה לא תרשום כשלונות כי אולי לשרת לוקח זמן לעלות ואחרי 3 כישלונות תסמן את השרת בתור Unhealthy.
נבנה את האימג' מתוך תיקיית הפרויקט שלנו עם:
$ docker build -t flask-healthcheck-demo .
אחרי זה ניצור ממנה קונטיינר עם:
$ docker run -p 4000:80 ynonp/flask-healthcheck-demo
ועכשיו נראה את הסטטוס:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
784235ba3604 ynonp/flask-healthcheck-demo "python app.py" 34 seconds ago Up 33 seconds (healthy) 0.0.0.0:4000->80/tcp pensive_banach
בדיקת התקינות הצליחה ואכן השרת מסומן בתור healthy. עכשיו מתוך הדפדפן נגלוש ל URL הבעייתי כדי לשבור את השרת. בדיקות התקינות עכשיו יתחילו להיכשל, ואחרי 3 כישלונות הסטטוס של הקונטיינר יהפוך ל unhealthy:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
784235ba3604 ynonp/flask-healthcheck-demo "python app.py" 2 minutes ago Up 2 minutes (unhealthy) 0.0.0.0:4000->80/tcp pensive_banach
3. הפעלה אוטומטית של קונטיינר אחרי שזיהינו בעיה
מבחינת דוקר לסמן את הקונטיינר בתור Unhealthy זה הכי רחוק שהוא מוכן ללכת. עכשיו הכדור עובר לידיים שלכם כדי לזהות איזה קונטיינרים שלכם אינם בריאים ולהחליט מה לעשות איתם. הרבה פעמים מה שנרצה לעשות עם קונטיינרים תקועים זה Restart - כלומר לסגור את הקונטיינר ולהפעיל מחדש. כלי בשם autoheal עוזר לנו לעשות בדיוק את זה.
אוטוהיל רץ בתוך קונטיינר משלו ומזהה קונטיינרים אחרים שנשברו על אותה מכונה. בשביל להריץ אותו נשתמש בפקודה:
docker run -d \
--name autoheal \
--restart=always \
-e AUTOHEAL_CONTAINER_LABEL=all \
-v /var/run/docker.sock:/var/run/docker.sock \
willfarrell/autoheal
אחרי שהרצתם אפשר להריץ את השרת המקורי שלנו שוב:
$ docker run -p 4000:80 ynonp/flask-healthcheck-demo
ולשבור אותו עם גלישה ל URL הבעייתי. נראה עכשיו את הסטטוס:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ec89e41484c3 ynonp/flask-healthcheck-demo "python app.py" 49 seconds ago Up 48 seconds (healthy) 0.0.0.0:4000->80/tcp nervous_gagarin
22e243bba079 willfarrell/autoheal "/docker-entrypoint …" 2 minutes ago Up 2 minutes (healthy) autoheal
בהתחלה ה HEALTHCHECK עדיין לא זיהה את הבעיה כי הגדרנו שאנחנו מוכנים לסבול עד 3 כישלונות. אבל כשהכישלונות נמשכים אנחנו רואים תופעה מעניינת - הקונטיינר שלנו נסגר ונפתח שוב:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ec89e41484c3 ynonp/flask-healthcheck-demo "python app.py" 2 minutes ago Up 13 seconds (health: starting) 0.0.0.0:4000->80/tcp nervous_gagarin
22e243bba079 willfarrell/autoheal "/docker-entrypoint …" 3 minutes ago Up 3 minutes (healthy) autoheal
ועכשיו אפשר שוב לגלוש לדף הראשי ולקבל את ההתנהגות הנכונה.