• בלוג
  • זיהוי נפילות באפליקציה בתוך Docker

זיהוי נפילות באפליקציה בתוך Docker

28/04/2019

פוסט זה כולל טיפ קצר על 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

ועכשיו אפשר שוב לגלוש לדף הראשי ולקבל את ההתנהגות הנכונה.