Thursday, September 17, 2015

Active-Active HAProxy Behind Google's Network Load Balancer


Pada artikel saya sebelumnya, saya sempat berbagi bagaimana cara mengkonfigurasi HAProxy secara active-passive menggunakan Keepalived. Namun konfigurasi tersebut tidak dapat diimplementasikan di semua environment. Salah satu isu yang sempat saya temui adalah ketika mencoba mengkonfigurasi hal serupa di Google Cloud Platform (GCP). Akses ke IP public pada GCP tidak dapat dimanipulasi secara langsung dari dalam VM. Masing-masing VM hanya memiliki interface private network, sementara Keepalived membutuhkan akses langsung ke network interface IP public.

GCP menyediakan 2 layanan load balancing pada platform-nya, yaitu HTTP/HTTPS Load Balancing dan Network Load Balancing. Awalnya saya ingin menggunakannya, namun saat artikel ini ditulis status HTTP/HTTPS Load Balancing pada GCP masih dalam beta release. Sehingga saya menjadi enggan untuk menggunakannya di production environment. Di lain pihak status Network Load Balancing pada GCP sudah dalam tahap tidak beta release, tetapi kurang fleksible jika digunakan sebagai load balancer dari Web server. Kerugian lain jika memanfaatkan platform load balancing milik GCP, adalah nantinya semua konfigurasi akan bertumpu pada GCP, sehingga jika suatu saat kita membutuhkan untuk pindah atau ekspansi ke cloud platform perlu konversi kembali pada konfigurasinya.

Akhirnya saya terpikir, bagaimana jika saya kombinasikan antara Network Load Balancer dengan HAProxy servers sebagai backend server-nya? Langsung saja kita lihat diagram berikut ini:
Gambar 1. Diagram Google Network Load Balancer dan HAProxy Server

Ide dasar dari topologi tersebut adalah Google Network Load Balancer hanya difungsikan sebagai IP public provider saja. Semua network traffic yang diterima oleh Google Network Load Balancer akan diteruskan ke HAProxy servers, seolah-olah HAProxy servers berada di DMZ. HAProxy servers yang akan melakukan load balancing yang sebenarnya ke masing-masing backend. Keuntungan yang saya peroleh dengan topologi tersebut antara lain adalah:
  1. Konfigurasi akan dititikberatkan pada HAProxy servers, sehingga tidak perlu melakukan konfigurasi ulang yang banyak jika diperlukan untuk migrasi ke cloud platform lain. 
  2. Fitur-fitur HAProxy seperti connection throttling, IP blacklisting, multiple backend rules dapat dilakukan seperti HAProxy biasa.
  3. HAProxy servers pada topologi tersebut sifatnya active-active, sehingga tidak ada CPU cycle yang terbuang percuma karena idle menunggu master HAProxy down terlebih dahulu.
Sebagai simulasi, saya telah membuat 2 VM bertipe f1-micro yang didedikasikan khusus untuk HAProxy saja.Konfigurasi HAProxy tidak akan saya detilkan pada kesempatan kali ini, karena tidak ada perbedaan yang signifikan. Adapun yang harus dilakukan pada konfigurasi HAProxy adalah tidak menetapkan listen address pada direktif bind seperti 10.11.12.13:80, namun menggunakan karakter asterisk (*) diikuti port, seperti *:80. Hal ini perlu dilakukan agar Google Network Load Balancer dapat berkomunikasi dengan HAProxy.

Langkah-langkah pengerjaan konfigurasi yang perlu dilakukan dapat digambarkan secara sederhana seperti diagram berikut ini:
Gambar 2. Alur Konfigurasi

Pertama-tama external static IP address perlu dibuat terlebih dahulu sebagai pintu masuk semua traffic. Menggunakan external IP address bertipe ephemeral juga dapat dilakukan, namun sebaiknya menggunakan static agar tidak akan berubah-ubah jika ada perubahan konfigurasi. Pastikan untuk tidak meng-attach IP tersebut ke node manapun, karena nantinya external static IP address tersebut akan digunakan oleh forwarding rule.
Gambar 3. Membuat External Static IP Address

Pilih menu Networking -> Network Load Balancing, dan pilih Basic Setup untuk membuat forwarding rule dan target pool yang baru. Isi sesuai dengan environment yang kita miliki. Pada saat menentukan protocol pilih TCP kemudian biarkan Port/range dikosongkan, karena di sini Google Network Load Balancer hanya dimanfaatkan sebagai IP address provider saja dan menyerahkan semua load balancer rule ke HAProxy. Pilih juga untuk membuat Target Pool baru yang anggotanya adalah VM HAProxy. Pada contoh berikut ada 2 instance HAProxy VM yang saya tentukan sebagai Target Pool.
Gambar 4. Forwarding Rule Protocol, Port Range, dan Target Pool

Pada bagian Health Check, buat sebuah health check baru dengan port 9200 dan path /. Untuk sementara kita buat saja seperti ini, nantinya akan kita buat health check khusus untuk HAProxy ini menggunakan xinetd service. Saya buat interval-nya pendek yaitu 1 detik agar jika salah satu instance HAProxy down, request bisa langsung ditangkap oleh HAProxy instance lainnya.
Gambar 5. Health Check HAProxy

Agar health check yang berasal dari Google Network Load Balancer dapat dilakukan, kita perlu meng-update firewall rule untuk memperbolehkan traffic yang berasal dari IP 169.254.169.254, sesuai dengan dokumentasi dari Google [1].
Gambar 6. Firewall Rule untuk Health Check yang dilakukan Google Network Load Balancer

Dari sisi konfigurasi GCP, seharusnya konfigurasi yang sudah dilakukan mencukupi. Namun kita masih perlu membuat health checker untuk HAProxy. Idenya terinspirasi dari script health check yang diperuntukkan untuk memeriksa proses MySQL [2] dengan menggunakan xinetd sebagai service endpoint-nya pada port 9200. Setelah mengikuti langkah-langkah pada artikel tersebut, ternyata Google Network Load Balancer masih menganggap HAProxy pada VM dalam kondisi yang tidak sehat. Setelah saya coba melakukan simulasi operasi download menggunakan wget ke port 9200, dianggap tidak ada. Namun telnet ke 9200 sudah mengembalikan output yang saya harapkan. Sepertinya Google Network Load Balancer mengharapkan health checker-nya berupa HTTP response dengan HTTP Status 200, sedangkan dengan mekanisme xinetd murni tidak seperti yang diharapkan.

Kemudian saya coba mencari cara lain dan menemui cara untuk mensimulasikan response HTTP menggunakan script Python [3]. Script xinetd tetap digunakan sebagai health check service endpoint, namun xinetd script tersebut memanggil Python script yang dapat mensimulasikan sebuah HTTP server sederhana yang akan mengembalikan HTTP Status 200 jika proses HAProxy pada VM tersebut masih berjalan, dan akan mengembalikan HTTP Status 503 jika tidak ada proses HAProxy yang berjalan.

Berikut adalah Python script yang digunakan:
#!/usr/bin/python

import subprocess
import sys

while True:
    data = sys.stdin.readline().strip()
    if data == "":
        subprocess.call(['/opt/haproxycheck.sh'])
        f = open('/tmp/haproxycheck.out','r')
        if f.read() != "":
            print 'HTTP/1.1 200 OK'
            print 'Content-Type: Content-Type: text/plain'
            print ''
            print 'HAProxy is running.'
            print ''
        else:
            print 'HTTP/1.1 503 Service Unavailable'
            print 'Content-Type: Content-Type: text/plain'
            print ''
            print 'HAProxy is down.'
            print ''
        sys.stdout.flush()
        break


Script tersebut memanggil Shell script untuk memeriksa proses HAProxy, di mana output dari Shell script tersebut dapat menghasilkan 2 output file yaitu /tmp/haproxycheck.out jika HAProxy berjalan normal dan /tmp/haproxycheck.err jika HAProxy tidak berjalan. Berikut adalah Shell script yang dimaksud:
#!/bin/bash

TMP_FILE="/tmp/haproxycheck.out"
ERR_FILE="/tmp/haproxycheck.err"

/bin/pidof haproxy > $TMP_FILE 2> $ERR_FILE


Restart xinetd service dan seharusnya dalam beberapa saat status health check dari HAProxy sudah dalam kondisi sehat seperti ini:
Gambar 7. Status Health Check HAProxy
Solusi yang saya aplikasikan di sini masih sangat mungkin untuk dioptimalkan kembali, terutama script Python dan Shell untuk meminimalkan overhead yang terjadi. Namun setidaknya dari artikel yang saya tulis ada gambaran bagaimana mengaplikasikan health check pada environment Google Cloud platform. Silahkan mencoba!


Daftar Referensi:
[1] Load Balancing Health Checks - Update Firewall Rules
[2] Having HAProxy check mysql status through a xinetd script, Unai Rodriguez, 4 December 2008
[3] Writing a Python xinetd Server, Johan Jordaan, November 2011

1 comment: