Kembali ke blog

Proxy untuk Arsitektur Mikros layanan: Perlindungan API, Penyeimbangan Beban, dan Keamanan

Panduan lengkap untuk mengintegrasikan proxy dalam arsitektur mikroservis: perlindungan API, penyeimbangan beban, keamanan komunikasi antar layanan, dan contoh pengaturan.

📅18 Februari 2026
```html

Arsitektur mikroservis memerlukan komunikasi yang andal antara layanan, perlindungan terhadap permintaan API eksternal, dan penyeimbangan beban. Server proxy menyelesaikan masalah ini dengan bertindak sebagai perantara antara layanan, API eksternal, dan klien. Dalam panduan ini, kita akan membahas cara yang tepat untuk mengintegrasikan proxy ke dalam infrastruktur mikroservis, jenis proxy yang digunakan untuk berbagai skenario, dan cara mengatur komunikasi yang aman.

Peran Proxy dalam Arsitektur Mikroservis

Dalam arsitektur mikroservis, server proxy menjalankan beberapa fungsi kritis yang berbeda dari penggunaan tradisional proxy untuk anonimisasi atau menghindari pemblokiran. Di sini, proxy menjadi bagian integral dari infrastruktur, memastikan komunikasi yang andal dan aman antara komponen sistem.

Peran utama proxy dalam mikroservis:

  • API Gateway — titik masuk tunggal untuk semua permintaan klien, yang mengarahkan mereka ke mikroservis yang sesuai, menyembunyikan arsitektur internal sistem
  • Sidecar Proxy — kontainer proxy yang berjalan di samping setiap layanan (pola Service Mesh), menangkap semua lalu lintas masuk dan keluar
  • Reverse Proxy — distribusi beban antara beberapa instance dari satu layanan, memastikan ketahanan
  • Forward Proxy — kontrol dan perlindungan permintaan keluar ke API eksternal, menyembunyikan alamat IP internal infrastruktur
  • Proxy Keamanan — terminasi SSL/TLS, autentikasi, otorisasi, perlindungan dari DDoS dan serangan lainnya

Proxy memungkinkan penerapan pola arsitektur penting: circuit breaker (pemutusan otomatis layanan yang tidak berfungsi), retry logic (coba ulang saat terjadi kesalahan), rate limiting (pembatasan frekuensi permintaan), request/response transformation (transformasi format data). Semua ini membuat sistem lebih tahan terhadap kegagalan dan menyederhanakan pengelolaan infrastruktur terdistribusi yang kompleks.

Penting: Dalam arsitektur mikroservis, proxy bekerja pada dua tingkat — sebagai gerbang eksternal untuk klien (API Gateway) dan sebagai proxy internal antara layanan (Service Mesh). Kedua tingkat ini sangat penting untuk keamanan dan keandalan sistem.

Jenis Proxy untuk Berbagai Skenario Penggunaan

Pemilihan jenis proxy tergantung pada tugas spesifik dalam arsitektur mikroservis. Berbagai skenario memerlukan karakteristik yang berbeda: kecepatan, keandalan, anonimitas, atau distribusi geografis.

Skenario Jenis Proxy Mengapa
Komunikasi internal antar layanan Proxy HTTP/HTTPS (Envoy, NGINX) Kecepatan maksimum, latensi rendah, dukungan HTTP/2
Permintaan ke API eksternal dengan batasan Proxy residensial Menghindari batasan, IP nyata pengguna, risiko pemblokiran rendah
Pengambilan data untuk analitik Proxy data center Kecepatan tinggi, biaya rendah, cocok untuk permintaan massal
Bekerja dengan API mobile Proxy mobile Meniru pengguna mobile nyata, akses ke API yang hanya untuk mobile
Penyeimbangan beban Reverse Proxy (HAProxy, NGINX) Distribusi lalu lintas, pemeriksaan kesehatan, pengalihan otomatis saat terjadi kesalahan
Sistem terdistribusi secara geografis Proxy residensial dengan geo-targeting Akses ke API regional, memenuhi persyaratan lokalisasi data

Untuk komunikasi internal antara mikroservis, biasanya digunakan solusi proxy khusus seperti Envoy Proxy atau NGINX, yang dioptimalkan untuk latensi rendah dan throughput tinggi. Mereka mendukung protokol modern (HTTP/2, gRPC) dan terintegrasi dengan sistem Service Mesh.

Untuk bekerja dengan API eksternal, pilihan tergantung pada kebutuhan layanan tertentu. Jika API memiliki batasan yang ketat atau memblokir permintaan dari IP data center, proxy residensial diperlukan. Untuk pengumpulan data massal, di mana kecepatan lebih penting daripada anonimitas, proxy data center cocok digunakan. Proxy mobile diperlukan saat bekerja dengan API yang memeriksa jenis perangkat atau memerlukan alamat IP mobile.

Proxy sebagai API Gateway: Perlindungan dan Rute

API Gateway adalah server proxy khusus yang berfungsi sebagai titik masuk tunggal untuk semua permintaan klien ke sistem mikroservis. Alih-alih klien langsung menghubungi puluhan layanan yang berbeda, mereka mengirimkan semua permintaan ke satu alamat API Gateway, yang mengarahkan mereka ke layanan yang tepat.

Fungsi utama API Gateway:

  • Rute Permintaan — menentukan layanan mikro mana yang harus memproses permintaan, berdasarkan URL, header, atau parameter lainnya
  • Autentikasi dan Otorisasi — memeriksa token (JWT, OAuth), mengelola akses ke berbagai layanan
  • Rate Limiting — membatasi jumlah permintaan dari satu klien untuk melindungi dari overload dan DDoS
  • Agregasi Respons — menggabungkan data dari beberapa layanan menjadi satu respons untuk klien
  • Transformasi Protokol — mengonversi REST ke gRPC, HTTP/1.1 ke HTTP/2
  • Cache — menyimpan data yang sering diminta untuk mengurangi beban pada layanan
  • Pencatatan dan Pemantauan — pengumpulan terpusat metrik dan log dari semua permintaan

Solusi populer untuk API Gateway: Kong, Tyk, AWS API Gateway, Azure API Management, NGINX Plus, Traefik. Pilihan tergantung pada skala sistem, kebutuhan kinerja, dan platform cloud yang digunakan.

// Contoh konfigurasi NGINX sebagai API Gateway
upstream auth_service {
    server auth:8001;
}

upstream user_service {
    server user:8002;
}

upstream order_service {
    server order:8003;
}

server {
    listen 80;
    server_name api.example.com;

    # Pembatasan frekuensi permintaan
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

    location /api/auth/ {
        limit_req zone=api_limit burst=20;
        proxy_pass http://auth_service/;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location /api/users/ {
        # Memeriksa token sebelum memproxy
        auth_request /auth/verify;
        proxy_pass http://user_service/;
    }

    location /api/orders/ {
        auth_request /auth/verify;
        proxy_pass http://order_service/;
    }

    # Endpoint internal untuk memeriksa token
    location = /auth/verify {
        internal;
        proxy_pass http://auth_service/verify;
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";
    }
}

API Gateway menyembunyikan arsitektur internal sistem dari klien eksternal. Klien tidak tahu berapa banyak mikroservis yang ada dan bagaimana mereka berinteraksi — mereka hanya melihat satu API. Ini menyederhanakan versi, memungkinkan perubahan struktur internal tanpa mempengaruhi klien, dan meningkatkan keamanan, karena layanan internal tidak dapat diakses langsung dari internet.

Integrasi dengan Service Mesh (Istio, Linkerd)

Service Mesh adalah lapisan infrastruktur yang mengelola komunikasi antara mikroservis menggunakan server proxy yang ditempatkan di samping setiap layanan (pola Sidecar). Berbeda dengan API Gateway, yang hanya menangani lalu lintas eksternal, Service Mesh mengontrol semua lalu lintas internal antara layanan.

Solusi Service Mesh yang paling populer adalah Istio (menggunakan Envoy Proxy sebagai sidecar) dan Linkerd (menggunakan proxy ringan sendiri). Mereka secara otomatis menyisipkan kontainer proxy di samping setiap pod di Kubernetes, menangkap semua lalu lintas masuk dan keluar.

Fitur Service Mesh melalui proxy:

  • Mutual TLS (mTLS) — enkripsi otomatis semua lalu lintas antara layanan dengan autentikasi timbal balik
  • Manajemen Lalu Lintas — pengelolaan rute, penyebaran canary, pengujian A/B
  • Observabilitas — pengumpulan otomatis metrik, jejak, dan log tanpa mengubah kode layanan
  • Ketahanan — pemutusan sirkuit, logika coba ulang, manajemen waktu habis, injeksi kesalahan untuk pengujian
  • Penemuan Layanan — penemuan layanan otomatis dan penyeimbangan beban
# Contoh konfigurasi Istio VirtualService untuk rute
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: user-service
spec:
  hosts:
  - user-service
  http:
  - match:
    - headers:
        version:
          exact: "v2"
    route:
    - destination:
        host: user-service
        subset: v2
  - route:
    - destination:
        host: user-service
        subset: v1
      weight: 90
    - destination:
        host: user-service
        subset: v2
      weight: 10  # Penyebaran canary: 10% lalu lintas ke v2

---
# Circuit Breaker untuk melindungi dari kegagalan berantai
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: user-service
spec:
  host: user-service
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100
      http:
        http1MaxPendingRequests: 50
        maxRequestsPerConnection: 2
    outlierDetection:
      consecutiveErrors: 5
      interval: 30s
      baseEjectionTime: 30s
      maxEjectionPercent: 50

Service Mesh menyelesaikan masalah "monolit terdistribusi" — ketika logika interaksi antara layanan (coba ulang, waktu habis, pemutusan sirkuit) diduplikasi dalam kode setiap layanan. Sebagai gantinya, semua logika ini dipindahkan ke lapisan proxy, yang menyederhanakan kode layanan dan memastikan perilaku yang konsisten di seluruh sistem.

Keuntungan penting — transparansi penuh lalu lintas. Setiap permintaan antara layanan melewati proxy, yang mencatat metrik: waktu respons, kode kesalahan, ukuran payload. Data ini secara otomatis dikirim ke sistem pemantauan (Prometheus, Grafana) dan pelacakan (Jaeger, Zipkin), menciptakan gambaran lengkap tentang kinerja sistem terdistribusi tanpa perlu menambahkan instrumentasi ke dalam kode setiap layanan.

Perlindungan Permintaan ke API Eksternal melalui Proxy

Mikroservis sering berinteraksi dengan API eksternal: sistem pembayaran, layanan geolokasi, API media sosial, penyedia data. Permintaan langsung ke API eksternal menciptakan beberapa masalah: pengungkapan alamat IP internal infrastruktur, risiko pemblokiran saat melebihi batasan, dan kurangnya kontrol atas lalu lintas keluar.

Penggunaan proxy untuk permintaan keluar menyelesaikan masalah ini dan menambahkan kemampuan tambahan:

  • Menyembunyikan Infrastruktur — API eksternal melihat alamat IP proxy, bukan server Anda
  • Menghindari Batasan — rotasi alamat IP untuk mendistribusikan permintaan
  • Distribusi Geografis — akses ke API regional melalui proxy di negara yang diperlukan
  • Pengelolaan Terpusat — titik kontrol tunggal untuk semua permintaan keluar
  • Cache Respons — mengurangi jumlah permintaan ke API yang mahal
  • Pemantauan dan Pencatatan — melacak semua permintaan ke layanan eksternal
// Python: pengaturan proxy untuk permintaan ke API eksternal
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

class ExternalAPIClient:
    def __init__(self, proxy_url, proxy_rotation=False):
        self.session = requests.Session()
        
        # Pengaturan proxy
        self.proxies = {
            'http': proxy_url,
            'https': proxy_url
        }
        
        # Logika coba ulang untuk ketahanan
        retry_strategy = Retry(
            total=3,
            backoff_factor=1,
            status_forcelist=[429, 500, 502, 503, 504],
            allowed_methods=["GET", "POST"]
        )
        adapter = HTTPAdapter(max_retries=retry_strategy)
        self.session.mount("http://", adapter)
        self.session.mount("https://", adapter)
    
    def call_payment_api(self, data):
        """Permintaan ke API pembayaran melalui proxy"""
        try:
            response = self.session.post(
                'https://api.payment-provider.com/charge',
                json=data,
                proxies=self.proxies,
                timeout=10,
                headers={'User-Agent': 'MyService/1.0'}
            )
            response.raise_for_status()
            return response.json()
        except requests.exceptions.RequestException as e:
            # Pencatatan kesalahan
            print(f"Kesalahan API Pembayaran: {e}")
            raise

# Penggunaan dengan kumpulan proxy untuk rotasi
class ProxyPool:
    def __init__(self, proxy_list):
        self.proxies = proxy_list
        self.current = 0
    
    def get_next(self):
        proxy = self.proxies[self.current]
        self.current = (self.current + 1) % len(self.proxies)
        return proxy

# Inisialisasi
proxy_pool = ProxyPool([
    'http://user:pass@proxy1.example.com:8080',
    'http://user:pass@proxy2.example.com:8080',
    'http://user:pass@proxy3.example.com:8080'
])

# Untuk setiap permintaan, gunakan proxy berikutnya
client = ExternalAPIClient(proxy_pool.get_next())

Untuk bekerja dengan API eksternal yang memiliki batasan ketat atau memblokir permintaan dari IP data center, proxy residensial menjadi kebutuhan. Mereka menyediakan alamat IP nyata dari pengguna rumah, yang mengurangi risiko pemblokiran dan memungkinkan untuk menghindari batasan geografis.

// Node.js: proxy untuk API eksternal dengan rotasi otomatis
const axios = require('axios');
const HttpsProxyAgent = require('https-proxy-agent');

class ExternalAPIService {
  constructor(proxyList) {
    this.proxyList = proxyList;
    this.currentProxyIndex = 0;
    this.requestCounts = new Map(); // Penghitung permintaan untuk pembatasan laju
  }

  getNextProxy() {
    const proxy = this.proxyList[this.currentProxyIndex];
    this.currentProxyIndex = (this.currentProxyIndex + 1) % this.proxyList.length;
    return proxy;
  }

  async callAPI(endpoint, data, options = {}) {
    const proxyUrl = this.getNextProxy();
    const agent = new HttpsProxyAgent(proxyUrl);

    // Pembatasan laju: tidak lebih dari 100 permintaan per menit pada proxy
    const proxyKey = proxyUrl;
    const now = Date.now();
    const count = this.requestCounts.get(proxyKey) || { count: 0, resetTime: now + 60000 };
    
    if (count.count >= 100 && now < count.resetTime) {
      // Beralih ke proxy berikutnya
      return this.callAPI(endpoint, data, options);
    }

    try {
      const response = await axios({
        method: options.method || 'POST',
        url: endpoint,
        data: data,
        httpsAgent: agent,
        timeout: options.timeout || 10000,
        headers: {
          'User-Agent': 'Mozilla/5.0 (compatible; MyService/1.0)',
          ...options.headers
        }
      });

      // Memperbarui penghitung
      if (now >= count.resetTime) {
        this.requestCounts.set(proxyKey, { count: 1, resetTime: now + 60000 });
      } else {
        count.count++;
      }

      return response.data;
    } catch (error) {
      if (error.response?.status === 429) {
        // Batas laju terlampaui - beralih ke proxy lain
        console.log(`Batas laju pada ${proxyUrl}, beralih proxy`);
        return this.callAPI(endpoint, data, options);
      }
      throw error;
    }
  }
}

// Penggunaan
const apiService = new ExternalAPIService([
  'http://user:pass@proxy1.example.com:8080',
  'http://user:pass@proxy2.example.com:8080'
]);

module.exports = apiService;

Penyeimbangan Beban dan Ketahanan

Server proxy memainkan peran kunci dalam memastikan ketersediaan tinggi sistem mikroservis melalui penyeimbangan beban dan pengalihan otomatis saat terjadi kesalahan. Ketika Anda menjalankan beberapa instance dari satu layanan (untuk penskalaan horizontal), proxy mendistribusikan permintaan di antara mereka, memastikan beban yang merata.

Algoritma penyeimbangan beban utama:

  • Round Robin — pengiriman permintaan secara bergiliran ke setiap server dalam daftar, sederhana dan efektif untuk server yang homogen
  • Least Connections — mengirim permintaan ke server dengan jumlah koneksi aktif paling sedikit, cocok untuk permintaan yang lama
  • IP Hash — mengikat klien ke server tertentu berdasarkan IP-nya, memastikan sesi yang lengket
  • Weighted Round Robin — distribusi dengan mempertimbangkan kekuatan server (server yang lebih kuat mendapatkan lebih banyak permintaan)
  • Random — pemilihan server secara acak, cocok untuk layanan stateless
# Konfigurasi HAProxy untuk penyeimbangan dengan pemeriksaan kesehatan
global
    maxconn 4096
    log stdout format raw local0

defaults
    mode http
    timeout connect 5s
    timeout client 50s
    timeout server 50s
    option httplog

frontend api_frontend
    bind *:80
    default_backend api_servers

backend api_servers
    balance roundrobin
    
    # Pemeriksaan kesehatan: memeriksa /health setiap 2 detik
    option httpchk GET /health
    http-check expect status 200
    
    # Logika coba ulang
    retries 3
    option redispatch
    
    # Server dengan bobot (server3 dua kali lebih kuat)
    server server1 10.0.1.10:8080 check weight 1 maxconn 500
    server server2 10.0.1.11:8080 check weight 1 maxconn 500
    server server3 10.0.1.12:8080 check weight 2 maxconn 1000
    
    # Server cadangan (digunakan hanya jika yang utama tidak tersedia)
    server backup1 10.0.2.10:8080 check backup

Pemeriksaan kesehatan adalah fungsi yang sangat penting untuk ketahanan. Proxy secara teratur memeriksa ketersediaan setiap server (biasanya melalui endpoint HTTP /health atau /ready) dan secara otomatis mengecualikan server yang tidak berfungsi dari kumpulan penyeimbangan. Ketika server pulih dan mulai merespons pemeriksaan kesehatan, ia secara otomatis kembali ke kumpulan.

Strategi ketahanan melalui proxy:

  • Pemeriksaan Kesehatan Aktif — proxy secara aktif menanyakan server untuk memeriksa ketersediaan mereka
  • Pemeriksaan Kesehatan Pasif — proxy melacak permintaan nyata dan mengecualikan server saat kesalahan terakumulasi
  • Circuit Breaker — pemutusan sementara layanan yang bermasalah untuk mencegah kegagalan berantai
  • Degradasi Halus — beralih ke mode operasi yang disederhanakan atau data yang di-cache saat terjadi kesalahan
  • Failover ke Cadangan — pengalihan otomatis ke server atau wilayah cadangan
// Python: implementasi Circuit Breaker untuk proxy ke layanan eksternal
from datetime import datetime, timedelta
from enum import Enum

class CircuitState(Enum):
    CLOSED = "closed"      # Normal operation
    OPEN = "open"          # Service unavailable, requests are blocked
    HALF_OPEN = "half_open"  # Test mode after recovery

class CircuitBreaker:
    def __init__(self, failure_threshold=5, timeout=60, success_threshold=2):
        self.failure_threshold = failure_threshold  # Errors before opening
        self.timeout = timeout  # Seconds before recovery attempt
        self.success_threshold = success_threshold  # Successes to close
        
        self.state = CircuitState.CLOSED
        self.failures = 0
        self.successes = 0
        self.last_failure_time = None
    
    def call(self, func, *args, **kwargs):
        if self.state == CircuitState.OPEN:
            if datetime.now() - self.last_failure_time > timedelta(seconds=self.timeout):
                self.state = CircuitState.HALF_OPEN
                print("Circuit breaker: transitioning to HALF_OPEN")
            else:
                raise Exception("Circuit breaker OPEN: service unavailable")
        
        try:
            result = func(*args, **kwargs)
            self._on_success()
            return result
        except Exception as e:
            self._on_failure()
            raise e
    
    def _on_success(self):
        self.failures = 0
        if self.state == CircuitState.HALF_OPEN:
            self.successes += 1
            if self.successes >= self.success_threshold:
                self.state = CircuitState.CLOSED
                self.successes = 0
                print("Circuit breaker: recovery, transitioning to CLOSED")
    
    def _on_failure(self):
        self.failures += 1
        self.last_failure_time = datetime.now()
        if self.failures >= self.failure_threshold:
            self.state = CircuitState.OPEN
            print(f"Circuit breaker OPEN: {self.failures} consecutive errors")

# Usage
breaker = CircuitBreaker(failure_threshold=3, timeout=30)

def call_external_service():
    # Your code to request external API via proxy
    pass

try:
    result = breaker.call(call_external_service)
except Exception as e:
    # Fallback logic: cache, default values, etc.
    print(f"Service unavailable: {e}")

Keamanan Komunikasi Antar Layanan

Dalam arsitektur mikroservis, server proxy menyediakan beberapa tingkat keamanan: enkripsi lalu lintas, autentikasi layanan, perlindungan dari serangan, dan isolasi segmen jaringan. Tanpa pengaturan keamanan yang tepat, lalu lintas internal antara layanan dapat disadap atau dipalsukan.

Aspek kunci keamanan melalui proxy:

  • Mutual TLS (mTLS) — autentikasi dua arah, di mana baik klien maupun server memverifikasi sertifikat satu sama lain. Service Mesh secara otomatis mengatur mTLS antara semua layanan
  • Terminasi TLS — proxy mendekripsi lalu lintas HTTPS di perbatasan, memverifikasinya, dan meneruskannya ke layanan melalui saluran yang aman
  • Validasi JWT — memeriksa token akses di tingkat proxy, sebelum permintaan mencapai layanan
  • IP Whitelisting — membatasi akses ke layanan hanya dari alamat IP yang diizinkan
  • Perlindungan DDoS — pembatasan laju, batas koneksi, perlindungan dari SYN flood di tingkat proxy
  • WAF (Web Application Firewall) — penyaringan permintaan berbahaya, perlindungan dari SQL injection, XSS
# Konfigurasi NGINX dengan SSL/TLS dan keamanan
server {
    listen 443 ssl http2;
    server_name api.internal.example.com;

    # Sertifikat SSL
    ssl_certificate /etc/nginx/certs/api.crt;
    ssl_certificate_key /etc/nginx/certs/api.key;
    
    # Protokol dan cipher modern
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    
    # Sertifikat klien untuk mTLS
    ssl_client_certificate /etc/nginx/certs/ca.crt;
    ssl_verify_client on;
    
    # Header aman
    add_header Strict-Transport-Security "max-age=31536000" always;
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    
    # Pembatasan laju
    limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s;
    limit_req zone=api burst=200 nodelay;
    
    # Pembatasan koneksi
    limit_conn_zone $binary_remote_addr zone=addr:10m;
    limit_conn addr 10;
    
    # IP whitelisting
    allow 10.0.0.0/8;      # Jaringan internal
    allow 172.16.0.0/12;   # VPC
    deny all;
    
    location / {
        # Memeriksa token JWT
        auth_jwt "Restricted API";
        auth_jwt_key_file /etc/nginx/jwt_key.json;
        
        proxy_pass http://backend_service;
        
        # Mengirim informasi tentang sertifikat klien
        proxy_set_header X-Client-DN $ssl_client_s_dn;
        proxy_set_header X-Client-Verify $ssl_client_verify;
    }
}

Service Mesh secara signifikan menyederhanakan pengaturan keamanan, secara otomatis menghasilkan dan merotasi sertifikat untuk mTLS, menerapkan kebijakan akses, dan mengenkripsi semua lalu lintas antara layanan. Misalnya, di Istio, Anda dapat menetapkan kebijakan bahwa layanan "pembayaran" hanya dapat menerima permintaan dari layanan "pesanan", dan ini akan diterapkan secara otomatis di tingkat proxy tanpa mengubah kode layanan.

Penting untuk produksi: Selalu gunakan mTLS untuk komunikasi internal antar layanan, bahkan jika mereka berada dalam satu jaringan privat. Ini melindungi dari serangan tipe man-in-the-middle dan memastikan autentikasi di tingkat layanan, bukan hanya di tingkat jaringan.

Pemantauan dan Pencatatan Lalu Lintas Proxy

Server proxy menyediakan kesempatan unik untuk pemantauan terpusat dari semua lalu lintas dalam sistem mikroservis. Karena semua lalu lintas melewati proxy (baik yang eksternal melalui API Gateway maupun yang internal melalui Service Mesh), Anda mendapatkan visibilitas penuh tentang kinerja sistem tanpa perlu menginstrumentasi setiap layanan.

Metrik kunci untuk pemantauan di tingkat proxy:

  • Latensi — waktu pemrosesan permintaan di setiap tahap: proxy, layanan, API eksternal
  • Throughput — jumlah permintaan per detik, volume data yang ditransfer
  • Rasio Kesalahan — persentase kesalahan (4xx, 5xx), jenis kesalahan, endpoint yang bermasalah
  • Metrik Koneksi — jumlah koneksi aktif, penggunaan pool koneksi
  • Status Circuit Breaker — status pemutus sirkuit untuk setiap layanan
  • Metrik SSL/TLS — status sertifikat, versi protokol, kesalahan handshake
# Konfigurasi NGINX untuk mengekspor metrik ke Prometheus
server {
    listen 9113;
    location /metrics {
        stub_status;
        access_log off;
        allow 10.0.0.0/8;  # Hanya untuk server Prometheus
        deny all;
    }
}

# Pencatatan dalam format JSON untuk pencatatan terstruktur
log_format json_combined escape=json
  '{'
    '"time_local":"$time_local",'
    '"remote_addr":"$remote_addr",'
    '"request":"$request",'
    '"status": "$status",'
    '"body_bytes_sent":"$body_bytes_sent",'
    '"request_time":"$request_time",'
    '"upstream_response_time":"$upstream_response_time",'
    '"upstream_addr":"$upstream_addr",'
    '"http_referrer":"$http_referer",'
    '"http_user_agent":"$http_user_agent",'
    '"http_x_forwarded_for":"$http_x_forwarded_for"'
  '}';

access_log /var/log/nginx/access.log json_combined;

Distributed Tracing — salah satu kemampuan pemantauan paling kuat melalui proxy. Setiap permintaan mendapatkan ID jejak unik, yang ditambahkan proxy ke header dan diteruskan ke rantai layanan. Sistem pelacakan (Jaeger, Zipkin) mengumpulkan informasi dari semua proxy dan membangun jalur lengkap permintaan melalui sistem, menunjukkan berapa lama ia menghabiskan waktu di setiap layanan.

// Node.js: menambahkan pelacakan dalam middleware proxy
const express = require('express');
const { v4: uuidv4 } = require('uuid');
const axios = require('axios');

const app = express();

// Middleware untuk menambahkan trace ID
app.use((req, res, next) => {
  // Mendapatkan trace ID dari header atau membuat yang baru
  const traceId = req.headers['x-trace-id'] || uuidv4();
  const spanId = uuidv4();
  
  // Menambahkan ke header untuk diteruskan
  req.traceId = traceId;
  req.spanId = spanId;
  res.setHeader('x-trace-id', traceId);
  
  // Mencatat awal pemrosesan
  const startTime = Date.now();
  
  res.on('finish', () => {
    const duration = Date.now() - startTime;
    
    // Log terstruktur untuk analisis
    console.log(JSON.stringify({
      timestamp: new Date().toISOString(),
      traceId: traceId,
      spanId: spanId,
      method: req.method,
      path: req.path,
      status: res.statusCode,
      duration: duration,
      userAgent: req.headers['user-agent'],
      ip: req.ip
    }));
  });
  
  next();
});

// Endpoint proxy dengan penerusan header pelacakan
app.all('/api/*', async (req, res) => {
  const targetService = determineTargetService(req.path);
  
  try {
    const response = await axios({
      method: req.method,
      url: `http://${targetService}${req.path}`,
      data: req.body,
      headers: {
        ...req.headers,
        'x-trace-id': req.traceId,
        'x-parent-span-id': req.spanId,
        'x-span-id': uuidv4()  // Span baru untuk permintaan downstream
      }
    });
    
    res.status(response.status).json(response.data);
  } catch (error) {
    console.error(JSON.stringify({
      traceId: req.traceId,
      error: error.message,
      service: targetService
    }));
    res.status(500).json({ error: 'Layanan tidak tersedia' });
  }
});

function determineTargetService(path) {
  if (path.startsWith('/api/users')) return 'user-service:8080';
  if (path.startsWith('/api/orders')) return 'order-service:8080';
  return 'default-service:8080';
}

app.listen(3000);

Peringatan berdasarkan metrik proxy memungkinkan deteksi masalah dengan cepat. Misalnya, Anda dapat mengatur peringatan untuk: lonjakan latensi yang tiba-tiba (mungkin salah satu layanan mengalami penurunan), peningkatan rasio kesalahan di atas ambang batas (masalah dengan kode atau dependensi), perubahan pola lalu lintas (kemungkinan serangan DDoS atau beban virus).

Contoh Implementasi di Python dan Node.js

Mari kita lihat contoh praktis integrasi proxy ke dalam mikroservis di Python dan Node.js untuk berbagai skenario: komunikasi internal, bekerja dengan API eksternal, penyeimbangan beban.

Python: layanan dengan proxy untuk API eksternal

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import httpx
import asyncio
from typing import List, Optional
import logging

app = FastAPI()
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class ProxyConfig(BaseModel):
    url: str
    max_requests_per_minute: int = 60

class ProxyPool:
    def __init__(self, proxies: List[ProxyConfig]):
        self.proxies = proxies
        self.current_index = 0
        self.request_counts = {p.url: 0 for p in proxies}
        self.reset_time = asyncio.get_event_loop().time() + 60
    
    async def get_next_proxy(self) -> str:
        # Reset penghitung setiap menit
        current_time = asyncio.get_event_loop().time()
        if current_time >= self.reset_time:
            self.request_counts = {p.url: 0 for p in self.proxies}
            self.reset_time = current_time + 60
        
        # Temukan proxy dengan permintaan yang tersedia
        for _ in range(len(self.proxies)):
            proxy = self.proxies[self.current_index]
            self.current_index = (self.current_index + 1) % len(self.proxies)
            
            if self.request_counts[proxy.url] < proxy.max_requests_per_minute:
                self.request_counts[proxy.url] += 1
                return proxy.url
        
        # Semua proxy telah mencapai batas
        raise HTTPException(status_code=429, detail="Semua proxy dibatasi")

# Inisialisasi kumpulan proxy
proxy_pool = ProxyPool([
    ProxyConfig(url="http://user:pass@proxy1.example.com:8080", max_requests_per_minute=100),
    ProxyConfig(url="http://user:pass@proxy2.example.com:8080", max_requests_per_minute=100),
    ProxyConfig(url="http://user:pass@proxy3.example.com:8080", max_requests_per_minute=100)
])

class ExternalAPIClient:
    def __init__(self, proxy_pool: ProxyPool):
        self.proxy_pool = proxy_pool
    
    async def fetch_data(self, endpoint: str, params: dict = None) -> dict:
        proxy_url = await self.proxy_pool.get_next_proxy()
        
        async with httpx.AsyncClient(proxies={"http://": proxy_url, "https://": proxy_url}) as client:
            try:
                response = await client.get(
                    endpoint,
                    params=params,
                    timeout=10.0,
                    headers={"User-Agent": "MyMicroservice/1.0"}
                )
                response.raise_for_status()
                
                logger.info(f"Berhasil mengambil dari {endpoint} melalui {proxy_url}")
                return response.json()
            
            except httpx.HTTPStatusError as e:
                logger.error(f"Kesalahan HTTP {e.response.status_code} dari {endpoint}")
                raise HTTPException(status_code=e.response.status_code, detail=str(e))
            
            except httpx.RequestError as e:
                logger.error(f"Kesalahan permintaan ke {endpoint}: {e}")
                raise HTTPException(status_code=503, detail="API eksternal tidak tersedia")

api_client = ExternalAPIClient(proxy_pool)

@app.get("/data/{resource_id}")
async def get_external_data(resource_id: str):
    """Endpoint yang mendapatkan data dari API eksternal melalui proxy"""
    external_endpoint = f"https://api.external-service.com/v1/resources/{resource_id}"
    
    try:
        data = await api_client.fetch_data(external_endpoint)
        return {"status": "success", "data": data}
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"Kesalahan tak terduga: {e}")
        raise HTTPException(status_code=500, detail="Kesalahan internal server")

@app.get("/health")
async def health_check():
    return {"status": "healthy", "service": "external-api-proxy"}

# Menjalankan: uvicorn main:app --host 0.0.0.0 --port 8000

Node.js: API Gateway dengan Penyeimbangan Beban

const express = require('express');
const axios = require('axios');
const rateLimit = require('express-rate-limit');

const app = express();
app.use(express.json());

// Konfigurasi mikroservis
const services = {
  users: [
    { url: 'http://user-service-1:8001', healthy: true, activeConnections: 0 },
    { url: 'http://user-service-2:8001', healthy: true, activeConnections: 0 },
    // Tambahkan lebih banyak layanan sesuai kebutuhan
  ],
  orders: [
    { url: 'http://order-service-1:8002', healthy: true, activeConnections: 0 },
    { url: 'http://order-service-2:8002', healthy: true, activeConnections: 0 },
    // Tambahkan lebih banyak layanan sesuai kebutuhan
  ]
};

// Middleware untuk penyeimbangan beban
app.use('/api/users', (req, res, next) => {
  const service = services.users.find(s => s.healthy);
  if (!service) {
    return res.status(503).json({ error: 'Semua layanan pengguna tidak tersedia' });
  }
  service.activeConnections++;
  axios({
    method: req.method,
    url: service.url + req.path,
    data: req.body,
    headers: req.headers
  }).then(response => {
    service.activeConnections--;
    res.status(response.status).json(response.data);
  }).catch(error => {
    service.activeConnections--;
    res.status(error.response?.status || 500).json({ error: error.message });
  });
});

// Implementasikan rute serupa untuk pesanan dan layanan lainnya

app.listen(3000, () => {
  console.log('API Gateway berjalan di http://localhost:3000');
});
```