כיצד להגדיר את uWSGI ואת Nginx כדי לשרת אפליקציות Python על Ubuntu 14.04

הקדמה

במדריך זה, נכין יישום WSGI פשוט המורץ על ידי uWSGI. נשתמש בשרת האינטרנט Nginx כפרוקסי הפוך לשרת היישום כדי לספק טיפול חיבה ואמין יותר בחיבורים. נתקין ונגדיר את הרכיבים הללו על שרת Ubuntu 14.04.

הגדרות ועקרונות

הסבר של מונחים

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

  • WSGI: תקן Python המגדיר ממשק סטנדרטי לתקשורת בין יישום או תשתית ובין שרת יישום/אינטרנט. נוצר על מנת לפשט ולתקן את התקשורת בין רכיבים אלה לצורך עקביות והחלפתיות. יישום זה מגדיר בסיס API שניתן להשתמש בו על ידי פרוטוקולים אחרים.
  • uWSGI: שרת אפליקציות המטרתו לספק מערכת מלאה לפיתוח והפצה של אפליקציות ושירותים אינטרנטיים. הרכיב העיקרי הוא שרת אפליקציות המטפל ביישומים בשפות שונות. הוא תוקף עם היישום באמצעות השיטות המוגדרות על ידי מפרט WSGI, ועם שרתי אינטרנט אחרים באמצעות מגוון של פרוטוקולים אחרים. זהו החלק שמתרגם בקשות משרת אינטרנט רגיל לפורמט שהיישום יכול לעבד.
  • uwsgi: פרוטוקול בינארי מהיר שמיושם על ידי שרת uWSGI לתקשורת עם שרת אינטרנט עוצמתי יותר. זהו פרוטוקול חוט, ולא פרוטוקול העברת נתונים. זו הדרך המועדפת לתקשורת עם שרתי אינטרנט שמפנים בקשות ל-uWSGI.

דרישות היישום WSGI

המפרט WSGI מגדיר את הממשק בין שרת האינטרנט והיישום במערכת. בהקשר זה, "שרת האינטרנט" מתייחס לשרת uWSGI, שאחראי לתרגם את בקשות הלקוח אל היישום באמצעות מפרט WSGI. זה מפשט את התקשורת ויוצר מרכיבים שנרכשים בצורה מרוחקת כך שניתן להחליף בקלות את אחד מהם בלי רבות כאבי ראש.

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

בתגובה, היישום מחזיר iterable שישמש ליצירת גוף תגובת הלקוח. יש גם קריאה ל-"callable" של רכיב שרת האינטרנט שקיבל כפרמטר. הפרמטר הראשון בעת הפעלת ה-"callable" של שרת האינטרנט יהיה קוד מצב HTTP והשני יהיה רשימת זוגות, כאשר כל אחד מגדיר כותרת תגובה וערך לשליחה חזרה ללקוח.

עם רכיב ה-"web server" של האינטראקציה הזו המסופק על ידי uWSGI במקרה זה, נצטרך רק לוודא שליישומים שלנו יש את התכונות שתוארו לעיל. נקבע גם את Nginx כדי לטפל בבקשות האמיתיות של הלקוח ולהפנות אותן לשרת ה-uWSGI.

התקנת הרכיבים

כדי להתחיל, נצטרך להתקין את הרכיבים הנדרשים על השרת שלנו Ubuntu 14.04. נוכל לעשות זאת בעיקר באמצעות apt ו־pip.

ראשית, נרענן את אינדקס חבילות ה־apt ואז נתקין את ספריות הפיתוח והכותרות של פייתון, מנהל החבילות של פייתון (pip), ושרת האינטרנט Nginx ופרוקסי הפוך:

sudo apt-get update
sudo apt-get install python-dev python-pip nginx

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

sudo pip install virtualenv

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

התקן ספריית App ו-Virtualenv

נתחיל על ידי יצירת תיקייה עבור היישום שלנו. תיקייה זו יכולה להכיל תיקייה מקוננת שמכילה את קוד היישום הממשי ביישום מורכב יותר. לצורך הכרחנו, התיקייה הזו תחזיק בעיקר את סביבת הווירטואלית שלנו ואת נקודת הכניסה שלנו ל-WSGI:

mkdir ~/myapp/

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

cd ~/myapp

נצור סביבה וירטואלית עם הפקודה virtualenv. נקרא לזה myappenv לפשטות:

virtualenv myappenv

A new Python environment will be set up under a directory called myappenv. We can activate this environment by typing:

source myappenv/bin/activate

הפרומט שלך צריך להשתנות כדי לציין שאתה כעת פועל בתוך הסביבה הווירטואלית. זה יראה משהו כזה:

(myappenv)username@host:~/my_app$

אם ברצונך לצאת מהסביבה הזו בכל עת, תוכל פשוט להקליד:

deactivate

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

עם הסביבה הזו פעילה, כל חבילות Python שמותקנות ייכללו בשושלת הספריות הזו. הן לא יתערבו עם סביבת ה-Python של המערכת. עם זאת בדעת, כעת נוכל להתקין את שרת uWSGI לתוך הסביבה שלנו באמצעות pip. החבילה לכך נקראת uwsgi (עדיין זהו שרת uWSGI ולא הפרוטוקול uwsgi):

pip install uwsgi

ניתן לוודא שהוא זמין כעת על ידי הקלדת:

uwsgi --version

אם הוא מחזיר מספר גרסה, השרת uWSGI זמין לשימוש.

צור אפליקציית WSGI

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

  • חייב לספק ממשק דרך callable (פונקציה או מבנה שפה אחר הניתן לקרוא לו)
  • ה-callable חייב לקבל כפרמטרים מילון המכיל זוגות דמויי משתני סביבה ו-callable שזמין בשרת (uWSGI)
  • ה-callable של האפליקציה צריך להחזיר iterable שיצור את הגוף לשליחה ללקוח.
  • האפליקציה צריכה לקרוא ל-callable של השרת האינטרנטי עם הסטטוס HTTP וכותרות הבקשה.

נכתוב את האפליקציה שלנו בקובץ בשם wsgi.py בתיקיית האפליקציה שלנו:

nano ~/myapp/wsgi.py

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

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return ["<h1 style='color:blue'>Hello There!</h1>"]

הקוד לעיל מהווה יישום WSGI מלא. כברירת מחדל, uWSGI תחפש פונקציה נקראת application, ולכן קראנו לפונקציה שלנו application. כפי שאתה יכול לראות, היא מקבלת שני פרמטרים.

הראשון שקראנו לו environ משום שזהו מילון של זוגות מפתח-ערך דומים למשתנים סביבתיים. השני נקרא start_response והוא השם שהיישום ישתמש בו בתוך היישום כדי להתייחס לקריאה של השרת האינטרנטי (uWSGI) שנשלחה אליו. שני שמות אלו נבחרו פשוט עקב השימוש בהם בדוגמאות בתקן PEP 333 שמגדיר אינטרקציות WSGI.

היישום שלנו חייב לקבל את המידע הזה ולעשות שני דברים. לראשונה, עליו לקרוא לפונקציה שקיבל בקוד מקוד בעל קוד מצב HTTP וכל כותרות שהוא רוצה לשלוח בחזרה. במקרה זה, אנו שולחים תגובת "200 OK" ומגדירים את כותרת ה-Content-Type ל-text/html.

בנוסף, הוא צריך להחזיר איטרבל לשימוש כגוף התגובה. כאן, פשוט השתמשנו ברשימה המכילה מחרוזת יחידה של HTML. מחרוזות הן איטרבילים גם כן, אך בתוך רשימה, uWSGI יוכל לעבד את כל המחרוזת באיטרציה אחת.

בסצנריו אמיתי, קובץ זה כנראה יועשה שימוש כקישור לשאר קוד האפליקציה שלך. לדוגמה, פרויקטים של Django כוללים בקובץ wsgi.py כברירת מחדל שמתרגם בקשות מהשרת האינטרנט (uWSGI) לאפליקציה (Django). ממשק ה-WSGI הפשוט נשאר זהה ללא קשר לכמות הקוד האפליקטיבי הממושק. זהו אחד מהיתרונות של הממשק.

שמור וסגור את הקובץ כאשר תסיים.

כדי לבדוק את הקוד, ניתן להתחיל את uWSGI. נספר לו להשתמש ב-HTTP לזמן הקצר ולהאזין בפורט 8080. נעביר לו את שם התסריט (ללא סיומת):

uwsgi --socket 0.0.0.0:8080 --protocol=http -w wsgi

עכשיו, אם תבקרו בכתובת ה-IP של השרת שלך או בשם הדומיין שלו בדפדפן האינטרנט שלך ותוסיפו אחריו את :8080, תראו את הטקסט של הכותרת שלרמות הראשונות שהעברנו כגוף בקובץ ה-wsgi.py שלנו:

עצור את השרת עם CTRL-C כאשר תאשר שהכל פועל.

סיימנו לעיצוב את האפליקציה שלנו בשלב זה. תוכל להשבית את הסביבה הווירטואלית שלנו אם תרצה:

deactivate

הגדר קובץ הגדרות uWSGI

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

כדי להמשיך עם השמות שהשתמשנו בהם עד כה, נקרא לקובץ myapp.ini ונציב אותו בתיקיית היישום שלנו:

nano ~/myapp/myapp.ini

בתוך הקובץ, אנו צריכים להקים סעיף בשם [uwsgi]. בסעיף זה יש להכניס את כל פריטי התצורה שלנו. נתחיל על ידי זיהוי היישום שלנו. השרת uWSGI צריך לדעת איפה נמצא הקריאה של היישום. אנו יכולים לספק את שם הקובץ והפונקציה בו:

[uwsgi]
module = wsgi:application

אנו רוצים לסמן את התהליך הראשוני של uwsgi כמאסטר ולאחר מכן להפעיל מספר תהליכי עובד. נתחיל עם חמישה עובדים:

[uwsgi]
module = wsgi:application

master = true
processes = 5

אנו מעוניינים לשנות את הפרוטוקול שבו ה-SERVER של uWSGI מדבר עם העולם החיצוני. כאשר ביצענו בדיקת היישום שלנו, ציינו --protocol=http כך שנוכל לראות אותו מדפדפן אינטרנט. מאחר שנקבע להגדיר את Nginx כפרוקסי הפוך מול uWSGI, אנו יכולים לשנות זאת. Nginx מיישמת מנגנון פרוקסי uwsgi, שהוא פרוטוקול בינארי מהיר ש-uWSGI יכול להשתמש בו כדי לדבר עם שרתים אחרים. הפרוטוקול uwsgi הוא בעצם הפרוטוקול המוגדר כברירת מחדל של uWSGI, ולכן רק על ידי הורדת ציון הפרוטוקול, הוא יחזור אוטומטית להשתמש בפרוטוקול uwsgi.

מכיוון שאנו מעצבים את התצורה הזו לשימוש עם Nginx, נשנה גם משימוש ביציאת רשת ונשתמש בקפיצת Unix במקום. זה יותר מאובטח ומהיר יותר. הקפיצה תיווצר בתיקייה הנוכחית אם נשתמש בנתיב יחסי. נקרא לזה myapp.sock. נשנה את ההרשאות ל־"664" כך ש־Nginx תוכל לכתוב לו (נפעיל את uWSGI עם קבוצת www-data שמשתמש בה Nginx). נוסיף גם את האפשרות vacuum, שתסיר את הקפיצה כאשר התהליך מפסיק:

[uwsgi]
module = wsgi:application

master = true
processes = 5

socket = myapp.sock
chmod-socket = 664
vacuum = true

אנו זקוקים לאפשרות אחרונה מאחר ונקריא קובץ Upstart כדי להתחיל את היישום שלנו בעת האתחול. Upstart ו־uWSGI מתחזים שונה ממה צריך לעשות האות SIGTERM ליישום. כדי לפתור את השוני הזה כדי שהתהליכים יטופלו כצפוי עם Upstart, אנו פשוט צריכים להוסיף אפשרות שנקראת die-on-term כך ש־uWSGI יהרוג את התהליך במקום לטעון אותו:

[uwsgi]
module = wsgi:application

master = true
processes = 5

socket = myapp.sock
chmod-socket = 664
vacuum = true

die-on-term = true

שמור וסגור את הקובץ כאשר תסיים. קובץ התצורה הזה מוגדר כעת לשימוש עם קובץ Upstart.

צור קובץ Upstart כדי לנהל את היישום

אנו יכולים להפעיל מופע uWSGI בעת האתחול כך שהיישום שלנו יהיה תמיד זמין. נציב זאת בתיקיית /etc/init שבה Upstart בודקת. נקרא לזה myapp.conf:

sudo nano /etc/init/myapp.conf

ראשית, נתחיל עם תיאור של השירות ונבחר את רמות הריצה שבהן הוא צריך להפעיל אוטומטית. רמות הריצה הרגילות של המשתמש הן 2 עד 5. נספר ל-Upstart לעצור את השירות כאשר המערכת פועלת ברמות הריצה שאינן חלק מהקבוצה הזו (כמו בעת האתחלת המערכת או במצב משתמש בודד):

description "uWSGI instance to serve myapp"

start on runlevel [2345]
stop on runlevel [!2345]

בשלב הבא, נספר ל-Upstart על המשתמש והקבוצה שבה התהליך צריך לרוץ. אנו רוצים להפעיל את היישום תחת חשבון המשתמש שלנו (אנו משתמשים ב־demo במדריך זה, אך עליך להחליף את המשתמש שלך). אנו רוצים להגדיר את הקבוצה למשתמש www-data שמשתמש בו Nginx. זה נחוץ מאחר ושרת האינטרנט צריך להיות מסוגל לקרוא ולכתוב לקצה השמש של ה־.ini שאנו נוצר:

description "uWSGI instance to serve myapp"

start on runlevel [2345]
stop on runlevel [!2345]

setuid demo
setgid www-data

בשלב הבא, נריץ את הפקודות הממשיות להפעלת uWSGI. מאחר שהתקנו את uWSGI בסביבה וירטואלית, יש לנו עבודה נוספת לעשות. נוכל פשוט לספק את הנתיב המלא לביצוע ה-nקוד, אך במקום זה, נפעיל את הסביבה הווירטואלית. זה יהפוך את זה קל יותר אם נסתמך על תוכנה נוספת המותקנת בסביבה:

כדי לעשות זאת, נשתמש בבלוק script. בתוך הבלוק, נשנה את הספרייה של היישום שלנו, נפעיל את הסביבה הווירטואלית (חובה להשתמש ב־. בתוך התסריטים במקום source), ונפעיל את המופע של uWSGI שמצביע ל־.ini שלנו:

description "uWSGI instance to serve myapp"

start on runlevel [2345]
stop on runlevel [!2345]

setuid demo
setgid www-data

script
    cd /home/demo/myapp
    . myappenv/bin/activate
    uwsgi --ini myapp.ini
end script

עם זאת, סקריפט Upstart שלנו הושלם. שמור וסגור את הקובץ כאשר תסיים:

עכשיו, נוכל להתחיל את השירות על ידי הקלדת:

sudo start myapp

נוכל לוודא שהוא הופעל על ידי הקלדה:

ps aux | grep myapp
demo   14618  0.0  0.5  35868  5996 ?        S    15:02   0:00 uwsgi --ini myapp.ini
demo   14619  0.0  0.5  42680  5532 ?        S    15:02   0:00 uwsgi --ini myapp.ini
demo   14620  0.0  0.5  42680  5532 ?        S    15:02   0:00 uwsgi --ini myapp.ini
demo   14621  0.0  0.5  42680  5532 ?        S    15:02   0:00 uwsgi --ini myapp.ini
demo   14622  0.0  0.5  42680  5532 ?        S    15:02   0:00 uwsgi --ini myapp.ini
demo   14623  0.0  0.5  42680  5532 ?        S    15:02   0:00 uwsgi --ini myapp.ini
demo   15520  0.0  0.0  11740   936 pts/0    S+   15:53   0:00 grep --color=auto myapp

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

sudo stop myapp

הגדרת Nginx כפרוקסי ל־uWSGI

בנקודה זו, יש לנו אפליקציית WSGI ואימתנו כי uWSGI יכול לקרוא ולספק אותה. יצרנו קובץ הגדרות וסקריפט Upstart. התהליך שלנו ישמע על קובץ תקשורת ויתקשר באמצעות פרוטוקול uwsgi.

כעת אנחנו בנקודה שבה אנו יכולים לעבוד על הגדרת Nginx כפרוקסי הפוך. Nginx מסוגל לפרוקסי באמצעות פרוטוקול uwsgi לתקשורת עם uWSGI. זהו פרוטוקול מהיר יותר מ־HTTP וישפר את הביצועים.

ההגדרה של Nginx שנקבעת תהיה קלה מאוד. יש ליצור קובץ חדש בתוך הספרייה sites-available במבנה ההגדרות של Nginx. נקרא לקובץ שלנו myapp כדי להתאים לשם האפליקציה שבה אנו משתמשים:

sudo nano /etc/nginx/sites-available/myapp

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

server {
    listen 80;
    server_name server_domain_or_IP;
}

מכיוון שאנו רוצים לשלוח את כל הבקשות בדומיין או כתובת ה-IP הזו ליישום ה-WSGI שלנו, ניצור בלוק מיקום יחיד לבקשות שמתחילות עם /, שצריך להתאים להכול. בתוך זה, נשתמש בהוראת include כדי לכלול מספר פרמטרים עם ברירות מחדל סבירות מקובץ בספריית התצורה של Nginx. הקובץ המכיל את אלו נקרא uwsgi_params. לאחר מכן, נעביר את התעבורה להפעלת ה- uWSGI שלנו על פרוטוקול ה- uwsgi. נשתמש בסוקט ה- unix שהגדרנו מראש:

server {
    listen 80;
    server_name server_domain_or_IP;

    location / {
        include         uwsgi_params;
        uwsgi_pass      unix:/home/demo/myapp/myapp.sock;
    }
}

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

אנו לא זקוקים לאף אחת מהתכונות האלו ביישום שלנו בשלושת השורות, אז נוכל לשמור ולסגור את הקובץ.

הפעל את תצורת השרת שיצרנו בלינק לתיקיית sites-enabled:

sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled

בדוק את קובץ התצורה לשגיאות תחביר:

sudo service nginx configtest

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

sudo service nginx restart

לאחר האתחול מחדש של Nginx, אתה צריך להיות מסוגל לעבור לשם הדומיין או לכתובת ה- IP של השרת שלך (בלי מספר פורט) ולראות את היישום שהגדרת:

סיכום

אם הגעת עד כאן, יצרת אפליקציה פשוטה בסגנון WSGI ויש לך מבט על איך אפליקציות מורכבות יותר יהיו צריכות להיות מעוצבות. התקנו את מעטפת/שרת האפליקציות uWSGI לתוך סביבת וירטואלית שנוצרה במיוחד כדי לשרת את האפליקציה שלנו. יצרנו קובץ הגדרות וסקריפט Upstart כדי לאוטומטזציה של תהליך זה. מול שרת ה uWSGI, הגדרנו פרוקסי חזרה של Nginx שיכול לדבר עם תהליך ה uWSGI באמצעות פרוטוקול החוט uwsgi.

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

Source:
https://www.digitalocean.com/community/tutorials/how-to-set-up-uwsgi-and-nginx-to-serve-python-apps-on-ubuntu-14-04