Menambahkan Proxy (Web Server)

Setelah berhasil menjalankan job di Nomad dan mendaftarkan service di Consul, sekarang kita akan menambahkan layer reverse proxy menggunakan Caddy. Dengan setup ini, kita tidak perlu lagi mengakses aplikasi melalui IP dan port acak, melainkan melalui domain name yang mudah diingat.

Seperti yang sudah dijelaskan di artikel pertama, Caddy akan diinstall di seluruh node dalam cluster. Setiap node menjalankan Caddy dengan public IP masing-masing, sehingga traffic dapat masuk melalui node mana pun.

Alur Kerja

Sebelum masuk ke tahap instalasi, mari kita pahami dulu bagaimana semua komponen bekerja bersama:

  1. Nomad menjalankan workload (container) di node mana saja dalam cluster
  2. Consul mencatat semua service yang berjalan beserta alamat dan port-nya
  3. consul-template memantau perubahan di Consul dan menggenerate konfigurasi Caddy secara otomatis
  4. Caddy membaca konfigurasi tersebut dan melakukan routing ke service yang tepat

Dengan alur ini, setiap kali ada service baru di-deploy, di-scale, atau dihentikan, routing akan ter-update secara otomatis tanpa intervensi manual.

Build Caddy dengan Custom Plugin

Kita akan menggunakan custom build Caddy yang menyertakan plugin caddy-tlsconsul dan cloudflare. Plugin caddy-tlsconsul memungkinkan Caddy menyimpan sertifikat TLS di Consul KV store, sehingga semua node dapat berbagi sertifikat yang sama.

Plugin Cloudflare di-build juga tapi tidak kita gunakan di artikel ini. Kita akan menggunakan HTTP challenge untuk mendapatkan sertifikat TLS dari Let’s Encrypt.

Install Go dan xcaddy

Proses build akan dilakukan di workstation lokal kita, kemudian binary-nya akan didistribusikan ke semua node menggunakan scp.

Pastikan Go sudah terinstall di workstation kita. Jika belum, ikuti cara install di https://go.dev/doc/install.

Setelah Go terinstall, install xcaddy untuk membangun Caddy dengan plugin:

go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest

# Tambahkan go bin ke PATH jika belum
echo 'export PATH=$PATH:~/go/bin' >> ~/.bashrc
source ~/.bashrc

Build Caddy

Sekarang kita bisa build Caddy dengan plugin yang dibutuhkan:

xcaddy build \
  --with github.com/pteich/caddy-tlsconsul \
  --with github.com/caddy-dns/cloudflare

Proses build akan memakan waktu beberapa menit. Setelah selesai, akan ada file binary caddy di direktori saat ini.

Verifikasi build berhasil:

./caddy version

# Output:
v2.x.x h1:...

Install Caddy dari Repository

Sebelum mendistribusikan binary custom, kita install Caddy default dari repository resmi terlebih dahulu di setiap node untuk mendapatkan systemd service dan struktur direktori yang sudah dikonfigurasi dengan baik:

# Tambahkan repository Caddy
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy

Instalasi ini akan otomatis membuat:

  • User dan group caddy
  • Systemd service yang sudah dikonfigurasi dengan security hardening
  • Direktori /etc/caddy untuk konfigurasi
  • Binary default di /usr/bin/caddy

Kita akan mengganti binary default dengan versi custom yang sudah kita build dengan plugin caddy-tlsconsul dan cloudflare.

Mengganti Binary dengan Versi Custom

Setelah Caddy default terinstall di semua node, sekarang kita ganti binary-nya dengan versi custom yang sudah kita build di workstation. Salin binary ke semua node menggunakan scp atau alternatif lain seperti croc atau rsync seperti yang disebutkan di artikel sebelumnya.

# Copy ke node-1
scp caddy [email protected]:/tmp/caddy

# Copy ke node-2
scp caddy [email protected]:/tmp/caddy

# Copy ke node-3
scp caddy [email protected]:/tmp/caddy

Setelah binary tersalin, login ke setiap node dan jalankan perintah berikut:

# Stop service dulu sebelum replace binary
sudo systemctl stop caddy

# Pindahkan binary ke /usr/bin (replace yang lama)
sudo mv /tmp/caddy /usr/bin/caddy

# Set permission
sudo chmod +x /usr/bin/caddy

# Verifikasi - seharusnya menampilkan custom build dengan plugin
caddy version

# Pastikan plugin consul ada
caddy list-modules | grep consul

Verifikasi Systemd Service

Systemd service untuk Caddy sudah otomatis dibuat oleh paket apt. Kita bisa verifikasi konfigurasinya:

# Lihat status service
systemctl status caddy

# Lihat konfigurasi lengkap service
systemctl cat caddy

Service default dari apt sudah memiliki konfigurasi yang baik dengan security hardening yang tepat. Kita tidak perlu membuat atau memodifikasi service file.

💡 Catatan
Service berjalan sebagai user caddy yang dibuat otomatis saat instalasi. File konfigurasi di /etc/caddy sudah memiliki permission yang tepat untuk user tersebut.

Modifikasi Service untuk Auto-Restart

Service Caddy dari paket apt sudah bagus, namun kita perlu menambahkan konfigurasi restart otomatis. Ini penting karena saat sistem startup, Caddy mungkin gagal start karena mencoba menghubungi Consul yang belum siap.

Buat file override untuk systemd service di setiap node:

sudo systemctl edit caddy

Tambahkan konfigurasi berikut:

[Service]
Restart=always
RestartSec=5

Simpan dan keluar dari editor. Systemd akan membuat file override di /etc/systemd/system/caddy.service.d/override.conf.

Penjelasan singkat:

  • Restart=always membuat Caddy otomatis restart jika gagal start atau crash.
  • RestartSec=5 memberi jeda 5 detik sebelum restart agar Consul sempat siap.

Verifikasi konfigurasi sudah diterapkan:

systemctl cat caddy

Reload systemd untuk memastikan override dibaca:

sudo systemctl daemon-reload

Konfigurasi Caddy

Direktori /etc/caddy sudah dibuat otomatis oleh paket apt. Berbeda dengan setup tradisional, kita tidak akan membuat Caddyfile secara manual.

Seluruh konfigurasi Caddy, termasuk global options dan routing, akan di-generate secara otomatis oleh consul-template berdasarkan service yang terdaftar di Consul. Pendekatan ini membuat setup menjadi sepenuhnya dinamis - tidak ada konfigurasi hardcoded yang perlu diubah saat menambah atau menghapus service.

Caddy akan membaca /etc/caddy/Caddyfile yang akan dibuat oleh consul-template pada langkah berikutnya.

Setup Consul Template

Consul template akan memantau service catalog di Consul dan menghasilkan seluruh konfigurasi Caddy secara dinamis, termasuk global options dan routing. Setiap kali ada perubahan (service baru, scale up/down, service dihapus), konfigurasi akan di-update dan Caddy akan di-reload secara otomatis.

Membuat Direktori

Pertama, buat direktori untuk template dan konfigurasi consul-template di setiap node:

sudo mkdir -p /consul/config/templates
sudo mkdir -p /etc/consul-template.d

Direktori /consul/config/templates digunakan untuk menyimpan template file (.ctmpl), sedangkan /etc/consul-template.d untuk menyimpan file konfigurasi consul-template (.hcl).

💡 Mengapa Direktori /consul/config?
Direktori ini dipilih karena satu namespace dengan konfigurasi Consul yang sudah kita setup sebelumnya. Ini memudahkan manajemen file yang berhubungan dengan Consul dan ekosistemnya dalam satu lokasi.

Membuat Systemd Service

Buat file /etc/systemd/system/consul-template.service di setiap node:

[Unit]
Description=Consul Template
Requires=network-online.target
After=network-online.target consul.service

[Service]
Restart=always
ExecStart=/usr/bin/consul-template -config=/etc/consul-template.d
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
PIDFile=/run/consul-template.pid
TimeoutSec=10s

[Install]
WantedBy=multi-user.target

Dengan -config=/etc/consul-template.d, consul-template akan membaca semua file .hcl di direktori tersebut. Ini lebih fleksibel jika nanti kita menambah template lain.

Setelah service dibuat, reload systemd:

sudo systemctl daemon-reload

Membuat File Konfigurasi

Buat file /etc/consul-template.d/caddy.hcl di setiap node:

consul {
  token = "<bootstrap-token-from-consul>"
}

template {
  source      = "/consul/config/templates/caddy.ctmpl"
  destination = "/etc/caddy/Caddyfile"
  command     = "systemctl reload caddy"
  wait {
    min = "5s"
    max = "15s"
  }
}

Penjelasan singkat:

  • source mengarah ke template file yang akan kita buat berikutnya.
  • destination langsung ke /etc/caddy/Caddyfile, jadi seluruh Caddyfile di-generate otomatis.
  • command akan me-reload Caddy setelah file berubah.
  • wait mencegah reload berlebihan jika ada banyak perubahan berdekatan.

Membuat Template File

Sekarang buat template file di /consul/config/templates/caddy.ctmpl:

{{- $SERVER_NAME := "fe1" -}}

{
	storage consul {
		address "localhost:8500"
		token "<bootstrap-token-from-consul>"
		timeout 300
	}
}

{{ range services }}

{{- $domains := sprig_list }}
{{- range service .Name }}

	{{- range $k, $v := .ServiceMeta }}
		{{- if eq $k "domain" }}
		{{- $domains = sprig_append $domains $v }}
		{{- $domains = $domains | sprig_uniq }}
		{{- end }}
	{{- end }}

{{- end }}

{{- if gt (len $domains) 0 }}
{{- $domains | sprig_join "," }} {
	handle /* {
		reverse_proxy {{ range service .Name }}{{ .Address }}:{{ .Port }} {{ end }} {
			lb_policy round_robin
			lb_retries 5

			{{- $sets := sprig_list }}
			{{- range service .Name }}
			{{- range $k, $v := .ServiceMeta }}

			{{- if not (in ($sets | sprig_join ",") $k) }}
			{{- if in $k "health" }}
			{{ $k }} {{ $v }}
			{{- $sets = sprig_append $sets $k -}}
			{{- end }}
			{{- end }}

			{{- end }}
			{{- end }}
		}
		
		header >Server {{ $SERVER_NAME }}
		header >Via {{ $SERVER_NAME }}
	}
}
{{ end }}
{{- end }}

Template ini menggunakan Go template dengan Sprig functions untuk logic tambahan. Dokumentasi Sprig ada di http://masterminds.github.io/sprig/.

Penjelasan Singkat

  • Template membaca ServiceMeta dari setiap service dan mencari key domain.
  • Jika ada domain, Caddy akan membuat site block untuk domain tersebut.
  • Semua instance service akan dimasukkan ke reverse_proxy dengan load balancing.
  • Health check diambil dari ServiceMeta yang memiliki prefix health.
  • Header Server dan Via ditambahkan untuk identifikasi node.

Enable dan Start Consul Template

sudo systemctl enable --now consul-template

# Cek status
sudo systemctl status consul-template

Consul-template akan mulai memantau Consul dan menghasilkan /etc/caddy/Caddyfile. Kita bisa cek hasilnya:

cat /etc/caddy/Caddyfile

Update Job Nomad dengan ServiceMeta

Agar service kita bisa di-detect oleh consul-template dan mendapatkan routing otomatis, kita perlu menambahkan ServiceMeta dengan key domain pada service definition. Edit file snake.hcl di workstation:

job "snake-web" {
  datacenters = ["dc1"]

  group "snake" {
    count = 1

    service {
      name = "snake-web"
      port = "http"

      # ServiceMeta untuk konfigurasi domain dan health check
      meta {
        domain          = "snake-web.example.com"
        health_uri      = "/"
        health_interval = "10s"
        health_timeout  = "5s"
      }

      check {
        type     = "http"
        path     = "/"
        interval = "3s"
        timeout  = "2s"
      }
    }

    network {
      port "http" {
        to = 8080
      }
    }

    task "snake" {
      driver = "docker"

      config {
        image = "aschil/snake:latest"
        ports = ["http"]
      }

      resources {
        cpu    = 50
        memory = 100
      }
    }
  }
}

Update job di cluster:

nomad job run snake.hcl

Setelah job ter-update, consul-template akan mendeteksi ServiceMeta domain dan menghasilkan konfigurasi Caddy baru. Kita bisa verifikasi dengan melihat log consul-template:

sudo journalctl -u consul-template -f

Start Caddy dan Testing

Sekarang kita bisa start Caddy di semua node:

sudo systemctl start caddy

# Cek status
sudo systemctl status caddy

# Lihat log jika ada error
sudo journalctl -u caddy -f

Verifikasi Konfigurasi yang Di-generate

Setelah consul-template mendeteksi service dengan ServiceMeta domain, konfigurasi Caddy akan di-generate. Mari kita periksa hasilnya:

cat /etc/caddy/Caddyfile

Setup DNS dan TLS Certificate

Untuk mendapatkan sertifikat TLS dari Let’s Encrypt menggunakan HTTP challenge pada setup multi-node seperti ini, kita perlu strategi khusus:

Langkah 1: Gunakan Satu IP Dulu

Tambahkan hanya satu IP (misalnya node-1) ke DNS A record di Cloudflare:

A    snake-web.example.com    203.0.113.10

Tunggu propagasi DNS (beberapa menit), kemudian akses https://snake-web.example.com dari browser. Caddy akan otomatis meminta sertifikat dari Let’s Encrypt menggunakan HTTP challenge.

Langkah 2: Tambahkan IP Lainnya

Setelah sertifikat berhasil didapatkan (cek dengan membuka domain di browser), tambahkan IP node lainnya ke DNS:

A    snake-web.example.com    203.0.113.10
A    snake-web.example.com    203.0.113.11
A    snake-web.example.com    203.0.113.12

Dengan setup storage Consul, sertifikat yang sudah didapatkan akan ter-sync ke semua node. Jadi semua node dapat melayani HTTPS dengan sertifikat yang sama.

💡 Mengapa Perlu Strategi Ini?
Pada HTTP challenge, Let’s Encrypt akan mengirim request ke domain untuk memverifikasi kepemilikan. Jika DNS memiliki multiple A record, request bisa masuk ke node mana saja. Node yang menerima challenge belum tentu node yang mengajukan permintaan sertifikat, sehingga validasi bisa gagal.

Dengan menggunakan satu IP dulu, kita pastikan request challenge masuk ke node yang tepat. Setelah sertifikat didapat dan tersimpan di Consul KV, semua node dapat mengaksesnya.

Verifikasi Routing

Buka browser dan akses https://snake-web.example.com. Kamu seharusnya bisa melihat game snake yang berjalan.

Sekarang kita sudah memiliki setup yang sepenuhnya otomatis:

  1. Deploy job baru di Nomad dengan ServiceMeta domain
  2. Nomad mendaftarkan service ke Consul
  3. Consul-template mendeteksi service baru
  4. Konfigurasi Caddy di-generate otomatis
  5. Caddy di-reload dan langsung bisa route traffic ke service baru

Tidak ada konfigurasi manual yang perlu dilakukan. Semua berjalan otomatis.

Untuk melihat bagaimana sistem merespons perubahan, mari kita scale up aplikasi snake-web dari 1 instance menjadi 3 instance. Edit file snake.hcl di workstation, ubah count menjadi 3:

job "snake-web" {
  datacenters = ["dc1"]

  group "snake" {
    count = 3

    service {
      name = "snake-web"
      port = "http"

      meta {
        domain          = "snake-web.example.com"
        health_uri      = "/"
        health_interval = "10s"
        health_timeout  = "5s"
      }

      check {
        type     = "http"
        path     = "/"
        interval = "3s"
        timeout  = "2s"
      }
    }

    network {
      port "http" {
        to = 8080
      }
    }

    task "snake" {
      driver = "docker"

      config {
        image = "aschil/snake:latest"
        ports = ["http"]
      }

      resources {
        cpu    = 50
        memory = 100
      }
    }
  }
}

Deploy perubahan:

nomad job run snake.hcl

Nomad akan men-deploy 2 instance tambahan. Lihat status deployment:

nomad job status snake-web

Setelah deployment selesai, consul-template akan otomatis mendeteksi ada 3 instance service dan meng-update Caddyfile. Tunggu beberapa detik (sesuai wait time 5-15 detik), kemudian cek Caddyfile:

cat /etc/caddy/Caddyfile

Seharusnya sekarang ada 3 upstream di dalam reverse_proxy.

Penutup

Dengan setup ini, kita sudah memiliki cluster Nomad yang lengkap dengan:

  • Service discovery otomatis menggunakan Consul
  • Dynamic reverse proxy dengan Caddy dan consul-template
  • TLS certificate management otomatis dari Let’s Encrypt
  • Multi-node setup untuk high availability
  • Load balancing otomatis untuk service yang di-scale

Alur kerja deployment menjadi sangat sederhana:

# 1. Buat job file dengan service tag "web"
# 2. Deploy ke Nomad
nomad job run myapp.hcl

# 3. Tambahkan DNS record
# 4. Done! Service otomatis accessible via HTTPS

Semua proses routing, TLS, dan load balancing ditangani secara otomatis oleh stack Nomad → Consul → consul-template → Caddy.

⚠️ Production Considerations
Setup ini cocok untuk belajar dan deployment skala kecil. Untuk production, pertimbangkan:

  • Pisahkan node server dan client Nomad
  • Gunakan private network untuk komunikasi internal
  • Implementasi ACL yang lebih granular
  • Monitoring dan alerting
  • Backup regular untuk Consul state
  • Rate limiting dan security headers di Caddy
  • Dedicated load balancer di depan node Caddy

Selamat! Kamu sudah berhasil membuat cluster orchestration dengan HashiCorp Nomad. 🎉