Skip to content

Load Balancing

Raditya P edited this page May 28, 2024 · 4 revisions

Load balancing

Cloud Load balancing adalah sebuah proses mendistribusikan beban kerja (workload) kepada seluruh sumber daya di lingkungan komputasi awan tertentu dengan konsiderasi jaringan lalu lintas.

Cara kerjanya, load balancer mendapat workload dan me-routing ke sumber daya sehingga workload yang lain diurus terus-menerus tanpa ada waiting yang panjang. Tidak hanya dalam beberapa server, load balancing bisa melakukan distribusi secara geographical.

Struktur Load balancing pada umumnya

Contoh Penyedia Layanan Cloud Load Balancing:

  • Amazon Web Services (AWS) menggunakan Elastic Load Balancing (ELB).
  • Google Cloud Platform (GCP) menggunakan Google Cloud Load Balancing.
  • Microsoft Azure menggunakan Azure Load Balancer.

Load Balancer Algorithm

Dibagi menjadi 2

  1. Static

    • Round robin Pembagian menggunakan DNS dalam bentuk rotasi.
    • Weighted round robin Pembagian berdasarkan beban yang ditentukan. Jika bisa handle traffic besar, makan weight nya makin besar.
    • IP hash Menggunakan fungsi matematika untuk mengubah IP Address ke hash. Berdasarkan hash tersebut, koneksi dihubungkan pada server tersebut.
  2. Dynamic

    • Least connection Melihat server yang memiliki koneksi yang sedikit (Asumsi seluruh kekuatan proses sama).
    • Weighted response time Melihat rata-rata waktu respons tiap server, tetapi juga melihat jumlah koneksi pada server.
    • Resource-based Melihat sumber daya (eg. CPU) pada server. Memerlukan 'agent' yang dapat memonitor sumber daya server.

Dan masih banyak lagi!

NGINX

Merupakan program open source yang digunakan untuk web serving, reverse proxy, caching, load balancing, streaming, dll.


Setup Load Balancer

Menginstall Library

Untuk host, akan menggunakan Docker dan Docker Compose dengan FastAPI dan MongoDB sebagai Database. Jadi, mari menginstall ketiga library tersebut dengan:

  1. Docker dan Docker Compose

Sudah dijelaskan di modul sebelumnya ya ;)

  1. FastAPI
pip install fastapi

Kemudian mengisntall uvicorn dengan

pip install uvicorn
  1. MongoDB Untuk library ini, hanya akan menginstal pydantic untuk keperluan input database dan pymango untuk mengkoneksikan FastAPI dangan database.
pip install pymango pydantic

Menyiapkan Struktur Folder/File

Silakan mengikuti struktur berikut

.
└── Awan/
    ├── app/
    │   ├── Dockerfile
    │   ├── main.py
    │   └── requirement.txt
    ├── app2/
    │   ├── Dockerfile
    │   ├── main.py
    │   └── requirement.txt
    ├── app3/
    │   ├── Dockerfile
    │   ├── main.py
    │   └── requirement.txt
    ├── docker-compose.yml
    └── locustfile.py

Konfigurasi main.py

mengimport library

from fastapi import FastAPI, Body, Request # FastAPI utama
from fastapi.encoders import jsonable_encoder # JSON Handler
import pymongo # Menghubungkan ke Database MongoDB 
from pydantic import BaseModel # 
from bson.objectid import ObjectId # Identifikasi ID pada mongoDB
import uuid # Default ID Generator
import socket # Identifier tiap docker 
import time # Simulasi waktu request

selanjutnya, mari kita menghubungkan python dengan database mongoDB.

MONGO_DETAILS = "mongodb://admin:admin@mongodb:27017/" # nama aplikasi docker mongodb
client = pymongo.MongoClient(MONGO_DETAILS)
db = client['tes'] # nama databesnya 'tes'
collection = db['tes'] # nama collectionnya 'tes'

untuk nama database dan collection bisa disesuaikan.

Selanjutnya, mendefinisikan jenis data di dalam collection:

class Item(BaseModel):
    name: str
    age: int 
    rank: str

collection mongoDB akan menggunakan struktur di atas.

Kemudian, mari mendefinisikan fungsi-fungsi untuk me-return data:

def myData(data):
    return {
        "id": str(data["_id"]),
        "name": data["name"],
        "age": data["age"],
        "rank": data["rank"],
    }

def myFullData(datas):
    return [myData(data) for data in datas]

    
def ResponseModel(data, message = "Success"):
    return {
        "data": [data],
        "code": 200,
        "message": message,
    }

def ErrorResponseModel(error, code, message):
    return {
        "error": error, 
        "code": code, 
        "message": message
    }

Penjelasannya:

  • myData untuk me-return data dengan format menyesuaikan struktur data atau lainnya.
  • myFullData untuk mengembalikan seluruh isi data pada collection.
  • ResponseModel sebagai handler data jika berhasil dilakukan.
  • ErrorResponseModel sebagai handler data jika gagal dilakukan.

Setelah seluruh handler telah dibuat, mari kita ke main course, yaitu pembu8atan REST API dengan FastAPI.

Deklarasikan aplikasi

app = FastAPI()

Kita akan menggunakan 6 endpoint untuk mensimulasikan aplikasi REST API sederhana.

  • GET / sebagai testing.
  • GET /fast sebagai testing dengan response cepat.
  • GET /slow sebagai testing dengan response lambat.
  • GET /all mendapatkan semua data pada collection.
  • POST /create memasukkan data baru pada collection.
  • GET /get/{id} mendapatkan data sesuai id.

berikut implementasinya:

@app.get('/')
async def home():
    return {
        "message": "This is server A",
        "hostname": socket.gethostname()}

@app.get('/fast')
async def hello():
    time.sleep(0.5)
    return {
        "message": "Hello world from server A",
        "opt": "fast"}

@app.get('/slow')
async def hello():
    time.sleep(1)
    return {
        "message": "Hello world from server A",
        "opt": "slow"}

@app.get("/all")
async def get_all_data():
    # print(client.tes.tes.find())
    # print(myFullData(client.tes.tes.find()))
    return ResponseModel(myFullData(collection.find()), "All Good")

# insert new data
@app.post("/create", response_model=Item)
async def create_data(request: Request, lister: Item = Body(..., embed=True)):
    lister = jsonable_encoder(lister)
    new_data = client.tes.tes.insert_one(lister)
    # return response with data inserted
    return lister

# get data by id
@app.get("/get/{id}")
async def get_data(id: str):
    Objinstance = ObjectId(id)
    data = collection.find_one({"_id": Objinstance})
    if data:
        return ResponseModel(myData(data), "A")
    else:
        return ErrorResponseModel("An error occurred.", 404, "Data doesn't exist.")

Konfigurasi Dockerfile

Adapun isi docker diisi berikut:

FROM python:3.9-slim
WORKDIR /app
COPY . /app
RUN pip install -r requirements.txt
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Docker akan membuat folder app dan akan menginstall seluruh requirement di dalam file requirement.txt

Saat docker diluncurkan, DOcker python akan menjalankan uvicorn pada port 8000

uvicorn akan menjalankan file main dengan aplikasi app (akan dijelakan pada pembuatan FastAPI).

Konfigurasi requirement.txt

silakan memasukkan library-library yang akan digunakan:

fastapi==0.78.0
uvicorn==0.18.2
pymongo
pydantic
uuid

Konfigurasi nginx

Pada nginx, kita akan mengkonfigurasikan Dockerfile dan file konfigurasi.

FROM nginx
RUN rm /etc/nginx/conf.d/default.conf
COPY ./nginx.conf /etc/nginx/conf.d/default.conf

dengan file konfigurasi nginx.conf sebagai berikut:

upstream app {
    server app:8000;
    server app2:8000;
    server app3:8000;
}

server {
    listen 80;
    server_name localhost;

    location / {
        proxy_pass http://app;
    }
}

mengingat seluruh app akan dijalankan di docker port 8000 dan nginx akan dijalankan pada host port 80

proxy_pass http://app nama app menyesuaikan nama uvicorn yang dijalankan (main:app)

Dari upstream yang kita masukkan, load balancer yang akan digunakan adalan Round-Robin.

Konfigurasi docker-compose

Pada docker compose, ada beberapa docker image yang akan digunakan, berupa:

  • nginx
  • mongo dan mongo-express

buatlah docker-compose dengan isi seperti di bawah ini:

version: '3'

services:
  app:
    build: ./app
    ports:
      - "8001:8000"
    depends_on:
      - mongodb
  app2:
    build: ./app2
    ports:
      - "8002:8000"
    depends_on:
      - mongodb
  app3:
    build: ./app3
    ports:
      - "8003:8000"
    depends_on:
      - mongodb  
  nginx:
    build: ./nginx
    ports:
      - "80:80"
      - "443:443"
    depends_on:
      - app
      - app2
      - app3
  mongodb:
    image: mongo
    ports:
      - "27017:27017"
    environment:
      - MONGO_INITDB_ROOT_USERNAME=admin
      - MONGO_INITDB_ROOT_PASSWORD=admin
    volumes:
      - mongodb_data:/data/db
  mongo-express:
    image: mongo-express
    ports:
      - "8082:8081"
    environment:
      - ME_CONFIG_MONGODB_ADMINUSERNAME=admin
      - ME_CONFIG_MONGODB_ADMINPASSWORD=admin
      - ME_CONFIG_MONGODB_SERVER=mongodb
      - ME_CONFIG_MONGODB_ENABLE_ADMIN=true
      - ME_CONFIG_BASICAUTH_USERNAME=admin
      - ME_CONFIG_BASICAUTH_PASSWORD=admin
    depends_on:
      - mongodb
volumes:
  mongodb_data:
    driver: local

app, app2, app3

pada service app app2 app3, pastikan arah file sudah menuju folder yang memiliki Dockerfile yang telah dibuat sebelumnya.

adapun host ports yang akan digunakan tiap service adalah 8001 8002 8003 dan docker port seluruhnya diarahkan ke 8000 Why? karena kita menjalankan uvicorn tiap app di port 8000 namun tiap docker harus dijalankan di port host yang berbeda.

Karena aplikasi akan menghubungkan database, dan databse perlu di load terbih dahulu, maka tambahkan mongodb

nginx

build diarahkan ke folder berisi Dockerfile nginx.

port yang akan digunakan minimal 80:80.

nginx akan bergantung penggunaanya dengan app yang ada, sehingga ditambahkan depends_on ke tiap app.

mongodb

Karena kita tidak menkonfigurasikan Dockerfile, kita akan load image dari repository Docker bernama mongo.

Port mongo dijalankan di 27017:27017 (default).

WAJIB memasukkan MONGO_INITDB_ROOT_USERNAME dan MONGO_INITDB_ROOT_PASSWORD untuk setup awal database.

Agar isi database tidak berubah setiap docker-composer di up, deklarasikan volumes dan mengisi sesuai isi di atas. di luar strukture services, jangan lupa deklarasikan:

volumes:
  mongodb_data:
    driver: local

mongo-express

Media penampilan database mongodb pada webUI.

Deklarasikan image mongo-express karena tidak ada konfigurasi tambahan.

Defaut port mongo-express adalah 8081 Jalankan pada ports 8082:8081.

Adapun environtment yang perlu disetup:

  • ME_CONFIG_MONGODB_ADMINUSERNAME=admin username ke mongodb
  • ME_CONFIG_MONGODB_ADMINPASSWORD=admin password ke mongodb
  • ME_CONFIG_MONGODB_SERVER=mongodb nama server mongodbyang digunakan (sesuai nama services)
  • ME_CONFIG_MONGODB_ENABLE_ADMIN=true menghidupkan admin
  • ME_CONFIG_BASICAUTH_USERNAME=admin username untuk mengakses webUI
  • ME_CONFIG_BASICAUTH_PASSWORD=admin password untuk mengakses webUI

karena mongo-express harus menunggu mongodb selesai load, maka harus menambahkan mongodb pada depends_on.

Done 🎉🎉

Apabila sudah sesuai, jalankan docker compose dengan

docker-compose up --build

Kemudiian buka localhost dan coba refresh beberapa kali

image

image

image

untuk setup mongodb, maka buka localhost:8082, kemudian masukkan username dan password yang sudah ditentukan sebelumnya.

image

Langkah selanjutnya: masukkan nama databse baru > Tekan create database > Buka database > masukkan nama koleksi baru > tekan Create collection

Pada halaman koleksi, mari buat dokumen baru dengan struktur seperti berikut:

{
        "_id": ObjectId(),
    	"name": "Lorem ipsum",
    	"age": 20,
    	"rank": "admin"
}

Jangan lupa mengganti nama database dan collection di main.py, kemudian docker-compose up ulang

image

Locust

Setelah itu instal locust dengan cara pip install locust lalu buat buat kode locustfile.py nya seperti dibawah

from locust import HttpUser, task, between

class HelloWorldUser(HttpUser):
    wait_time = between(0.5, 2.5)
    # a = 0
    # b = 0
    # c = 0 

    @task
    def test_index(self):
        response = self.client.get('/')
        message = response.json()['message']
        # print(message)
        # if message == 'This is server A':
        #     self.a += 1
        # elif message == 'This is server B':
        #     self.b += 1
        # elif message == 'This is server C':
        #     self.c += 1
        
        # print(f'A: {self.a}, B: {self.b}, C: {self.c}')
    
    @task
    def test_fast(self):
        self.client.get('/fast')
    
    @task
    def test_slow(self):
        self.client.get('/slow')
    
    @task
    def test_slow(self):
        self.client.get('/all')

    @task
    def test_get_id(self):
        response = self.client.get('/get/[ID]')
        message = response.json()['message']
        # if message == 'A':
        #     self.a += 1
        # elif message == 'B':
        #     self.b += 1
        # elif message == 'C':
        #     self.c += 1
        
        # print(f'A: {self.a}, B: {self.b}, C: {self.c}')

lalu di terminal masukkan command locust lalu akses di localhost:8089

test endpoint loadbalancer nginx nya disitu


Clone this wiki locally