Pada artikel Blocking IO Dan Non Blocking IO, penulis telah membahas mengenai implementasi non blocking IO pada node js. Pada artikel ini, penulis akan melakukan implementasi non blocking IO dengan menggunakan spring async. Contoh kasus yang penulis gunakan disini adalah melakukan call web service REST API.

Spring Async berfungsi untuk membuat suatu proses dijalankan secara asynchronous sehingga suatu proses dapat dijalankan tanpa harus menunggu proses yang lain selesai.

Setup Project

Silahkan akses start.spring.io untuk melakukan setup project spring. Kemudian silahkan lakukan setup seperti berikut

Screenshot from 2021-02-22 08-58-42.png

Lalu buka project tersebut dengan IDE yang anda gunakan misal nya seperti netbeans, eclipse atau Intellij IDEA. Buka file pom.xml lalu tambahkan library spring-security-oauth2 sehingga konfigurasi pom.xml menjadi

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>org.rizki.mufrizal.async</groupId>
    <artifactId>Belajar-Async</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Belajar-Async</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.3.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Development Project

Pada tulisan ini, penulis akan mencoba melakukan demo spring async dengan cara melakukan call http client. Web service yang akan penulis lakukan adalah sebuah REST API, dimana penulis akan menggunakan service album dan category yang terdapat pada Developer Spotify.

Ubah file application.properties menjadi application.yml yang terdapat di dalam folder resources. Kemudian masukkan konfigurasi berikut

security:
  oauth2:
    client:
      clientId: 1baff7d8d760442184bf7d49acd5477d
      clientSecret: b0c85b1291c64a19bca01cb14907a918
      accessTokenUri: https://accounts.spotify.com/api/token
      grantType: client_credentials

service:
  getAllNewRelease:
    url: https://api.spotify.com/v1/browse/new-releases
  getCategories:
    url: https://api.spotify.com/v1/browse/categories

Key diatas dapat kamu ambil dari halaman Developer Spotify dengan mengikuti ketentuan prosedur pada web tersebut. Selanjutnya silahkan buat sebuah package configuration lalu tambahkan java class AsyncConfiguration dengan code seperti berikut

package org.rizki.mufrizal.async.configuration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@Configuration
@EnableAsync
public class AsyncConfiguration {

    @Bean(name = "asyncTaskExecutor")
    public Executor asyncTaskExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setCorePoolSize(3);
        threadPoolTaskExecutor.setMaxPoolSize(3);
        threadPoolTaskExecutor.setQueueCapacity(50);
        threadPoolTaskExecutor.setThreadNamePrefix("async-task-");
        threadPoolTaskExecutor.initialize();
        return threadPoolTaskExecutor;
    }

}

Code diatas berfungsi untuk mendefinisikan bean asyncTaskExecutor, dimana penulis membuat sebuah thread pool dengan prefix async-task-, sehingga nanti nya kita dapat menggunakan prefix tersebut untuk memanggil thread pool nya, contoh penggunaan nya yaitu async-task-test atau async-task-sample.

Untuk mempermudah penggunaan security OAuth2 maka penulis menggunakan spring-security-oauth2. Silahkan buat sebuah class java RestClientConfiguration di dalam package configuration lalu tambahkan code seperti berikut.

package org.rizki.mufrizal.async.configuration;

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 org.springframework.security.oauth2.client.DefaultOAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
import org.springframework.web.client.RestTemplate;

@Configuration
@EnableOAuth2Client
public class RestClientConfiguration {

    @Autowired
    private Environment environment;

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @Bean
    protected OAuth2ProtectedResourceDetails oAuth2ProtectedResourceDetails() {
        ClientCredentialsResourceDetails clientCredentialsResourceDetails = new ClientCredentialsResourceDetails();
        clientCredentialsResourceDetails.setClientId(environment.getRequiredProperty("security.oauth2.client.clientId"));
        clientCredentialsResourceDetails.setClientSecret(environment.getRequiredProperty("security.oauth2.client.clientSecret"));
        clientCredentialsResourceDetails.setAccessTokenUri(environment.getRequiredProperty("security.oauth2.client.accessTokenUri"));
        clientCredentialsResourceDetails.setGrantType(environment.getRequiredProperty("security.oauth2.client.grantType"));
        return clientCredentialsResourceDetails;
    }

    @Bean
    public OAuth2RestOperations oAuth2RestOperations() {
        return new OAuth2RestTemplate(oAuth2ProtectedResourceDetails(), new DefaultOAuth2ClientContext(new DefaultAccessTokenRequest()));
    }

}

Pada code diatas, penulis mendefinisikan konfigurasi OAuth2 untuk client, sehingga penulis tidak perlu membuat code untuk memanggil service OAuth2 dikarenakan secara otomatis akan dipanggil oleh spring-security-oauth2, ini hanya berlaku jika OAuth2 yang digunakan adalah OAuth2 dengan standard yang sama atau dengan standard secara global.

Selanjutnya penulis membuat class - class mapper untuk dilakukan mapping antara request dan response. Silahkan buat sebuah package mapper lalu masukkan code berikut pada java class ItemMapper, ItemCategoryMapper, CategoriesMapper, CategoryMapper, AlbumsMapper dan AlbumMapper.

package org.rizki.mufrizal.async.mapper;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class ItemMapper implements Serializable {

    @JsonProperty("album_type")
    private String albumType;

    @JsonProperty("href")
    private String href;

    @JsonProperty("id")
    private String id;

    @JsonProperty("name")
    private String name;

    @JsonProperty("release_date")
    private String releaseDate;

    @JsonProperty("release_date_precision")
    private String releaseDatePrecision;

    @JsonProperty("total_tracks")
    private Double totalTracks;

    @JsonProperty("type")
    private String type;

    @JsonProperty("uri")
    private String uri;

}
package org.rizki.mufrizal.async.mapper;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class ItemCategoryMapper implements Serializable {

    @JsonProperty("href")
    private String href;

    @JsonProperty("id")
    private String id;

    @JsonProperty("name")
    private String name;

}
package org.rizki.mufrizal.async.mapper;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class CategoriesMapper implements Serializable {

    @JsonProperty("href")
    public String href;

    @JsonProperty("items")
    public List<ItemCategoryMapper> items;

    @JsonProperty("limit")
    public Double limit;

    @JsonProperty("next")
    public String next;

    @JsonProperty("offset")
    public String offset;

    @JsonProperty("previous")
    public String previous;

    @JsonProperty("total")
    public Double total;

}
package org.rizki.mufrizal.async.mapper;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class CategoryMapper implements Serializable {

    @JsonProperty("categories")
    public CategoriesMapper categoriesMapper;

}
package org.rizki.mufrizal.async.mapper;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class AlbumsMapper implements Serializable {

    @JsonProperty("href")
    private String href;

    @JsonProperty("items")
    private List<ItemMapper> items;

    @JsonProperty("limit")
    private Double limit;

    @JsonProperty("next")
    private String next;

    @JsonProperty("offset")
    private String offset;

    @JsonProperty("previous")
    private String previous;

    @JsonProperty("total")
    private Double total;

}
package org.rizki.mufrizal.async.mapper;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class AlbumMapper implements Serializable {

    @JsonProperty("albums")
    public AlbumsMapper albums;

}

Code - code diatas secara otomatis akan dibuatkan getter setter dengan bantuan lombok. Lalu selanjutnya kita akan masuk ke pembahasan spring async nya. Silahkan buat sebuah package httpclient lalu tambahkan sebuah class java SyncHttpClient dan tambahkan code berikut.

package org.rizki.mufrizal.async.httpclient;

import lombok.extern.slf4j.Slf4j;
import org.rizki.mufrizal.async.mapper.AlbumMapper;
import org.rizki.mufrizal.async.mapper.CategoryMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.concurrent.CompletableFuture;

@Service
@Slf4j
public class SyncHttpClient {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private OAuth2RestOperations oAuth2RestOperations;

    @Autowired
    private Environment environment;

    public CompletableFuture<AlbumMapper> getAlbum() {
        log.info("Start Get Album");

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        httpHeaders.set("Authorization", "Bearer " + oAuth2RestOperations.getAccessToken().getValue());
        HttpEntity<HttpHeaders> entity = new HttpEntity<>(httpHeaders);

        ResponseEntity<AlbumMapper> albumMapperResponseEntity = restTemplate
                .exchange(environment.getRequiredProperty("service.getAllNewRelease.url"), HttpMethod.GET, entity, AlbumMapper.class);
        log.info("End Get Album");
        return CompletableFuture.completedFuture(albumMapperResponseEntity.getBody());
    }

    public CompletableFuture<CategoryMapper> getCategory() {
        log.info("Start Get Category");

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        httpHeaders.set("Authorization", "Bearer " + oAuth2RestOperations.getAccessToken().getValue());
        HttpEntity<HttpHeaders> entity = new HttpEntity<>(httpHeaders);

        ResponseEntity<CategoryMapper> categoryMapperResponseEntity = restTemplate
                .exchange(environment.getRequiredProperty("service.getCategories.url"), HttpMethod.GET, entity, CategoryMapper.class);
        log.info("End Get Category");
        return CompletableFuture.completedFuture(categoryMapperResponseEntity.getBody());
    }

}

Kemudian buat sebuah class AsyncHttpClient dan masukkan code berikut

package org.rizki.mufrizal.async.httpclient;

import lombok.extern.slf4j.Slf4j;
import org.rizki.mufrizal.async.mapper.AlbumMapper;
import org.rizki.mufrizal.async.mapper.CategoryMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.concurrent.CompletableFuture;

@Service
@Slf4j
public class AsyncHttpClient {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private OAuth2RestOperations oAuth2RestOperations;

    @Autowired
    private Environment environment;

    @Async("asyncTaskExecutor")
    public CompletableFuture<AlbumMapper> getAlbum() {
        log.info("Start Get Album");

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        httpHeaders.set("Authorization", "Bearer " + oAuth2RestOperations.getAccessToken().getValue());
        HttpEntity<HttpHeaders> entity = new HttpEntity<>(httpHeaders);

        ResponseEntity<AlbumMapper> albumMapperResponseEntity = restTemplate
                .exchange(environment.getRequiredProperty("service.getAllNewRelease.url"), HttpMethod.GET, entity, AlbumMapper.class);
        log.info("End Get Album");
        return CompletableFuture.completedFuture(albumMapperResponseEntity.getBody());
    }

    @Async("asyncTaskExecutor")
    public CompletableFuture<CategoryMapper> getCategory() {
        log.info("Start Get Category");

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        httpHeaders.set("Authorization", "Bearer " + oAuth2RestOperations.getAccessToken().getValue());
        HttpEntity<HttpHeaders> entity = new HttpEntity<>(httpHeaders);

        ResponseEntity<CategoryMapper> categoryMapperResponseEntity = restTemplate
                .exchange(environment.getRequiredProperty("service.getCategories.url"), HttpMethod.GET, entity, CategoryMapper.class);
        log.info("End Get Category");
        return CompletableFuture.completedFuture(categoryMapperResponseEntity.getBody());
    }

}

Dari kedua code diatas tidak ada perbedaan, hanya terdapat perbedaan yaitu di class AsyncHttpClient setiap method nya menggunakan annotation @Async dengan nama bean nya asyncTaskExecutor. Dari segi code memang sama dan tidak ditemukan perbedaan nya sehingga kita tidak dapat mengetahui apakan proses tersebut dilakukans secara sync atau async.

Test Async Dan Sync

Pada tulisan ini, penulis akan menggunakan library bawaan spring boot untuk melakukan unit test yaitu spring-boot-starter-test. Silahkan buka code BelajarAsyncApplicationTests di dalam package test lalu tambahkan code berikut

package org.rizki.mufrizal.async;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.rizki.mufrizal.async.httpclient.AsyncHttpClient;
import org.rizki.mufrizal.async.httpclient.SyncHttpClient;
import org.rizki.mufrizal.async.mapper.AlbumMapper;
import org.rizki.mufrizal.async.mapper.CategoryMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

@SpringBootTest
@Slf4j
class BelajarAsyncApplicationTests {

    @Autowired
    private AsyncHttpClient asyncHttpClient;

    @Autowired
    private SyncHttpClient syncHttpClient;

    @Test
    void asyncHttpClientTest() throws ExecutionException, InterruptedException, JsonProcessingException {
        log.info("Start Test Async Test");
        CompletableFuture<AlbumMapper> albumMapperResponseMapperCompletableFuture = asyncHttpClient.getAlbum();
        CompletableFuture<CategoryMapper> categoryMapperCompletableFuture = asyncHttpClient.getCategory();

        log.info("Result Get Album {}", new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(albumMapperResponseMapperCompletableFuture.get()));
        log.info("Result Get Category {}", new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(categoryMapperCompletableFuture.get()));
        log.info("End Test Async Test");
    }

    @Test
    void syncHttpClientTest() throws ExecutionException, InterruptedException, JsonProcessingException {
        log.info("Start Test Sync Test");
        CompletableFuture<AlbumMapper> albumMapperResponseMapperCompletableFuture = syncHttpClient.getAlbum();
        CompletableFuture<CategoryMapper> categoryMapperCompletableFuture = syncHttpClient.getCategory();

        log.info("Result Get Album {}", new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(albumMapperResponseMapperCompletableFuture.get()));
        log.info("Result Get Category {}", new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(categoryMapperCompletableFuture.get()));
        log.info("End Test Sync Test");
    }

}

Kemudian jalankan test nya dengan perintah

mvn clean install

Pada terminal akan muncul log seperti berikut untuk proses async nya

2021-02-22 13:46:33.335  INFO 48219 --- [           main] o.r.m.a.BelajarAsyncApplicationTests     : Start Test Async Test
2021-02-22 13:46:33.349  INFO 48219 --- [   async-task-1] o.r.m.async.httpclient.AsyncHttpClient   : Start Get Album
2021-02-22 13:46:33.349  INFO 48219 --- [   async-task-2] o.r.m.async.httpclient.AsyncHttpClient   : Start Get Category
2021-02-22 13:46:34.003  INFO 48219 --- [   async-task-2] o.r.m.async.httpclient.AsyncHttpClient   : End Get Category
2021-02-22 13:46:34.063  INFO 48219 --- [   async-task-1] o.r.m.async.httpclient.AsyncHttpClient   : End Get Album
2021-02-22 13:46:34.088  INFO 48219 --- [           main] o.r.m.a.BelajarAsyncApplicationTests     : End Test Async Test

sedangkan untuk proses sync nya

2021-02-22 13:46:34.100  INFO 48219 --- [           main] o.r.m.a.BelajarAsyncApplicationTests     : Start Test Sync Test
2021-02-22 13:46:34.101  INFO 48219 --- [           main] o.r.m.async.httpclient.SyncHttpClient    : Start Get Album
2021-02-22 13:46:34.226  INFO 48219 --- [           main] o.r.m.async.httpclient.SyncHttpClient    : End Get Album
2021-02-22 13:46:34.226  INFO 48219 --- [           main] o.r.m.async.httpclient.SyncHttpClient    : Start Get Category
2021-02-22 13:46:34.299  INFO 48219 --- [           main] o.r.m.async.httpclient.SyncHttpClient    : End Get Category
2021-02-22 13:46:34.314  INFO 48219 --- [           main] o.r.m.a.BelajarAsyncApplicationTests     : End Test Sync Test

Dari log diatas dapat dilihat bahwa proses async berjalan dengan menggunakan thread masing - masing, dapat dilihat thread nya yaitu async-task-1 dan async-task-2 sedangkan pada proses sync hanya menggunakan thread yang sama yaitu main thread. Untuk proses async dapat berjalan secara independent sehingga proses call http client dapat berjalan secara bersamaan, sedangkan pada proses sync, call http client akan dijalankan secara sequence. Untuk source code diatas dapat anda akses di Belajar-Async. Sekian artikel mengenai belajar spring async jika ada saran dan komentar silahkan isi dibawah dan terima kasih :).

Aplikasi - aplikasi yang telah berjalan pada zaman sekarang biasa nya dibuat dalam bentuk blocking. Contoh yang sangat sederhana adalah aplikasi yang dibangun dengan menggunakan spring webmvc, dimana aplikasi ini masih menggunakan konsep blocking. Sebelum penulis membahas mengenai blocking dan non blocking, maka penulis akan membahas terlebih dahulu mengenai thread dikarenakan blocking dan non blocking sangat berkaitan dengan thread.

Apa Itu Thread ?

Thread adalah rangkain eksekusi dari sebuah aplikasi, biasa nya setiap aplikasi akan memiliki thread, minimal 1 thread.

Contoh yang paling sederhana dapat dilihat dari sebuah aplikasi web. Setiap request yang masuk ke dalam sebuah aplikasi web, maka akan membentuk sebuah koneksi. Koneksi yang dibentuk karena sebuah request akan dijadikan 1 thread pada aplikasi web tersebut. Berikut merupakan sample thread jika diilustrasikan pada sebuah aplikasi web.

sample-thread-web.png

Dari gambar diatas, dapat dilihat terdapat request yang masuk secara bersamaan dari pengguna 1 dan pengguna 2. Masing - masing pengguna akan dibuatkan sebuah thread atau istilahnya multithread, dimana masing - masing proses akan dijalankan secara independent sehingga tidak ada proses tunggu menunggu antara kedua request tersebut.

Mungkin ada yang bertanya - tanya, apa perbedaan nya antara multithread dan multicore ? dan apakah dengan komputer single core dapat menggunakan multithread ?. Secara arsitektur, 1 core mempunya beberapa thread, biasanya 1 core memiliki 2 thread. Berikut jika digambarkan arsitektur dari core dan thread.

core-thread.png

Jika anda menggunakan linux, kita dapat melakukan pengecekan core dan thread dengan perintah

lscpu

Kemudian lihat pada bagian Thread(s) per core yang menandakan jumlah thread per core dan Core(s) per socket yang menandakan jumlah core dari processor. berikut contohnya

Architecture:                    x86_64
CPU op-mode(s):                  32-bit, 64-bit
Byte Order:                      Little Endian
Address sizes:                   43 bits physical, 48 bits virtual
CPU(s):                          16
On-line CPU(s) list:             0-15
Thread(s) per core:              2
Core(s) per socket:              8
Socket(s):                       1
NUMA node(s):                    1
Vendor ID:                       AuthenticAMD
CPU family:                      23
Model:                           8
Model name:                      AMD Ryzen 7 2700X Eight-Core Processor

Dari informasi diatas dapat dilihat bahwa cpu nya memiliki 8 core dan setiap core memiliki 2 thread sehingga total nya ada 16 thread.

Apa Itu Thread pool Dan Task Queue ?

Setiap menjalankan 1 thread, biasa nya pada komputer dengan arsitektur x64 membutuhkan memory sebanyak 1MB. Bayangkan jika thread tidak di manage dengan baik maka dapat dipastikan resource sebuah komputer akan habis dan aplikasi tersebut tidak dapat berjalan.

Thread Pool merupakan sebuah management thread yang dapat digunakan untuk memanage thread dari sebuah aplikasi.

Dengan ada nya thread pool ini, kita dapat mendefinisikan berapa maksimal thread yang akan dibuat, minimal thread, idle dari setiap thread dan lain sebagainya. Jika thread pool nya penuh, bagaimana dengan thread baru selanjutnya ?. Thread baru selanjutnya akan dibuat di dalam sebuah antrian dengan nama task queue.

Task Queue berisi thread antrian jika jumlah thread pada thread pool telah penuh.

Task queue atau istilah lain nya yaitu backlog jika disisi http port. Task queue atau backlog juga dapat dibatasi, jika melebihi dari yang ditentukan maka task tersebut tidak akan masuk ke dalam sebuah task queue. Berikut merupakan gambar dari arsitektur thread pool dan task queue.

thread-pool-task-queue.png

Apa Itu Blocking IO ?

Blocking IO adalah sebuah proses akan dijalankan jika proses sebelum nya telah selesai dijalankan atau istilah lain nya yaitu synchronous

Blocking IO diadopsi oleh hampir semua web server dan web framework seperti apache tomcat, jetty, dan lain sebagai nya. Blocking IO sendiri mengadopsi teknologi thread pool dan task queue sehingga proses dijalankan jika terdapat thread yang sedang idle atau tidak digunakan. Berikut contoh arsitektur blocking IO jika diimplementasikan ke dalam sebuah web server.

blocking-io.png

Dari gambar diatas, 1 request yang datang dari client dianggap sebagai sebuah thread, dimana 1 thread ini juga mewakili dari sebuah connection. Backlog pada gambar diatas berfungsi untuk menampung request jika thread yang terdapat pada thread pool sedang penuh atau istilahnya request tersebut akan diantrikan di dalam backlog tersebut. Jika thread yang terdapat pada thread pool tersebut ada yang telah direlease atau dilepas maka request yang terdapat pada backlog akan dimasukkan ke dalam thread pool. Misal maksimal kapasitas thread pool kita setting di angka 500, dan backlog di setting di angka 200. Request yang datang dari client sebanyak 1000 request maka 500 request akan di process secara bersamaan, 200 request akan dimasukkan ke dalam antrian sedangkan 300 request lagi akan di reject karena melebihi kapasitas dari backlog yang telah ditentukan. Dari proses diatas, response server biasa nya terlihat sangat lambat dikarenakan adanya antrian dan harus menunggu proses yang sebelum nya. Contoh framework yang menggunakan blocking IO adalah spring webmvc, ruby on rails, laravel dan lain sebagain nya.

Apa Itu Non Blocking IO ?

Non Blocking IO adalah sebuah proses akan dijalankan tanpa harus menunggu proses lain selesai atau istilah lain nya yaitu asynchronous.

Berbeda dengan blocking IO, proses pada non blocking IO dapat berjalan tanpa harus menunggu proses sebelum nya selesai sehingga non blocking io dapat berjalan pada single thread. Jika pada aplikasi web, semua request dapat ditampung tanpa ada proses thread pool dan task queue sehingga response server terlihat sangat cepat. Berikut contoh arsitektur Non blocking IO jika diimplementasikan ke dalam sebuah web server.

non-blocking-io.png

Dari gambar diatas, semua request akan diterima oleh event loop atau istilahnya diterima oleh thread bos. Semua request tersebut langsung diteruskan ke worker, jika worker tersebut lambat maka dapat dipastikan response juga lambat. Pada proses non blocking ini, cepat atau lambat nya sebuah aplikasi bergantung dari cara melakukan pemrograman dari seorang developer.

Contoh Implementasi Blocking IO Dan Non Blocking IO

Pada artikel ini, penulis akan memberikan contoh implementasi blocking IO dan non blocking IO dengan menggunakan node js. Bagi kamu yang belum tau apa itu node js, silahkan simak artikel Instalasi Perlengkapan Coding Node JS.

Implementasi Blocking IO

Silkahkan buat sebuah file data.txt lalu isi dengan text berikut.

Contoh Blocking IO dan Non Blocking IO dengan menggunakan node js

Lalu buat sebuah file blocking-io.js lalu isi dengan codingan berikut

const fs = require('fs');

console.log('start');

const dataFromFile = fs.readFileSync('data.txt');
console.log(dataFromFile.toString());

console.log('End');

Kemudian jalankan dengan perintah node blocking-io.js maka akan muncul hasil

start
Contoh Blocking IO dan Non Blocking IO dengan menggunakan node js
End

Dari codingan diatas, penulis menggunakan module fs untuk membaca sebuah file. Dapat dilihat, pada codingan diatas penulis menggunakan fungsi readFileSync, dimana fungsi tersebut berfungsi untuk membaca file dengan metode sync atau blocking IO sehingga proses diatas berjalan secara sequence. Jika proses readFileSync belum selesai maka console.log(‘End’) tidak akan dijalankan sampai proses readFileSync selesai.

Implementasi Non Blocking IO

Silahkan buat sebuah file non-blocking-io.js lalu isi dengan codingan berikut

const fs = require('fs');

console.log('start');

fs.readFile('data.txt', function (error, data) {
    if(error) console.log(error);
    console.log(data.toString());
});

console.log('End');

Kemudian jalankan dengan perintah node non-blocking-io.js maka akan muncul hasil

start
End
Contoh Blocking IO dan Non Blocking IO dengan menggunakan node js

Dari codingan diatas dapat dilihat perbedaannya yaitu pada non blocking IO, penulis menggunakan fungsi readFile dimana fungsi tersebut berfungsi untuk membaca file dengan metode async atau non blocking IO. Dikarenakan proses read file lama maka proses console.log(‘End’) dijalankan terlebih dahulu pada implementasi non blocking IO. Pada non blocking IO, semua proses akan dijalankan tapi tidak seperti blocking IO dimana proses selanjutnya tidak berpengaruh jika proses sebelum nya masih belum selesai.

Dalam dunia programming, setiap developer pasti akan membuat aplikasi yang dapat berjalan secara concurrent dan parallel. Salah satu contohnya yaitu sebuah web application. Web application merupakan salah satu contoh dari penerapan concurrent dan parallel, hal ini dapat dilihat jika web application tersebut dapat melakukan processing untuk setiap pengguna yang berbeda. Proses concurrent dan parallel biasa nya digunakan secara bersamaan untuk mendapatkan hasil terbaik dan efisien. Pada zaman sekarang, semua bahasa pemrograman sudah mendukung proses yang berjalan secara concurrent dan parallel, contohnya seperti bahasa pemrograman java, golang, php dan lain sebagainya.

Apa Itu Concurrent ?

Concurrent adalah sebuah proses yang dijalankan secara sekaligus atau sebuah proses yang dapat melakukan beberapa hal secara sekaligus

Dari pengertian diatas dapat diartikan bahwa concurrent tidak dijalankan secara bersamaan akan tetapi dilakukan secara sekaligus. Contoh di dalam kehidupan nyata adalah ketika seseorang bersepeda, lalu tali sepatu nya terlepas maka orang tersebut memperbaiki tali sepatu kemudian bersepeda kembali. Pada contoh diatas, terdapat 2 pengerjaan yaitu bersepeda dan memperbaiki tali sepatu, dimana pengerjaan diatas dapat dilakukan secara sekaligus tetapi tidak dilakukan secara parallel. Berikut merupakan gambar concurrent.

Concurrent.png

Apa Itu Parallel ?

Parallel adalah beberapa proses yang dilakukan secara bersamaan

Parallel dapat melakukan sebuah proses secara bersamaan, contoh nya adalah misal ada 2 orang yang bersepeda, mereka sama - sama melakukan proses yang bersamaan yaitu bersepeda tetapi dengan pengguna yang berbeda. Contoh lain nya adalah misal seseorang sedang bersepeda, ketika dia bersepeda, dia juga mendengarkan musik. Pada kasus yang kedua, orang tersebut melakukan aktifitas bersamaan yaitu bersepeda dan mendengarkan musik. Berikut merupakan gambar parallel.

Parallel.png

Implementasi Concurrent Pada Java Programming

Silahkan buat sebuah file dengan nama Concurrent.java lalu sisipkan code berikut

/**
 *
 * @author rizki mufrizal
 */
public class Concurrent {

    static int calculate(int a, int b) {
        return a + b;
    }

    static void print(int result) {
        System.out.println("Result " + result);
    }

    public static void main(String[] args) {
        int a = 1;
        int b = 1;
        int calculate = calculate(a, b);
        print(calculate);
    }
}

Dari contoh diatas, dapat dilihat yang pertama kali dilakukan adalah task calculate angka dari variable a dan variable b, lalu kemudian dijalankan task print. Concurrent juga dapat dilakukan dengan melakukan implementasi multi thread karena pada dasar nya concurrent biasa nya menggunakan single core dengan banyak thread sedangkan pada parallel akan menggunakan multi core processor.

Implementasi Parallel Pada Java Programming

Silahkan buat sebuah file dengan nama Parallel.java lalu sisipkan code berikut

import java.util.stream.IntStream;

/**
 *
 * @author rizki
 */
public class Parallel {

    public static void main(String[] args) {
        System.out.println("Normal Iterasi");
        IntStream iterasiNormal = IntStream.rangeClosed(1, 10);
        System.out.println("Is Parallel : " + iterasiNormal.isParallel());
        iterasiNormal.forEach(i -> System.out.println("Normal Iterasi : " + i));

        System.out.println("Parallel Iterasi");
        IntStream iterasiParallel = IntStream.rangeClosed(1, 10).parallel();
        System.out.println("Is Parallel : " + iterasiParallel.isParallel());
        iterasiParallel.forEach(i -> System.out.println("Parallel Iterasi : " + i));
    }

}

Pada saat code java diatas dijalankan maka akan muncul output seperti berikut

Normal Iterasi
Is Parallel : false
Normal Iterasi : 1
Normal Iterasi : 2
Normal Iterasi : 3
Normal Iterasi : 4
Normal Iterasi : 5
Normal Iterasi : 6
Normal Iterasi : 7
Normal Iterasi : 8
Normal Iterasi : 9
Normal Iterasi : 10

Parallel Iterasi
Is Parallel : true
Parallel Iterasi : 7
Parallel Iterasi : 6
Parallel Iterasi : 8
Parallel Iterasi : 1
Parallel Iterasi : 4
Parallel Iterasi : 2
Parallel Iterasi : 9
Parallel Iterasi : 5
Parallel Iterasi : 10
Parallel Iterasi : 3

Hasil dari percobaan diatas dapat dilihat bahwa jika sebuah proses tidak dilakukan dengan parallel maka iterasi yang dihasilkan adalah iterasi sequence atau berurutan. Pada saat dijalankan secara parallel maka iterasi yang dihasilkan dapat berbagai macam dan tidak sequence atau berurutan. Pada sample code diatas, penulis menggunakan java 8 stream yaitu IntStream, dimana class ini dapat digunakan untuk melakukan proses secara parallel maupun non parallel.

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 :).