Latar belakang

Bagi organisasi yang mengimplementasikan konsep microservices, akan mengalami kesulitan dalam mengatur penempatan atau deployment aplikasi-aplikasinya ketika aplikasinya semakin banyak, apalagi load-nya yang semakin tinggi. Bukan hanya deployment yang menjadi tantangannya, tapi juga bagaimana memantau aplikasi kita agar selalu berjalan sesuai kebutuhan.

Atas dasar itu diperlukanlah tool untuk membantu mengatasi kesulitan itu, tentu dengan cara otomatis dan seminimal mungkin campur tangan manusia. Sebetulnya ada beberapa tools untuk mengatasi masalah ini, namun dalam tulisan kali ini saya akan fokus memperkenalkan nomad dari Hashicorp.

Apakah nomad ini?

Nomad adalah tool untuk deploy, manage, & scale suatu service atau aplikasi. Tool ini bisa menangani ketiga fitur utama tersebut secara otomatis, itulah mengapa tool semacam ini disebut orchestrator karena seperti seorang konduktor orkestra yang mengatur ritme suatu proses yang ingin dijalankan.

Deploy and Manage Any Application on Any Infrastructure with Ease

Slogan dari nomad ini menyebutkan bahwa kita bisa mengatur berbagai jenis aplikasi. Ya, anda tidak salah baca, “berbagai jenis aplikasi” yang mana bukan hanya berupa container, bisa juga legacy binary app, maupun batch jobs. Lalu dapat dijalankan pada berbagai macam platform infrastructure.

Kenapa & Kapan memerlukan nomad?

Seperti yang sudah saya bocorkan di awal, ada beberapa hal yang akan membuat kita membutuhkan tool semacam ini, di antaranya:

  • Services yang semakin banyak
  • Menggunakan banyak host server
  • Jika kita menginginkan otomatisasi, tentu environment-nya akan bertransformasi dari yang statis menjadi dinamis.
  • Kita akan terbantu oleh strategi deployment-nya, yang mana akan menghindarkan aplikasi kita dari downtime, termasuk ketika akan rilis versi baru dari aplikasi kita.

Kenapa tidak Kubernetes?

Ya, tidak dapat dipungkiri, kubernetes sangat populer sebagai orkestrasi service, dan sangat mungkin Anda lebih sering mendengar kubernetes dibanding nomad. Tapi kubernetes mungkin saja tidak cocok atau menjadi pilihan bagi beberapa organisasi. Dan nomad sudah dapat memenuhi kebutuhan minimum dari apa yang kubernetes tawarkan. Beberapa alasan kenapa tidak memilih kubernetes adalah:

  • Kurva pembelajaran kubernetes cukup curam. Fiturnya cukup kompleks karena merupakan end to end container platform.
  • Bagi sebagian orang kubernetes itu overkill¹. Ketika kita hanya perlu a, b, c, kenapa kita harus ambil sampai j? 😏
  • Nomad lebih sederhana, dan kita dapat memutuskan integrasi dengan tool lain untuk memenuhi kebutuhan tambahannya.
  • Sebagai tambahan, di efishery, kami mencoba menyelesaikan kebutuhan kami dengan alternatif yang lebih sederhana & cocok, dan yang terpenting, berusaha untuk tidak terbawa hype akan tool yang populer 🤓

Bagaimana cara mereka bekerja?

Istilah dasar yang digunakan pada nomad adalah server dan agent (atau client). Server berperan sebagai controller, sedangkan agent sebagai eksekutor pada masing-masing host. Pada dasarnya nomad harus terpasang pada masing-masing host. Server dan agent akan membaca file konfigurasinya masing-masing, yang mana konfigurasi ini mengatur cara kerja nomad terhadap host dan cluster server kita. Selain file konfigurasi, server memiliki tugas tambahan, yaitu membaca job file. Job file ini yang mengatur cara kerja nomad terhadap aplikasi kita.

Nomad dapat berperan sebagai server maupun client/agent sekaligus, ini bisa dilakukan sebagai latihan, tapi untuk lingkungan production, ini tidak disarankan, dan juga, 3 atau 5 nomad server sangat direkomendasikan demi ketersediaan yang tinggi (high availability). Tujuan akhir latihan ini adalah menghasilkan arsitektur seperti pada gambar di bawah ini:

Saya rasa cukup perkenalannya, mari kita mulai mempelajari cara menggunakannya dan juga cara mereka bekerja. (Di sini saya contohkan dengan menggunakan linux ubuntu).

Install

$ curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
$ sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
$ sudo apt-get update && sudo apt-get install nomad

//verifikasi instalasi nomad
$ nomad

Setelah berhasil install, sekarang coba jalankan sudo nomad agent -dev, tanpa file konfigurasi, nomad akan berjalan sebagai server dan juga client.

==> No configuration files loaded
==> Starting Nomad agent...
==> Nomad agent configuration:

Advertise Addrs: HTTP: 127.0.0.1:4646; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
Bind Addrs: HTTP: 127.0.0.1:4646; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
                Client: true
             Log Level: DEBUG
                Region: global (DC: dc1)
                Server: true
               Version: 0.12.1

Untuk melihat host/node mana saja yang sudah terpasang agent, buka terminal baru, dan jalankan nomad node status pada kondisi nomad server masih berjalan. Karena kita baru menjalankan agent pada 1 host (server yang berperan sebagai client/agent sekaligus), maka akan muncul 1 row node.

ID        DC   Name            Class   Drain  Eligibility  Status
f98051bd  dc1  hostname-server <none>  false  eligible     ready

Jika kita menjalankan 3 atau 5 nomad server, maka mereka berkomunikasi dengan menggunakan gossip protocol² agar satu sama lain selalu *up-to-date *dan siap untuk saling menggantikan jika server leader mati. Coba jalankan nomad server members, maka akan muncul:

Name                   Address    Port  Status  Leader  Protocol  Build   Datacenter  Region
hostname-server.global 127.0.0.1  4648  alive   true    2         0.12.1  dc1         global

Job File / JobSpec

Kita bisa memiliki banyak job file atau jobspec, berarti nama jobspec harus unik secara global pada project kita agar tidak bentrok. Selengkapnya silahkan dibaca di dokumen resminya³. Satu jobspec dapat berisi satu atau banyak group. Satu group dapat memiliki satu atau banyak task. Nah*, task *inilah yang menjadi acuan langsung bagi nomad untuk menjalankan aplikasi kita. Mari kita mulai dengan generate kerangka jobspec, sebagai permulaan nomad job init akan menghasilkan jobspec bernama example.nomad. Coba perhatikan isinya, setiap property konfigurasi (atau nomad menyebutnya dengan stanza) terdapat penjelasannya. Job generator secara default akan menggunakan task docker. Saat ini nomad mendukung 4 driver, docker, qemu, java dan exec .

Untuk latihan kali ini, mari fokus pada stanza task. Jobspec generator secara default membuat task cache redis dengan driver docker, dan pada latihan ini akan saya ubah task nya menjadi aplikasi web nginx, berikut modifikasinya:

task "nginx" {
      driver = "docker"

      config {
        image = "nginx:alpine"

        port_map {
          web = 80
        }
      }
...

lalu di bawahnya, resources.network.port “web” {} , seperti ini:

resources {
    cpu    = 500 # 500 MHz
    memory = 256 # 256MB

    network {
      mbits = 10
      port "web" {}
    }
}
...

Untuk stanza service ini menginstruksikan agar nomad mendaftarkan task tersebut kepada service discovery (dalam hal ini consul yang mana tidak dibahas di sini), karena belum akan kita bahas, maka kita beri comment dulu saja.

#service {
  #  name = "web-nginx"
  #  tags = ["global", "web"]
  #  port = "web"

  #  check {
  #    name     = "alive"
  #    type     = "tcp"
  #    interval = "10s"
  #    timeout  = "2s"
  #  }
#}

Jalankan

Mari kita jalankan job pertama kita dengan nomad job run example.nomad. Jika kita ingin melihat status job, eksekusi command nomad job status <job name> (nama file akan menjadi job name dan ID) :

$ nomad job status example
ID            = example
Name          = example
Submit Date   = 2020-08-02T18:52:28Z
Type          = service
Priority      = 50
Datacenters   = dc1
Namespace     = default
Status        = running
Periodic      = false
Parameterized = false

Summary
Task Group  Queued  Starting  Running  Failed  Complete  Lost
web         0       0         1        0       0         0

Latest Deployment
ID          = c6b89419
Status      = successful
Description = Deployment completed successfully

Deployed
Task Group  Desired  Placed  Healthy  Unhealthy  Progress Deadline
web         1        1       1        0          2020-08-02T19:02:39Z

**Allocations**
ID        Node ID   Task Group  Version  Desired  Status    Created     Modified
**e32c1bdf**  f98051bd  web         5        run      running   2m22s ago   2m12s ago

Nomad akan membuat allocation yang merepresentasikan instance dari task group yang ditempatkan pada node (client). Mari kita inspect allocation berdasarkan ID-nya:nomad alloc status **e32c1bdf**

ID                  = e32c1bdf-2f50-22b9-9e33-fcc86e20461c
Eval ID             = fb5f21f1
Name                = example.web[0]
Node ID             = f98051bd
Node Name           = hostname-server
Job ID              = example
Job Version         = 5
Client Status       = running
Client Description  = Tasks are running
Desired Status      = run
Desired Description = <none>
Created             = 5m40s ago
Modified            = 5m30s ago
Deployment ID       = c6b89419
Deployment Health   = healthy

Task "nginx" is "running"
**Task Resources**
CPU        Memory           Disk     Addresses
0/500 MHz  1.8 MiB/256 MiB  300 MiB  web: 127.0.0.1:26926

Task Events:
Started At     = 2020-08-02T18:52:29Z
Finished At    = N/A
Total Restarts = 0
Last Restart   = N/A

Recent Events:
Time                  Type        Description
2020-08-02T18:52:29Z  Started     Task started by client
2020-08-02T18:52:28Z  Task Setup  Building Task Directory
2020-08-02T18:52:28Z  Received    Task received by client

Secara default, nomad akan menjalankan aplikasi kita dengan port yang dinamis, dapat dilihat pada “Task Resources”, aplikasi saya berjalan pada port 26926 (port akan berbeda-beda setiap allocation, jadi port di mesin anda pun pasti berbeda dengan contoh latihan kita ini). Sebelum kita coba aplikasi kita, mari cek log di dalamnya dengan nomad alloc logs e32c1bdf

/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Configuration complete; ready for start up

Persis seperti ketika kita menjalankan docker nginx secara manual. Sekarang kita coba hit aplikasi web-nya:

$ curl 127.0.0.1:26926
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="[http://nginx.org/](http://nginx.org/)">nginx.org</a>.<br/>
Commercial support is available at
<a href="[http://nginx.com/](http://nginx.com/)">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Sesuai ekspektasi, aplikasi web berjalan di atas docker nginx, mudah bukan? Untuk saat ini, arsitektur nya baru seperti gambar di bawah.

Clustering

Server

Untuk lingkungan production, nomad server dan agent tidak boleh berjalan dalam 1 mesin, maka dari itu mari kita gunakan host tambahan sebagai agent client untuk latihan selanjutnya. Hashicorp membuat jenis konfigurasi baru, bernama HCL (HashiCorp Configuration Language), jika masih kurang familiar, bisa juga menggunakan json. Kita buat konfigurasi pertama kita untuk nomad server:

# Increase log verbosity
log_level = "DEBUG"

# Setup data dir
data_dir = "/tmp/server1"

# Enable the server
server {
    enabled = true

# Self-elect, should be 3 or 5 for production
    bootstrap_expect = 1
}

Sebenarnya ada banyak variabel stanza yang bisa diatur⁴, tapi untuk kemudahan latihan ini, cukup dengan konfigurasi minimal seperti di atas. Kemudian jalankan server menggunakan konfigurasi yang baru saja kita buat, Anda akan melihat bahwa mode client/agent dimatikan:

$ nomad agent -config server.hcl
==> WARNING: Bootstrap mode enabled! Potentially unsafe operation.
==> Loaded configuration from server.hcl
==> Starting Nomad agent...
==> Nomad agent configuration:

Advertise Addrs: HTTP: server_ip:4646; RPC: server_ip:4647; Serf: server_ip:4648
     Bind Addrs: HTTP: 0.0.0.0:4646; RPC: 0.0.0.0:4647; Serf: 0.0.0.0:4648
                Client: false
             Log Level: DEBUG
                Region: global (DC: dc1)
                Server: true
               Version: 0.12.1

Client

Sama seperti server, untuk konfigurasi client pada latihan kali ini, cukup dengan minimal seperti di bawah ini, dan simpan pada host client pertama:

# Increase log verbosity
log_level = "DEBUG"

# Setup data dir
data_dir = "/tmp/client1"

# Give the agent a unique name. Defaults to hostname
name = "client1"

# Enable the client
client {
    enabled = true

# For demo assume we are talking to server1. For production,
    # this should be like "nomad.service.consul:4647" and a system
    # like Consul used for service discovery.
    servers = ["server_ip:4647"]
}

# Modify our port to avoid a collision with server1
ports {
    http = 5656
}

Lalu ulangi dan sesuaikan untuk “client2” di host client kedua. Stanza client.servers menginstruksikan untuk setiap client agar berkomunikasi pada nomad server yang telah kita jalankan di atas tadi. Jalankan dengan perintah sudo nomad agent -config client1.hcl pada host client 1, dan sudo nomad agent -config client2.hcl pada host client 2, maka node status akan menampilkan:

$ nomad node status
ID        DC   Name     Class   Drain  Eligibility  Status
7ad4b137  dc1  client1  <none>  false  eligible     ready
8c95c895  dc1  client2  <none>  false  eligible     ready

Update Jobspec

Sekarang mari kita coba ubah jobspec-nya, misalnya ternyata aplikasi kita sudah overload, dan kita membutuhkan horizontal scaling. Ubah group.countmenjadi 2.

group "web" {
    # The "count" parameter specifies the number of the task groups that should
    # be running under this group. This value must be non-negative and defaults
    # to 1.
    count = 2
...

Setelah itu, cek jobspec dengan nomad job plan example.nomad :

+/- Job: "example"
+/- Task Group: "web" (1 create, 1 in-place update)
  +/- Count: "1" => "2" (forces create)
      Task: "nginx"

Scheduler dry-run:
- All tasks successfully allocated.

Job Modify Index: 169
To submit the job with version verification run:

**nomad job run -check-index 169 example.nomad**

When running the job with the check-index flag, the job will only be run if the
job modify index given matches the server-side version. If the index has
changed, another user has modified the job and the plan's results are
potentially invalid.

Nomad akan konfirmasi perubahan jobspec-nya, dan memberikan index terhadap jobspec update. Dengan nomad job run -check-index 169 example.nomad, job akan dieksekusi sesuai index yang valid, ini menjaga dari perubahan paralel yang mungkin dilakukan oleh engineer lain. Perlu diingat, setiap host client yang akan kita gunakan harus terinstall docker karena pada latihan ini kita menggunakan nomad driver docker.

Silahkan cek job status-nya, anda akan lihat allocations menjadi 3, dengan status 1 complete, dan 2 running.

Allocations
ID        Node ID   Task Group  Version  Desired  Status    Created    Modified
341a1758  8c95c895  web         6        run      running   1m8s ago   58s ago
9f5173fc  7ad4b137  web         6        run      running   23m3s ago  58s ago
6c14a960  f98051bd  web         3        stop     complete  54m6s ago  23m31s ago

1 allocation yang complete itu adalah bagian dari job versi sebelumnya, 2 yang baru adalah 2 task yang berjalan sesuai jobspec update yang baru saja kita lakukan. Sekarang kita coba check masing-masing allocation yang running:

Task "nginx" is "running"
Task Resources
CPU        Memory           Disk     Addresses
0/500 MHz  1.9 MiB/256 MiB  300 MiB  web: 127.0.0.1:**27458**
====================================================================Task "nginx" is "running"
Task Resources
CPU        Memory           Disk     Addresses
0/500 MHz  1.9 MiB/256 MiB  300 MiB  web: 127.0.0.1:**26926**

Dapat kita lihat, sekarang 2 instance aplikasi kita berjalan pada host yang berbeda, agar lebih meyakinkan, coba kita check docker container nya, pada masing-masing host, berjalan container nginx, dengan format nama {taskName}-{allocId}.

$ docker ps -a
55265f5a0846        nginx:alpine                                                                       "/docker-entrypoint.…"   About a minute ago   Up About a minute        client1_ip:**27458**->80/tcp, client1_ip:**27458**->80/udp   **nginx-9f5173fc-b83b-d532-7e03-c35d3659f816**
====================================================================
$ docker ps -a
1456b1e9f98a        nginx:alpine                                                                          "/docker-entrypoint.…"   2 minutes ago       Up 2 minutes        client2_ip:**26926**->80/tcp, client2_ip:**26926**->80/udp   **nginx-341a1758-1934-c25a-0722-2f3c9762cbee**

Selamat! sekarang anda sudah cukup mengerti dasar dari bagaimana nomad bekerja. Nah sekarang pertanyaanya, jika nomad memberikan port secara dinamis, apalagi aplikasi berjalan lebih dari satu instance, bagaimana aplikasi frontend dapat mengetahui port tersebut? dan bagaimana cara mengakses kedua aplikasi tersebut?

Consul to the rescue!

Ya, dengan consul, setiap aplikasi yang dijalankan nomad akan terdaftar, dan dapat dipantau kesehatannya, juga dapat diintegrasikan dengan berbagai load balancer. Ingin tau cara integrasinya? Tunggu kelanjutan dari tulisan ini 😎

Saya cukupkan sesi tulisan ini. Bila ada kritik dan saran boleh tuangkan saja di komentar. Bila tulisan ini bermanfaat bagi anda, bantu share ya. Terima kasih sudah berkunjung.

staypositive. Ciao!

disclaimer: tulisan ini tidak terafiliasi dengan hashicorp, murni sebatas sharing saja. Bukan juga untuk membandingkan nomad dengan kubernetes, kedua tool tersebut memiliki kekurangan dan kelebihan. Pilihan kembali tergantung masing-masing kebutuhan dan kemampuan.

[1] Matthias Endler | Maybe You Don’t Need Kubernetes https://endler.dev/2019/maybe-you-dont-need-kubernetes/

[2] Gossip Protocol https://www.nomadproject.io/docs/internals/gossip

[3] Nomad Job Specification https://www.nomadproject.io/docs/job-specification

[4] Nomad Configuration https://www.nomadproject.io/docs/configuration