Apa Itu ISO ?

ISO (International Organization for Standardization) adalah badan penetap standar internasional yang terdiri dari wakil-wakil dari badan standardisasi nasional setiap negara.

ISO biasanya akan menetapkan suatu standarisasi untuk kebutuhan tertentu misalnya seperti penetapan standarisasi sistem keamanan rantai pasokan pada ISO 28000, sistem terhadap manajemen lingkungan pada ISO 14001 dan lain - lain nya. Penetapan standarisasi ini berguna untuk membuat keseragaman / standarisasi antar negara sehingga memudahkan dalam proses integrasi.

Apa Itu ISO 8583 ?

ISO 8583 adalah sebuah standart internasional untuk transaksi financial.

ISO 8583 ini sudah sangat banyak digunakan untuk hal transaksi financial terutama di dunia perbankan. ISO 8583 ini tidak hanya dapat digunakan di bagian perbankan melainkan dapat digunakan di bagian lain yang berhubungan dalam hal transaksi financial. Contoh implementasi dari ISO 8583 sendiri adalah terletak pada mesin ATM dan EDC, dimana pada saat proses transaksi, mesin ATM atau EDC akan mengirimkan data dalam bentuk ISO 8583 ke server untuk diproses.

Komponent ISO 8583

Secara global, komponent ISO 8583 terdiri dari :

  • Message Header : biasanya digunakan sebagai penanda awal dari sebuah message
  • Application Data : bagian ini berisi dari inti ISO 8583
  • Message Trailer : digunakan sebagai penanda akhir sebuah message atau pembeda antar message

Pada artikel ini, penulis hanya akan membahas mengenai application data dikarenakan hal yang paling banyak dibahas atau yang perlu dipelajari adalah pada bagian application data. Di dalam application data terdiri dari beberapa komponent yaitu : MTI (Message Type Indicator), Bitmap dan Data Element.

MTI (Message Type Indicator)

MTI (Message Type Indicator) adalah 4 digit angka yang berfungsi sebagai message utama dan penjelasan mengenai tentang tipe pesan.

Di dalam MTI terdapat 4 digit dimana 4 digit tersebut dibagi menjadi :

  • Message Version : berfungsi mendefinisikan versi ISO 8583 yang digunakan, berikut adalah versi yang dapat digunakan :

MTI.png

  • Message Class : berfungsi mendefinisikan tujuan message yang akan dikirim, berikut adalah tabel untuk message class.

Message Class.png

  • Message Function : berfungsi sebagai type dari message, misalnya apakah dia berupa request, response, advice dan lain - lain nya. berikut adalah tabel untuk message function.

Message Function.png

  • Message Origin berfungsi untuk mendefinisikan sumber pengirimkan data misalnya seperti acquirer (instansi keuangan yang menerbitkan kartu pembeli), issuer (instansi keuangan yang berhubungan langsung dengan mesin EDC penjual) dan lain - lain. erikut adalah tabel untuk message origin.

Message Origin.png

Berikut adalah contoh sederhana dari MTI.

0100 artinya Authorization Request dimana

  • 0 -> ISO 8583 tahun 1987
  • 1 -> Authorization message
  • 0 -> Request
  • 0 -> Acquirer

Contoh response nya 0110 artinya Authorization Response dimana

  • 0 -> ISO 8583 tahun 1987
  • 1 -> Authorization message
  • 1 -> Request response
  • 0 -> Acquirer

Bitmap

Bitmap adalah salah satu komponent dari ISO 8583 dimana terdiri dari 16 angka (dalam bentuk hexadecimal) atau lebih tergantung dari apakah terdapat secondary dan tertiary bitmap yang aktif.

Bitmap terdiri dari hexadecimal, dimana hexadecimal ini nantinya akan dirubah terlebih dahulu menjadi biner. Biner yang terdapat dalam bitmap yang telah dirubah dari hexadecimal menjadi biner berfungsi untuk melakukan lookup terhadap posisi data element yang akan digunakan. Untuk melakukan lookup terhadap posisi data element maka digunakan biner yang aktif, dimana biner yang aktif bernilai 1 sedangkan biner yang non aktif bernilai 0.

Di dalam bitmap, terdapat beberapa bitmap yaitu :

  • Primary Bitmap : ini merupakan bitmap yang wajib untuk setiap ISO 8583, data element yang aktif pada bitmap ini yaitu dari biner 1 sampai 64.
  • Secondary Bitmap : merupakan bitmap dengan data element yang aktif pada biner 65 sampai 128.
  • Tertiary Bitmap : merupakan bitmap dengan data element yang aktif pada biner 129 sampai 192.

Cara Menghitung Bitmap Dari Hexadecimal

Untuk menghitung bitmap, maka kita perlu melakukan convert dari hexadecimal menjadi biner. Berikut adalah tabel convert yang biasanya digunakan.

DecBinHex.png

Misalnya terdapat contoh bitmap : F23C449108E080000000000000000021

dimana bitmap diatas terdiri dari 32 karakter maka dapat dipastikan bitmap diatas terdapat Secondary bitmap. Cara merubah hexadecimal diatas menjadi biner adalah silakan pecah hexadecimal tersebut menjadi 2 digit lalu convert 1 digit hexadecimal menjadi biner seperti berikut.

Hexa Hexa Biner Biner
F 2 1111 0010
3 C 0011 1100
4 4 0100 0100
9 1 1001 0001
0 8 0000 1000
E 0 1110 0000
8 0 1000 0000
0 0 0000 0000
0 0 0000 0000
0 0 0000 0000
0 0 0000 0000
0 0 0000 0000
0 0 0000 0000
0 0 0000 0000
0 0 0000 0000
2 1 0010 0001

Kemudian silahkan gabungkan seluruh binari tersebut menjadi seperti berikut.

11110010001111000100010010010001000010001110000010000000000000000000000000000000000000000000000000000000000000000000000000100001

Dari binari diatas dapat dilihat bahwa angka 1 menunjukkan bahwa binari aktif, berikut adalah binari yang aktif pada posisi : 1,2,3,4,7,11,12,13,14,18,22,25,28,32,37,41,42,43,49,123 dan 128.

Cara Menghitung Bitmap Dari Document Spesification

Hal yang terpenting selanjutnya adalah bagaimana cara membaca spec dari dokumen yang diberikan ketika pihak kita ingin melakukan request ke pihak lain. Pada artikel ini, penulis menggunakan dokumen NIBSS yaitu salah satu bank di negara nigeria. Dokumen tersebut dapat anda akses di Document Spesification NIBSS. Lalu silahkan buka halaman 7 pada bagian Authorization Request Response. Disana terdapat tabel yang berisi mengenai informasi posisi bit dan indicator dari bit tersebut. Pada artikel ini, kita hanya menggunakan mandatory bit. Berikut adalah bit yang digunakan yaitu pada bit 1,2,3,4,12,18,22,23,25,32,37,39,49,123,128. Mengapa bit 1 digunakan ? dikarenakan bit 1 menandakan terdapat secondary dikarenakan posisi bit sampai dengan 128.

Hal yang pertama dilakukan adalah silahkan tulis binari sebanyak 128 seperti berikut.

00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000

Lalu isikan bit 1 sesuai dengan posisi bit diatas maka hasilnya seperti berikut.

11110000 00010000
01000110 10000001
00001010 00000000
10000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00100001

Lalu silahkan convert binari diatas menjadi hexadecimal, misalnya hasil dari 11110000 adalah F0 dan 00010000 adalah 10 lalu substring dari awal hingga akhir. Jika hasil dari convert tersebut memberikan angka digit maka tambahkan angka 0 di depan nya misalnya hasil hexadecimal dari 00001010 adalah A maka tambahkan 0 di depan nya sehingga menjadi 0A. Dan hasilnya adalah.

F01046810A0080000000000000000021

Data Element

Data Element merupakan inti dari ISO 8583 dimana di dalam data element, semua informasi transaksi akan tersedia di dalam nya.

Di dalam data element, terdapat format data yang harus diperhatikan, misalnya panjang data nya, data nya hanya boleh berbentuk angka dan sebagainya. Berikut adalah format data untuk data element yang biasanya digunakan.

format-data-element.jpg

data-fixed.jpg

Contoh Data Element

Berikut adalah contoh dari data element

Data Element Type Usage
1 b 64 Bit Map Extended
2 n 19 Primary account number (PAN)
3 n 6 Processing code
4 an 12 Retrieval reference number
5 ans 40 Card acceptor name/location

dari contoh diatas dapat dilihat bahwa misalnya data Primary account number (PAN) menunjukan n 19 yang menjelaskan bahwa PAN menggunakan type data number dengan length nya yaitu 19 angka.

Menyusun Contoh Message ISO 8583

Data Element Type Usage Nilai
2 LLVAR (n ..16) Primary account number (PAN) 92581234124234567
3 an 6 Processing code A00001
4 n 12 Amount, transaction 10000000
12 n 6 (Hhmmss) Time, local transaction 072320
18 n 4 Merchant’s type 0010
22 n 3 POS entry mode 123
23 n 3 Card sequence number 321
25 n 2 POS condition code 11
32 n 2 Acquiring institution id code 01
37 an 12 Retrieval reference number RRN001
39 an 2 Response code 00
49 n 3 Currency code, transaction 100
123 an 15 POS data code A101
128 16 (sha-256) Secondary Message Hash Value 6AA107B5118BC17D

Diatas adalah contoh dari data element untuk Financial Request Response, maka kita akan menggunakan MTI 0210. Data element yang digunakan adalah pada posisi 2,3,4,12,18,22,23,25,32,37,39,49,123 dan 128 sehingga silahkan buat binari 128 dan hasilnya seperti berikut.

11110000 00010000
01000110 10000001
00001010 00000000
10000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00100001

Maka hasil hexadecimal nya adalah

F01046810A0080000000000000000021

Lalu untuk data element maka silahkan concat seluruh data berdasarkan urutan nya maka hasilnya menjadi seperti berikut.

92581234124234567A000011000000007232000101233211101RRN00100100A1016AA107B5118BC17D

maka kesimpulan nya adalah

MIT          : 0210
Bitmap       : F01046810A0080000000000000000021
Data Element : 92581234124234567A000011000000007232000101233211101RRN00100100A1016AA107B5118BC17D
Message ISO 8583 : 0210F01046810A008000000000000000002192581234124234567A000011000000007232000101233211101RRN00100100A1016AA107B5118BC17D

Message ISO 8583 yang akan dikirim adalah dalam bentuk

0210F01046810A008000000000000000002192581234124234567A000011000000007232000101233211101RRN00100100A1016AA107B5118BC17D

Sekian artikel mengenai Belajar ISO 8583 dan terima kasih :).

Pada artikel sebelumnya Belajar Membuat Cluster Apache Cassandra Dengan Docker Swarm, penulis telah membahas mengenai tentang cluster pada apache cassandra dengan menggunakan docker swarm. Untuk dapat membuat cluster pada apache cassandra, kita juga dapat menggunakan kubernetes sebagai penganti dari docker swarm. Jika docker swarm merupakan bawaan dari docker maka kubernetes perlu dilakukan instalasi dan setup. Pada artikel ini, penulis akan membahas bagaimana cara instalasi, setup kubernetes dan membuat cluster apache cassandra pada kubernetes.

Apa Itu Kubernetes ?

Kubernetes adalah salah satu produk open source untuk sistem manajemen container

Sama seperti docker swarm, kubernetes dapat melakukan manajemen container, akan tetapi kubernetes merupakan produk yang akan terus berkembang dan akan lebih banyak digunakan dibandingkan dengan docker swarm. Salah satu kelebihan dari kubernetes adalah kita dapat menggunakan kubernetes dashboard untuk memonitoring container, kita dapat melakukan deployment melalui dashboard dan pastinya struktur yang diusulkan oleh kubernetes dan docker swarm sangatlah berbeda. Berikut adalah struktur yang biasanya digunakan di dalam kubernetes.

Arsitektur Kubernetes.png

Berikut adalah penjelasan nya

  • Node : biasanya sebagai server, bisa sebagai server fisik atau virtual server sehingga node ini juga dapat berupa sebuah server yang berjalan diatas virtual machine seperti VMWare atau virtualbox.
  • Deployment : biasanya berfungsi untuk mengontrol dari sebuah pods, misalnya adanya update dari sebuah pods, adanya penambahan replikasi pada pods dan lain sebagainya.
  • Service : berfungsi untuk mengexpose pods, tujuan nya adalah agar client dapat mengakses service yang terdapat di dalam sebuah pods.
  • Pods : di dalam sebuah deployment terdapat beberapa pods, biasanya pods ini terdapat 1 container atau lebih. Pods ini biasanya akan dilakukan replikasi sesuai dengan kebutuhan.
  • Container : container ini mewakili dari 1 aplikasi, misalnya database postgresql atau web server apache tomcat.

Instalasi Kubernetes

Pada artikel ini, kita akan menggunakan 2 buat vm yaitu :

  1. VM Master : berfungsi sebagai node master, master disini berfungsi untuk mendeploy container ke dalam node worker kubernetes.
  2. VM Worker : berfungsi sebagai node worker.

Requirment Node Master

Adapun kebutuhan untuk node master adalah :

  • Ram 3 GB
  • Disk 10 GB
  • Bridge Network
  • IP 192.168.88.100
  • Ubuntu 16.04 Server

Requirment Node Worker

Adapun kebutuhan untuk node worker adalah :

  • Ram 2 GB
  • Disk 10 GB
  • Bridge Network
  • IP 192.168.88.101
  • Ubuntu 16.04 Server

Dengan requirment diatas, silahkan lakukan instalasi ubuntu server pada 2 vm tersebut yaitu pada vm master dan vm worker. IP yang digunakan disini adalah IP static, sehingga anda perlu melakukan sedikit konfigurasi pada jaringan ubuntu server.

Untuk melakukan instalasi kubernetes, anda wajib melakukan instalasi docker terlebih dahulu, bagi yang belum mengerti docker, silahkan baca artikel Belajar Docker. Hal yang pertama dilakukan adalah lakukan update dan upgrade ubuntu server dengan perintah.

sudo -s
apt update && apt upgrade -y

Lalu update certificate dan kebutuhan untuk kubernetes dengan perintah

apt install apt-transport-https ca-certificates curl software-properties-common -y

Lalu tambahkan key untuk kubernetes dengan perintah

curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -

Lalu tambahkan repository kubernetes untuk ubuntu dengan perintah

cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb http://apt.kubernetes.io/ kubernetes-xenial main
EOF

Lalu lakukan update kembali dengan perintah

apt update

Dan lakukan instalasi dengan perintah

apt install kubelet kubeadm kubernetes-cni -y

Setup Kubernetes

Setelah selesai melakukan instalasi kubernetes, tahapan selanjutnya adalah kita akan melakukan setup kubernetes. Setup kubernetes akan dilakukan secara bertahap, yaitu dilakukan pada node master terlebih dahulu kemudian akan dilakukan pada node worker.

Setup Kubernetes Master

Silahkan login ke node master, lalu aktifkan bridged IPv4 traffic dengan perintah

sysctl net.bridge.bridge-nf-call-iptables=1

Lalu jalankan perintah berikut untuk disable swap pada ubuntu

swapoff -a

Setelah selesai, jalankan perintah berikut untuk inisialisasi kubernetes pada node master.

kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=192.168.88.100 --kubernetes-version stable-1.9

Jika berhasil maka akan muncul output seperti berikut.

Your Kubernetes master has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

You can now join any number of machines by running the following on each node
as root:

  kubeadm join --token d36cc6.a9d32c295072f336 192.168.88.100:6443 --discovery-token-ca-cert-hash sha256:4a6626451ad1ad54e1c85e93dd26d7bfd66a2d1da4052f04b5bd1f424a4082d4

Perintah kubeadm join nantinya akan kita gunakan untuk melakukan registrasi dari node worker ke node master.

Lalu jalankan perintah berikut agar kubectl dapat diakses tanpa perlu menggunakan user root.

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

Setelah selesai, jika kita melakukan setup kubernetes cluster maka dibutuhkan network overlay seperti docker swarm yang dapat menyambung pods yang berbeda node. Jika kita menggunakan kubernetes, maka ada beberapa opsi network pod plugin yang dapat kita gunakan yaitu :

Pada artikel ini, penulis akan menggunakan flannel sebagai network plugin. Untuk melakukan setup flannel pada kubernetes, silahkan jalankan perintah berikut.

kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

Jika telah selesai, silahkan lakukan pengecekan pods dengan perintah

kubectl get pods --all-namespaces

Jika beberapa pods masih dalam proses creating / prosess pull image maka akan muncul seperti berikut.

NAMESPACE     NAME                             READY     STATUS     RESTARTS   AGE
kube-system   etcd-master                      1/1       Running    0          4s
kube-system   kube-apiserver-master            1/1       Running    3          4s
kube-system   kube-controller-manager-master   1/1       Running    0          4s
kube-system   kube-dns-6f4fd4bdf-47lc6         0/3       Pending    0          7m
kube-system   kube-flannel-ds-j5d75            0/1       Init:0/1   0          19s
kube-system   kube-proxy-gmrfm                 1/1       Running    0          8m
kube-system   kube-scheduler-master            1/1       Running    0          4s

Jika semua pods telah berjalan maka akan muncul seperti berikut.

NAMESPACE     NAME                             READY     STATUS    RESTARTS   AGE
kube-system   etcd-master                      1/1       Running   0          7m
kube-system   kube-apiserver-master            1/1       Running   3          7m
kube-system   kube-controller-manager-master   1/1       Running   0          7m
kube-system   kube-dns-6f4fd4bdf-47lc6         3/3       Running   0          16m
kube-system   kube-flannel-ds-j5d75            1/1       Running   0          9m
kube-system   kube-proxy-gmrfm                 1/1       Running   0          16m
kube-system   kube-scheduler-master            1/1       Running   0          7m

Setup Kubernetes Worker

Silahkan login ke node worker lalu aktifkan bridged IPv4 traffic dengan perintah

sysctl net.bridge.bridge-nf-call-iptables=1

Lalu jalankan perintah berikut untuk disable swap pada ubuntu

swapoff -a

Setelah selesai, jalankan perintah berikut untuk inisialisasi kubernetes pada node worker dengan melakukan register ke node master.

kubeadm join --token d36cc6.a9d32c295072f336 192.168.88.100:6443 --discovery-token-ca-cert-hash sha256:4a6626451ad1ad54e1c85e93dd26d7bfd66a2d1da4052f04b5bd1f424a4082d4

Setelah selesai, silahkan login kembali ke node master, lalu jalankan perintah berikut untuk melihat node yang berhasil melakukan register.

kubectl get nodes

Jika berhasil maka akan muncul seperti berikut.

NAME      STATUS     ROLES     AGE       VERSION
master    Ready      master    18m       v1.9.6
worker    NotReady   <none>    2s        v1.9.6

Dan berikut jika kedua node telah siap untuk digunakan.

NAME      STATUS    ROLES     AGE       VERSION
master    Ready     master    23m       v1.9.6
worker    Ready     <none>    5m        v1.9.6

Setup Kubernetes Dashboard

Secara default, kubernetes tidak memiliki dashboard akan tetapi kita dapat melakukan setup untuk kubernetes dashboard. Yang pertama kali dilakukan adalah kita akan membuat sebuah file yaitu admin.yaml lalu isikan dengan source berikut.

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: kubernetes-dashboard
  labels:
    k8s-app: kubernetes-dashboard
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: kubernetes-dashboard
  namespace: kube-system

Konfigurasi diatas berfungsi untuk user authentication pada kubernetes dashboard. Lalu jalankan perintah berikut untuk menambahkan user tersebut.

kubectl create -f admin.yaml

Setelah selesai, silahkan buat sebuah file dashboard.yaml lalu isikan source berikut.

apiVersion: v1
kind: Secret
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-certs
  namespace: kube-system
type: Opaque

---
# ------------------- Dashboard Service Account ------------------- #

apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kube-system

---
# ------------------- Dashboard Role & Role Binding ------------------- #

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: kubernetes-dashboard-minimal
  namespace: kube-system
rules:
  # Allow Dashboard to create 'kubernetes-dashboard-key-holder' secret.
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["create"]
  # Allow Dashboard to create 'kubernetes-dashboard-settings' config map.
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: ["create"]
  # Allow Dashboard to get, update and delete Dashboard exclusive secrets.
- apiGroups: [""]
  resources: ["secrets"]
  resourceNames: ["kubernetes-dashboard-key-holder", "kubernetes-dashboard-certs"]
  verbs: ["get", "update", "delete"]
  # Allow Dashboard to get and update 'kubernetes-dashboard-settings' config map.
- apiGroups: [""]
  resources: ["configmaps"]
  resourceNames: ["kubernetes-dashboard-settings"]
  verbs: ["get", "update"]
  # Allow Dashboard to get metrics from heapster.
- apiGroups: [""]
  resources: ["services"]
  resourceNames: ["heapster"]
  verbs: ["proxy"]
- apiGroups: [""]
  resources: ["services/proxy"]
  resourceNames: ["heapster", "http:heapster:", "https:heapster:"]
  verbs: ["get"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: kubernetes-dashboard-minimal
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: kubernetes-dashboard-minimal
subjects:
- kind: ServiceAccount
  name: kubernetes-dashboard
  namespace: kube-system

---
# ------------------- Dashboard Deployment ------------------- #

kind: Deployment
apiVersion: apps/v1beta2
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kube-system
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: kubernetes-dashboard
  template:
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
    spec:
      containers:
      - name: kubernetes-dashboard
        image: k8s.gcr.io/kubernetes-dashboard-amd64:v1.8.3
        ports:
        - containerPort: 8443
          protocol: TCP
        args:
          - --auto-generate-certificates
          # Uncomment the following line to manually specify Kubernetes API server Host
          # If not specified, Dashboard will attempt to auto discover the API server and connect
          # to it. Uncomment only if the default does not work.
          # - --apiserver-host=http://my-address:port
        volumeMounts:
        - name: kubernetes-dashboard-certs
          mountPath: /certs
          # Create on-disk volume to store exec logs
        - mountPath: /tmp
          name: tmp-volume
        livenessProbe:
          httpGet:
            scheme: HTTPS
            path: /
            port: 8443
          initialDelaySeconds: 30
          timeoutSeconds: 30
      volumes:
      - name: kubernetes-dashboard-certs
        secret:
          secretName: kubernetes-dashboard-certs
      - name: tmp-volume
        emptyDir: {}
      serviceAccountName: kubernetes-dashboard
      # Comment the following tolerations if Dashboard must not be deployed on master
      tolerations:
      - key: node-role.kubernetes.io/master
        effect: NoSchedule

---
# ------------------- Dashboard Service ------------------- #

kind: Service
apiVersion: v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kube-system
spec:
  ports:
    - port: 443
      targetPort: 8443
  selector:
    k8s-app: kubernetes-dashboard
  type: NodePort

Lalu jalankan dashboard tersebut dengan perintah

kubectl apply -f dashboard.yaml

Setelah selesai, tunggu hingga pods tersebut berjalan. Jika pods telah berjalan, silahkan lakukan pengecekan services dengan perintah.

kubectl get services --all-namespaces

Dan berikut adalah hasilnya

NAMESPACE     NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)         AGE
default       kubernetes             ClusterIP   10.96.0.1       <none>        443/TCP         31m
kube-system   kube-dns               ClusterIP   10.96.0.10      <none>        53/UDP,53/TCP   31m
kube-system   kubernetes-dashboard   NodePort    10.101.10.168   <none>        443:31601/TCP   2m

Dari konfigurasi diatas, dapat dilihat bahwa kubernetes dashboard jalan pada port 31601, lalu silahkan akses kubernetes dashboard di https://<ip>:31601 seperti berikut.

Screenshot from 2018-03-25 18-36-20.png

Lalu pilih menu skip dan akan muncul halaman seperti berikut.

Screenshot from 2018-03-25 18-37-31.png

Arsitektur Cluster Apache Cassandra Pada Kubernetes

Arsitektur yang akan kita gunakan pada kubernetes tidak jauh berbeda dengan docker swarm. Pada kubernetes, kita akan melakukan deployment container pada sebuah deployment. Setiap 1 deployment akan mewakili 1 cassandra. Berikut adalah arsitektur yang akan digunakan.

cassandra-kubernetes.png

Silahkan buat sebuah file yaml cassandra-service.yaml lalu isikan dengan source berikut.

apiVersion: v1
kind: Service
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.7.0 (767ab4b)
  creationTimestamp: null
  labels:
    io.kompose.service: cassandra-dc-1
  name: cassandra-dc-1
spec:
  ports:
  - name: "9042"
    port: 9042
    targetPort: 9042
  - name: "7000"
    port: 7000
    targetPort: 7000
  selector:
    io.kompose.service: cassandra-dc-1
status:
  loadBalancer: {}

---
apiVersion: v1
kind: Service
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.7.0 (767ab4b)
  creationTimestamp: null
  labels:
    io.kompose.service: cassandra-dc-2
  name: cassandra-dc-2
spec:
  ports:
  - name: "9042"
    port: 9042
    targetPort: 9042
  - name: "7000"
    port: 7000
    targetPort: 7000
  selector:
    io.kompose.service: cassandra-dc-2
status:
  loadBalancer: {}

---
apiVersion: v1
kind: Service
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.7.0 (767ab4b)
  creationTimestamp: null
  labels:
    io.kompose.service: cassandra-dc-3
  name: cassandra-dc-3
spec:
  ports:
  - name: "9042"
    port: 9042
    targetPort: 9042
  - name: "7000"
    port: 7000
    targetPort: 7000
  selector:
    io.kompose.service: cassandra-dc-3
status:
  loadBalancer: {}

---
apiVersion: v1
kind: Service
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.7.0 (767ab4b)
  creationTimestamp: null
  labels:
    io.kompose.service: cassandra-dr-1
  name: cassandra-dr-1
spec:
  ports:
  - name: "9042"
    port: 9042
    targetPort: 9042
  - name: "7000"
    port: 7000
    targetPort: 7000
  selector:
    io.kompose.service: cassandra-dr-1
status:
  loadBalancer: {}

---
apiVersion: v1
kind: Service
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.7.0 (767ab4b)
  creationTimestamp: null
  labels:
    io.kompose.service: cassandra-dr-2
  name: cassandra-dr-2
spec:
  ports:
  - name: "9042"
    port: 9042
    targetPort: 9042
  - name: "7000"
    port: 7000
    targetPort: 7000
  selector:
    io.kompose.service: cassandra-dr-2
status:
  loadBalancer: {}

---
apiVersion: v1
kind: Service
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.7.0 (767ab4b)
  creationTimestamp: null
  labels:
    io.kompose.service: cassandra-dr-3
  name: cassandra-dr-3
spec:
  ports:
  - name: "9042"
    port: 9042
    targetPort: 9042
  - name: "7000"
    port: 7000
    targetPort: 7000
  selector:
    io.kompose.service: cassandra-dr-3
status:
  loadBalancer: {}

Kemudian kembali ke menu dashboard, pilih menu create lalu isikan source diatas pada form seperti berikut.

Screenshot from 2018-03-25 18-47-06.png

Silahkan buat sebuah file yaml cassandra-deployment.yaml lalu isikan dengan source berikut.

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.7.0 (767ab4b)
  creationTimestamp: null
  labels:
    io.kompose.service: cassandra-dc-1
  name: cassandra-dc-1
spec:
  replicas: 1
  minReadySeconds: 60
  strategy: 
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  template:
    metadata:
      creationTimestamp: null
      labels:
        io.kompose.service: cassandra-dc-1
    spec:
      containers:
      - args:
        - bash
        - -c
        - 'if [ -z "$$(ls -A /var/lib/cassandra/)" ] ; then sleep 0; fi && /docker-entrypoint.sh cassandra -f'
        image: cassandra:latest
        name: cassandra-dc-1
        imagePullPolicy: IfNotPresent
        env:
        - name: CASSANDRA_CLUSTER_NAME
          value: CassandraCluster
        - name: CASSANDRA_BROADCAST_ADDRESS
          value: cassandra-dc-1
        - name: CASSANDRA_SEEDS
          value: cassandra-dc-1,cassandra-dr-1
        - name: CASSANDRA_DC
          value: DC
        - name: CASSANDRA_RACK
          value: RACK1
        - name: CASSANDRA_ENDPOINT_SNITCH
          value: GossipingPropertyFileSnitch
        - name: MAX_HEAP_SIZE
          value: 50m
        - name: HEAP_NEWSIZE
          value: 10m
        resources: {}
      restartPolicy: Always
status: {}

---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.7.0 (767ab4b)
  creationTimestamp: null
  labels:
    io.kompose.service: cassandra-dc-2
  name: cassandra-dc-2
spec:
  replicas: 1
  minReadySeconds: 60
  strategy: 
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  template:
    metadata:
      creationTimestamp: null
      labels:
        io.kompose.service: cassandra-dc-2
    spec:
      containers:
      - args:
        - bash
        - -c
        - 'if [ -z "$$(ls -A /var/lib/cassandra/)" ] ; then sleep 120; fi && /docker-entrypoint.sh cassandra -f'
        image: cassandra:latest
        name: cassandra-dc-2
        imagePullPolicy: IfNotPresent
        env:
        - name: CASSANDRA_CLUSTER_NAME
          value: CassandraCluster
        - name: CASSANDRA_BROADCAST_ADDRESS
          value: cassandra-dc-2
        - name: CASSANDRA_SEEDS
          value: cassandra-dc-1,cassandra-dr-1
        - name: CASSANDRA_DC
          value: DC
        - name: CASSANDRA_RACK
          value: RACK2
        - name: CASSANDRA_ENDPOINT_SNITCH
          value: GossipingPropertyFileSnitch
        - name: MAX_HEAP_SIZE
          value: 50m
        - name: HEAP_NEWSIZE
          value: 10m
        resources: {}
      restartPolicy: Always
status: {}

---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.7.0 (767ab4b)
  creationTimestamp: null
  labels:
    io.kompose.service: cassandra-dc-3
  name: cassandra-dc-3
spec:
  replicas: 1
  minReadySeconds: 60
  strategy: 
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  template:
    metadata:
      creationTimestamp: null
      labels:
        io.kompose.service: cassandra-dc-3
    spec:
      containers:
      - args:
        - bash
        - -c
        - 'if [ -z "$$(ls -A /var/lib/cassandra/)" ] ; then sleep 240; fi && /docker-entrypoint.sh cassandra -f'
        image: cassandra:latest
        name: cassandra-dc-3
        imagePullPolicy: IfNotPresent
        env:
        - name: CASSANDRA_CLUSTER_NAME
          value: CassandraCluster
        - name: CASSANDRA_BROADCAST_ADDRESS
          value: cassandra-dc-3
        - name: CASSANDRA_SEEDS
          value: cassandra-dc-1,cassandra-dr-1
        - name: CASSANDRA_DC
          value: DC
        - name: CASSANDRA_RACK
          value: RACK3
        - name: CASSANDRA_ENDPOINT_SNITCH
          value: GossipingPropertyFileSnitch
        - name: MAX_HEAP_SIZE
          value: 50m
        - name: HEAP_NEWSIZE
          value: 10m
        resources: {}
      restartPolicy: Always
status: {}

---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.7.0 (767ab4b)
  creationTimestamp: null
  labels:
    io.kompose.service: cassandra-dr-1
  name: cassandra-dr-1
spec:
  replicas: 1
  minReadySeconds: 60
  strategy: 
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  template:
    metadata:
      creationTimestamp: null
      labels:
        io.kompose.service: cassandra-dr-1
    spec:
      containers:
      - args:
        - bash
        - -c
        - 'if [ -z "$$(ls -A /var/lib/cassandra/)" ] ; then sleep 60; fi && /docker-entrypoint.sh cassandra -f'
        image: cassandra:latest
        name: cassandra-dr-1
        imagePullPolicy: IfNotPresent
        env:
        - name: CASSANDRA_CLUSTER_NAME
          value: CassandraCluster
        - name: CASSANDRA_BROADCAST_ADDRESS
          value: cassandra-dr-1
        - name: CASSANDRA_SEEDS
          value: cassandra-dr-1,cassandra-dc-1
        - name: CASSANDRA_DC
          value: DR
        - name: CASSANDRA_RACK
          value: RACK1
        - name: CASSANDRA_ENDPOINT_SNITCH
          value: GossipingPropertyFileSnitch
        - name: MAX_HEAP_SIZE
          value: 50m
        - name: HEAP_NEWSIZE
          value: 10m
        resources: {}
      restartPolicy: Always
status: {}

---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.7.0 (767ab4b)
  creationTimestamp: null
  labels:
    io.kompose.service: cassandra-dr-2
  name: cassandra-dr-2
spec:
  replicas: 1
  minReadySeconds: 60
  strategy: 
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  template:
    metadata:
      creationTimestamp: null
      labels:
        io.kompose.service: cassandra-dr-2
    spec:
      containers:
      - args:
        - bash
        - -c
        - 'if [ -z "$$(ls -A /var/lib/cassandra/)" ] ; then sleep 180; fi && /docker-entrypoint.sh cassandra -f'
        image: cassandra:latest
        name: cassandra-dr-2
        imagePullPolicy: IfNotPresent
        env:
        - name: CASSANDRA_CLUSTER_NAME
          value: CassandraCluster
        - name: CASSANDRA_BROADCAST_ADDRESS
          value: cassandra-dr-2
        - name: CASSANDRA_SEEDS
          value: cassandra-dr-1,cassandra-dc-1
        - name: CASSANDRA_DC
          value: DR
        - name: CASSANDRA_RACK
          value: RACK2
        - name: CASSANDRA_ENDPOINT_SNITCH
          value: GossipingPropertyFileSnitch
        - name: MAX_HEAP_SIZE
          value: 50m
        - name: HEAP_NEWSIZE
          value: 10m
        resources: {}
      restartPolicy: Always
status: {}

---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.7.0 (767ab4b)
  creationTimestamp: null
  labels:
    io.kompose.service: cassandra-dr-3
  name: cassandra-dr-3
spec:
  replicas: 1
  minReadySeconds: 60
  strategy: 
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  template:
    metadata:
      creationTimestamp: null
      labels:
        io.kompose.service: cassandra-dr-3
    spec:
      containers:
      - args:
        - bash
        - -c
        - 'if [ -z "$$(ls -A /var/lib/cassandra/)" ] ; then sleep 300; fi && /docker-entrypoint.sh cassandra -f'
        image: cassandra:latest
        name: cassandra-dr-3
        imagePullPolicy: IfNotPresent
        env:
        - name: CASSANDRA_CLUSTER_NAME
          value: CassandraCluster
        - name: CASSANDRA_BROADCAST_ADDRESS
          value: cassandra-dr-3
        - name: CASSANDRA_SEEDS
          value: cassandra-dr-1,cassandra-dc-1
        - name: CASSANDRA_DC
          value: DR
        - name: CASSANDRA_RACK
          value: RACK3
        - name: CASSANDRA_ENDPOINT_SNITCH
          value: GossipingPropertyFileSnitch
        - name: MAX_HEAP_SIZE
          value: 50m
        - name: HEAP_NEWSIZE
          value: 10m
        resources: {}
      restartPolicy: Always
status: {}

Kemudian kembali ke menu dashboard, pilih menu create lalu isikan source diatas pada form, Berikut adalah jika pods dalam keadaan creating atau pull images.

Screenshot from 2018-03-25 19-21-35.png

Dan berikut ketika seluruh pods telah berjalan.

Screenshot from 2018-03-25 19-27-18.png

Lalu pada kubernetes dashboard, silahkan klik di salah satu pods, misalnya penulis ingin memilih pods cassandra-dc-1-<code>, contohnya cassandra-dc-1-8f6fdf494-w247g, maka akan muncul halaman seperti berikut.

Screenshot from 2018-03-25 19-44-42.png

Lalu pilih menu exec dan akan muncul halaman seperti berikut.

Screenshot from 2018-03-25 19-44-59.png

Pada halaman tersebut, kita dapat mengakses terminal yang terdapat pada pods tersebut. Lalu jalankan perintah berikut untuk melihat topology cassandra cluster yang telah terbentuk.

nodetool status

Jika berhasil maka akan muncul seperti ini

Screenshot from 2018-03-25 19-46-15.png

Atau jika dalam bentuk bash

root@cassandra-dc-1-8f6fdf494-w247g:/# nodetool status
Datacenter: DC
==============
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
--  Address         Load       Tokens       Owns (effective)  Host ID                               Rack
UN  10.106.21.202   75.01 KiB  256          32.4%             f15606c4-e7e7-4dd3-81fc-5a072ac6b3d8  RACK3
UN  10.108.237.0    110.2 KiB  256          31.5%             a907cb28-3e39-4b0b-bce4-052e16485e99  RACK1
UN  10.108.167.162  74.97 KiB  256          33.7%             b9b8feb1-6ebc-442a-a386-44b8c9eea3da  RACK2
Datacenter: DR
==============
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
--  Address         Load       Tokens       Owns (effective)  Host ID                               Rack
UN  10.109.153.57   74.88 KiB  256          32.8%             f8d3f2b1-ebfc-4826-b1c9-39bc37e9c9bb  RACK1
UN  10.107.250.188  69.94 KiB  256          35.1%             00d659f4-d473-492f-afed-fc46ce1c0402  RACK2
UN  10.111.237.50   151.11 KiB  256         34.5%             7471c743-a2e1-4eb8-96f7-ad852d262e43  RACK3

Sekian artikel mengenai Instalasi Dan Setup Kubernetes Cluster dan terima kasih :).

Pada artikel sebelumnya, penulis telah menjelaskan bagaimana cara melakukan deployment docker ke heroku, akan tetapi cara tersebut bisa dibilang sangat melelahkan ketika kita harus memanage banyak service. Untuk dapat melakukan otomatisasi keseluruhan service maka kita akan menggunakan bantuan CI/CD yang ditawarkan oleh gitlab. Untuk penjelasan mengenai CI/CD, anda dapat membaca artikel Belajar Melakukan Integrasi Jenkins Dan Gitlab Pada Docker. Pada artikel ini, kita akan mencoba membuat CI/CD untuk kebutuhan deployment service yang terdapat pada artikel Belajar Deployment Docker Pada Heroku.

Arsitektur Deployment Dengan Gitlab CI/CD

Berikut adalah arsitektur yang akan kita gunakan untuk CI/CD pada gitlab.

Arsitektur Gitlab CI_CD.png

Berikut adalah penjelasan dari gambar diatas.

  1. Developer akan melakukan pull code terlebih dahulu yang berasal dari gitlab
  2. Jika telah selesai mengubah atau menambahkan code, maka developer melakukan push code ke gitlab
  3. Jika terdapat perubahan, gitlab akan melakukan build source code lalu melakukan testing, biasanya trigger perubahan ini dapat dilakukan pada branch tertentu.
  4. Jika proses build source code dan testing berhasil maka gitlab akan melakukan build docker image, lalu docker image ini akan di push ke docker hub.
  5. Setelah selesai melakukan push ke docker hub, gitlab akan melakukan deployment ke heroku. Pada saat proses deployment, gitlab akan melakukan pull image terlebih dahulu dari docker hub, lalu image terebut nantinya akan di push ke registry heroku.
  6. Jika proses push docker image ke registry heroku berhasil, maka heroku akan menjalankan image tersebut.

Import Project Dari Github Ke Gitlab

Salah satu alasan menggunakan gitlab adalah gitlab memberikan banyak fitur terutama untuk penggunaan devops. Untuk melakukan import project dari github ke gitlab, silahkan lakukan langkah - langkah berikut.

  1. Silahkan login pada website gitlab. Lalu silahkan buat sebuah project, lalu pilih tab import project seperti gambar berikut.

Screen Shot 2018-03-03 at 7.20.05 PM

  1. Lalu pilih import dari github, maka akan muncul output seperti berikut.

Screen Shot 2018-03-03 at 7.29.22 PM.png

Lalu klik authorize sehingga gitlab dapat mengakses repo anda yang ada di github.

  1. Lalu pilih repo seperti berikut.

Screen Shot 2018-03-03 at 7.31.37 PM.png

  1. Setelah selesai, silahkan clone kembali repo Heroku-Container yang berasal dari gitlab seperti berikut
git clone git@gitlab.com:RizkiMufrizal/Heroku-Container.git

Membuat Unit Test Pada Spring Boot

Silahkan buka class HerokuContainerApplicationTests yang terdapat di dalam package org.rizki.mufrizal.heroku.container. Kemudian silahkan ubah source code nya menjadi seperti berikut untuk kebutuhan testing.

package org.rizki.mufrizal.heroku.container

import com.fasterxml.jackson.databind.ObjectMapper
import org.hamcrest.Matchers
import org.junit.Test
import org.junit.runner.RunWith
import org.rizki.mufrizal.heroku.container.domain.Barang
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.http.MediaType
import org.springframework.test.context.TestPropertySource
import org.springframework.test.context.junit4.SpringRunner
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultHandlers
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
import java.math.BigDecimal

@RunWith(SpringRunner::class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = [HerokuContainerApplication::class])
@AutoConfigureMockMvc
@TestPropertySource(locations = ["classpath:application.properties"])
class HerokuContainerApplicationTests {

    @Autowired
    lateinit var mockMvc: MockMvc

    @Test
    @Throws(Exception::class)
    fun getBarangsTest() {
        mockMvc
                .perform(MockMvcRequestBuilders.get("/api/barang").contentType(MediaType.APPLICATION_JSON))
                .andDo(MockMvcResultHandlers.print())
                .andExpect(MockMvcResultMatchers.status().isOk)
                .andExpect(MockMvcResultMatchers.jsonPath("$._embedded.barang", Matchers.hasSize<Int>(0)))
    }

    @Test
    @Throws(Exception::class)
    fun saveBarangTest() {
        val barang = Barang(namaBarang = "rinso", hargabarang = BigDecimal.valueOf(5000), jumlahBarang = 100)
        mockMvc
                .perform(MockMvcRequestBuilders.post("/api/barang").contentType(MediaType.APPLICATION_JSON).content(ObjectMapper().writeValueAsString(barang)).accept(MediaType.APPLICATION_JSON))
                .andDo(MockMvcResultHandlers.print())
                .andExpect(MockMvcResultMatchers.status().isCreated)
    }

}

Lalu buat sebuah file application.properties di dalam folder resources, lalu tambahkan konfigurasinya seperti berikut.

spring.profiles=heroku

spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=postgres://postgresqldb:postgresqldb@postgres:5432/heroku_container_test
spring.datasource.poolName=SpringBootHikariCP
spring.datasource.maximumPoolSize=3
spring.datasource.minimumIdle=5
spring.datasource.maxLifetime=2000000
spring.datasource.connectionTimeout=30000
spring.datasource.idleTimeout=30000

spring.jpa.generate-ddl=true
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQL9Dialect

spring.hateoas.use-hal-as-default-json-media-type=true

endpoints.cors.allowed-methods = POST, GET, OPTIONS, DELETE, PUT
endpoints.cors.allowed-origins = '*'
endpoints.cors.exposed-headers = accept, authorization, x-requested-with, content-type
endpoints.cors.max-age = 3600

spring.jackson.serialization.indent-output=true

spring.data.rest.base-path=/api

Maka struktur project akan berubah menjadi seperti berikut.

Screen Shot 2018-03-03 at 8.00.33 PM.png

Setup CI/CD Gitlab

Pada tahapan ini, kita akan mencoba setup CI/CD pada gitlab. Untuk membuat CI/CD pada gitlab sangatlah mudah yaitu dengan membuat sebuah file di dalam root project dengan nama .gitlab-ci.yml, lalu tambahkan source code berikut

image: docker:latest

stages:
 - build
 - publish-image
 - deploy-heroku-container

build:
 stage: build
 image: gradle:alpine
 variables:
  POSTGRES_DB: heroku_container_test
  POSTGRES_USER: postgresqldb
  POSTGRES_PASSWORD: postgresqldb
 services:
   - postgres:alpine
 script:
  - gradle clean test build
 artifacts:
  paths:
    - build/libs/*.jar

publish-image:
 stage: publish-image
 variables:
  POSTGRES_DB: heroku_container_test
  POSTGRES_USER: postgresqldb
  POSTGRES_PASSWORD: postgresqldb
  DOCKER_DRIVER: overlay2
 services:
  - docker:dind
  - postgres:alpine
 before_script:
  - docker info
 script:
 - echo $DOCKER_PASSWORD | docker login --username $DOCKER_USERNAME --password-stdin
 - apk add --no-cache openjdk8
 - java -version
 - ./gradlew clean test build docker dockerPush

deploy-heroku-container:
 stage: deploy-heroku-container
 image: rizkimufrizal/docker-node
 variables:
  DOCKER_DRIVER: overlay2
 services:
  - docker:dind
 before_script:
  - docker info
 script:
   - npm install -g heroku-cli
   - cd heroku-docker
   - echo $HEROKU_API_KEY | docker login --username=$HEROKU_USERNAME --password-stdin registry.heroku.com
   - heroku container:push web --app heroku-docker

Pada konfigurasi diatas terdapat 3 proses yang akan dijalankan oleh gitlab yaitu

  1. build yaitu proses test dan build project menjadi file jar, nantinya kita dapat mendownload file jar yang telah dibuild oleh gitlab.
  2. publish-image yaitu proses test build dan juga sekaligus proses push docker image ke docker hub.
  3. deploy-heroku-container yaitu proses deployment docker image ke heroku.

Setelah selesai, tahap selanjutnya adalah kita perlu melakukan konfigurasi Variables yang dibutuhkan. Berikut adalah variabel yang dibutuhkan untuk konfigurasi gitlab diatas.

  • DOCKER_USERNAME yaitu username docker hub
  • DOCKER_PASSWORD yaitu password docker hub
  • HEROKU_USERNAME yaitu username heroku biasanya menggunakan email
  • HEROKU_API_KEY yaity api key sebagai pengganti password heroku

Untuk melakukan konfigurasi variabel diatas, silahkan akses project di gitlab anda. Lalu pilih menu setting dan pilih menu CI / CD. Lalu expand Secret variables dan isikan konfigurasi nya seperti berikut.

Screen Shot 2018-03-03 at 8.15.20 PM.png

Untuk melihat API Key nya heroku, silahkan buka dashboard heroku, klik profile lalu pilih menu account settings, lalu scroll kebawah sehingga akan muncul menu API Key seperti berikut.

Screen Shot 2018-03-03 at 8.17.01 PM.png

Silahkan pilih menu reveal untuk melihat API Key tersebut.

Mengecek Hasil Deployment

Setelah konfigurasi diatas selesai, silahkan commit lalu push source code anda, jika berhasil maka muncul proses pipeline yang sedang berjalan pada project gitlab anda seperti berikut.

Screen Shot 2018-03-03 at 8.21.11 PM.png

Lalu silahkan pilih menu CI / CD di menu project anda, nantinya akan muncul pipeline yang sedang berjalan seperti berikut.

Screen Shot 2018-03-03 at 8.22.29 PM.png

Jika ingin lebih detail, silahkan klik menu jobs, maka akan muncul seperti berikut.

Screen Shot 2018-03-03 at 8.23.09 PM.png

Pada gambar diatas dapat dilihat bahwa jobs yang pertama yaitu build telah berhasil dikerjakan dan anda dapat melakukan download artifact jar melalui menu berikut.

Screen Shot 2018-03-03 at 8.24.29 PM.png

Berikut adalah gambar jika pipeline nya berhasil dijalankan

Screen Shot 2018-03-03 at 8.28.10 PM.png

Dan berikut adalah gambar jika semua jobs berhasil dijalankan

Screen Shot 2018-03-03 at 8.28.19 PM.png

Bagi anda yang ingin melihat source code codingan, silahkan lihat di Heroku-Container. Sekian artikel mengenai Belajar Gitlab Continuous Integration Dan Continuous Deployment dan terima kasih :).

Apache Cassandra adalah salah satu database yang banyak digunakan, terutama jika anda adalah developer API Gateway. Beberapa product API Gateway seperti axway dan kong menggunakan database Apache Cassandra untuk menyimpan seluruh konfigurasi API Gateway. Agar data lebih aman ketika terjadi sesuatu yang tidak diinginkan pada database Apache Cassandra maka dibutuhkan suatu proses replication pada database tersebut. Secara default, Apache Cassandra mendukung replication dimana replication yang ditawarkan dari Apache Cassandra adalah master - master, sehingga kita tidak perlu membuat 2 konfigurasi seperti konfigurasi master - slave pada MySQL atau mariadb. Pada artikel ini, penulis akan membahas mengenai bagaimana cara membuat replication di dalam 1 cluster apache cassandra dengan menggunakan docker swarm.

Arsitektur Cluster Apache Cassandra

Arsitektur cluster Apache Cassandra yang akan kita gunakan adalah sebagai berikut

Cassandra.png

Pada gambar diatas terdapat :

  • 6 Node Apache Cassandra, node disini adalah satu instalasi Apache Cassandra, dimana 1 node hanya dipasang pada 1 server, maka seharusnya kita menggunakan 6 server.
  • 3 Rack, rack disini untuk menentukan replication akan dilakukan secarah horizontal
  • 2 Data Center (data center dan data recovery), di dalam cassandra biasanya penulis menentukan 2 data center sehingga data center pertama adalah sebagai data center pada umumnya dan data ceter kedua sebagai data recovery.
  • 1 Cluster

Apa Itu Docker Swarm

Sebelum membahas tentang arsitektur nya, kita akan membahas terlebih dahulu mengenai docker swarm.

Docker Swarm adalah Salah satu product nya docker untuk dapat mendeploy container pada multihost.

Multihost disini adalah di banyak server. Misalnya kita mempunyai 2 server, misalnya server A dan server B. Masing - masing server akan dilakukan instalasi docker, dan masing - masing docker mempunyai 1 container yang sedang berjalan. Bagaimana caranya agar container yang terdapat pada server A dan melakukan komunikasi dengan container yang ada pada server B, sedangkan jaringan yang terdapat pada server berbeda dengan jaringan yang terdapat di dalam masing - masing container. Jawaban nya adalah dengan menggunakan docker swarm, dengan menggunakan docker swarm maka docker yang yang terdapat di dalam beberapa host dapat kita lakukan cluster sehingga seakan - akan docker tersebut terdapat pada 1 host. Dengan menggunakan docker swarm, kita bebas menetukan container tersebut ingin di deploy ke host yang diinginkan.

Arsitektur Cluster Apache Cassandra Pada Docker Swarm

Berikut adalah arsitektur yang akan digunakan.

Docker Swarm.png

Pada gambar diatas terdapat

  • 2 server yaitu dengan IP 192.168.50.2 dan 192.168.50.3
  • 3 container di masing - masing server, di dalam 1 container terdapat 1 node cassandra

Dari gambar diatas dapat kita lihat bahwa antar server, terdapat jaringan fisik misalnya LAN dan sebagainya, sedangkan di dalam docker container terdapat jaringan tersendiri, sehingga untuk menyambungkan container antar server/host maka digunakan jaringan overlay.

Jaringan Overlay adalah jaringan komputer virtual yang dibangun di atas jaringan lain.

Jaringan overlay ini nantinya akan dibuatkan oleh docker swarm sehingga kita tidak perlu melakukan konfigurasi secara manual, sehingga setiap container yang berbeda host dapat berkomunikasi pada 1 jaringan overlay.

Setup Centos 7 Dengan Vagrant

Pada artikel ini, penulis akan menggunakan vagrant untuk virtualisasi 2 server, bagi yang belum paham vagrant, silahkan simak artikel Belajar Vagrant. Silahkan buat sebuah file Vagrantfile lalu masukkan code berikut.

Vagrant.configure("2") do |config|

  config.vm.provider "virtualbox" do |v|
    v.memory = 3072
  end

  config.vm.define "master" do |master|
    master.vm.box = "centos/7"
    master.vm.hostname = 'master'
    master.vm.network "private_network", ip: "192.168.50.2"
  end

  config.vm.define "worker" do |worker|
    worker.vm.box = "centos/7"
    worker.vm.hostname = 'worker'
    worker.vm.network "private_network", ip: "192.168.50.3"
  end
end

Dari konfigurasi diatas, kita membuat 2 server dengan masing - masing ip yaitu 192.168.50.2 dan 192.168.50.3. Sistem operasi yang kita gunakan adalah centos 7. Setelah selesai, silahkan jalankan perintah berikut untuk menjalankan kedua server tersebut.

vagrant up

Setelah selesai, silahkan akses kedua vagrant tersebut dengan perintah

vagrant ssh master
vagrant ssh worker

Lakukan perintah berikut pada kedua server diatas. Silahkan login dengan user dengan perintah.

sudo -s

Kemudian jalankan perintah berikut untuk menghapus docker versi lama

yum remove docker docker-common docker-selinux docker-engine

Jalankan perintah berikut untuk instalasi config manager yum

yum install -y yum-utils device-mapper-persistent-data lvm2

Lalu tambahkan repo docker pada centos dengan perintah berikut.

yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

Lalu lakukan update dengan perintah

yum update -y

Setelah selesai, lakukan instalasi docker dengan perintah

yum install -y docker-ce

Setelah selesai, silahkan jalankan docker dengan perintah

systemctl start docker

Setup Docker Swarm

Silahkan akses server master, lalu jalankan perintah berikut untuk inisialisasi docker swarm

docker swarm init --advertise-addr 192.168.50.2

IP diatas adalah IP dari server master, jika berhasil maka akan muncul output seperti berikut.

[root@master vagrant]# docker swarm init --advertise-addr 192.168.50.2
Swarm initialized: current node (hw3kzewnluvm9gmf6cz8sfunb) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-2il6rouemqho08tl2dmipd77prj293gqqn8elzgzeiu5qhmwrb-efy6r4nj9wnrvci6x7aen3ikz 192.168.50.2:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

Lalu silahkan akses server worker lalu jalankan perintah join seperti berikut

docker swarm join --token SWMTKN-1-2il6rouemqho08tl2dmipd77prj293gqqn8elzgzeiu5qhmwrb-efy6r4nj9wnrvci6x7aen3ikz 192.168.50.2:2377

Perintah join tersebut berfungsi untuk mendaftarkan server/host pada docker swarm master. Setelah selesai, silahkan akses server master kembali lalu jalankan perintah berikut untuk melihat node / host / server apa saja yang telah terdaftar.

docker node ls

Dan berikut adalah outputnya.

[root@master vagrant]# docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS
hw3kzewnluvm9gmf6cz8sfunb *   master              Ready               Active              Leader
wknzip11beucg2u4e6u46noy4     worker              Ready               Active

Setelah selesai, langkah berikut nya yaitu kita akan memberikan penamaan kepada setiap node / host / server agar nantinya kita dapat menentukan sebuah container dapat di deploy pada node / host / server yang mana. Silahkan jalankan perintah berikut untuk melakukan penamaan label pada node / host / server master dan worker.

docker node update --label-add server=master hw3kzewnluvm9gmf6cz8sfunb
docker node update --label-add server=worker wknzip11beucg2u4e6u46noy4

ID diatas dapat dilihat dengan perintah menampilkan semua node yang telah kita lakukan sebelumnya.

Membuat Compose Cassandra Untuk Docker Swarm

Langkah selanjutnya, kita akan membuat sebuah file yaitu docker-compose.yml, isinya sama seperti konfigurasi docker compose hanya saja terdapat penambahan untuk kebutuhan docker swarm, silahkan buat file docker-compose.yml di dalam server master, lalu tambahkan code berikut.

version: '3'
services:
  cassandra_dc_1:
    image: cassandra:latest
    command: bash -c 'if [ -z "$$(ls -A /var/lib/cassandra/)" ] ; then sleep 0; fi && /docker-entrypoint.sh cassandra -f'
    deploy:
      placement:
        constraints:
          - node.labels.server == master
      restart_policy:
        condition: on-failure
        max_attempts: 3
        window: 120s
    environment:
      CASSANDRA_CLUSTER_NAME: "CassandraCluster"
      CASSANDRA_BROADCAST_ADDRESS: cassandra_dc_1
      CASSANDRA_SEEDS: cassandra_dc_1,cassandra_dr_1
      CASSANDRA_DC: DC
      CASSANDRA_RACK: RACK1
      CASSANDRA_ENDPOINT_SNITCH: GossipingPropertyFileSnitch
      MAX_HEAP_SIZE: 300m
      HEAP_NEWSIZE: 200m
    ports:
    - "7000"
    networks:
      default:

  cassandra_dc_2:
    image: cassandra:latest
    command: bash -c 'if [ -z "$$(ls -A /var/lib/cassandra/)" ] ; then sleep 120; fi && /docker-entrypoint.sh cassandra -f'
    deploy:
      placement:
        constraints:
          - node.labels.server == master
      restart_policy:
        condition: on-failure
        max_attempts: 3
        window: 120s
    environment:
      CASSANDRA_CLUSTER_NAME: "CassandraCluster"
      CASSANDRA_BROADCAST_ADDRESS: cassandra_dc_2
      CASSANDRA_SEEDS: cassandra_dc_1,cassandra_dr_1
      CASSANDRA_DC: DC
      CASSANDRA_RACK: RACK2
      CASSANDRA_ENDPOINT_SNITCH: GossipingPropertyFileSnitch
      MAX_HEAP_SIZE: 300m
      HEAP_NEWSIZE: 200m
    depends_on:
      - cassandra_dc_1
    ports:
    - "7000"
    networks:
      default:

  cassandra_dc_3:
    image: cassandra:latest
    command: bash -c 'if [ -z "$$(ls -A /var/lib/cassandra/)" ] ; then sleep 240; fi && /docker-entrypoint.sh cassandra -f'
    deploy:
      placement:
        constraints:
          - node.labels.server == master
      restart_policy:
        condition: on-failure
        max_attempts: 3
        window: 120s
    environment:
      CASSANDRA_CLUSTER_NAME: "CassandraCluster"
      CASSANDRA_BROADCAST_ADDRESS: cassandra_dc_3
      CASSANDRA_SEEDS: cassandra_dc_1,cassandra_dr_1
      CASSANDRA_DC: DC
      CASSANDRA_RACK: RACK3
      CASSANDRA_ENDPOINT_SNITCH: GossipingPropertyFileSnitch
      MAX_HEAP_SIZE: 300m
      HEAP_NEWSIZE: 200m
    depends_on:
      - cassandra_dc_2
    ports:
    - "7000"
    networks:
      default:

  cassandra_dr_1:
    image: cassandra:latest
    command: bash -c 'if [ -z "$$(ls -A /var/lib/cassandra/)" ] ; then sleep 60; fi && /docker-entrypoint.sh cassandra -f'
    deploy:
      placement:
        constraints:
          - node.labels.server == worker
      restart_policy:
        condition: on-failure
        max_attempts: 3
        window: 120s
    environment:
      CASSANDRA_CLUSTER_NAME: "CassandraCluster"
      CASSANDRA_BROADCAST_ADDRESS: cassandra_dr_1
      CASSANDRA_SEEDS: cassandra_dr_1,cassandra_dc_1
      CASSANDRA_DC: DR
      CASSANDRA_RACK: RACK1
      CASSANDRA_ENDPOINT_SNITCH: GossipingPropertyFileSnitch
      MAX_HEAP_SIZE: 300m
      HEAP_NEWSIZE: 200m
    depends_on:
      - cassandra_dc_1
    ports:
    - "7000"
    networks:
      default:

  cassandra_dr_2:
    image: cassandra:latest
    command: bash -c 'if [ -z "$$(ls -A /var/lib/cassandra/)" ] ; then sleep 180; fi && /docker-entrypoint.sh cassandra -f'
    deploy:
      placement:
        constraints:
          - node.labels.server == worker
      restart_policy:
        condition: on-failure
        max_attempts: 3
        window: 120s
    environment:
      CASSANDRA_CLUSTER_NAME: "CassandraCluster"
      CASSANDRA_BROADCAST_ADDRESS: cassandra_dr_2
      CASSANDRA_SEEDS: cassandra_dr_1,cassandra_dc_1
      CASSANDRA_DC: DR
      CASSANDRA_RACK: RACK2
      CASSANDRA_ENDPOINT_SNITCH: GossipingPropertyFileSnitch
      MAX_HEAP_SIZE: 300m
      HEAP_NEWSIZE: 200m
    depends_on:
      - cassandra_dr_1
    ports:
    - "7000"
    networks:
      default:

  cassandra_dr_3:
    image: cassandra:latest
    command: bash -c 'if [ -z "$$(ls -A /var/lib/cassandra/)" ] ; then sleep 300; fi && /docker-entrypoint.sh cassandra -f'
    deploy:
      placement:
        constraints:
          - node.labels.server == worker
      restart_policy:
        condition: on-failure
        max_attempts: 3
        window: 120s
    environment:
      CASSANDRA_CLUSTER_NAME: "CassandraCluster"
      CASSANDRA_BROADCAST_ADDRESS: cassandra_dr_3
      CASSANDRA_SEEDS: cassandra_dr_1,cassandra_dc_1
      CASSANDRA_DC: DR
      CASSANDRA_RACK: RACK3
      CASSANDRA_ENDPOINT_SNITCH: GossipingPropertyFileSnitch
      MAX_HEAP_SIZE: 300m
      HEAP_NEWSIZE: 200m
    depends_on:
      - cassandra_dr_2
    ports:
    - "7000"
    networks:
      default:

networks:
  default:

Setelah selesai, silahkan jalankan perintah berikut untuk melakukan pull image cassandra terlebih dahulu.

docker pull cassandra

Setelah selesai, silahkan jalankan semua cassandra dengan perintah berikut

docker stack deploy --compose-file docker-compose.yml cassandra

Jika berhasil, silahkan cek service nya dengan perintah

docker service ls

Maka akan muncul output seperti berikut

ID                  NAME                       MODE                REPLICAS            IMAGE               PORTS
hx95sgod38ip        cassandra_cassandra_dc_1   replicated          1/1                 cassandra:latest    *:30020->7000/tcp
n3dbljgnptsj        cassandra_cassandra_dc_2   replicated          1/1                 cassandra:latest    *:30021->7000/tcp
tdqtwh7eaib3        cassandra_cassandra_dc_3   replicated          1/1                 cassandra:latest    *:30016->7000/tcp
l69lcdacvnnw        cassandra_cassandra_dr_1   replicated          1/1                 cassandra:latest    *:30017->7000/tcp
tgyypjfaeenf        cassandra_cassandra_dr_2   replicated          1/1                 cassandra:latest    *:30018->7000/tcp
7ao6ubi2j7e4        cassandra_cassandra_dr_3   replicated          1/1                 cassandra:latest    *:30019->7000/tcp

Silahkan cek container yang sudah jalan dengan perintah

docker ps

Maka akan muncul output seperti berikut

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                         NAMES
305e963a4989        cassandra:latest    "docker-entrypoint.s…"   8 minutes ago       Up 8 minutes        7000-7001/tcp, 7199/tcp, 9042/tcp, 9160/tcp   cassandra_cassandra_dc_2.1.53t1s2c5obqwwfzgenbnj3a3f
03501ebc8870        cassandra:latest    "docker-entrypoint.s…"   8 minutes ago       Up 8 minutes        7000-7001/tcp, 7199/tcp, 9042/tcp, 9160/tcp   cassandra_cassandra_dc_1.1.qj4nikx2x9qweqrjp5h2i644a
16b1d316c721        cassandra:latest    "docker-entrypoint.s…"   8 minutes ago       Up 8 minutes        7000-7001/tcp, 7199/tcp, 9042/tcp, 9160/tcp   cassandra_cassandra_dc_3.1.f3fesq271jnpky0zw1la4vdhy

Cari NAMES yang memiliki nama cassandra_cassandra_dc_1, misalnya jika dilihat dari atas, kita menemukan nya di cassandra_cassandra_dc_1.1.qj4nikx2x9qweqrjp5h2i644a. Lalu silahkan akses container tersebut dengan perintah.

docker exec -it 03501ebc8870 /bin/bash

Lalu jalankan perintah berikut untuk mengecek cluster cassandra

nodetool status

Jika berhasil maka akan muncul output seperti berikut.

Datacenter: DC
==============
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
--  Address    Load       Tokens       Owns (effective)  Host ID                               Rack
UN  10.0.0.67  92.35 KiB  256          31.5%             a5c66734-eb77-4c2c-9555-7b6de6d61529  RACK1
UN  10.0.0.69  75.05 KiB  256          34.2%             4a1db9a3-8a29-4da7-9fce-809ef056a35c  RACK2
UN  10.0.0.59  74.99 KiB  256          35.2%             0e12bb91-91db-4344-b532-1ca01a0e36df  RACK3
Datacenter: DR
==============
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
--  Address    Load       Tokens       Owns (effective)  Host ID                               Rack
UN  10.0.0.61  69.93 KiB  256          33.1%             d07abb28-c4ce-4b49-b322-0aed46324adc  RACK1
UN  10.0.0.63  69.93 KiB  256          34.9%             697e25ba-0e1f-4e8b-ba65-d7fa0c8c98b1  RACK2
UN  10.0.0.65  74.98 KiB  256          31.2%             3c28cce9-7514-4a54-97ca-97a063ecdd06  RACK3

Sekian artikel mengenai Belajar Membuat Cluster Apache Cassandra Dengan Docker Swarm dan terima kasih :).

Setelah sekian lama tidak aktif untuk menulis artikel, akhirnya hari ini penulis menyempatkan waktu untuk menulis artikel baru :D. Pada artikel ini, penulis akan membahas mengenai bagaimana teknik deployment docker pada heroku. Secara default, heroku sebenarnya mendukung deploment untuk docker akan tetapi hanya sedikit artikel dan tutorial mengenai deployment pada heroku.

Mungkin ada yang bertanya, mengapa kita menggunakan heroku untuk melakukan deployment docker ? sebenarnya untuk melakukan deployment docker dapat menggunakan provider lain misalnya google cloud, akan tetapi google cloud mewajibkan kita menggunakan kartu kredit untuk menggunakan versi free :(, untuk mengatasi hal tersebut, kita dapat menggunakan heroku :D. Proses deployment ini juga penulis gunakan untuk deployment aplikasi - aplikasi microservice yang penulis bangun untuk kebutuhan tesis :), jadi dapat dipastikan bahwa deployment docker pada heroku merupakan alternative yang tepat bagi developer yang ingin mencoba teknologi docker.

Setup Project Spring Boot

Pada artikel ini, penulis akan membuat sebuah project spring boot dengan bahasa pemrograman kotlin yang nantinya akan di deploy ke container. Silahkan akses start.spring.io, lalu isi seperti berikut.

Screen Shot 2018-02-11 at 9.56.34 PM.png

Silahkan download lalu extract project tersebut dan import ke dalam IDE Intellij IDEA. Lalu silahkan buka file build.gradle dan ubah menjadi seperti berikut.

buildscript {
    ext {
        kotlinVersion = '1.2.21'
        springBootVersion = '1.5.10.RELEASE'
        gradleDockerPlugin = '0.13.0'
    }
    repositories {
        maven {
            url "https://plugins.gradle.org/m2/"
        }
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
        classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}")
        classpath("gradle.plugin.com.palantir.gradle.docker:gradle-docker:${gradleDockerPlugin}")
    }
}

apply plugin: 'kotlin'
apply plugin: 'kotlin-spring'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'com.palantir.docker'

group = 'org.rizki.mufrizal.heroku.container'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

jar {
    baseName = 'heroku-container'
    version = '0.0.1'
}

docker {
    name "rizkimufrizal/${jar.baseName}"
    tags 'latest'
    files jar.archivePath
    buildArgs(['JAR_FILE': "${jar.archiveName}"])
}

compileKotlin {
    kotlinOptions {
        freeCompilerArgs = ["-Xjsr305=strict"]
        jvmTarget = "1.8"
    }
}
compileTestKotlin {
    kotlinOptions {
        freeCompilerArgs = ["-Xjsr305=strict"]
        jvmTarget = "1.8"
    }
}

repositories {
    mavenCentral()
}

dependencies {
    compile('org.springframework.boot:spring-boot-starter-data-jpa')
    compile('org.springframework.boot:spring-boot-starter-data-rest')
    compile('org.springframework.boot:spring-boot-starter-hateoas')
    compile('org.springframework.boot:spring-boot-starter-web')
    compile("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}")
    compile("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
    compile('com.zaxxer:HikariCP')
    runtime('org.postgresql:postgresql')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

Dapat dilihat pada bagian berikut.

jar {
    baseName = 'heroku-container'
    version = '0.0.1'
}

docker {
    name "rizkimufrizal/${jar.baseName}"
    tags 'latest'
    files jar.archivePath
    buildArgs(['JAR_FILE': "${jar.archiveName}"])
}

merupakan konfigurasi plugin docker pada gradle yang nantinya kita gunakan untuk melakukan proses pembuatan image. rizkimufrizal adalah nama dari account docker hub. Langkah selanjutnya silahkan buka file application.properties di dalam folder src/main/resources lalu ubah konfigurasinya menjadi seperti berikut.

spring.profiles=heroku

spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=${DATABASE_URL}
spring.datasource.poolName=SpringBootHikariCP
spring.datasource.maximumPoolSize=3
spring.datasource.minimumIdle=5
spring.datasource.maxLifetime=2000000
spring.datasource.connectionTimeout=30000
spring.datasource.idleTimeout=30000

spring.jpa.generate-ddl=true
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQL9Dialect

spring.hateoas.use-hal-as-default-json-media-type=true

endpoints.cors.allowed-methods = POST, GET, OPTIONS, DELETE, PUT
endpoints.cors.allowed-origins = '*'
endpoints.cors.exposed-headers = accept, authorization, x-requested-with, content-type
endpoints.cors.max-age = 3600

spring.jackson.serialization.indent-output=true

spring.data.rest.base-path=/api

${DATABASE_URL} merupakan variabel yang ada pada heroku, variabel ini nantinya kita gunakan untuk mengambil url database. Lalu silahkan buat sebuah package configuration di dalam package org.rizki.mufrizal.heroku.container lalu buatlah sebuah class kotlin DataSourceConfiguration untuk keperluan konfigurasi datasource, dan ubah codingan nya seperti berikut.

package org.rizki.mufrizal.heroku.container.configuration

import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.env.Environment
import java.net.URI
import javax.sql.DataSource

/**
 *
 * @Author Rizki Mufrizal <mufrizalrizki@gmail.com>
 * @Web <https://RizkiMufrizal.github.io>
 * @Since 11 February 2018
 * @Time 10:31 PM
 * @Project Heroku-Container
 * @Package org.rizki.mufrizal.heroku.container.configuration
 * @File DataSourceConfiguration
 *
 */

@Configuration
class DataSourceConfiguration @Autowired constructor(val environment: Environment) {

    @Bean(destroyMethod = "close")
    fun dataSource(): DataSource {
        val databaseUrl = URI(environment.getRequiredProperty("spring.datasource.url"))
        val dataSourceConfig = HikariConfig()
        dataSourceConfig.driverClassName = environment.getRequiredProperty("spring.datasource.driver-class-name")
        dataSourceConfig.jdbcUrl = "jdbc:postgresql://${databaseUrl.host}:${databaseUrl.port}${databaseUrl.path}"
        dataSourceConfig.username = databaseUrl.userInfo.split(":")[0]
        dataSourceConfig.password = databaseUrl.userInfo.split(":")[1]
        dataSourceConfig.maximumPoolSize = environment.getRequiredProperty("spring.datasource.maximumPoolSize").toInt()
        dataSourceConfig.minimumIdle = environment.getRequiredProperty("spring.datasource.minimumIdle").toInt()
        dataSourceConfig.connectionTimeout = environment.getRequiredProperty("spring.datasource.connectionTimeout").toLong()
        dataSourceConfig.idleTimeout = environment.getRequiredProperty("spring.datasource.idleTimeout").toLong()
        dataSourceConfig.addDataSourceProperty("poolName", environment.getRequiredProperty("spring.datasource.poolName"))
        dataSourceConfig.addDataSourceProperty("cachePrepStmts", true)
        dataSourceConfig.addDataSourceProperty("prepStmtCacheSize", 250)
        dataSourceConfig.addDataSourceProperty("prepStmtCacheSqlLimit", 2048)
        return HikariDataSource(dataSourceConfig)
    }
}

Langkah selanjutnya silahkan buat package domain pada package org.rizki.mufrizal.heroku.container lalu buat sebuah class kotlin dengan nama Barang lalu masukkan codingan seperti berikut.

package org.rizki.mufrizal.heroku.container.domain

import org.hibernate.annotations.GenericGenerator
import java.math.BigDecimal
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.Id
import javax.persistence.Table

/**
 *
 * @Author Rizki Mufrizal <mufrizalrizki@gmail.com>
 * @Web <https://RizkiMufrizal.github.io>
 * @Since 11 February 2018
 * @Time 10:34 PM
 * @Project Heroku-Container
 * @Package org.rizki.mufrizal.heroku.container.domain
 * @File Barang
 *
 */

@Entity
@Table(name = "tb_barang")
data class Barang(
        @Id
        @GeneratedValue(generator = "uuid2")
        @GenericGenerator(name = "uuid2", strategy = "uuid2")
        @Column(name = "id_barang", length = 36)
        val idBarang: String? = null,

        @Column(name = "nama_barang", length = 50)
        val namaBarang: String? = null,

        @Column(name = "jumlah_barang")
        val jumlahBarang: Int? = null,

        @Column(name = "harga_barang")
        val hargabarang: BigDecimal? = null
)

Setelah selesai silahkan buat package repository di dalam package org.rizki.mufrizal.heroku.container lalu buat sebuah class kotlin dengan nama BarangRepostory lalu ubah codingan menjadi seperti berikut.

package org.rizki.mufrizal.heroku.container.repository

import org.rizki.mufrizal.heroku.container.domain.Barang
import org.springframework.data.repository.PagingAndSortingRepository
import org.springframework.data.rest.core.annotation.Description
import org.springframework.data.rest.core.annotation.RepositoryRestResource

/**
 *
 * @Author Rizki Mufrizal <mufrizalrizki@gmail.com>
 * @Web <https://RizkiMufrizal.github.io>
 * @Since 11 February 2018
 * @Time 10:38 PM
 * @Project Heroku-Container
 * @Package org.rizki.mufrizal.heroku.container.repository
 * @File BarangRepostory
 *
 */

@RepositoryRestResource(collectionResourceRel = "barang", path = "barang", collectionResourceDescription = Description("API Barang"))
interface BarangRepostory : PagingAndSortingRepository<Barang, String>

Lalu ubah main class HerokuContainerApplication menjadi seperti berikut.

package org.rizki.mufrizal.heroku.container

import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.hateoas.config.EnableHypermediaSupport

/**
 *
 * @Author Rizki Mufrizal <mufrizalrizki@gmail.com>
 * @Web <https://RizkiMufrizal.github.io>
 * @Since 11 February 2018
 * @Time 10:31 PM
 * @Project Heroku-Container
 * @Package org.rizki.mufrizal.heroku.container
 * @File HerokuContainerApplication
 *
 */

@SpringBootApplication
@EnableHypermediaSupport(type = [EnableHypermediaSupport.HypermediaType.HAL])
class HerokuContainerApplication

fun main(args: Array<String>) {
    SpringApplication.run(HerokuContainerApplication::class.java, *args)
}

Langkah selanjutnya silahkan buat file Dockerfile pada root project lalu tambahkan codingan berikut.

FROM openjdk:8-jdk-alpine
ARG JAR_FILE
ADD ${JAR_FILE} app.jar
RUN sh -c 'touch /app.jar'

Setelah selesai, selanjutnya kita akan membuat image berdasarkan project tersebut, silahkan jalankan command berikut untuk login ke docker hub.

docker login

Setelah selesai login, lalu jalankan perintah gradle berikut.

gradle clean build docker dockerPush -x test

Jika telah selesai, maka anda dapat melihat image anda telah tersedia di docker hub seperti berikut.

Screen Shot 2018-02-11 at 11.00.13 PM.png

Deploy Docker Container Ke Heroku

Silahkan buat sebuah folder di dalam root project dengan nama heroku-docker lalu buat sebuah file Dockerfile di dalamnya dan tambahkan konfigurasi docker seperti berikut.

FROM rizkimufrizal/heroku-container
CMD java -Xmx300m -Xss512k -Dspring.profiles.active=heroku -jar /app.jar --server.port=$PORT

Container yang ada pada heroku wajib menggunakan perintah CMD untuk menjalankan service nya, dikarenakan ini adalah project java maka penulis menggunakan command java untuk menjalankan aplikasi web tersebut. -Xmx300m -Xss512k merupakan perintah untuk membatasi penggunaan memory dikarenakan memory untuk heroku versi gratis dibatasi hanya sampai 512 MB.

Silahkan login pada web heroku di https://dashboard.heroku.com, lalu buat sebuah aplikasi dengan nama heroku-docker, untuk nama silahkan gunakan nama yang anda inginkan. Setelah selesai, silahkan masuk ke dalam detail aplikasi tersebut lalu pilih menu resources, lalu pada bagian add-ons silahkan ketika postgres maka akan muncul seperti berikut.

Screen Shot 2018-02-11 at 11.09.14 PM.png

Nantinya akan muncul seperti gambar berikut dan pilih provision

Screen Shot 2018-02-11 at 11.09.23 PM.png

Nantinya akan muncul seperti berikut.

Screen Shot 2018-02-11 at 11.09.29 PM.png

Lalu silahkan akses folder heroku-docker melalui terminal lalu jalankan perintah berikut untuk melakukan login ke heroku.

heroku login

silahkan isikan email dan password anda, lalu login ke registry docker heroku dengan perintah

heroku container:login

Kemudian untuk melakukan deploy ke heroku, silakan jalankan perintah berikut. Perintah berikut menggunakan heroku-cli, silahkan lihat cara instalasi heroku-cli di heroku-cli.

heroku container:push web --app heroku-docker

heroku-docker silahkan gunakan nama aplikasi yang anda buat di heroku. Jika berhasil maka akan muncul output seperti berikut pada terminal anda.

Screen Shot 2018-02-11 at 11.22.15 PM.png

Test REST API Spring Boot

Untuk melakukan test terhadap REST API yang telah kita bangun, silahkan jalankan command berikut untuk melakukan proses save data.

curl -X POST -H "Content-type: application/json" -d '{
  "namaBarang": "rinso",
  "jumlahBarang": 100,
  "hargabarang": 5000
}' 'https://heroku-docker.herokuapp.com/api/barang'

Lalu silahkan akses data dengan command berikut

curl -X GET https://heroku-docker.herokuapp.com/api/barang

Untuk mematikan container pada heroku, anda dapat melakukan scale down menjadi 0 dengan perintah

heroku ps:scale web=0 --app heroku-docker

Bagi anda yang ingin melihat source code codingan, silahkan lihat di Heroku-Container. Sekian artikel mengenai Belajar Deployment Docker Pada Heroku dan terima kasih :).