Di Balik LEARNFAZZ
Software Architecture, Deployment, Docker Orchestration
Halo, di sini saya akan membahas bagaimana arsitektur LEARNFAZZ dibuat agar bisa berjalan di HP setiap orang yang meng-install APK-nya. Sebelumnya karena ini cukup terkait dengan deployment dan docker, saya akan mencoba menjelaskan secara singkat mengenai docker bagi pembaca yang belum familiar dengan docker.
Docker
Docker adalah platform untuk develop, deploy, dan run aplikasi dengan container. Docker banyak digunakan terutama dalam deployment secara universal. Beberapa istilah yang cukup umum dalam menggunakan Docker yaitu:
- Image
Image adalah executable package yang memuat semua yang diperlukan untuk menjalankan sebuah aplikasi. Image dapat diibaratkan sebagai binary file yang dapat dieksekusi tanpa melakukan instalasi.
- Container
Container adalah instance dari image saat dijalankan. Container menyediakan sebuah layer yang akan digunakan untuk menyimpan data yang dibuat image saat sedang berjalan. Layer ini dapat diibaratkan memory yang digunakan program saat berjalan.
- Volume
Volume adalah tempat penyimpanan data yang berada di luar container. Volume umumnya digunakan sebagai media penyimpanan persistent seperti database karena tentunya kita tidak ingin ada data penting yang hilang saat container dihapus.
Jadi bagaimana arsitektur LEARNFAZZ?
Secara garis besar ada 4 komponen utama yang membangun LEARNFAZZ:
- Android Package (APK)
Sebuah aplikasi android tentu perlu APK yang di-install pada smartphone yang akan menjalankan LEARNFAZZ. Aplikasi ini berfungsi sebagai antarmuka dengan pengguna dan ditulis dengan Flutter.
- Backend Server
Server yang menjalankan inti logic yang dibutuhkan LEARNFAZZ. Ditulis dengan Golang
- Database Server (Postgres)
Server yang digunakan sebagai penyimpanan data yang diperlukan LEARNFAZZ seperti informasi user, atau course.
- Storage & Messaging Service (Firebase)
Layanan yang digunakan untuk menyimpan berkas dan gambar yang di-upload pengguna serta messaging guna memberikan notifikasi untuk pengguna.
Sebagai contoh, alur sederhana yang terjadi ketika pengguna mendaftarkan akun baru adalah:
- Aplikasi android LEARNFAZZ mengirimkan form yang sudah diisi pengguna beserta foto profil pengguna ke backend server.
- Backend server menerima form, kemudian meng-upload foto profil pengguna ke Firebase.
- Selesai meng-upload, backend server akan menyimpan informasi pengguna di database server.
- Backend server kemudian memberi respons kepada aplikasi android bahwa register sukses.
Berdasarkan arsitektur LEARNFAZZ, maka kami perlu men-deploy ke server untuk backend dan database.
Deployment
Mula-mula perlu disiapkan image yang akan dieksekusi di server. Untuk database, kami menggunakan image postgres yang disediakan docker. Sedangkan untuk backend image golang belum cukup karena tidak memuat kode backend LEARNFAZZ.
Image dapat dibuat dengan docker, namun untuk membuatnya perlu Dockerfile sebagai definisi image yang akan dibuat. Berikut Dockerfile yang kami gunakan untuk backend LEARNFAZZ:
FROM golang
ENV GOPKG $GOPATH/src/gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2019/PPLB4/back-end
ENV GO111MODULE on
RUN mkdir -p $GOPKG
COPY . $GOPKG/
WORKDIR $GOPKG
RUN ./dependency.sh
RUN GOBIN=`pwd`/output go install infrastructure/cmd/main.go
ENTRYPOINT ["./docker-entrypoint.sh"]
Penjelasan terkait perintah pada Dockerfile:
FROM [image_name]
berfungsi untuk menyebutkan parent image dari image yang akan dibuat. Dalam kasus ini golang sebagai image bahasa Golang yang digunakan.ENV [key] [value]
berfungsi untuk mendefinisikan variabel pada environment container, sehingga variabel[key]
bernilai[value]
dan dapat diakses dengan$[key]
.RUN [command]
berfungsi untuk menjalankan perintah shell.COPY [from] [to]
berfungsi untuk copy file.WORKDIR [new_directory]
berfungsi untuk berpindah directory.ENTRYPOINT [executable, param..]
menyebutkan perintah yang dijalankan saat container dibuat. Dalam kasus ini menjalankan docker-entrypoint.sh.
Script yang dijalankan pada Dockerfile, yaitu dependency.sh
bertujuan untuk meng-install library untuk database migration:
#!/bin/shcurl -s https://packagecloud.io/install/repositories/golang-migrate/migrate/script.deb.sh | bashapt-get install -y migrate
Script docker-entrypoint.sh
menjalankan migration:
#!/bin/shmigrate -path infrastructure/migrations/ -database ${DB_SOURCE} up
./output/main
Selanjutnya image dapat dibuat dengan menjalankan perintah docker build
, pada LEARNFAZZ:
docker build -t registry.docker.ppl.cs.ui.ac.id/pplb4/back-end:${ENVIRONMENT} .
Penjelasan pada perintah docker build : -t
digunakan untuk memberi nama pada image dan tag dalam format nama:tag
. .
merupakan path files yang akan digunakan untuk build image. ${ENVIRONMENT}
adalah variabel yang bernilai sesuai dengan environment yang sedang digunakan, kami memiliki 2 environment yaitu development
dan staging
.
Image kemudian dapat di-push dengan perintah docker push
:
docker push registry.docker.ppl.cs.ui.ac.id/pplb4/back-end:${ENVIRONMENT}
Selanjutnya, sebelum membuat container. Ada beberapa hal yang perlu dipersiapkan pada server, untungnya server fasilkom menyediakan Portainer sebagai antarmuka untuk manajemen docker.
Untuk penyimpanan data yang persistent, kami perlu menyiapkan volume:
Selanjutnya karena backend perlu tersambung dengan database, maka perlu adanya komunikasi antar container. Komunikasi antar container dapat disediakan melalui network:
Network dengan driver bridge menyediakan komunikasi bagi container yang ada dalam host dan network yang sama.
Setelah semuanya siap, kami membuat container dengan.. docker orchestration!
Docker Orchestration
Docker Orchestration adalah tentang mengatur lifecycle dari container, untuk mempermudah manajemen kontainer ketika kontainer berjumlah cukup banyak.
Untuk Docker Orchestration, kami menggunakan Docker Swarm yang terintegrasi dengan Portainer. Yang perlu dilakukan adalah menyiapkan Portainer Stack atau dengan docker-compose.yml
:
version: '2'services:
postgres:
image: postgres:latest
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- learnfazz-development-postgres_data:/var/lib/postgresql/data
networks:
private:
ipv4_address: ${DB_PRIVATE_IP}
backend:
depends_on:
- "postgres"
image: registry.docker.ppl.cs.ui.ac.id/pplb4/back-end:${ENVIRONMENT}
ports:
- ${BACKEND_PUBLIC_PORT}:8080
environment:
DB_SOURCE: postgres://${DB_USER}:${DB_PASSWORD}@${DB_PRIVATE_IP}:5432/${DB_NAME}?sslmode=disable
networks:
private:
ipv4_address: ${BACKEND_PRIVATE_IP}volumes:
learnfazz-development-postgres_data:
name: learnfazz-${ENVIRONMENT}-postgres_data
external: truenetworks:
private:
external:
name: learnfazz-${ENVIRONMENT}-private
Dapat terlihat terdapat 2 service yang digunakan yaitu database dan backend. Database menggunakan volume untuk penyimpanan data. Kedua service menggunakan network yang telah dibuat untuk komunikasi.
Selain itu, pada sprint terakhir kami juga melakukan deployment ke Google Cloud Platform dan menggunakan Kubernetes sebagai tools Docker Orchestration, dengan membuat yaml
:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: learnfazz-backend
labels:
app: learnfazz-backend
spec:
template:
metadata:
labels:
app: learnfazz-backend
spec:
# This section describes the containers that make up the deployment
containers:
- name: cloudsql-proxy
image: gcr.io/cloudsql-docker/gce-proxy:1.11
command: ["/cloud_sql_proxy",
"-instances=learnfazz:asia-southeast1:learnfazz-db=tcp:5432",
"-credential_file=/secrets/cloudsql/credentials.json"]
volumeMounts:
- name: my-secrets-volume
mountPath: /secrets/cloudsql
readOnly: true
- name: learnfazz-backend
image: asia.gcr.io/learnfazz/back-end:latest
imagePullPolicy: Always
ports:
- containerPort: 8080
env:
- name: DB_USER
valueFrom:
secretKeyRef:
name: cloudsql-db-credentials
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: cloudsql-db-credentials
key: password
- name: DB_NAME
valueFrom:
secretKeyRef:
name: cloudsql-db-credentials
key: dbname
- name: DB_SOURCE
value: "postgres://$(DB_USER):$(DB_PASSWORD)@127.0.0.1:5432/$(DB_NAME)?sslmode=disable"
volumes:
- name: my-secrets-volume
secret:
secretName: cloudsql-instance-credentials
Dapat terlihat beberapa kesamaan seperti database yang menggunakan volume sebagai tempat penyimpanan.
Otomatisasi Deployment
Selanjutnya dengan memanfaatkan CI/CD Gitlab, deployment backend dapat diotomatisasi dengan menambahkan stage deploy pada gitlab-ci.yml
:
.back-end-deployment-script: &back-end-deployment-script
image: gitlab/dind:latest
stage: deploy
tags:
- docker
script:
- docker info
- if [ $CI_COMMIT_TAG ]; then DOCKER_IMAGE_VERSION=$CI_COMMIT_TAG; else DOCKER_IMAGE_VERSION=$CI_COMMIT_SHA_SHORT; fi
- if [ ! $DOCKER_IMAGE_VERSION ]; then DOCKER_IMAGE_VERSION=latest; fi
- cd back-end/
- DOCKER_IMAGE_TAG_NAME=$DOCKER_IMAGE_PREFIX/back-end
- docker build -t $DOCKER_IMAGE_TAG_NAME:$CI_ENVIRONMENT_NAME -t $DOCKER_IMAGE_TAG_NAME:$DOCKER_IMAGE_VERSION .
- if [[ $CI_ENVIRONMENT_NAME =~ production ]]; then docker build -t $DOCKER_IMAGE_TAG_NAME:latest . ; fi
- docker push $DOCKER_IMAGE_TAG_NAME
retry: 1back-end:deploy-to-staging:
<<: *back-end-deployment-script
only:
refs:
- staging
changes:
- back-end/**/*
- .gitlab-ci.yml
environment:
name: staging
back-end:deploy-to-development:
<<: *back-end-deployment-script
only:
refs:
- development
changes:
- back-end/**/*
- .gitlab-ci.yml
environment:
name: development
Script membuat image pada branch dan environment yang sesuai kemudian di-push.