# [Explore] Try Out Istio

Eksplorasi awal mengenai istio bertujuan untuk membuat 2 hal berikut:

1. Membuat model deployment stable dan canary untuk control centre
    
2. Membuat HTTP routing rule yang menggunakan header sebagai selectornya. Use case ini bisa digunakan oleh QA untuk stress test atau untuk kebutuhan yang lain.
    

### **Step 1: Pengaturan *Development Environment***

Eksplorasi akan menggunakan minikube sebagai media menjalankan ***node*** kubernetes. Berikut versi minikube yang digunakan:

```bash
minikube version: v1.30.1
commit: 08896fd1dc362c097c925146c4a0d0dac715ace0
```

Pertama, jalankan minikube dengan perintah sebagai berikut:

```bash
minikube start --cpus=4 --memory=8g
```

Perintah tersebut akan membuat ***node*** k8s pada komputer lokal dengan spesifikasi 4 logical CPU dan 8 gigabyte RAM. Patut diperhatikan bahwa spesifikasi yang dipakai juga memperhatikan bahwa istio sendiri meminta cukup banyak resource untuk digunakan.

Selain itu, istio akan diinstall menggunakan istioctl dengan menggunakan profile defaultnya. Berikut adalah perintahnya:

```bash
istioctl install
```

Perintah diatas akan memasang komponen istio (istio core, istiod, dan ingress gateway). Setelah istioctl, masukkan perintah berikut untuk memastikan komponen istio telah terinstall dengan baik.

```bash
kubectl -n istio-system get deployments
NAME                   READY   UP-TO-DATE   AVAILABLE   AGE
istio-ingressgateway   1/1     1            1           32h
istiod                 1/1     1            1           32h
```

Untuk menghapus pemasangan istio dalam ***node*** kubernetes lokal, perintah dibawah dapat dimasukkan ke dalam terminal, dengan mengasumsikan ***node*** kubernetes tengah berjalan dalam mesin lokal.

```bash
istioctl uninstall --purge
```

### **Step 2 Pembuatan Webserver**

Eksplorasi dilanjutkan dengan pembuatan kode **webserver** sederhana sebagai berikut:

```go
package main

import (
	"encoding/json"
	"log"
	"net/http"
)

func httpHandler(w http.ResponseWriter, r *http.Request) {
	log.Println("http call")
	message := map[string]string{
		"message": "version: v1",
	}
	jsonByte, err := json.Marshal(message)
	if err != nil {
		log.Panic(err.Error())
	}
	w.Write(jsonByte)
}

func main() {
	http.HandleFunc("/version", httpHandler)
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Panic(err.Error())
	}
}
```

Kode diatas cukup sederhana, sebuah kode untuk menjalankan suatu webserver yang ***listen*** ke port 8080. Webserver ini memiliki 1 endpoint (/version) yang mengembalikan message berikut.

```json
{
	"message": "version: v1"
}
```

Setelah kode diatas dibuat, mari buat image docker dari webserver ini dengan tag simple-service:v1.0.0. Berikut dockerfile yang digunakan untuk melakukan build image docker dan jalankan perintah untuk build docker ***image***:

```plaintext
FROM golang:1.20-alpine3.17 AS builder

WORKDIR /app

COPY . .

RUN go mod vendor

RUN go clean && CGO_ENABLED=0 go build -o main ./main.go

FROM alpine:3.17

WORKDIR /app

COPY --from=builder /app/main ./

CMD [ "./main" ]
```

```bash
// flag --no-cache digunakan supaya image yang digunakan dalam proses build
// tidak tersimpan
docker build . -t simple-service:v1.0.0 --no-cache
```

Untuk memastikan image dapat dijalankan dengan baik, masukkan perintah berikut untuk menjalankan ***container*** dari **image** yang baru saja dibuat. Perintah ini akan membuat dan menjalankan container yang dapat diakses melalui port 8080

```bash
docker run --name simple-service-v1.0.0 -p 8080:8080 simple-service:v1.0.0
```

Setelah kita pastikan bahwa **container** telah berjalan sebagaimana mestinya, mari buat **image** kedua dengan tidak lupa mengubah balikan dari ***endpoint*** menjadi sebagai berikut:

```bash
{
	"message": "version: v2"
}
```

Lakukan proses pembuatan **image** docker sama seperti pada pembuatan image pertama. Setelah proses pembuatan **image** selesai. Kita bisa memindahkan **image** yang telah kita **build** ke minikube dengan perintah sebagai berikut

```bash
minikube image load <namaImage:vX.Y.Z>
```

Setelah kedua docker **image** berhasil diunggah ke minikube, kita bisa memastikan kedua image tersebut terunggah ke minikube dengan perintah berikut:

```bash
minikube image ls | grep simple-service
docker.io/library/simple-service:v2.0.0
docker.io/library/simple-service:v1.0.0
```

### **Step 3 Pengaturan Skrip K8s**

Buat skrip deployment untuk kedua ***image*** docker dan ***service*** untuk keduanya. Skrip dibawah merupakan contoh yang bisa digunakan untuk untuk pembuatan dua ***deployment*** yang masing - masing memiliki ***service.*** Kedua **service** ini nanti digabungkan bersama dengan menggunakan virtual service untuk mengatur ***http traffic.***

```yaml
apiVersion: v1
kind: Service
metadata:
  name: simple-service-stable
  labels:
    app: simple-service
    service: simple-service
spec:
  selector:
    app: simple-service
    version: v1
  ports:
  - port: 8080
    targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: simple-service-canary
  labels:
    app: simple-service
    service: simple-service
spec:
  selector:
    app: simple-service
    version: v2
  ports:
  - port: 8080
    targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: simple-service-v1
  labels: 
    app: simple-service
    version: v1
spec:
  selector:
    matchLabels:
      app: simple-service
      version: v1
  template:
    metadata:
      labels:
        app: simple-service
        version: v1
    spec:
      containers:
      - name: simple-service
        image: docker.io/library/simple-service:v1.0.0
        resources:
          limits:
            memory: "64Mi"
            cpu: "100m"
        ports:
        - containerPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: simple-service-v2
  labels: 
    app: simple-service
    version: v2
spec:
  selector:
    matchLabels:
      app: simple-service
      version: v2
  template:
    metadata:
      labels:
        app: simple-service
        version: v2
    spec:
      containers:
      - name: simple-service
        image: docker.io/library/simple-service:v2.0.0
        resources:
          limits:
            memory: "64Mi"
            cpu: "100m"
        ports:
        - containerPort: 8080
```

### **Step 4 Pengaturan Skrip K8s x Istio : Traffic Splitting (Canary Deployment)**

Sebelum memasukkan resource istio, patut diperhatikan kita memilih untuk menggunakan gateway API yang baru ketimbang penggunaan API dari istio. Hal ini dikarenakan istio sendiri akan mendukung Gateway API kedepannya untuk lebih mengintegrasikan API istio dengan API dari kubernetes itu sendiri. Berikut skrip untuk penambahan CR (Custom Resource):

```bash
kubectl get crd gateways.gateway.networking.k8s.io || \
  { kubectl kustomize "github.com/kubernetes-sigs/gateway-api/config/crd?ref=v0.5.0" | kubectl apply -f -; }
```

Setelah menambahan CR untuk Gateway API, kita dapat menambahkan skrip sebagai berikut untuk menambahkan skrip gateway dan HTTPRoute. Gateway digunakan sebagai jalan masuk traffic dari luar ke dalam ***node*** kubernetes. Sedangkan HTTPRoute digunakan untuk traffic ***east-west*** di dalam ***node*** kubernetes:

```yaml
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
  name: simple-service-gateway
spec:
  gatewayClassName: istio
  listeners:
  - name: http
    port: 80
    protocol: HTTP
    allowedRoutes:
      namespaces:
        from: All
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata: 
  name: simple-http-route
spec:
  parentRefs:
    - name: simple-service-gateway
  rules:
    - matches:
      - path:
          type: Exact
          value: /version
      backendRefs:
        - name: simple-service-stable
          port: 8080
          weight: 90
        - name: simple-service-canary
          port: 8080
          weight: 10
```

Setelah skrip diatas dijalankan, mari kita coba ***deployment istio*** kita. Untuk mendapatkan IP dari gateway yang kita punya, kita dapat menjalankan skrip berikut:

```bash
kubectl wait --for=condition=ready gtw simple-service-gateway   
export INGRESS_HOST=$(kubectl get gtw simple-service-gateway -o jsonpath='{.status.addresses[*].value}')
```

Setelah kita mendapatkan IP, kita dapat menjalankan skrip ***curl*** untuk melakukan API **call.**

```bash
for run in {1..10}; do curl http://$INGRESS_HOST/version; done
{"message":"version: v1"}
{"message":"version: v1"}
{"message":"version: v1"}
{"message":"version: v1"}
{"message":"version: v1"}
{"message":"version: v1"}
{"message":"version: v1"}
{"message":"version: v1"}
{"message":"version: v1"}
{"message":"version: v2"}
```

Dari hasil curl diatas, terlihat bahwa istio dapat mengarahkan **traffic** sesuai dengan ***weight*** yang kita atur dalam skrip HTTPRoute.

### **Step 5 Pengaturan Skrip K8s x Istio : Traffic Routing (Content Based)**

Istio juga mendukung **routing** berdasarkan **http header** yang tersemat dalam ***http request.*** Pada skrip k8s dibawah, istio akan mengarahkan traffic ke “simple-service-qa” apabila ***http request*** memiliki headers “user” bernilai “iot-qa”.

```yaml
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
  name: simple-service-gateway
spec:
  gatewayClassName: istio
  listeners:
  - name: http
    port: 80
    protocol: HTTP
    allowedRoutes:
      namespaces:
        from: All
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata: 
  name: simple-http-route
spec:
  parentRefs:
    - name: simple-service-gateway
  rules:
    - matches:
      - headers:
          - name: user
            value: iot-qa
      backendRefs:
        - name: simple-service-qa
          port: 8080
    - backendRefs:
        - name: simple-service-stable
          port: 8080
          weight: 90
        - name: simple-service-canary
          port: 8080
          weight: 10
```

Berikut hasil percobaannya:

```bash
curl -H "user:iot-qa" $INGRESS_HOST/version
{"message":"version: test"}
for run in {1..10}; do curl http://$INGRESS_HOST/version; done 
{"message":"version: v1"}
{"message":"version: v1"}
{"message":"version: v1"}
{"message":"version: v2"}
{"message":"version: v1"}
{"message":"version: v1"}
{"message":"version: v1"}
{"message":"version: v1"}
{"message":"version: v1"}
{"message":"version: v1"}%
```

Berdasarkan hasil percobaan diatas, dengan menambahkan **headers** berupa “user:iot-qa”, kita dapat mengarahkan ***traffic*** ke **service** yang kita inginkan. Dapat dilihat juga, apabila kita tidak memberikan ***header*** pada ***request, gateway*** mengirimkan **traffic** sesuai dengan ***weight*** yang ada pada skrip k8s.
