Deployment, Continuous Integration, Software Quality Assurance

Deployment

Secara singkat, deployment merupakan suatu aksi yang membuat suatu aplikasi dapat digunakan. Terdapat beberapa strategi deployment yang banyak digunakan pada industri. Saya akan mencoba menjelaskan sebagian dari apa yang saya ketahui, kemudian saya akan mencoba mencocokkan strategi deployment yang saya jelaskan ke strategi yang digunakan pada aplikasi kami.

Sebagai bantuan, katakan ada aplikasi A, yang merupakan aplikasi yang telah ada pada server, dan aplikasi B, yang merupakan aplikasi yang ingin di-deploy. Asumsi juga cara menjalankan aplikasi yang telah di-deploy adalah dengan command yang telah di-setup melalui script yang berjalan secara otomatis.

Strategi Big-Bang atau Replace/Recreate

Strategi yang pertama, yang menurut saya adalah strategi yang paling sederhana, adalah strategi Big-Bang, atau bisa dikatakan juga sebagai strategi Replace/Recreate. Strategi replace dan recreate merupakan strategi yang kurang lebih berjalan seperti ini:  aplikasi A akan diturunkan terlebih dahulu, kemudian aplikasi B akan disalin ke server setelah aplikasi A sepenuhnya turun. Setelah aplikasi B telah disalin secara keseluruhan, maka aplikasi B akan dijalankan.

Strategi ini mengharuskan adanya waktu turun, yang terjadi saat aplikasi A diturunkan, sampai aplikasi B disalin dan selesai dijalankan. Tergantung dari skala aplikasinya, waktu turun dapat menjadi kecil, atau besar. Tentu waktu turun akan bertambah jika terjadi masalah dalam deployment.

Strategi Ramped

Strategi Ramped adalah strategi dimana aplikasi yang telah ada di server digantikan instansinya dengan yang baru sampai semuanya telah digantikan. Hal ini terjadi seperti berikut: Katakan ada sekelompok server A yang disajikan melalui Load Balancer. Satu instansi dari aplikasi B dibuat, kemudian di-deploy.  Ketika instansi tersebut sudah siap menerima traffic, baru ditambahkan ke kelompok server yang disajikan. Kemudian, salah satu dari instansi yang ada di dalam kelompok tersebut akan dimatikan. Hal ini akan berjalan terus sampai instansi aplikasi A sepenuhnya dimatikan dan digantikan dengan instansi aplikasi B.

Instansi yang di-deploy secara bersamaan tidak harus satu. Deployment dapat dilakukan dengan lebih dari satu instansi aplikasi pada waktu tertentu secara bersamaan. Ketika aplikasi B gagal di-deploy, maka dapat dilakukan rollback, yang tentu membutuhkan waktu.

Perlu diingat bahwa agar aplikasi B dapat di-deploy secara keseluruhan, karena aplikasinya di-deploy secara gradual, maka akan memakan waktu yang relatif lebih banyak dibandingkan strategi Big Bang.

Strategi Blue/Green (Biru/Hijau)

Strategi Blue-Green berbeda dengan Strategi Ramped dimana aplikasi B akan di-deploy secara bersamaan dengan aplikasi A yang telah di-deploy sebelumnya. Instansi yang akan di-deploy adalah sama untuk kedua aplikasi tersebut. Ketika aplikasi B telah diuji dan sesuai ketentuan, maka load balancer akan diatur untuk hanya menyajikan instansi dari aplikasi B saja.

Di sini diperoleh keuntungan dimana jika aplikasi B tidak sesuai ketentuan, maka kita hanya perlu mematikan instansinya saja, atau melepaskan koneksi instansi aplikasi B dari load balancer. Hanya saja, karena dibutuhkan jumlah instansi yang sama untuk kedua aplikasi, maka sumber daya yang dibutuhkan adalah dua kali lebih banyak dibandingkan strategi sebelumnya.

Strategi Canary

Strategi Canary berbeda dengan strategi blue-green dimana traffic dipindahkan secara gradual dari aplikasi A ke aplikasi B dengan metrik berupa weight. Ini digunakan ketika test tidak reliable dan developer lebih memilih untuk menguji aplikasinya secara langsung.

Karena deployment dilakukan secara gradual, maka waktu deployment akan relatif lama dibandingkan metode yang melakukannya secara langsung. Tetapi, jika ingin dilakukan rollback, maka akan relatif lebih cepat karena seluruh instansi bisa saja belum semuanya diganti.

Strategi A/B

Strategi A/B mirip dengan Canary, tetapi kondisi yang digunakan bukan hanya weight, melainkan kondisi yang ditentukan secara khusus untuk aplikasi tersebut oleh developer. Ini biasa digunakan untuk deployment dua versi aplikasi yang memiliki desain UI/UX yang berbeda untuk menguji user retention, atau menguji kesuksesan suatu versi berdasarkan statistika.

Strategi Shadow

Strategi Shadow merupakan strategi yang mirip dengan Blue/Green, tetapi alih-alih men-switch secara langsung, traffic yang pergi ke A akan juga dikirimkan ke B. Ini memiliki kelemahan yang serupa dengan Blue/Green, yaitu sumber daya yang dibutuhkan adalah dua kali lebih banyak dibandingkan strategi Ramped.

Keuntungan dari strategi ini adalah ini dapat dijadikan sebagai penguji load, dan juga performa yang memiliki pengaruh yang minim ke user.

Deployment Pada Aplikasi Kami

Pada saat saya berbicara dengan asisten dosen dan menggunakan kata docker, dia mengatakan kepada saya “Buat apa pakai docker?” dengan nada yang “unik”. Saya sampai kesal karena ini bukan hanya sekali, tetapi setiap kali saya berbicara tentang deployment menggunakan docker. Padahal, meski setup docker agak lama di awal, akan ada banyak sekali benefit yang di berikan, beberapa diantaranya yaitu:

  1. Selama production environment dapat menjalankan docker image, maka tidak perlu lagi memikirkan kompatibilitas aplikasi kita dengan production environment dan juga tidak perlu memikirkan dependencies yang butuh diinstal (asumsi pada saat development ini sudah disetup dan tidak didelete setelah selesai development)
  2. Jika anggota memiliki komputer yang kompatibel dengan docker, maka setup dependencies hanya perlu dilakukan sekali.
  3. Dalam environment manapun, asumsi docker image telah disetup secara identik, maka aplikasi akan berjalan secara identik.

Oleh karena itu saya enggan men-setup docker di aplikasi kami, dan berencana ketika aplikasi di-deploy ke production menggunakan cara alternatif yang disarankan oleh asisten dosen saya yaitu:

  1. Meng-copy file ke server
  2. Melakukan install dependencies
  3. Menjalankan server.

Dan juga untuk development, menggunakan cara setup seperti berikut:

  1. Mencopy/clone file ke komputer
  2. Melakukan install dependencies
  3. Mulai development

Khusus untuk staging, kami menggunakan cara yang serupa, hanya saja kami meng-otomasinya. Target deployment di staging adalah Heroku Dyno. Strategi yang kami gunakan adalah strategi Big-Bang Deployment, yang akan terjadi setiap kali kita push ke repository. Untuk lebih detilnya akan dijelaskan pada bagian Continuous Integration dan Software Quality Assurance pada aplikasi kami.

Ini adalah metode yang konvensional dan membutuhkan kita untuk mengetahui:

  1. Spesifikasi hardware dan software (termasuk OS) yang digunakan pada production
  2. Spesifik untuk development, semua hardware dan software yang digunakan oleh setiap anggota pada development team.

Frontend dan backend aplikasi kami menggunakan package manager yang berbeda, backend menggunakan NPM, sementara frontend menggunakan Yarn. Awalnya adalah untuk mengevaluasi keduanya, tetapi saya berencana untuk memindahkan semuanya ke NPM, dikarenakan NPM memiliki satu fitur krusial yang tidak dimiliki oleh Yarn dan berguna untuk development aplikasi kami, yaitu Auto Install Updates for Vulnerable Dependencies.

Fitur ini dapat dijalankan menggunakan command npm audit fix. Ada beberapa parameter lain yang dapat digunakan, tetapi untuk aplikasi kami, menjalankan command tersebut tanpa parameter cukup untuk menghilangkan seluruh vulnerability karena dependency yang ada.

Developer Yarn tidak memiliki rencana untuk mengeluarkan fitur ini anytime soon, karena isu yang mendeskripsikan tidak adanya fitur ini ditutup https://github.com/yarnpkg/yarn/issues/7075. 

Continuous Integration

Continuous Integration adalah praktik pengembangan perangkat lunak dimana developer mengintegrasikan kode ke shared repository sesering mungkin. Meski setiap integrasi akan dibarengi dengan tes dan build yang dilakukan secara otomatis, tes dan build tersebut bukanlah bagian dari Continuous Integration.

Meski dapat disingkat menjadi CI/CD, Continuous Integration berbeda dengan Continuous Deployment dan Continuous Delivery.  Continuous Delivery adalah praktik pengembangan perangkat lunak dimana aplikasi yang dibuat harus deployable jika telah lolos semua tes yang diotomasi. Aplikasi itu kemudian dapat di-deploy secara manual oleh tim Developer. Continuous Deployment mirip dengan Continuous Delivery, hanya saja deployment terjadi secara otomatis.

Software Quality Assurance

Software Quality Assurance adalah proses yang memastikan bahwa apa yang kami lakukan dalam proses pembuatan perangkat lunak adalah sesuai dengan standar yang telah ditetapkan. Untuk kasus kami, karena kami melakukan ini sebagai proyek dari salah satu mata kuliah, maka standar yang kami pakai adalah standar yang telah ditetapkan untuk mata kuliah tersebut.

Continuous Integration dan Software Quality Assurance pada aplikasi kami

Continuous Integration untuk aplikasi kami dihandle secara otomatis melalui CI/CD di dalam repository Gitlab dengan bantuan referensi konfigurasi dalam .gitlab-ci.yml, dan Software Quality Assurance akan dibantu dengan aplikasi SonarQube.

Sebelum saya lanjut ke snippet .gitlab-ci.yml kami, saya akan menjelaskan terlebih dahulu apa itu SonarQube. SonarQube (sebelumnya bernama Sonar) adalah sebuah perkakas yang digunakan untuk menginspeksi kualitas kode dan keamanan dari keseluruhan kode, dan membantu proses review kode. SonarQube mensupport 27 bahasa (termasuk bahasa Javascript yang kami gunakan), dan dapat dimasukkan dalam Pipeline CI/CD yang diatur melalui referensi konfigurasi yang dibuat.

sonarqube continuous inspection

Dengan memakai SonarQube, kami didorong untuk menjunjung tinggi Continuous Inspection, dengan Leak Management, Branch Analysis, Parallel Report Processing, Governance Features, High Availability serta Short Feedback Loop.

Sonar

Selain itu, Sonarqube memiliki Quality Gate, suatu fitur yang menginformasikan apakah aplikasi yang dibuat lolos atau gagal kriteria rilis. Jika telah lolos, maka akan ada tanda “Quality Gate Passed”, dan kalau gagal akan ada tanda “Quality Gate Failed”.

Karena saya rasa penjelasan tentang SonarQube sudah cukup, berikut adalah snippet dari gitlab-ci.yml kami:

stages:
  - test
  - linter
  - qa
  - staging-frontend
  - staging-backend
test-frontend:
  image: node:latest
  stage: test
  before_script:
    - cd frontend
    - yarn install
  script:
    - yarn run test --coverage
  artifacts:
    paths:
      - ./frontend/coverage
    expire_in: 1 hrs
  tags:
    - docker
test-backend:
  image: node:latest
  stage: test
  before_script:
    - cd backend
    - npm install
  script:
    - npm run test --coverage
  artifacts:
    paths:
      - ./backend/coverage
    expire_in: 1 hrs
  tags:
    - docker
linter-frontend:
  image: node:latest
  stage: linter
  before_script:
    - cd frontend
    - npm install
    - yarn add eslint --dev
  script:
    - npx eslint -f json -o report.json src/
  artifacts:
    paths:
      - ./frontend/report.json
    expire_in: 1 hrs
  tags:
    - docker
linter-backend:
  image: node:latest
  stage: linter
  before_script:
    - cd backend
    - npm install
    - npm install eslint --save-dev
  script:
    - npx eslint -f json -o report.json ./
  artifacts:
    paths:
      - ./backend/report.json
    expire_in: 1 hrs
  tags:
    - docker
SonarScanner:
  image: addianto/sonar-scanner-cli:latest
  dependencies:
    - test-frontend
    - test-backend
    - linter-frontend
    - linter-backend
  stage: qa
  script:
  - sonar-scanner -X -Dsonar.host.url="https://pmpl.cs.ui.ac.id/sonarqube" -Dsonar.login=$SONARQUBE_TOKEN -Dsonar.branch.name=$CI_COMMIT_REF_NAME -Dsonar.projectKey=$SONARQUBE_PROJECT_KEY
staging-frontend:
  image: ruby:2.4
  stage: staging-frontend
  before_script:
    - cd frontend
    - gem install dpl
    - wget -qO- https://cli-assets.heroku.com/install-ubuntu.sh | sh
  script:
    - dpl --provider=heroku --app=$HEROKU_APP_NAME --api-key=$HEROKU_API
    - heroku run --app $HEROKU_APP_NAME migrate
  environment:
    name: production
    url: $HEROKU_APP_HOST
  only:
    - staging
staging-backend:
  image: ruby:2.4
  stage: staging-backend
  before_script:
    - cd backend
    - gem install dpl
    - wget -qO- https://cli-assets.heroku.com/install-ubuntu.sh | sh
  script:
    - dpl --provider=heroku --app=$HEROKU_APP_NAME_BACKEND --api-key=$HEROKU_API
    - heroku run --app $HEROKU_APP_NAME migrate
  environment:
    name: production
    url: $HEROKU_APP_HOST
  only:
    - staging

Untuk aplikasi kami, kami membaginya dalam lima stage, dan setiap stage memiliki jumlah job yang berbeda. Berikut adalah konfigurasi job dan stage untuk aplikasi kami:

  1. stage “test”, yang memiliki dua job yaitu “test-frontend” dan “test-backend” yang menjalankan test case untuk setiap aplikasi. Setiap job ini akan menghasilkan artifact berupa hasil dari test beserta coveragenya.
  2. stage “linter”, yang memiliki dua job yaitu “linter-frontend” dan “linter-backend” yang menjalankan linter untuk setiap aplikasi. Setiap job ini akan menghasilkan artifact berupa hasil dari linter.
  3. stage “qa”, yang memiliki satu job yaitu “SonarScanner” yang dijalankan untuk quality assurance bagi branch yang bersangkutan. Karena sonarqube tidak melakukan testing dan linting secara otomatis, dan hanya menerima hasil dari eksekusi test dan linter, maka artifactnya diprovide ke job ini agar bisa diproses oleh sonarqube.
  4. stage “staging-frontend”, yang memiliki satu job yaitu “staging-frontend” yang dijalankan untuk mendeploy aplikasi ke staging environment di heroku.
  5. stage “staging-backend”, yang memiliki satu job yaitu staging-backend yang dijalankan untuk mendeploy aplikasi ke staging environment di heroku.

Alasan mengapa deployment frontend dan backend ke heroku tidak dilakukan pada stage yang sama adalah karena plan gratis heroku hanya memperbolehkan satu concurrent builds.

Alasan mengapa staging dilakukan pada Heroku adalah:

  1. Mensupport banyak bahasa dan environment, termasuk Javascript dan Node.js yang digunakan oleh aplikasi kami. Karena itu, kami tidak perlu memikirkan kompatibilitas.
  2. Heavy lifting is done at the back. Ini sangat memudahkan deployment. Dengan Script sesedikit apa yang anda lihat pada .gitlab-ci.yml dan sedikit setup environment variables pada Gitlab, aplikasi dapat terdeploy dengan sukses.
  3. It’s free and scalable. Banyak dev team telah menggunakan heroku untuk mendeploy MVP mereka dan beberapa dari mereka bahkan tetap menggunakan Heroku sebagai platform of choice karena skalabilitasnya dan entry price gratisnya, meski dalam plan freenya dyno heroku tidur terlalu cepat dan bangunnya cukup lama (tapi tidak apa apa untuk staging environment).

Bagaimana Jika Aplikasi Kami Dijalankan Dengan Bantuan Docker?

Seperti saya jelaskan sebelumnya sebenarnya bisa saja aplikasi kami dijalankan menggunakan docker pada server client, selama docker kompatibel pada mesin tersebut. Maka dari itu, saya akan menjelaskan simulasi running aplikasi menggunakan docker di mesin saya secara lokal.

Sebelumnya, saya akan menjelaskan kembali arsitektur aplikasi kami secara pendek (jika anda ingin melihat secara lengkap penjelasan tentang arsitektur aplikasi kami, maka anda bisa mengklik tautan ini). Aplikasi kami memiliki 3 service: Frontend, Backend dan Storage. Frontend dan Backend masing-masing ditaruh di heroku dyno, sementara untuk Storage, kami menggunakan Firebase Cloud Storage yang berbasis Google Cloud Storage.

Mari kita mengubah “pembungkus” aplikasi dari Heroku Dyno menjadi Docker Container. Untuk melakukannya, kita harus membuat Dockerfile untuk Frontend dan Backend secara masing-masing. Setiap Dockerfile akan membantu kita membuat satu container.

Berikut adalah Dockerfile yang saya buat untuk Frontend dan Backend.

# pull official base image

FROM node:latest

# set working directory

WORKDIR /app

# install app dependencies

COPY package.json /app

RUN npm install --silent

# add app

COPY . /app

EXPOSE 3000

# start app

CMD ["npm", "start"]

Di sini, setiap baris merupakan satu command, dan command tersebut akan dieksekusi secara sekuensial. Berikut hal-hal yang akan dilakukan oleh Dockerfile kami:

  1. Mengambil Image Node yang terbaru (:latest). Ini bisa juga diubah ke versi yang anda inginkan
  2. Set Working Directory, yang berarti membuat folder baru dimana kita akan mengkonstruksi Docker Container
  3. Menyalin package.json ke Working Directory dan menginstall Dependencies
  4. Menyalin file-file yang berhubungan ke aplikasi yang anda buat ke Working Directory
  5. Meng-expose port 3000 sehingga bisa diakses dari luar (sangat penting agar dapat melakukan port forwarding)
  6. Menjalankan command yang ada di dalam array. Docker akan menganggap array satu baris input ke terminal tersebut dan setiap indeks array diberi jeda spasi.

Mungkin sekarang anda akan bertanya, mengapa Dockerfile yang digunakan berisi hal yang sama? Alasannya adalah karena kedua service tersebut, Backend dan Frontend, berbasis Node.js dan tidak memiliki konfigurasi khusus yang membutuhkan kita untuk membuat Dockerfile yang berbeda. Alasan ini juga berlaku ke dockerignore file, yang dibuat agar file-file yang tidak dibutuhkan tidak dicopy ke container. Berikut adalah dockerignore file yang saya tulis:

node_modules
build
.dockerignore
Dockerfile
Dockerfile.prod

Sekarang, sebenarnya aplikasi sudah dapat dijalankan dengan melakukan Docker Build, dan Docker Run kepada setiap service. Tetapi saya merasa bahwa hal itu merupakan hal yang dapat dibuat automated dengan menggunakan Docker Compose.

Sedikit penjelasan, Docker Compose adalah sebuah alat yang dapat digunakan untuk menjalankan aplikasi Docker yang memiliki lebih dari satu container/Multi-Container Apps. Aplikasi kami memiliki dua container, oleh karena itu memenuhi kriteria untuk menggunakan tool ini.

Untuk menggunakan Docker Compose, kita perlu membuat docker-compose.yml yang merupakan konfigurasi keseluruhan Docker Compose. Berikut adalah isi docker-compose.yml kami:

version: "3.7"

services:

  frontend:

    build:

      context: ./frontend

      dockerfile: Dockerfile

    ports: 

      - 3001:3000

    stdin_open: true

  backend:

    build:

      context: ./backend

      dockerfile: DockerFile

    ports: 

      - 3000:3000

    stdin_open: true

Berikut adalah deskripsi setiap hal yang tertulis pada docker-compose.yml tersebut:

  1. Kita men-set versi file format dari compose file yang kami buat. Untuk lebih lanjutnya dapat dilihat di sini tetapi pendeknya adalah: versi mempengaruhi kompatibilitas dengan Docker Engine yang digunakan. Karena kami menggunakan Docker Engine 18.06.0, maka kami menggunakan version 3.7 sebagai versi file format kami.
  2. Kami men-set kedua service kami yaitu frontend dan backend
  3. Kami men-set build (ini sama dengan command Docker Build) dengan konteks yaitu folder dimana service kami berada dan Dockerfile yang digunakan untuk membantu proses Build tersebut. Sebenarnya bisa saja tidak menggunakan Dockerfile, tetapi kita harus menulis command sendiri di docker-compose.yml yang dibuat.
  4. Kemudian kita melakukan port forwarding. Ini penting untuk dilakukan karena default port untuk Node.js adalah 3000, dan jika anda belum tahu, setiap port hanya akan dapat dihubungkan ke satu aplikasi/service. Oleh karena itu, untuk salah satu service, kita harus menghubungkannya ke port lain. Men-set port akan mem-port forward aplikasi tersebut dari port dimana aplikasi tersebut berjalan pada Docker Container ke port local ataupun server dimana docker container dijalankan.
  5. stdin_open: true ditambahkan karena menurut dokumentasi hal tersebut akan menyelesaikan masalah-masalah common yang dialami jika melakukan containerization aplikasi node.

Setelah selesai, anda dapat menjalankan docker-compose up, atau dapat menjalankan aplikasi tersebut menggunakan docker swarm.

 

Penutup

Aplikasi yang saya buat adalah aplikasi penyusun jadwal ujian yang terdiri dari tiga komponen: Frontend, Backend dan File Storage dan seperti yang saya tulis diatas, aplikasi yang saya buat untuk sekarang memang menggunakan strategi deployment Big Bang, dimana pada saat Deployment, aplikasi yang baru akan secara langsung menggantikan aplikasi yang lama. Tetapi tidak menutup kemungkinan bahwa aplikasi yang kami buat dapat menggunakan strategi Deployment yang berbeda di masa depan. Misalnya, ketika aplikasinya sudah banyak digunakan, kita tidak bisa menggunakan strategi Big Bang, karena akan menyebabkan downtime yang cukup lama.

Mungkin kita bisa berargumen bahwa aplikasinya hanya akan digunakan pada waktu pertengahan dan akhir semester kuliah, saat dimana jadwal ujian perlu dibuat secara otomatis, tetapi ketika ingin melakukan stress testing, contohnya untuk situasi dimana terjadi banyak File Upload dalam waktu yang sama dan kita ingin melakukan migrasi layanan File Upload dari Firebase ke back end, kita tidak bisa menggunakan strategi Big Bang, melainkan kita harus menggunakan strategi Shadow. Hal ini agar kita dapat menguji kelayakan layanan File Upload yang telah kita susun sebelum mematikan koneksi aplikasi kita ke Firebase.

Juga pada saat kita ingin melakukan revamp dari user interface yang ada. Strategi Big Bang kurang tepat untuk itu, karena kita tidak bisa menguji kedua user interface secara bersamaan. Untuk kasus ini mengubah strategi deployment menjadi A/B adalah langkah yang tepat agar kita bisa melakukan survey atas kepuasan pengguna dengan tampilan antarmuka yang baru.

Intinya, pada saat awal-awal deployment pertama, atau pada saat development, strategi yang digunakan bisa saja berbeda dengan yang digunakan pada saat aplikasi memang sudah berjalan secara live, dimana waktu dan statistika seringkali lebih berharga dibandingkan harga layanan tempat kita men-deploy aplikasi. Oleh karena itu, kita harus me-review strategi deployment aplikasi secara berkala dan mengubahnya pada saat yang tepat.

Sumber

https://codeship.com/continuous-integration-essentials#:~:text=Continuous%20Integration%20(CI)%20is%20a,automated%20build%20and%20automated%20tests.

Six Strategies for Application Deployment

https://www.sonarsource.com/products/sonarqube/

 

 

 

 

 

 

 

 

 

 

Leave a comment