มีโจทย์ว่าจะต้องทำ Load balance มาครอบ Middleware ชุดหนึ่ง ก็เลยจะเอา HAProxy มาใช้ แต่เคยใช้แต่ใน mode http ก็เลยไม่มั่นใจว่าจะทำได้ไหม เลยลองทำ Lab เล่นดู

มาเริ่มกันเลย ผมจะทำ TCP Server กับ Client ขึ้นมาก่อน

Simple TCP Server.go

package main

import (
  "fmt"
  "log"
  "net"
)

func main() {
  fmt.Println("Hey Baby, I glad to see you again")
  ln, err := net.Listen("tcp", ":9000")
  if err != nil {
    log.Fatal(err)
  }
  defer ln.Close()

  for {
    // Wait for connection
    conn, err := ln.Accept()
    if err != nil {
      log.Fatal(err)
    }   

    go handleConnection(conn)
  }
}

func handleConnection(conn net.Conn) {
  // Send Hello
  conn.Write([]byte("Send by Worker 1"))
  conn.Close()
}

มาดูฝั่ง Client.go กันบ้าง

package main

import (
  "fmt"
  "log"
  "net"
)

func main() {
  fmt.Println("Hi Honey")
  conn, err := net.Dial("tcp", "192.168.99.100:9900") // Server IP
  if err != nil {
    log.Fatal(err)
  }
  defer conn.Close()

  buf := make([]byte, 1024)
  bl, err := conn.Read(buf)
  if err != nil || bl == 0 {
    log.Fatal(err)
  }
  fmt.Println(string(buf))
}

ทดลอง run ดูก่อนว่าทำงานได้ไหม

(server) $ go run server.go      
Hey Baby, I glad to see you again
(client) $ go run client.go      
Hi Honey
Send by Worker 1

ทุกอย่างเป็นไปได้สวย ต่อไปเราลองมาจับ server.go ยัดลงไปใน Docker เพื่อทำให้เหมือนสถานการณ์จริง ผมเปลี่ยนเป็น worker1 กับ worker2 เพื่อที่จะได้ระบุถูกว่า client เราต่อไปที่เครื่องไหน

Create worker1 (สร้าง directory worker1 แล้วเรา server.go ไปว่างรอไว้)

$ docker run -d --name worker1 -p 9000:9000 -v `pwd`/worker1:/go golang:alpine go run server.go

Create worker2

docker run -d --name worker2 -p 9001:9001 -v `pwd`/worker2:/go golang:alpine go run server.go

ต่อมาสร้าง file haproxy.cfg แบบง่ายๆ ก่อนกัน

global
  maxconn 45000
  daemon

defaults
  timeout server 10s
  timeout client 30s
  timeout connect 30s

listen foreman
  bind *:9900
  mode tcp
  balance roundrobin
  server w1 worker1:9000
  server w2 worker2:9001

เราใช้ mode เป็น tcp และ algorithm เป็น roundrobin คือสลับไปเรื่อยๆ

Create haproxy1

docker run -d --name haproxy1 -p 9900:9900 --link worker1:worker1 --link worker2:worker2 -v `pwd`/haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro haproxy:alpine

link ที่กำหนดลงไปมาจาก Container name ของ TCP Server ที่ทำไว้ก่อนหน้า

จริงๆ แล้วเราไม่ต้อง expose port ของ worker ก็ได้ เพราะ link ใน docker มันจะทำการ map port ให้เราเอง

List process ดูว่าทุกตัว run อยู่

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
585f3c43ffef        haproxy:alpine      "/docker-entrypoint.s"   24 seconds ago      Up 23 seconds       0.0.0.0:9900->9900/tcp   haproxy1
cb134a0672b3        golang:alpine       "go run server.go"       2 minutes ago       Up 2 minutes        0.0.0.0:9001->9001/tcp   worker2
3c71a5d835ba        golang:alpine       "go run server.go"       3 minutes ago       Up 3 minutes        0.0.0.0:9000->9000/tcp   worker1

ลองมาเทสกันครับ

$ go run client.go
Hi Honey
Send by Worker 1
$ go run client.go
Hi Honey
Send by Worker 2
$ go run client.go
Hi Honey
Send by Worker 1
$ go run client.go
Hi Honey
Send by Worker 2

Good jobs, well done!

Haproxy configuration