הטמעה לדוגמה של מסד נתונים של PostgreSQL ב-GDC עם air gap

במדריך הזה מוסבר איך פורסים מחסנית PostgreSQL עם זמינות גבוהה בשלושה אזורים בסביבת Air-gapped של Google Distributed Cloud‏ (GDC). תלמדו איך להכין את ארטיפקטים התוכנה הדרושים, להפעיל את מכונות ה-VM של היעד ולהשתמש ב-Autobase כדי לבצע אוטומציה של כל תהליך ההקצאה. במדריך הזה, Patroni משמש כשכבת הניהול הראשית לתיאום מחזור החיים של PostgreSQL ולטיפול במעברים אוטומטיים לשירות גיבוי בעת כשל.

ארכיטקטורה

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

ארכיטקטורה של שלוש מכונות וירטואליות שמריצה מחסנית של שירותים שמוקמו יחד.

כל מכונה וירטואלית זהה ומריצה מחסנית של שירותים שמוצבים יחד:

  • ‫PostgreSQL 17: המנוע של מסד הנתונים הרלציוני המרכזי.
  • ‫Patroni: הכלי לניהול זמינות גבוהה. הוא מטפל במחזור החיים של תהליך PostgreSQL ומבצע מעבר אוטומטי לגיבוי (failover). הוא חושף REST API של HTTPS ביציאה 8008 (נקודת קצה /primary) שמאזן העומסים משתמש בו כדי לזהות את הליבה הנוכחית.
  • etcd: מאגר ההגדרות המבוזר (DCS). הוא מספק את שכבת הקונצנזוס לבחירת מנהיג ומאחסן את ההגדרה של Patroni.
  • ‫PgBouncer: מאגר חיבורים שמוצב לפני PostgreSQL כדי לייצב את תקורה החיבור. הוא מספק את נקודת הכניסה המומלצת לתנועת נתונים של אפליקציות ביציאה 6432.

הסטאק כולל גם מאזן עומסים גלובלי ברמה 4 (L4) של GDC עם air gap, שהוא שירות מנוהל בפלטפורמה שמספק כתובת IP וירטואלית (VIP) יציבה. האפליקציות מתחברות ל-VIP היציב ביציאה 6432, שמאזן העומסים מנתב ל-PgBouncer במכונת ה-VM הראשית הנוכחית. לאחר מכן, PgBouncer מעביר את הבקשה למופע המקומי של PostgreSQL. כדי לנהל את זרימת התנועה, מאזן העומסים מבצע בדיקות תקינות של נקודות הקצה של Patroni HTTPS באופן רציף.

בדיקת התקינות במכונת ה-VM הראשית מחזירה HTTP 200 OK כדי לציין שהמכונה מוכנה לקבל תנועה, ובדיקות התקינות במכונות ה-VM המשוכפלות מחזירות HTTP 503 Service Unavailable כדי לציין למאזן העומסים לדלג עליהן. אם הליבה נכשלת, נבחרת ליבה חדשה ומופע Patroni שלה מתחיל להחזיר HTTP 200 OK, וכתוצאה מכך מאזן העומסים מפנה אוטומטית את התנועה ליציאת PgBouncer של מכונת ה-VM החדשה.

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

שיקולי ביצועים

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

  • התאמת גודל החומרה: הדרישות משתנות בהתאם לעומס העבודה, אבל אפשר להשתמש בפרופילים הסטנדרטיים האלה כנקודת התחלה לכל מכונה וירטואלית:
    • פיתוח/הוכחת היתכנות: 2 vCPU, ‏ 8GB RAM (מינימום לפעולה יציבה).
    • סביבת ייצור קטנה: 4 vCPU, ‏ 16GB RAM. מתאים לכלים פנימיים עם רמת בו-זמניות בינונית.
    • Standard Production: 8 vCPU, 32GB RAM. ההמלצה הבסיסית לאפליקציות קריטיות.
    • תפוקה גבוהה: 16 ליבות vCPU ומעלה, 64GB RAM ומעלה. לעומסי עבודה שנדרש בהם אחסון נרחב של נתונים במטמון בזיכרון (מאגרי נתונים משותפים של PostgreSQL).
  • ביצועי אחסון: אחסון עם ביצועים גבוהים הוא חיוני. מומלץ מאוד להשתמש בדיסקים מסוג SSD כדי לשמור על היציבות של etcd. המערכת etcd רגישה מאוד לזמן האחזור של כתיבת הנתונים בדיסק. בהנחיות הרשמיות של etcd בנושא חומרה מומלץ להשתמש בזמן אחזור של fdatasync של WAL בדיסק p99 של פחות מ-10 אלפיות השנייה.
  • זמן האחזור של הרשת: זמן האחזור בין מכונות וירטואליות משפיע ישירות על ביצועי השכפול:
    • קוורום של etcd: זמן הלוך ושוב (RTT) ממוצע צריך להיות פחות מ-50 אלפיות השנייה (באופן אידיאלי פחות מ-10 אלפיות השנייה) כדי למנוע פסק זמן לבחירה וחוסר יציבות של האשכול.
    • שכפול סינכרוני: אם מוגדר, כל פעולת כתיבה צריכה להמתין לאישור של העותק המשוכפל. החביון בין אזורים ב-GDC עם air gap הוא בדרך כלל פחות מ-1ms, מה שמצוין לשמירה על תקורה מינימלית של כתיבה (בדרך כלל 10-30%).
  • התפקיד של PgBouncer: מערכת PostgreSQL יוצרת תהליך חדש במערכת ההפעלה לכל חיבור, שצורכת כ-10MB של RAM וגורמת לעלויות של החלפת הקשר ב-CPU. ‫PgBouncer מצמצם את התקורה הזו על ידי שמירה של מאגר חיבורים קבועים, וכך מאפשר למסד הנתונים לטפל באלפי חיבורים של אפליקציות עם הרבה פחות תהליכי קצה עורפי.
  • רכיבי Sidecar: ‏ Patroni ו-etcd הם קלי משקל אבל דורשים זמינות עקבית של CPU. בתרחישים של עומס גבוה, חשוב לוודא שאין הקצאת יתר של מכונות וירטואליות ברמת ההיפר-ויז'ור, כדי למנוע "גניבה" של מחזורי CPU שנדרשים לפעימות לב ולתחזוקת הלידים.
  • התאמה אישית של ליבת מערכת ההפעלה: האוטומציה של Autobase מחילה באופן אוטומטי אופטימיזציות שמועילות ל-PostgreSQL, כמו הגדרת פרמטרים של sysctl (למשל, vm.swappiness, net.core.somaxconn) והשבתה של דפים גדולים שקופים (THP). השינויים האלה מצמצמים את התקורה של ניהול הזיכרון ומשפרים את קצב העברת הנתונים ברשת (throughput) עבור מופעי מסד נתונים עם תנועה גבוהה.

לפני שמתחילים

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

בדיקת הדרישות לגבי מכונות וירטואליות

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

  • חלוקה לאזורים: כדי שהפריסה הזו תהיה עמידה באמת לכשל באזור, כדאי לחלק את מכונות ה-VM בין שלושה אזורי זמינות שונים. עם זאת, הפריסה נשארת זהה אם המכונות הווירטואליות ממוקמות בשני אזורים או אפילו באזור אחד. הדבר הכי חשוב הוא שכל המכונות הווירטואליות יוכלו לתקשר ביניהן ברשת באמצעות כתובות ה-IP הפנימיות שלהן.
  • מערכת הפעלה: במדריך הזה אנחנו מניחים שאתם משתמשים בתמונות של Ubuntu 22.04. אם אתם משתמשים בהפצה אחרת, יכול להיות שהשלבים הבאים במדריך הזה יהיו שונים.
  • משאבים: כדי לבצע את ההדרכה הזו, צריך להקצות לפחות 2 מעבדי CPU וזיכרון בנפח 8GB לכל מכונה וירטואלית. בסביבת ייצור, צריך להקצות משאבים שמתאימים לעומסי העבודה הספציפיים (ראו שיקולים לגבי ביצועים).
  • כתובות IP ברשת: כדאי לשים לב לכתובות ה-IP הפנימיות והחיצוניות של כל מכונה וירטואלית. במדריך הזה משתמשים בכתובות IP חיצוניות כדי לשלוט ב-Ansible, כי מריצים פקודות מתחנת עבודה חיצונית. כתובות IP פנימיות משמשות לתקשורת בין שירותים ולקישור. אם הקציתם מכונה וירטואלית של bootstrapper בתוך הרשת, תצטרכו רק את כתובות ה-IP הפנימיות.
  • גישה: נדרשת גישת sudo ללא סיסמה למשתמש הפריסה, כי האוטומציה של Ansible צריכה לבצע משימות אדמיניסטרטיביות (התקנת חבילות, שינוי הגדרות מערכת) בלי להיחסם על ידי הנחיות להזנת סיסמה.
  • ‫SSH: צריך להפעיל אימות מבוסס-מפתח כדי לאפשר ל-Ansible להתחבר למכונות הווירטואליות של היעד בצורה מאובטחת ולא אינטראקטיבית.

הכנת תוכנה בתחנת עבודה מקומית

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

  • ‫Ansible 2.17.0+: מנוע האוטומציה שמבצע את פריסת ה-playbooks והתפקידים.
  • ‫Docker: משמש לשליפה ולאריזה של תלויות במערכת ההפעלה בסביבה זהה למכונות הווירטואליות של היעד (Ubuntu 22.04).
  • לקוח PostgreSQL‏ (psql): נדרש להפעלת שאילתות הבדיקה ולאימות השכפול של הנתונים מתחנת העבודה המקומית.
  • מאגר Autobase:

    • משכפלים את המאגר כדי לגשת ל-playbook ולאפשרויות האוטומציה: https://github.com/vitabaks/autobase
    • בודקים גרסה ספציפית (במדריך הזה נעשה שימוש בגרסה 2.5.2):

      git checkout 2.5.2

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

    • כדי להריץ את מדריכי ההפעלה במדריך הזה, צריך להתקין את קוד המקור המקומי autobase כקולקציית Ansible כדי שניתן יהיה לפתור את הבעיה של קידומות התפקידים:

      cd autobase/automation
      ansible-galaxy collection install . --force
      

יצירת משתני סביבה

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

שימו לב: למרות שאפשר להגדיר שמות שרוצים למכונות ה-VM, במדריך הזה נעשה שימוש בשמות postgres-vm-1, postgres-vm-2 ו-postgres-vm-3 כדוגמאות שרירותיות לשמות של צמתי האשכול:

export PROJECT_ID="your-project-id"
export ZONES="zone1 zone2 zone3"
export VM1_NAME="postgres-vm-1"
export VM2_NAME="postgres-vm-2"
export VM3_NAME="postgres-vm-3"
export VM_LABEL="app=my-postgres-cluster"
export CLUSTER_NAME="my-postgres-cluster"

הגדרת Ansible

מגדירים את סביבת ה-VM בקובץ inventory.ini באמצעות התבנית הבאה:

[master]
postgres-vm-1 ansible_host=XX.XX.XX.XX hostname=postgres-vm-1 bind_address=XX.XX.XX.XX

[replica]
postgres-vm-2 ansible_host=XX.XX.XX.XX hostname=postgres-vm-2 bind_address=XX.XX.XX.XX
postgres-vm-3 ansible_host=XX.XX.XX.XX hostname=postgres-vm-3 bind_address=XX.XX.XX.XX

[postgres_cluster:children]
master
replica

[etcd_cluster]
postgres-vm-1
postgres-vm-2
postgres-vm-3

[all:vars]
ansible_user=...
ansible_ssh_private_key_file=~/.ssh/...
postgresql_version=17
with_haproxy_load_balancing=false
patroni_superuser_password=...
etcd_package_repo="file:///tmp/packages/etcd-v3.5.25-linux-amd64.tar.gz"
installation_method="packages"
install_postgresql_repo=false
install_timescale_repo=false
install_citus_repo=false
apt_repository=[]
yum_repository=[]
install_system_packages=false
patroni_installation_method=deb

הסבר על ההגדרה:

  • ‫[master] ו-[replica]: הגדרת מכונות וירטואליות של מסד הנתונים הראשי והמשני. חשוב להשתמש בשמות המכונות הווירטואליות שהוגדרו במשתני הסביבה VM1_NAME,‏ VM2_NAME ו-VM3_NAME.
  • ‫[postgres_cluster:children]: קבוצה שמצטברים בה גם הצמתים הראשיים וגם הצמתים המשוכפלים, וכך אפשר לטרגט את כל אשכול מסד הנתונים באמצעות פקודה אחת של Ansible.
  • ‫[etcd_cluster]: הגדרה של הצמתים שישתתפו באשכול הקונצנזוס של etcd. הפעולה הזו כוללת את כל שלושת צמתי מסד הנתונים כדי להבטיח זמינות גבוהה.
  • ‫ansible_host: (לכל מכונה וירטואלית) כתובת ה-IP החיצונית של המכונה הווירטואלית שמשמשת את Ansible כדי להתחבר למכונה הווירטואלית הזו. מחליפים את XX.XX.XX.XX בכתובת ה-IP החיצונית בפועל.
  • ‫bind_address: (לכל מכונה וירטואלית) כתובת ה-IP הפנימית של המכונה הווירטואלית. מחליפים את XX.XX.XX.XX בכתובת ה-IP הפנימית בפועל.
  • ‫ansible_user: המשתמש המרוחק ש-Ansible משתמש בו כדי להתחבר למכונות הווירטואליות של היעד באמצעות SSH. מחליפים את ... בשם המשתמש בפועל.
  • ‫ansible_ssh_private_key_file: הנתיב המקומי למפתח ה-SSH הפרטי שמשמש לאימות למכונות הווירטואליות של היעד. מחליפים את ~/.ssh/... בנתיב בפועל.
  • ‫patroni_superuser_password: הסיסמה של המשתמש postgres. חשוב להשתמש כאן בסיסמה חזקה ומאובטחת.
  • ‫with_haproxy_load_balancing=false: משבית את HAProxy המקומי כי אתם משתמשים במאזן עומסים L4 מקורי של הפלטפורמה.
  • ‫etcd_package_repo: מצביע על הנתיב המקומי של הקובץ הבינארי etcd בתוך ספריית האתחול של המכונה הווירטואלית.
  • ‫installation_method="packages": מנחה את האוטומציה להתקין רכיבים עם חבילות של מערכת ההפעלה במקום לקמפל ממקור או להשתמש ב-Python pip.
  • ‫install_..._repo=false ו-_repository=[]: ההגדרות האלה מונעות מ-Ansible לנסות להתחבר לאינטרנט כדי להוסיף מאגרי מידע חיצוניים או לעדכן רשימות חבילות.
  • ‫install_system_packages=false: מונע מהאוטומציה לנסות להוריד ולהתקין חבילות שכבר הקציתם במהלך שלב האתחול או האתחול הראשוני.
  • ‫patroni_installation_method=deb: מציין במפורש לתפקיד להשתמש בחבילה .deb שהתקנתם.

אתחול המכונות הווירטואליות

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

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

  • משתמשים בקונטיינר Docker בתחנת העבודה המקומית כדי להוריד את כל הקבצים הדרושים. הפקודה הבאה משתמשת ב-apt-rdepends כדי לזהות באופן רקורסיבי כל ספרייה משותפת ותלות שנדרשות על ידי אפליקציות היעד. הוא מגדיר את מאגר PostgreSQL הרשמי בתוך הקונטיינר כדי לאחזר ארטיפקטים מגרסה 17, ואז מבצע איטרציה ברשימת התלות כדי להוריד קובצי .deb בודדים, תוך סינון של ספריות מערכת ליבה (כמו libc6 או hostname) כדי למנוע התנגשויות גרסאות במכונות הווירטואליות של היעד. לבסוף, הוא מאחזר את הקובץ הבינארי העצמאי של etcd ישירות מ-GitHub.

    קודם יוצרים ספרייה להחזקת החבילות:

    mkdir -p ./packages
    

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

    docker run --rm --platform linux/amd64 -v "$(pwd)/packages:/packages" \
      ubuntu:22.04 bash -c "
      set -e
      apt-get update
      apt-get install -y ca-certificates curl gnupg apt-rdepends
    
      # Add PostgreSQL Repository
      curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | \
        gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg
      echo 'deb http://apt.postgresql.org/pub/repos/apt jammy-pgdg main' > \
        /etc/apt/sources.list.d/pgdg.list
      apt-get update
    
      # Define Application Targets + Explicit dependencies needed for air-gap
      TARGETS='unzip tar pgbouncer patroni netdata postgresql-17 \
        postgresql-client-17 postgresql-contrib-17 \
        postgresql-server-dev-17 postgresql-17-dbgsym \
        python3-psycopg2 python3-click python3-yaml python3-prettytable \
        python3-urllib3 python3-tz python3-pip python3-setuptools \
        python3-cryptography moreutils vim jq acl zstd libjq1 \
        libpython3.10-stdlib libexpat1-dev zlib1g-dev libipc-run-perl \
        libtime-duration-perl libjson-perl libpython3-dev \
        libjs-sphinxdoc python3-wheel'
    
      # Resolve all recursive dependencies
      ALL_DEPS=\$(apt-cache depends --recurse --no-recommends --no-suggests \
        --no-conflicts --no-breaks --no-replaces --no-enhances \$TARGETS | \
        grep '^\w' | sort -u)
    
      cd /packages
      for pkg in \$ALL_DEPS; do
        if apt-cache show \"\$pkg\" > /dev/null 2>&1; then
          # Filter system core to avoid VM conflicts/breaks
          # We exclude core OS libraries (libc, systemd, etc.) because these
          # often cause version conflicts if the VM's patch level differs
          # from the online container.
          FILTER='base-files|debianutils|coreutils|findutils|diffutils|sed'
          FILTER+='|grep|gzip|hostname|ncurses|perl-base|libc6|binutils'
          FILTER+='|linux-libc|libc-bin|libc-dev-bin|systemd|dpkg|init'
          if [[ ! \"\$pkg\" =~ \$FILTER ]]; then
            apt-get download \"\$pkg\" || echo \"Failed \$pkg\"
          fi
        fi
      done
    
      # Download etcd binary
      if [ ! -f etcd-v3.5.25-linux-amd64.tar.gz ]; then
        curl -L https://github.com/etcd-io/etcd/releases/download/v3.5.25/\
    etcd-v3.5.25-linux-amd64.tar.gz -o etcd-v3.5.25-linux-amd64.tar.gz
      fi
    "
    
  • משתמשים ב-Ansible כדי להעלות את הארכיון לכל שלוש המכונות הווירטואליות בו-זמנית:

    ansible all -i inventory.ini -m copy -a "src=packages.tar.gz dest=/tmp/" -b
    
  • מנקים את כל נתוני החבילה הקיימים במכונות הווירטואליות ומחלצים את קובץ ה-tar החדש:

    ansible all -i inventory.ini -m shell -a "rm -rf /tmp/packages && \
      mkdir -p /tmp/packages && tar -xzf /tmp/packages.tar.gz -C /tmp/packages" -b
    
  • ביצוע התקנה לא אינטראקטיבית של כל חבילות .deb שהורדו. כדי למנוע בעיות עם תלויות ספציפיות מראש בסביבה עם פערי אבטחה, משתמשים בדגל --force-depends ואחריו בדגל apt-get install -fy כדי לפתור את עץ התלויות באופן מקומי:

    ansible all -i inventory.ini -m shell -a "DEBIAN_FRONTEND=noninteractive \
      NEEDRESTART_MODE=a dpkg -i --force-depends /tmp/packages/*.deb" -b
    
    ansible all -i inventory.ini -m shell -a "DEBIAN_FRONTEND=noninteractive \
      NEEDRESTART_MODE=a apt-get install -fy" -b
    
  • צריך להפסיק מיד את כל השירותים כדי למנוע את ההפעלה שלהם במצבי ברירת מחדל לא מוגדרים לפני שהאוטומציה מוכנה:

    ansible all -i inventory.ini -m shell -a \
      "systemctl stop patroni etcd pgbouncer postgresql || true" -b
    
  • לבסוף, מסירים את אשכולות PostgreSQL שמוגדרים כברירת מחדל ואת כל נתוני etcd הקיימים כדי לאפשר הפעלה נקייה:

    ansible all -i inventory.ini -m shell -a \
      "pg_dropcluster 17 main --stop || true" -b
    ansible all -i inventory.ini -m shell -a \
      "rm -rf /var/lib/postgresql/17/main/* /var/lib/etcd/default.etcd/*" -b
    

הקצאת תשתית למסד נתונים

אחרי שמפעילים את המכונות הווירטואליות ומגדירים את המלאי, אפשר להשתמש ב-playbook של אוטומציה של Autobase עם Ansible כדי לפרוס את מחסנית PostgreSQL עם זמינות גבוהה.

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

ansible-playbook vitabaks.autobase.deploy_pgcluster -i inventory.ini \
  --tags pre_checks

אם הבדיקות עוברות, ממשיכים לפריסה מלאה:

ansible-playbook vitabaks.autobase.deploy_pgcluster -i inventory.ini

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

PLAY RECAP ********************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=254  rescued=0    ignored=0
postgres-vm-1              : ok=160  changed=53   unreachable=0    failed=0    skipped=514  rescued=0    ignored=2
postgres-vm-2              : ok=116  changed=40   unreachable=0    failed=0    skipped=505  rescued=0    ignored=2
postgres-vm-3              : ok=116  changed=40   unreachable=0    failed=0    skipped=505  rescued=0    ignored=2

אימות הפריסה

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

בדיקת סטטוס הזמינות הגבוהה

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

ansible master -i inventory.ini -m shell -a "patronictl list" -b

פלט לדוגמה:

+ Cluster: postgres-cluster (7607933704953386478) ---+----+-------------+-----+------------+-----+
| Member        | Host         | Role    | State     | TL | Receive LSN | Lag | Replay LSN | Lag |
+---------------+--------------+---------+-----------+----+-------------+-----+------------+-----+
| postgres-vm-1 | 10.253.1.254 | Leader  | running   |  1 |             |     |            |     |
| postgres-vm-2 | 10.253.1.253 | Replica | streaming |  1 |   0/6000000 |   0 |  0/6000000 |   0 |
| postgres-vm-3 | 10.253.1.252 | Replica | streaming |  1 |   0/6000000 |   0 |  0/6000000 |   0 |
+---------------+--------------+---------+-----------+----+-------------+-----+------------+-----+

אימות של נקודות קצה לבדיקת תקינות

בודקים ש-Patroni מזהה נכון את הלידר והרפליקות באמצעות ה-API ל-REST. בבדיקת התקינות של מכונת ה-VM הראשית צריך להיות ערך ההחזרה 200 OK, ובבדיקות התקינות של מכונות ה-VM המשוכפלות צריך להיות ערך ההחזרה 503 Service Unavailable:

ansible ${VM1_NAME} -i inventory.ini -m shell -a \
  "curl -ks -o /dev/null -w '%{http_code}' https://localhost:8008/primary" -b

ansible ${VM2_NAME} -i inventory.ini -m shell -a \
  "curl -ks -o /dev/null -w '%{http_code}' https://localhost:8008/primary" -b

ansible ${VM3_NAME} -i inventory.ini -m shell -a \
  "curl -ks -o /dev/null -w '%{http_code}' https://localhost:8008/primary" -b

אימות התקינות של מכונה וירטואלית ספציפית

בודקים את המוּכנוּת של כל מכונות PostgreSQL:

ansible postgres_cluster -i inventory.ini -m shell -a "pg_isready -p 5432" -b

פלט לדוגמה:

postgres-vm-1 | CHANGED | rc=0 >>
/var/run/postgresql:5432 - accepting connections

postgres-vm-2 | CHANGED | rc=0 >>
/var/run/postgresql:5432 - accepting connections

postgres-vm-3 | CHANGED | rc=0 >>
/var/run/postgresql:5432 - accepting connections

אימות התקינות של etcd

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

ansible all -i inventory.ini -m shell -a "ETCDCTL_API=3 etcdctl \
  --endpoints=https://localhost:2379 \
  --cacert=/etc/etcd/tls/ca.crt \
  --cert=/etc/etcd/tls/server.crt \
  --key=/etc/etcd/tls/server.key \
  endpoint health" -b

פלט לדוגמה:

postgres-vm-1 | CHANGED | rc=0 >>
https://localhost:2379 is healthy: successfully committed proposal: \
took = 28.78311ms

postgres-vm-2 | CHANGED | rc=0 >>
https://localhost:2379 is healthy: successfully committed proposal: \
took = 33.081265ms

postgres-vm-3 | CHANGED | rc=0 >>
https://localhost:2379 is healthy: successfully committed proposal: \
took = 24.414291ms

הגדרת מאזן העומסים הגלובלי

כדי לספק כתובת IP וירטואלית (VIP) יציבה למערך מסדי הנתונים, צריך להגדיר את מאזן העומסים הגלובלי ברמה L4 של הפלטפורמה באמצעות gdcloud CLI.

  • דרישות מוקדמות:

    • מוודאים שיש לכם את התפקיד load-balancer-admin בפרויקט.
    • מחילים תווית על מכונות וירטואליות כדי שמאזן העומסים יוכל לטרגט בצורה נכונה את המופעים שהוא צריך לשרת (מחליפים את ערכי הפרמטר kubeconfig בקבצים המתאימים של Management API kubeconfig לכל אזור):

      kubectl --kubeconfig=ZONE_A_MANAGEMENT_API label VirtualMachine \
        -n ${PROJECT_ID} \
        ${VM1_NAME} \
        ${VM_LABEL}
      
      kubectl --kubeconfig=ZONE_B_MANAGEMENT_API label VirtualMachine \
        -n ${PROJECT_ID} \
        ${VM2_NAME} \
        ${VM_LABEL}
      
      kubectl --kubeconfig=ZONE_C_MANAGEMENT_API label VirtualMachine \
        -n ${PROJECT_ID} \
        ${VM3_NAME} \
        ${VM_LABEL}
      
  • הגדרת רמת הגישה לאיזון העומסים. מגדירים EXTERNAL אם צריך להתחבר מחוץ לרשת של הפרויקט, או INTERNAL אם נדרשת גישה רק מתוך ה-VPC. במדריך הזה נשתמש בהגדרה חיצונית:

    export LB_SCHEME=EXTERNAL
    
  • יצירת בדיקת תקינות. מאזן העומסים משתמש ב-API בארכיטקטורת REST של Patroni כדי לזהות את ה-leader:

    gdcloud compute health-checks create https ${CLUSTER_NAME}-hc \
      --project=${PROJECT_ID} \
      --port=8008 \
      --request-path="/primary" \
      --check-interval=10 \
      --timeout=5 \
      --healthy-threshold=2 \
      --unhealthy-threshold=3 \
      --global
    
  • יוצרים עורף קצה אזורי נפרד לכל אזור שבו נמצאים המכונות הווירטואליות:

    for zone in $(echo $ZONES); do
      gdcloud compute backends create ${CLUSTER_NAME}-backend-${zone} \
        --project=${PROJECT_ID} \
        --zone=${zone} \
        --labels="${VM_LABEL}"
    done
    
  • יוצרים שירות גלובלי לקצה העורפי:

    gdcloud compute backend-services create ${CLUSTER_NAME}-bes \
      --project=${PROJECT_ID} \
      --health-check="${CLUSTER_NAME}-hc" \
      --global
    
  • מוסיפים את השרתים העורפיים האזוריים לשירות הגלובלי:

    for zone in $(echo $ZONES); do
      gdcloud compute backend-services add-backend ${CLUSTER_NAME}-bes \
        --project=${PROJECT_ID} \
        --backend=${CLUSTER_NAME}-backend-${zone} \
        --backend-zone=${zone} \
        --global
    done
    
  • יוצרים כלל העברה גלובלי (הכתובת הווירטואלית). הכלל הזה חושף את מסד הנתונים ביציאה 6432:

    gdcloud compute forwarding-rules create ${CLUSTER_NAME}-fr \
      --project=${PROJECT_ID} \
      --load-balancing-scheme=${LB_SCHEME} \
      --backend-service=${CLUSTER_NAME}-bes \
      --ip-protocol-port="TCP:6432" \
      --global
    
  • מאחזרים את כתובת ה-VIP:

    LB_IP=$(gdcloud compute forwarding-rules describe ${CLUSTER_NAME}-fr \
      --project=${PROJECT_ID} \
      --load-balancing-scheme=${LB_SCHEME} \
      --global \
      --format=json \
      | jq -r '.metadata.annotations["networking.gke.io/forwardingRuleCIDR"]'  \
      | cut -d '/' -f 1)
    
    echo "The load balancer IP is: ${LB_IP}"
    
  • יוצרים ProjectNetworkPolicy (PNP) כדי לאפשר תעבורת נתונים נכנסת (ingress) ליציאת PgBouncer (מחליפים את ערך הפרמטר kubeconfig בקובץ ה-API הגלובלי kubeconfig של הסביבה).

    kubectl --kubeconfig=GLOBAL_API_KUBECONFIG apply -f - <<EOF
    apiVersion: networking.global.gdc.goog/v1
    kind: ProjectNetworkPolicy
    metadata:
      name: allow-pgbouncer
      namespace: ${PROJECT_ID}
    spec:
      ingress:
      - ports:
        - port: 6432
          protocol: TCP
      policyType: Ingress
      subject:
        subjectType: UserWorkload
    EOF
    

אימות שכפול הנתונים

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

הוספת נתונים לדוגמה

האוטומציה יוצרת סיסמה אקראית למשתמש postgres במהלך הפריסה הראשונה, אם לא צוינה סיסמה ב-inventory.ini. אפשר לאחזר אותו מכל מכונה וירטואלית:

export PG_PASSWORD=$(ansible master -i inventory.ini -m shell -a \
  "grep -A10 'authentication:' /etc/patroni/patroni.yml | \
  grep -A3 'superuser' | grep 'password:' | awk '{ print \$2 }'" -b | \
  tail -n 1)
echo $PG_PASSWORD

מתחברים ל-VIP של מאזן העומסים ביציאת PgBouncer ‏ (6432) ויוצרים טבלה לדוגמה:

PGPASSWORD="${PG_PASSWORD}" psql -h ${LB_IP} -p 6432 -U postgres -c "
  CREATE TABLE employees (first_name TEXT, last_name TEXT);
  INSERT INTO employees (first_name, last_name) VALUES ('John', 'Doe');
"

הפלט הצפוי:

INSERT 0 1

אימות מצב הרפליקציה

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

ansible postgres_cluster -i inventory.ini -m shell -a "psql -U postgres -c \
  'SELECT * FROM employees;'" -b

פלט לדוגמה:

postgres-vm-1 | CHANGED | rc=0 >>
 first_name | last_name
------------+-----------
 John       | Doe
(1 row)

postgres-vm-2 | CHANGED | rc=0 >>
 first_name | last_name
------------+-----------
 John       | Doe
(1 row)

postgres-vm-3 | CHANGED | rc=0 >>
 first_name | last_name
------------+-----------
 John       | Doe
(1 row)

בדיקת מעבר ידני

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

זיהוי המנהיג הנוכחי

בודקים את התפקיד והסטטוס הנוכחיים של מכונות ה-VM:

ansible master -i inventory.ini -m shell -a "patronictl list" -b

פלט לדוגמה:

+ Cluster: postgres-cluster (7607933704953386478) ---+----+-------------+-----+------------+-----+
| Member        | Host         | Role    | State     | TL | Receive LSN | Lag | Replay LSN | Lag |
+---------------+--------------+---------+-----------+----+-------------+-----+------------+-----+
| postgres-vm-1 | 10.253.1.254 | Leader  | running   |  1 |             |     |            |     |
| postgres-vm-2 | 10.253.1.253 | Replica | streaming |  1 |   0/6000000 |   0 |  0/6000000 |   0 |
| postgres-vm-3 | 10.253.1.252 | Replica | streaming |  1 |   0/6000000 |   0 |  0/6000000 |   0 |
+---------------+--------------+---------+-----------+----+-------------+-----+------------+-----+

ביצוע מעבר

מפעילים מעבר משרת ה-Leader הנוכחי למכונה וירטואלית אחרת (במקרה הזה, מ-postgres-vm-1 ל-postgres-vm-2). הפקודה משתמשת ב---force כדי לדלג על הנחיות לאישור ידני:

ansible master -i inventory.ini -m shell -a "patronictl switchover \
  --leader ${VM1_NAME} --candidate ${VM2_NAME} --force" -b

פלט לדוגמה:

Successfully switched over to "postgres-vm-2"
+ Cluster: postgres-cluster (7607933704953386478) -+----+-------------+-----+------------+-----+
| Member        | Host         | Role    | State   | TL | Receive LSN | Lag | Replay LSN | Lag |
+---------------+--------------+---------+---------+----+-------------+-----+------------+-----+
| postgres-vm-1 | 10.253.1.254 | Replica | stopped |    |     unknown |     |    unknown |     |
| postgres-vm-2 | 10.253.1.253 | Leader  | running |  1 |             |     |            |     |
| postgres-vm-3 | 10.253.1.252 | Replica | running |  1 |   0/70000A0 |   0 |  0/70000A0 |   0 |
+---------------+--------------+---------+---------+----+-------------+-----+------------+-----+

אימות של שינוי בבדיקת התקינות

אחרי המעבר, מוודאים שסטטוס בדיקת התקינות השתנה ל-leader החדש:

ansible ${VM1_NAME} -i inventory.ini -m shell -a \
  "curl -ks -o /dev/null -w '%{http_code}' https://localhost:8008/primary" -b

ansible ${VM2_NAME} -i inventory.ini -m shell -a \
  "curl -ks -o /dev/null -w '%{http_code}' https://localhost:8008/primary" -b

השרת הראשי הישן (postgres-vm-1) צריך להחזיר 503, והשרת הראשי החדש (postgres-vm-2) צריך להחזיר 200.

בדיקת מעבר אוטומטי לגיבוי (failover)

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

זיהוי המנהיג הנוכחי

בודקים את התפקיד והסטטוס הנוכחיים של מכונות ה-VM:

ansible master -i inventory.ini -m shell -a "patronictl list" -b

פלט לדוגמה:

+ Cluster: postgres-cluster (7607933704953386478) ---+----+-------------+-----+------------+-----+
| Member        | Host         | Role    | State     | TL | Receive LSN | Lag | Replay LSN | Lag |
+---------------+--------------+---------+-----------+----+-------------+-----+------------+-----+
| postgres-vm-1 | 10.253.1.254 | Replica | streaming |  2 |   0/8000000 |   0 |  0/8000000 |   0 |
| postgres-vm-2 | 10.253.1.253 | Leader  | running   |  2 |             |     |            |     |
| postgres-vm-3 | 10.253.1.252 | Replica | streaming |  2 |   0/8000000 |   0 |  0/8000000 |   0 |
+---------------+--------------+---------+-----------+----+-------------+-----+------------+-----+

סימולציה של כשל ב-VM

עוצרים את שירות patroni במכונה הווירטואלית הראשית כדי לדמות קריסה או כשל חמור:

ansible master -i inventory.ini -m shell -a "systemctl stop patroni" -b

צפייה בבחירות החדשות

מחכים 10-20 שניות ובודקים את הסטטוס ממכונה וירטואלית אחרת כדי לראות את הקידום של מנהיג חדש:

ansible replica -i inventory.ini -m shell -a "patronictl list" -b

פלט לדוגמה:

postgres-vm-3 | CHANGED | rc=0 >>
+ Cluster: postgres-cluster (7607933704953386478) ---+----+-------------+-----+------------+-----+
| Member        | Host         | Role    | State     | TL | Receive LSN | Lag | Replay LSN | Lag |
+---------------+--------------+---------+-----------+----+-------------+-----+------------+-----+
| postgres-vm-1 | 10.253.1.254 | Leader  | running   |  3 |             |     |            |     |
| postgres-vm-2 | 10.253.1.253 | Replica | stopped   |    |     unknown |     |    unknown |     |
| postgres-vm-3 | 10.253.1.252 | Replica | streaming |  3 |   0/90003F8 |   0 |  0/90003F8 |   0 |
+---------------+--------------+---------+-----------+----+-------------+-----+------------+-----+

אחת ממכונות ה-VM האחרות (postgres-vm-1 או postgres-vm-3) הפכה למובילה, והמובילה הקודמת (postgres-vm-2) מסומנת כ'הופסקה'.

אימות של השינוי בבדיקת התקינות

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

ansible ${VM1_NAME} -i inventory.ini -m shell -a \
  "curl -ks -o /dev/null -w '%{http_code}' https://localhost:8008/primary" -b

המנהיג החדש צריך להחזיר את 200 OK.

שחזור ה-VM שנכשל

מפעילים מחדש את שירות patroni במכונה הווירטואלית המקורית כדי לאפשר לו להצטרף מחדש לסטאק כרפליקה ולהתעדכן בכל הנתונים שהוחמצו:

ansible ${VM2_NAME} -i inventory.ini -m shell -a \
  "systemctl start patroni" -b