מעבדות GCP – מעבדה חמישית

מעבדות GCP – מעבדה חמישית

הנגשת אפליקציה דרך סביבות שונות

הקדמה:

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

סקירת המעבדה הזו תהווה הקדמה למעבדות הבאות בסדרה, שתהיינה מורכבות יותר.

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

1.   הנגשת קוד של פייתון כאפליקצייה דרך הדפדפן.

2.   Organization Policy מותנה על פי תג.

3.   אתר סטטי שפועל מתוך דלי אחסון.

4.   הנגשת אותו אתר דרך שרת nginx.

על הפרק:

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

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

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

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

לצורך כך כתבתי בפייתון משחק מחשב פשוט, מתוך הזכרון שלי על משחקי "שובר הלבנים" הישנים שהיו רצים על סביבת DOS.

המשחק בסיסי ביותר ללא שלבים או תפריט, ומכיל כמה באגים קטנים. אבל זה לא משנה ואין טעם להשקיע מעבר לזה. הוא פועל ומשמש מצוין את תכליתו כ-POC‏ – Prove of Concept.

משחק שובר בלוקים

הרצת משחק פייתון:

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

–        הקובץ BreakBricks.py מכיל את המשחק כפי שעובד במחשב שבו מותקן פייתון עם המודול pygame.

–        התיקיה works מכילה את הקובץ main.py שהוא הגרסה המתקדמת יותר לאחר העיבוד והתאמה לעבודה א-סינכרונית.

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

התקנת רכיבי פייתון:

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

עם תום ההתקנה, לפתוח את התוכנה ולגשת ל-Python Packages

Python Packages

2.      ומשם להתקין את החבילות הבאות: pip, pygame, pygbag

מי שלא רוצה להתקין PyCharm והתקין ישירות פייתון, יוכל לפתוח שורת פקודה ומשם להריץ את הפקודות:

python -m pip install 
pip install pygame 
pip install pygbag 
  • התקנה ידנית יכולה לדרוש עיסוק נוסף בהוספת משתנים סביבתיים.

3.      יצירת תיקיה ובתוכה הקובץ BreakBricks.py. אל תוך הקובץ מעתיקים את הקוד מנספח א' שבסוף המעבדה.

4.      אם משתמשים ב-PyCharm אפשר משם להריץ את הקוד. אם לא, מתוך שורת הפקודה:

 python path\BreakBricks.py 

אז יפתח חלון והמשחק ירוץ בתוכו.

הטמעת תכנית פייתון בדף Web:

ניקח את הקוד מנספח א' ונשנה אותו מעט:

1.      שם הקובץ הראשי של התכנית חייב להיות main.py

2.      לראש הקוד בקובץ main.py צריך להוסיף יבוא של מודול asyncio.

3.      להוסיף פונקציית main של async בפקודה async def main()

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

5.      בסוף הפונקציה, בתחתית ריצת הלולאה – לאחר השורה שבה נקבע כמות תקתוקי השעון (מהירות ריצת התכנית), להוסיף את הפקודה await asyncio.sleep(0) שאומרת לתכנית לבצע המתנה של 0 פריימים עד להמשך.

6.      בסוף לקבוע קריאה לפונקציית main בפקודה asyncio.run(main())

7.      להסיר את הפקודה של pygame.quite(). שום דבר לא יכול להיות אחרי השורה שקוראת לפונקציית main.

8.      לפתוח את שורת הפקודה או את הטרמינל ב-PyCharm, לעבור לתיקיה שבה יושב קובץ main.py באמצעות פקודת cd path.

9.      להריץ את הקובץ ולוודא שהמשחק עדיין פועל תקין אחרי כל העריכות.

10.  להריץ את הפקודה pygbag main.py. כעת החבילה pygbag תאסוף את כל הרכיבים הנדרשים להרצת התכנית, ותבנה מהם חבילה שרצה בסביבת דפדפן. לבסוף יפתח פורט 8000 להאזין לקריאות web, שישויכו ל"אתר" שכרגע נוצר.

11.  בפתיחת לשונית דפדפן וגלישה לכתובת: http://localhost:8000

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

מי שיסגור את השירות וירצה לפתוח שוב האזנה מקומית לשירות ה-Web, ישתמש בפקודה:

python -m http.server 
  • כדאי לבצע את הפקודה בתיקיה שבה נמצא קובץ ה-index שנוצר. כך גלישה לכתובת הנ"ל תפתח ישר את הדף.
בחינת שינויים:

כעת נראה שנוצרה בתיקיה בה יושב קובץ main.py, תיקיה בשם build. שם בתוך תיקיית web יושבים קבצי ההרצה. נשתמש בהם בהמשך.

בנוסף, ניתן להבחין שהריצה של המשחק דרך הדפדפן הרבה יותר איטית מריצה ישירה שלו דרך פייתון. דרך הדפדפן כמות החלפת המסגרות שנקבעת בשורה 214 (clock.tick) לא ממש משנה. לכן כדאי לערוך את המשחק.

נעבור למתודות update של הכדור ושל המגש בתחתית, כדי שיזוזו 5 פיקסלים בכל פעם, במקום 1.

שורות 99-100 מכילות את מתודת update של המגש בתחתית. כעת מוגדר בקוד להזיז את המגש 5 פיקסלים בכל פעם. נשנה ל-10.

תזוזת המגש

שורות 132-139 קובעות את תזוזת הכדור. אם כעת מוגדר לו לזוז נקודה בכל פעם, נשנה ל-5.

תזוזת הכדור

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

הגשת המשחק מתוך דלי אחסון:

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

לצורך כך צריך להגדיר דלי אחסון ובו הרשאת גישה ציבורית – כל אחד יכול לקרוא את התוכן מתוך הדלי.

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

למשל – בסביבת organization ניתן לאכוף Organization Policy שאינו מאפשר פרסום ציבורי של דלי. אז כדי להחריג דליים ספציפיים מן האכיפה, נוכל להגדיר תנאי ב-Policy שיבטל אכיפה אם הדלי נושא תג מסוים.

Tags:

הפעלת API של cloudresourcemanager:

1.      בתפריט הקונסול לבחור API & SERVICES.

2.      שם ללחוץ למעלה על ENABLE APIS AND SERVICES.

3.      בדף שיפתח, לשים בשורת החיפוש את הביטוי cloudresourcemanager

4.      ללחוץ על התוצאה – Cloud Resource Manager API.

5.      בדף שיפתח, ללחוץ ENABLE.

יצירת תג:

1.      IAM & Admin >> Tags >> Create

2.      בשדה Tag Key, לשים Public-Bucket.

3.      למטה יש כותר Tag values ומתחתיו לחצן ADD VALUE – ללחוץ.

4.      בשדה Tag value 1 שמופיע, לשים את המילה Public.

5.      ללחוץ למטה CREATE TAG KEY.

6.      להכנס לתג, להכנס לערך שלו, בדף של הערך יופיע Tag value path. להעתיק ולשמור בפנקס רשימות. נשתמש בהמשך.

Organization Policy:

את זה ניתן לעשות רק בסביבת ארגון. מי שמתרגל מעבדה תחת חשבון פרטי בתקופת ניסיון חינמית, לא תהיה לו גישה לאפשרות הזו.

ב-GCP Console:

1.      IAM & Admin >> Organization Policies

2.      בתפריט הפרוייקטים למעלה, לפתוח ולבחור את הארגון עצמו

3.      נראה מיד רשימה של Policies שאפשר להגדיר כדי לאכוף הגדרות ונהלים בצורה כללית – ברמת הפרוייקט, ברמת תיקיה או ברמת הארגון כולו. בראש הרשימה יש שורת חיפוש, נכתוב שם Public ותקפוץ לנו האפשרות של constraints/storage.publicAccessPrevention. להכנס לשם.

4.      ללחוץ למעלה על MANAGE POLICY.

5.      בראש העמוד תופיע פסקה שמסבירה מה עושה ה-Policy הזה, ואחריו הגדרת Applies to. לסמן – Customize.

6.      כעת ניתן ליצור חוקי אכיפה ותנאים. בחוק הראשון לסמן Enforced.

7.      ללחוץ על ADD RULE כדי לפתוח חוק ובו תנאי, שכן לא ניתן לשים רק תנאי בלבד. חובה לשים חוק אחד שקובע אכיפה/אי-אכיפה, ואז חוקים שקובעים שהאפשרות הזו לא תקרה על פי תנאים מסוימים.

8.      לאחר לחיצה על ADD RULE יפתח כרטיס של החוק. לבחור Enforcement – off.

9.      ללחוץ ADD CONDITION.

10.  לתת שם, לבחור בתנאי מסוג תג, Operator של Has value, ובערך להדביק את הנתיב לערך של התג, ששמנו קודם בפנקס רשימות.

11.  ללחוץ SAVE.

12.  ללחוץ SET POLICY.

יצירת דלי פומבי:

יצירת דלי פומבי:

נלך ל-Google Cloud Console ושם ניצור Storage Bucket. כרגע אני מסמן בכוונה Enforce public access prevention.

מעט על יצירת דלי אחסון ניתן לקרוא במאמר הקודם שלי – מעבדה שלישית, אחסון והרשאות.

מי שיוצר את הדלי בסביבת Organization יוכל להוסיף את התרגול הבא:

1.      לוודא אכיפה של ה-Policy שמונע הגדרת דלי ציבורי.

2.      כניסה לדלי ב-Console ושם לפנות ללשונית CONFIGURATION.

3.      ללחוץ על העפרון בשורה של Tags.

4.      ואז בחלון שקופץ בצד, תחת Direct tags, ללחוץ SELECT SCOPE.

5.      מי שיצר תג ברמת הארגון, לבחור Scope ברמת הארגון.

6.      לחיצה על השדה Key 1 תפתח תפריט, שם נבחר את התג שיצרנו – Public-Bucket.

7.      לחיצה על השדה Value 1 תפתח תפריט, שם נבחר את הערך שיצרנו – Public.

8.      ללחוץ SAVE ולאשר.

חשוב!

חובה לפתוח את ה-API של Cloud Rresource Manager כדי לאפשר הצמדה של תג לדלי!

נוכל לבצע את אותה הפעולה באמצעות הפקודות הבאות:
gcloud services enable cloudresourcemanager.googleapis.com --project mobile-app-yosi

gcloud resource-manager tags bindings create `
--tag-value mobile-app-yosi/Public-Bucket/Public `
--parent //storage.googleapis.com/projects/_/buckets/mobile-bricksbrake `
--location me-west1
הסבר:

הפקודה הראשונה מאפשרת את ה-API של Resource Manager והשניה מצמידה את התגית.

צריך להכנס לתגית, ואז להכנס לערך שבתוך התג, ולקחת משם את Tag value path. זה מה שמכניסים ב-Flag של Tag Value פה בפקודה.

בשדה של parent צריך רק לשנות את השם של הדלי בחלק האחרון.

ביטול Public Access Prevention ברמת הדלי:

עכשיו אפשר לבטל את האכיפה של Public Access Prevention שיש על הדלי באמצעות הפקודה הבאה:

gcloud storage buckets update gs://mobile-bricksbrake --no-public-access-prevention
מתן הרשאה ציבורית על הדלי:

1.      בתוך הדלי הולכים ללשונית PERMISSIONS.

2.      לרדת קצת למטה, ובראש רשימת ההרשאות ללחוץ על GRANT ACCESS.

3.      בחלון שנפתח, בשדה New principal, לכתוב – allUsers.

4.      בשדה Select a role ללחוץ, בחלון שנפתח לכתוב בחיפוש Storage Object Viewer.

5.      לבחור בתוצאה.

6.      ללחוץ SAVE.

7.      בחלון שקופץ, ללחוץ ALLOW PUBLIC ACCESS.

כעת נותר להעלות לדלי את כל התוכן של התיקיה build.

התחברות לאפליקציה:

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

https://<bucket-name>.storage.googleapis.com/web/index.html

או במקרה שלי:

https://mobile-bricksbrake.storage.googleapis.com/web/index.html

העלאת האפליקציה לשרת NginX:

בהתאם למה שהכרנו במעבדה מס' 1, נפעיל את ה-API של Compute Engine ונקים שרת Ubuntu בשם brake-bricks, ובהליך היצירה נסמן לו Allow http וגם https. נאפשר לו כתובת IP חיצונית כדי לפשט את כל השאר.

כשהמכונה חיה, נתחבר אליה באמצעות לחצן SSH המובנה בקונסול של GCP.

כעת נהפוך את המכונה לשרת שמגיש את האפליקציה כשירות Web:

1.      בחלון SSH למעלה נלחץ Upload Files ונעלה את שלושת הקבצים של האפליקציה לשרת.

2.      נעבור למשתמש root באמצעות הפקודה sudo -i.

3.      נתקין שרת nginx באמצעות הפקודה – apt install nginx -y.

4.      נוודא גלישה לשרת ה-Web באמצעות לשונית בדפדפן שבה הכתובת: http://external-ip – כאשר external-ip מתייחס לכתובת החיצונית שהמכונה קיבלה עם היצירה שלה.

5.      נעבור לתיקיה של שרת ה-Web: cd /var/www/html/

6.      נעתיק את כל הקבצים מן הדלי אל אותה התיקיה:  gsutil cp -r gs://mobile-bricksbrake/* ./

7.      כעת ניגש להגדרות של nginx – nano /etc/nginx/sites-available/default

נרד קצת למטה עד שנראה את השורות האלה:
הגדרות Nginx

1.      את השורה שמתחילה ב-root צריך לערוך. שם מוגדרת תיקיית הבית של קובץ האינדקס של nginx. לשנות ל-  /var/www/html/web

2.      כעת נרענן את הדף של ה-nginx שגלשנו אליו קודם עם הכתובת החיצונית, והמשחק נטען.

נספח א':

import pygame, pygame.gfxdraw, random

from pygame.locals import (
    RLEACCEL,
    K_UP,
    K_DOWN,
    K_RIGHT,
    K_LEFT,
    KEYDOWN,
    K_ESCAPE,
    QUIT
)

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600

# ===============  Create Bricks  ===============

def create_bricks():

    bricks = pygame.sprite.Group()

    for x in range(10):
        if (x % 2) == 1:
            length = 0
        else:
            length = -20
        for y in range(21):
            height = 100 + (x * 20)
            length_position = length + (y * 40)
            br = Bricks()
            br.rect.left = length_position
            br.rect.top = height
            bricks.add(br)

    return  bricks

# ===============  Paint Bricks  ===============

def random_color():
    accepted = False
    while not accepted:
        a = random.randrange(0,255)
        b = random.randrange(0,255)
        c = random.randrange(0,255)

        if ((a > 20) or (b > 20) or (c > 20)):
            color = (a, b, c)
            accepted = True

    return color


# ===============  Change direction  ===============
def change_direction(direction, collision):
    if direction == 'up-right' and collision == 'right':
        direction = 'up-left'
    if direction == 'down-right' and collision == 'right':
        direction = 'down-left'
    if direction == 'up-left' and collision == 'left':
        direction = 'up-right'
    if direction == 'down-left' and collision == 'left':
        direction = 'down-right'
    if direction == 'up-right' and collision == 'top':
        direction = 'down-right'
    if direction == 'up-left' and collision == 'top':
        direction = 'down-left'
    if direction == 'down-right' and collision == 'bottom':
        direction = 'up-right'
    if direction == 'down-left' and collision == 'bottom':
        direction = 'up-left'

    return direction

# ===============  Check bricks collision  ===============

def bricks_collision(ball, brick):
    if ((ball.rect.top + 1) or (ball.rect.top) or (ball.rect.top - 1)) == brick.rect.bottom:
        colision = 'top'
    elif ((ball.rect.bottom + 1) or (ball.rect.bottom) or (ball.rect.bottom - 1)) == brick.rect.top:
        colision = 'bottom'
    elif ((ball.rect.right + 1) or (ball.rect.right) or (ball.rect.right - 1)) == brick.rect.left:
        colision = 'right'
    elif ((ball.rect.left + 1) or (ball.rect.left) or (ball.rect.left - 1)) == brick.rect.right:
        colision = 'left'
    else:
        colision = 'none'

    return colision


# ===============  Player  ===============
class Player(pygame.sprite.Sprite):
    def __init__(self):
        super(Player, self).__init__()
        self.surf = pygame.Surface((75, 10))
        self.surf.fill((255, 255, 255))
        self.rect = self.surf.get_rect(center = ((SCREEN_WIDTH / 2), 585))

    def update(self, pressed_keys):
        if pressed_keys[K_LEFT]:
            self.rect.move_ip(-5, 0)
        if pressed_keys[K_RIGHT]:
            self.rect.move_ip(5, 0)

        if self.rect.right > SCREEN_WIDTH:
            self.rect.right = SCREEN_WIDTH
        if self.rect.left < 0:
            self.rect.left = 0

# ===============  Ball  ===============

class Ball(pygame.sprite.Sprite):
    def __init__(self):
         super(Ball, self).__init__()
         circle = pygame.Surface((32, 32), pygame.SRCALPHA)
         pygame.draw.circle(circle, (255, 255, 255), (16, 16), 8, 16)
         self.surf = circle
         self.rect = self.surf.get_rect(center=(random.randrange(5, 790), 550))

    def update(self, direction):

        if self.rect.right > SCREEN_WIDTH:
            direction = change_direction(direction, 'right')

        if self.rect.left < 0:
            direction = change_direction(direction, 'left')

        if self.rect.top < 0:
            direction = change_direction(direction, 'top')

        if self.rect.top > (SCREEN_HEIGHT + 5):
            self.kill()


        if direction == 'up-right':
            self.rect.move_ip(1, -1)
        if direction == 'up-left':
            self.rect.move_ip(-1, -1)
        if direction == 'down-right':
            self.rect.move_ip(1, 1)
        if direction == 'down-left':
            self.rect.move_ip(-1, 1)

        return direction

# ===============  Bricks  ===============
class Bricks(pygame.sprite.Sprite):
    def __init__(self):
        super(Bricks, self).__init__()
        color = random_color()
        self.surf = pygame.Surface((40, 20))
        self.surf.fill(color)
        self.rect = self.surf.get_rect()
        pygame.draw.rect(self.surf, (128,128,128), (0, 0, 40, 20), 2)

    def update(self):
        if self.surf.get_at((5, 5))[:3] != (0,0,0):
            color = self.surf.get_at((5, 5))[:3]
            self.surf.fill((0, 0, 0))
            pygame.draw.rect(self.surf, color, (0, 0, 40, 20), 2)
        else:
            self.kill()

# ===============  Run the game  ===============

pygame.init()
screen = pygame.display.set_mode([SCREEN_WIDTH, SCREEN_HEIGHT])
pygame.display.set_caption("BreakBricks")
bricks = create_bricks()
all_sprites = pygame.sprite.Group()
ball = Ball()
player = Player()

players = pygame.sprite.Group(player)
all_sprites.add(ball, player)

for x in bricks:
    all_sprites.add(x)

clock = pygame.time.Clock()
running = True
direction = 'up-right'

while running:
    for x in pygame.event.get():
        if x.type == KEYDOWN:

            if x.key == K_ESCAPE:
                running = False


        elif x.type == QUIT:
            running = False

    if pygame.sprite.spritecollideany(ball, players):
        direction = change_direction(direction, 'bottom')

    brick_col = pygame.sprite.spritecollide(ball, bricks, False)
    if brick_col:

        collision = bricks_collision(ball, brick_col[0])
        if collision != 'none':
            brick_col[0].update()
            direction = change_direction(direction, collision)

    pressed_keys = pygame.key.get_pressed()
    player.update(pressed_keys)
    direction = ball.update(direction)
    screen.fill((0,0,0))

    for spr in all_sprites:
        screen.blit(spr.surf, spr.rect)

    pygame.display.flip()
    clock.tick(200)

pygame.quit()

כתיבת תגובה