Kemarin gue habis melihat tweet dari manajer gue yang membahas tentang Hashicorp Waypoint. Meskipun belum pernah mendengar sebelumnya, yang gue tau produk-produknya Hashicorp itu pasti keren.

Setelah tadi malam baca-baca sedikit dokumentasinya (gue persingkat jadi Waypoint aja) Waypoint, ini jadi mengingatkan gue tentang apa yang sedang gue kembangkan juga, namun yang gue lebih spesifik untuk front end domain.

Sebelum kita membahas lebih lanjut, mari kita bahas sedikit tentang apa itu Waypoint.

Di halaman utamanya, Waypoint memiliki motto "Build. Deploy. Release — Waypoint provides a modern workflow to build, deploy, and release across platforms." yang mengingatkan gue dengan motto nya Vercel yakni Develop. Preview. Ship.

Waypoint menurut gue versi sederhana (dan ringan) nya dari CloudFoundry, dan versi "self-hosted" nya dari Heroku. Waypoint berada di ranah Platform as a Service (PaaS) yang memudahkan organisasi untuk bisa bergerak lebih cepat tanpa harus berurusan dengan kompleksitas dalam membangun dan memelihara infrastruktur terkait dalam menjalankan aplikasi tersebut.

Secara high-level, arsitektur dari Waypoint seperti ini:

Sumber

Yang mana sangat-sangat-sangat familiar dengan apa yang gue tau, karena apa yang gue kembangkan pun menggunakan pendekatan tersebut (Entrypoint & Deployment), sebagai referensi bisa cek ini.

Intinya, setiap deployment pasti memiliki endpoint sendiri. Di dunia cloud, kita seringkali mendengar kata edge. Jika bingung maksud dari edge, kamu bisa sederhanakan itu hanyalah sebuah server yang berada di lokasi terdekat yang bertindak sebagai reverse proxy ke server aslinya (origin).

Nah, si edge tersebut yang meneruskan permintaan (entrypoint) ke sumber nya (origin).

Kita bandingkan dengan proses "deployment" biasa dulu.

Misal, blog ini. Blog ini memiliki domain (entrypoint) blog.evilfactory.id. Ketika kamu mengakses blog.evilfactory.id, peramban/klien akan meminta ke DNS Server akan alamat IP dari domain tersebut.

Setelah mendapatkan alamat IP nya (172.104.42.136), peramban/klien akan mengirim permintaan ke peladen yang memiliki alamat IP tersebut, yang umum nya ke port 80/443.

Karena gue pakai Reverse Proxy (Traefik), permintaan tersebut di handle oleh Traefik, dan karena Traefik tau permintaan tersebut apa (berdasarkan header Host), maka si Traefik meneruskan permintaannya ke instace ghost yang berada di port (internal) 2368 sehingga kamu bisa melihat halaman ini.

Berbicara tentang Waypoint, kurang lebih apa yang dilakukan waypoint pun sama. Bedanya, mungkin tidak menggunakan Traefik, tapi Waypoint hahaha.

Ilustrasi sederhana

Setiap deployment yang dilakukan pasti mendapatkan entrypoint yang by default diatas domain waypoint.run yang sudah mendukung SSL yang sertifikatnya berlaku secara wildcard dari domain tersebut.

Oke mungkin pertanyaannya sekarang adalah "mengapa kita membutuhkan pendekatan seperti ini?", membutuhkan pendekatan "Develop. Preview. Ship™", pendekatan "Build. Deploy. Release™".

Sekarang mari kita bahas itu terlebih dahulu.

Deployment Preview

Dimulai dari sebuah keresahan ketika founder dari...

Anjing dah kayak cerita startup, skip.

Oke oke serius, jadi, di era yang serba bergerak cepat seperti sekarang ini, organisasi dituntut untuk dapat bergerak lebih cepat juga. Ber-inovasi lebih cepat. Validasi ide lebih cepat. Terjun ke pasar lebih cepat.

Tak heran layanan seperti Heroku, laris manis di pasar karena organisasi hanya perlu fokus ke sesuatu yang dia kembangkan, bukan hal-hal boring terkait apa yang harus dilakukan terlebih dahulu seperti mengatur basis data, mengatur web server, mengatur dns server, dan sebagainya.

Lalu ada hal lain yang lebih mendasar: Lingkungan development. Jika Heroku mungkin lebih cocok untuk lingkungan production, kita belum memiliki sesuatu yang mirip seperti Heroku namun untuk di lingkungan development.

Dan muncul nama-nama baru seperti Zeit Now, Netlify, Fly, dsb yang menawarkan solusi akan permintaan tersebut.

Tujuannya?

Umumnya, dalam mengembangkan sebuah aplikasi, ada 2 hal yang dirasakan oleh pengguna akhir: Fitur dan Bug.

Untuk bisa memastikan fitur yang pengembang kembangkan tersebut stabil dan berjalan sebagaimana mestinya, organisasi harus memastikannya terlebih dahulu dengan pengujian manual yang biasa disebut Smoke Test dan atau Sanity Test.

Karena ini berada di lingkungan development, penguji harus melakukan hal-hal boring seperti docker pull; docker-compose up dan sebagainya untuk bisa menjalankan aplikasi tersebut di mesin nya sendiri.

Beda dengan lingkungan Staging yang mana hampir serupa dengan lingkungan Production—alias sudah selangkah lebih dekat dengan production—di lingkungan ini, mungkin ada berada di beberapa langkah jauh ke production.

Agar lebih singkat, bayangkan ketika organisasi sedang mengembangkan sebuah fitur yang berada di Epic.

Untuk menghemat waktu—karena waktu adalah uang—bagaimana bila kita memberikan lingkungan serupa, namun tanpa perlu banyak upacara?

Hanya sebuah URL, yang me-representasikan proses-proses boring diatas, namun dapat diakses oleh seluruh pihak yang terkait, karena proses tersebut tidak dilakukan di lingkungannya penguji.

Disitulah kita membutuhkan Deployment Preview, sebuah pratinjau sebelum deployment tersebut dibawa hidup/Live.

Sebuah pratinjau yang hanya bisa diakses oleh pihak tertentu, yang nantinya akan diberikan ke pengguna akhir.

Do It Yourself

Kamu bisa saja membuat "platform" tersebut sendiri. Yang kamu butuhkan hanyalah instance aplikasi yang berjalan dan sebuah instance yang berguna untuk meneruskan permintaan yang masuk kepada instance tersebut, silahkan sebut dengan Service Discovery untuk si penerusnya itu.

Dalam konteks menggunakan Docker, gambaran sederhananya bisa seperti ini:

Tapi ada waktu, tenaga, dan uang yang dihabiskan untuk membuat platform tersebut. Dan mungkin, alasan membuat sendiri daripada menggunakan layanan yang sudah ada—untuk menjawab build vs buy—karena alasan keamanan dan kebebasan.

Sekarang, kita memiliki Waypoint.

Kita bisa menggunakan Waypoint di tempat kita sendiri, sehingga tidak perlu khawatir untuk alasan keamanan serta besarnya biaya yang harus dikeluarkan.

Dan juga Open Source, kamu bisa menyesuaikannya dengan kebutuhan kamu bila mau dan harus.

Waypoint® Up and Running

Ada 2 tipe dari si Waypoint ini: Client dan Server.

Client ini anggap seperti heroku-cli, now-cli, dan sebagainya yang berguna untuk berkomunikasi dengan Waypoint Server.

Server ini adalah proses dimana Waypoint berjalan yang berguna untuk melakukan build, release, dan deploy aplikasi kamu.

Karena pada akhirnya, harus ada tempat untuk aplikasi tersebut berjalan. Dan jika tempat tersebut bukan di mesin kamu, berarti berada di mesin orang lain Cloud.

Kita mulai dari instalasi Waypoint Server.

Sebelumnya, kamu harus memiliki server terlebih dahulu yang memiliki alamat IP publik.

Jika belum memiliki, silahkan sewa di Linode dengan harga yang paling murah $5/bulan.

Dan pastikan kamu sudah memasang docker di server kamu, karena jika tidak pakai Docker maka akan menggunakan Kubernetes hahaha.

Hal pertama yang harus dilakukan adalah mengunduh docker image si waypoint yang bisa dilakukan dengan perintah berikut:

$ docker pull hashicorp/waypoint:latest

Setelah itu, kita pasang Waypoint Client yang perintah lengkapnya bisa diakses disini.

Jika sudah, kita bootstrap Waypoint Server kita dengan menjalankan perintah berikut:

$ waypoint install --platform=docker -accept-tos

Setelah selesai, akan mendapatkan pesan keluaran yang kurang lebih seperti ini:

✓ Server container started
Waypoint server successfully installed and configured!

The CLI has been configured to connect to the server automatically. This
connection information is saved in the CLI context named "install-14045".
Use the "waypoint context" CLI to manage CLI contexts.

The server has been configured to advertise the following address for
entrypoint communications. This must be a reachable address for all your
deployments. If this is incorrect, manually set it using the CLI command
"waypoint server config-set".

Advertise Address: waypoint-server:9701
HTTP UI Address: localhost:9702

Jika kamu menggunakan Firewall, silahkan allow port tersebut matikan terlebih dahulu hahaha nanti akan dijelaskan kenapanya.

Kita mulai dari membuat aplikasi Next.js, mari kita buat aplikasi $1B yang menampilkan "Hello World" di Next.js.

$ mkdir -p my-project/pages
$ cd my-project
$ yarn add react react-dom next
$ echo "export default () => <p>Hello World</p>" > pages/index.js

Cara diatas tidak direkomendasikan dan hanya untuk keperluan konten, jadi, ya, maafkan hehe.

Waypoint menggunakan berkas konfigurasi dengan format HCL/HashiCorp Configuration Language dengan nama waypoint.hcl. Silahkan jalankan waypoint init dan kita lihat berkas waypoint.hcl tersebut yang dibuat untuk kita sebagai sampel.

# The name of your project. A project typically maps 1:1 to a VCS repository.
# This name must be unique for your Waypoint server. If you're running in# local mode, this must be unique to your machine.
project = "my-project"

# Labels can be specified for organizational purposes.
# labels = { "foo" = "bar" }

# An application to deploy.
app "web" {
    # Build specifies how an application should be deployed. In this case,
    # we'll build using a Dockerfile and keeping it in a local registry.
    build {
        use "docker" {}
        
        # Uncomment below to use a remote docker registry to push your built images.
        #
        # registry {
        #   use "docker" {
        #     image = "registry.example.com/image"
        #     tag   = "latest"
        #   }
        # }

    }

    # Deploy to Docker
    deploy {
        use "docker" {}
    }
}

Oke, di Waypoint ada 3 stage yang akan dilalui: Build, Deploy, Release. Berarti kita harus memberitahu Waypoint apa yang harus dilakukan di setiap stage tersebut.

Build

Dalam proses build, kamu bisa menggunakan 2 cara: Melalui Docker langsung atau melalui Cloudnative Buildpacks.

Agar lebih sederhana, mari kita menggunakan Docker. Karena menggunakan Docker, kita harus memiliki berkas Dockerfile di project kita. Mari kita buat berkas minimalnya untuk project Next.js.

Sebelumnya, kita buat berkas .dockerignore juga yang bertindak seperti .gitignore namun untuk di Docker.

$ printf "node_modules\n.next\n*.db\nbuild\nout\n" >> .dockerignore

Sekarang, kita buat Dockerfile.

FROM node:12.16.3-alpine3.9

ADD ./package.json /app/package.json
ADD ./yarn.lock /app/yarn.lock

WORKDIR /app

RUN yarn --production

ADD . /app

RUN yarn next build

CMD ["yarn", "next", "start"]

Sintaks diatas pada dasarnya hanya melakukan hal-hal boring yang biasa kamu lakukan untuk membuat project tersebut Up & Running. Sekarang kita sesuaikan waypoint.hcl nya dan menjadi seperti ini:

project = "unicorn"

app "unicorn" {
  build {
    use "docker" {}
  }

  deploy { 
    use "docker" {}
  }
}

Disitu nama project dan aplikasi kita adalah "unicorn" yang mana gak penting-penting amat tapi harus unik namanya (tidak boleh sama).

Sekarang, mari kita coba build dengan perintah waypoint build, dan setelah selesai, ketika kita jalankan perintah docker images, maka kita akan mendapatkan image baru dengan nama waypoint.local/unicorn.

Sekarang kita coba deploy dengan perintah waypoint deploy, jika pengaturannya benar, maka kamu akan mendapatkan URL yang dapat diakses oleh publik, ini contohnya: https://optionally-composed-gar.waypoint.run

Jika kita menjalankan perintah docker ps, maka akan ada container baru yang berjalan di mesin kita:

CONTAINER ID        IMAGE                               COMMAND                  CREATED              STATUS                  PORTS                                            NAMES

0f10293e38f7        waypoint.local/unicorn:latest       "/waypoint-entrypoin…"   About a minute ago   Up About a minute       0.0.0.0:32774->3000/tcp                          unicorn-01EMV1XXR52ZK46TYD4Y1XXTSV

Sangat mudah!

Integrasi dengan Gitlab (CI)

Sekarang kita ke yang lebih ke kasus nyata, mari kita buat sistem deployment preview sederhana sebagaimana yang dijanjikan oleh judul tulisan ini.

Sebelumnya pernah menulis serupa namun dalam membuat staging server yang berjudul Membuat Staging server menggunakan Docker + Traefik + Gitlab CI jika tertarik untuk dibaca.

Disini kita akan menggunakan 3 stage:

  • Test
  • Preview
  • Deploy

Biar terlihat seperti motto startup. Yang diasumsikan:

  • Di Test, hanya murni melakukan test
  • Di Preview, melakukan build dan melakukan "deployment preview"
  • Di Deploy, kita melakukan "bersih-bersih" terkait preview, push ke registry docker image nya

Aplikasi yang kita buat sangat sederhana, hanya sebuah aplikasi Next.js yang memiliki 2 route (Index dan About), dan di halaman about, kita akan memanggil API dari somewhere.

Kita hanya menggunakan Waypoint sebagai Deployment Preview bukan sebagai lingkungan Production, jadi, kita hanya melakukan push ke registry ketika stage Deploy yang dilakukan secara manual (harus klik) dengan tag nama branch.

Untuk terkait aplikasi, silahkan clone repository ini, karena disini kita akan fokus membahas terkait infra.

Sebelum kita membahas, ada hal yang perlu diketahui terlebuh dahulu oleh pemirsa sekalian.

Kita sudah mengetahui arsitekturnya secara high-level di atas, namun ada beberapa hal yang belum kita lihat: Alur operasional.

Di atas kita sudah mencoba untuk melakukan waypoint build dan waypoint deploy, pertanyaannya, apa yang terjadi dibalik perintah tersebut?

Ilustrasi

Apa maksud dari gambar diatas?

Gak tau hehehe.

Oke oke, maksudnya adalah setiap operasi pasti diinisialisi oleh client atau cli. Nah, ketika melakukan operasi, dia pasti memberitahu server akan adanya tugas baru sekaligus menjalankan runner.

Runner adalah tempat dimana tugas-tugas tersebut dilakukan, dari build; deploy, sampai release.

Karena kita pakai Gitlab (dan CI nya), berarti client dan runner nya berada di bagian nya Gitlab. Waypoint Server kamu mengetahui ada tugas build; deploy, dan release, dan juga mengetahui kemana request harus diteruskan, namun, deployment kamu berada di bagian Gitlab.

Sederhananya, docker container yang berjalan ada di server nya Gitlab, bukan di server kamu.

Gue udah berusaha keras dalam menyelesaikan masalah ini, dari menggunakan docker context; waypoint context, sampai ke ssh. SSH bisa saja dilakukan, pertanyaannya, untuk apa kita pakai shared runner nya Gitlab CI, toh?

Juga, dokumentasi terkait Runner agent dan -remote ini gak jelas banget. Dan gue gak ada waktu untuk baca sumber kodenya.

Solusinya?

Fuck.

Oke oke, singkatnya, runner harus berjalan di server kita.

Yang berarti, gue harus menggunakan (gitlab) runner sendiri yang berjalan di server gue, karena si Waypoint kagak bisa pakai docker context.

Alias, proses "terkait CI" berada di server gue.

Yang berarti, kalau lo inget tahun-tahun lalu zaman-zaman nya pakai Zeit Now versi 1 atau Heroku, yang mana pas lu jalanin now atau git push heroku something, terus lu harus nunggu proses (build+deploy+release) tersebut selesai, nah si Waypoint juga kurang lebih seperti itu, tapi sekarang tahun 2020 akhir.

Oke oke, jika belum memasang gitlab runner sendiri, silahkan pasang sendiri.

Jika sudah, mari kita bahas satu-satu per stage.

Test

Di stage test, kita hanya melakukan yarn install dan yarn test, ini untuk memastikan behavior aplikasi kita sesuai harapan di lingkungan versi Node.js yang kita gunakan di production.

test:
  image: node:12.19.0
  stage: test
  script:
    - yarn
    - yarn test

Dengan kasus, di production kita menggunakan Node.js versi 12.9.0 (LTS).

Preview

Disini yang paling utama, karena kita akan berhubungan dengan Waypoint Server kita.

Di contoh  ini, kita akan menggunakan "token" daripada "invite token" sebagaimana yang dibahas di bagian Authentication, karena gue anggep ini sebagaimana kita melakukan "ssh handshake" namun menggunakan password.

Kita buat token nya dengan perintah berikut:

$ waypoint token new

Dan simpan token yang sudah dibuat tersebut di tempat yang aman seperti di laci fbi.

Oke oke intinya simpan di tempat yang sedikit aman seperti di env variable, karena siapapun yang memiliki token tersebut maka memiliki otoritas untuk bertindak "sebagai kamu" dalam konteks Waypoint.

Nah, disini kita melakukan hal-hal boring yang biasa kita lakukan di mesin kita seperti waypoint init, waypoint up, dsb yang intinya buat berurusan dengan Waypoint untuk melakukan D E P L O Y M E N T   P R E V I E W.

preview:
  image: docker:19
  stage: preview
  services:
    - docker:19-dind
  variables:
    WAYPOINT_VERSION: 0.1.2
    WAYPOINT_SERVER_TLS: 1
    WAYPOINT_SERVER_TLS_SKIP_VERIFY: 1
  script:
    - wget -q -O /tmp/waypoint.zip https://releases.hashicorp.com/waypoint/${WAYPOINT_VERSION}/waypoint_${WAYPOINT_VERSION}_linux_amd64.zip
    - unzip -d /usr/local/bin /tmp/waypoint.zip
    - rm -rf /tmp/waypoint*
    - waypoint init
    - waypoint up -workspace $CI_COMMIT_BRANCH

Perintah waypoint up pada dasarnya adalah melakukan waypoint {build,deploy,release} yang masing-masing dipelajari lebih lanjut disini kalo penasaran banget.

Nah, untuk bagian -workspace, ini berguna khususnya untuk alur kerja yang menggunakan git. Jadi, setiap aplikasi pasti memiliki URL yang unik, kan? Nah, ketika seseorang melakukan deployment, by default URL utama tersebut akan mengarah ke perubahan yang terbaru, meskipun perubahan tersebut berada di cabang lain (misal feat-add-button) bukan di cabang utama (misal main).

Disini parameter -workspace membantu, jadi, ketika seseorang membuat perubahan di cabang lain (silahkan lihat bagian CI_COMMIT_BRANCH yang menjadi nama workspace), maka kita akan mendapatkan URL lain, namun masih tetap untuk project tersebut.

Jangan lupa untuk menyetel env variable untuk WAYPOINT_SERVER_ADDR dan WAYPOINT_SERVER_TOKEN di pengaturan CI/CD di repository Gitlab untuk project kamu.

Deploy

Ini adalah tahap terakhir, dan tidak ada hubungannya dengan Waypoint sebenarnya kalo di konteks ini.

Di organisasi tempat gue kerja, kita menggunakan Docker dalam deployment, dan mengunakan strategi rilis via git tag like the rest of us sih sebenernya.

Dan CD di kita adalah Continuous Delivery bukan Deployment, jadi untuk deployment, kita masih melakukan manual.

Untuk konten, kita pun akan sama menggunakan trigger secara manual via klik sebagaimana yang pernah kita bahas disini. Tapi disini kita akan pakai tag berdasarkan last commit id biar lebih simple.

deploy:
  image: docker:19
  stage: deploy
  variables:
    IMAGE_NAME: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
  services:
    - docker:19-dind
  when: manual
  only:
    - master
  script:
    - echo -n $CI_JOB_TOKEN | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
    - docker build -t $IMAGE_NAME .
    - docker push $IMAGE_NAME

Di kasus nyata mungkin proses diatas seharusnya berada di release, dan di deploy biasanya melakukan pull terhadap image tersebut, lalu doing something like sshpass docker-compose up -d sebagai contoh.

Tapi untuk konten, biarkan seperti ini.

Oke sejauh ini, kode lengkap dari .gitlab-ci.yml kurang lebih seperti ini:

stages:
  - test
  - preview
  - deploy

test:
  image: node:12.19.0
  stage: test
  script:
    - yarn
    - yarn test

preview:
  image: docker:19
  stage: preview
  services:
    - docker:19-dind
  variables:
    WAYPOINT_VERSION: 0.1.2
    WAYPOINT_SERVER_TLS: 1
    WAYPOINT_SERVER_TLS_SKIP_VERIFY: 1
  script:
    - wget -q -O /tmp/waypoint.zip https://releases.hashicorp.com/waypoint/${WAYPOINT_VERSION}/waypoint_${WAYPOINT_VERSION}_linux_amd64.zip
    - unzip -d /usr/local/bin /tmp/waypoint.zip
    - waypoint init
    - waypoint up -workspace $CI_COMMIT_BRANCH

deploy:
  image: docker:19
  stage: deploy
  variables:
    IMAGE_NAME: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
  services:
    - docker:19-dind
  when: manual
  only:
    - main
  script:
    - echo -n $CI_JOB_TOKEN | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
    - docker build -t $IMAGE_NAME .
    - docker push $IMAGE_NAME

Dan berikut tangkapan layar dari aplikasi yang kita kembangkan yang bisa diakses disini.

Tangkapan layar aplikasi unicorn

Sekarang gue akan coba buat perubahan, misal menambah Waypoint ke Hello World di branch baru.

  const IndexPage = () => (
   <div>
-    <h2>Hello World</h2>
+    <h2>Hello World, Waypoint!</h2>
     <p>
       <Link href='/about'>
         <a>About me</a>

Lalu gue push perubahan tersebut, dan berikut tangkapan layar yang ada di CI untuk stage preview.

Tangkapan layar stage preview

Dan bila URL tersebut diakses, maka sesuai dengan yang kita harapkan:

https://informally-emerging-elephant.waypoint.run/

Penutup

Konfigurasi terkait gitlab diatas tentu tidak disarankan untuk lingkungan production, selain karena saya adalah anak front end bukan SRE juga karena tidak ada penangan untuk masalah terkait cache, strategi rilis, dsb.

Dan juga, Waypoint ini masih di versi 0.0.2, yang bahkan versi minor nya saja belum menyentuh angka satu.

Weekend kali ini lumayan produktif karena bermain-main dengan Waypoint, ada beberapa masalah terkait real world case yang belum di-cover disini seperti menjalankan spesifik backend (yang meskipun sepertinya bisa diselesaikan dengan sed(1) 😉), hanya trigger ketika Merge Request, dsb.

Juga, mungkin stages yang ada kurang umum, jadi mohon maaf ini runner soalnya berjalan diatas VPS $5/bulan yang sudah menjalankan > 5 container, jadi, leave it as is aja lah ya.

Oh iya, alasan menggapa mematikan firewall karena si Waypoint ini akan menjalankan container dengan port random, jika peduli dengan keamanan, maka, ya, silahkan gunakan iptables(8) dengan tangan sendiri :))

Anyway untuk melihat sumber lengkapnya bisa di cek disini.

Terakhir, mungkin Waypoint cocok digunakan di organisasi kamu untuk menerapkan Deployment Preview ini bila malas menggunakan layanan pihak ketiga karena alasan apapun itu atau malas menggunakan CloudFoundry karena ada kata cloud nya.

01010100 01100101 01110010 01101001 01101101 01100001 00100000 01101011 01100001 01110011 01101001 01101000 00100000 01101011 01100101 01110000 01100001 01100100 01100001 00100000 01001000 01100001 01110011 01101000 01101001 01100011 01101111 01110010 01110000 00100000 01110101 01101110 01110100 01110101 01101011 00100000 01101001 01101011 01100001 01101110 00100000 01101110 01111001 01100001 00100000 00111010 00101001